SQL Server 日期表 : programatically find the last Sunday in a given month

标签 sql sql-server

(SQL Server 2008 及更高版本):我需要更新 dimDate 表以包含以下列:

  • 本月最后一个星期日:
  • 本月最后一个星期一:
  • 本月最后一个星期二:
  • 本月最后一个星期三:
  • 本月最后一个星期四:
  • 本月最后一个星期五:
  • 本月最后一个星期六:

别误会我的意思;通过以下代码在 SQL 中定位该月的最后一天非常简单:

CONVERT(DATE, DATEADD(DD, - (DATEPART(DD, (DATEADD(MM, 1, DayInTime)))), DATEADD(MM, 1, DayInTime))) AS [LastDayOfTheMonth]

但我在互联网上找不到任何地方,而且我承认没有花费超过 2 小时的搜索时间,其中人们公开展示了如何识别该月的最后一个(插入日期名称)

因此,我解决了这个方程并将其发布在这里,以防它对其他人有用,或者,如果有人有一个我可以使用的更简单的方法,但根本看不到它。

dimDates 表通过预填充的 dimNumbers 表进行填充:

 IF OBJECT_ID('dbo.dimNumbers') IS NOT NULL 
      DROP TABLE dbo.dimNumbers;

DECLARE @UpperBound INT = 1000000;

;WITH cteN(Number) AS
(
     SELECT 
         ROW_NUMBER() OVER (ORDER BY s1.[object_id]) - 1
     FROM 
         sys.all_columns AS s1
     CROSS JOIN 
         sys.all_columns AS s2
)
SELECT [Number] 
INTO ref.dimNumbers
FROM cteN 
WHERE [Number] <= @UpperBound;

CREATE UNIQUE CLUSTERED INDEX CIX_dimNumbers ON ref.dimNumbers([Number]);

然后通过以下方式填充暗淡的日期表。是的,我很懒,希望 SQL 执行所有可能的计算。

DECLARE @YearsToPopulate INT = 130;

-- Use the Magic of SQL to identify 1 Jan and then 31st December at the various edges of the required date time frames.
DECLARE @StartDate DATE = DATEADD(yy, DATEDIFF(yy,0,DATEADD(yyyy,-@YearsToPopulate,GETDATE())), 0);
DECLARE @EndDate DATE = DATEADD(yy, DATEDIFF(yy,0,DATEADD(yyyy,@YearsToPopulate,GETDATE())) + 1, -1);

DECLARE @RecordsToCreate INT =  DATEDIFF(dd,@StartDate,@EndDate);

;WITH MyFullDateRange AS 
(
   SELECT TOP (@RecordsToCreate)  
       CAST(DATEADD(dd, Number, @StartDate) AS DATE) AS DayInTime
   FROM 
       ref.[dimNumbers]
) 
SELECT 
    --Insert Formulas here, using [DayInTime] as the Variable
    -- The Formulas I have used here are not the topic of this discussion.
FROM 
    MyFullDateRange

因此,我花了几个小时来证明,但我最终想出了一个简单的、可重复的模式来发现“本月的最后一个(插入日期名称)”。

注意:将以下内容放入上述 SELECT 语句中。

--Sunday [DayOfWeek] = 1.. Need to convert 1 to 0 <-> N + (7 - 1) % 7
DATEADD(DD,
        - ((DATEPART(dw,  DATEADD(DD, - (DATEPART(DD, (DATEADD(MM, 1, DayInTime)))), DATEADD(MM, 1, DayInTime)))) + (7 - 1) ) % 7 ,
        CONVERT(DATE, DATEADD(DD, - (DATEPART(DD, (DATEADD(MM, 1, DayInTime)))), DATEADD(MM, 1, DayInTime)))
    )
    AS [LastsSundayOfTheMonth]


