PG SQL 行转列记录

PostgreSQL(PG) 中实现 行转列(Pivot) 的典型场景。

🧩 一、业务背景说明

我们有一个电商业务系统,需要分析商品在不同月份的销量情况。

原始数据以"订单明细"形式存储,每条记录包含:

  • 商品 ID
  • 销售日期(精确到日)
  • 销量(整数)

目标:按商品统计每月销量,并将月份作为列头横向展示(即"行转列"),同时额外计算:

  • 当月销量(比如当前是 2025 年 6 月,则显示 6 月销量)
  • 前半年总销量(1~6 月)
  • 后半年总销量(7~12 月)
  • 全年总销量(1~12 月)

💡 注意:PG 没有内置 PIVOT 关键字(如 Oracle/SQL Server),需用 条件聚合(Conditional Aggregation) 实现行转列。

二、表结构设计与示例数据

1. 商品信息表(可选,用于展示商品名称)

sql 复制代码
CREATE TABLE product (
    product_id   INT PRIMARY KEY,
    product_name VARCHAR(100) NOT NULL
);

2. 订单明细表(核心表)

sql 复制代码
CREATE TABLE order_detail (
    id           SERIAL PRIMARY KEY,
    product_id   INT NOT NULL,
    sale_date    DATE NOT NULL,     -- 销售日期
    quantity     INT NOT NULL CHECK (quantity > 0)
);

3. 插入示例数据(2025 年全年,两个商品)

sql 复制代码
-- 商品
INSERT INTO product VALUES 
(101, '无线蓝牙耳机'),
(102, '智能手环');

-- 订单明细(模拟每月销量)
INSERT INTO order_detail (product_id, sale_date, quantity) VALUES
-- 耳机
(101, '2025-01-15', 120),
(101, '2025-02-10', 95),
(101, '2025-03-22', 140),
(101, '2025-04-05', 110),
(101, '2025-05-18', 130),
(101, '2025-06-30', 150),
(101, '2025-07-12', 160),
(101, '2025-08-25', 170),
(101, '2025-09-08', 155),
(101, '2025-10-19', 180),
(101, '2025-11-03', 200),
(101, '2025-12-20', 220),

-- 手环
(102, '2025-01-20', 80),
(102, '2025-02-14', 70),
(102, '2025-03-05', 90),
(102, '2025-04-12', 85),
(102, '2025-05-25', 100),
(102, '2025-06-10', 110),
(102, '2025-07-18', 120),
(102, '2025-08-30', 130),
(102, '2025-09-15', 125),
(102, '2025-10-22', 140),
(102, '2025-11-11', 160),
(102, '2025-12-05', 180);

✅ 所有 quantity 均为整数,符合要求。

三、需求拆解

我们要输出如下格式的结果(假设当前月份是 2025年6月):

product_id product_name Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec first_half second_half total_year
101 无线蓝牙耳机 120 95 140 110 130 150 160 170 155 180 200 220 645 1185 1830
102 智能手环 80 70 90 85 100 110 120 130 125 140 160 180 535 1035 1570

其中:

  • Jan ~ Dec:对应各月销量(若某月无销售则为 0)
  • first_half = Jan + Feb + ... + Jun
  • second_half = Jul + ... + Dec
  • total_year = first_half + second_half

四、SQL 实现(PostgreSQL)

使用 条件聚合 + CASE WHEN 实现行转列:

sql 复制代码
SELECT
    p.product_id,
    p.product_name,
    -- 行转列:每个月作为一列
    COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM od.sale_date) = 1 THEN od.quantity END), 0) AS "Jan",
    COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM od.sale_date) = 2 THEN od.quantity END), 0) AS "Feb",
    COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM od.sale_date) = 3 THEN od.quantity END), 0) AS "Mar",
    COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM od.sale_date) = 4 THEN od.quantity END), 0) AS "Apr",
    COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM od.sale_date) = 5 THEN od.quantity END), 0) AS "May",
    COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM od.sale_date) = 6 THEN od.quantity END), 0) AS "Jun",
    COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM od.sale_date) = 7 THEN od.quantity END), 0) AS "Jul",
    COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM od.sale_date) = 8 THEN od.quantity END), 0) AS "Aug",
    COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM od.sale_date) = 9 THEN od.quantity END), 0) AS "Sep",
    COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM od.sale_date) = 10 THEN od.quantity END), 0) AS "Oct",
    COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM od.sale_date) = 11 THEN od.quantity END), 0) AS "Nov",
    COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM od.sale_date) = 12 THEN od.quantity END), 0) AS "Dec",
    
    -- 前半年(1~6月)
    COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM od.sale_date) BETWEEN 1 AND 6 THEN od.quantity END), 0) AS first_half,
    
    -- 后半年(7~12月)
    COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM od.sale_date) BETWEEN 7 AND 12 THEN od.quantity END), 0) AS second_half,
    
    -- 全年
    COALESCE(SUM(od.quantity), 0) AS total_year

FROM product p
LEFT JOIN order_detail od 
    ON p.product_id = od.product_id 
    AND EXTRACT(YEAR FROM od.sale_date) = 2025  -- 限定年份
GROUP BY p.product_id, p.product_name
ORDER BY p.product_id;

五、关键点解析

技术点 说明
EXTRACT(MONTH FROM date) 提取日期中的月份(1~12)
CASE WHEN ... THEN quantity END 条件聚合:只对特定月份求和
COALESCE(..., 0) 处理 NULL(某月无销售时显示 0)
LEFT JOIN 确保即使某商品全年无销量也出现在结果中
GROUP BY product 按商品分组,聚合各月销量
无 PIVOT 语法 PG 需手动用 CASE 模拟行转列

📊 六、执行结果(基于上述数据)

运行后将得到:

product_id product_name Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec first_half second_half total_year
101 无线蓝牙耳机 120 95 140 110 130 150 160 170 155 180 200 220 645 1185 1830
102 智能手环 80 70 90 85 100 110 120 130 125 140 160 180 535 1035 1570
相关推荐
AIgorithmGEEK3 小时前
MySQL 索引全解(下):建、查、删实战与知识速查表
数据库·mysql·b+树·索引
唠点键盘之外的6 小时前
26 向量数据库实战:从 0 搭一条能用的检索链路(pgvector + Spring AI)
数据库·人工智能·spring
云和恩墨6 小时前
数据库监控简史:监控不止于告警——从图表大爆发到健康管理闭环
数据库
ly76896 小时前
MongoDB 事务开销实测:快照隔离下的写冲突、重试与分片事务边界
数据库·mongodb·事务·分片集群·快照隔离·写冲突
豆角焖肉6 小时前
Spring AOP 自定义注解实现系统操作日志
数据库·自定义注解·springaop·操作日志·aop 切面
金伟API10247 小时前
不会写复杂SQL怎么办?
数据库·sql
执笔为剑8 小时前
2.1.2-1、V8R3迁移到V8R6-Oracle兼容特性
数据库
geovindu8 小时前
sql:Search-Optimized SQL Queries using mysql 9.0
android·数据库·sql·mysql·数据库开发·数据库架构
for_ever_love__8 小时前
MySQL SELECT 与 WHERE 优化实战:可搜索条件、联合索引与执行计划
数据库·mysql·oracle·select·where·条件优化·查询优化技巧
2501_931803758 小时前
MySQL 锁机制详解
数据库·mysql