lag()更适合库存水位趋势判断,因其可直接获取前一时序点库存值计算变化量或率;row_number()仅编号,无法提供时序值,强行使用需多层子查询。

为什么 LAG() 比 ROW_NUMBER() 更适合库存水位趋势判断
实时预警不是简单查当前库存是否低于阈值,而是要看“库存是否在快速下降”。比如某商品当前库存 50,但过去 3 分钟每分钟掉 20 件,比静态阈值(如 30)更危险。LAG() 能直接取上一时间点的库存值,方便算差值或变化率;而 ROW_NUMBER() 只编号,不带时序值,硬凑会多绕两层子查询。
实操建议:
- 按
product_id和event_time排序,确保LAG(stock, 1)拿到的是前一分钟(或前一条)的实际库存 - 避免用
ORDER BY event_time DESC配LAG—— 这会导致取“后一条”,逻辑翻车 - 如果数据有乱序(如延迟写入),先用
WHERE event_time > NOW() - INTERVAL '5 minutes'缩小窗口范围,再排序,减少错位风险
OVER (PARTITION BY product_id ORDER BY event_time ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) 这段窗口定义到底在框什么
它不是只取上一行,而是定义一个动态滑动窗口:对每个商品,把“当前行 + 它前一行”这两条打包计算。配合 SUM(stock) 或 AVG(stock) 就能算短周期均值;配合 MIN(stock) 可捕获最近两分钟最低水位。
常见错误现象:
- 写成
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW—— 累计求和,越算越不准,预警延迟拉长 - 漏写
PARTITION BY product_id—— 所有商品混在一起排序,LAG取到隔壁 SKU 的库存,预警全乱 - 用
RANGE替代ROWS且event_time有重复值 —— 可能吞掉多条记录,窗口实际大小不可控
如何用 CASE WHEN + 窗口函数输出分级预警信号
别把预警逻辑塞进应用层。数据库里直接返回 'CRITICAL'、'WARNING'、'NORMAL' 字符串,BI 或告警系统直取字段即可。
示例片段(PostgreSQL):
SELECT
product_id,
stock,
event_time,
CASE
WHEN stock - LAG(stock, 1) OVER (PARTITION BY product_id ORDER BY event_time) 5
THEN 'WARNING'
WHEN stock NOW() - INTERVAL '3 minutes';
注意点:
-
LAG返回NULL(首行无前值)时,stock - NULL是NULL,不会触发CRITICAL—— 符合预期,不用额外COALESCE - 两个
CRITICAL条件(突降量大 + 绝对值极低)是 or 关系,必须分开写,不能合并成WHEN stock —— 否则 <code>OR在CASE中优先级易出错 - WHERE 过滤必须放在窗口函数外层,否则窗口范围被截断,
LAG可能取不到真实前值
为什么不要在预警 SQL 里 JOIN 商品主表查类目信息
实时预警要求亚秒响应,而商品主表可能有千万级数据,JOIN 会拖慢整个窗口计算。类目、品牌这些维度信息应该由下游服务缓存后补,而不是让 OLAP 式的预警 SQL 扛。
更稳的做法:
- 预警 SQL 只输出
product_id和alert_level,字段越少越好 - 用 Redis 或本地 Map 缓存
{product_id: {category: "手机", brand: "X品牌"}},应用层查完预警再补维 - 如果必须维表关联,用
LEFT JOIN LATERAL (SELECT ... FROM categories WHERE id = t.product_id LIMIT 1)控制关联开销,避免笛卡尔积
窗口函数本身不缓存中间结果,每行都重新计算。一旦加上复杂 JOIN,执行计划容易退化成 nested loop,预警延迟从 200ms 涨到 2s+,就失去“实时”意义了。











