Featured image of post DuckDB GROUP BY 与聚合函数完全指南:数据分组与汇总

DuckDB GROUP BY 与聚合函数完全指南:数据分组与汇总

DuckDB GROUP BY 与聚合函数完整教程,涵盖 COUNT、SUM、AVG、MIN、MAX、HAVING、GROUPING SETS、CUBE、ROLLUP 和 GROUP BY ALL 语法,附真实数据实战案例。

引言

如果你从事数据处理工作,GROUP BY聚合函数就是分析查询的基石。无论是计算每个地区的总销售额、统计每日用户数,还是查找平均交易金额,DuckDB 都提供了远超 SQL 标准的丰富聚合功能。

DuckDB 是一款专为数据科学和分析工作负载设计的嵌入式分析数据库。它的 GROUP BY 实现包含 GROUP BY ALLGROUPING SETSCUBEROLLUP 等现代扩展,能大幅简化复杂的报表查询。

在本指南中,你将学习:

  • 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;

结果:

categorytotal_sales
电子产品6000
服装2100

2. 常用聚合函数

DuckDB 支持所有标准 SQL 聚合函数。以下是使用频率最高的几个:

COUNT — 计数

SELECT category, COUNT(*) AS num_orders, COUNT(DISTINCT region) AS regions
FROM sales
GROUP BY category;
categorynum_ordersregions
电子产品42
服装42

SUM — 求和

SELECT region, SUM(amount) AS total_revenue
FROM sales
GROUP BY region;
regiontotal_revenue
华北2850
华南5250

AVG — 求平均值

SELECT category, AVG(amount) AS avg_order_value
FROM sales
GROUP BY category;
categoryavg_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;
categorysmallest_orderlargest_order
电子产品9002100
服装300750

组合多个聚合函数

你可以在一个查询中混合使用多个聚合函数:

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;
categoryregiontotal_salesnum_orders
电子产品华北21002
电子产品华南39002
服装华北7502
服装华南13502

这对于层级报表非常有用——一次查询就能看到按多个维度分解的业绩表现。


4. HAVING 子句

HAVING 在聚合后过滤分组,类似于 WHERE 在聚合前过滤行。

SELECT category, SUM(amount) AS total_sales
FROM sales
GROUP BY category
HAVING SUM(amount) > 2500;
categorytotal_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 的地区
regiontotal_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),            -- 按地区小计
    ()                   -- 总计
);
categoryregiontotal_sales
电子产品华北2100
电子产品华南3900
服装华北750
服装华南1350
电子产品NULL6000
服装NULL2100
NULL华北2850
NULL华南5250
NULLNULL8100

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 性能优化技巧

  1. 列顺序很重要——将高基数(唯一值多)的列放在 GROUP BY 前列,能获得更好的哈希表性能。
  2. 探索阶段使用 GROUP BY ALL——减少打字错误,加速迭代查询。
  3. 优先使用 ROLLUP 而非多个 UNION ALL 查询——ROLLUP 一次扫描就能计算所有小计。
  4. 尽早过滤——在聚合前使用 WHERE 减少行数,能显著加快查询速度。
  5. 考虑使用物化视图——对于大数据集上的重复聚合,DuckDB 的物化视图可以缓存结果。

总结

DuckDB 的 GROUP BY 和聚合功能是所有数据库系统中最为强大的之一。从基础的 COUNTSUM,到借助 GROUPING SETSCUBEROLLUP 进行高级多维分析,DuckDB 为数据汇总和报表提供了所需的全部工具。

GROUP BY ALL 语法是一个突出的特色功能,能显著提升探索性数据分析的效率——其他主流数据库均未提供此便利。结合 DuckDB 在分析工作负载中的出色性能,它成为数据科学家、分析师和工程师快速汇总分析数据的理想选择。

要继续你的 DuckDB 学习之旅,请查阅 DuckDB SQL 语法指南DuckDB 2026 入门指南,获取更多基础知识。


最后更新:2026年5月30日

📺 Watch video tutorials → DuckDB Lab YouTube

Subscribe for more DuckDB & AI automation tutorials

使用 Hugo 构建
主题 StackJimmy 设计