问题描述
我想编写一个 oracle sql 查询来计算给定维度的所有可能组合的每月 YTD 收入(累计总和)。还有一些月份没有交易,因此没有收入,在这种情况下,必须为该维度组合显示上个月的年初至今收入。给定表:
| Month | site | channel | type | revenue |
| ----- | ---- | ------- | ---- | ------- |
| 2017-02 | abc | 1 | A | 50 |
| 2017-04 | abc | 2 | B | 100 |
| 2018-12 | xyz | 1 | A | 150 |
样本期望输出:
| Month | site | channel | type | ytd revenue |
| ----- | ---- | ------- | ---- | ------- |
| 2017-01 | abc | 1 | A | 0 |
| 2017-02 | abc | 1 | A | 50 |
| 2017-03 | abc | 1 | A | 50 |
| 2017-04 | abc | 1 | A | 50 |
| ------ | --- | -- | -- | --- |
| 2018-12 | abc | 1 | A | 1000 |
| ----- | -- | -- | -- | --- |
| 2017-04 | abc | 2 | A | 100 |
| ---- | --- | - | - | -- |
| 2018-12 | abc | 2 | A | 10 |
| --- | -- | - | - | -- |
| 2018-12 | xyz | 1 | A | 150 |
会计年度从第 1 个月开始,到第 12 个月结束。因此,所有维度组合的累计总和或 YTD 收入必须是每年第 1 个月到第 12 个月,如上面的示例输出所示。
解决方法
使用 PARTITION OUTER JOIN
:
SELECT ADD_MONTHS( t.year,c.month - 1 ) AS month,t.site,t.channel,t.type,SUM( COALESCE( t.revenue,0 ) ) OVER (
PARTITION BY t.site,t.year
ORDER BY c.month
) AS ytd_revenue
FROM (
SELECT LEVEL AS month
FROM DUAL
CONNECT BY LEVEL <= 12
) c
LEFT OUTER JOIN (
SELECT t.*,TRUNC( month,'YY' ) AS year
FROM table_name t
) t
PARTITION BY ( site,channel,type,year )
ON ( c.month = EXTRACT( MONTH FROM t.month ) );
对于样本数据:
CREATE TABLE table_name ( Month,site,revenue ) AS
SELECT DATE '2017-02-01','abc',1,'A',50 FROM DUAL UNION ALL
SELECT DATE '2017-04-01',2,'B',100 FROM DUAL UNION ALL
SELECT DATE '2018-12-01','xyz',150 FROM DUAL;
输出:
MONTH | SITE | CHANNEL | TYPE | YTD_REVENUE :------------------ | :--- | ------: | :--- | ----------: 2017-01-01 00:00:00 | abc | 1 | A | 0 2017-02-01 00:00:00 | abc | 1 | A | 50 2017-03-01 00:00:00 | abc | 1 | A | 50 2017-04-01 00:00:00 | abc | 1 | A | 50 2017-05-01 00:00:00 | abc | 1 | A | 50 2017-06-01 00:00:00 | abc | 1 | A | 50 2017-07-01 00:00:00 | abc | 1 | A | 50 2017-08-01 00:00:00 | abc | 1 | A | 50 2017-09-01 00:00:00 | abc | 1 | A | 50 2017-10-01 00:00:00 | abc | 1 | A | 50 2017-11-01 00:00:00 | abc | 1 | A | 50 2017-12-01 00:00:00 | abc | 1 | A | 50 2017-01-01 00:00:00 | abc | 2 | B | 0 2017-02-01 00:00:00 | abc | 2 | B | 0 2017-03-01 00:00:00 | abc | 2 | B | 0 2017-04-01 00:00:00 | abc | 2 | B | 100 2017-05-01 00:00:00 | abc | 2 | B | 100 2017-06-01 00:00:00 | abc | 2 | B | 100 2017-07-01 00:00:00 | abc | 2 | B | 100 2017-08-01 00:00:00 | abc | 2 | B | 100 2017-09-01 00:00:00 | abc | 2 | B | 100 2017-10-01 00:00:00 | abc | 2 | B | 100 2017-11-01 00:00:00 | abc | 2 | B | 100 2017-12-01 00:00:00 | abc | 2 | B | 100 2018-01-01 00:00:00 | xyz | 1 | A | 0 2018-02-01 00:00:00 | xyz | 1 | A | 0 2018-03-01 00:00:00 | xyz | 1 | A | 0 2018-04-01 00:00:00 | xyz | 1 | A | 0 2018-05-01 00:00:00 | xyz | 1 | A | 0 2018-06-01 00:00:00 | xyz | 1 | A | 0 2018-07-01 00:00:00 | xyz | 1 | A | 0 2018-08-01 00:00:00 | xyz | 1 | A | 0 2018-09-01 00:00:00 | xyz | 1 | A | 0 2018-10-01 00:00:00 | xyz | 1 | A | 0 2018-11-01 00:00:00 | xyz | 1 | A | 0 2018-12-01 00:00:00 | xyz | 1 | A | 150
或者,如果您想要完整的日期范围而不仅仅是每年:
WITH calendar ( month ) AS (
SELECT ADD_MONTHS( start_month,LEVEL - 1 )
FROM (
SELECT MIN( ADD_MONTHS( TRUNC( ADD_MONTHS( month,-3 ),'YY' ),3 ) ) AS start_month,ADD_MONTHS( MAX( TRUNC( ADD_MONTHS( month,'YY' ) ),14 ) AS end_month
FROM table_name
)
CONNECT BY
ADD_MONTHS( start_month,LEVEL - 1 ) <= end_month
)
SELECT TO_CHAR( c.month,'YYYY-MM' ) AS month,TRUNC( c.month,'YY' )
ORDER BY c.month
) AS ytd_revenue
FROM calendar c
LEFT OUTER JOIN (
SELECT t.*,type )
ON ( c.month = t.month )
ORDER BY
site,month;
输出:
MONTH | SITE | CHANNEL | TYPE | YTD_REVENUE :------------------ | :--- | ------: | :--- | ----------: 2017-01-01 00:00:00 | abc | 1 | A | 0 2017-02-01 00:00:00 | abc | 1 | A | 50 2017-03-01 00:00:00 | abc | 1 | A | 50 2017-04-01 00:00:00 | abc | 1 | A | 50 2017-05-01 00:00:00 | abc | 1 | A | 50 2017-06-01 00:00:00 | abc | 1 | A | 50 2017-07-01 00:00:00 | abc | 1 | A | 50 2017-08-01 00:00:00 | abc | 1 | A | 50 2017-09-01 00:00:00 | abc | 1 | A | 50 2017-10-01 00:00:00 | abc | 1 | A | 50 2017-11-01 00:00:00 | abc | 1 | A | 50 2017-12-01 00:00:00 | abc | 1 | A | 50 2018-01-01 00:00:00 | abc | 1 | A | 0 2018-02-01 00:00:00 | abc | 1 | A | 0 2018-03-01 00:00:00 | abc | 1 | A | 0 2018-04-01 00:00:00 | abc | 1 | A | 0 2018-05-01 00:00:00 | abc | 1 | A | 0 2018-06-01 00:00:00 | abc | 1 | A | 0 2018-07-01 00:00:00 | abc | 1 | A | 0 2018-08-01 00:00:00 | abc | 1 | A | 0 2018-09-01 00:00:00 | abc | 1 | A | 0 2018-10-01 00:00:00 | abc | 1 | A | 0 2018-11-01 00:00:00 | abc | 1 | A | 0 2018-12-01 00:00:00 | abc | 1 | A | 0 2017-01-01 00:00:00 | abc | 2 | B | 0 2017-02-01 00:00:00 | abc | 2 | B | 0 2017-03-01 00:00:00 | abc | 2 | B | 0 2017-04-01 00:00:00 | abc | 2 | B | 100 2017-05-01 00:00:00 | abc | 2 | B | 100 2017-06-01 00:00:00 | abc | 2 | B | 100 2017-07-01 00:00:00 | abc | 2 | B | 100 2017-08-01 00:00:00 | abc | 2 | B | 100 2017-09-01 00:00:00 | abc | 2 | B | 100 2017-10-01 00:00:00 | abc | 2 | B | 100 2017-11-01 00:00:00 | abc | 2 | B | 100 2017-12-01 00:00:00 | abc | 2 | B | 100 2018-01-01 00:00:00 | abc | 2 | B | 0 2018-02-01 00:00:00 | abc | 2 | B | 0 2018-03-01 00:00:00 | abc | 2 | B | 0 2018-04-01 00:00:00 | abc | 2 | B | 0 2018-05-01 00:00:00 | abc | 2 | B | 0 2018-06-01 00:00:00 | abc | 2 | B | 0 2018-07-01 00:00:00 | abc | 2 | B | 0 2018-08-01 00:00:00 | abc | 2 | B | 0 2018-09-01 00:00:00 | abc | 2 | B | 0 2018-10-01 00:00:00 | abc | 2 | B | 0 2018-11-01 00:00:00 | abc | 2 | B | 0 2018-12-01 00:00:00 | abc | 2 | B | 0 2017-01-01 00:00:00 | xyz | 1 | A | 0 2017-02-01 00:00:00 | xyz | 1 | A | 0 2017-03-01 00:00:00 | xyz | 1 | A | 0 2017-04-01 00:00:00 | xyz | 1 | A | 0 2017-05-01 00:00:00 | xyz | 1 | A | 0 2017-06-01 00:00:00 | xyz | 1 | A | 0 2017-07-01 00:00:00 | xyz | 1 | A | 0 2017-08-01 00:00:00 | xyz | 1 | A | 0 2017-09-01 00:00:00 | xyz | 1 | A | 0 2017-10-01 00:00:00 | xyz | 1 | A | 0 2017-11-01 00:00:00 | xyz | 1 | A | 0 2017-12-01 00:00:00 | xyz | 1 | A | 0 2018-01-01 00:00:00 | xyz | 1 | A | 0 2018-02-01 00:00:00 | xyz | 1 | A | 0 2018-03-01 00:00:00 | xyz | 1 | A | 0 2018-04-01 00:00:00 | xyz | 1 | A | 0 2018-05-01 00:00:00 | xyz | 1 | A | 0 2018-06-01 00:00:00 | xyz | 1 | A | 0 2018-07-01 00:00:00 | xyz | 1 | A | 0 2018-08-01 00:00:00 | xyz | 1 | A | 0 2018-09-01 00:00:00 | xyz | 1 | A | 0 2018-10-01 00:00:00 | xyz | 1 | A | 0 2018-11-01 00:00:00 | xyz | 1 | A | 0 2018-12-01 00:00:00 | xyz | 1 | A | 150
dbfiddle here
会计年度(4 月至 3 月):
WITH calendar ( month ) AS (
SELECT ADD_MONTHS( start_month,LEVEL - 1 )
FROM (
SELECT MIN( TRUNC( ADD_MONTHS( month,'YY' ) ) AS start_month,11 ) AS end_month
FROM table_name
)
CONNECT BY
ADD_MONTHS( start_month,LEVEL - 1 ) <= end_month
)
SELECT TO_CHAR( ADD_MONTHS( c.month,3 ),'YY' )
ORDER BY c.month
) AS ytd_revenue
FROM calendar c
LEFT OUTER JOIN (
SELECT ADD_MONTHS( month,-3 ) AS month,revenue,TRUNC( ADD_MONTHS( month,month;
dbfiddle here
,如果我理解正确,您可以使用 cross join
获取所有行,然后使用 left join
和累积总和获取最新值:
select m.month,sc.site,sc.channel,sc.type,sum(revenue) over (partition by sc.site,trunc(m.month,'YYYY') order by m.month) as ytd_revenue
from (select distinct month from t) m cross join
(select distinct site,type from t) sct left join
t
on t.month = m.month and t.site = sct.site and
t.channel = sc.channel and t.type = sct.type;
这假设所有月份都在数据中可用。如果没有,您需要生成月份。 . .使用显式列表或使用某种生成器,例如:
with months(month) as (
select date '2019-01-01' as month
from dual
union all
select month + interval '1' month
from months
where month < date '2021-1-01'
)