SQL joins 与窗口函数:一个电商库讲透两种核心能力
SQL 是关系数据库数据操作的骨架,其中最核心的两块是 join 与窗口函数:join 把多表数据组合起来,窗口函数在行间做高级计算而不把行折叠成聚合。用一个只有两张表的小电商库(customers 与 orders,客户 Carla 还没有订单)演示全部细节。
Part 1:SQL Joins
INNER JOIN:只返回两边都匹配的行。
SELECT customers.name, orders.amount
FROM customers
INNER JOIN orders ON customers.id = orders.customer_id;
-- 结果:Aisha 250 / Aisha 100 / Brian 400
Carla 因为没有匹配订单而完全消失。这是最常见的 join,但它会静默丢掉未匹配行——"我的数据怎么少了"类 bug 的高频来源。
LEFT JOIN:返回左表全部行,右表匹配不上补 NULL。
SELECT customers.name, orders.amount
FROM customers
LEFT JOIN orders ON customers.id = orders.customer_id;
-- Carla 出现,amount 为 NULL
需要保留"主表"全部记录时用 LEFT JOIN(注意 NULL 表示"无匹配",不是零——过滤和聚合时要区分)。
RIGHT JOIN:与 LEFT 相反,保右表全部;FULL OUTER JOIN:两边都保留,无匹配补 NULL。SELF JOIN:表与自身连接,典型场景是层级数据(员工-经理)或"同表比较行"。
窗口函数:不折叠行的计算
窗口函数在行组(partition)内计算,但返回行数与输入一致——这是它和 GROUP BY 的本质区别。
ROW_NUMBER / RANK / DENSE_RANK:行号、排名(同分跳过)、密集排名(同分不跳)。取"每个客户最近一笔订单":
SELECT customer_id, amount,
ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY id DESC) AS rn
FROM orders;
-- 过滤 rn = 1 即得每组最新一条
SUM/AVG 窗口版:滚动聚合。累计订单金额:
SELECT customer_id, amount,
SUM(amount) OVER (PARTITION BY customer_id ORDER BY id) AS running_total
FROM orders;
LAG / LEAD:访问前一行/后一行,经典用途是环比:amount - LAG(amount) OVER (PARTITION BY customer_id ORDER BY id) 算出每笔订单相对上一笔的差值。
实践建议
- join 前先想清主表与保留语义:要保留哪边的行、NULL 代表什么,先用小数据集验证再上生产;
- 窗口函数里 ORDER BY 决定帧(frame)方向,
SUM() OVER (PARTITION BY ... ORDER BY ...)是累计、不带 ORDER BY 才是分组内全量; - 取 Top-N 用 ROW_NUMBER 过滤,同分场景再换成 RANK/DENSE_RANK;
- 大数据量下窗口函数通常比多次自连接高效,但也更耗内存,先按分区键裁剪数据。