IPA|情報処理技術者試験

SQLのPIVOT/UNPIVOTを徹底解説!CASE式での書き換えから応用情報・DB試験対策まで

SQLを学習していると、試験問題や実務で「行と列を入れ替えて集計したい」という場面に遭遇します。例えば、月別・製品別に縦に並んだ売上データを、製品を行に、月を列にしたクロス集計表(マトリクス表)に変換したいケースです。このような「縦持ち」のデータを「横持ち」に、あるいはその逆の変換を実現するのがPIVOTUNPIVOTです。

これらの操作は、データベーススペシャリスト試験や応用情報技術者試験の午後問題でも頻出のテーマであり、得点源になる重要なスキルです。しかし、その独特な構文から苦手意識を持つ受験者も少なくありません。

この記事では、PIVOTとUNPIVOTの基本的な考え方から、具体的なSQL構文、そしてCASE式を用いた書き換え方法までを、豊富な図解とサンプルコードを使って体系的に解説します。単なる構文の暗記ではなく、「なぜそうなるのか」という論理的なつながりを理解し、実務でも応用できる知識を身につけていきましょう。

PIVOTとは?データベースの縦持ちデータを横持ちに変換するSQLの強力な武器

データベーススペシャリスト試験や応用情報技術者試験のSQL問題では、与えられたテーブル(表)を加工し、問題の要求に合った形式で結果を出力する必要があります。その際、頻繁に登場するのが「縦持ちデータを横持ちデータに変換する」という操作です。これを実現する強力な機能が PIVOT です。

そもそも「縦持ち」「横持ち」とは?

まず、データの持ち方である「縦持ち」と「横持ち」の違いを理解しましょう。身近な「家計簿」のデータを例に考えてみます。

【縦持ちデータ】

正規化されたデータベースでよく見られる形式です。1つの行に1つの事実(いつ、何に、いくら使ったか)が記録されています。データ追加が容易というメリットがあります。

費目 金額
4月 食費 50000
4月 交通費 10000
5月 食費 52000
5月 交通費 11000

【横持ちデータ】

Excelの集計表やレポートでよく見かける形式です。月ごとに行をまとめ、費目を列に展開しています。人間にとってはこちらの方が見やすく、月ごとの比較などがしやすいというメリットがあります。

食費 交通費
4月 50000 10000
5月 52000 11000

PIVOT は、この「縦持ち」の表から「横持ち」の表を生成するためのSQL操作です。

PIVOTの仕組みを図解でイメージする

PIVOTは、以下の3つの要素を指定することで機能します。

  1. 集約キー (残す行): グループ化の基準となる列(例:月)。
  2. ピボット列 (列に変換する値が含まれる列): 新しい列名のもとになる値が入っている列(例:費目)。
  3. 集計対象 (セルに入る値): 新しく作られたセルの値となる列(例:金額)と、それをどうまとめるか(例:合計 SUM)。

これを図でイメージすると、以下のようになります。

【縦持ちデータ】
+------+--------+--------+
| 月   | 費目   | 金額   |  ← ②ピボット列の値(食費、交通費)が...
+------+--------+--------+
| 4月  | 食費   | 50000  |
| 4月  | 交通費 | 10000  |
| 5月  | 食費   | 52000  |
| 5月  | 交通費 | 11000  |
+------+--------+--------+
   ↑      ↑        ↑
   ①     ②        ③
集約キー ピボット列 集計対象

       ↓ PIVOT実行

【横持ちデータ】
+------+--------+--------+
| 月   | 食費   | 交通費 |  ← 新しい列になる
+------+--------+--------+
| 4月  | 50000  | 10000  |
| 5月  | 52000  | 11000  |
+------+--------+--------+

このように、ピボット列(費目)の値が行から列に「回転」し、集計対象(金額)が対応するセルに配置されるのがPIVOTの基本動作です。

基本的なPIVOT構文とサンプルコード

