简体   繁体   中英

SQL Server 2008 - Alternatives to While Loop

I have an INSERT statement that operates within a WHILE loop. In each iteration of the WHILE loop, some functions are called with dates variables passed as parameters. These date variables increase by one day at each iteration of the loop.

This is a stripped down example:

-- Start and End Date
DECLARE 
     @StartDate DATE = '20170101'
    ,@EndDate   DATE = '20170110'

-- Initialise Loop variables to Start Date
DECLARE
     @InsertDate  DATE = @StartDate
    ,@NextDate    DATE = @StartDate  

-- Loop for All Dates
WHILE (@InsertDate <> @EndDate)
BEGIN
     -- Gather Data to Insert
     INSERT INTO tblCombinedData
     SELECT 
         a.SomeString
        ,b.SomeNumber
        ,dbo.fnDoSomeStuff(a.AKey,@InsertDate,@NextDate)
        ,dbo.fnDoSomeMoreStuff(b.AKey,@InsertDate,@NextDate)
    FROM
        tblATable a 
    INNER JOIN tblAnotherTable b 
        ON a.ID = b.ID

    -- Move to next Set of Dates
    SET @InsertDate = DATEADD(DAY,1,@InsertDate)
    SET @NextDate   = DATEADD(DAY,1,@InsertDate)
END

Is There a more efficient way of achieving this combined insert? (possibly via a CTE?) Thanks.

Note: (SQL Server 2008 R2)

Calendar CTE, anyone?

with CTE as
(
select @startdate as InsertDay
union all
select dateadd(day, 1, @startdate)
from CTE
where @startdate < @enddate
)

insert into tblCombinedData
select a1.stuff, fn(a1.stuff2, InsertDay, dateadd(day,1,InsertDay))
from CTE
cross join 
(
select stuff, stuff2
from tab1
inner join tab2
on tab1.thing = tab2.thing
)

Another way....

declare @table1 table (dt datetime)
declare @table2 table (notDT char)

insert into @table1 (dt) values
('1/1/2017'),
('1/2/2017'),
('1/3/2017'),
('1/4/2017')


insert into @table2 (notDT) values
('a'),
('b'),
('c')


;with t2 as(
    select 
        *,
        ROW_NUMBER() over (order by (select null)) as rn
    from @table2),

t1 as(
    select 
        *,
        ROW_NUMBER() over (order by dt) as rn
    from @table1)


select 
    t2.notDT,
    t1.dt
from
    t2
    inner join t1 on t1.rn = t2.rn

You can use an ad-hoc Tally Table in concert with a CROSS APPLY

Declare @Date1 date = '20170101'
Declare @Date2 date = '20170110'

-- Insert Into tblCombinedData
Select B.*
 From (Select Top (DateDiff(DD,@Date1,@Date2)+1) D=DateAdd(DD,-1+Row_Number() Over (Order By Number),@Date1) From  master..spt_values) DT
 Cross Apply (
                 SELECT a.SomeString
                       ,b.SomeNumber
                       ,dbo.fnDoSomeStuff(a.AKey,DT.D,DT.D)     --<< Notice DT.D
                       ,dbo.fnDoSomeMoreStuff(b.AKey,DT.D,DT.D) --<< Notice DT.D
                  FROM  tblATable a 
                  INNER JOIN tblAnotherTable b ON a.ID = b.ID
             ) B

If it helps with the visualization, the ad-hoc tally table looks like this

D
2017-01-01
2017-01-02
2017-01-03
2017-01-04
2017-01-05
2017-01-06
2017-01-07
2017-01-08
2017-01-09
2017-01-10

try it with Recursive CTE :

;WITH CTE 
AS (
 SELECT @StartDate AS StartDate,  DATEADD(DAY,1,@StartDate) AS NextDate
 UNION ALL
 SELECT DATEADD(DAY,1,StartDate) AS StartDate, DATEADD(DAY,1,NextDate) AS NextDate
 FROM CTE
 WHERE DATEADD(DAY,1,NextDate) <= @EndDate
)
INSERT INTO tblCombinedData
     SELECT 
         a.SomeString
        ,b.SomeNumber
        ,dbo.fnDoSomeStuff(a.AKey, CTE.StartDate, CTE.NextDate)
        ,dbo.fnDoSomeMoreStuff(b.AKey, CTE.StartDate, CTE.NextDate)
    FROM  tblATable a 
    INNER JOIN tblAnotherTable b ON a.ID = b.ID
    CROSS JOIN CTE

This would be better handled with a Calendar table, but if you must use something to generate the dates on demand then this would do:

declare @fromdate date = '20170101';
declare @thrudate date = '20170110';

;with dates as (
  select top (datediff(day, @fromdate, @thrudate)+1) 
     InsertDate=convert(date,dateadd(day,row_number() over(order by (select 1))-1,@fromdate))
   , NextDate  =convert(date,dateadd(day,row_number() over(order by (select 1))  ,@fromdate))
  from master..spt_values a cross join master..spt_values b
  order by 1
)
insert into tblCombinedData
select 
    a.SomeString
  , b.SomeNumber
  , dbo.fnDoSomeStuff(a.akey,d.InsertDate,d.NextDate)
  , dbo.fnDoSomeMoreStuff(b.akey,d.InsertDate,d.NextDate)
from tblatable a 
  inner join tblAnotherTable b 
    on a.id = b.id
  cross join dates d;

This does not use a recursive common table expression (cte), just a regular cte. Using a recursive cte to generate a sequence of is one slowest ways to generate a set or sequence without a loop.

Number and Calendar table reference:

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM