引言
如果你从事数据处理工作,GROUP BY 和聚合函数就是分析查询的基石。无论是计算每个地区的总销售额、统计每日用户数,还是查找平均交易金额,DuckDB 都提供了远超 SQL 标准的丰富聚合功能。
DuckDB 是一款专为数据科学和分析工作负载设计的嵌入式分析数据库。它的 GROUP BY 实现包含 GROUP BY ALL、GROUPING SETS、CUBE 和 ROLLUP 等现代扩展,能大幅简化复杂的报表查询。
在本指南中,你将学习:
- GROUP BY 基础语法和常用聚合函数
- 多列分组与 HAVING 子句
- DuckDB 特色功能:GROUPING SETS、CUBE、ROLLUP
- 便捷的
GROUP BY ALL语法 - 真实数据场景的实战案例
本指南基于 DuckDB SQL 语法指南 和 DuckDB 2026 入门指南 中的知识。如果你是 DuckDB 新手,建议先从这些文章入手。
1. GROUP BY 基础语法
GROUP BY 子句将指定列中具有相同值的行分组,然后对每个分组应用聚合函数。
SELECT 列名, 聚合函数(列名)
FROM 表名
GROUP BY 列名;
我们先创建一个示例数据集——销售表:
CREATE TABLE sales AS
SELECT * FROM (VALUES
('电子产品', '华北', 1200, '2026-01-15'),
('服装', '华北', 450, '2026-01-16'),
('电子产品', '华南', 1800, '2026-01-17'),
('服装', '华南', 600, '2026-01-18'),
('电子产品', '华北', 900, '2026-01-19'),
('服装', '华北', 300, '2026-02-01'),
('电子产品', '华南', 2100, '2026-02-02'),
('服装', '华南', 750, '2026-02-03')
) AS t(category, region, amount, sale_date);
简单的 GROUP BY 查询——按类别统计总销售额:
SELECT category, SUM(amount) AS total_sales
FROM sales
GROUP BY category;
结果:
| category | total_sales |
|---|---|
| 电子产品 | 6000 |
| 服装 | 2100 |
2. 常用聚合函数
DuckDB 支持所有标准 SQL 聚合函数。以下是使用频率最高的几个:
COUNT — 计数
SELECT category, COUNT(*) AS num_orders, COUNT(DISTINCT region) AS regions
FROM sales
GROUP BY category;
| category | num_orders | regions |
|---|---|---|
| 电子产品 | 4 | 2 |
| 服装 | 4 | 2 |
SUM — 求和
SELECT region, SUM(amount) AS total_revenue
FROM sales
GROUP BY region;
| region | total_revenue |
|---|---|
| 华北 | 2850 |
| 华南 | 5250 |
AVG — 求平均值
SELECT category, AVG(amount) AS avg_order_value
FROM sales
GROUP BY category;
| category | avg_order_value |
|---|---|
| 电子产品 | 1500.0 |
| 服装 | 525.0 |
MIN / MAX — 求极值
SELECT category,
MIN(amount) AS smallest_order,
MAX(amount) AS largest_order
FROM sales
GROUP BY category;
| category | smallest_order | largest_order |
|---|---|---|
| 电子产品 | 900 | 2100 |
| 服装 | 300 | 750 |
组合多个聚合函数
你可以在一个查询中混合使用多个聚合函数:
SELECT category,
COUNT(*) AS num_orders,
SUM(amount) AS total_revenue,
AVG(amount) AS avg_order_value,
MIN(amount) AS min_order,
MAX(amount) AS max_order
FROM sales
GROUP BY category;
3. 多列 GROUP BY
按多列分组会为每一组唯一的值组合创建一个独立分组:
SELECT category, region,
SUM(amount) AS total_sales,
COUNT(*) AS num_orders
FROM sales
GROUP BY category, region;
| category | region | total_sales | num_orders |
|---|---|---|---|
| 电子产品 | 华北 | 2100 | 2 |
| 电子产品 | 华南 | 3900 | 2 |
| 服装 | 华北 | 750 | 2 |
| 服装 | 华南 | 1350 | 2 |
这对于层级报表非常有用——一次查询就能看到按多个维度分解的业绩表现。
4. HAVING 子句
HAVING 在聚合后过滤分组,类似于 WHERE 在聚合前过滤行。
SELECT category, SUM(amount) AS total_sales
FROM sales
GROUP BY category
HAVING SUM(amount) > 2500;
| category | total_sales |
|---|---|
| 电子产品 | 6000 |
WHERE 与 HAVING 的关键区别:
WHERE在分组前过滤行(不能使用聚合函数)HAVING在分组后过滤分组(可以使用聚合函数)
-- WHERE 在聚合前过滤行
-- HAVING 在聚合后过滤分组
SELECT region, SUM(amount) AS total_sales
FROM sales
WHERE amount > 500 -- 分组前排除小额订单
GROUP BY region
HAVING SUM(amount) > 2000; -- 只显示总额超过 2000 的地区
| region | total_sales |
|---|---|
| 华南 | 4650 |
(注:华北地区超过 500 元的订单总额仅为 1350,因此被 HAVING 排除。)
5. GROUPING SETS、CUBE 和 ROLLUP
这是 DuckDB 的 GROUP BY 功能最闪耀的地方。这些特性让你能用一条查询生成小计和总计。
GROUPING SETS
GROUPING SETS 允许你显式指定多个分组层级:
SELECT category, region, SUM(amount) AS total_sales
FROM sales
GROUP BY GROUPING SETS (
(category, region), -- 明细层级
(category), -- 按类别小计
(region), -- 按地区小计
() -- 总计
);
| category | region | total_sales |
|---|---|---|
| 电子产品 | 华北 | 2100 |
| 电子产品 | 华南 | 3900 |
| 服装 | 华北 | 750 |
| 服装 | 华南 | 1350 |
| 电子产品 | NULL | 6000 |
| 服装 | NULL | 2100 |
| NULL | 华北 | 2850 |
| NULL | 华南 | 5250 |
| NULL | NULL | 8100 |
GROUPING() 函数可以帮助区分实际数据中的 NULL 值和小计标记:
SELECT
CASE WHEN GROUPING(category) = 0 THEN category ELSE 'ALL' END AS category,
CASE WHEN GROUPING(region) = 0 THEN region ELSE 'ALL' END AS region,
SUM(amount) AS total_sales
FROM sales
GROUP BY GROUPING SETS ((category, region), (category), (region), ());
ROLLUP
ROLLUP 按层级生成小计——非常适合时间序列和层级数据:
-- 层级:类别 → 地区 → 总计
SELECT category, region, SUM(amount) AS total_sales
FROM sales
GROUP BY ROLLUP (category, region);
等价于:
GROUP BY GROUPING SETS ((category, region), (category), ())
ROLLUP 非常适合按年→季度→月或部门→团队→员工进行报表统计。
CUBE
CUBE 生成所有可能的组合小计:
-- 所有组合:类别 × 地区
SELECT category, region, SUM(amount) AS total_sales
FROM sales
GROUP BY CUBE (category, region);
等价于:
GROUP BY GROUPING SETS ((category, region), (category), (region), ())
对于 N 个维度,CUBE 生成 2^N 个分组集合,而 ROLLUP 生成 N+1 个。
6. GROUP BY ALL —— DuckDB 的生产力加速器
DuckDB 0.8.0 引入的 GROUP BY ALL 是编写快速分析查询的游戏规则改变者。它会自动根据 SELECT 列表中所有未聚合的列进行分组——你无需手动列出它们。
不使用 GROUP BY ALL:
SELECT category, region, SUM(amount) AS total_sales
FROM sales
GROUP BY category, region; -- 必须重复列名
使用 GROUP BY ALL:
SELECT category, region, SUM(amount) AS total_sales
FROM sales
GROUP BY ALL; -- 自动按 category 和 region 分组
这看起来简单,但在探索性分析中,当你快速迭代查询时,它能节省大量时间:
-- 增加更多维度——GROUP BY ALL 自动处理
SELECT category, region, sale_date, SUM(amount) AS daily_sales
FROM sales
GROUP BY ALL;
-- 复杂表达式也能工作
SELECT category,
year(sale_date) AS sale_year,
SUM(amount) AS total_sales
FROM sales
GROUP BY ALL;
GROUP BY ALL 遵循一个简单规则:SELECT 列表中所有未被聚合函数包裹的列都成为分组列。当你有很多列且不想重复输入时,这尤其强大。
专业提示:
GROUP BY ALL是 DuckDB 在主流数据库中的独特功能。这也是让 DuckDB 在交互式数据探索中异常顺手的功能之一。
7. 真实数据实战案例
案例一:电商订单分析
-- 创建代表真实电商数据的订单表
CREATE TABLE orders AS
SELECT * FROM (VALUES
('ORD-001', '张三', '标准件', 3, 15.99, '2026-01-05'),
('ORD-002', '李四', '精密件', 1, 49.99, '2026-01-06'),
('ORD-003', '张三', '标准件', 2, 15.99, '2026-01-10'),
('ORD-004', '王五', '精密件', 5, 49.99, '2026-01-12'),
('ORD-005', '李四', '标准件', 10, 15.99, '2026-01-15'),
('ORD-006', '张三', '精密件', 1, 49.99, '2026-01-20'),
('ORD-007', '王五', '标准件', 4, 15.99, '2026-01-22'),
('ORD-008', '李四', '高端件', 2, 199.99,'2026-02-01'),
('ORD-009', '张三', '高端件', 1, 199.99,'2026-02-05'),
('ORD-010', '王五', '高端件', 3, 199.99,'2026-02-10')
) AS t(order_id, customer, product, quantity, price, order_date);
-- 每位客户的消费总额(含订单数和平均消费)
SELECT customer,
COUNT(*) AS num_orders,
SUM(quantity * price) AS total_spent,
AVG(quantity * price) AS avg_order_value,
MAX(order_date) AS last_order_date
FROM orders
GROUP BY customer
ORDER BY total_spent DESC;
-- 月度收入报表(含小计)
SELECT year(order_date) AS yr,
month(order_date) AS mo,
SUM(quantity * price) AS revenue
FROM orders
GROUP BY ROLLUP (yr, mo)
ORDER BY yr NULLS LAST, mo NULLS LAST;
案例二:客户分层
-- 根据消费行为对客户分层
SELECT customer,
SUM(quantity * price) AS total_spent,
COUNT(*) AS order_count,
CASE
WHEN SUM(quantity * price) >= 500 THEN 'VIP'
WHEN SUM(quantity * price) >= 200 THEN '普通'
ELSE '入门'
END AS segment
FROM orders
GROUP BY customer
ORDER BY total_spent DESC;
案例三:各地区热销品类
-- 使用 GROUP BY 加过滤条件
SELECT region, category, SUM(amount) AS total_sales
FROM sales
GROUP BY region, category
HAVING SUM(amount) > 1000
ORDER BY region, total_sales DESC;
案例四:统计聚合
DuckDB 还支持统计类聚合函数:
SELECT category,
AVG(amount) AS mean,
STDDEV(amount) AS std_dev,
VARIANCE(amount) AS variance,
MEDIAN(amount) AS median_value,
MODE(amount) AS most_common_value
FROM sales
GROUP BY category;
8. GROUP BY 性能优化技巧
- 列顺序很重要——将高基数(唯一值多)的列放在 GROUP BY 前列,能获得更好的哈希表性能。
- 探索阶段使用 GROUP BY ALL——减少打字错误,加速迭代查询。
- 优先使用 ROLLUP 而非多个 UNION ALL 查询——ROLLUP 一次扫描就能计算所有小计。
- 尽早过滤——在聚合前使用 WHERE 减少行数,能显著加快查询速度。
- 考虑使用物化视图——对于大数据集上的重复聚合,DuckDB 的物化视图可以缓存结果。
总结
DuckDB 的 GROUP BY 和聚合功能是所有数据库系统中最为强大的之一。从基础的 COUNT 和 SUM,到借助 GROUPING SETS、CUBE 和 ROLLUP 进行高级多维分析,DuckDB 为数据汇总和报表提供了所需的全部工具。
GROUP BY ALL 语法是一个突出的特色功能,能显著提升探索性数据分析的效率——其他主流数据库均未提供此便利。结合 DuckDB 在分析工作负载中的出色性能,它成为数据科学家、分析师和工程师快速汇总分析数据的理想选择。
要继续你的 DuckDB 学习之旅,请查阅 DuckDB SQL 语法指南 和 DuckDB 2026 入门指南,获取更多基础知识。
最后更新:2026年5月30日
