PostgreSQL 教程

PostgreSQL GROUP BY 子句

GROUP BY 子句是数据分析的基础工具,它通过将数据划分为逻辑上的“组”来进行分析。该子句通常与聚合函数配合使用,允许你对数据的子集(而不是整个数据集)执行计算。它将具有相同特征的数据归纳到一起,并为每个分组返回一行汇总结果。

1. 什么是 GROUP BY 子句

GROUP BY 子句的主要目的是将具有相同值的多行数据整合为每个分组的单行汇总数据。

当你想要对这些分组应用聚合函数(如上一章讨论的 COUNTSUMAVGMINMAX)时,这尤为有用。如果没有 GROUP BY,聚合函数将针对整个查询结果集运行,只返回一个单一的汇总值;而使用了 GROUP BY,你将获得每个独立分组的汇总值。

1.1 基本语法

GROUP BY 子句通常跟在 FROMWHERE 子句之后,并位于 ORDER BYHAVING 子句之前。

SELECT
    column1,
    aggregate_function(column2) -- 聚合函数
FROM
    table_name
WHERE
    condition -- 过滤条件
GROUP BY
    column1;

在这里,column1 是你希望用来分组的列(可以是一列或多列)。特别注意:SELECT 列表中,所有未包含在聚合函数中的列,都必须包含在 GROUP BY 子句中。

2. 单列分组

按单列分组会根据该列中不重复(唯一)的值来创建组。

2.1 示例 1:计算每位客户的订单数

假设有一个包含 order_id(订单 ID)、customer_id(客户 ID)和 order_total(订单总额)的 orders(订单)表。想知道每位客户下了多少订单,你需要按 customer_id 分组,并使用 COUNT() 聚合函数。

SELECT
    customer_id,
    COUNT(order_id) AS 总订单数
FROM
    orders
GROUP BY
    customer_id;

此查询返回两列:customer_id 以及该特定客户的总订单数。在结果集中,每个 customer_id 只会出现一次,代表一个独立的分组。

2.2 示例 2:计算每个分类的平均产品价格

假设有一个 products(产品)表,包含 product_id(产品 ID)、product_name(产品名称)、category(产品分类)和 price(价格)。要计算每个分类内产品的平均价格,你需要按 category 分组。

SELECT
    category,
    AVG(price) AS 平均价格
FROM
    products
GROUP BY
    category;

这个查询将生成一个产品分类列表,每个分类旁边会显示该分类下所有产品的平均价格。

3. 多列分组

你可以按多个列对数据进行分组。当按多列分组时,GROUP BY 子句会基于所有指定列的唯一组合来创建分组。

3.1 示例 1:每个客户在每个地区的总销售额

如果 orders 表还包含一个 region(地区)列,并且你想查看每位客户在每个特定地区的订单总金额。

SELECT
    customer_id,
    region,
    SUM(order_total) AS 总销售额
FROM
    orders
GROUP BY
    customer_id,
    region;

在这种情况下,系统会为 customer_idregion 的每一个独特组合创建一个新组。例如,“北美地区的客户 A” 和 “欧洲地区的客户 A” 将是两个不同的分组。

3.2 示例 2:每个部门和地点的员工人数

考虑一个 employees(员工)表,包含 employee_id(员工 ID)、employee_name(员工姓名)、department(部门)和 location(办公地点)。要统计部门和办公地点每种组合的员工人数:

SELECT
    department,
    location,
    COUNT(employee_id) AS 员工人数
FROM
    employees
GROUP BY
    department,
    location;

该查询将显示例如:“位于纽约的销售部”有多少员工,“位于伦敦的销售部”有多少员工等等。

4. 使用表达式分组

GROUP BY 子句不仅限于列名;你还可以按“表达式”进行分组。这提供了更灵活的分组策略。

4.1 示例:按订单日期的年份分组

如果你的 orders 表有一个 order_date(订单日期)列(类型为 DATETIMESTAMP),你可能希望按订单下达的年份进行分组。你可以使用日期函数提取年份。

SELECT
    EXTRACT(YEAR FROM order_date) AS 订单年份,
    COUNT(order_id) AS 总订单数
FROM
    orders
GROUP BY
    EXTRACT(YEAR FROM order_date);

在这里,EXTRACT(YEAR FROM order_date) 是一个表达式,它计算每个订单的年份,然后 GROUP BY 子句基于这些计算出的年份值进行分组。