--Monday [DayOfWeek] = 2.. Need to convert 2 to 0 <->  N + (7 - 2) % 7   
DATEADD(DD,
            - ((DATEPART(dw,  DATEADD(DD, - (DATEPART(DD, (DATEADD(MM, 1, DayInTime)))), DATEADD(MM, 1, DayInTime)))) + (7 - 2) ) % 7    ,
            CONVERT(DATE, DATEADD(DD, - (DATEPART(DD, (DATEADD(MM, 1, DayInTime)))), DATEADD(MM, 1, DayInTime)))
        )
        AS  [LastMondayOfTheMonth]


--Tuesday [DayOfWeek] = 3.. Need to convert 3 to 0 <->  N + (7 - 3) % 7
DATEADD(DD,
            - ((DATEPART(dw,  DATEADD(DD, - (DATEPART(DD, (DATEADD(MM, 1, DayInTime)))), DATEADD(MM, 1, DayInTime))))  + (7 -3) ) % 7   ,
            CONVERT(DATE, DATEADD(DD, - (DATEPART(DD, (DATEADD(MM, 1, DayInTime)))), DATEADD(MM, 1, DayInTime)))
        )
        AS [LastTuesdayOfTheMonth]

--Wednesday [DayOfWeek] = 4.. Need to convert 4 to 0 <-> N + (7 - 4) % 7
DATEADD(DD,
            - ((DATEPART(dw,  DATEADD(DD, - (DATEPART(DD, (DATEADD(MM, 1, DayInTime)))), DATEADD(MM, 1, DayInTime)))) + (7 - 4) ) % 7   ,
            CONVERT(DATE, DATEADD(DD, - (DATEPART(DD, (DATEADD(MM, 1, DayInTime)))), DATEADD(MM, 1, DayInTime)))
        )
        AS [LastWednesdayOfTheMonth]

--Thursday [DayOfWeek] = 5.. Need to convert 5 to 0 <-> N + (7 - 5 ) % 7
DATEADD(DD,
            - ((DATEPART(dw,  DATEADD(DD, - (DATEPART(DD, (DATEADD(MM, 1, DayInTime)))), DATEADD(MM, 1, DayInTime))))  + (7 - 5) ) % 7   ,
            CONVERT(DATE, DATEADD(DD, - (DATEPART(DD, (DATEADD(MM, 1, DayInTime)))), DATEADD(MM, 1, DayInTime)))
        )
        AS [LastThursdayOfTheMonth]

--Friday [DayOfWeek] = 6.. Need to convert 6 to 0 <-> N + (7 - 6 ) % 7
DATEADD(DD,
            - ((DATEPART(dw,  DATEADD(DD, - (DATEPART(DD, (DATEADD(MM, 1, DayInTime)))), DATEADD(MM, 1, DayInTime))))  + (7-6) ) % 7   ,
            CONVERT(DATE, DATEADD(DD, - (DATEPART(DD, (DATEADD(MM, 1, DayInTime)))), DATEADD(MM, 1, DayInTime)))
        )
        AS [LastFridayOfTheMonth]


--Saturday [DayOfWeek] = 7.. Need to convert 7 to 0 <-> N + (7 - 7 ) % 7
DATEADD(DD,
            - ((DATEPART(dw,  DATEADD(DD, - (DATEPART(DD, (DATEADD(MM, 1, DayInTime)))), DATEADD(MM, 1, DayInTime))))  + (7-7) ) % 7   ,
            CONVERT(DATE, DATEADD(DD, - (DATEPART(DD, (DATEADD(MM, 1, DayInTime)))), DATEADD(MM, 1, DayInTime)))
        )
        AS [LastsSaturdayOfTheMonth]

我希望这对其他人有用,或者有人能够指出执行这些操作的更简单方法。

扩展 [Matts] SQL Server 2012 的答案:

下面的代码适用于 SQL Server 2012 及更高版本,并希望清楚地说明如何识别每一天。 (如果不是,请告诉我,我会澄清)

DECLARE @YearsToPopulate INT = 130;

