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
相关推荐
智购科技智能售货柜5 小时前
2026自动售货机整机可靠性测试:从高低温交变到EMC电磁兼容的认证工程实践~YH
运维·服务器·数据库·人工智能·物联网
ltl6 小时前
RocksDB 架构演进:相对 LevelDB 的 diff 地图
数据库
ltl6 小时前
存储读取性能优化:索引、Bloom Filter 与读放大
数据库
xier_ran9 小时前
【infra之路】GPU 存储层次总结:L1 / L2 / HBM
java·网络·数据库
山甫aa10 小时前
Javaweb---MyBatis增删改查
java·数据库·后端·mybatis·springboot
其实防守也摸鱼11 小时前
接口审计:从原理到实践的万字深度指南
数据库·安全·自动化·github·copilot
@insist12311 小时前
系统集成项目管理工程师-信息和文档管理
网络·数据库·软考·系统集成项目管理工程师·软考中项·软件水平考试
残 风13 小时前
PostgreSQL 存储机制详解
数据库·postgresql·开源·数据库开发
JacksonMx14 小时前
Java CompletableFuture 异步编程实战:从入门到架构师避坑指南
linux·数据库·python