2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

SQLで複数行のデータを1行にまとめて列として表示する方法

Posted at

はじめに

本記事は、G's ACADEMY Advent Calendar 2025 に寄せて投稿した記事です。

メリクリ!🎄
元メガネ屋さんのひよっこエンジニアです。

G's Advent Calendar ということで何か面白い記事を書こうと思っていたのですが……
結局ネタが思いつかなかったので、今年仕事で「なるほど!」となったSQLの書き方を1つアウトプットします。

今回のテーマは、「SQLで、複数行のデータを1行にまとめて、列として表示する方法」です。
先輩方からすると「そんなことも知らんのか」と思われるかもしれませんが……

「しょーがねーだろ、赤ちゃんなんだから」(CV. ポプ子)

ということで、ツラツラ書き殴っていきたいと思います。

この記事に書いてあること

  • 1人につき複数行あるデータを、1行にまとめる方法
  • CASE文と集約関数(MAX)をなぜ組み合わせるのか

例題

従業員の緊急連絡先を管理するテーブルがあります。
このテーブルには、本人用の電話番号と家族用の電話番号が、それぞれ別の行として登録されています。
従業員ごとにデータを集約し、本人の電話番号と家族の電話番号を横に並べて1行で表示するSQLを作成してください。

※補足条件

  • 本人用・家族用の連絡先は、それぞれ最大1件まで
  • 該当する連絡先が存在しない場合は「設定なし」と表示する
<!--テーブル-->

EMP_ID | CONTACT_TYPE | TEL
-------+--------------+---------------
E001   | 01           | 090-1111-1111
E001   | 02           | 080-2222-2222
E002   | 01           | 090-3333-3333
E003   | 02           | 070-4444-4444

//テーブル名:EMP_EMERGENCY_CONTACT
//EMP_ID:従業員ID
//CONTACT_TYPE:緊急連絡先のタイプ(01:本人連絡先,02:家族連絡先)
//TEL:電話番号
<!--表示させたい結果-->

従業員ID | 本人電話        | 家族電話
---------+----------------+----------------
E001     | 090-1111-1111 | 080-2222-2222
E002     | 090-3333-3333 | 設定なし
E003     | 設定なし       | 070-4444-4444

会社のベテランエンジニアの先輩に「こういうケースはよくあるから、スラーっと書けるようになってね」と言われたのですが、ひよっこの私は正直「???」な状態でした...( ;∀;)

SQLの書き方

まずは完成形として、下記のように書くことができます。

SELECT
  EMP_ID AS '従業員ID',
  COALESCE(
    MAX(CASE WHEN CONTACT_TYPE = 01 THEN TEL END),
    '設定なし'
  ) AS '本人電話',
  COALESCE(
    MAX(CASE WHEN CONTACT_TYPE = 02 THEN TEL END),
    '設定なし'
  ) AS '家族電話'
FROM EMP_EMERGENCY_CONTACT
GROUP BY EMP_ID;

ここから順を追って、整理していきましょう。

STEP1:まずは「従業員ごと」にまとめる

今回の目的は「従業員ごとにまとめて、1行で表示する」 ことです。
そのためベースになるSQLは、SELECTとGROUPBYを用いた次の文になります。

SELECT
  EMP_ID
FROM EMP_EMERGENCY_CONTACT
GROUP BY EMP_ID;

これで、従業員IDごとに1行で表示することができます。

STEP2:列として表示したい項目を考える

最終的に表示したい列は、「従業員ID」「本人の電話番号」「家族の電話番号」の3つなので、下記のようなSQL文を考えることができます。

SELECT
  EMP_ID,
  本人連絡先,
  家族連絡先
FROM EMP_EMERGENCY_CONTACT
GROUP BY EMP_ID;

あとは、「本人電話」「家族電話」をどうやって取り出すか?を考えればOKです。

STEP3:CASE文で「どの電話番号か」を振り分ける

