【问题标题】:Find the First Wednesday after the last Tuesday of last financial month查找上一个财政月最后一个星期二之后的第一个星期三
【发布时间】:2015-09-23 07:52:58
【问题描述】:

我的一个客户(出于奇怪的财务原因)将财务月定义为 从一个月的最后一个星期二(含)之后的星期三开始并持续到下个月的最后一个星期二的时间段(含)

我需要找到上一个和当前财政月份的开始日期。

一些例子:

如果今天是 2015 年 9 月 23 日,我需要得到 7 月 29 日和 8 月 26 日,因为当前的财政月份是从 8 月 26 日到 9 月 29 日。

如果今天是 2015 年 9 月 30 日,我需要得到 8 月 26 日到 9 月 30 日。

我有不同的客户,他们有不同的定义,这意味着他们中的一些人使用星期三,其他人使用星期一,所以我需要将这一天作为参数,比如星期一 = 1 和星期三 = 3。我称之为 FDOM,FirstDayOfMonth .

到目前为止,我的工作重点是使用我在当前和上个月的第一天和最后一天找到的公式,并进行了修改以考虑 FDOM。我设法得到了上个月的最后一个星期三,但这有时是不正确的,因为我正在考虑一个月中属于太阳月但也属于下一个财政月的一天,比如 9 月 30 日属于太阳 9 月但属于财政 10 月, 10 月财政从 9 月 30 日开始。

DECLARE @BASE AS DateTime = '19000101 00:00'

DECLARE @FDOM AS INT = 3 --Wednesday

DECLARE @Datevalue AS DATE = GETDATE()

SET DATEFIRST @FDOM

select DATEADD(D,1-(DATEPART(dw,DATEADD(D,-1,DATEADD(MONTH, DATEDIFF(MONTH, @BASE, @Datevalue) , @BASE)))),DATEADD(D,-1,DATEADD(MONTH, DATEDIFF(MONTH, @BASE, @Datevalue) , @BASE)))

这给了我上个月最后一个星期二之后的第一个星期三,从 9 月 1 日到 9 月 29 日(它给 8 月 26 日)作为“当前财政月的开始”是正确的。但它在 9 月 30 日是错误的,因为它应该给出 9 月 30 日,并且从 8 月 26 日到 8 月底也是错误的,因为它应该给出 8 月 26 日,但是却给出了 7 月 29 日。

【问题讨论】:

  • 那么 2015 年 4 月的财务数据是从 4 月 1 日到 4 月 28 日(但您想找到 4 月 1 日和 4 月 29 日的日期)?
  • 如果今天是4月29日到5月26日(含)我需要拿到4月1日到4月29日,是的
  • 换句话说:我正在为 SSRS 创建订阅,当我运行报告时,我需要它大约是最后一个完整的财政月。因此,如果我从 4 月 29 日到 5 月 26 日(含)运行它,那么最后一个完整的财政月应该是财政 4 月,即从 4 月 1 日(含)到 4 月 29 日(不含)
  • 无论如何,不​​要专注于我谈到 SSRS 的事实。我正在寻找的公式应该是通用的,我应该能够在任何地方使用它,这就是为什么我强迫自己避免 CTE 和自定义函数,因为我不知道我是否总是被允许使用它们。我认为一个 CASE 应该足以处理 今天 是一个太阳月的一部分,同时又是下一个财政月的一部分
  • 您的意思是“2015 年 8 月 23 日”而不是“2015 年 9 月 23 日”,对吧?

标签: sql-server tsql date financial


【解决方案1】:

认为这可以满足您的要求。它很长,但希望通过分解和命名,我清楚地说明了我们如何得到最终答案,所以如果它不太正确,可以进行调整:

declare @FDOM int
set @FDOM = 3 --Wednesday. 0 = Sunday, 6 = Saturday

declare @KnownDay datetime
set @KnownDay = DATEADD(day,@FDOM - 1,'20150301') --Offset from a "known good" Sunday to the day before FDOM
declare @EOLastDec datetime
set @EOLastDec = DATEADD(year,DATEDIFF(year,'20010101',GETDATE()),'20001231')
declare @Today datetime
set @Today = DATEADD(day,DATEDIFF(day,0,GETDATE()),0) --You can change this to test other key dates

