Oracle SQL 收入 YTD 计算

Oracle SQL revenue YTD computation

我想编写一个 oracle SQL 查询来计算给定维度的所有可能组合的每月 YTD 收入(累计总和)。还有一些月份没有交易,因此没有收入,在这种情况下,必须为该维度组合显示上个月 YTD 收入。给定 table:

| 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 个月,如上面的示例输出所示。

如果我没理解错的话,您可以使用cross join获取所有行,然后使用left join和累加总和来获取最新值:

select m.month, sc.site, sc.channel, sc.type,
       sum(revenue) over (partition by sc.site, sc.channel, sc.type, trunc(m.month, 'YYYY') order by m.month) as ytd_revenue
from (select distinct month from t) m cross join
     (select distinct site, channel, 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;

这假设所有月份都在数据中可用。如果没有,您需要生成 months 。 . .使用显式列表或使用某种生成器,例如:

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

使用 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.channel, t.type, 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, channel, type, revenue ) AS
SELECT DATE '2017-02-01', 'abc', 1, 'A',  50 FROM DUAL UNION ALL
SELECT DATE '2017-04-01', 'abc', 2, 'B', 100 FROM DUAL UNION ALL
SELECT DATE '2018-12-01', 'xyz', 1, 'A', 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, -3 ), '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,
       t.site,
       t.channel,
       t.type,
       SUM( COALESCE( t.revenue, 0 ) ) OVER (
         PARTITION BY t.site, t.channel, t.type, TRUNC( c.month, 'YY' )
         ORDER BY c.month
       ) AS ytd_revenue
FROM   calendar c
       LEFT OUTER JOIN (
         SELECT t.*,
                TRUNC( month, 'YY' ) AS year
         FROM   table_name t
       ) t
       PARTITION BY ( site, channel, type )
       ON ( c.month = t.month )
ORDER BY
       site, channel, type, 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

db<>fiddle here


财政年度(4 月至 3 月):

WITH calendar ( month ) AS (
  SELECT ADD_MONTHS( start_month, LEVEL - 1 )
  FROM   (
    SELECT MIN( TRUNC( ADD_MONTHS( month, -3 ), 'YY' ) ) AS start_month,
           ADD_MONTHS( MAX( TRUNC( ADD_MONTHS( month, -3 ), 'YY' ) ), 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 ), 'YYYY-MM' ) AS month,
       t.site,
       t.channel,
       t.type,
       SUM( COALESCE( t.revenue, 0 ) ) OVER (
         PARTITION BY t.site, t.channel, t.type, TRUNC( c.month, 'YY' )
         ORDER BY c.month
       ) AS ytd_revenue
FROM   calendar c
       LEFT OUTER JOIN (
         SELECT ADD_MONTHS( month, -3 ) AS month,
                site,
                channel,
                type,
                revenue,
                TRUNC( ADD_MONTHS( month, -3 ), 'YY' ) AS year
         FROM   table_name t
       ) t
       PARTITION BY ( site, channel, type )
       ON ( c.month = t.month )
ORDER BY
       site, channel, type, month;

db<>fiddle here