それでは、実際のSQLコードを見てみましょう。(※これは多くのデータベース製品でサポートされている標準的な構文例です)


SELECT
    集約キー,
    [新しい列名1],
    [新しい列名2]
FROM
    (元のテーブル) AS SourceTable
PIVOT
(
    集計関数(集計対象の列)
    FOR ピボット列 IN ([新しい列名1], [新しい列名2])
) AS PivotTable;

先ほどの家計簿の例を、この構文に当てはめてみましょう。


-- サンプルデータ(WITH句で仮想テーブルを作成)
WITH Kakeibo AS (
  SELECT '4月' AS 月, '食費' AS 費目, 50000 AS 金額 UNION ALL
  SELECT '4月' AS 月, '交通費' AS 費目, 10000 AS 金額 UNION ALL
  SELECT '5月' AS 月, '食費' AS 費目, 52000 AS 金額 UNION ALL
  SELECT '5月' AS 月, '交通費' AS 費目, 11000 AS 金額
)
-- PIVOTクエリ
SELECT
    月,
    [食費],
    [交通費]
FROM
    Kakeibo
PIVOT
(
    SUM(金額)
    FOR 費目 IN ([食費], [交通費])
) AS PivotTable;

このSQLを実行すると、まさに先ほど見た「横持ちデータ」の表が結果として返されます。`SUM(金額)`で金額を合計し、`FOR 費目 IN ([食費], [交通費])`で、「費目」列の値が'食費'なら[食費]列に、'交通費'なら[交通費]列に、それぞれの金額を配置するように指示しています。

応用情報・DB試験対策の要!CASE式によるPIVOTの書き換え方法

`PIVOT` 演算子は非常に便利ですが、実はすべてのデータベース管理システム(DBMS)でサポートされているわけではありません。そのため、応用情報技術者試験やデータベーススペシャリスト試験では、特定の製品機能に依存しない、より汎用的なSQL知識が問われます。

そこで必須となるのが、`GROUP BY` 句と `CASE` 式を組み合わせたPIVOTの代替実装です。この方法は、あらゆるSQL環境で動作する普遍的なテクニックであり、試験対策の要と言えます。

なぜ `GROUP BY` と `CASE` 式で実現できるのか?

一見すると全く違う構文ですが、論理的に分解していくと両者が同じ処理をしていることが分かります。その思考プロセスを追ってみましょう。

  1. 最終的に欲しい表の「行」は何か? → 横持ちにした家計簿の例では「月」ごとのデータです。これは `GROUP BY 月` でグルーピングすれば実現できます。
  2. 最終的に欲しい表の「列」は何か? → 「食費」の列と「交通費」の列です。
  3. 「食費」列の値はどう作るか? → 元の縦持ちデータの「費目」列が '食費' の場合だけ「金額」を抽出し、それ以外は無視(または0)すれば作れます。これを実現するのが `CASE` 式です。
    • `CASE WHEN 費目 = '食費' THEN 金額 ELSE 0 END`
  4. グループ内で値をどうまとめるか? → `GROUP BY 月` でまとめられたグループ(4月のグループ、5月のグループ)内で、上記3.の `CASE` 式の結果を合計すれば、その月の食費の合計値が得られます。これが `SUM()` などの集計関数です。

このロジックをSQLに落とし込むと、PIVOTと同じ結果を得られるのです。

CASE式を使った書き換えサンプルコード

それでは、先ほどのPIVOTの例を `GROUP BY` と `CASE` 式で書き換えたコードを見てみましょう。


-- サンプルデータ(WITH句で仮想テーブルを作成)
WITH Kakeibo AS (
  SELECT '4月' AS 月, '食費' AS 費目, 50000 AS 金額 UNION ALL
  SELECT '4月' AS 月, '交通費' AS 費目, 10000 AS 金額 UNION ALL
  SELECT '5月' AS 月, '食費' AS 費目, 52000 AS 金額 UNION ALL
  SELECT '5月' AS 月, '交通費' AS 費目, 11000 AS 金額
)
-- GROUP BY と CASE式を使ったPIVOTクエリ
SELECT
    月,
    SUM(CASE WHEN 費目 = '食費' THEN 金額 ELSE 0 END) AS 食費,
    SUM(CASE WHEN 費目 = '交通費' THEN 金額 ELSE 0 END) AS 交通費
