同比、环比、累计值,这几个常见分析 SQL 写法一次讲明白
做数据分析,最常见也最容易说混的三个词,就是:同比、环比、累计值。
很多人平时都在用,但一到自己写 SQL 就容易乱:到底该跟谁比、在哪个粒度上比、什么时候该先聚合、什么时候该上窗口函数。
这一篇不整虚的,直接把这三个概念和最常见 SQL 写法讲透。
同比,就是和去年同期比。
比如:
同比适合看长期趋势,尤其适合有季节性的业务。
环比,就是和上一个统计周期比。
比如:
环比适合看短期变化和拐点。
累计值,就是从起点开始一直往后加,到当前为止。
比如:
累计值适合看进度和总量。
假设有一张订单明细表:
create table orders (
order_id bigint,
user_id bigint,
order_date date,
amount decimal(18,2)
);如果你要看按月销售额,就先按月聚合,不要拿明细直接硬算。
with month_gmv as (
select
date_trunc('month', order_date) as month_dt,
sum(amount) as gmv
from orders
group by 1
)
select *
from month_gmv
order by month_dt;这一点特别重要:先聚合到目标粒度,再做同比、环比、累计。
这是最稳的一种写法。
with month_gmv as (
select
date_trunc('month', order_date) as month_dt,
sum(amount) as gmv
from orders
group by 1
)
select
a.month_dt,
a.gmv as curr_gmv,
b.gmv as last_year_gmv,
a.gmv - b.gmv as yoy_diff,
case
when b.gmv is null or b.gmv = 0 then null
else (a.gmv - b.gmv) / b.gmv
end as yoy_rate
from month_gmv a
left join month_gmv b
on a.month_dt = b.month_dt + interval '1 year'
order by a.month_dt;同比增长率公式就是:
本期减去年同期,再除以去年同期。
如果你的时间序列是连续的,也可以直接用窗口函数。
with month_gmv as (
select
date_trunc('month', order_date) as month_dt,
sum(amount) as gmv
from orders
group by 1
)
select
month_dt,
gmv,
lag(gmv, 12) over(order by month_dt) as last_year_gmv,
case
when lag(gmv, 12) over(order by month_dt) is null
or lag(gmv, 12) over(order by month_dt) = 0 then null
else (gmv - lag(gmv, 12) over(order by month_dt))
/ lag(gmv, 12) over(order by month_dt)
end as yoy_rate
from month_gmv
order by month_dt;如果中间有缺月,lag 可能会错位。这种情况下,还是 self join 更稳。
环比本质上就是和上一期比较。
with month_gmv as (
select
date_trunc('month', order_date) as month_dt,
sum(amount) as gmv
from orders
group by 1
)
select
month_dt,
gmv,
lag(gmv, 1) over(order by month_dt) as prev_gmv,
case
when lag(gmv, 1) over(order by month_dt) is null
or lag(gmv, 1) over(order by month_dt) = 0 then null
else (gmv - lag(gmv, 1) over(order by month_dt))
/ lag(gmv, 1) over(order by month_dt)
end as mom_rate
from month_gmv
order by month_dt;别把“环比”理解得太窄。它不只是月环比,而是当前周期和上一周期比。
累计值最常见的写法就是窗口函数 sum over。
比如按天累计销售额:
with day_gmv as (
select
order_date,
sum(amount) as gmv
from orders
group by order_date
)
select
order_date,
gmv,
sum(gmv) over(
order by order_date
rows between unbounded preceding and current row
) as cumulative_gmv
from day_gmv
order by order_date;如果要算年累计,就按年份分区。
sum(gmv) over(
partition by extract(year from order_date)
order by order_date
rows between unbounded preceding and current row
)如果要算月累计,就按月份分区。
嘴上说按月同比,SQL 却拿天级数据直接 lag(12)。这不是去年同月,是前 12 天。
lag 取的是前 N 行,不是自动帮你找去年同期。缺月时很容易错位。
同比、环比分母为 0 时,SQL 层建议返回 null,展示层再决定怎么显示。
累计前应该先按目标粒度聚合,否则得到的是逐行累计,不是按天或按月累计。
像 DAU、MAU、付费用户数这种去重指标,应该先按周期聚合出结果,再做同比或环比。
如果你想看长期趋势,用同比。
如果你想看最近有没有变好变差,用环比。
如果你想看当前进度和总体规模,用累计值。
现实工作里,三者通常一起看。
老板最爱的一句通常是:给我看下本月收入、环比、同比,还有年累计。
嗯,经典四件套,跑不掉。
很多 SQL 问题,表面看是函数不会写,实际上是粒度错了、口径歪了、时间轴没补齐。
语法问题一般几分钟修完,口径问题能让你和业务扯一下午。
别给自己找这个罪受。
阅读原文:原文链接