趣动WillGo手机版
233.06MB · 2025-10-14
GROUP BY
是PostgreSQL中用于分组聚合的核心子句,它将表中具有相同值的行归为一组,然后对每组计算聚合函数(如sum
、avg
、count
)。例如,我们有一张记录商品销售的test1
表:
CREATE TABLE test1 (x TEXT, y INT);
INSERT INTO test1 VALUES ('a', 3), ('c', 2), ('b', 5), ('a', 1);
若要计算每个x
对应的y
之和,可使用:
SELECT x, sum(y) FROM test1 GROUP BY x;
结果会按x
分组,返回每组的求和结果:
x | sum
---+-----
a | 4
b | 5
c | 2
关键规则:SELECT
列表中的列要么在GROUP BY
中(分组键),要么被聚合函数包裹(否则会因“非分组列无法确定唯一值”报错)。
PostgreSQL支持功能依赖(Functional Dependency)优化:若GROUP BY
的列是表的主键或唯一约束,则其他依赖于该列的列(即主键能唯一确定的列)无需加入GROUP BY
。例如,products
表的product_id
是主键,name
和price
依赖于product_id
:
CREATE TABLE products (
product_id INT PRIMARY KEY,
name TEXT NOT NULL,
price NUMERIC(10,2) NOT NULL
);
CREATE TABLE sales (
sale_id INT PRIMARY KEY,
product_id INT REFERENCES products(product_id),
units INT NOT NULL,
sale_date DATE NOT NULL
);
查询每个产品的总销售额时,无需将name
和price
加入GROUP BY
:
SELECT
p.product_id,
p.name, -- 依赖于product_id,无需GROUP BY
sum(s.units * p.price) AS total_sales
FROM products p
LEFT JOIN sales s ON p.product_id = s.product_id
GROUP BY p.product_id; -- 仅需分组主键
这一优化减少了分组的复杂度,因为PostgreSQL知道主键能唯一确定其他列的值,无需额外分组检查。
当需要生成多个分组的聚合结果时(如同时按“品牌”“尺寸”“总合计”分组),多次查询会重复扫描数据,而GROUPING SETS
、CUBE
、ROLLUP
能一次性生成多组聚合,大幅提升效率。
以items_sold
表为例:
CREATE TABLE items_sold (brand TEXT, size TEXT, sales INT);
INSERT INTO items_sold VALUES
('Foo', 'L', 10), ('Foo', 'M', 20), ('Bar', 'M', 15), ('Bar', 'L', 5);
若要同时按brand
、size
和“总合计”分组,使用GROUPING SETS
:
SELECT brand, size, sum(sales)
FROM items_sold
GROUP BY GROUPING SETS ((brand), (size), ()); -- 三组分组
结果会返回三组聚合:
brand | size | sum
-------+------+-----
Foo | | 30 -- 按brand分组
Bar | | 20
| L | 15 -- 按size分组
| M | 35
| | 50 -- 总合计(空分组)
CUBE(a, b)
:生成a
、b
、a+b
、空分组的所有组合(即“立方体”聚合);ROLLUP(a, b)
:生成a+b
、a
、空分组的层级聚合(如“省份+城市”“省份”“全国”)。这些扩展避免了多次全表扫描,是处理多维度分析的高效工具。
HAVING
用于过滤分组后的结果,而WHERE
用于过滤原始行。例如:
-- 过滤“sum(y) > 3”的分组
SELECT x, sum(y) FROM test1 GROUP BY x HAVING sum(y) > 3;
-- 过滤“x < 'c'”的原始行,再分组
SELECT x, sum(y) FROM test1 WHERE x < 'c' GROUP BY x;
注意:HAVING
可以使用聚合函数,WHERE
不能(WHERE
过滤的是未分组的行,聚合函数尚未计算)。
ORDER BY
的性能核心是是否能利用索引避免排序。PostgreSQL的索引是有序的(如B-tree索引),若ORDER BY
的列顺序与索引完全一致(包括ASC/DESC),则可直接通过索引获取有序数据,跳过Sort
操作。
例如,orders
表的order_date
列有索引:
CREATE INDEX idx_orders_order_date ON orders(order_date DESC);
查询最新订单时,执行计划会使用Index Scan
而非Seq Scan + Sort
:
SELECT * FROM orders ORDER BY order_date DESC LIMIT 10;
执行计划示例:
Limit (cost=0.29..1.04 rows=10 width=44)
-> Index Scan using idx_orders_order_date on orders (cost=0.29..74.29 rows=1000 width=44)
若ORDER BY
的列没有索引,PostgreSQL会进行内存排序(in-memory sort),若数据量超过work_mem
(默认4MB),则会写入临时文件(外部排序),性能骤降。
Top-N查询(如“取最新10条数据”)是ORDER BY
的常见场景,PostgreSQL会使用Top-N Heapsort优化:只需维护一个大小为N的堆(如10),遍历数据时不断替换堆中最小的元素,无需排序整个结果集。
例如,查询销量最高的5个产品:
SELECT product_id, sum(units) AS total_units
FROM sales
GROUP BY product_id
ORDER BY total_units DESC
LIMIT 5;
若sum(units)
无法用索引,Top-N Heapsort仍比全排序高效——因为堆的大小远小于总数据量。
work_mem
参数控制PostgreSQL用于排序、哈希等操作的内存上限。若排序数据量超过work_mem
,会触发外部排序(将数据分成多个块,每个块内存排序后写入临时文件,最后合并块),性能下降明显。
优化方法:
work_mem
(会话级别,不影响全局):
SET work_mem = '64MB'; -- 将排序内存提升至64MB
WHERE
过滤不必要的行,或LIMIT
限制结果数。窗口函数(如row_number()
、sum() OVER()
)用于计算每行的“窗口内”聚合(如累计销售额、排名),其执行顺序在GROUP BY
之后、SELECT
之前:
FROM → WHERE → GROUP BY → HAVING → 窗口函数 → SELECT → ORDER BY
例如,计算每个产品的累计销售额:
SELECT
s.product_id,
s.sale_date,
s.units * p.price AS daily_sales,
sum(s.units * p.price) OVER (
PARTITION BY s.product_id -- 按产品分区
ORDER BY s.sale_date -- 按日期排序
) AS running_total
FROM sales s
JOIN products p ON s.product_id = p.product_id;
PARTITION BY
将数据分成多个“窗口”(如每个产品一组),ORDER BY
定义窗口内的行顺序,sum() OVER()
计算窗口内的累计和。
窗口函数的性能取决于窗口内数据的有序性。若PARTITION BY
和ORDER BY
的列有复合索引,PostgreSQL可快速分区并排序,避免额外的Sort
操作。
例如,为sales
表创建product_id + sale_date
的复合索引:
CREATE INDEX idx_sales_product_date ON sales(product_id, sale_date);
上述累计销售额查询的执行计划会跳过排序,直接使用索引获取有序数据:
WindowAgg (cost=0.56..1.71 rows=100 width=56)
-> Index Scan using idx_sales_product_date on sales s (cost=0.29..1.21 rows=100 width=28)
Join Filter: (s.product_id = p.product_id)
若多个窗口函数使用完全相同的PARTITION BY
和ORDER BY
,可通过WINDOW
子句定义窗口并复用,减少重复计算。
例如,同时计算累计销售额和排名:
SELECT
product_id,
sale_date,
daily_sales,
sum(daily_sales) OVER w AS running_total, -- 复用窗口w
row_number() OVER w AS rank -- 复用窗口w
FROM (
SELECT
s.product_id,
s.sale_date,
s.units * p.price AS daily_sales
FROM sales s
JOIN products p ON s.product_id = p.product_id
) AS subquery
WINDOW w AS (PARTITION BY product_id ORDER BY sale_date); -- 定义窗口w
WINDOW
子句将窗口逻辑集中定义,PostgreSQL只需计算一次窗口,提升效率。
窗口函数的Frame Clause
(如ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
)定义窗口内的行范围,不同类型的Frame性能差异显著:
例如,累计销售额应使用ROWS Frame
(默认即ROWS UNBOUNDED PRECEDING
):
sum(daily_sales) OVER (PARTITION BY product_id ORDER BY sale_date) -- 默认ROWS
若需按值范围计算(如“最近7天的销售额”),则需使用RANGE Frame
,但需注意性能:
sum(daily_sales) OVER (
PARTITION BY product_id
ORDER BY sale_date
RANGE BETWEEN INTERVAL '7 days' PRECEDING AND CURRENT ROW
)
答案:因为主键列具有功能依赖性——主键的值唯一确定了其他列的值(如product_id
确定name
和price
)。PostgreSQL支持这一优化,允许SELECT
列表中包含依赖列,无需将它们加入GROUP BY
,减少分组的复杂度和计算量。
答案:
ORDER BY
的列顺序与索引完全一致(包括ASC/DESC),直接通过索引获取有序数据。ORDER BY
使用表达式(如LOWER(name)
),需为表达式创建索引(如CREATE INDEX idx_name_lower ON users(LOWER(name))
)。答案:
PARTITION BY
的列若有索引,可快速将数据分成不同窗口,避免额外的分组操作。ORDER BY
的列若有索引,可直接获取窗口内的有序数据,跳过Sort
操作。PARTITION BY列 + ORDER BY列
)能最大化窗口函数的性能,因为无需任何额外排序或分组。ERROR: column "table.column" must appear in the GROUP BY clause or be used in an aggregate function
原因:SELECT
列表中的列既不在GROUP BY
中,也未被聚合函数包裹(违反分组规则)。
解决方法:
GROUP BY
(如GROUP BY x, y
)。sum(y)
)。GROUP BY
的主键,无需加入)。ERROR: window function requires an OVER clause
原因:使用了窗口函数(如row_number()
)但未指定OVER
子句(窗口函数必须通过OVER
定义窗口)。
解决方法:为窗口函数添加OVER
子句,指定PARTITION BY
和ORDER BY
(如row_number() OVER (PARTITION BY product_id ORDER BY sale_date)
)。
预防建议:编写窗口函数时,确保每个函数都有对应的OVER
子句。
ERROR: could not sort because work_mem exceeded
原因:排序数据量超过work_mem
,触发外部排序(写入临时文件)。
解决方法:
work_mem
:SET work_mem = '64MB'
(会话级别,不影响全局)。WHERE
过滤或LIMIT
限制结果数。work_mem
,避免不必要的排序。参考链接:www.postgresql.org/docs/17/que…
参考链接:www.postgresql.org/docs/17/que…
参考链接:www.postgresql.org/docs/17/ind…
参考链接:www.postgresql.org/docs/17/sql…
参考链接:www.postgresql.org/docs/17/run…
233.06MB · 2025-10-14
126.01MB · 2025-10-14
238.09MB · 2025-10-14
谷歌 Pixel 9、三星 Galaxy S25 等手机中招:安卓新攻击 30 秒内可盗取 2FA 验证码,成功率最高 73%
全球首个聚变能研究与培训协作中心落地中国