SQL 查询构建:如何分解不同行中的时间段
SQL Query building: howto decompose periods of time in different rows
我如何构建一个 SQL 查询来分解某些时期,例如以月为单位。
数据库table:
id fromdate todate value
--------------------------------------------
100 01.01.2015 01.03.2015 10
想要的查询结果:
id fromdate todate value
--------------------------------------------
100 01.01.2015 01.02.2015 5,25
100 01.02.2015 01.03.2015 4,75
其中值基于两个日期之间的天数,例如:
value(january) = 31(january nr of days) * 10(original value) / 59(total days) = 5,25
谢谢
对于这样的计算,您可以使用 date dimension
- 一个 table,其中包含您域中的所有日期作为单行(请参阅 example)。
一旦你的数据库中有了日期维度,事情就变得简单了:
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
使用函数add_months()
和分层子查询为每个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 )
使用分层查询为每个条目生成月份列表:
Oracle 11g R2 模式设置:
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
查询 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
)
| 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 |
我如何构建一个 SQL 查询来分解某些时期,例如以月为单位。
数据库table:
id fromdate todate value
--------------------------------------------
100 01.01.2015 01.03.2015 10
想要的查询结果:
id fromdate todate value
--------------------------------------------
100 01.01.2015 01.02.2015 5,25
100 01.02.2015 01.03.2015 4,75
其中值基于两个日期之间的天数,例如:
value(january) = 31(january nr of days) * 10(original value) / 59(total days) = 5,25
谢谢
对于这样的计算,您可以使用 date dimension
- 一个 table,其中包含您域中的所有日期作为单行(请参阅 example)。
一旦你的数据库中有了日期维度,事情就变得简单了:
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
使用函数add_months()
和分层子查询为每个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 )
使用分层查询为每个条目生成月份列表:
Oracle 11g R2 模式设置:
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
查询 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
)
| 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 |