如何使用 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 并加入它来获取你想要的数据。
- 在这种情况下,我曾经为只有年月列的连接制作虚拟 table
ex) 2012-02, 2012-03, 2012-04, ~~~ 2020-12
- 这里是获取每个月价格的简单查询。
ex) SELECT price/TIMESTAMPDIFF(MONTH, start_date, end_datE) AS salary, start_date, end_date FROM TESTTABLE
- 加入 (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;
这样做的背景是我想为基于订阅的业务计算 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 并加入它来获取你想要的数据。
- 在这种情况下,我曾经为只有年月列的连接制作虚拟 table ex) 2012-02, 2012-03, 2012-04, ~~~ 2020-12
- 这里是获取每个月价格的简单查询。 ex) SELECT price/TIMESTAMPDIFF(MONTH, start_date, end_datE) AS salary, start_date, end_date FROM TESTTABLE
- 加入 (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;