How to split rows recursively - sql-server

I've following table:
Id CreationDate FromEntryNo ToEntryNo
1 2013-01-01 1 4
2 2013-01-03 5 8
3 2013-01-05 9 11
...
I want to split this into multiple rows to have a list with all consecutive EntryNo, something like this:
Id CreationDate FromEntryNo ToEntryNo EntryNo
1 2013-01-01 1 4 1
1 2013-01-01 1 4 2
1 2013-01-01 1 4 3
1 2013-01-01 1 4 4
2 2013-01-03 5 8 5
2 2013-01-03 5 8 6
2 2013-01-03 5 8 7
2 2013-01-03 5 8 8
3 2013-01-05 9 11 9
3 2013-01-05 9 11 10
3 2013-01-05 9 11 11
...
My first attempt is CTE with recursion, but it doesn't work:
with cte as
(select gr.Id, gr.CreationDate, gr.FromEntryNo, gr.ToEntryNo, gr.FromEntryNo as [EntryNo]
from dbo.[Register] gr
union all
select No, CreationDate, FromEntryNo, ToEntryNo, EntryNo + 1 from cte where EntryNo <= ToEntryNo
)
select Id, CreationDate, FromEntryNo, ToEntryNo, EntryNo from cte
<
Any idea how to do this using one SQL query?

with cte as
(select gr.Id, gr.CreationDate, gr.FromEntryNo, gr.ToEntryNo,
gr.FromEntryNo as [EntryNo]
from dbo.[Register] gr
union all
select Id, CreationDate, FromEntryNo,
ToEntryNo, EntryNo + 1
from cte where EntryNo < ToEntryNo
)
select Id, CreationDate, FromEntryNo, ToEntryNo, EntryNo
from cte
ORDER BY Id,EntryNo

Related

How to count number of months in T-SQL