FROM
    Kakeibo
GROUP BY
    月
ORDER BY
    月;

このコードのポイントは、`SELECT` 句の中にあります。

  • `SUM(CASE WHEN 費目 = '食費' ...)`: 「費目」列が '食費' の行の「金額」だけを合計して、「食費」という名前の新しい列を作成しています。
  • `GROUP BY 月`: これにより、月ごとに集計処理が行われます。

このSQLを実行すると、`PIVOT` 演算子を使った場合と全く同じ結果が得られます。

CASE式版のメリットと試験での重要性

この書き方には、実務的にも試験対策としても大きなメリットがあります。

  • 高い汎用性・移植性: `PIVOT` をサポートしないDBMS(例: MySQL, PostgreSQLの古いバージョンなど)でも同じロジックで実装できます。
  • 柔軟な条件設定: `CASE` 式の中には複雑な条件(例: `金額が3000以上の場合のみ` など)を組み込むことができ、`PIVOT` よりも柔軟な集計が可能です。

特に情報処理技術者試験では、特定のベンダーに依存しない標準SQLの理解度が試されるため、この `GROUP BY + CASE` 式での実装方法は必ずマスターしておきましょう。

UNPIVOTとは?横持ちデータを縦持ちデータに戻すSQL逆変換

PIVOTが「縦から横へ」の変換であったのに対し、その逆、つまり「横持ち」のデータを「縦持ち」のデータに戻すのが UNPIVOT です。実務においては、分析のために一時的に横持ちにしたデータを、データベースの正規化された形式(縦持ち)に戻して格納し直す際などに利用されます。

PIVOTの操作を理解していれば、UNPIVOTはその逆のプロセスをイメージするだけなので、比較的スムーズに理解できるはずです。

横持ちから縦持ちへの変換イメージ

先ほどの家計簿の例で、PIVOTによって変換された「横持ちデータ」を元に戻すケースを考えてみましょう。

【変換前:横持ちデータ】

食費 交通費
4月 50000 10000
5月 52000 11000

【変換後:縦持ちデータ】

UNPIVOTを実行すると、PIVOTする前の元のデータ形式に戻ります。

費目 金額
4月 食費 50000
4月 交通費 10000
5月 食費 52000
5月 交通費 11000

UNPIVOTの仕組みを図解でイメージする

UNPIVOTでは、横に並んでいた列(食費、交通費)が、指定した2つの新しい列(値を入れる列と、カテゴリ名を入れる列)に分解されて縦に並び替えられます。

【横持ちデータ】
+------+--------+--------+  ← この列名(食費、交通費)が...
| 月   | 食費   | 交通費 |
+------+--------+--------+
| 4月  | 50000  | 10000  |
| 5月  | 52000  | 11000  |
+------+--------+--------+
         ↑        ↑
       これらの値が...

       ↓ UNPIVOT実行

【縦持ちデータ】
+------+--------+--------+
| 月   | 費目   | 金額   |  ← 新しい「費目」列に格納される
+------+--------+--------+
| 4月  | 食費   | 50000  |  ← 新しい「金額」列に格納される
| 4月  | 交通費 | 10000  |
| 5月  | 食費   | 52000  |
| 5月  | 交通費 | 11000  |
+------+--------+--------+

基本的なUNPIVOT構文とサンプルコード

UNPIVOTの基本的な構文は以下の通りです。


SELECT
    固定する列,
    カテゴリを格納する新しい列名,
    値を格納する新しい列名
FROM
    (元のテーブル) AS SourceTable
