做数据开发、ETL、数据分析的朋友,肯定绕不开「行列转换」—— 把行数据转成列,或列数据转成行。
今天一次性讲透 4 种常用方法,附完整代码,新手也能直接抄。
先定义一个测试表(大家先建表,方便跟着试):
-- 新建业务表:销售明细表
CREATE TABLE SALES
(
SNAME VARCHAR2(50),
SDATE DATE,
PRODUCT VARCHAR2(200),
AMOUNT NUMBER(12,2),

AREA VARCHAR2(100)
)
-- 注释表与字段名称
COMMENT ON TABLE SALES IS '销售明细表';
COMMENT ON COLUMN SALES.SNAME IS '姓名';
COMMENT ON COLUMN SALES.SDATE IS '销售日期';
COMMENT ON COLUMN SALES.PRODUCT IS '销售产品';
COMMENT ON COLUMN SALES.AMOUNT IS '销售金额';
COMMENT ON COLUMN SALES.AREA IS '销售区域';
-- 插入测试数据
INSERT INTO SALES (SNAME, SDATE, PRODUCT, AMOUNT, AREA) VALUES ('张三', TO_DATE('2026-01-01', 'YYYY-MM-DD'), '手机', 1299.59, '华北');
INSERT INTO SALES (SNAME, SDATE, PRODUCT, AMOUNT, AREA) VALUES ('李四', TO_DATE('2026-01-02', 'YYYY-MM-DD'), '电脑', 2792.36, '华东');
INSERT INTO SALES (SNAME, SDATE, PRODUCT, AMOUNT, AREA) VALUES ('王五', TO_DATE('2026-01-03', 'YYYY-MM-DD'), '平板', 3020.00, '华南');
INSERT INTO SALES (SNAME, SDATE, PRODUCT, AMOUNT, AREA) VALUES ('张三', TO_DATE('2026-01-06', 'YYYY-MM-DD'), '手机', 2679.50, '华北');
INSERT INTO SALES (SNAME, SDATE, PRODUCT, AMOUNT, AREA) VALUES ('李四', TO_DATE('2026-01-12', 'YYYY-MM-DD'), '电脑', 3629.02, '华东');
INSERT INTO SALES (SNAME, SDATE, PRODUCT, AMOUNT, AREA) VALUES ('王五', TO_DATE('2026-01-18', 'YYYY-MM-DD'), '平板', 2789.63, '华南');
COMMIT;
INSERT INTO SALES (SNAME, SDATE, PRODUCT, AMOUNT, AREA) VALUES ('张三', TO_DATE('2026-02-16', 'YYYY-MM-DD'), '手机', 2027.90, '华北');
INSERT INTO SALES (SNAME, SDATE, PRODUCT, AMOUNT, AREA) VALUES ('李四', TO_DATE('2026-02-18', 'YYYY-MM-DD'), '电脑', 1169.12, '华东');
INSERT INTO SALES (SNAME, SDATE, PRODUCT, AMOUNT, AREA) VALUES ('王五', TO_DATE('2026-02-22', 'YYYY-MM-DD'), '平板', 1269.03, '华南');
INSERT INTO SALES (SNAME, SDATE, PRODUCT, AMOUNT, AREA) VALUES ('张三', TO_DATE('2026-02-20', 'YYYY-MM-DD'), '手机', 2873.32, '华北');
INSERT INTO SALES (SNAME, SDATE, PRODUCT, AMOUNT, AREA) VALUES ('李四', TO_DATE('2026-02-22', 'YYYY-MM-DD'), '电脑', 3269.07, '华东');
INSERT INTO SALES (SNAME, SDATE, PRODUCT, AMOUNT, AREA) VALUES ('王五', TO_DATE('2026-02-23', 'YYYY-MM-DD'), '平板', 3369.03, '华南');
COMMIT;
一、行转列(最常用)
方法 1:CASE WHEN(通用所有数据库,新手首选)
-- 行转列:按员工分组,展示各月销售额
SELECT
SNAME AS "员工姓名",
SUM(CASE WHEN TO_CHAR(SDATE,'YYYYMM')='202601' THEN AMOUNT ELSE 0 END) AS "2026年1月",
SUM(CASE WHEN TO_CHAR(SDATE,'YYYYMM')='202602' THEN AMOUNT ELSE 0 END) AS "2026年2月"
FROM SALES
GROUP BY SNAME;
优点:兼容 MySQL、Oracle、SQL Server,无版本限制
缺点:列固定,新增月份需扩展代码
方法 2:PIVOT(SQL Server/Oracle 专用,简洁)
-- Oracle 行转列
WITH SALES_BASE AS
(
SELECT SNAME
,TO_CHAR(SDATE,'YYYYMM') AS SMONTH
,AMOUNT
FROM SALES
)
SELECT SNAME AS "员工姓名"
,"2026年1月"
,"2026年2月"
FROM SALES_BASE
PIVOT (
SUM(AMOUNT)
FOR SMONTH IN (
'202601' AS "2026年1月",
'202602' AS "2026年2月"
)
);
优点:代码简洁,适合固定列转换
缺点:仅支持部分数据库
二、列转行
方法 3:UNION ALL(通用所有数据库)
列转回行(以上面方法1行转列结果为基表):
WITH SALES_BASE AS
(
SELECT
SNAME AS "员工姓名",
SUM(CASE WHEN TO_CHAR(SDATE,'YYYYMM')='202601' THEN AMOUNT ELSE 0 END) AS "2026年1月销售额",
SUM(CASE WHEN TO_CHAR(SDATE,'YYYYMM')='202602' THEN AMOUNT ELSE 0 END) AS "2026年2月销售额"
FROM SALES
GROUP BY SNAME
)
-- 列转行
SELECT 员工姓名
,'2026年1月' AS 月份
,"2026年1月销售额" AS 销售额
FROM SALES_BASE
UNION ALL
SELECT 员工姓名
,'2026年2月' AS 月份
,"2026年2月销售额" AS 销售额
FROM SALES_BASE;
优点:通用、逻辑简单,新手易理解
缺点:扩展时代码也多
方法 4:UNPIVOT(SQL Server/Oracle 专用)
列转回行(以上面方法2行转列结果为中间表):
WITH SALES_BASE AS
(
SELECT SNAME
,TO_CHAR(SDATE,'YYYYMM') AS SMONTH
,AMOUNT
FROM SALES
)
,SALES_PIVOT AS
(
SELECT SNAME AS "员工姓名"
,"2026年1月销售额"
,"2026年2月销售额"
FROM SALES_BASE
PIVOT (
SUM(AMOUNT)
FOR SMONTH IN (
'202601' AS "2026年1月销售额",
'202602' AS "2026年2月销售额"
)
)
)
SELECT 员工姓名
,SMONTH AS "年月"
,AMOUNT AS "销售额"
FROM SALES_PIVOT
UNPIVOT (
AMOUNT FOR SMONTH IN (
"2026年1月销售额" AS '202601'
,"2026年2月销售额" AS '202602'
)
)
优点:代码短,适合批量列转行
缺点:仅支持部分数据库
总结:
新手优先用 CASE WHEN / UNION ALL:通用所有数据库,不会踩版本坑;
SQL Server/Oracle 可用 PIVOT/ UNPIVOT:简化代码,效率更高;
行转列关键:GROUP BY 分组 + 条件判断;列转行关键:UNION ALL 合并结果。