SQL データ整形・変換 — CAST・COALESCE・CASEの基礎

基礎データ変換CAST / COALESCECASE WHEN文字列・日付・数値関数STRING_AGG / REGEXPPostgreSQL対応全5問
1 / 5 · 学習中 0 / 5 問完了
QUESTION 1

CAST / :: — テキスト型データを数値・日付型に変換する

CAST型変換クレンジングCSVインポート
前提知識

CAST(式 AS 型) は値を別のデータ型に変換する関数です。CSVインポートや外部連携で全列がTEXTとして入ってくるデータを正しい型に直す際に必須です。PostgreSQL では 式::型 という短縮記法も使えます。

CAST('42'         AS INTEGER)  -- '42'       → 42         (整数)
CAST('9.99'       AS NUMERIC)  -- '9.99'     → 9.99       (小数)
CAST('2024-05-01' AS DATE)     -- '2024-05-01'→ 日付型
'42'::INTEGER                   -- PostgreSQL 短縮記法 (同等)
なぜ型変換が必要か:TEXTのまま ORDER BY すると '10' < '2'(辞書順)になります。また '2024-05-01' + 7 のような日付計算もエラーになります。適切な型変換で算術・日付演算・正しいソートが可能になります。
問題

raw_imports テーブルの全列はTEXT型でインポートされています。id を INTEGER、amount を NUMERIC、registered_at を DATE に変換し、さらに税率10%の税込金額amount_with_tax)を付与してください。id 昇順で出力してください。

使用テーブル
▸ raw_imports (全列 TEXT型)
idamountregistered_at
'1''8000''2024-05-01'
'2''12500''2024-05-15'
'3''3200''2024-06-02'
期待出力
idamountregistered_atamount_with_tax
180002024-05-018800
2125002024-05-1513750
332002024-06-023520
模範解答コード
SELECT
  CAST(id            AS INTEGER) AS id,             -- TEXT '1','2','3' → 整数
  CAST(amount        AS NUMERIC) AS amount,         -- TEXT → 数値(算術可能に)
  CAST(registered_at AS DATE)    AS registered_at,  -- TEXT → 日付型
  CAST(amount        AS NUMERIC) * 1.1
                                  AS amount_with_tax  -- CAST後に算術演算
FROM   raw_imports
ORDER BY id;  -- 整数として昇順(TEXT なら '10'<'2' になる辞書順になる)

/*
  実行順序(SQLの論理的な評価順):
  1. FROM raw_imports            → 全3行を TEXT型のまま読み込む
  2. SELECT CAST(id AS INTEGER)  → '1','2','3'
  3. ORDER BY id                 → 整数として昇順ソート
  */
