简体   繁体   English

将日期范围分解为每个日历年的月数

[英]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

  • 01/11/2016 > 31/12/2016 - 2 months in year 2016 01/11/2016 > 31/12/2016 - 2016 年的 2 个月
  • 01/01/2017 > 31/12/2017 - 12 months in year 2017 01/01/2017 > 31/12/2017 - 2017 年 12 个月
  • 01/01/2018 > 01/06/2018 - 6 months in year 2018 01/01/2018 > 01/06/2018 - 2018 年 6 个月

Contract: 456合约: 456

Start date: 31/05/2017开始日期: 31/05/2017

End date: 01/06/2019结束日期: 01/06/2019

  • 31/05/2017 > 31/12/2017 - 6 months in year 2017 31/05/2017 > 31/12/2017 - 2017 年 6 个月
  • 01/01/2018 > 31/12/2018 - 12 months in year 2018 01/01/2018 > 31/12/2018 - 2018 年 12 个月
  • 01/01/2019 > 01/06/2019 - 6 months in year 2019 01/01/2019 > 01/06/2019 - 2019 年 6 个月

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:表定义:

  • contract_id: int合同 ID:整数
  • start_date: datetime开始日期:日期时间
  • 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM