现在该聊聊用窗口函数时可能遇到的各种麻烦了。就像编程(还有生活)里一样,最好还是多看看别人怎么踩坑的。我们来拆解下新手(有时候甚至老司机)常犯的错误,然后学着别再踩。
错误 №1:PARTITION BY 用错了
最常见的错误之一——忘了写 PARTITION BY,或者写错了,尤其是你想把数据分组的时候。如果没有它,PostgreSQL 会把所有行当成一个大组,结果肯定和你想的不一样。
比如我们有个表叫 sales,里面是销售数据:
| id | region | month | total |
|---|---|---|---|
| 1 | North | 2023-01 | 1000 |
| 2 | South | 2023-01 | 800 |
| 3 | North | 2023-02 | 1200 |
| 4 | South | 2023-02 | 900 |
你想查每个地区每个月的累计销售额(SUM())。你可能会写出这样的查询:
SELECT
region,
month,
SUM(total) OVER (ORDER BY month) AS running_total
FROM
sales;
结果:
| region | month | running_total |
|---|---|---|
| North | 2023-01 | 1000 |
| South | 2023-01 | 1800 |
| North | 2023-02 | 3000 |
| South | 2023-02 | 3900 |
乍一看好像没啥问题。但其实结果完全不对,因为累计和是对所有行一起算的,不是按地区分组。问题就是我们忘了加 PARTITION BY region。
修正后的代码:
SELECT
region,
month,
SUM(total) OVER (PARTITION BY region ORDER BY month) AS running_total
FROM
sales;
结果:
| region | month | running_total |
|---|---|---|
| North | 2023-01 | 1000 |
| North | 2023-02 | 2200 |
| South | 2023-01 | 800 |
| South | 2023-02 | 1700 |
现在就对了:数据按地区分组,每个地区的累计和是分开算的。
错误 №2:ORDER BY 顺序不对
OVER() 里的 ORDER BY 控制窗口内行的顺序。如果顺序错了,结果会很奇怪。
你想查销售额的累计和,按月份降序排列。你可能会写:
SELECT
month,
total,
SUM(total) OVER (ORDER BY month DESC) AS running_total
FROM
sales;
结果:
| month | total | running_total |
|---|---|---|
| 2023-02 | 1200 | 1200 |
| 2023-02 | 900 | 2100 |
| 2023-01 | 1000 | 3100 |
| 2023-01 | 800 | 3900 |
看起来没啥问题,但注意:行是按月份分组的,累计和其实没按你想的方式算,因为顺序是降序,结果就乱了。
修正方法: 把查询里的 ORDER BY 顺序改对:
SELECT
month,
total,
SUM(total) OVER (ORDER BY month ASC) AS running_total
FROM
sales;
错误 №3:用窗口函数但没加索引
窗口函数经常要处理很多数据,如果关键列没加索引,性能会惨不忍睹。
举个例子: 我们有个 large_sales 表,里面有几百万行,我们想算销售排名:
SELECT
id,
total,
RANK() OVER (ORDER BY total DESC) AS rank
FROM
large_sales;
数据量小的时候还行,数据一多就慢得要命。
修正方法:给 ORDER BY 用到的列加索引:
CREATE INDEX idx_total ON large_sales(total DESC);
现在查询速度会快很多。
错误 №4:没搞懂 ROWS 和 RANGE 的区别
用 ROWS 和 RANGE 时,得明白它们窗口是怎么算的。搞错了,结果就会很迷。
举个例子: 你想算当前月和前两个月的滑动平均销售额:
SELECT
month,
AVG(total) OVER (ORDER BY month ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS moving_avg
FROM
sales;
如果你把 ROWS 换成 RANGE:
SELECT
month,
AVG(total) OVER (ORDER BY month RANGE BETWEEN 2 PRECEDING AND CURRENT ROW) AS moving_avg
FROM
sales;
结果就不一样了,因为 RANGE 是按值范围算的,不是按行数。
错误 №5:窗口函数用太多
一个查询里用好几个窗口函数,可能会导致重复计算,性能也会变差。
例子:
SELECT
id,
total,
SUM(total) OVER (PARTITION BY region) AS region_total,
SUM(total) OVER (PARTITION BY region) / COUNT(total) OVER (PARTITION BY region) AS region_avg
FROM
sales;
这里 SUM(total) 和 COUNT(total) 每行都要算好几遍。
修正方法: 用子查询或者 CTE 简化一下:
WITH cte_region_totals AS (
SELECT
region,
SUM(total) AS region_total,
COUNT(total) AS region_count
FROM
sales
GROUP BY
region
)
SELECT
s.id,
s.total,
t.region_total,
t.region_total / t.region_count AS region_avg
FROM
sales s
JOIN
cte_region_totals t ON s.region = t.region;
避免这些坑的小建议
检查 PARTITION BY 和 ORDER BY:每次都确认窗口是不是你想要的。
给数据加索引:特别是你要排序(ORDER BY)或者过滤的时候。
多次计算用 CTE:这样能少点重复步骤。
看执行计划:用 EXPLAIN 和 EXPLAIN ANALYZE,看看 PostgreSQL 到底怎么跑你的查询。
用真实数据多测测:确保结果真的是你想要的,问题都能解决。
GO TO FULL VERSION