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;