-- Use the Magic of SQL to identify 1 Jan and then 31st December at the various edges of the required date time frames.
DECLARE @Date1 DATE = DATEADD(yy, DATEDIFF(yy,0,DATEADD(yyyy,-@YearsToPopulate,GETDATE())), 0);
DECLARE @Date2 DATE = DATEADD(yy, DATEDIFF(yy,0,DATEADD(yyyy,@YearsToPopulate,GETDATE())) + 1, -1);

DECLARE @RecordsToCreate INT =  DATEDIFF(dd,@Date1,@Date2);

WITH MyFullDateRange AS 
(
   SELECT TOP (@RecordsToCreate)  CAST(DATEADD(dd, Number, @Date1) AS DATE) AS DayInTime
   FROM ref.[dimNumbers]
) 

SELECT  DayInTime
    --Sunday [DayOfWeek] = 1.. Need to convert 1 to 0 <-> N + (0 - 1) % 7
    ,LastSundayOfMonth = DATEADD(DAY, 0 - (@@DATEFIRST - 1 + DATEPART(dw,EOMONTH(DayInTime))) % 7, EOMONTH(DayInTime))

    --Monday [DayOfWeek] = 2.. Need to convert 2 to 0 <->  N + (0 - 2) % 7   
    ,LastMondayOfMonth = DATEADD(DAY, 0 - (@@DATEFIRST - 2 + DATEPART(dw,EOMONTH(DayInTime))) % 7, EOMONTH(DayInTime))

    --Tuesday [DayOfWeek] = 3.. Need to convert 2 to 0 <->  N + (0 - 3) % 7   
    ,LastTuesdayOfMonth = DATEADD(DAY, 0 - (@@DATEFIRST - 3 + DATEPART(dw,EOMONTH(DayInTime))) % 7, EOMONTH(DayInTime))

    --Wednesday [DayOfWeek] = 4.. Need to convert 4 to 0 <->  N + (0 - 4) % 7   
    ,LastWednesdayOfMonth = DATEADD(DAY, 0 - (@@DATEFIRST - 4 + DATEPART(dw,EOMONTH(DayInTime))) % 7, EOMONTH(DayInTime))

    --Thursday [DayOfWeek] = 5.. Need to convert 2 to 0 <->  N + (0 - 5) % 7   
    ,LastThursdayOfMonth = DATEADD(DAY, 0 - (@@DATEFIRST - 5 + DATEPART(dw,EOMONTH(DayInTime))) % 7, EOMONTH(DayInTime))

    --Friday [DayOfWeek] = 6.. Need to convert 2 to 0 <->  N + (0 - 6) % 7   
    ,LastFridayOfMonth = DATEADD(DAY, 0 - (@@DATEFIRST - 6 + DATEPART(dw,EOMONTH(DayInTime))) % 7, EOMONTH(DayInTime))

    --Saturday [DayOfWeek] = 7.. Need to convert 2 to 0 <->  N + (0 - 7) % 7   
    ,LastSaturdayOfMonth = DATEADD(DAY, 0 - (@@DATEFIRST - 7 + DATEPART(dw,EOMONTH(DayInTime))) % 7, EOMONTH(DayInTime))

FROM MyFullDateRange;

如果我不必支持 SQL Server 2008,我会使用此代码。

更多 SQL Server 2012 及以上代码:

不幸的是,我所希望的一个好的替代方案并没有起作用。请记住,该解决方案需要适合 dimDate 表。有什么建议可以改进这一点吗?我不喜欢这个解决方案的部分是我需要对 dimDate 表执行两次操作来更新它。

DECLARE @YearsToPopulate INT = 130;

DECLARE @Date1 DATE = DATEADD(yy, DATEDIFF(yy,0,DATEADD(yyyy,-@YearsToPopulate,GETDATE())), 0);
DECLARE @Date2 DATE = DATEADD(yy, DATEDIFF(yy,0,DATEADD(yyyy,@YearsToPopulate,GETDATE())) + 1, -1);

DECLARE @RecordsToCreate INT =  DATEDIFF(dd,@Date1,@Date2);

