查找上个财政月最后一个星期二之后的第一个星期三

Find the First Wednesday after the last Tuesday of last financial month

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

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

一些示例:

如果今天是 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 月 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 日)这将是正确的 "the beginning of the current financial month"。但是 9 月 30 日是错误的,因为它应该给出 9 月 30 日,从 8 月 26 日到 8 月底也是错误的,因为它应该给出 8 月 26 日,而不是 7 月 29 日。

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

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 具有合理的解释性。我们生成一个数字 table,然后我们计算每个月的最后一天,然后从那里开始,我们最多向后退 6 天以找到正确类型的一天(即星期二,如果 @FDOM 是 3 )

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

试试这个。您可以使用 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

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

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

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

所以基本上首先我了解一个案例,如果我正在考虑的日期是在它所属的月份的最后一个星期三之前或之后。然后我 return 两个月前的最后一个星期三或一个月前和这个月的最后一个星期三。 这也可以改变 FDOM,我今年对它进行了几个月的测试。它似乎总是有效。 可能使用 EOMonth 会缩短它,但我必须验证我可以在我的服务器中使用它。

很抱歉,我只是在评论中指定了不能仅使用 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]