解説(テーブル変化・ポイント)
SELECT CAST(id AS INTEGER) AS id, CAST(amount AS NUMERIC) AS amount, CAST(registered_at AS DATE) AS registered_at, CAST(amount AS NUMERIC) * 1.1 AS amount_with_tax FROM raw_imports ORDER BY id;
LEGEND
データ取得・読込対象
① FROM — 元データ (全列 TEXT)
FROM raw_importsCSVインポートで取り込まれた全列がTEXT型です。このままでは数値計算も日付演算もできません。CAST で適切な型に変換します。
1 / 4
id (TEXT)amount (TEXT)registered_at (TEXT)
'1''8000''2024-05-01'
'2''12500''2024-05-15'
'3''3200''2024-06-02'
3行 (全列 TEXT)
SELECT id, amount FROM raw_imports ORDER BY amount;
LEGEND
データ取得・読込対象
① FROM
FROM raw_imports全列がTEXT型のデータです。このまま amount をソートするとどうなるでしょうか?
1 / 2
idamount (TEXT)
'1''8000'
'2''12500'
'3''3200'
3行
学習ポイント
CAST と :: の使い分け:CAST(col AS INTEGER) はSQL標準記法でどのDBMSでも動作します。col::INTEGER はPostgreSQL独自の短縮記法で可読性が高い反面、MySQL・SQL Serverでは使えません。チームのDBMS状況に合わせて選択しましょう。
TEXT型のまま ORDER BY すると辞書順ソートになる:TEXTの昇順では '10' < '2' が成立します(1文字目で比較するため)。数値データは必ず NUMERIC/INTEGER に CAST してから ORDER BY しましょう。
CAST は失敗することがある:CAST('abc' AS INTEGER) はエラーになります。変換失敗時にNULLを返したい場合は PostgreSQL の CASE WHEN col ~ '^\d+$' THEN col::INTEGER ENDTO_NUMBER などのパターンを使います。
アンチパターン
暗黙キャストに頼る:PostgreSQL では '2024-05-01' + INTERVAL '7 days' が動く場合もありますが、DBMSや型の組み合わせによって動作が異なります。明示的な CAST を書くことで意図が明確になり、移植性・可読性が向上します。
日付をTEXTで保存・比較する:WHERE registered_at > '2024-05-01'(TEXT型同士)は辞書比較になります。日付データは必ず DATE/TIMESTAMP 型で保存し、CAST はデータ受け取り時のみの変換として使いましょう。
実務コラム:データ型設計はパフォーマンスにも影響する
テーブル設計時に適切な型を選ぶことはストレージ効率・インデックス性能・集計速度に直接影響します。「金額は NUMERIC、IDはINTEGER/BIGINT、日時はTIMESTAMP、フラグはBOOLEAN」という基本を守るだけで多くのパフォーマンス問題を予防できます。既存テーブルの型が間違っている場合は ALTER TABLE ... ALTER COLUMN ... TYPE で変更できますが、既存データの再検証が必要です。
QUESTION 2

COALESCE / NULLIF — NULL を別の値に置換・逆変換する

COALESCENULLIFNULL安全NULL注意
前提知識

COALESCE(値1, 値2, ...) は引数を左から順に評価し、最初の非NULL値を返します。NULL の代替値を設定する最も基本的な関数です。NULLIF(値1, 値2) は逆で、値1と値2が等しければNULLを返します(特定の値をNULLに変換)。

COALESCE(NULL, '未設定')   -- → '未設定'  (NULLを置換)
COALESCE('田中', '未設定') -- → '田中'    (非NULLはそのまま)
COALESCE(NULL, NULL, 0)   -- → 0        (複数候補も可)
NULLIF(0, 0)              -- → NULL     (0=0なのでNULLに変換)
NULLIF(42, 0)             -- → 42       (42≠0なのでそのまま)
ゼロ除算の防御パターン:total / NULLIF(count, 0) のように NULLIF を使うと、countが0の行でNULLが返りゼロ除算エラーを防げます。集計クエリで頻出のパターンです。
問題

users テーブルには nicknamescore にNULLの行があります。COALESCE で①表示名(nickNameあればnickname、なければname)、②スコア(NULLなら0)を生成してください。またNULLIF でscoreが0の場合はNULLに戻す score_nullif 列も付与してください。

使用テーブル
▸ users
user_idnamenicknamescore
1田中 太郎タナカ85
2佐藤 花子NULL92
3鈴木 一郎スズキ0
4山田 次郎NULLNULL
期待出力
user_iddisplay_namescorescore_nullif
1タナカ8585
2佐藤 花子9292
3スズキ0NULL
4山田 次郎0NULL
模範解答コード
SELECT
  user_id,
  COALESCE(nickname, name) AS display_name,  -- nickname がNULLなら name を使用
  COALESCE(score, 0)       AS score,         -- score がNULLなら 0 を使用
  NULLIF(score, 0)         AS score_nullif   -- score=0 ならNULLに変換(COALESCEの逆)
FROM   users
ORDER BY user_id;