;With Numbers(n) as (--If you have a numbers table, you can skip this CTE
    select ROW_NUMBER() OVER (ORDER BY so1.object_id) - 1
    from sys.objects so1 cross join sys.objects so2
), LastOfMonths as (
    select DATEADD(month,n,@EOLastDec) as LOM
    from Numbers
    where n between 0 and 13
), LastImportant as (
    select DATEADD(day,-n,LOM) as EOFMonth
    from LastOfMonths cross join Numbers
    where n between 0 and 6 and
    DATEPART(weekday,DATEADD(day,-n,LOM)) = DATEPART(weekday,@KnownDay)
)
select DATEADD(day,1,li0.EOFMonth) as StartOfMonth,DATEADD(day,1,li1.EOFMonth) as EndOfMonth
from
    LastImportant li1
        cross join
    LastImportant li2
        left join
    LastImportant li1_anti
        on
            li1.EOFMonth < li1_anti.EOFMonth and
            li1_anti.EOFMonth <= @Today
        left join
    LastImportant li2_anti
        on
            li2.EOFMonth > li2_anti.EOFMonth and
            li2_anti.EOFMonth >= @Today
        inner join
    LastImportant li0
        on
            li0.EOFMonth < li1.EOFMonth
        left join
    LastImportant li0_anti
        on
            li0_anti.EOFMonth < li1.EOFMonth and
            li0.EOFMonth < li0_anti.EOFMonth
where
    li1.EOFMonth <= @Today and
    li2.EOFMonth >= @Today and
    li1_anti.EOFMonth is null and
    li2_anti.EOFMonth is null and
    li0_anti.EOFMonth is null

希望 CTE 具有合理的解释性。我们生成一个数字表,然后计算每个月的最后一天,然后从那里向后最多 6 天以找到正确类型的一天(即星期二,如果 @FDOM 为 3)

我最初只使用 li1li2(以及 li1_antili2_anti)进行了更简单的最终查询,但我意识到查询只是查找当前财务月份 - 所以我添加了另外一对的连接数(使用li0li0_anti)来查找上一个财政月的开始。

