Get Multiple rows from multiple values (columns) in SQL Server 2008 R2 - sql

I have this table
+-----+------+--------+--------+
| ID | Name | Start | End |
+-----+------+--------+--------+
| 20 | Mike | 1 | 3 |
| 21 | Luke | 4 | 7 |
+-----+------+--------+--------+
And I want to generate all rows based on the range (start / end) of each person.
The outcome should be this
+-----+------+-----------------+
| ID | Name | Start_End |
+-----+------+-----------------+
| 20 | Mike | 1 |
| 20 | Mike | 2 |
| 20 | Mike | 3 |
| 21 | Luke | 4 |
| 21 | Luke | 5 |
| 21 | Luke | 6 |
| 21 | Luke | 7 |
+-----+------+--------+--------+
To get unique values based on Start and End column, I have this function
CREATE FUNCTION [dbo].[ufn_SplitRange] (#Start INT, #End INT)
RETURNS TABLE
AS
RETURN
(
SELECT TOP (#End - #Start+1) ROW_NUMBER() OVER (ORDER BY S.[object_id])+(#Start - 1) [Start_End]
FROM sys.all_objects S WITH (NOLOCK)
);
The above function returns the output of (based on Mike range of 1-3):
1
2
3
I have been trying several approaches and, I can't find the right solution, it seems a very common task, but a tricky one.
Any input is highly appreciated

using cross apply():
select t.Id, t.Name, x.Start_End
from t
cross apply dbo.ufn_SplitRange(t.Start,t.[End]) as x
rextester demo: http://rextester.com/FVA48693
returns:
+----+------+-----------+
| Id | Name | Start_End |
+----+------+-----------+
| 20 | Mike | 1 |
| 20 | Mike | 2 |
| 20 | Mike | 3 |
| 21 | Luke | 4 |
| 21 | Luke | 5 |
| 21 | Luke | 6 |
| 21 | Luke | 7 |
+----+------+-----------+

You can use tally table as below:
Select Id, Name, Start_end from #Values
cross apply (
Select top ([end] - [start] +1) Start_end = [start] + Row_number() over (order by (Select NULL))-1
from master..spt_values s1, master..spt_values s2
) a
Output :
+----+------+----+
| Id | Name | RN |
+----+------+----+
| 20 | Mike | 1 |
| 20 | Mike | 2 |
| 20 | Mike | 3 |
| 21 | Luke | 4 |
| 21 | Luke | 5 |
| 21 | Luke | 6 |
| 21 | Luke | 7 |
+----+------+----+

You could use recursive cte like this
DECLARE #SampleData AS TABLE
(
Id int,
Name varchar(10),
Start int,
[End] int
)
INSERT INTO #SampleData
(
Id,
Name,
Start,
[End]
)
VALUES
(1,'Mike',1,3),
(2,'Luke',4,7)
;WITH temp AS
(
SELECT Id, sd.Name, sd.Start , sd.[End]
FROM #SampleData sd
UNION ALL
SELECT t.Id, t.Name, t.Start + 1, t.[End]
FROM temp t
WHERE t.Start < t.[End]
)
SELECT t.Id, t.Name, t.Start AS [Start_End]
FROM temp t
ORDER BY t.Id
OPTION (MAXRECURSION 0)
Demo link: http://rextester.com/AFNYFW81782

Related

TSQL - Referencing a changed value from previous row

I am trying to do a row calculation whereby the larger value will carry forward to the subsequent rows until a larger value is being compared. It is done by comparing the current value to the previous row using the lag() function.
Code
DECLARE #TAB TABLE (id varchar(1),d1 INT , d2 INT)
INSERT INTO #TAB (id,d1,d2)
VALUES ('A',0,5)
,('A',1,2)
,('A',2,4)
,('A',3,6)
,('B',0,4)
,('B',2,3)
,('B',3,2)
,('B',4,5)
SELECT id
,d1
,d2 = CASE WHEN id <> (LAG(id,1,0) OVER (ORDER BY id,d1)) THEN d2
WHEN d2 < (LAG(d2,1,0) OVER (ORDER BY id,d1)) THEN (LAG(d2,1,0) OVER (ORDER BY id,d1))
ELSE d2 END
Output (Added row od2 for clarity)
+----+----+----+ +----+
| id | d1 | d2 | | od2|
+----+----+----+ +----+
| A | 0 | 5 | | 5 |
| A | 1 | 5 | | 2 |
| A | 2 | 4 | | 4 |
| A | 3 | 6 | | 6 |
| B | 0 | 4 | | 4 |
| B | 2 | 4 | | 3 |
| B | 3 | 3 | | 2 |
| B | 4 | 5 | | 5 |
+----+----+----+ +----+
As you can see from the output it lag function is referencing the original value of the previous row rather than the new value. Is there anyway to achieve this?
Desired Output
+----+----+----+ +----+
| id | d1 | d2 | | od2|
+----+----+----+ +----+
| A | 0 | 5 | | 5 |
| A | 1 | 5 | | 2 |
| A | 2 | 5 | | 4 |
| A | 3 | 6 | | 6 |
| B | 0 | 4 | | 4 |
| B | 2 | 4 | | 3 |
| B | 3 | 4 | | 2 |
| B | 4 | 5 | | 5 |
+----+----+----+ +----+
Try this:
SELECT id
,d1
,d2
,MAX(d2) OVER (PARTITION BY ID ORDER BY d1)
FROM #TAB
The idea is to use the MAX to get the max value from the beginning to the current row for each partition.
Thanks for providing the DDL scripts and the DML.
One way of doing it would be using recursive cte as follows.
1. First rank all the records according to id, d1 and d2. -> cte block
2. Use recursive cte and get the first elements using rnk=1
3. the field "compared_val" will check against the values from the previous rnk to see if the value is > than the existing and if so it would swap
DECLARE #TAB TABLE (id varchar(1),d1 INT , d2 INT)
INSERT INTO #TAB (id,d1,d2)
VALUES ('A',0,5)
,('A',1,2)
,('A',2,4)
,('A',3,6)
,('B',0,4)
,('B',2,3)
,('B',3,2)
,('B',4,5)
;with cte
as (select row_number() over(partition by id order by d1,d2) as rnk
,id,d1,d2
from #TAB
)
,data(rnk,id,d1,d2,compared_val)
as (select rnk,id,d1,d2,d2 as compared_val
from cte
where rnk=1
union all
select a.rnk,a.id,a.d1,a.d2,case when b.compared_val > a.d2 then
b.compared_val
else a.d2
end
from cte a
join data b
on a.id=b.id
and a.rnk=b.rnk+1
)
select * from data order by id,d1,d2

How to get values of rows and columns

I have two tables.
Student Table
Property Table
Result Table
How can I get the value of Student Table and the property ID of the column fron the Property table and merge that into the Result table?
Any advice would be helpful.
Update #1:
I tried using Christian Moen 's suggestion, this is what i get.
You need to UNPIVOT the Student's columns first, to get the columns (properties names) in one column as rows. Then join with the Property table based on the property name like this:
WITH UnPivoted
AS
(
SELECT ID, value,col
FROM
(
SELECT ID,
CAST(Name AS NVARCHAR(50)) AS Name,
CAST(Class AS NVARCHAR(50)) AS Class,
CAST(ENG AS NVARCHAR(50)) AS ENG,
CAST(TAM AS NVARCHAR(50)) AS TAM,
CAST(HIN AS NVARCHAR(50)) AS HIN,
CAST(MAT AS NVARCHAR(50)) AS MAT,
CAST(PHY AS NVARCHAR(50)) AS PHY
FROM Student
) AS s
UNPIVOT
(value FOR col IN
([Name], [class], [ENG], [TAM], [HIN], [MAT], [PHY])
)AS unpvt
)
SELECT
ROW_NUMBER() OVER(ORDER BY u.ID,PropertyID) AS ID,
p.PropertyID,
u.Value,
u.ID AS StudID
FROM Property AS p
INNER JOIN UnPivoted AS u ON p.PropertyName = u.col;
For the first ID, I used the ranking function ROW_NUMBER() to generate this sequence id.
This will give the exact results that you are looking for.
Results:
| ID | PropertyID | Value | StudID |
|----|------------|--------|--------|
| 1 | 1 | Jack | 1 |
| 2 | 2 | 10 | 1 |
| 3 | 3 | 89 | 1 |
| 4 | 4 | 88 | 1 |
| 5 | 5 | 45 | 1 |
| 6 | 6 | 100 | 1 |
| 7 | 7 | 98 | 1 |
| 8 | 1 | Jill | 2 |
| 9 | 2 | 10 | 2 |
| 10 | 3 | 89 | 2 |
| 11 | 4 | 99 | 2 |
| 12 | 5 | 100 | 2 |
| 13 | 6 | 78 | 2 |
| 14 | 7 | 91 | 2 |
| 15 | 1 | Trevor | 3 |
| 16 | 2 | 12 | 3 |
| 17 | 3 | 100 | 3 |
| 18 | 4 | 50 | 3 |
| 19 | 5 | 49 | 3 |
| 20 | 6 | 94 | 3 |
| 21 | 7 | 100 | 3 |
| 22 | 1 | Jim | 4 |
| 23 | 2 | 8 | 4 |
| 24 | 3 | 100 | 4 |
| 25 | 4 | 91 | 4 |
| 26 | 5 | 92 | 4 |
| 27 | 6 | 100 | 4 |
| 28 | 7 | 100 | 4 |
Other option is to use of apply if you don't want to go unpivot way
select row_number() over (order by (select 1)) ID, p.PropertyID [PropID], a.Value, a.StuID
from Student s
cross apply
(
values (s.ID, 'Name', s.Name),
(s.ID, 'Class', cast(s.Class as varchar)),
(s.ID, 'ENG', cast(s.ENG as varchar)),
(s.ID, 'TAM', cast(s.TAM as varchar)),
(s.ID, 'HIN', cast(s.HIN as varchar)),
(s.ID, 'MAT', cast(s.MAT as varchar)),
(s.ID, 'PHY', cast(s.PHY as varchar))
) as a(StuID, Property, Value)
join Property p on p.PropertyName = a.Property

How to Find Items that Do NOT Have a pre-Pipe "Base" Value

I have a database with a column (obj_id) in a table (parts) where I SHOULD have an obj_id of 12345 that is a set for another row that would have 12345|.
So:
select obj_id from parts where obj_id like '12345%';
12345
12345|A
12345|B
12345|77
Now, someone violated the guideline and put in some items with the piped-value but not the base value w/o the pipe (e.g. 12378|J, 12378|8 but not 12378).
I need to know how to write a SQL query to find these piped-values that do NOT have their matching base (non-piped) value in the table.
Without some realistic sample data to work with it's hard to know what you really want. Below a 2 queries that may be of assistance, but perhaps it will also make you note how useful sample data can be:
See this working at SQL Fiddle
CREATE TABLE PARTS
(id int, OBJ_ID varchar2(200))
;
INSERT ALL
INTO PARTS (id, OBJ_ID)
VALUES (1,'12345 12345|A 12345|B 12345|77')
INTO PARTS (id, OBJ_ID)
VALUES (2,'12346|A 12346|B 12346|77')
INTO PARTS (id, OBJ_ID)
VALUES (3,'12378|J, 12378|8')
INTO PARTS (id, OBJ_ID)
VALUES (4,NULL)
INTO PARTS (id, OBJ_ID)
VALUES (5,'fred. wilma, barney, betty')
SELECT * FROM dual
;
Query 1:
select
*
from PARTS p
where instr(p.OBJ_ID,' ') > instr(p.OBJ_ID,'|')
Results:
| ID | OBJ_ID |
|----|----------------------------|
| 2 | 12346|A 12346|B 12346|77 |
| 3 | 12378|J, 12378|8 |
| 5 | fred. wilma, barney, betty |
Query 2:
select
id, rn_a, regexp_substr (OBJ_ID_SPLIT, '[^|]+', 1, rn_b) as OBJ_ID_SPLIT
from (
select
p.id, c1.rn_a, regexp_substr (p.OBJ_ID, '[^ ]+', 1, c1.rn_a) as OBJ_ID_SPLIT
from PARTS p
cross join (select rownum as rn_a
from (select max(length (regexp_replace (OBJ_ID, '[^|]+'))) + 1 as mx
from PARTS
)
connect by level <= mx) c1
where p.OBJ_ID like '%|%'
) d
cross join (select 1 rn_b from dual union all select 2 from dual) c2
order by id, rn_a
Results:
| ID | RN_A | OBJ_ID_SPLIT |
|----|------|--------------|
| 1 | 1 | 12345 |
| 1 | 1 | (null) |
| 1 | 2 | 12345 |
| 1 | 2 | A |
| 1 | 3 | 12345 |
| 1 | 3 | B |
| 1 | 4 | 12345 |
| 1 | 4 | 77 |
| 2 | 1 | 12346 |
| 2 | 1 | A |
| 2 | 2 | 12346 |
| 2 | 2 | B |
| 2 | 3 | 12346 |
| 2 | 3 | 77 |
| 2 | 4 | (null) |
| 3 | 1 | 12378 |
| 3 | 1 | J, |
| 3 | 2 | 12378 |
| 3 | 2 | 8 |
| 3 | 3 | (null) |
| 3 | 4 | (null) |

How do I select each data set from a Row_Number Over Partition by table based on the Row_Number Over Partition by column?

How do I select each data set from a Row_Number Over Partition by table based on the Row_Number Over Partition by column?
please diagram below:
+-----------+-------------+-------------------+------------+----------+
| packageid | packagename | package max units | references | row_Numb |
+-----------+-------------+-------------------+------------+----------+
| 44 | Basic | 10 | 103 | 1 |
| 45 | Basic | 10 | 103 | 2 |
| 42 | Cola | 10 | 102 | 1 |
| 43 | Cola | 10 | 102 | 2 |
| 46 | Cola | 10 | 102 | 3 |
| 2 | Home | 11 | 101 | 1 |
| 11 | Home | 11 | 101 | 2 |
| 21 | Home | 11 | 101 | 3 |
| 1 | Spicy | 11 | 104 | 1 |
| 3 | Spicy | 11 | 104 | 2 |
| 41 | Spicy | 11 | 104 | 3 |
+-----------+-------------+-------------------+------------+----------+
I want select each data set in each group based on the row_num column.
Every attempt is welcomed.
Although it sounds like you already have the ROW_NUMBER() column, I believe it is what you are asking for . For the first record for each PACKAGENAME use:
SELECT s.* FROM (
SELECT t.*,
ROW_NUMBER() OVER(PARTITION BY t.packagename ORDER BY t.packageid) as rnk
FROM YourTable t) s
WHERE s.rnk = 1
For all of them use only the inner query.
Here is the cte version, if you want to fetch single record from each group.
;with cte_1
as(
SELECT *,
ROW_NUMBER() OVER(PARTITION BY [packagename],[package max units], [references] ORDER BY [packageid]) as row_Numb
FROM YourTable )
SELECT [packageid],[packagename],[package max units],[reference]
FROM cte_1
WHERE row_Numb = 1
You can use TOP 1 WITH TIES with ordering by ROW_NUMBER():
SELECT TOP 1 WITH TIES *
FROM YourTable
ORDER BY ROW_NUMBER() OVER (PARTITION BY packagename ORDER BY packageid)
Output:
packageid packagename package max units references
44 Basic 10 103
42 Cola 10 102
2 Home 11 101
1 Spicy 11 104

group by top two results based on order

I have been trying to get this to work with some row_number, group by, top, sort of things, but I am missing some fundamental concept. I have a table like so:
+-------+-------+-------+
| name | ord | f_id |
+-------+-------+-------+
| a | 1 | 2 |
| b | 5 | 2 |
| c | 6 | 2 |
| d | 2 | 1 |
| e | 4 | 1 |
| a | 2 | 3 |
| c | 50 | 4 |
+-------+-------+-------+
And my desired output would be:
+-------+---------+--------+-------+
| f_id | ord_n | ord | name |
+-------+---------+--------+-------+
| 2 | 1 | 1 | a |
| 2 | 2 | 5 | b |
| 1 | 1 | 2 | d |
| 1 | 2 | 4 | e |
| 3 | 1 | 2 | a |
| 4 | 1 | 50 | c |
+-------+---------+--------+-------+
Where data is ordered by the ord value, and only up to two results per f_id. Should I be working on a Stored Procedure for this or can I just do it with SQL? I have experimented with some select TOP subqueries, but nothing has even come close..
Here are some statements to create the test table:
create table help(name varchar(255),ord tinyint,f_id tinyint);
insert into help values
('a',1,2),
('b',5,2),
('c',6,2),
('d',2,1),
('e',4,1),
('a',2,3),
('c',50,4);
You may use Rank or DENSE_RANK functions.
select A.name, A.ord_n, A.ord , A.f_id from
(
select
RANK() OVER (partition by f_id ORDER BY ord asc) AS "Rank",
ROW_NUMBER() OVER (partition by f_id ORDER BY ord asc) AS "ord_n",
help.*
from help
) A where A.rank <= 2
Sqlfiddle demo