Select Multiple Rows from Timespan

时间:2017-08-04 12:14:03

标签: sql sql-server select sql-server-2014 timespan

Problem

In my sql-server-2014 I store projects in a table with the columns:

Startdate .. | Enddate ....| Projectname .................| Volume
2017-02-13 | 2017-04-12 | GenerateRevenue .........| 20.02
2017-04-02 | 2018-01-01 | BuildRevenueGenerator | 300.044
2017-05-23 | 2018-03-19 | HarvestRevenue ............| 434.009

I need a SELECT to give me one row per month of the project for each project. the days of the month don't have to be considered.

Date .......... | Projectname..................| Volume
2017-02-01 | GenerateRevenue .........| 20.02
2017-03-01 | GenerateRevenue .........| 20.02
2017-04-01 | GenerateRevenue .........| 20.02
2017-04-01 | BuildRevenueGenerator | 300.044
2017-05-01 | BuildRevenueGenerator | 300.044
2017-06-01 | BuildRevenueGenerator | 300.044
...

Extra

Ideally the logic of the SELECT allows me both to calculate the monthly volume and also the difference between each month and the previous.

Date .......... | Projectname..................| VolumeMonthly
2017-02-01 | GenerateRevenue .........| 6.6733
2017-03-01 | GenerateRevenue .........| 6.6733
2017-04-01 | GenerateRevenue .........| 6.6733
2017-04-01 | BuildRevenueGenerator | 30.0044
2017-05-01 | BuildRevenueGenerator | 30.0044
2017-06-01 | BuildRevenueGenerator | 30.0044
...

Also...

I know I can map it on a temporary calendar table, but that tends to get bloated and complex very fast. Im really looking for a better way to solve this problem.

Solution

Gordons solution worked very nicely and it doesn't require a second table or mapping on a calendar of some sort. Although I had to change a few things, like making sure both sides of the union have the same SELECT.

Here my adapted version:

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;

Volume monthly can be achieved by replacing volume with:

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

3 个答案:

答案 0 :(得分:1)

Another option is with an ad-hoc tally table

Example

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

enter image description here

Note: Use a LEFT JOIN to see gaps

答案 1 :(得分:0)

You can use a recursive subquery to expand the rows for each project, based on the 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;

I'm not sure if this actually answers your question. When I first read the question, I figured the table had one row per project.

答案 2 :(得分:0)

Using a couple of common table expressions, an adhoc calendar table for months and lag() (SQL Server 2012+) for the final delta calculation:

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 demo: 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   |
+------------+---------------+-------+