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
相关推荐
一米阳光86613 小时前
软考(中级)软件设计师核心笔记(3)数据库系统——概念、数据库设计
数据库·笔记·职场发展·软考·软件设计师
奈斯先生Vector4 小时前
告别工具碎片化:基于 Nano Banana 全模态 AI 聚合架构搭建“文本-图像-视频”自动化协同生产线
运维·数据库·人工智能·架构·自动化·aigc·音视频
建筑工程企业管理系统5 小时前
erp工程项目管理系统落地价值:实现工程多项目成本精细化核算与管控
大数据·数据库·人工智能
AI大模型-小华6 小时前
Codex 三方充值快速入门指南
java·前端·数据库·chatgpt·ai编程·codex·chatgpt pro
foolishlee6 小时前
Neon wal日志处理流程2
数据库
2601_965798477 小时前
Is Hygia Good for Maid & Janitorial Sites? Technical Audit
服务器·网络·数据库
Cloud云卷云舒7 小时前
海山数据库(HaishanDB)面向工业云场景技术方案
数据库·haishandb·工业云·移动云海山数据库·天工云
逃逸线LOF8 小时前
Spring配置数据源{连接池}(Druid、c3p0)
java·数据库·spring
严同学正在努力10 小时前
从备份到恢复:我用 30 分钟恢复了误删的核心业务表
android·java·数据库·ai
xqqxqxxq10 小时前
DQL 数据查询 select
sql