UNPIVOT
(
    値を格納する新しい列名
    FOR カテゴリを格納する新しい列名 IN ([元の列名1], [元の列名2])
) AS UnpivotTable;

この構文を使い、横持ちの家計簿データを縦持ちに戻すSQLを作成します。


-- サンプルデータ(WITH句で横持ちの仮想テーブルを作成)
WITH Kakeibo_Pivoted AS (
  SELECT '4月' AS 月, 50000 AS 食費, 10000 AS 交通費 UNION ALL
  SELECT '5月' AS 月, 52000 AS 食費, 11000 AS 交通費
)
-- UNPIVOTクエリ
SELECT
    月,
    費目,
    金額
FROM
    Kakeibo_Pivoted
UNPIVOT
(
    金額          -- 値を格納する新しい列
    FOR 費目 IN (食費, 交通費)  -- カテゴリを格納する新しい列と、その元となる列
) AS UnpivotTable;

このSQLの `UNPIVOT` 句では、「`食費`列と`交通費`列を対象に、その列名を`費目`という新しい列に、セルの値を`金額`という新しい列に格納しなさい」と指示しています。これにより、1行だったデータが2行に展開され、横持ちから縦持ちへの変換が実現します。

UNION ALLを使ったUNPIVOTの書き換え│DB試験で役立つ汎用SQLテクニック

`PIVOT` と同様に、`UNPIVOT` 演算子もすべてのDBMSでサポートされているわけではありません。そのため、応用情報やDBスペシャリストの試験では、より基本的で汎用的なSQLコマンドを組み合わせて、横持ちから縦持ちへの変換を行うスキルが求められます。

このUNPIVOTの代替実装で中心的な役割を果たすのが `UNION ALL` です。

なぜ `UNION ALL` で実現できるのか?

`UNION ALL` は、複数の `SELECT` 文の結果セットを縦に連結する機能です。この性質を利用して、横持ちデータの各列を個別の行として抽出し、それらを結合することで縦持ちデータを作成します。

思考プロセスは以下の通りです。

  1. 「食費」列を1つの行に変換する: 横持ちデータの1行目(4月)から、「月」「'食費'という文字列」「食費の金額」を `SELECT` 文で取得します。
  2. 「交通費」列を1つの行に変換する: 同様に、1行目(4月)から「月」「'交通費'という文字列」「交通費の金額」を `SELECT` 文で取得します。
  3. 上記の結果を連結: 1と2で作成した `SELECT` 文を `UNION ALL` で連結します。
  4. すべての行に対して繰り返す: この操作がテーブルのすべての行(4月の行、5月の行)に対して行われることで、結果的に完全な縦持ちデータが生成されます。

UNION ALLを使った書き換えサンプルコード

言葉で説明すると複雑に聞こえますが、実際のコードを見ると構造は非常にシンプルです。


-- サンプルデータ(WITH句で横持ちの仮想テーブルを作成)
WITH Kakeibo_Pivoted AS (
  SELECT '4月' AS 月, 50000 AS 食費, 10000 AS 交通費 UNION ALL
  SELECT '5月' AS 月, 52000 AS 食費, 11000 AS 交通費
)
-- UNION ALL を使ったUNPIVOTクエリ
SELECT 月, '食費' AS 費目, 食費 AS 金額 FROM Kakeibo_Pivoted
UNION ALL
SELECT 月, '交通費' AS 費目, 交通費 AS 金額 FROM Kakeibo_Pivoted
ORDER BY
    月, 費目;

このコードを分解してみましょう。

  • `SELECT 月, '食費' AS 費目, 食費 AS 金額 FROM Kakeibo_Pivoted`
    • `Kakeibo_Pivoted`テーブルから、「月」列、`'食費'`という固定文字列(これが新しい`費目`列になる)、そして「食費」列の値(これが新しい`金額`列になる)を取り出します。
  • `UNION ALL`
    • 上記のSELECT結果と、次に来るSELECT結果を縦に結合します。
  • `SELECT 月, '交通費' AS 費目, 交通費 AS 金額 FROM Kakeibo_Pivoted`
    • 今度は交通費の分を同じように取り出します。

