【问题标题】:Generate row for month of each year from given datetime从给定的日期时间为每年的月份生成行
【发布时间】:2021-09-02 11:09:52
【问题描述】:

我使用以下查询根据名为“InsertDate”的表字段生成行:

;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 是 2021 年 4 月 19 日,我有以下输出:

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

【问题讨论】:

  • 这也将持续到“13”月;您每次只是在月份数字上加 +1,而不是实际加一个月。
  • 有意义的样本数据和预期结果将帮助我们在这里为您提供帮助。
  • @DaleK 我放的是代码,而不是它的图像;你看到的图像是结果!
  • @DaleK 好的,很抱歉,但它的格式不正确
  • @DaleK 你能在 SO 中向我展示另一个将表格数据显示为文本的问题吗?

标签: sql sql-server tsql datetime common-table-expression


【解决方案1】:

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


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

【讨论】:

  • 3 个交叉连接在这里完全够用了,给你 4096 个月
  • 永远不会创建交叉连接,因为它受到行目标的限制,即 SELECT TOP
  • 如果将返回值的数据类型限制为 smallint、int 等,Tally 函数的性能会有所提升。
  • @Charlieface 多表 smallint_n, int_n, bigint_n
  • 除非您发布您的代码,否则我也不知道。我的建议是多次运行代码并注释掉不同的部分,看看它是如何工作的
【解决方案2】:

我会将月份表示为 日期

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 Server 2008 R2?我们的生产服务器基于 SQL Server 2008 R2!!!
  • 我的日期时间基于波斯日期(例如:1398/02/29),它给我错误消息 530,级别 16,状态 1,第 15 行语句终止。在语句完成之前,最大递归 100 已用完。
  • @amin getdate() 对应的波斯日期是多少?
  • @SalmanA SQL2008 中没有等价物,我创建了自定义函数来转换它
猜你喜欢
  • 1970-01-01
  • 2018-10-15
  • 1970-01-01
  • 2023-03-23
  • 2020-12-13
  • 1970-01-01
  • 1970-01-01
  • 2021-09-21
  • 1970-01-01
相关资源
最近更新 更多