简体   繁体   中英

SQL Query building: howto decompose periods of time in different rows

How can I build a SQL Query to decompose some periods, for example in months.

database table:

id      fromdate         todate        value
--------------------------------------------
100     01.01.2015       01.03.2015    10

desired query result:

id      fromdate         todate        value 
--------------------------------------------
100     01.01.2015       01.02.2015    5,25
100     01.02.2015       01.03.2015    4,75

where value is based on days between the 2 dates, for example:

value(january) = 31(january nr of days) * 10(original value) / 59(total days) = 5,25

Thank you

For calculations like this you can use date dimension - a table that contains all the dates in your domain as single rows (see this for example ).

Once you have date dimension in your database things become simple:

WITH data_by_date AS 
( -- Here we join dates to your periods to turn each row in 
  -- as many rows as there are days in the period.
  -- We also turn value field into value_per_day.
 SELECT
   d.date,
   d.month_year,  
   t.id,
   value / (t.todate - t.fromdate) as value_per_day
 FROM
   dim_date d INNER JOIN 
   my_table t ON d.date >= t.fromdate AND d.date < t.todate
)
SELECT -- Here we group by results by month.
  dd.id, 
  MIN(dd.date) as fromdate, 
  MAX(dd.date) as todate, 
  SUM(dd.value_per_day) as value 
FROM  data_by_date dd
GROUP BY dd.id, dd.month_year

Use a hierarchical query to generate a list of months for each entry:

SQL Fiddle

Oracle 11g R2 Schema Setup :

CREATE TABLE TEST (id, fromdate, todate, value ) AS
          SELECT 100, DATE '2015-01-01', DATE '2015-03-01', 10 FROM DUAL
UNION ALL SELECT 200, DATE '2014-12-22', DATE '2015-01-06', 30 FROM DUAL

Query 1 :

SELECT ID,
       fromdate,
       todate,
       VALUE * ( todate - fromdate ) / ( maxdate - mindate ) AS value
FROM (
  SELECT ID,
         GREATEST( t.fromdate, m.COLUMN_VALUE ) AS fromdate,
         LEAST( t.todate, ADD_MONTHS( m.COLUMN_VALUE, 1 ) ) AS todate,
         t.fromdate AS mindate,
         t.todate AS maxdate,
         t.value
  FROM   TEST t,
         TABLE(
           CAST(
             MULTISET(
               SELECT  ADD_MONTHS( TRUNC( t.fromdate, 'MM' ), LEVEL - 1 )
               FROM    DUAL
               CONNECT BY
                       ADD_MONTHS( TRUNC( t.fromdate, 'MM' ), LEVEL - 1 ) < t.todate
             )
             AS SYS.ODCIDATELIST
           )
         ) m
)

Results :

|  ID |                   FROMDATE |                     TODATE |             VALUE |
|-----|----------------------------|----------------------------|-------------------|
| 100 |  January, 01 2015 00:00:00 | February, 01 2015 00:00:00 | 5.254237288135593 |
| 100 | February, 01 2015 00:00:00 |    March, 01 2015 00:00:00 | 4.745762711864407 |
| 200 | December, 22 2014 00:00:00 |  January, 01 2015 00:00:00 |                20 |
| 200 |  January, 01 2015 00:00:00 |  January, 06 2015 00:00:00 |                10 |

Use function add_months() and hierarchical subquery to generate periods for each id:

select id, d1, d2, round(value*(d2-d1)/nod, 2) value 
  from (
    select id, value, todate-fromdate nod, add_months(fromdate, level-1) d1, 
           least(add_months(fromdate, level), todate) d2
      from data 
      connect by add_months(fromdate, level) <= trunc(add_months(todate, 1)-1) 
        and id = prior id and prior dbms_random.value is not null )

SQLFiddle demo

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM