生成表示完整日历月的日历 Table

Generate Calendar Table Representing a Full Calendar Month

我正在尝试创建一个日历 table 代表日历中的完整月份,包括上个月和下个月的重叠天数。

我已经非常接近从另一个 site/post 改编的以下脚本:

DECLARE
      @year     INT = 2019
    , @month    INT = 1
;

DECLARE
      @firstDOM DATETIME
    , @lastDOM  DATETIME
    , @firstDay VARCHAR(10)
    , @weekid   TINYINT
    , @opDate   DATE

    , @firstDOW DATE
    , @lastDOW  DATE
;

-- get mid-month operation date
SELECT @opDate = CONVERT(DATE, (CAST(@year AS CHAR(4)) + '-' + CAST(@month AS VARCHAR(2)) + '-15'))
;

-- get first day of month
SET @firstDOM = DATEADD(mm, DATEDIFF(mm, 0, @opDate), 0)
;

-- get last day of month
SET @lastDOM = DATEADD (dd, -1, DATEADD(mm, DATEDIFF(mm, 0, @opDate) + 1, 0))
;

-- get first day of week
SET @firstDOW = @firstDOM - DATEPART(dw, @firstDOM) + 1
;

-- get last day of week
SET @lastDOW = @lastDOM + (7 - DATEPART(dw, @lastDOM))
;

-- get first day name
SELECT @firstDay = DATENAME(WEEKDAY, @firstDOW)
;

---------- Recursive CTE to get Days and Dates for the month
;WITH cte_cal ([Date], [Day], [WeekID])
as (
    SELECT
          @firstDOW
        , @firstDay
        , DATEPART(WW, @firstDOW) AS WeekID

    UNION ALL

    SELECT
          DATEADD(DD, 1, [Date])
        , CAST(DATENAME(WEEKDAY, DATEADD(DD, 1, [Date])) AS VARCHAR(10))
        , DATEPART(WW, DATEADD(DD, 1, [Date]))AS WeekID
    FROM
        cte_cal
    WHERE
        [Date] < @lastDOW
)
------- Use Pivot to display the result in calender format
SELECT
    [WeekID], [Sunday] , [Monday], [Tuesday], [Wednesday], [Thursday], [Friday], [Saturday]
FROM (
    SELECT
        [WeekID], [Date], [DAY]
    FROM
        cte_cal
) pvt
PIVOT (
    MAX([Date]) FOR [Day] IN ([Sunday], [Monday], [Tuesday], [Wednesday], [Thursday], [Friday], [Saturday])
) Pvttab
;

但是,1 月和 12 月的行为不正确,如下图所示,其中 2019 年的每个月都会被触发:

Calendar table output with incorrect January and December values

如有任何帮助,我们将不胜感激。

谢谢!

对您的查询进行了一些小的更改以解决问题周。可能需要做一些 QA 以确保它在所有情况下都能正常工作。这是 SQL Fiddle

特别是在 CTE(两个联合查询)中,我添加了一个 CASE 语句来强制将周数设为 1 或 53:

...
, CASE WHEN YEAR(@firstDOW) = @year + 1 THEN 52
       WHEN YEAR(@firstDOW) = @year - 1 THEN 1
  ELSE DATEPART(WW, @firstDOW) END AS WeekID

  UNION ALL

...
, CASE WHEN YEAR(DATEADD(DD, 1, [Date])) = @year + 1 THEN 53
       WHEN YEAR(DATEADD(DD, 1, [Date])) = @year - 1 THEN 1
  ELSE DATEPART(WW, DATEADD(DD, 1, [Date])) END AS WeekID
...

总计:

DECLARE
  @year     INT = 2020
, @month    INT = 12
, @firstDOM DATETIME
, @lastDOM  DATETIME
, @firstDay VARCHAR(10)
, @weekid   TINYINT
, @opDate   DATE
, @firstDOW DATE
, @lastDOW  DATE


-- get mid-month operation date
SELECT @opDate = CONVERT(DATE, (CAST(@year AS CHAR(4)) + '-' + CAST(@month AS VARCHAR(2)) + '-15'))


-- get first day of month
SET @firstDOM = DATEADD(mm, DATEDIFF(mm, 0, @opDate), 0)


-- get last day of month
SET @lastDOM = DATEADD (dd, -1, DATEADD(mm, DATEDIFF(mm, 0, @opDate) + 1, 0))


-- get first day of week
SET @firstDOW = @firstDOM - DATEPART(dw, @firstDOM) + 1


-- get last day of week
SET @lastDOW = @lastDOM + (7 - DATEPART(dw, @lastDOM))