/*
  実行順序(SQLの論理的な評価順):
  1. FROM users                 → 全4行を読み込む
  2. COALESCE(nickname, name)   → 左から順に評価し最初の非NULL値を返す
     タナカ (非NULL)             → タナカ
     NULL, 佐藤 花子             → 佐藤 花子 (nickNameがNULLなのでnameを使用)
     スズキ (非NULL)             → スズキ
     NULL, 山田 次郎             → 山田 次郎 (同上)
  3. COALESCE(score, 0)         → NULLなら0、それ以外はそのまま(0はNULLではない)
  4. NULLIF(score, 0)           → score=0ならNULL、それ以外はscoreをそのまま返す
  5. ORDER BY user_id           → user_id 昇順で出力
*/
解説(テーブル変化・ポイント)
SELECT user_id, COALESCE(nickname, name) AS display_name, COALESCE(score, 0) AS score, NULLIF(score, 0) AS score_nullif FROM users ORDER BY user_id;
LEGEND
データ取得・読込対象
① FROM — NULL含む元データ
FROM usersnickname と score にNULLが混在しています。NULLのまま集計するとSUM/AVGで意図しない結果が出ることがあります。
1 / 5
user_idnamenicknamescore
1田中 太郎タナカ85
2佐藤 花子NULL92
3鈴木 一郎スズキ0
4山田 次郎NULLNULL
4行 (NULL含む)
SELECT user_id, nickname FROM users WHERE nickname = NULL;
LEGEND
データ取得・読込対象
① FROM
FROM usersnicknameがNULLの行が含まれています。
1 / 3
user_idnickname
1タナカ
2NULL
3スズキ
4NULL
4行
学習ポイント
COALESCEは複数の候補を取れる:COALESCE(nickname, display_name, name, '名無し') のように3つ以上の候補を指定できます。左から順に最初の非NULLで確定します。フォールバックチェーンが自然に書けます。
NULLIFの主な用途はゼロ除算防止:SUM(amount) / NULLIF(COUNT(*), 0) のパターンで、件数が0のグループでNULLが返るようになります(ゼロ除算エラーを防止)。集計クエリで頻出します。
NULLは「値の欠如」であり0や空文字とは異なる:score=0は「スコアが確定して0点」、score=NULLは「スコアが未入力」という別の状態です。NULLIFで0→NULLに変換するとこの二つを同じ「未確定」として扱えます。
アンチパターン
NULLとの比較に = を使う:WHERE nickname = NULL は常にFALSEになります。NULLの検出には必ず IS NULL / IS NOT NULL を使いましょう。
COALESCE(col, '') で空文字とNULLを混在させる:NULLを空文字に変換すると「未入力」と「空文字入力」の区別がなくなります。アプリ側でのNULLチェックとCOALESCEの使い所を明確に設計しましょう。
実務コラム:NULLファーストな設計思想
実務では「NULLをなるべく入れない」設計(NOT NULL制約+デフォルト値)と「NULLを許容して意味を持たせる」設計が混在します。一般的には「任意入力・まだ決まっていない値にはNULLを許容し、COALESCEで出力時に変換する」スタイルが多いです。重要なのは「NULLが入るケースをドキュメント化しておくこと」で、後からクエリを書く開発者が正しい場所にCOALESCEを書けるようになります。
QUESTION 3

CASE WHEN — 条件に応じて列の値をラベル変換・カテゴリ化する

CASE WHEN条件分岐ラベル変換分析集計
前提知識

SQLの CASE 式は、行ごとに条件分岐して異なる値を返します。2種類の書き方があります。

-- ①単純 CASE(値と直接比較)
CASE status
  WHEN 'completed' THEN '完了'
  WHEN 'pending'   THEN '保留中'
  ELSE 'その他'   -- ELSE省略時はNULLが返る
END

-- ②検索 CASE(任意の条件式)
CASE
  WHEN amount >= 10000 THEN 'high'
  WHEN amount >=  5000 THEN 'medium'  -- 上から順に評価、最初にTRUEになったTHENを返す
  ELSE                   'low'
END
評価順のポイント:WHEN句は上から順に評価され、最初に条件がTRUEになったTHENの値が返ります。以降のWHENは評価されません。範囲条件は「より厳しい条件(上位帯)を先に書く」のが定石です。
問題

orders テーブルの status(英語コード)を日本語ラベルに変換した status_label 列と、amount の大きさでランク付けした amount_rank(10000以上:'high'、5000以上:'medium'、それ以外:'low')列を追加してください。

