给定示例结束日期和季度编号,计算日期的季度

Calculate quarter for dates given an example end date and quarter number

我有一个问题,我需要确定财政季度,但并不总是知道季度的 start/end 日期。但是,它们将始终为 3 个月。我将知道的是当前季度的结束日期,以及所指的季度和年份。例如,我可能会得到:

当前季度:第 4 季度
当前年份:2021
当前季度结束日期:1/31/2021

我怎样才能得到任何其他日期的季度?如果这 3 个值中的任何一个发生变化,查询仍然需要根据这 3 个参数提供任何给定日期的季度。

我想到了以下内容,将过去 4 年放入临时表 table:

DECLARE @QuarterEnd             DATE            =   '1/31/2022'
,       @CurrentQuarter         INT             =   1
,       @CurrentYear            INT             =   2022
,       @Counter                INT             =   16
,       @qs                     INT             =   0
,       @qe                     INT             =   2
,       @DateToTest             DATE            =   '12/15/2021'

CREATE TABLE #Quarters (
        StartDate   DATE
,       EndDate     DATE
,       Qtr         INT
,       Yr          INT
)

WHILE @Counter <> 0
BEGIN
    INSERT INTO #Quarters VALUES (
            cast(DATEADD(MONTH, DATEDIFF(MONTH, 0, @QuarterEnd)-@qe , 0) as date)
        ,   cast(DATEADD(MONTH, DATEDIFF(MONTH, -1, @QuarterEnd)-@qs, -1) as date)
        ,   @CurrentQuarter
        ,   @CurrentYear
    )
    SET @Counter = @Counter - 1
    SET @qs = @qs + 3
    SET @qe = @qe + 3
    SET @CurrentQuarter = CASE WHEN @CurrentQuarter = 1 THEN 4 ELSE @CurrentQuarter - 1 END
    SET @CurrentYear = CASE WHEN @CurrentQuarter = 4 THEN @CurrentYear - 1 ELSE @CurrentYear END
END

