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
相关推荐
若阳安好11 分钟前
【提效小工具】IN SQL、UPDATE SQL、INSERT SQL
java·数据库·sql
二月十六13 分钟前
SQL Server 2022 新函数:DATETRUNC 日期截断详解
数据库·sqlserver·datetrunc
qq_380619161 小时前
SQL中如何实现特定范围内数据的批量删除_范围分区与分区删除
jvm·数据库·python
qq_380619161 小时前
HTML函数开发需要独立显卡吗_HTML函数与显卡关系详解【说明】
jvm·数据库·python
2201_756847331 小时前
Golang如何处理JSON空值null_Golang JSON空值处理教程【精通】
jvm·数据库·python
我登哥MVP1 小时前
【Spring6笔记】 - 11 - JDBCTemplate
java·数据库·spring boot·mysql·spring
hef2881 小时前
怎么诊断MongoDB Config Server响应极慢的问题_高频Auto-split导致的元库写入压力
jvm·数据库·python
qq_380619162 小时前
html怎么用deno运行_Deno如何作为本地服务器运行HTML文件
jvm·数据库·python
ruan1145142 小时前
Redis--个人学习记录
数据库·redis·学习