使用テーブル
▸ orders
order_iduser_idamountstatus
10118000completed
102212500pending
10333200cancelled
10416700completed
1054450pending
期待出力
order_iduser_idamountstatus_labelamount_rank
10118000完了medium
102212500保留中high
10333200キャンセルlow
10416700完了medium
1054450保留中low
模範解答コード
SELECT
  order_id, user_id, amount,
  CASE status                       -- 単純 CASE:値を直接比較
    WHEN 'completed' THEN '完了'
    WHEN 'pending'   THEN '保留中'
    WHEN 'cancelled' THEN 'キャンセル'
    ELSE                  '不明'      -- 想定外の値は '不明'
  END                    AS status_label,
  CASE                               -- 検索 CASE:範囲条件で判定
    WHEN amount >= 10000 THEN 'high'
    WHEN amount >=  5000 THEN 'medium'  -- 上のWHENがFALSEの行だけ評価
    ELSE                    'low'
  END                    AS amount_rank
FROM   orders
ORDER BY order_id;

/*
  実行順序(SQLの論理的な評価順):
  1. FROM orders                  → 全5行を読み込む
  2. CASE status WHEN ... END     → 各行のstatusを上から順にWHENと比較し変換
  3. CASE WHEN amount >= ... END  → 各行のamountを上から順に評価してランク付け
  4. ORDER BY order_id            → order_id 昇順で出力
  */
解説(テーブル変化・ポイント)
SELECT order_id, user_id, amount, CASE status WHEN 'completed' THEN '完了' WHEN 'pending' THEN '保留中' WHEN 'cancelled' THEN 'キャンセル' ELSE '不明' END AS status_label, CASE WHEN amount >= 10000 THEN 'high' WHEN amount >= 5000 THEN 'medium' ELSE 'low' END AS amount_rank FROM orders ORDER BY order_id;
LEGEND
データ取得・読込対象
① FROM — 元データ読み込み
FROM ordersstatusは英語コード、amountは整数値のままです。表示・分析のためにCASE WHENで人が読みやすい形式に変換します。
1 / 4
order_iduser_idamountstatus
10118000completed
102212500pending
10333200cancelled
10416700completed
1054450pending
5行
SELECT user_id, SUM(CASE WHEN status = 'completed' THEN amount ELSE 0 END) AS completed_total, SUM(CASE WHEN status = 'pending' THEN amount ELSE 0 END) AS pending_total FROM orders GROUP BY user_id;
LEGEND
データ取得・読込対象
① FROM
FROM ordersuser_idごとの各ステータスの合計金額を横並びで出したいとします。
1 / 4
user_idstatusamount
1completed8000
2pending12500
3cancelled3200
1completed6700
4pending450
5行
学習ポイント
CASE は SELECT 以外でも使える:ORDER BY CASE status WHEN 'completed' THEN 1 WHEN 'pending' THEN 2 ELSE 3 END のようにORDER BYでカスタムソート順を指定したり、WHERE句・GROUP BY句でも使用できます。
条件付き集計(ピボット)への応用:SUM(CASE WHEN status='completed' THEN amount ELSE 0 END) AS completed_total のように集計関数の中でCASEを使うと、ステータスごとの合計を横並びで出す「ピボット集計」ができます。実務で頻出のパターンです。
ELSEを省略するとNULLになる:どのWHENにも一致しない場合、ELSEがないとNULLが返ります。意図しないNULLを防ぐため、ELSE '不明'ELSE NULL と明示的に書く習慣を持ちましょう。
アンチパターン
WHEN条件の順序ミス:WHEN amount >= 5000 THEN 'medium' を先に書くと、12500も 'medium' に分類されてしまいます。検索CASEは必ず上位の条件(より厳しい条件)を先に書きましょう。
IIF/DECODEとの混同:MySQLの IF()、Oracleの DECODE() はPostgreSQLでは使えません。標準SQLの CASE WHEN を使うことで移植性が高まります。
実務コラム:ステータスコードの管理とCASE WHEN
実務では 'completed'/'pending'/'cancelled' のようなステータスコードをデータベースに英語で保存し、表示時にCASE WHENで日本語変換するパターンが多いです。利点はコードの一意性・ソート安定性・多言語対応の容易さです。ただしCASEが複数箇所に散らばると変更コストが高くなるため、変換ロジックはビューやアプリ層に集約する設計も有効です。
QUESTION 4

