如何使用 SQL 在可变时间段(每月、每年等)上分配收入?

How to spread revenue over variable time period (Monthly, Yearly etc) with SQL?

这样做的背景是我想为基于订阅的业务计算 MRR(每月经常性收入),其中在进行销售时记录收入,以及合同的时间段,代表在有效结束日期之前。

然后任务是将收入拆分为合同期限,并汇总多个合同,以便显示每月收入。如果还显示流失情况,即合同终止且未续签时发生的美元损失,那将是一个加分项。

数据格式如下(忽略计费周期):

╔══════════════════════════════════╦══════════════════════════╦══════════════════════════╦════════════════╦═══════╗
║            account_id            ║        start_date        ║    effective_end_date    ║ billing_period ║ price ║
╠══════════════════════════════════╬══════════════════════════╬══════════════════════════╬════════════════╬═══════╣
║ 2c92a0fd5286d62801528d6578230fa7 ║ 2015-10-01T00:00:00.000Z ║ 2017-10-15T00:00:00.000Z ║ Annual         ║ 1440  ║
║ 2c92a0fd5286d62801528d6578230fa8 ║ 2015-10-01T00:00:00.000Z ║ 2016-10-15T00:00:00.000Z ║ Annual         ║ 3500  ║
║ 2c92a0fd5286d62801528d6578230fa9 ║ 2015-10-01T00:00:00.000Z ║ 2015-12-31T00:00:00.000Z ║ Annual         ║ 700  ║
╚══════════════════════════════════╩══════════════════════════╩══════════════════════════╩════════════════╩═══════╝

期望的结果将显示如下:

2c92a0fd5286d62801528d6578230fa7 从 2015 年 10 月到 2017 年 10 月,24 个月的价差为 1440 美元。

2c92a0fd5286d62801528d6578230fa8 从 2015 年 10 月到 2016 年 10 月,12 个月的价差 $3500。

2c92a0fd5286d62801528d6578230fa9 从 2015 年 10 月到 2015 年 12 月的 3 个月内价差 $700。

我意识到我需要使用日期 table 来交叉连接,否则所有日期都不会被表示。我可以用 CTE 做到这一点。但我更困惑于如何分配收入。任何帮助,将不胜感激!

这是我到目前为止的进展:

SELECT account_id, date_trunc('month',effective_start_date) as start_date, effective_end_date, mrr as price, 
EXTRACT(YEAR FROM age(date_trunc('month',effective_end_date)::date,date_trunc('month', effective_start_date)::date))*12 + EXTRACT(month from age(date_trunc('month',effective_end_date)::date,date_trunc('month', effective_start_date)::date)) as contract_length_months, 
mrr/NULLIF(EXTRACT(YEAR FROM age(date_trunc('month',effective_end_date)::date,date_trunc('month', effective_start_date)::date))*12 + EXTRACT(month from age(date_trunc('month',effective_end_date)::date,date_trunc('month', effective_start_date)::date)),0) as divided_price
FROM "public"."zuora_rate_plan_charge" where mrr <> 0 and mrr is not null 
order by date_trunc('month',effective_start_date)

结果:

╔══════════════════════════════════╦══════════════════════════╦══════════════════════════╦═══════╦════════════════════════╦═══════════════╗
║            account_id            ║        start_date        ║    effective_end_date    ║ price ║ contract_length_months ║ divided_price ║
╠══════════════════════════════════╬══════════════════════════╬══════════════════════════╬═══════╬════════════════════════╬═══════════════╣
║ 2c92a0fd5286d62801528d6578230fa7 ║ 2015-10-01T00:00:00.000Z ║ 2017-10-15T00:00:00.000Z ║  1440 ║                     24 ║            60 ║
╚══════════════════════════════════╩══════════════════════════╩══════════════════════════╩═══════╩════════════════════════╩═══════════════╝

期望的结果:

╔════════╦════════════════╗
║ Month  ║      MRR       ║
╠════════╬════════════════╣
║ Oct 15 ║ 585            ║
║ Nov 15 ║ 585            ║
║ Dec 15 ║ 585            ║
║ Jan 16 ║ 351.6666666667 ║
╚════════╩════════════════╝

这里有一些可能有用的提示。这个想法很简单,你可以简单地制作只有 YEAR-MONTH 的虚拟 table 并加入它来获取你想要的数据。

  1. 在这种情况下,我曾经为只有年月列的连接制作虚拟 table ex) 2012-02, 2012-03, 2012-04, ~~~ 2020-12
  2. 这里是获取每个月价格的简单查询。 ex) SELECT price/TIMESTAMPDIFF(MONTH, start_date, end_datE) AS salary, start_date, end_date FROM TESTTABLE
  3. 加入 (1) table 和你的结果,并按 (1) 列分组获得薪水..

祝你好运。

您可以使用 generate_series() 来获取月份,然后使用一些算术来获取数据。要获取一位客户的所有月份:

select t.*, price / count(*) over (partition by account_id) as monthy
from (select t.*,
             generate_series(start_date, end_date, interval '1 month') as yyyymm
      from t
     ) t;

然后,如果你想要每月的金额,你可以合计:

select yyyymm, sum(monthly)
from (select t.*,
             price / count(*) over (partition by account_id) as monthly
      from (select t.*,
                   generate_series(start_date, end_date, interval '1 month') as yyyymm
            from t
           ) t
      ) t
group by yyyymm
order by yyyymm;