SQL: 获取上个月的盈亏绝对值和百分比
SQL: get gain loss absolute and percentage over previous month
id | year | month | category | value
1 2019 01 apple 3
1 2018 12 apple 2
1 2019 01 carrot 4
对于这个例子,预期的结果是:
category | last month | gain or loss % | gain or loss
apple 2019-01 +50% +1
carrot 2019-01 +100% +4 // Note: no carrot value on previous month
有什么方法可以在不进行任何昂贵的连接的情况下做到这一点?
如果你是运行 MySQL 8.0,可以使用window函数和聚合:
select
category,
max(case when rn = 1 then concat(year, '-', month)) last_month,
coalesce(
(
max(case when rn = 1 then value end)
- max(case when rn = 2 then value end)
)
/ max(case when rn = 2 then value end),
1) gain_or_loss_ratio,
max(case when rn = 1 then value end)
- coalesce(max(case when rn = 2 then value end), 0) gain_or_loss
from (
select
t.*,
row_number() over(partition by category order by year desc, month desc) rn
from mytable t
) t
where rn in (1, 2)
group by category
这样您就可以比较每个类别的最后 2 个值,这就是我对您问题的理解。第三列包含一个介于 0 和 1 之间的值而不是百分比,我发现它更有用(您可以根据需要在应用程序端对其进行格式化)。
您可以通过添加一层嵌套来缩短算术表达式,如下所示:
select
category,
last_month,
coalesce((last_value1 - last_value2) / last_value2, 1) gain_or_loss_ratio,
last_value1 - coalesce(last_value2, 0) gain_or_loss
from (
select
category,
max(case when rn = 1 then concat(year, '-', month)) last_month,
max(case when rn = 1 then value end) last_value1,
max(case when rn = 2 then value end) last_value2
from (
select
t.*,
row_number() over(partition by category order by year desc, month desc) rn
from mytable t
) t
where rn in (1, 2)
group by category
) t
如果您没有遗漏中间月份,则可以使用简单的滞后(以及一些过滤):
select category, concat_ws('-', year, month),
coalesce(val / prev_value, 1) as gain_loss_rate,
(val - coalesce(prev_value, 0)) as gain_or_loss
from (select t.*,
lag(value) over (partition by id, category order by year, month) as prev_value
from t
) t
where year = 2019 and month = '01';
如果您特别想要数据中的最近月份而不指定它,也可以使用 rank()
:
select category, concat_ws('-', year, month),
coalesce(val / prev_value, 1) as gain_loss_rate,
(val - coalesce(prev_value, 0)) as gain_or_loss
from (select t.*,
lag(value) over (partition by id, category order by year, month) as prev_value,
rank() over (order by year desc, month desc) as seqnum
from t
) t
where seqnum = 1;
id | year | month | category | value
1 2019 01 apple 3
1 2018 12 apple 2
1 2019 01 carrot 4
对于这个例子,预期的结果是:
category | last month | gain or loss % | gain or loss
apple 2019-01 +50% +1
carrot 2019-01 +100% +4 // Note: no carrot value on previous month
有什么方法可以在不进行任何昂贵的连接的情况下做到这一点?
如果你是运行 MySQL 8.0,可以使用window函数和聚合:
select
category,
max(case when rn = 1 then concat(year, '-', month)) last_month,
coalesce(
(
max(case when rn = 1 then value end)
- max(case when rn = 2 then value end)
)
/ max(case when rn = 2 then value end),
1) gain_or_loss_ratio,
max(case when rn = 1 then value end)
- coalesce(max(case when rn = 2 then value end), 0) gain_or_loss
from (
select
t.*,
row_number() over(partition by category order by year desc, month desc) rn
from mytable t
) t
where rn in (1, 2)
group by category
这样您就可以比较每个类别的最后 2 个值,这就是我对您问题的理解。第三列包含一个介于 0 和 1 之间的值而不是百分比,我发现它更有用(您可以根据需要在应用程序端对其进行格式化)。
您可以通过添加一层嵌套来缩短算术表达式,如下所示:
select
category,
last_month,
coalesce((last_value1 - last_value2) / last_value2, 1) gain_or_loss_ratio,
last_value1 - coalesce(last_value2, 0) gain_or_loss
from (
select
category,
max(case when rn = 1 then concat(year, '-', month)) last_month,
max(case when rn = 1 then value end) last_value1,
max(case when rn = 2 then value end) last_value2
from (
select
t.*,
row_number() over(partition by category order by year desc, month desc) rn
from mytable t
) t
where rn in (1, 2)
group by category
) t
如果您没有遗漏中间月份,则可以使用简单的滞后(以及一些过滤):
select category, concat_ws('-', year, month),
coalesce(val / prev_value, 1) as gain_loss_rate,
(val - coalesce(prev_value, 0)) as gain_or_loss
from (select t.*,
lag(value) over (partition by id, category order by year, month) as prev_value
from t
) t
where year = 2019 and month = '01';
如果您特别想要数据中的最近月份而不指定它,也可以使用 rank()
:
select category, concat_ws('-', year, month),
coalesce(val / prev_value, 1) as gain_loss_rate,
(val - coalesce(prev_value, 0)) as gain_or_loss
from (select t.*,
lag(value) over (partition by id, category order by year, month) as prev_value,
rank() over (order by year desc, month desc) as seqnum
from t
) t
where seqnum = 1;