UPPER / LOWER / TRIM / LENGTH — 文字列を正規化する

UPPER/LOWERTRIM文字列正規化クレンジング
前提知識

ユーザー入力や外部連携データには大文字小文字の混在・前後スペースが頻繁に含まれます。主要な文字列正規化関数を押さえましょう。

TRIM('  hello  ')       -- → 'hello'    前後の半角スペースを除去
LTRIM('  hello  ')      -- → 'hello  '  左側のみ除去
RTRIM('  hello  ')      -- → '  hello'  右側のみ除去
LOWER('Hello@WORLD.COM') -- → 'hello@world.com'
UPPER('hello')          -- → 'HELLO'
LENGTH('hello')         -- → 5  (文字数)
ネストして使える:LOWER(TRIM(email)) のように内側から外側へ順に適用されます。①TRIMで空白除去 → ②LOWERで小文字化 という処理が1行で書けます。
問題

user_inputs テーブルには大文字小文字の不統一と前後スペースを含む emailusername があります。次の列を取得してください。email: ①TRIM後(email_trimmed)②TRIM+小文字化(email_normalized)③TRIM後の文字数(email_length)、username: ①TRIM(username_trimmed)②TRIM+大文字化(username_upper)。

使用テーブル
▸ user_inputs
idemailusername
1' Alice@Example.COM ''alice_dev'
2'BOB@GMAIL.COM'' Bob Smith '
3'carol@test.org ''CAROL'
期待出力
idemail_trimmedemail_normalizedemail_lengthusername_trimmedusername_upper
1Alice@Example.COMalice@example.com17alice_devALICE_DEV
2BOB@GMAIL.COMbob@gmail.com13Bob SmithBOB SMITH
3carol@test.orgcarol@test.org14CAROLCAROL
模範解答コード
SELECT
  id,
  TRIM(email)                AS email_trimmed,    -- 前後の半角スペースを除去
  LOWER(TRIM(email))        AS email_normalized, -- ①TRIMで空白除去 → ②LOWERで小文字化
  LENGTH(TRIM(email))       AS email_length,    -- TRIM後の文字数
  TRIM(username)             AS username_trimmed, -- 前後スペース除去
  UPPER(TRIM(username))     AS username_upper    -- TRIM後に大文字化
FROM   user_inputs
ORDER BY id;

/*
  実行順序(SQLの論理的な評価順):
  1. FROM user_inputs            → 全3行を読み込む(前後スペース・大文字混在のまま)
  2. TRIM(email)                 → 各行のemailの前後スペースを除去
  3. LOWER(TRIM(email))          → TRIMの結果をさらに小文字化(内側から外側へ評価)
  4. LENGTH(TRIM(email))         → TRIM後の文字数をカウント
  5. TRIM(username), UPPER(...)  → usernameも同様に変換
  6. ORDER BY id                 → id 昇順で出力
  */
