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 |
+------------+---------------+-------+
问题
在我的 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 |
+------------+---------------+-------+