CodeGym /课程 /SQL SELF /使用窗口函数时的常见错误

使用窗口函数时的常见错误

SQL SELF
第 30 级, 课程 4
可用

现在该聊聊用窗口函数时可能遇到的各种麻烦了。就像编程(还有生活)里一样,最好还是多看看别人怎么踩坑的。我们来拆解下新手(有时候甚至老司机)常犯的错误,然后学着别再踩。

错误 №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 到底怎么跑你的查询。

用真实数据多测测:确保结果真的是你想要的,问题都能解决。

2
任务
SQL SELF, 第 30 级, 课程 4
已锁定
在窗口函数中使用 ORDER BY
在窗口函数中使用 ORDER BY
1
调查/小测验
窗口框架设置第 30 级,课程 4
不可用
窗口框架设置
窗口框架设置
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION