想象一下,你在一家大餐厅当服务员(或者你喜欢咖啡的话,可以当 barista)。每天你都要统计一下今天收到的小费。但有个细节:餐厅分了好几个区域,你想知道每个区域的小费分别是多少。PARTITION BY 就是 SQL 用来“把餐厅分区”的工具。
正式点说,PARTITION BY 是在窗口函数里用来把表里的所有行分成不同的小组(或者说“分区”)。每个分区里,窗口函数会重新执行一次。就像你在每个“分区”里单独用一次函数一样。
例子:它怎么用
假设我们有个 sales 表,里面是销售数据:
| region | salesperson | amount |
|---|---|---|
| North | Alice | 100 |
| North | Bob | 200 |
| South | Alice | 150 |
| South | Charlie | 250 |
如果我们想算每个销售员赚了多少钱,但要按区域分别算,PARTITION BY 就是你要用的东西。
PARTITION BY 的语法
语法其实很简单:
窗口函数() OVER (PARTITION BY 列名_或_列名们)
窗口函数()— 比如SUM()、AVG()、ROW_NUMBER()这些。PARTITION BY 列名— 指定要按哪个列来分组。OVER()— 这个操作符告诉 SQL:“在这个窗口范围内做点啥”。
例子:分组求和
来算一下每个区域的销售总额:
SELECT
region,
salesperson,
amount,
SUM(amount) OVER (PARTITION BY region) AS total_sales_by_region
FROM sales;
结果会是这样:
| region | salesperson | amount | total_sales_by_region |
|---|---|---|---|
| North | Alice | 100 | 300 |
| North | Bob | 200 | 300 |
| South | Alice | 150 | 400 |
| South | Charlie | 250 | 400 |
发生了啥?SQL 按 region(North 和 South)把行分组,然后对每组单独用 SUM()。所以,"North" 组里的每行总额一样,"South" 组也是一样。
PARTITION BY 的实际用法例子
来看看 PARTITION BY 在实际开发里怎么用。
例子 1:组内排名
假如我们想在每个区域里给销售员按销售额排个名。可以用 PARTITION BY 加 RANK():
SELECT
region,
salesperson,
amount,
RANK() OVER (PARTITION BY region ORDER BY amount DESC) AS rank_in_region
FROM sales;
结果:
| region | salesperson | amount | rank_in_region |
|---|---|---|---|
| North | Bob | 200 | 1 |
| North | Alice | 100 | 2 |
| South | Charlie | 250 | 1 |
| South | Alice | 150 | 2 |
RANK() 会在每个 region 组里从 1 开始给行编号。注意,每组都是从 1 开始。
例子 2:和组内平均值对比
比如我们想看看每个销售员赚的和自己区域的平均比起来咋样。用 AVG():
SELECT
region,
salesperson,
amount,
AVG(amount) OVER (PARTITION BY region) AS avg_sales_by_region,
amount - AVG(amount) OVER (PARTITION BY region) AS diff_from_avg
FROM sales;
结果:
| region | salesperson | amount | avg_sales_by_region | diff_from_avg |
|---|---|---|---|---|
| North | Alice | 100 | 150 | -50 |
| North | Bob | 200 | 150 | 50 |
| South | Alice | 150 | 200 | -50 |
| South | Charlie | 250 | 200 | 50 |
SQL 先按 region 分组,然后算每组的 AVG(amount),最后每行再算和平均的差值。
例子 3:组内行号
比如你想给每个区域里的交易编号。用 ROW_NUMBER():
SELECT
region,
salesperson,
amount,
ROW_NUMBER() OVER (PARTITION BY region ORDER BY amount DESC) AS row_number
FROM sales;
结果:
| region | salesperson | amount | row_number |
|---|---|---|---|
| North | Bob | 200 | 1 |
| North | Alice | 100 | 2 |
| South | Charlie | 250 | 1 |
| South | Alice | 150 | 2 |
和 GROUP BY 的区别
很多人会把 PARTITION BY 和 GROUP BY 搞混。来对比一下:
GROUP BY
GROUP BY 会改变输出结构——它把表的行变成聚合结果。比如:
SELECT
region,
SUM(amount) AS total_sales
FROM sales
GROUP BY region;
结果:
| region | total_sales |
|---|---|
| North | 300 |
| South | 400 |
这里我们就看不到销售员的信息了,因为数据被聚合了。
PARTITION BY
PARTITION BY 则不会改变结构。每行都还在,只是多了按组算出来的值。也就是说,PARTITION BY 能聚合但不丢细节。
用 PARTITION BY 时常见的坑
坑 1:忘了 PARTITION BY
有时候你想分组,但忘了写 PARTITION BY。比如:
SELECT
region,
salesperson,
amount,
SUM(amount) OVER () AS total_sales
FROM sales;
结果:
| region | salesperson | amount | total_sales |
|---|---|---|---|
| North | Alice | 100 | 700 |
| North | Bob | 200 | 700 |
| South | Alice | 150 | 700 |
| South | Charlie | 250 | 700 |
这里 SUM(amount) 是全表的总和,不是按区域分的。如果你想按区域分,记得加 PARTITION BY region。
坑 2:ORDER BY 顺序写错了
窗口里的行顺序对 RANK() 或 ROW_NUMBER() 这些函数很重要。用 OVER() 里的 ORDER BY 时要小心顺序。
GO TO FULL VERSION