给定示例结束日期和季度编号,计算日期的季度
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;
如果您需要同时支持多个会计日历,只需添加一列(如CalendarID
或CompanyID
或CustomerID
)。
实际上,您甚至不需要日历或季度 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)
我最终创建了一个函数来处理这个问题。由于给出了季度的最后一天、它是哪个季度以及它是哪一年,我可以确定该财政年度的开始和结束日期。由于一个季度总是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
我有一个问题,我需要确定财政季度,但并不总是知道季度的 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;
如果您需要同时支持多个会计日历,只需添加一列(如CalendarID
或CompanyID
或CustomerID
)。
实际上,您甚至不需要日历或季度 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)
我最终创建了一个函数来处理这个问题。由于给出了季度的最后一天、它是哪个季度以及它是哪一年,我可以确定该财政年度的开始和结束日期。由于一个季度总是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