将 SQL 服务器游标转换为 Azure Synapse

Translating SQL Server Cursor to Azure Synapse

我有以下代码循环遍历具有唯一型号的 table 并创建一个新的 table,其中对于每个型号,包含基于年份和周数的行。我怎样才能翻译它使其不使用光标?

DECLARE @current_model varchar(50);

--declare a cursor that iterates through model numbers in ItemInformation table
DECLARE model_cursor CURSOR FOR
SELECT model from ItemInformation
--start the cursor
OPEN model_cursor
--get the next (first value)
FETCH NEXT FROM model_cursor INTO @current_model;

DECLARE @year_counter SMALLINT;
DECLARE @week_counter TINYINT;

WHILE (@@FETCH_STATUS = 0) --fetch status returns the status of the last cursor, if 0 then there is a next value (FETCH statement was successful)
BEGIN
    SET @year_counter = 2019;
    WHILE (@year_counter <= Datepart(year, Getdate() - 1) + 2)
    BEGIN
        SET @week_counter = 1;
        WHILE (@week_counter <= 52)
        BEGIN
            INSERT INTO dbo.ModelCalendar(
                model,
                sales_year,
                sales_week
            )
            VALUES(
                @current_model,
                @year_counter,
                @week_counter
            )
            SET @week_counter = @week_counter + 1   
        END
        SET @year_counter = @year_counter + 1
    END
    FETCH NEXT FROM model_cursor INTO @current_model
END;
CLOSE model_cursor;
DEALLOCATE model_cursor;

如果 ItemInformation 包含以下 table:

model,invoice
a,4.99
b,9.99
c,1.99
d,8.99

那么预期的输出是:

model,sales_year,sales_week
A,2019,1
A,2019,2
A,2019,3
...
A,2019,52
A,2020,1
A,2020,2
A,2020,3
...
A,2020,51
A,2020,52
A,2020,53 (this is 53 because 2020 is leap year and has 53 weeks)
A,2021,1
A,2021,2
...
A,2022,1
A,2022,2
...
A,2022,52
B,2019,1
B,2019,2
...
D, 2022,52

使用 CTE,您可以获得所需范围内的周和年的所有组合。然后加入你的数据table上。

declare @Test table (model varchar(1), invoice varchar(4));

insert into @Test (model, invoice)
values
('a', '4.99'),
('b', '9.99'),
('c', '1.99'),
('d', '8.99');

with Week_CTE as (
  select 1 as WeekNo
  union all
  select 1 + WeekNo
  from Week_CTE 
  where WeekNo < 53
), Year_CTE as (
  select 2019 YearNo
  union all
  select 1 + YearNo
  from Year_CTE 
  where YearNo <= datepart(year, current_timestamp)
)
select T.model, yr.YearNo, wk.WeekNo 
from Week_CTE wk
cross join (
  select YearNo
    -- Find the last week of the year (52 or 53) -- might need to change the start day of the week for this to be correct
    , datepart(week, dateadd(day, -1, dateadd(year, 1, '01 Jan ' + convert(varchar(4),YearNo)))) LastWeek
  from Year_CTE yr
) yr
cross join (
  -- Assuming only a single row per model is required, and the invoice column can be ignored
  select model
  from @Test
  group by model
) T
where wk.WeekNo <= yr.LastWeek
order by yr.YearNo, wk.WeekNo;

正如您所建议的那样,使用递归 CTE 不是一种选择,您可以尝试使用没有递归的 CTE:

with T(N) as (
  select X.N
  from (values (0),(0),(0),(0),(0),(0),(0),(0)) X(N)
), W(N) as (
  select top (53) row_number() over (order by @@version) as N
  from T T1
  cross join T T2
), Y(N) as (
  -- Upper limit on number of years
  select top (12) 2018 + row_number() over (order by @@version) AS N
  from T T1
  cross join T T2
)
select W.N as WeekNo, Y.N YearNo, T.model
from W
cross join (
  select N
    -- Find the last week of the year (52 or 53) -- might need to change the start day of the week for this to be correct
    , datepart(week, dateadd(day, -1, dateadd(year, 1, '01 Jan ' + convert(varchar(4),N)))) LastWeek
  from Y
) Y
cross join (
  -- Assuming only a single row per model is required, and the invoice column can be ignored
  select model
  from @Test
  group by model
) T
-- Filter to required number of years.
where Y.N <= datepart(year, current_timestamp) + 1
and W.N <= Y.LastWeek
order by Y.N, W.N, T.model;

注意:如果您将来使用此处所示的 DDL/DML 设置示例数据,您将极大地帮助尝试回答的人。

