Select 来自时间跨度的多行

Select Multiple Rows from Timespan

问题

在我的 sql-server-2014 中,我将项目存储在 table 中,其中包含以下列:

开始日期 .. | 结束日期 ....| 项目名称 ...............| 音量
2017-02-13 | 2017-04-12 |产生收入.........| 20.02
2017-04-02 | 2018-01-01 |建立收入生成器 | 300.044
2017-05-23 | 2018-03-19 |收获收入 ...............| 434.009

我需要一个 SELECT 给我每个项目的项目每月一行。不必考虑一个月中的几天。

日期 ......... | 项目名称.................| 音量
2017-02-01 |产生收入.........| 20.02
2017-03-01 |产生收入.........| 20.02
2017-04-01 |产生收入.........| 20.02
2017-04-01 |建立收入生成器 | 300.044
2017-05-01 |建立收入生成器 | 300.044
2017-06-01 |建立收入生成器 | 300.044
...

额外

理想情况下,SELECT 的逻辑允许我计算每月的交易量以及每个月与前一个月之间的差异。

日期 ......... | 项目名称.................| 每月交易量
2017-02-01 |产生收入.........| 6.6733
2017-03-01 |产生收入.........| 6.6733
2017-04-01 |产生收入.........| 6.6733
2017-04-01 |建立收入生成器 | 30.0044
2017-05-01 |建立收入生成器 | 30.0044
2017-06-01 |建立收入生成器 | 30.0044
...

还有...

我知道我可以将它映射到一个临时日历上 table,但这往往会很快变得臃肿和复杂。我真的在寻找更好的方法来解决这个问题。

解决方案

Gordon 的解决方案非常有效,它不需要第二个 table 或映射到某种日历上。虽然我不得不改变一些事情,比如确保联盟的双方有相同的 SELECT.

这里是我改编的版本:

with cte as (
  select startdate as mondate, enddate, projectName, volume 
  from projects
  union all
  select dateadd(month, 1, mondate), enddate, projectName, volume
  from cte
  where eomonth(dateadd(month, 1, mondate)) <= eomonth(enddate)
)
select * from cte;

每月的交易量可以通过将交易量替换为:

CAST(Cast(volume AS DECIMAL) / Cast(Datediff(month, 
startdate,enddate)+ 1 AS DECIMAL) AS DECIMAL(15, 2)) 
END AS [volumeMonthly]

您可以使用递归子查询来扩展每个项目的行,基于 table:

with cte as (
      select stardate as mondate, p.*
      from projects
      union all
      select dateadd(month, 1, mondate), . . .  -- whatever columns you want here
      from cte
      where eomonth(dateadd(month, 1, mondate)) <= eomonth(enddate)
    )
select *
from cte;

我不确定这是否真的回答了您的问题。当我第一次阅读这个问题时,我认为 table 每个项目一行。

另一种选择是使用临时计数 table

例子

-- Some Sample Data
Declare @YourTable table (StartDate date,EndDate date,ProjectName varchar(50), Volume float)
Insert Into @YourTable values
 ('2017-03-15','2017-07-25','Project X',25)
,('2017-04-01','2017-06-30','Project Y',50)

-- Set Your Desired Date Range   
Declare @Date1 date = '2017-01-01'
Declare @Date2 date = '2017-12-31'

Select Period = D
      ,B.*
      ,MonthlyVolume = sum(Volume) over (Partition By convert(varchar(6),D,112))
 From (Select Top (DateDiff(MONTH,@Date1,@Date2)+1) D=DateAdd(MONTH,-1+Row_Number() Over (Order By (Select Null)),@Date1) 
        From  master..spt_values n1
      ) A
 Join @YourTable B on convert(varchar(6),D,112) between convert(varchar(6),StartDate,112) and convert(varchar(6),EndDate,112)
 Order by Period,ProjectName

Returns

注意:使用 LEFT JOIN 查看差距

使用几个 common table expressions,一个临时日历 table 几个月,lag()(SQL Server 2012+)用于最终的 delta 计算:

create table projects (id int identity(1,1), StartDate date, EndDate date, ProjectName varchar(32), Volume float);
insert into projects values ('20170101','20170330','SO Q1',240),('20170214','20170601','EX Q2',120)

declare @StartDate date = '20170101'
      , @EndDate   date = '20170731';
;with Months as (
  select top (datediff(month,@startdate,@enddate)+1) 
        MonthStart = dateadd(month, row_number() over (order by number) -1, @StartDate)
      , MonthEnd = dateadd(day,-1,dateadd(month, row_number() over (order by number), @StartDate))
    from master.dbo.spt_values
  )
, ProjectMonthlyVolume as (
  select p.*
    , MonthlyVolume = Volume/(datediff(month,p.StartDate,p.EndDate)+1)
    , m.MonthStart
  from Months m
    left join Projects p
      on p.EndDate >= m.MonthStart
     and p.StartDate <= m.MonthEnd
)
select 
    MonthStart = convert(char(7),MonthStart,120)
  , MonthlyVolume = isnull(sum(MonthlyVolume),0)
  , Delta = isnull(sum(MonthlyVolume),0) - lag(Sum(MonthlyVolume)) over (order by MonthStart)
from ProjectMonthlyVolume pmv
group by MonthStart

rextester 演示:http://rextester.com/DZL54787

returns:

+------------+---------------+-------+
| MonthStart | MonthlyVolume | Delta |
+------------+---------------+-------+
| 2017-01    |            80 | NULL  |
| 2017-02    |           104 | 24    |
| 2017-03    |           104 | 0     |
| 2017-04    |            24 | -80   |
| 2017-05    |            24 | 0     |
| 2017-06    |            24 | 0     |
| 2017-07    |             0 | -24   |
+------------+---------------+-------+