[英]Breaking down a date range in to number of months in each calendar year
I have a large number of date ranges as below, example below.我有大量的日期范围,如下所示,示例如下。 I need to calculate how many months are in each actual calendar year.
我需要计算每个实际日历年中有多少个月。 So this would break down as:
所以这将分解为:
Contract: 123合约: 123
Start date: 01/11/2016开始日期: 01/11/2016
End date: 01/06/2018结束日期: 01/06/2018
Contract: 456合约: 456
Start date: 31/05/2017开始日期: 31/05/2017
End date: 01/06/2019结束日期: 01/06/2019
Does anyone know of a solution to handle this?有谁知道解决这个问题的解决方案? Each contract has a row, all in the same table and the start and end date listed in the same row.
每个合同都有一行,都在同一个表中,开始和结束日期列在同一行中。
I was originally going down the CTE route but this blew my mind.我最初是沿着 CTE 路线走的,但这让我大吃一惊。
Expected outcome:预期结果:
contract_id year number of months
123 2016 2
123 2017 12
123 2018 6
456 2017 6
456 2018 12
456 2019 6
Or similar, I am more than happy to amend my original query to incorporate what the best outcome/method to achieve this is.或类似的,我很乐意修改我的原始查询以合并实现这一目标的最佳结果/方法。
Table definition:表定义:
end_date: datetime end_date:日期时间
contract_id start_date end_date 123 2016-01-11 00:00:00.000 2018-06-01 00:00:00.000 456 2017-05-31 00:00:00.000 2019-06-01 00:00:00.000 contract_id start_date end_date 123 2016-01-11 00:00:00.000 2018-06-01 00:00:00.000 456 2017-05-31 00:00:00.000 2019:0006:0006:0006
I would use a tally for this.我会为此使用一个计数。 I keep one on my system as a view which is lightning fast.
我在我的系统上保留了一个视图,它的速度快如闪电。 Here is the view.
这是视图。
create View [dbo].[cteTally] as
WITH
E1(N) AS (select 1 from (values (1),(1),(1),(1),(1),(1),(1),(1),(1),(1))dt(n)),
E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows
E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max
cteTally(N) AS
(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
)
select N from cteTally
GO
Then we need some sample data.然后我们需要一些样本数据。 Something like this.
像这样的东西。
declare @Something table
(
Contract char(1)
, StartDate date
, EndDate date
)
insert @Something values
('A', '20161101', '20180601')
, ('B', '20170531', '20190601')
Now we can query against the sample and utilize the tally table to make short work of this.现在我们可以查询样本并利用计数表来简化此工作。
select s.Contract
, ContractYear = datepart(year, DATEADD(month, t.N - 1, s.StartDate))
, NumMonths = count(*)
from @Something s
join cteTally t on t.N <= datediff(month, s.StartDate, s.EndDate) + 1
group by s.Contract
, datepart(year, DATEADD(month, t.N - 1, s.StartDate))
order by s.Contract
, datepart(year, DATEADD(month, t.N - 1, s.StartDate))
You can use master..spt_values with type = 'P' to get numbers from 0 to 2047. Filtering this number so it is between year of start date and year of end date and you get the years between the two dates as rows.您可以使用带有 type = 'P' 的 master..spt_values 来获取从 0 到 2047 的数字。过滤这个数字,使其位于开始日期的年份和结束日期的年份之间,并且您将两个日期之间的年份作为行。 The EndOfYear and BeginOfYear returns the first date, respectively last date, of each of this years.
EndOfYear 和 BeginOfYear 分别返回今年每一年的第一个日期和最后一个日期。 Months returns the months between the first and last date.
Months 返回第一个日期和最后一个日期之间的月份。
DECLARE @Table TABLE
(
Contract VARCHAR(5),
StartDate DATETIME,
EndDate DATETIME
)
INSERT INTO @Table(Contract, StartDate, EndDate)
SELECT 'A', '20161101', '20180601' UNION ALL
SELECT 'B', '20170531', '20190601'
SELECT Contract,
Year = spt_values.number,
Months = Months.Value
FROM @Table CROSS JOIN
master..spt_values CROSS APPLY
(
SELECT CAST(CONCAT(spt_values.number, '1231') AS DATETIME) AS Value
) AS EndOfYear CROSS APPLY
(
SELECT DATEADD(YEAR, -1, EndOfYear.Value) + 1 AS Value
) AS BeginOfYear CROSS APPLY
(
SELECT DATEDIFF(MONTH, IIF(BeginOfYear.Value < StartDate, StartDate, BeginOfYear.Value), IIF(EndOfYear.Value > EndDate, EndDate, EndOfYear.Value)) + 1 AS Value
) Months
WHERE type = 'P' AND
spt_values.number <= YEAR(EndDate) AND
spt_values.number >= YEAR(StartDate)
Here's a possibility:这是一种可能性:
select t.contract_id,n.id as year,q2.[#months]
from yourtable t
cross apply
(
select year([Start Date]) as first_year,
select year([End Date]) as last_year
)q
inner join numbers_table n on n.id between q.first_year and q.last_year
cross apply
(
select case
when n.id=first_year then 12-month([Start Date])
when n.id=last_year then month([End Date])
else 12
end as [#months]
)q2
If you don't have a numbers table, put this before the query:如果您没有数字表,请将其放在查询之前:
;WITH numbers_table(id) AS
(
SELECT ROW_NUMBER() OVER (ORDER BY s1.[object_id]) - 1
FROM sys.all_columns AS s1
CROSS JOIN sys.all_columns AS s2
)
I would go for the tested variation of Sean Lange, though不过,我会选择 Sean Lange 的经过测试的变体
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.