【问题标题】:Translating SQL Server Cursor to Azure Synapse将 SQL Server 游标转换为 Azure Synapse
【发布时间】:2021-03-27 15:42:25
【问题描述】:

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

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 包含下表:

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

【问题讨论】:

  • 您能否展示一些示例数据(如 DDL/DML)和预期结果。
  • 你不需要游标,你只需要一个日历表的交叉连接。第 1 步:创建一个包含所有年份和周数的表格。
  • @Nick.McDermaid 是的,我在另一个标签上打开了它。这是我迷路的地方:'UPDATE STATISTICS '+QUOTENAME([name]) AS sql_code。我对 SQL 思维过程很陌生
  • @kindofhungry 我添加了另一个使用更基本 SQL 的解决方案 - 我讨厌看到有人在 SQL 中使用循环 :) 我很想知道它是否有效。

标签: sql-server azure-synapse


【解决方案1】:

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

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 设置示例数据,您将极大地帮助尝试回答的人。

【讨论】:

  • 感谢 dale,请记住这是如何呈现示例数据
  • Azure 突触不支持递归 CTE,但没关系。我能够从您的回答中汲取一些想法并使其发挥作用。我无法获得的一个部分是给定年份的 ISO 周数。有什么建议吗?我尝试了DECLARE @year_counter SMALLINT DECLARE @week_counter TINYINT SET @year_counter = 2019 select DATEPART(isowk, concat('12-31-',@year_counter)) 之类的方法,但这并不总是有效,因为有时 12 月 31 日是明年的一部分。
  • 是的,我想知道它是否会被支持。那么它只是检查一年是否是闰年的情况吗?这相当简单。
  • 这是我想到的一个技巧:select (DATEPART(isowk, concat('12-25-',@year_counter)), DATEPART(isowk, concat('12-26-',@year_counter)), DATEPART(isowk, concat('12-27-',@year_counter)), DATEPART(isowk, concat('12-28-',@year_counter)), DATEPART(isowk, concat('12-29-',@year_counter)), DATEPART(isowk, concat('12-30-',@year_counter)), DATEPART(isowk, concat('12-31-',@year_counter)))。只需要弄清楚如何选择具有最大值的列。如果它低于明年的第 52 周、第 53 周或第 1 周,我可以知道我何时执行某种类型的 max 函数。
  • 我正在使用带有递增 year_counter 的 while 循环插入到新表中,其中包含每年的所有周数,然后我将交叉加入
【解决方案2】:

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

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 

【讨论】:

    【解决方案3】:

    我不希望看到可以找到固定解决方案的循环解决方案。所以这里有没有CTE、没有values 和没有row_number() 的Take II(表变量只是为了模拟您的数据,所以不是实际解决方案的一部分):

    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;
    

    【讨论】:

    • 嘿戴尔,这真的很快,几乎对我有用,但周数不正确。它显示:2019, 51 2020, 51 2021, 51 2022, 51 2019, 52 2020, 52 2021, 52 2022, 52 2019, 53 2020, 53 2021, 53 2022, 53 2019, 1 2020, 1 2021, 1 2022, 1 仅 2020 年应该有 53 周
    • 另外,如果将来我想在 2022 年之后拥有几年,我将如何修改这个?
    • 我把这些作为练习留给你 :) 我什至不确定你是如何锻炼 52/53 周的事情的。因此,只需添加一个过滤器,它可以计算出一年有多少周,并相应地过滤器 - 就像我在其他解决方案中所做的那样。至于年份,将过滤器Y.N &lt; 3 修改为更高,即包含更多年份。目前它支持 9,但您可以根据需要向 years 子查询添加更多行。代码中有 cmets 可以解释这一点。
    猜你喜欢
    • 1970-01-01
    • 2021-09-19
    • 1970-01-01
    • 2021-08-18
    • 1970-01-01
    • 1970-01-01
    • 2020-05-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多