I've got a problem in SQL Server.
"Whate'er is well conceived is clearly said, And the words to say it flow with ease", Nicolas Boileau-Despreaux
Well, I don't think I'll be able to make it clear but I'll try ! And I'd like to apologize for my bad english !
I've got this table :
id ind lvl result date
1 1 a 3 2017-01-31
2 1 a 3 2017-02-28
3 1 a 1 2017-03-31
4 1 a 1 2017-04-30
5 1 a 1 2017-05-31
6 1 b 1 2017-01-31
7 1 b 3 2017-02-28
8 1 b 3 2017-03-31
9 1 b 1 2017-04-30
10 1 b 1 2017-05-31
11 2 a 3 2017-01-31
12 2 a 1 2017-02-28
13 2 a 3 2017-03-31
14 2 a 1 2017-04-30
15 2 a 3 2017-05-31
I'd like to count the number of month the combo {ind, lvl} remain in the result 1 before re-initializing the number of month to 0 if the result is not 1.
Clearly, I need to get something like that :
id ind lvl result date BadResultRemainsFor%Months
1 1 a 3 2017-01-31 0
2 1 a 3 2017-02-28 0
3 1 a 1 2017-03-31 1
4 1 a 1 2017-04-30 2
5 1 a 1 2017-05-31 3
6 1 b 1 2017-01-31 1
7 1 b 3 2017-02-28 0
8 1 b 3 2017-03-31 0
9 1 b 1 2017-04-30 1
10 1 b 1 2017-05-31 2
11 2 a 3 2017-01-31 0
12 2 a 1 2017-02-28 1
13 2 a 3 2017-03-31 0
14 2 a 1 2017-04-30 1
15 2 a 3 2017-05-31 0
So that if I was looking for the number of months the result was 1 for the date 2017-05-31 with the id 1 and the lvl a, I know it's been 3 months.
Assume all the date the the end day of month:
;WITH tb(id,ind,lvl,result,date) AS(
select 1,1,'a',3,'2017-01-31' UNION
select 2,1,'a',3,'2017-02-28' UNION
select 3,1,'a',1,'2017-03-31' UNION
select 4,1,'a',1,'2017-04-30' UNION
select 5,1,'a',1,'2017-05-31' UNION
select 6,1,'b',1,'2017-01-31' UNION
select 7,1,'b',3,'2017-02-28' UNION
select 8,1,'b',3,'2017-03-31' UNION
select 9,1,'b',1,'2017-04-30' UNION
select 10,1,'b',1,'2017-05-31' UNION
select 11,2,'a',3,'2017-01-31' UNION
select 12,2,'a',1,'2017-02-28' UNION
select 13,2,'a',3,'2017-03-31' UNION
select 14,2,'a',1,'2017-04-30' UNION
select 15,2,'a',3,'2017-05-31'
)
SELECT t.id,t.ind,t.lvl,t.result,t.date
,CASE WHEN t.isMatched=1 THEN ROW_NUMBER()OVER(PARTITION BY t.ind,t.lvl,t.id-t.rn ORDER BY t.id) ELSE 0 END
FROM (
SELECT t1.*,c.MonthDiff,CASE WHEN c.MonthDiff=t1.result THEN 1 ELSE 0 END AS isMatched
,CASE WHEN c.MonthDiff=t1.result THEN ROW_NUMBER()OVER(PARTITION BY t1.ind,t1.lvl,CASE WHEN c.MonthDiff=t1.result THEN 1 ELSE 0 END ORDER BY t1.id) ELSE null END AS rn
FROM tb AS t1
LEFT JOIN tb AS t2 ON t1.ind=t2.ind AND t1.lvl=t2.lvl AND t2.id=t1.id-1
CROSS APPLY(VALUES(ISNULL(DATEDIFF(MONTH,t2.date,t1.date),1))) c(MonthDiff)
) AS t
ORDER BY t.id
id ind lvl result date
----------- ----------- ---- ----------- ---------- --------------------
1 1 a 3 2017-01-31 0
2 1 a 3 2017-02-28 0
3 1 a 1 2017-03-31 1
4 1 a 1 2017-04-30 2
5 1 a 1 2017-05-31 3
6 1 b 1 2017-01-31 1
7 1 b 3 2017-02-28 0
8 1 b 3 2017-03-31 0
9 1 b 1 2017-04-30 1
10 1 b 1 2017-05-31 2
11 2 a 3 2017-01-31 0
12 2 a 1 2017-02-28 1
13 2 a 3 2017-03-31 0
14 2 a 1 2017-04-30 1
15 2 a 3 2017-05-31 0
By slightly tweaking your input data and slightly tweaking how we define the requirement, it becomes quite simple to produce the expected results.
First, we tweak your date values so that the only thing that varies is the month and year - the days are all the same. I've chosen to do that my adding 1 day to each value1. The fact that this produces results which are one month advanced doesn't matter here, since all values are similarly transformed, and so the monthly relationships stay the same.
Then, we introduce a numbers table - here, I've assumed a small fixed table is adequate. If it doesn't fit your needs, you can easily locate examples online for creating a large fixed numbers table that you can use for this query.
And, finally, we recast the problem statement. Instead of trying to count months, we instead ask "what's the smallest number of months, greater of equal to zero, that I need to go back from the current row, to locate a row with a non-1 result?". And so, we produce this query:
declare #t table (id int not null,ind int not null,lvl varchar(13) not null,
result int not null,date date not null)
insert into #t(id,ind,lvl,result,date) values
(1 ,1,'a',3,'20170131'), (2 ,1,'a',3,'20170228'), (3 ,1,'a',1,'20170331'),
(4 ,1,'a',1,'20170430'), (5 ,1,'a',1,'20170531'), (6 ,1,'b',1,'20170131'),
(7 ,1,'b',3,'20170228'), (8 ,1,'b',3,'20170331'), (9 ,1,'b',1,'20170430'),
(10,1,'b',1,'20170531'), (11,2,'a',3,'20170131'), (12,2,'a',1,'20170228'),
(13,2,'a',3,'20170331'), (14,2,'a',1,'20170430'), (15,2,'a',3,'20170531')
;With Tweaked as (
select
*,
DATEADD(day,1,date) as dp1d
from
#t
), Numbers(n) as (
select 0 union all select 1 union all select 2 union all select 3 union all select 4
union all
select 5 union all select 6 union all select 7 union all select 8 union all select 9
)
select
id, ind, lvl, result, date,
COALESCE(
(select MIN(n) from Numbers n1
inner join Tweaked t2
on
t2.ind = t1.ind and
t2.lvl = t1.lvl and
t2.dp1d = DATEADD(month,-n,t1.dp1d)
where
t2.result != 1
),
1) as [BadResultRemainsFor%Months]
from
Tweaked t1
The COALESCE is just there to deal with the edge case, such as for your 1,b data, where there is no previous row with a non-1 result.
Results:
id ind lvl result date BadResultRemainsFor%Months
----------- ----------- ------------- ----------- ---------- --------------------------
1 1 a 3 2017-01-31 0
2 1 a 3 2017-02-28 0
3 1 a 1 2017-03-31 1
4 1 a 1 2017-04-30 2
5 1 a 1 2017-05-31 3
6 1 b 1 2017-01-31 1
7 1 b 3 2017-02-28 0
8 1 b 3 2017-03-31 0
9 1 b 1 2017-04-30 1
10 1 b 1 2017-05-31 2
11 2 a 3 2017-01-31 0
12 2 a 1 2017-02-28 1
13 2 a 3 2017-03-31 0
14 2 a 1 2017-04-30 1
15 2 a 3 2017-05-31 0
1An alternative way to perform the adjustment is to use a DATEADD/DATEDIFF pair to perform a "floor" operation against the dates:
DATEADD(month,DATEDIFF(month,0,date),0) as dp1d
Which resets all of the date values to be the first of their own month rather than the following month. This may fell more "natural" to you, or you may already have such values available in your original data.
Assuming the dates are continously increasing in month, you can use window function like so:
select
t.id, ind, lvl, result, dat,
case when result = 1 then row_number() over (partition by grp order by id) else 0 end x
from (
select t.*,
dense_rank() over (order by e, result) grp
from (
select
t.*,
row_number() over (order by id) - row_number() over (partition by ind, lvl, result order by id) e
from your_table t
order by id) t ) t;