WITH MyFullDateRange AS 
(
   SELECT TOP (@RecordsToCreate)  CAST(DATEADD(dd, Number, @Date1) AS DATE) AS DayInTime
   FROM ref.[dimNumbers]
) 
, CreateListOfDatesAndDOWs As 
(
    Select DayInTime AS DayInTime,  DatePART( DW , DayInTime ) AS DayNumber
    From MyFullDateRange
)

 Select DayInTime AS [currentDate]--SQL 2012 --   DateFromParts(Year(DayInTime),Month(DayInTime) , 1) AS [currentDate]
       ,LastSun = MAX(CASE WHEN  DayNumber=1 THEN DayInTime END)
       ,LastMon = MAX(CASE WHEN  DayNumber=2 THEN DayInTime END)
       ,LastTue = MAX(CASE WHEN  DayNumber=3 THEN DayInTime END)
       ,LastWed = MAX(CASE WHEN  DayNumber=4 THEN DayInTime END)
       ,LastThu = MAX(CASE WHEN  DayNumber=5 THEN DayInTime END)
       ,LastFri = MAX(CASE WHEN  DayNumber=6 THEN DayInTime END)
       ,LastSat = MAX(CASE WHEN  DayNumber=7 THEN DayInTime END)
 From  CreateListOfDatesAndDOWs

 Group By DayInTime --DateFromParts(Year(DayInTime),Month(DayInTime),1)--SQL 2012

因为这会返回以下结果集,对于 dimDate 表来说,这不是我们想要的结果。 (我需要弄清楚如何格式化表格!)

currentDate LastSun    LastMon    LastTue    LastWed    LastThu    LastFri    LastSat
----------- ---------- ---------- ---------- ---------- ---------- ---------- ----------
1886-01-01  NULL       NULL       NULL       NULL       NULL       1886-01-01 NULL
1886-01-02  NULL       NULL       NULL       NULL       NULL       NULL       1886-01-02
1886-01-03  1886-01-03 NULL       NULL       NULL       NULL       NULL       NULL
1886-01-04  NULL       1886-01-04 NULL       NULL       NULL       NULL       NULL
1886-01-05  NULL       NULL       1886-01-05 NULL       NULL       NULL       NULL
1886-01-06  NULL       NULL       NULL       1886-01-06 NULL       NULL       NULL
1886-01-07  NULL       NULL       NULL       NULL       1886-01-07 NULL       NULL
1886-01-08  NULL       NULL       NULL       NULL       NULL       1886-01-08 NULL
1886-01-09  NULL       NULL       NULL       NULL       NULL       NULL       1886-01-09
1886-01-10  1886-01-10 NULL       NULL       NULL       NULL       NULL       NULL
1886-01-11  NULL       1886-01-11 NULL       NULL       NULL       NULL       NULL
1886-01-12  NULL       NULL       1886-01-12 NULL       NULL       NULL       NULL
1886-01-13  NULL       NULL       NULL       1886-01-13 NULL       NULL       NULL
1886-01-14  NULL       NULL       NULL       NULL       1886-01-14 NULL       NULL
1886-01-15  NULL       NULL       NULL       NULL       NULL       1886-01-15 NULL
1886-01-16  NULL       NULL       NULL       NULL       NULL       NULL       1886-01-16
1886-01-17  1886-01-17 NULL       NULL       NULL       NULL       NULL       NULL
1886-01-18  NULL       1886-01-18 NULL       NULL       NULL       NULL       NULL
1886-01-19  NULL       NULL       1886-01-19 NULL       NULL       NULL       NULL
1886-01-20  NULL       NULL       NULL       1886-01-20 NULL       NULL       NULL
1886-01-21  NULL       NULL       NULL       NULL       1886-01-21 NULL       NULL
1886-01-22  NULL       NULL       NULL       NULL       NULL       1886-01-22 NULL
1886-01-23  NULL       NULL       NULL       NULL       NULL       NULL       1886-01-23
1886-01-24  1886-01-24 NULL       NULL       NULL       NULL       NULL       NULL
1886-01-25  NULL       1886-01-25 NULL       NULL       NULL       NULL       NULL
1886-01-26  NULL       NULL       1886-01-26 NULL       NULL       NULL       NULL
1886-01-27  NULL       NULL       NULL       1886-01-27 NULL       NULL       NULL
1886-01-28  NULL       NULL       NULL       NULL       1886-01-28 NULL       NULL
1886-01-29  NULL       NULL       NULL       NULL       NULL       1886-01-29 NULL
1886-01-30  NULL       NULL       NULL       NULL       NULL       NULL       1886-01-30
1886-01-31  1886-01-31 NULL       NULL       NULL       NULL       NULL       NULL
1886-02-01  NULL       1886-02-01 NULL       NULL       NULL       NULL       NULL
1886-02-02  NULL       NULL       1886-02-02 NULL       NULL       NULL       NULL
1886-02-03  NULL       NULL       NULL       1886-02-03 NULL       NULL       NULL
1886-02-04  NULL       NULL       NULL       NULL       1886-02-04 NULL       NULL
1886-02-05  NULL       NULL       NULL       NULL       NULL       1886-02-05 NULL
1886-02-06  NULL       NULL       NULL       NULL       NULL       NULL       1886-02-06

