【问题标题】:Breaking down a date range in to number of months in each calendar year将日期范围分解为每个日历年的月数
【发布时间】:2019-08-03 00:25:42
【问题描述】:

我有大量的日期范围如下,示例如下。我需要计算每个实际日历年有多少个月。所以这将分解为:


合同: 123

开始日期: 2016 年 1 月 11 日

结束日期: 01/06/2018

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

合同: 456

开始日期: 2017 年 5 月 31 日

结束日期: 01/06/2019

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

有人知道解决这个问题的方法吗?每个合同都有一行,都在同一个表中,并且开始和结束日期列在同一行中。

我原本打算走 CTE 路线,但这让我大吃一惊。

预期结果:

contract_id    year    number of months
123            2016    2
123            2017    12
123            2018    6
456            2017    6
456            2018    12
456            2019    6

或者类似的,我非常乐意修改我的原始查询,以纳入实现这一目标的最佳结果/方法。


表定义:

  • contract_id: int
  • 开始日期:日期时间
  • 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

【问题讨论】:

  • 你能发布你期望的输出吗?还要发布表定义?
  • @SeanLange 是的 - 抱歉,已添加到原始问题中。
  • 你有一个数字表,即所有数字从 1 到(一个大值)的表吗?
  • 另外,您使用的是什么 SQL Server 版本?
  • A的2016年怎么只有1个月? 11 月和 12 月?

标签: sql sql-server tsql sql-server-2016


【解决方案1】:

我会为此使用一个计数。我在我的系统上保留了一个视图,它的速度快如闪电。这是视图。

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

然后我们需要一些样本数据。像这样的。

declare @Something table
(
    Contract char(1)
    , StartDate date
    , EndDate date
)

insert @Something values
('A', '20161101', '20180601')
, ('B', '20170531', '20190601')

现在我们可以查询样本并利用计数表来完成这项工作。

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))

【讨论】:

  • 关于你的计数表,你应该看看这篇文章; Bad habits to kick : using old-style JOINs。无法抗拒。 +1 1)一个好的答案和 2)使用我使用的相同(格式错误的)计数表。
  • @EricBrandt 哈哈,我一直在向人们指出那篇文章。我想这是有罪的,但它故意在这里交叉连接。这是基本上来自 Itzik Ben-Gan 的代码。我认为在这种情况下对他来说已经足够好了,对我来说当然已经足够好了。
【解决方案2】:

这是一种可能性:

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

如果您没有数字表,请将其放在查询之前:

;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
)

不过,我会选择 Sean Lange 的经过测试的变体

【讨论】:

    【解决方案3】:

    您可以使用 type = 'P' 的 master..spt_values 来获取 0 到 2047 之间的数字。过滤此数字,使其介于开始日期的年份和结束日期的年份之间,您将获得两个日期之间的年份行。 EndOfYear 和 BeginOfYear 分别返回每一年的第一个日期和最后一个日期。 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)
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-03-09
      • 1970-01-01
      • 2019-09-09
      • 1970-01-01
      • 2014-02-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多