从给定的日期时间为每年的月份生成行

Generate row for month of each year from given datetime

我使用以下查询根据名为 'InsertDate' 的 table 字段生成行:

;WITH listOfMonth(MonthNumber,ContractId )
AS
(
    select 
       DATEPART(month, StartDate) as m,Id as ContractId from tblContract  where  Id = 6674
    union all
    select MonthNumber + 1 as MonthNumber ,ContractId 
    from listOfMonth
    where MonthNumber <  DATEPART(month, GETDATE())
)
select * from listOfMonth  

例如,如果 InsertDate 是 4/19/2021,我有以下输出:

ID MonthNumber ContractId
1 4 6674
2 5 6674
3 6 6674
4 7 6674
5 8 6674
6 9 6674

但我还需要根据年份生成行,所以我更改了查询:

;WITH listOfMonth(YearNumber,MonthNumber,ContracId)
AS
(
    select 
       DATEPART(Year, StartDate) as YearNumber,DATEPART(month, StartDate) as m,Id as ContracId from tblContract where  Id = 6674
    union all
    select YearNumber, MonthNumber + 1 as MonthNumber ,ContracId
    from listOfMonth
    where MonthNumber <  DATEPART(month, GETDATE())
)
select * from listOfMonth  

此查询生成年份列,但仅针对 InsertDate 的当前年份, 例如,如果 InsertDate 是 4/19/2020,它只生成 2020 年的列,但我想为 2020 年和 2021 年生成列 我使用 SQL Server 2008 R2 最终输出应该是这样的:

ID MonthNumber ContractId YearNumber
1 4 6674 2020
2 5 6674 2020
3 6 6674 2020
4 7 6674 2020
5 8 6674 2020
6 9 6674 2020
7 10 6674 2020
8 11 6674 2020
9 12 6674 2020
10 1 6674 2021
11 2 6674 2021
12 3 6674 2021
13 4 6674 2021
14 5 6674 2021
15 6 6674 2021
16 7 6674 2021
16 8 6674 2021
16 9 6674 2021

我会将月份表示为 日期:

with listOfMonth AS(
      select datefromparts(year(startdate), month(startdate), 1) as yyyymm,
             Id as ContractId
      from tblContract
      where Id = 6674
      union all
      select dateadd(month, 1, yyyymm), ContractId 
      from listOfMonth
      where yyyymm < datefromparts(year(getdate()), month(getdate()), 1)
     )
select *
from listOfMonth
option maxrecursion (0);

如果您想在单独的列中显示年份和月份,请在外部查询中执行此操作:

select year(yyyymm) as year, month(yyyymm) as month

SQL 服务器中的递归 CTE 是循环数据的缓慢方式(这就是限制设置得如此低的原因,即“最大递归 100 已用尽”)。生成行的快速方法是使用“数字 table”(包含序列作为行的 table)或 function(又名“计数函数”)。这使用称为 dbo.fnTally 的计数函数。可以用数字 table 代替计数功能。


CREATE FUNCTION [dbo].[fnTally]
/**********************************************************************************************************************
    Jeff Moden Script on SSC: https://www.sqlservercentral.com/scripts/create-a-tally-function-fntally
**********************************************************************************************************************/
        (@ZeroOrOne BIT, @MaxN BIGINT)
RETURNS TABLE WITH SCHEMABINDING AS 
 RETURN WITH
  H2(N) AS ( SELECT 1 
               FROM (VALUES
                     (1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    ,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    ,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    ,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    ,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    ,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    ,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    ,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    ,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    ,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    ,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    ,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    ,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    ,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    ,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    ,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    )V(N))            --16^2 or 256 rows
, H4(N) AS (SELECT 1 FROM H2 a, H2 b) --16^4 or 65,536 rows
, H8(N) AS (SELECT 1 FROM H4 a, H4 b) --16^8 or 4,294,967,296 rows
            SELECT N = 0 WHERE @ZeroOrOne = 0 UNION ALL
            SELECT TOP(@MaxN)
                   N = ROW_NUMBER() OVER (ORDER BY N)
              FROM H8
;
select year(calc.dt) YearNumber,
       month(calc.dt) MonthNumber,
       c.ContractId
from (values (cast('20190101' as date), 123),
             (cast('20201201' as date), 456)) c(StartDate, ContractId)
     cross apply dbo.fnTally(0, datediff(month, StartDate, getdate())) fn
     cross apply (values (dateadd(month, fn.n, StartDate))) calc(dt)
order by c.ContractId, YearNumber, MonthNumber;
YearNumber  MonthNumber ContractId
2019        1           123
2019        2           123
...
2019        12          123
2020        1           123
...
2021        9           123

2020        12          456
2021        1           456
...
2021        9           456