最佳答案

这是另一个快速选项。这将返回每个月的最后一个工作日

使用临时计数表和窗口函数 Row_Number()。

只是为了好玩,您可以在第一个、第二个......每月、每季度、每年的星期一进行另一个字段计数

Edit - Added the final conditional aggregation

Declare @Date1 date = '2000-01-01'
Declare @Date2 date = DateAdd(YY,130,@Date1)
;with cte0(N) As (Select 1 From (Values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) N(N))
     ,cteD(D) As (Select Top (DateDiff(DD,@Date1,@Date2)) cast(DateAdd(DD,Row_Number() over (Order By (Select NULL))-1,@Date1) as date) From cte0 N1, cte0 N2, cte0 N3, cte0 N4, cte0 N5, cte0 N6) -- 1 Million
     ,cte     As (
                    Select Date=D
                          ,DOW       = DateName(DW,D) 
                          ,DOWPosNeg = Row_Number() over (Partition By Year(D),Month(D),DateName(DW,D) Order by D Desc)
                    From cteD
                 )
 Select Date    = DateFromParts(Year(Date),Month(Date),1)
       ,LastSun = max(case when DOW='Sunday'    then Date else null end)
       ,LastMon = max(case when DOW='Monday'    then Date else null end)
       ,LastTue = max(case when DOW='Tuesday'   then Date else null end)
       ,LastWed = max(case when DOW='Wednesday' then Date else null end)
       ,LastThu = max(case when DOW='Thursday'  then Date else null end)
       ,LastFri = max(case when DOW='Friday'    then Date else null end)
       ,LastSat = max(case when DOW='Saturday'  then Date else null end)
 From  cte
 Where DOWPosNeg = 1
 Group By DateFromParts(Year(Date),Month(Date),1)
 Order BY 1

返回

enter image description here

关于SQL Server 日期表 : programatically find the last Sunday in a given month,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40953682/

相关文章:

mysql - SQL - 在 BigDecimal 中转换两个别名

c# - 命名空间 'DataSetExtensions' 中不存在类型或命名空间名称 'System.Data'(是否缺少程序集引用?)

sql - TSQL 日期时间 ISO 8601

sql - 为什么递归 CTE 按程序运行分析函数 (ROW_NUMBER)?

sql-server - 愚蠢用户的可用 View ?

sql-server - SQL持续时间计算

mysql - 从表中检索按条件分组的所有行

asp.net - 执行sql脚本,不等待完成

sql - 合并多对多关系连接中的列值

sql-server - 无法在 ap-southeast-2 中创建 db.r3.8xlarge SQL Server EE 实例