解説(テーブル変化・ポイント)
SELECT id, TRIM(email) AS email_trimmed, LOWER(TRIM(email)) AS email_normalized, LENGTH(TRIM(email)) AS email_length, TRIM(username) AS username_trimmed, UPPER(TRIM(username)) AS username_upper FROM user_inputs ORDER BY id;
LEGEND
データ取得・読込対象
① FROM — 前後スペース・大文字混在データ
FROM user_inputsemailに前後スペースや大文字小文字混在、usernameにも前後スペース・全大文字が含まれます。DBに保存する前またはSELECT時に正規化が必要です。
1 / 4
idemail (元)username (元)
1' Alice@Example.COM ''alice_dev'
2'BOB@GMAIL.COM'' Bob Smith '
3'carol@test.org ''CAROL'
3行 (要クレンジング)
SELECT email, LENGTH(email) AS len_raw, LENGTH(TRIM(email)) AS len_trimmed FROM user_inputs;
LEGEND
データ取得・読込対象
① FROM
FROM user_inputs前後にスペースを含むデータです。
1 / 2
email (元)
' Alice@Example.COM '
'BOB@GMAIL.COM'
'carol@test.org '
3行
学習ポイント
大文字小文字を問わない検索はILIKEも使える:PostgreSQLの ILIKE 演算子は大文字小文字を区別しないパターンマッチングです。WHERE email ILIKE '%@example.com' のように使えます。ただし LOWER(email) にインデックスを張る方が大量データでは高速なことが多いです。
TRIM はデフォルトで両端スペース除去:TRIM(BOTH ' ' FROM col) が完全な書き方ですが TRIM(col) で同等です。特定の文字を除去したい場合は TRIM(BOTH 'x' FROM 'xxxhelloxxx') → 'hello' のように指定できます。
関数を適用する順序が重要:TRIM(LOWER(email))LOWER(TRIM(email)) は結果が同じですが、一般的には「まずTRIMで整形してから変換」が意図を明確にします。
アンチパターン
TRIM前にLENGTHを計算する:LENGTH(email) は前後スペースを含む文字数を返します。正規化後の文字数が欲しい場合は必ず LENGTH(TRIM(email)) にしましょう。
正規化せずにWHEREで比較する:WHERE email = 'alice@example.com'' Alice@Example.COM ' にはマッチしません。入力値と保存値の両方をLOWER(TRIM(...))で正規化してから比較する習慣が重要です。
実務コラム:正規化はINSERT時とSELECT時のどちらで行うか
実務では「INSERT時に正規化してDBに保存」vs「SELECT時にCAST/TRIM/LOWERで正規化」の設計判断があります。INSERT時正規化(CHECK制約・トリガー・アプリ層バリデーション)のメリットはデータが常にクリーンな状態で、クエリが単純になること。SELECT時正規化は既存の汚れたデータに対応できる柔軟性があります。新規設計ではINSERT時に正規化し、レガシーデータの分析にはSELECT時変換を組み合わせるのが実務のバランスです。
QUESTION 5

CONCAT / || / LPAD — 文字列を結合・書式整形する

CONCAT||演算子文字列結合表示整形
前提知識

文字列の結合には || 演算子(SQL標準)と CONCAT 関数の2種類があります。NULL への挙動が異なる点が重要です。

'田中' || ' ' || '太郎'        -- → '田中 太郎'  (SQL標準)
CONCAT('田中', ' ', '太郎')     -- → '田中 太郎'  (関数版)
CONCAT(NULL, '太郎')           -- → '太郎'       (NULLを空文字扱い)
NULL || '太郎'                  -- → NULL         (NULLが伝播する)
LPAD('7', 4, '0')             -- → '0007'       (左側を'0'で埋める)
RPAD('ABC', 6, '-')           -- → 'ABC---'     (右側を'-'で埋める)
|| と CONCAT のNULL挙動の違い:|| 演算子は片方がNULLだと結果全体がNULLになります。CONCAT はNULLを空文字として扱い結合を続けます。NULL が含まれる可能性がある列の結合には CONCATCOALESCE(col, '') を使いましょう。
問題

customers テーブルから、last_name || ' ' || first_namefull_name を、CONCAT 関数版の full_name_v2 を、pref || cityfull_address を、そして LPAD でゼロ埋め4桁の顧客番号を含む customer_label(例: 'お客様番号: 0001')を取得してください。