我创建了一个 table 临时日历 table 包含所有周和年。为了计算闰年,我用一年的最后 7 天计算每一天的 ISO 周数。要知道一年有多少周,我将这些值放入另一个温度 table 并取其最大值。 Azure Synapse 不支持在一个插入中插入多个值,因此它看起来比应有的要长很多。我还必须将每个声明为变量,因为 Synapse 只能插入文字或变量。然后我交叉加入了我的 ItemInformation table.

CREATE TABLE #temp_dates 
  ( 
     year SMALLINT, 
     week TINYINT 
  ); 

CREATE TABLE #temp_weeks 
  ( 
     week_num TINYINT 
  ); 

DECLARE @year_counter SMALLINT 

SET @year_counter = 2019 

DECLARE @week_counter TINYINT 

WHILE ( @year_counter <= Datepart(year, Getdate() - 1) + 2 ) 
  BEGIN 
      SET @week_counter = 1; 

      DECLARE @day_1 TINYINT 

      SET @day_1 = Datepart(isowk, Concat('12-25-', @year_counter)) 

      DECLARE @day_2 TINYINT 

      SET @day_2 = Datepart(isowk, Concat('12-26-', @year_counter)) 

      DECLARE @day_3 TINYINT 

      SET @day_3 = Datepart(isowk, Concat('12-27-', @year_counter)) 

      DECLARE @day_4 TINYINT 

      SET @day_4 = Datepart(isowk, Concat('12-28-', @year_counter)) 

      DECLARE @day_5 TINYINT 

      SET @day_5 = Datepart(isowk, Concat('12-29-', @year_counter)) 

      DECLARE @day_6 TINYINT 

      SET @day_6 = Datepart(isowk, Concat('12-30-', @year_counter)) 

      DECLARE @day_7 TINYINT 

      SET @day_7 = Datepart(isowk, Concat('12-31-', @year_counter)) 

      TRUNCATE TABLE #temp_weeks 

      INSERT INTO #temp_weeks 
                  (week_num) 
      VALUES      (@day_1) 

      INSERT INTO #temp_weeks 
                  (week_num) 
      VALUES      (@day_2) 

      INSERT INTO #temp_weeks 
                  (week_num) 
      VALUES      (@day_3) 

      INSERT INTO #temp_weeks 
                  (week_num) 
      VALUES      (@day_4) 

      INSERT INTO #temp_weeks 
                  (week_num) 
      VALUES      (@day_5) 

      INSERT INTO #temp_weeks 
                  (week_num) 
      VALUES      (@day_6) 

      INSERT INTO #temp_weeks 
                  (week_num) 
      VALUES      (@day_7) 

      DECLARE @max_week TINYINT 

      SET @max_week = (SELECT Max(week_num) 
                       FROM   #temp_weeks) 

      WHILE ( @week_counter <= @max_week ) 
        BEGIN 
            INSERT INTO #temp_dates 
                        (year, 
                         week) 
            VALUES     ( @year_counter, 
                         @week_counter ) 

            SET @week_counter = @week_counter + 1 
        END 

      SET @year_counter = @year_counter + 1 
  END 

DROP TABLE #temp_weeks; 

SELECT i.model, 
       d.year, 
       d.week 
FROM   dbo.iteminformation i 
       CROSS JOIN #temp_dates d 
ORDER  BY model, 
          year, 
          week
DROP TABLE #temp_dates 

我不喜欢看到可以找到集合解决方案的循环解决方案。所以 Take II 没有 CTE,没有 values 也没有 row_number()(table 变量只是为了模拟您的数据,而不是实际解决方案的一部分):

declare @Test table (model varchar(1), invoice varchar(4));

insert into @Test (model, invoice)
values
('a', '4.99'),
('b', '9.99'),
('c', '1.99'),
('d', '8.99');

select Y.N + 2019 YearNumber, W.WeekNumber, T.Model
from (
  -- Cross join 5 * 10, then filter to 52/53 as required
  select W1.N * 10 + W2.N + 1 WeekNumber
  from (
    select 0 N
    union all select 1
    union all select 2
    union all select 3
    union all select 4
    union all select 5
  ) W1
  cross join (
    select 0 N
    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
  ) W2
) W
-- Cross join number of years required, just ensure its more than will ever be needed then filter back
cross join (
  select 0 N
  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
) Y
cross join (
  -- Assuming only a single row per model is required, and the invoice column can be ignored
  select model
  from @Test
  group by model
) T
-- Some filter to restrict the years
where Y.N <= 3
-- Some filter to restrict the weeks
and W.WeekNumber <= 53
order by YearNumber, WeekNumber;