-- get first day name
SELECT @firstDay = DATENAME(WEEKDAY, @firstDOW)


---------- Recursive CTE to get Days and Dates for the month
;WITH cte_cal ([Date], [Day], [WeekID])
as (
    SELECT
          @firstDOW
        , @firstDay
        , CASE WHEN YEAR(@firstDOW) = @year + 1 THEN 52
               WHEN YEAR(@firstDOW) = @year - 1 THEN 1
          ELSE DATEPART(WW, @firstDOW) END AS WeekID

    UNION ALL

    SELECT
          DATEADD(DD, 1, [Date])
        , CAST(DATENAME(WEEKDAY, DATEADD(DD, 1, [Date])) AS VARCHAR(10))
        , CASE WHEN YEAR(DATEADD(DD, 1, [Date])) = @year + 1 THEN 53
               WHEN YEAR(DATEADD(DD, 1, [Date])) = @year - 1 THEN 1
          ELSE DATEPART(WW, DATEADD(DD, 1, [Date])) END AS WeekID
    FROM
        cte_cal
    WHERE
        [Date] < @lastDOW
)
------- Use Pivot to display the result in calender format
SELECT
    [WeekID], [Sunday], [Monday], [Tuesday], [Wednesday], [Thursday], [Friday], 
[Saturday]
FROM (
    SELECT
        [WeekID], [Date], [DAY]
    FROM
        cte_cal
) pvt
PIVOT (
    MAX([Date]) FOR [Day] IN ([Sunday], [Monday], [Tuesday], [Wednesday], [Thursday], [Friday], [Saturday])
) Pvttab
;

还有一个选择

例子

Declare @Date1 date = '2020-02-01'

Select RowNr,[Sun],[Mon],[Tue],[Wed],[Thu],[Fri],[Sat]
 From  (
        Select D
              ,DOW=left(datename(WEEKDAY,d),3)
              ,RowNr = sum(Flg) over (order by D)
         From (
                Select D,Flg=case when datename(WEEKDAY,d)= 'Sunday' then 1 else 0 end
                 From (Select Top (42) D=DateAdd(DAY,-7+Row_Number() Over (Order By (Select Null)),@Date1) From  master..spt_values n1 ) A
              ) A
       ) src
 Pivot (max(d) for DOW in ([Sun],[Mon],[Tue],[Wed],[Thu],[Fri],[Sat]) )pvg
 Where [Sun] is not null
   and [Sat] is not null

Returns

当我注意到电流T-Sql时,我开始怀疑它是否真的需要那么多变量。

所以我有点开始从头开始涂鸦。

结果适用于 DATEFIRST 7(周日开始)。

递归 CTE 只展开当月的日期。
但它会针对一周的第一个日期进行更正,即使该周是从前一年开始的。结束日期类似。

因为大多数与日期相关的信息都可以从日期中导出。

DECLARE @year INT, @month INT;
SET @year = YEAR(GetDate());
SET @month = 1;
SET DATEFIRST 7;

WITH CTE_DATES AS
(
    SELECT 
     DATEADD(DAY, 1-DATEPART(WEEKDAY, DATEFROMPARTS(@year, @month, 1)), DATEFROMPARTS(@year, @month, 1)) AS [Date],
     DATEFROMPARTS(@year, @month, 1) AS initDate,
     DATEADD(dd, 7-(DATEPART(dw, EOMONTH(DATEFROMPARTS(@year, @month, 1))   )), EOMONTH(DATEFROMPARTS(@year, @month, 1)))  AS endDate

    UNION ALL

    SELECT DATEADD(day,1, [Date]), initDate, endDate
    FROM CTE_DATES
    WHERE [Date] < endDate
), 
CTE_CALENDAR AS
(
  SELECT 
   (CASE
    WHEN MONTH(initDate) = 1 AND DATEPART(WW, [Date]) > 50
    THEN 1
    WHEN YEAR([Date]) > YEAR(initDate)
    THEN DATEPART(WW, EOMONTH(initDate))
    ELSE DATEPART(WW, [Date])
    END) AS [WeekID], 
   DATENAME(WEEKDAY, [Date]) AS [Day],
   CONVERT(VARCHAR(10),[Date],23) AS [Date]
  FROM CTE_DATES
)
SELECT *
FROM CTE_CALENDAR
PIVOT 
(
   MAX([Date]) 
   FOR [Day] IN ([Sunday] , [Monday], [Tuesday], [Wednesday], [Thursday], [Friday], [Saturday])
) Pvt
ORDER BY [WeekID];

rextester 测试here