この方法の利点は、SQLの基本的な命令(`SELECT`, `UNION ALL`)だけで構成されているため、ほぼすべてのリレーショナルデータベースで動作する点です。列の数だけ `SELECT` 文を書く必要がありコードは長くなりますが、非常に信頼性の高いテクニックとして覚えておきましょう。

【午後試験対策】PIVOTとUNPIVOTの使い分け│実践的な活用シナリオ

これまで、PIVOTとUNPIVOTの基本的な構文と、CASE式やUNION ALLを使った汎用的な書き換え方法を学んできました。最後に、これらの技術を「いつ」「なぜ」使うのか、具体的なシナリオを通じて理解を深め、応用情報やDBスペシャリストの午後試験で正しく使い分けるためのポイントを整理します。

PIVOT / UNPIVOT 使い分け早見表

まずは、両者の特徴と目的を比較表で簡潔にまとめます。

項目 PIVOT (または GROUP BY + CASE) UNPIVOT (または UNION ALL)
目的 データの集計と可視化、レポート作成 データの正規化、再利用しやすい形式への変換
変換方向 縦持ち → 横持ち(行を列に変換) 横持ち → 縦持ち(列を行に変換)
利用シーン 月別・支店別の売上集計表、ダッシュボード用のデータソース作成 Excelなどからインポートした非正規データの正規化、属性データを一括処理

実践的な活用シナリオ

シナリオ1:レポート作成のための「PIVOT」

状況:
あなたはECサイトのデータベース担当者です。DBには以下のような日別の製品カテゴリ売上テーブル(Sales)が存在します。

  • Sales テーブル(縦持ち): (販売日, カテゴリ, 売上金額)

要求:
マネージャーから「月別に各カテゴリの売上合計が一覧できるクロス集計レポートが欲しい」と依頼されました。Excelで加工しやすいように、行がカテゴリ、列が月(4月, 5月, 6月...)の形式で出力する必要があります。

解決策:
この「縦持ち」データをレポートに適した「横持ち」データに変換するため、PIVOT(または GROUP BY + CASE 式)を使用します。
カテゴリを行として残し(GROUP BY)、を新しい列に展開し、セルには売上金額の合計(SUM)を配置するSQLを実行します。これにより、マネージャーが求めるレポート形式のデータを直接DBから出力できます。

シナリオ2:データ正規化のための「UNPIVOT」

状況:
外部の調査会社から、製品アンケートの結果をExcelファイルで受け取りました。データは以下のような形式で、各質問への回答が個別の列になっています。

  • SurveyResults テーブル(横持ち): (回答者ID, Q1_評価, Q2_評価, Q3_評価)

要求:
このアンケート結果をデータベースに格納し、「各質問(Q1, Q2, Q3)の平均評価点」をSQLで算出できるようにしたい。

解決策:
現在の横持ち形式では、質問ごとの平均点を算出するSQLを書くのが非常に困難です(AVG(Q1_評価), AVG(Q2_評価)...と列の数だけ書く必要がある)。
そこで、まず UNPIVOT(または UNION ALL)を使い、データを正規化します。Q1_評価, Q2_評価... といった列を、「質問番号」と「評価点」という2つの新しい列に分解し、縦持ちのテーブル (回答者ID, 質問番号, 評価点) に変換します。
この縦持ち形式にすれば、GROUP BY 質問番号 を使って AVG(評価点) を計算するだけで、簡単に要求された分析が実現できます。

午後試験の問題文で「~のような形式の帳票を出力せよ」とあればPIVOTを、「列に属性が並んだテーブルを加工して~を求めよ」とあればUNPIVOTによる正規化を、それぞれ連想できるようにしておきましょう。

-IPA|情報処理技術者試験