CONTACT_TYPE の値によって、「01 → 本人の電話番号」「02 → 家族の電話番号」を取り出したいので、CASE文を使います。

<!--CONTACT_TYPE = 01 のとき → 本人の電話番号を取り出したい-->
CASE WHEN CONTACT_TYPE = 01 THEN TEL END

このCASE文は、条件に一致する場合は電話番号を返し、一致しない場合はNULLを返すという動きをします。
CASE文だけを評価するのであれば、下記のようなデータ取得が行われます。

CASE WHEN CONTACT_TYPE = 01 THEN TEL END
<!--
[結果]
EMP_ID | CONTACT_TYPE | TEL
-------+--------------+---------------
E001   | 01           | 090-1111-1111
E001   | 02           | NULL
E002   | 01           | 090-3333-3333
E003   | 02           | NULL
-->

STWP4:集約関数MAXを用いて、適切な値を返す

ここで注意したいのが、GROUPBYを用いているということです。
GROUPBY を使う場合、SELECT句には 集約関数(MAX / MIN など) を使う必要があります。
CASE文の結果は、下記のように複数行の値を返すため、そのままではSELECTすることができません。

CASE WHEN CONTACT_TYPE = 01 THEN TEL END

<!--EMP_ID=01にグループ化した場合の、本人電話列の取得結果-->
EMP_ID | CONTACT_TYPE | TEL
-------+--------------+---------------
E001   | 01           | 090-1111-1111
E001   | 02           | NULL

そこで MAX関数を使います。
今回は「最大1件まで」という前提があるため、MAX関数を用いることで、取得結果の「NULL」「電話番号」を比較し、最大値として電話番号の値を返すことになります。

 MAX(CASE WHEN CONTACT_TYPE = 01 THEN TEL END)
 
<!--EMP_ID=01にグループ化した場合の、本人電話列の取得結果-->
EMP_ID | CONTACT_TYPE | TEL
-------+--------------+---------------
E001   | 01           | 090-1111-1111

つまり、MAX関数とCASE文を用いることで、下記のように従業員ごとにデータを集約し、本人の電話番号と家族の電話番号を横並びで1行に表示することができます。

SELECT
  EMP_ID AS '従業員ID',

  MAX(CASE WHEN CONTACT_TYPE = 01 THEN TEL END) AS '本人電話',

  MAX(CASE WHEN CONTACT_TYPE = 02 THEN TEL END) AS '家族電話'

FROM EMP_EMERGENCY_CONTACT
GROUP BY EMP_ID;

<!--実行結果
従業員ID | 本人電話        | 家族電話
---------+----------------+----------------
E001     | 090-1111-1111 | 080-2222-2222
E002     | 090-3333-3333 | NULL
E003     | NULL          | 070-4444-4444
-->

STEP5:COALESCEでNULLを「設定なし」に置き換える

ここまでくれば、あとはシンプルで、COALESCEを用いてNULLを「設定なし」に置換してあげればよいということになります。

結果完成した、最終的なSQLを再掲しておきます。

SELECT
  EMP_ID AS '従業員ID',

  COALESCE(
    MAX(CASE WHEN CONTACT_TYPE = 01 THEN TEL END),
    '設定なし'
  ) AS '本人電話',

  COALESCE(
    MAX(CASE WHEN CONTACT_TYPE = 02 THEN TEL END),
    '設定なし'
  ) AS '家族電話'
  
FROM EMP_EMERGENCY_CONTACT
GROUP BY EMP_ID;

最後に...ひとりごと

日々勉強して、精進しないといけないなぁと思う今日この頃です...( ;∀;)

ただやっぱり新しいことを知ったり、コードを書くことは楽しい!!
G'sでもらった種火に、しっかり薪をくべて大きな炎にしていきたいなと思います。

最後に来年の抱負として、LAB17のBuzzワードを掲げたいと思います。
「燃えろ、俺の小宇宙!!」

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?