5. 与聚合函数的关系

正如上一章所讨论的,聚合函数(如 COUNTSUMAVGMINMAX)从一组输入值中计算出一个单一结果。GROUP BY 子句改变了这些函数的作用范围,使它们独立地对每个分组进行操作。

如果在 SELECT 语句中使用聚合函数而不加 GROUP BY 子句,该函数会将 FROMWHERE 子句返回的所有行视为一整个大组。

SELECT
    SUM(order_total) AS 所有订单总收入
FROM
    orders;

此查询返回所有订单的一个单一总和。

当引入 GROUP BY 时,聚合函数会分别应用于每个组,为每个组提供一个汇总结果。

SELECT
    customer_id,
    SUM(order_total) AS 每位客户总收入
FROM
    orders
GROUP BY
    customer_id;

此查询为每个唯一的 customer_id 返回对应的 order_total 总和。

6. 实际案例与演示

让我们使用一个假设的 sales(销售)数据库,包含以下 transactions(交易)表的表结构:

transactions 表:

  • transaction_id (整数, 主键)
  • product_id (整数)
  • customer_id (整数)
  • sale_date (日期)
  • quantity (整数 - 数量)
  • unit_price (小数 - 单价)
  • store_location (字符串 - 门店位置)

插入一些样本数据:

INSERT INTO transactions (transaction_id, product_id, customer_id, sale_date, quantity, unit_price, store_location) VALUES
(1, 101, 1, '2023-01-10', 2, 15.00, '市中心 (Downtown)'),
(2, 102, 2, '2023-01-10', 1, 25.50, '商业区 (Uptown)'),
(3, 101, 1, '2023-01-11', 3, 15.00, '市中心 (Downtown)'),
(4, 103, 3, '2023-01-11', 1, 50.00, '郊区 (Suburban)'),
(5, 102, 2, '2023-01-12', 2, 25.50, '商业区 (Uptown)'),
(6, 104, 1, '2023-01-12', 1, 10.00, '市中心 (Downtown)'),
(7, 101, 4, '2023-01-13', 1, 15.00, '商业区 (Uptown)'),
(8, 103, 3, '2023-01-13', 2, 50.00, '郊区 (Suburban)'),
(9, 105, 5, '2023-01-14', 1, 5.00, '市中心 (Downtown)'),
(10, 101, 1, '2023-01-14', 1, 15.00, '商业区 (Uptown)');

6.1 示例 1:按门店位置统计总收入

计算每个门店位置产生的总收入。

SELECT
    store_location,
    SUM(quantity * unit_price) AS 总收入
FROM
    transactions
GROUP BY
    store_location;

解析:

  • SUM(quantity * unit_price) 计算每笔交易的总收入。
  • GROUP BY store_location 将每个独特的 store_location 的各笔交易收入汇总。
  • 结果将把 市中心商业区郊区 作为不同的行展示,每行都有其对应的总收入。

6.2 示例 2:按客户统计交易次数

统计每位客户完成了多少笔交易。

SELECT
    customer_id,
    COUNT(transaction_id) AS 交易次数
FROM
    transactions
GROUP BY
    customer_id;

解析:

  • COUNT(transaction_id) 计算独立交易的笔数。
  • GROUP BY customer_id 确保计数是针对每个唯一的 customer_id 进行的。

6.3 示例 3:每种产品的平均售出数量

确定所有交易中每种产品的平均售出数量。

SELECT
    product_id,
    AVG(quantity) AS 平均售出数量
FROM
    transactions
GROUP BY
    product_id;

解析:

  • AVG(quantity) 计算平均数量。
  • GROUP BY product_id 计算每个独特 product_id 的平均值。

6.4 示例 4:各门店每日销售量

计算特定销售日期下每个门店售出产品的总数量。

SELECT
    sale_date,
    store_location,
    SUM(quantity) AS 每日售出总量
FROM
    transactions
GROUP BY
    sale_date,
    store_location
ORDER BY
    sale_date,
    store_location;

解析:

  • 该查询在 GROUP BY 子句中使用了多列 (sale_date, store_location)。
  • 它将为日期和门店位置的每一个组合创建一个唯一的组。
  • SUM(quantity) 然后对这些特定的“每日门店”组的数量进行汇总。
  • 添加 ORDER BY 是为了使结果更易读,首先按日期排序,然后按位置排序。