將「寬表」 (Wide Format) 轉為「長表」 (Long Format) 是資料整理、ETL、BI 建模常見需求。標準 SQL 使用 UNION ALL 逐欄位拆解再合併,跨資料庫通用、效能穩定。
CREATE TABLE score_wide (
name VARCHAR(20),
yw INT, -- 語文
sx INT -- 數學
);
INSERT INTO score_wide VALUES
('小明', 85, 92),
('小紅', 78, 88),
('小剛', 90, 95);
寬表結構:
+------+------+------+ | name | yw | sx | +------+------+------+ | 小明 | 85 | 92 | | 小紅 | 78 | 88 | | 小剛 | 90 | 95 | +------+------+------+
+------+---------+-------+ | name | subject | score | +------+---------+-------+ | 小明 | yw | 85 | | 小明 | sx | 92 | | 小紅 | yw | 78 | | 小紅 | sx | 88 | | 小剛 | yw | 90 | | 小剛 | sx | 95 | +------+---------+-------+
SELECT name, 'yw' AS subject, yw AS score FROM score_wide UNION ALL SELECT name, 'sx' AS subject, sx AS score FROM score_wide ORDER BY name, subject;

# Python 範例
columns = ['yw', 'sx', 'en', 'ph'] # 從資料字典或 INFORMATION_SCHEMA 取得
selects = [f"SELECT name, '{c}' AS subject, {c} AS score FROM score_wide" for c in columns]
sql = " UNION ALL ".join(selects) + " ORDER BY name, subject"
# UNION ALL 會保留 NULL,若需過濾
SELECT name, subject, score FROM (
SELECT name, 'yw' AS subject, yw AS score FROM score_wide
UNION ALL
SELECT name, 'sx' AS subject, sx AS score FROM score_wide
) t
WHERE score IS NOT NULL;
SELECT name, subject, score, grade FROM (
SELECT name, 'yw' AS subject, yw AS score, yw_grade AS grade FROM score_wide
UNION ALL
SELECT name, 'sx' AS subject, sx AS score, sx_grade AS grade FROM score_wide
) t;
| 資料庫 | 語法 | 範例 |
|---|---|---|
| SQL Server | UNPIVOT | SELECT name, subject, score FROM score_wide UNPIVOT (score FOR subject IN (yw, sx)) u |
| Oracle 11g+ | UNPIVOT | SELECT * FROM score_wide UNPIVOT (score FOR subject IN (yw, sx)) |
| PostgreSQL | 無原生 | 使用 UNION ALL 或 jsonb_each_text |
| MySQL | 無原生 | 使用 UNION ALL (本文方法) |
| Spark SQL | stack | df.selectExpr("name", "stack(2, 'yw', yw, 'sx', sx) as (subject, score)") |
# UNION: 需排序去重,複雜度 O(N log N) # UNION ALL: 直接串接,複雜度 O(N),無去重開銷 # 列轉行場景源資料無重複,必須用 UNION ALL
| 錯誤 | 後果 | 修正 |
|---|---|---|
| 使用 UNION 而非 UNION ALL | 效能差、若不同學生同分數會被誤去重 | 改用 UNION ALL |
| 欄位順序/型別不一致 | 報錯或隱含轉換錯誤 | 各 SELECT 欄位數、順序、型別完全對齊 |
| 忘記常數欄位 (subject) | 無法識別來源欄位 | 每 SELECT 必須帶上 'yw' AS subject |
INSERT INTO target SELECT ... UNION ALL ... 直寫目標表。UNION ALL 列轉行:簡單、標準、高效,是 SQL 開發者工具箱必備技能。