Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SQL Server cursor type of run (without cursor?)

Tags:

sql-server

given the following table:

create table xx (start_time datetime, end_time datetime, label varchar(100));
insert into xx values
('20180101 08:00', '20180103 08:00', 'test 1'), 
('20180101 06:30', '20180101 08:00', 'test 2'), 
('20180101 10:00', '20180102 08:00', 'test 3');

I have to generate a list where the records are duplicated as many times as I have days between start_time and end_time. The expected result is:

run_date    label
2018-01-01  test 1
2018-01-02  test 1
2018-01-03  test 1
2018-01-01  test 2
2018-01-01  test 3
2018-01-02  test 3

How can I achieve this in a performant way (maybe without any ugly cursor)? The time interval is undefined dynamic (between 1 and 10 days) The source table is quite big (some million records)

like image 974
Gabor Avatar asked Aug 02 '26 02:08

Gabor


2 Answers

If you don't have a calendar table, one approach is an ad-hoc tally table in concert with a CROSS APPLY

Example

Select run_date=cast(B.D as date)
      ,A.label
 from XX A
 Cross Apply (
                Select Top (DateDiff(DAY,Start_Time,End_Time)+1) D=DateAdd(DAY,-1+Row_Number() Over (Order By (Select Null)),Start_Time) 
                 From  master..spt_values n1 -- ,master..spt_values n2  -- remove comment if span > 6 years
             ) B

Returns

run_date    label
2018-01-01  test 1
2018-01-02  test 1
2018-01-03  test 1
2018-01-01  test 2
2018-01-01  test 3
2018-01-02  test 3

EDIT

Just Noticed your note of millions of records. You may be better off using a JOIN to a Calendar Table

like image 129
John Cappelletti Avatar answered Aug 03 '26 18:08

John Cappelletti


This is one of the places when a Numbers table comes handy. Here an article of Aaron Bertrand talking about those tables.

Essentially, you create a table Numbers with a single column Number that has N rows, going from 0 (or 1) to the max number you want (and that the underlying type supports). Then you can JOIN easily:

SELECT CONVERT(date, xx.start_time + n.Number) AS RunningDate, xx.label
FROM xx
INNER JOIN Numbers n ON n.Number <= DATEDIFF(DAY, xx.start_time, xx.end_time)

This solution assumes that the Numbers table starts at 0. It should be slightly modified to allow the same for a table starting at 1.

like image 23
Jorge Y. Avatar answered Aug 03 '26 16:08

Jorge Y.



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!