CAST / :: — テキスト型データを数値・日付型に変換する
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 短縮記法 (同等)
'10' < '2'(辞書順)になります。また '2024-05-01' + 7 のような日付計算もエラーになります。適切な型変換で算術・日付演算・正しいソートが可能になります。raw_imports テーブルの全列はTEXT型でインポートされています。id を INTEGER、amount を NUMERIC、registered_at を DATE に変換し、さらに税率10%の税込金額(amount_with_tax)を付与してください。id 昇順で出力してください。
| id | amount | registered_at |
|---|---|---|
| '1' | '8000' | '2024-05-01' |
| '2' | '12500' | '2024-05-15' |
| '3' | '3200' | '2024-06-02' |
| id | amount | registered_at | amount_with_tax |
|---|---|---|---|
| 1 | 8000 | 2024-05-01 | 8800 |
| 2 | 12500 | 2024-05-15 | 13750 |
| 3 | 3200 | 2024-06-02 | 3520 |
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 → 整数として昇順ソート */
LEGEND
① FROM — 元データ (全列 TEXT)
FROM raw_importsCSVインポートで取り込まれた全列がTEXT型です。このままでは数値計算も日付演算もできません。CAST で適切な型に変換します。| id (TEXT) | amount (TEXT) | registered_at (TEXT) |
|---|---|---|
| '1' | '8000' | '2024-05-01' |
| '2' | '12500' | '2024-05-15' |
| '3' | '3200' | '2024-06-02' |
LEGEND
① FROM
FROM raw_imports全列がTEXT型のデータです。このまま amount をソートするとどうなるでしょうか?| id | amount (TEXT) |
|---|---|
| '1' | '8000' |
| '2' | '12500' |
| '3' | '3200' |
CAST(col AS INTEGER) はSQL標準記法でどのDBMSでも動作します。col::INTEGER はPostgreSQL独自の短縮記法で可読性が高い反面、MySQL・SQL Serverでは使えません。チームのDBMS状況に合わせて選択しましょう。'10' < '2' が成立します(1文字目で比較するため)。数値データは必ず NUMERIC/INTEGER に CAST してから ORDER BY しましょう。CAST('abc' AS INTEGER) はエラーになります。変換失敗時にNULLを返したい場合は PostgreSQL の CASE WHEN col ~ '^\d+$' THEN col::INTEGER END や TO_NUMBER などのパターンを使います。'2024-05-01' + INTERVAL '7 days' が動く場合もありますが、DBMSや型の組み合わせによって動作が異なります。明示的な CAST を書くことで意図が明確になり、移植性・可読性が向上します。WHERE registered_at > '2024-05-01'(TEXT型同士)は辞書比較になります。日付データは必ず DATE/TIMESTAMP 型で保存し、CAST はデータ受け取り時のみの変換として使いましょう。ALTER TABLE ... ALTER COLUMN ... TYPE で変更できますが、既存データの再検証が必要です。COALESCE / NULLIF — 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 テーブルには nickname と score にNULLの行があります。COALESCE で①表示名(nickNameあればnickname、なければname)、②スコア(NULLなら0)を生成してください。またNULLIF でscoreが0の場合はNULLに戻す score_nullif 列も付与してください。
| user_id | name | nickname | score |
|---|---|---|---|
| 1 | 田中 太郎 | タナカ | 85 |
| 2 | 佐藤 花子 | NULL | 92 |
| 3 | 鈴木 一郎 | スズキ | 0 |
| 4 | 山田 次郎 | NULL | NULL |
| user_id | display_name | score | score_nullif |
|---|---|---|---|
| 1 | タナカ | 85 | 85 |
| 2 | 佐藤 花子 | 92 | 92 |
| 3 | スズキ | 0 | NULL |
| 4 | 山田 次郎 | 0 | NULL |
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 昇順で出力 */
LEGEND
① FROM — NULL含む元データ
FROM usersnickname と score にNULLが混在しています。NULLのまま集計するとSUM/AVGで意図しない結果が出ることがあります。| user_id | name | nickname | score |
|---|---|---|---|
| 1 | 田中 太郎 | タナカ | 85 |
| 2 | 佐藤 花子 | NULL | 92 |
| 3 | 鈴木 一郎 | スズキ | 0 |
| 4 | 山田 次郎 | NULL | NULL |
LEGEND
① FROM
FROM usersnicknameがNULLの行が含まれています。| user_id | nickname |
|---|---|
| 1 | タナカ |
| 2 | NULL |
| 3 | スズキ |
| 4 | NULL |
COALESCE(nickname, display_name, name, '名無し') のように3つ以上の候補を指定できます。左から順に最初の非NULLで確定します。フォールバックチェーンが自然に書けます。SUM(amount) / NULLIF(COUNT(*), 0) のパターンで、件数が0のグループでNULLが返るようになります(ゼロ除算エラーを防止)。集計クエリで頻出します。WHERE nickname = NULL は常にFALSEになります。NULLの検出には必ず IS NULL / IS NOT NULL を使いましょう。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
orders テーブルの status(英語コード)を日本語ラベルに変換した status_label 列と、amount の大きさでランク付けした amount_rank(10000以上:'high'、5000以上:'medium'、それ以外:'low')列を追加してください。
| order_id | user_id | amount | status |
|---|---|---|---|
| 101 | 1 | 8000 | completed |
| 102 | 2 | 12500 | pending |
| 103 | 3 | 3200 | cancelled |
| 104 | 1 | 6700 | completed |
| 105 | 4 | 450 | pending |
| order_id | user_id | amount | status_label | amount_rank |
|---|---|---|---|---|
| 101 | 1 | 8000 | 完了 | medium |
| 102 | 2 | 12500 | 保留中 | high |
| 103 | 3 | 3200 | キャンセル | low |
| 104 | 1 | 6700 | 完了 | medium |
| 105 | 4 | 450 | 保留中 | 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 昇順で出力 */
LEGEND
① FROM — 元データ読み込み
FROM ordersstatusは英語コード、amountは整数値のままです。表示・分析のためにCASE WHENで人が読みやすい形式に変換します。| order_id | user_id | amount | status |
|---|---|---|---|
| 101 | 1 | 8000 | completed |
| 102 | 2 | 12500 | pending |
| 103 | 3 | 3200 | cancelled |
| 104 | 1 | 6700 | completed |
| 105 | 4 | 450 | pending |
LEGEND
① FROM
FROM ordersuser_idごとの各ステータスの合計金額を横並びで出したいとします。| user_id | status | amount |
|---|---|---|
| 1 | completed | 8000 |
| 2 | pending | 12500 |
| 3 | cancelled | 3200 |
| 1 | completed | 6700 |
| 4 | pending | 450 |
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 '不明' や ELSE NULL と明示的に書く習慣を持ちましょう。WHEN amount >= 5000 THEN 'medium' を先に書くと、12500も 'medium' に分類されてしまいます。検索CASEは必ず上位の条件(より厳しい条件)を先に書きましょう。IF()、Oracleの DECODE() はPostgreSQLでは使えません。標準SQLの CASE WHEN を使うことで移植性が高まります。UPPER / LOWER / TRIM / LENGTH — 文字列を正規化する
ユーザー入力や外部連携データには大文字小文字の混在・前後スペースが頻繁に含まれます。主要な文字列正規化関数を押さえましょう。
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 テーブルには大文字小文字の不統一と前後スペースを含む email・username があります。次の列を取得してください。email: ①TRIM後(email_trimmed)②TRIM+小文字化(email_normalized)③TRIM後の文字数(email_length)、username: ①TRIM(username_trimmed)②TRIM+大文字化(username_upper)。
| id | username | |
|---|---|---|
| 1 | ' Alice@Example.COM ' | 'alice_dev' |
| 2 | 'BOB@GMAIL.COM' | ' Bob Smith ' |
| 3 | 'carol@test.org ' | 'CAROL' |
| id | email_trimmed | email_normalized | email_length | username_trimmed | username_upper |
|---|---|---|---|---|---|
| 1 | Alice@Example.COM | alice@example.com | 17 | alice_dev | ALICE_DEV |
| 2 | BOB@GMAIL.COM | bob@gmail.com | 13 | Bob Smith | BOB SMITH |
| 3 | carol@test.org | carol@test.org | 14 | CAROL | CAROL |
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 昇順で出力 */
LEGEND
① FROM — 前後スペース・大文字混在データ
FROM user_inputsemailに前後スペースや大文字小文字混在、usernameにも前後スペース・全大文字が含まれます。DBに保存する前またはSELECT時に正規化が必要です。| id | email (元) | username (元) |
|---|---|---|
| 1 | ' Alice@Example.COM ' | 'alice_dev' |
| 2 | 'BOB@GMAIL.COM' | ' Bob Smith ' |
| 3 | 'carol@test.org ' | 'CAROL' |
LEGEND
① FROM
FROM user_inputs前後にスペースを含むデータです。| email (元) |
|---|
| ' Alice@Example.COM ' |
| 'BOB@GMAIL.COM' |
| 'carol@test.org ' |
ILIKE 演算子は大文字小文字を区別しないパターンマッチングです。WHERE email ILIKE '%@example.com' のように使えます。ただし LOWER(email) にインデックスを張る方が大量データでは高速なことが多いです。TRIM(BOTH ' ' FROM col) が完全な書き方ですが TRIM(col) で同等です。特定の文字を除去したい場合は TRIM(BOTH 'x' FROM 'xxxhelloxxx') → 'hello' のように指定できます。TRIM(LOWER(email)) と LOWER(TRIM(email)) は結果が同じですが、一般的には「まずTRIMで整形してから変換」が意図を明確にします。LENGTH(email) は前後スペースを含む文字数を返します。正規化後の文字数が欲しい場合は必ず LENGTH(TRIM(email)) にしましょう。WHERE email = 'alice@example.com' は ' Alice@Example.COM ' にはマッチしません。入力値と保存値の両方をLOWER(TRIM(...))で正規化してから比較する習慣が重要です。CONCAT / || / LPAD — 文字列を結合・書式整形する
文字列の結合には || 演算子(SQL標準)と CONCAT 関数の2種類があります。NULL への挙動が異なる点が重要です。
'田中' || ' ' || '太郎' -- → '田中 太郎' (SQL標準) CONCAT('田中', ' ', '太郎') -- → '田中 太郎' (関数版) CONCAT(NULL, '太郎') -- → '太郎' (NULLを空文字扱い) NULL || '太郎' -- → NULL (NULLが伝播する) LPAD('7', 4, '0') -- → '0007' (左側を'0'で埋める) RPAD('ABC', 6, '-') -- → 'ABC---' (右側を'-'で埋める)
CONCAT か COALESCE(col, '') を使いましょう。customers テーブルから、last_name || ' ' || first_name で full_name を、CONCAT 関数版の full_name_v2 を、pref || city で full_address を、そして LPAD でゼロ埋め4桁の顧客番号を含む customer_label(例: 'お客様番号: 0001')を取得してください。
| customer_id | last_name | first_name | pref | city |
|---|---|---|---|---|
| 1 | 田中 | 太郎 | 東京都 | 渋谷区 |
| 2 | 佐藤 | 花子 | 大阪府 | 北区 |
| 3 | 鈴木 | 一郎 | 愛知県 | 中区 |
| customer_id | full_name | full_name_v2 | full_address | customer_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 昇順で出力 */
LEGEND
① FROM — 元データ読み込み
FROM customers氏名がlast_name/first_nameに分かれています。アプリ表示用に結合する必要があります。| customer_id | last_name | first_name | pref | city |
|---|---|---|---|---|
| 1 | 田中 | 太郎 | 東京都 | 渋谷区 |
| 2 | 佐藤 | 花子 | 大阪府 | 北区 |
| 3 | 鈴木 | 一郎 | 愛知県 | 中区 |
LEGEND
① 仮想データ
仮想データ (middle_name が NULL)ミドルネームが存在しない(NULL)顧客データがあるとします。| first | middle | last |
|---|---|---|
| '太郎' | NULL | '田中' |
CONCAT(first_name, COALESCE(' ' || middle_name, ''), ' ', last_name) のようにCOALESCEと組み合わせるか、CONCATを使いましょう。LPAD(order_id::TEXT, 8, '0') で 'ORD00000001' のような固定長コード生成ができます。帳票出力・バーコード生成・ファイル名付番などで頻出します。customer_id::TEXT や CAST(customer_id AS TEXT) で整数をTEXTに変換してから || や LPAD に渡します。型が合わないまま連結しようとするとエラーになります。'prefix_' || NULL || '_suffix' は NULL になります。first_nameやmiddle_nameがNULLになりうる場合は COALESCE(col, '') または CONCAT を使いましょう。42 || ' 個' はエラーになります(PostgreSQL)。42::TEXT || ' 個' または CONCAT(42, ' 個') のように型変換が必要です。