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
相关推荐
怕孤单的草丛10 分钟前
缓存管理面临的主要问题
java·数据库·缓存
我会尽全力 乐观而坚强44 分钟前
MySQL零基础入门(二)
数据库·mysql·adb
kali-Myon2 小时前
某校园门禁系统高危 SQL 注入漏洞挖掘复盘
数据库·sql·安全·web安全
程序员在囧途3 小时前
likeadmin-api API 中转站怎么做统一报价?从 /pricing、/user/balance 到 task_id 回执的落地方法
运维·服务器·数据库·likeadmin-api·api中转站·token计费
龙石数据4 小时前
MySQL 全量同步到 Hive 怎么做?三步配置教程
数据库·hive·mysql·数据治理·数据中台
程序员在囧途4 小时前
likeadmin-api API 算力超市怎么做供应商切换?统一鉴权、task_id 和 callback_url 才能稳交付
java·服务器·数据库·开放api·likeadmin-api·api算力超市
数据工匠老o5 小时前
连接池配置实战——从“连不上“到“连太多“的完整排查指南
数据库
一只专注api接口开发的技术猿5 小时前
电商评论自动化监控与情感数据分析完整落地教程(附可直接运行 Python 代码)
大数据·数据库·python·数据分析·自动化
hh真是个慢性子5 小时前
GaussDB Inside 2.23.01.280 集中式一主一备安装
数据库·database·gaussdb·国产数据库·高斯
沉静的小伙5 小时前
Spring事务
java·数据库·spring