SQL Server SELECT Rank() and Grouping of data

Please help me with this specific query.
Here's my sample query
with cte as
(
select entryID,LogDateTime,logtype[logtype],batch,
rank() over (partition by logType
order by logdatetime) rnk
from Emp_TimeLog
where cast(LogDateTime as date) = '2016-05-17'
) select entryID,LogDateTime,logType,batch, rnk
from cte order by LogDateTime
It resulted like this
2016-05-17 11:57:44.000 1 1 1
2016-05-17 11:57:53.000 5 1 1
2016-05-17 11:57:58.000 6 1 1
2016-05-17 11:58:10.000 2 1 1
2016-05-17 11:58:18.000 1 2 2
2016-05-17 11:58:25.000 3 1 1
2016-05-17 11:58:32.000 4 1 1
2016-05-17 11:58:42.000 5 2 2
2016-05-17 11:58:49.000 6 2 2
2016-05-17 11:58:55.000 2 2 2
But what I want is like this.
2016-05-17 11:57:44.000 1 1 1
2016-05-17 11:57:53.000 5 1 1
2016-05-17 11:57:58.000 6 1 1
2016-05-17 11:58:10.000 2 1 1
2016-05-17 11:58:18.000 1 2 2
2016-05-17 11:58:25.000 3 2 2
2016-05-17 11:58:32.000 4 2 2
2016-05-17 11:58:42.000 5 2 2
2016-05-17 11:58:49.000 6 2 2
2016-05-17 11:58:55.000 2 2 2
The last 2 columns are the Batch and Rank,
while the 3rd column is the logType.
it should be group by batch of logType wherein LogType group is 1,2,3,4,5,6.
so, for every 1 group it is another batch and I need to place it on last 2 columns.
Please help with this, on how I can achieve this.
Thanks in advance.
You just need to fix the order in the rank... as:
rank() over (partition by logType
order by batch) rnk