使用テーブル
▸ customers
customer_idlast_namefirst_nameprefcity
1田中太郎東京都渋谷区
2佐藤花子大阪府北区
3鈴木一郎愛知県中区
期待出力
customer_idfull_namefull_name_v2full_addresscustomer_label
1田中 太郎田中 太郎東京都渋谷区お客様番号: 0001
2佐藤 花子佐藤 花子大阪府北区お客様番号: 0002
3鈴木 一郎鈴木 一郎愛知県中区お客様番号: 0003
模範解答コード
SELECT
  customer_id,
  last_name || ' ' || first_name         AS full_name,     -- || で文字列結合(SQL標準)
  CONCAT(last_name, ' ', first_name)    AS full_name_v2,  -- CONCAT関数版(NULL安全)
  pref || city                           AS full_address,  -- 都道府県 + 市区町村
  'お客様番号: ' ||
    LPAD(customer_id::TEXT, 4, '0')   AS customer_label -- ::TEXT でキャスト後にゼロ埋め
FROM   customers
ORDER BY customer_id;

/*
  実行順序(SQLの論理的な評価順):
  1. FROM customers                      → 全3行を読み込む
  2. last_name || ' ' || first_name      → 文字列を || で順に結合
  3. CONCAT(last_name, ' ', first_name)  → 関数版(NULLは空文字として扱う)
  4. pref || city                        → 都道府県と市区町村を結合
  5. LPAD(customer_id::TEXT, 4, '0')     → ①整数をTEXTにキャスト ②左側を'0'で4桁に埋める
     'お客様番号: ' || ...               → ラベル文字列と結合
  6. ORDER BY customer_id                → customer_id 昇順で出力
*/
解説(テーブル変化・ポイント)
SELECT customer_id, last_name || ' ' || first_name AS full_name, CONCAT(last_name, ' ', first_name) AS full_name_v2, pref || city AS full_address, 'お客様番号: ' || LPAD(customer_id::TEXT, 4, '0') AS customer_label FROM customers ORDER BY customer_id;
LEGEND
データ取得・読込対象
① FROM — 元データ読み込み
FROM customers氏名がlast_name/first_nameに分かれています。アプリ表示用に結合する必要があります。
1 / 4
customer_idlast_namefirst_nameprefcity
1田中太郎東京都渋谷区
2佐藤花子大阪府北区
3鈴木一郎愛知県中区
3行
SELECT first_name || middle_name || last_name AS op_result, CONCAT(first_name, middle_name, last_name) AS fn_result FROM ...
LEGEND
データ取得・読込対象
① 仮想データ
仮想データ (middle_name が NULL)ミドルネームが存在しない(NULL)顧客データがあるとします。
1 / 3
firstmiddlelast
'太郎'NULL'田中'
1行
学習ポイント
|| と CONCAT の使い分け:NULLが含まれない保証がある列には || でシンプルに書けます。middle_nameのような任意項目が混ざる場合は CONCAT(first_name, COALESCE(' ' || middle_name, ''), ' ', last_name) のようにCOALESCEと組み合わせるか、CONCATを使いましょう。
LPADは番号・コードの表示形式統一に便利:LPAD(order_id::TEXT, 8, '0') で 'ORD00000001' のような固定長コード生成ができます。帳票出力・バーコード生成・ファイル名付番などで頻出します。
数値をTEXTに変換してから連結する:customer_id::TEXTCAST(customer_id AS TEXT) で整数をTEXTに変換してから || や LPAD に渡します。型が合わないまま連結しようとするとエラーになります。
アンチパターン
NULLを含む列を || で結合する:'prefix_' || NULL || '_suffix' は NULL になります。first_nameやmiddle_nameがNULLになりうる場合は COALESCE(col, '') または CONCAT を使いましょう。
整数のまま || しようとする:42 || ' 個' はエラーになります(PostgreSQL)。42::TEXT || ' 個' または CONCAT(42, ' 個') のように型変換が必要です。
実務コラム:表示用の文字列生成はアプリ側かDB側か
「氏名結合・住所整形・ラベル付与」のような表示用文字列生成はアプリ側(フロントエンド・バックエンド)でもSQL側でもできます。SQLで処理するメリットは「CSVエクスポート・帳票・バッチ処理で再利用しやすい」こと。アプリで処理するメリットは「多言語対応・UIフォーマット変更が容易」なこと。両者を組み合わせてビューに整形済み列を用意するのが実務で多いパターンです。