SELECT  @DateToTest
,       (SELECT CONCAT('Q', Qtr, ' ', Yr) FROM #Quarters WHERE @DateToTest BETWEEN StartDate and EndDate)
FROM    #Quarters

但是,当我 运行 查询将 return 数十万条记录时,这似乎并不实用。

我想我可以把它放到一个函数中并用以下方法调用它:

SELECT MyQuarter = dbo.MyQuarterFunction(@QuarterEnd, @CurrentQuarter, @CurrentYear, @DateToTest)

必须有更有效的方法来做到这一点。有什么建议吗?

只需创建一个名为 Quarters 的永久 table。

CREATE TABLE dbo.Quarters
(
  StartDate     date,
  QuarterNumber tinyint,
  FiscalYear    int,
  NextQuarterStartDate AS (DATEADD(MONTH, 3, StartDate))
);

INSERT dbo.Quarters(StartDate, QuarterNumber, FiscalYear)
  VALUES('20200201',1,2020),
        ('20200501',2,2020),
        ('20200801',3,2020),
        ('20201101',4,2020),
        ('20210201',1,2021),
        ('20210501',2,2021),
        ('20210801',3,2021),
        ('20211101',4,2021),
        ('20220201',1,2022),
        ('20220501',2,2022),
        ('20220801',3,2022),
        ('20221101',4,2022);

现在,只要给定日期(例如 GETDATE()),您就可以轻松找到其他信息:

DECLARE @date date = GETDATE();

SELECT * FROM dbo.Quarters 
  WHERE @date >= StartDate
  AND @date < NextQuarterStartDate;

如果您需要同时支持多个会计日历,只需添加一列(如CalendarIDCompanyIDCustomerID)。

实际上,您甚至不需要日历或季度 table。您已经有 table 个客户,对吧?只需添加一列来存储他们的财政年度开始的月份。这就是您真正需要的。

CREATE TABLE dbo.Clients
(
  ClientID        int           NOT NULL CONSTRAINT PK_Clients PRIMARY KEY,
  Name            nvarchar(200) NOT NULL CONSTRAINT UQ_ClientName UNIQUE,
  FiscalYearStart tinyint       NOT NULL CONSTRAINT CK_ValidMonth 
    CHECK (FiscalYearStart BETWEEN 1 AND 12)
);

现在让我们插入一些具有不同会计年度的客户的行:

INSERT dbo.Clients(ClientID, Name, FiscalYearStart)
  VALUES(1, N'ClientFeb',    2), -- fiscal year starts in February 
        (2, N'ClientMay',    5), -- fiscal year starts in May
        (3, N'ClientNormal', 1); -- fiscal year matches calendar

现在,是的,我们需要一个函数,但我们不要做任何 while 循环或计数器或#temp tables。

CREATE FUNCTION dbo.GetLast16Quarters
(
  @DateToTest date,
  @ClientID   int
)
RETURNS TABLE WITH SCHEMABINDING AS
RETURN 
(
  WITH n(n) AS
  (
    SELECT n = 1 UNION ALL 
    SELECT n + 1 FROM n WHERE n < 20
  ),
  Last20Quarters(QuarterStart, FiscalYearStart) AS
  (
    SELECT QuarterStart = DATEADD(QUARTER, 1-n, 
      DATEFROMPARTS(YEAR(@DateToTest)+1, FiscalYearStart, 1)), 
      FiscalYearStart
    FROM dbo.Clients CROSS JOIN n WHERE ClientID = @ClientID
  ),
  Last16Quarters AS
  (
    SELECT TOP (16) QuarterStart, 
      y = YEAR(DATEADD(MONTH, 1-FiscalYearStart, QuarterStart)) 
    FROM Last20Quarters WHERE QuarterStart < @DateToTest
    ORDER BY QuarterStart DESC
  )
  SELECT QuarterStart, 
         QuarterEnd = EOMONTH(QuarterStart, 2),
         FiscalYear = y,
         QuarterNumber = ROW_NUMBER() OVER 
           (PARTITION BY y ORDER BY QuarterStart)
  FROM Last16Quarters);

然后调用它:

DECLARE @DateToTest date = '20211215';
SELECT * FROM dbo.GetLast16Quarters(@DateToTest, 1);

输出:

QuarterStart QuarterEnd FiscalYear QuarterNumber
2018-02-01 2018-04-30 2018 1
2018-05-01 2018-07-31 2018 2
2018-08-01 2018-10-31 2018 3
2018-11-01 2019-01-31 2018 4
2019-02-01 2019-04-30 2019 1
2019-05-01 2019-07-31 2019 2
2019-08-01 2019-10-31 2019 3
2019-11-01 2020-01-31 2019 4
2020-02-01 2020-04-30 2020 1
2020-05-01 2020-07-31 2020 2
2020-08-01 2020-10-31 2020 3
2020-11-01 2021-01-31 2020 4
2021-02-01 2021-04-30 2021 1
2021-05-01 2021-07-31 2021 2
2021-08-01 2021-10-31 2021 3
2021-11-01 2022-01-31 2021 4

假设您有两个输入变量:

declare @quarter_end date = '2021-01-31';
declare @current_quarter int = 4;

您可以计算财政年度的第一个月:

declare @first_month_of_fy int = (month(@quarter_end) - @current_quarter * 3 + 12) % 12 + 1;
-- 2 i.e. February

然后使用该值使用一些数学计算任何日期的季度和年份:

select *
from (values
    ('2020-12-15'),
    ('2021-01-15'),
    ('2021-12-15'),
    ('2022-01-15')
) as t(testdate)
cross apply (select
    (month(testdate) - @first_month_of_fy + 12) % 12 + 1
) as ca1(month_of_fy)
cross apply (select
    (month_of_fy - 1) / 3 + 1,
    year(dateadd(month, 12 - month_of_fy, dateadd(day, - day(testdate) + 1, testdate)))
) as ca2(fy_quarter, fy_year)

DB<>Fiddle

我最终创建了一个函数来处理这个问题。由于给出了季度的最后一天、它是哪个季度以及它是哪一年,我可以确定该财政年度的开始和结束日期。由于一个季度总是3个月,我也可以确定哪些月份属于哪个季度。

前 4 个变量,@qa、@qb、@qc、@qd 保存每个季度内以逗号分隔的月份列表(@qa 是当前季度,@qb 是当前季度 -1,@qc 是当前季度 -2,@qd 是当前季度 -3)

后2个变量决定会计日历的第一天和最后一天

要获取季度和年份,我首先从提供的日期 (@Date) 中获取月份,然后查看它是否在 @qa、@qb、@qc 或 @qd 中。这告诉我财政季度。

最后,我将给定日期与当前财政年度的开始和结束日期以及之前的 6 年进行比较(回溯 6 年足以满足我的需要)

CREATE FUNCTION [dbo].[FunctionNameHere]
(
    @Date           DATE
,   @QuarterEnd     DATE
,   @CurrentQuarter INT
,   @CurrentYear    INT
)
RETURNS VARCHAR(7)
AS
BEGIN

DECLARE     @qa                 VARCHAR(8)  = (concat(datepart(m, dateadd(m,  0, @QuarterEnd)),',', datepart(m, dateadd(m, -1,  @QuarterEnd)),',', datepart(m, dateadd(m, -2,  @QuarterEnd))))
DECLARE     @qb                 VARCHAR(8)  = (concat(datepart(m, dateadd(m, -3, @QuarterEnd)),',', datepart(m, dateadd(m, -4,  @QuarterEnd)),',', datepart(m, dateadd(m, -5,  @QuarterEnd))))
DECLARE     @qc                 VARCHAR(8)  = (concat(datepart(m, dateadd(m, -6, @QuarterEnd)),',', datepart(m, dateadd(m, -7,  @QuarterEnd)),',', datepart(m, dateadd(m, -8,  @QuarterEnd))))
DECLARE     @qd                 VARCHAR(8)  = (concat(datepart(m, dateadd(m, -9, @QuarterEnd)),',', datepart(m, dateadd(m, -10, @QuarterEnd)),',', datepart(m, dateadd(m, -11, @QuarterEnd))))
DECLARE     @YearStart          DATE        = DATEADD(d, 1, DATEADD(q, -@CurrentQuarter, @QuarterEnd))
DECLARE     @YearEnd            DATE        = DATEADD(q, 4-@CurrentQuarter, @QuarterEnd)

DECLARE     @Qtr                VARCHAR(8)  =   CONCAT('Q', CASE WHEN DATEPART(m, @Date) IN (SELECT value FROM string_split(@qa, ',')) THEN @CurrentQuarter 

                                                                 WHEN DATEPART(m, @Date) IN (SELECT value FROM string_split(@qb, ',')) THEN CASE WHEN @CurrentQuarter = 1 THEN 4
                                                                                                                                                 WHEN @CurrentQuarter = 2 THEN 1
                                                                                                                                                 WHEN @CurrentQuarter = 3 THEN 2
                                                                                                                                                 WHEN @CurrentQuarter = 4 THEN 3 END

                                                                 WHEN DATEPART(m, @Date) IN (SELECT value FROM string_split(@qc, ',')) THEN CASE WHEN @CurrentQuarter = 1 THEN 3
                                                                                                                                                 WHEN @CurrentQuarter = 2 THEN 4
                                                                                                                                                 WHEN @CurrentQuarter = 3 THEN 1
                                                                                                                                                 WHEN @CurrentQuarter = 4 THEN 2 END

                                                                 WHEN DATEPART(m, @Date) IN (SELECT value FROM string_split(@qd, ',')) THEN CASE WHEN @CurrentQuarter = 1 THEN 2
                                                                                                                                                 WHEN @CurrentQuarter = 2 THEN 3
                                                                                                                                                 WHEN @CurrentQuarter = 3 THEN 4
                                                                                                                                                 WHEN @CurrentQuarter = 4 THEN 1 END
                                                END,
                                                ' ',
                                                CASE WHEN @Date BETWEEN @YearStart AND @YearEnd THEN @CurrentYear
                                                     WHEN @Date BETWEEN dateadd(Year, -1, @YearStart) AND dateadd(Year, -1, @YearEnd) THEN @CurrentYear - 1
                                                     WHEN @Date BETWEEN dateadd(Year, -2, @YearStart) AND dateadd(Year, -2, @YearEnd) THEN @CurrentYear - 2
                                                     WHEN @Date BETWEEN dateadd(Year, -3, @YearStart) AND dateadd(Year, -3, @YearEnd) THEN @CurrentYear - 3
                                                     WHEN @Date BETWEEN dateadd(Year, -4, @YearStart) AND dateadd(Year, -4, @YearEnd) THEN @CurrentYear - 4
                                                     WHEN @Date BETWEEN dateadd(Year, -5, @YearStart) AND dateadd(Year, -5, @YearEnd) THEN @CurrentYear - 5
                                                     WHEN @Date BETWEEN dateadd(Year, -6, @YearStart) AND dateadd(Year, -6, @YearEnd) THEN @CurrentYear - 6
                                                     ELSE 9999 END)

    return @Qtr

END