如何不使用UNION将SQL表中指定列转换为行?
问题描述
现有Orderdetails表结构如下:
----------------------------------------------------------------- | ID | ItemName | OldValue | newValue | OrderId | sequenceNo ----------------------------------------------------------------- | 1 | Item1 | 1 | 1.5 | SO2 | 6 | 2 | Item2 | 4 | 6 | SO2 | 4 | 3 | Item3 | 3 | 68 | SO2 | 9 ------------------------------------------------------------------
需要编写查询语句将OldValue列数据转为新行,输出结果如下:
ItemName | allValues |OrderId | sequenceNo ---------------------------------------------- Item1 | 1 | SO2 | 0 Item2 | 4 | SO2 | 0 Item3 | 3 | SO2 | 0 Item1 | 1.5 | SO2 | 6 Item2 | 6 | SO2 | 4 Item3 | 68 | SO2 | 9 -----------------------------------------------
目前已通过UNION实现需求,代码如下:
select itemName , oldValue as allValues , OrderId, 0 as sequenceNo from Orderdetails UNION select itemName , newValue as allValues , OrderId, sequenceNo from Orderdetails
请问是否存在不使用UNION的更优实现方式?
非UNION的实现方案
当然有,不同SQL方言有原生的列转行方案,这些方法大多只需扫描一次原表,比UNION(尤其是带去重的UNION)性能更优,代码逻辑也更紧凑。以下是主流数据库的实现方式:
1. SQL Server / Azure SQL(T-SQL环境)
使用CROSS APPLY VALUES展开多列到多行,是这类场景下性能最优的方案之一:
SELECT od.ItemName, v.allValues, od.OrderId, v.sequenceNo FROM Orderdetails od CROSS APPLY ( VALUES (od.OldValue, 0), (od.NewValue, od.sequenceNo) ) v(allValues, sequenceNo) ORDER BY v.sequenceNo, od.ItemName;
2. PostgreSQL / BigQuery(支持数组与UNNEST)
通过数组包装目标列,再用UNNEST展开,配合序号标记区分新旧值:
-- PostgreSQL 示例 SELECT od.ItemName, unnest_data.allValues, od.OrderId, CASE unnest_data.rn WHEN 1 THEN 0 ELSE od.sequenceNo END AS sequenceNo FROM Orderdetails od CROSS JOIN UNNEST(ARRAY[od.OldValue, od.NewValue]) WITH ORDINALITY AS unnest_data(allValues, rn) ORDER BY sequenceNo, od.ItemName; -- BigQuery 示例 SELECT od.ItemName, allValues, od.OrderId, IF(offset = 0, 0, od.sequenceNo) AS sequenceNo FROM Orderdetails od, UNNEST([od.OldValue, od.NewValue]) AS allValues WITH OFFSET ORDER BY sequenceNo, od.ItemName;
3. MySQL 8.0+
利用JSON_TABLE解析包含新旧值的JSON数组,实现行转列:
SELECT od.ItemName, jt.allValues, od.OrderId, CASE jt.rn WHEN 1 THEN 0 ELSE od.sequenceNo END AS sequenceNo FROM Orderdetails od JOIN JSON_TABLE( JSON_ARRAY(od.OldValue, od.NewValue), '$[*]' COLUMNS ( allValues DECIMAL(10,2) PATH '$', rn INT ORDINALITY ) ) jt ORDER BY sequenceNo, od.ItemName;
4. Oracle
使用CONNECT BY生成行号,再映射新旧值:
SELECT od.ItemName, CASE level WHEN 1 THEN od.OldValue ELSE od.NewValue END AS allValues, od.OrderId, CASE level WHEN 1 THEN 0 ELSE od.sequenceNo END AS sequenceNo FROM Orderdetails od CONNECT BY level <= 2 AND PRIOR od.ID = od.ID AND PRIOR sys_guid() IS NOT NULL -- 防止循环生成数据 ORDER BY sequenceNo, od.ItemName;
方案优势
- 仅扫描一次原表,避免了
UNION需要两次扫描+合并/去重的额外开销; - 转换逻辑集中在一处,代码可读性和维护性更强。
内容的提问来源于stack exchange,提问作者CrazyCoder
相关产品推荐
相关产品推荐

