SQL split number over date range

走远了吗. 提交于 2019-12-06 14:11:57

Use spt_values table to create a calendar table, then join to your table to split date range into any part you want.

If split by year and divide amount by months you could:

with dates as
(
select number,DATEADD(day,number,'20130101') as dt
    from master..spt_values
    where number between 0 and 1000 AND TYPE='P'
)
select
    m.start_date as org_start_date,
    m.end_date as org_end_date,
    min(d.dt) as new_start_date,
    max(d.dt) as new_end_date,
    m.amount*count(distinct month(d.dt))/(datediff(month,m.start_date,m.end_date)+1) as amount
from 
    MonthSplit m
join
    dates d
on 
    d.dt between m.start_date and m.end_date
group by 
    m.start_date, m.end_date, year(d.dt),m.amount

Here is the SQL FIDDLE DEMO.

Here is a solution using a numbers table:

SQL Fiddle Example

DECLARE @STARTYR INT = (SELECT MIN(YEAR([Start Date])) FROM Table1)
DECLARE @ENDYR INT = (SELECT MAX(YEAR([End Date])) FROM Table1)

SELECT [Id]
     , @STARTYR + Number AS [Year]
     , CASE WHEN YEAR([Start Date]) < @STARTYR + Number 
            THEN DATEADD(YEAR, @STARTYR - 1900 + Number,0) 
            ELSE [Start Date] END AS [Start]
     , CASE WHEN YEAR([End Date]) > @STARTYR + Number 
            THEN DATEADD(YEAR, @STARTYR - 1900 + Number + 1,0) 
            ELSE [End Date] END AS [End]
     , DATEDIFF(MONTH,CASE WHEN YEAR([Start Date]) < @STARTYR + Number 
                           THEN DATEADD(YEAR, @STARTYR - 1900 + Number,0) 
                           ELSE [Start Date] END
                     ,CASE WHEN YEAR([End Date]) > @STARTYR + Number 
                           THEN DATEADD(YEAR, @STARTYR - 1900 + Number + 1,0) 
                           ELSE DATEADD(MONTH,DATEDIFF(MONTH,0,DATEADD(MONTH,1,[End Date])),0) END) AS [Months]
     , DATEDIFF(MONTH,[Start Date],[End Date]) + 1 [Total Months]
     , ([Amount] / (DATEDIFF(MONTH,[Start Date],[End Date]) + 1)) 
       *
       DATEDIFF(MONTH,CASE WHEN YEAR([Start Date]) < @STARTYR + Number 
                           THEN DATEADD(YEAR, @STARTYR - 1900 + Number,0) 
                           ELSE [Start Date] END
                     ,CASE WHEN YEAR([End Date]) > @STARTYR + Number 
                           THEN DATEADD(YEAR, @STARTYR - 1900 + Number + 1,0) 
                           ELSE DATEADD(MONTH,DATEDIFF(MONTH,0,DATEADD(MONTH,1,[End Date])),0) END) AS [Proportion]

FROM Numbers
LEFT JOIN Table1 ON YEAR([Start Date]) <= @STARTYR + Number
                 AND YEAR([End Date]) >= @STARTYR + Number


WHERE Number <= @ENDYR - @STARTYR

I don't have a ready made SQL for you but just a thought to solve this problem. If you have some experience with SQL it won't be hard to express it in SQL.

You could do this by defining a reference table for the months that are between your begin and end date:

ID    |   month       |   year  |   month start   |  month end    |   count |
-----------------------------------------------------------------------------
1001  |   dec-2013    |   2013  |   1-12-2013     |   31-12-2013  |     1   |
1001  |   jan-2014    |   2014  |   1-1-2014      |   31-1-2014   |     1   |
1001  |   feb-2014    |   2014  |   1-2-2014      |   28-2-2014   |     1   |
1001  |   mar-2014    |   2014  |   1-3-2014      |   31-3-2014   |     1   |

Maybe you already have such a time ref table in your DWH.

When you join (with a between statement) your table with the record that contains the start and end date per row, with this reference table, you'll have the split from 1 row to the number of months contained in the range correct. The count column will help you to get the split ratio per year right by grouping over the year afterwards (like : 1/4 for 2013 and 3/4 for 2014). You'll have to apply the ratio to the field 'amount' that you want to split up.

Tanner

It's very similar to this question:

Split date range into one row per month in sql server

Although you are doing grouping by year, so based on that answer you can modify it to do what you want by adding MIN, MAX to your date values and grouping by the YEAR():

SQL Fiddle Demo

Schema Setup:

CREATE TABLE MonthSplit
    ([ID] varchar(2), [start_date] datetime, [end_date] datetime, [amount] int)
;

INSERT INTO MonthSplit
    ([ID], [start_date], [end_date], [amount])
VALUES
    ('a1', '2013-12-01 00:00:00', '2014-03-31 00:00:00', 100),
    ('a2', '2013-10-01 00:00:00', '2015-05-01 00:00:00', 400)
;

Recursive CTE to group by Year:

WITH cte AS
(SELECT ID
      , start_date
      , end_date
      , start_date AS from_date
      , DATEADD(day, day(start_date)* -1 + 1, start_date) AS first_of_month
 FROM MonthSplit
 UNION ALL
 SELECT ID
     , start_date
     , end_date
     , DATEADD(month,1,first_of_month)
     , DATEADD(month,1,first_of_month)
  FROM cte
  WHERE DATEADD(month,1,from_date) < end_date
)
SELECT ID as ID, 
       min(start_date) as org_start_date, 
       min(end_date) as org_end_date, 
       min(from_date) AS new_start_date,
       CASE when max(end_date) < DATEADD(month,1,max(first_of_month)) THEN
           max(end_date)
       ELSE
           DATEADD(day, -1, DATEADD(month,1,max(first_of_month)))
       END AS new_end_date
FROM cte
group by year(from_date), ID

Results:

| ID | ORG_START_DATE    | ORG_END_DATE   | NEW_START_DATE    | NEW_END_DATE      |
|----|-------------------|----------------|-------------------|-------------------|
| a1 | December, 01 2013 | March, 31 2014 | December, 01 2013 | December, 31 2013 |
| a1 | December, 01 2013 | March, 31 2014 | January, 01 2014  | March, 31 2014    |
| a2 |  October, 01 2013 | May, 01 2015   | October, 01 2013  | December, 31 2013 |
| a2 |  October, 01 2013 | May, 01 2015   | January, 01 2014  | December, 31 2014 |
| a2 |  October, 01 2013 | May, 01 2015   | January, 01 2015  | April, 30 2015    |
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!