SQL Server 2008 R2: Recursive query

This is the follow up question of : Prepare a recursive query
I have the table with the two columns namely cola and colb as shown below:
Table : Test
create table Test
(
cola int,
colb int
);
Records I have entered are:
Cola Colb
------------
1 2
1 3
1 4
2 5
2 6
2 3
3 2
3 4
3 7
3 10
10 11
11 12
11 13
11 14
12 15
13 16
14 99
15 88
16 77
Note: Now I want to show the only records who are connected with value I have pass. For example If I pass the value as 1 then it should display me the connected number to it and form connect like a tree.
For the above requirement I have got the script from Dark Knight as shown below which works fine.
;WITH CTE AS
(
SELECT COLA,COLB,','+CAST(COLA AS VARCHAR(MAX))+',' AS CHCK FROM test WHERE COLA=1
UNION ALL
SELECT C1.COLA,C1.COLB,C.CHCK+CAST(C1.cola AS VARCHAR(MAX))+','
FROM CTE C INNER JOIN test C1 ON C.colb = C1.cola
WHERE CHARINDEX(','+CAST(C.colb AS VARCHAR(MAX))+',',C.CHCK)=0
),
OUTERCTE AS
(
SELECT DISTINCT COLA,COLB,ROW_NUMBER() OVER(PARTITION BY Colb ORDER BY Cola) rn FROM CTE --ORDER BY COLA
)
SELECT Cola,Colb FROM OUTERCTE
WHERE rn<=1
ORDER BY CASE WHEN Cola = 1 THEN 1 ELSE 2 END;
Which gives me this:
----------------
Cola Colb
----------------
1 2
1 3
1 4
2 5
2 6
3 7
3 10
10 11
11 12
11 13
11 14
12 15
13 16
16 77
15 88
14 99
Requirement: Now I want to show the levels of records.
Expected Result:
------------------------------
Cola Colb Level
------------------------------
1 2 1
1 3 1
1 4 1
2 5 2
2 6 2
3 7 2
3 10 2
10 11 3
11 12 4
11 13 4
11 14 4
12 15 5
13 16 5
16 77 6
15 88 6
14 99 5
;WITH CTE AS
(
SELECT COLA,COLB
,','+CAST(COLA AS VARCHAR(MAX))+',' AS CHCK
, 1 as lvl FROM #Test WHERE COLA=1
UNION ALL
SELECT C1.COLA,C1.COLB ,C.CHCK+CAST(C1.cola AS VARCHAR(MAX))+','
, c.lvl+1
FROM CTE C INNER JOIN #Test C1 ON C.colb = C1.cola
WHERE CHARINDEX(','+CAST(C.colb AS VARCHAR(MAX))+',',C.CHCK)=0
),
cte2 as (
select * , ROW_NUMBER() over (partition by colb order by lvl)as rn From CTE
)
select cola,colb,lvl from cte2 where rn = 1

Merge two tables while dragging one column from one table to the other