【讨论】:

    【解决方案2】:

    计算上个月的开始日期和当月的最后一天,并使用 CTE 生成它们之间的所有日期。稍后,获取两个月的 MAX 个工作日。

    DECLARE @CurrentDate    DATE = '2015-08-23'
    DECLARE @StartDate      DATE,
            @EndDate        DATE,
            @MonthEnd       INT = 3
    
    --  Get the first day from previous month and last day from current month
    SELECT  @StartDate      = DATEADD(MONTH , DATEDIFF(MONTH, 0, @CurrentDate)-1, 0),
            @EndDate        = DATEADD(SECOND,-1, 
                              DATEADD(MONTH , DATEDIFF(MONTH, 0, @CurrentDate)+1,0))
    
    ;WITH   Calendar    AS
    (       -- Generate all dates between @StartDate and @EndDate
            SELECT  @StartDate  [Date]
            UNION   ALL
            SELECT  DATEADD(D, +1, Calendar.[Date])
            FROM    Calendar
            WHERE   Calendar.[Date] < @EndDate
    )
    SELECT  DATEADD(DAY, +1, MAX(StartDate.[Date])) StartDate,
            DATEADD(DAY, +1, MAX(EndDate  .[Date])) EndDate
    FROM    Calendar    StartDate,
            Calendar    EndDate
    WHERE   -- Get the max weekday from previous month
            DATEPART(MONTH  , StartDate.[Date]) = DATEPART(MONTH, @StartDate) AND
            DATEPART(WEEKDAY, StartDate.[Date]) = @MonthEnd AND
            -- Get the max weekday from current month
            DATEPART(MONTH  , EndDate  .[Date]) = DATEPART(MONTH, @EndDate) AND
            DATEPART(WEEKDAY, EndDate  .[Date]) = @MonthEnd
    

    【讨论】:

      【解决方案3】:

      试试这个。您可以使用EOMONTH 函数在 Sql Server 2012 或更高版本上获取月末。

      Click to see the fiddle demo.

      DECLARE @date DATETIME = GETDATE()
      DECLARE @LastMonthEnd DATETIME = DATEADD(day, -1, DATEADD(month, DATEDIFF(month, 0, @date), 0)) 
      DECLARE @CurrentMonthEnd DATETIME =  DATEADD(day, -1, DATEADD(month, DATEDIFF(month, 0, @date) + 1, 0)) 
      
      
      SET DATEFIRST 1
      
      ;WITH CTE1 AS
      (
          SELECT 1 number, DATEPART(WEEKDAY, @LastMonthEnd) FirstDay, 
                           DATEPART(WEEKDAY, @CurrentMonthEnd) LastDay    
          UNION ALL
      
          SELECT 1+number,  DATEPART(WEEKDAY, DATEADD(DAY, -number, @LastMonthEnd)),  
                            DATEPART(WEEKDAY, DATEADD(DAY, -number, @CurrentMonthEnd))
          FROM CTE1 
          WHERE number < 7
      )
      SELECT DATEADD(DAY, -(SELECT Number FROM CTE1 WHERE FirstDay = 3), @LastMonthEnd) StartDate,
             DATEADD(DAY, -(SELECT Number FROM CTE1 WHERE LastDay = 3), @CurrentMonthEnd) EndDate
      

      【讨论】:

        【解决方案4】:

        经过大量努力,我设法找到了一个不使用 CTE 的表达式,因为我不确定我不能在所有必须使用它的地方使用 CTE。

        所以基本上我首先通过 CASE 来了解我正在考虑的日期是否在它所属月的最后一个星期三之前或之后。然后我返回一个月前的最后一个星期三和一个月前的最后一个星期三或一个月前和本月的最后一个星期三。 这也改变了 FDOM,我在今年的几个月里测试了它。它似乎总是有效。 可能使用 EOMonth 会缩短它,但我必须验证我可以在我的服务器中使用它。

        很抱歉,我只是指定了一个要求,即我不能仅在 cmets 中使用 CTE,但感谢您的帮助

        DECLARE @datevalue AS Datetime = getdate()
        DECLARE @BASE AS DateTime = '19000101 00:00'
        DECLARE @FDOM AS INT = 3 --1 is for Monday
        SET DATEFIRST @FDOM
        
        SELECT CASE WHEN (@datevalue < DATEADD(D, 1-(DATEPART(dw,DATEADD(MONTH, DATEDIFF(MONTH, @BASE, @datevalue) + 1, @BASE))),DATEADD(MONTH, DATEDIFF(MONTH, @BASE, @datevalue) + 1, @BASE)))
                THEN(DATEADD(D, 1-(DATEPART(dw,DATEADD(MONTH, DATEDIFF(MONTH, @BASE, @datevalue) - 1, @BASE))),DATEADD(MONTH, DATEDIFF(MONTH, @BASE, @datevalue) - 1, @BASE)))
                ELSE(DATEADD(D, 1-(DATEPART(dw,DATEADD(MONTH, DATEDIFF(MONTH, @BASE, @datevalue) - 0, @BASE))),DATEADD(MONTH, DATEDIFF(MONTH, @BASE, @datevalue) - 0, @BASE)))
                END AS [Start of Last Financial Month]
        ,CASE WHEN (@datevalue < DATEADD(D, 1-(DATEPART(dw,DATEADD(MONTH, DATEDIFF(MONTH, @BASE, @datevalue) + 1, @BASE))),DATEADD(MONTH, DATEDIFF(MONTH, @BASE, @datevalue) + 1, @BASE)))
                THEN(DATEADD(D, 1-(DATEPART(dw,DATEADD(MONTH, DATEDIFF(MONTH, @BASE, @datevalue) + 0, @BASE))),DATEADD(MONTH, DATEDIFF(MONTH, @BASE, @datevalue) + 0, @BASE)))
                ELSE(DATEADD(D, 1-(DATEPART(dw,DATEADD(MONTH, DATEDIFF(MONTH, @BASE, @datevalue) + 1, @BASE))),DATEADD(MONTH, DATEDIFF(MONTH, @BASE, @datevalue) + 1, @BASE)))
                END AS [Start of Current Financial Month]
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2020-06-21
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多