With Sql Server 2014:
I have two tables - Events and Locations, that share a time column and I need to merge them into one table order by time. In the Events table there is an Event column that I need to place in all the Locations row following that event (time wise), here is an example:
Events:
time event
------------
09:00 2
09:10 3
10:15 1
10:17 2
10:30 3
Locations:
time X Y
-------------
09:01 1 3
09:02 2 3
09:05 4 1
09:09 6 4
09:10 7 8
09:11 8 8
09:12 9 7
10:17 1 2
10:19 5 4
10:20 4 3
10:25 5 4
10:28 3 5
Merged Table:
time X Y event
--------------------
09:00 0 0 2
09:01 1 3 2 <
09:02 2 3 2 <
09:05 4 1 2 <
09:09 6 4 2 <
09:10 0 0 3
09:10 7 8 3 <
09:11 8 8 3 <
09:12 9 7 3 <
10:15 0 0 1
10:17 0 0 2
10:17 1 2 2 <
10:19 5 4 2 <
10:20 4 3 2 <
10:25 5 4 2 <
10:28 3 5 2 <
10:30 0 0 3
The elements that mark with '<' are the inserted Events.
Any ideas and help on how to perform this task is welcome.
You can use UNION ALL and APPLY:
SQL Fiddle
SELECT
[Time], X = 0, Y = 0, [Event]
FROM [Events]
UNION ALL
SELECT l.*, x.Event
FROM Locations l
CROSS APPLY(
SELECT TOP 1 *
FROM [Events]
WHERE [Time] <= l.[Time]
ORDER BY [Time] DESC
)x
ORDER BY [Time]

How to get TOP (1) row within each Group in sql server 2000

I have some following set of data from where I want to select Top 1 row for each PK_PatientId based on the current order
PK_PatientId PK_PatientVisitId PK_VisitProcedureId DateSort
------------ ----------------- ------------------- -----------------------
1 4 4 2009-06-22 00:00:00.000
1 3 3 2009-06-22 00:00:00.000
1 2 2 2010-03-11 00:00:00.000
1 1 1 2010-03-11 00:00:00.000
5 6 6 2009-05-24 00:00:00.000
5 5 5 2009-11-07 00:00:00.000
7 7 7 2009-05-24 00:00:00.000
8 8 8 2009-05-24 00:00:00.000
9 9 9 2009-05-24 00:00:00.000
10 10 10 2009-05-24 00:00:00.000
Query that lead me to this result is
SELECT
P.PK_PatientId
, PV.PK_PatientVisitId
, MAX(TVP.PK_VisitProcedureId) AS PK_VisitProcedureId
, MAX(PV.LastUpdated) AS DateSort
--, Row_Number() OVER (Partition BY PK_PatientId ORDER BY PV.PK_PatientVisitId DESC) AS RowNo
FROM
dbo.M_Patient AS P
INNER JOIN
dbo.M_PatientVisit AS PV
ON
P.PK_PatientId = PV.FK_PatientId
INNER JOIN
dbo.TX_VisitProcedure AS TVP
ON
PV.PK_PatientVisitId = TVP.FK_PatientVisitId
WHERE
(P.IsActive = 1)
AND
(PV.IsActive = 1)
AND
(TVP.IsActive = 1)
GROUP BY
PK_PatientId
, PK_PatientVisitId
ORDER BY
PK_PatientId
, PK_PatientVisitId DESC
and I have to get the remaining functionality that I was doing with Row Number function by taking RowNo=1. But Now I have to take this procedure to SQL 2000 due to which this function can't be used.
Desired Result is
PK_PatientId PK_PatientVisitId PK_VisitProcedureId DateSort RowNo
------------ ----------------- ------------------- ----------------------- --------------------
1 4 4 2009-06-22 00:00:00.000 1
5 6 6 2009-05-24 00:00:00.000 1
7 7 7 2009-05-24 00:00:00.000 1
8 8 8 2009-05-24 00:00:00.000 1
9 9 9 2009-05-24 00:00:00.000 1
which I am getting when using Row_Number in sql 2005. I want same result using sql 2000 only.
I have to use SQL 2000
You just need to strap this to the end of your WHERE clause:
AND NOT EXISTS (
SELECT *
FROM dbo.M_PatientVisit PV2
WHERE P.PK_PatientId = PV2.FK_PatientId
AND PV2.PK_PatientVisitId > PV.PK_PatientVisitId
)
...which will result in the query returning "the patient visits for patients where there does not exist another visit for that patient with a higher ID" - that is, you'll get the visits with the highest IDs.
Note that you'll need to include the other logic in the WHERE clause in this subquery in order to ensure that the bits are active etc.
Would you mind use temporary table in your procedure? I mean that you can insert max(PatientVisitId) rows into a temporary table.

Resources