PostgreSQLのCOALESCE関数でNULL値を賢く処理!実例で学ぶ完全マスター

データベース・SQL

「計算結果がNULLになって困る…」 「NULLが含まれるとレポートが正しく表示されない」 「IF文をたくさん書かないとNULL処理ができない」

データベースを扱っていると、NULL値は必ず出てくる厄介者。

でも大丈夫!PostgreSQLのCOALESCE関数を使えば、NULL処理が驚くほどシンプルになります。

この記事を読み終える頃には、あなたもNULL処理のエキスパートになっているはずです!


スポンサーリンク

COALESCE関数を一言で説明すると?

超簡単な説明

**COALESCE(コアレス)**は、「最初に見つかったNULLじゃない値を返す」関数です。

日常生活に例えると:

  • 第1希望のレストランが満席 → 第2希望へ
  • 第2希望も満席 → 第3希望へ
  • 空いているレストランが見つかったら、そこに決定!

COALESCEも同じように、NULL(空席)をスキップして、最初の値(空いている店)を返します

基本的な書き方

COALESCE(値1, 値2, 値3, ...)

左から順番にチェックして、最初のNULLじゃない値を返します。

具体例:

SELECT COALESCE(NULL, NULL, 'やっと見つかった!', 'これも候補');
-- 結果: 'やっと見つかった!'

実際のテーブルで理解する:基本編

サンプルテーブルを作ろう

まず、従業員テーブルで実例を見ていきましょう:

-- 従業員テーブルの作成
CREATE TABLE employees (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100),
    mobile_phone VARCHAR(20),
    office_phone VARCHAR(20),
    home_phone VARCHAR(20),
    email VARCHAR(100)
);

-- サンプルデータの挿入
INSERT INTO employees (name, mobile_phone, office_phone, home_phone, email) VALUES
('田中太郎', '090-1234-5678', '03-1234-5678', NULL, 'tanaka@example.com'),
('佐藤花子', NULL, '03-2345-6789', '044-123-4567', 'sato@example.com'),
('鈴木一郎', NULL, NULL, '045-234-5678', 'suzuki@example.com'),
('高橋美咲', '080-3456-7890', NULL, NULL, 'takahashi@example.com'),
('山田次郎', NULL, NULL, NULL, 'yamada@example.com');

基本的な使い方:連絡先を1つ表示

問題: 従業員の連絡先電話番号を表示したい。優先順位は携帯→会社→自宅。

SELECT 
    name AS 名前,
    COALESCE(mobile_phone, office_phone, home_phone, '電話番号なし') AS 連絡先
FROM employees;

結果:

名前        | 連絡先
------------|------------------
田中太郎    | 090-1234-5678
佐藤花子    | 03-2345-6789
鈴木一郎    | 045-234-5678
高橋美咲    | 080-3456-7890
山田次郎    | 電話番号なし

素晴らしい!複雑なCASE文なしで、優先順位付きの処理ができました。


実践的な使用例:これができれば業務で困らない!

1. 売上レポートでNULL対策

売上データにNULLがあると、合計が正しく計算されません:

-- 売上テーブル
CREATE TABLE sales (
    product_name VARCHAR(100),
    quantity INTEGER,
    unit_price DECIMAL(10,2),
    discount DECIMAL(10,2)
);

INSERT INTO sales VALUES
('商品A', 10, 1000, 100),
('商品B', 5, 2000, NULL),
('商品C', NULL, 1500, 200),
('商品D', 8, NULL, NULL);

NULL対策なしの計算(間違い):

SELECT 
    product_name,
    quantity * unit_price - discount AS 売上金額
FROM sales;
-- NULLが含まれると結果もNULLに!

COALESCE使用(正解):

SELECT 
    product_name AS 商品名,
    COALESCE(quantity, 0) AS 数量,
    COALESCE(unit_price, 0) AS 単価,
    COALESCE(discount, 0) AS 割引,
    COALESCE(quantity, 0) * COALESCE(unit_price, 0) - COALESCE(discount, 0) AS 売上金額
FROM sales;

結果:

商品名 | 数量 | 単価  | 割引 | 売上金額
-------|-----|-------|------|--------
商品A  | 10  | 1000  | 100  | 9900
商品B  | 5   | 2000  | 0    | 10000
商品C  | 0   | 1500  | 200  | -200
商品D  | 8   | 0     | 0    | 0

2. 文字列の結合でNULL対策

住所を結合する時のNULL処理:

-- 住所テーブル
CREATE TABLE addresses (
    id SERIAL PRIMARY KEY,
    prefecture VARCHAR(50),
    city VARCHAR(50),
    street VARCHAR(100),
    building VARCHAR(100)
);

INSERT INTO addresses (prefecture, city, street, building) VALUES
('東京都', '渋谷区', '道玄坂1-1-1', 'ABCビル5F'),
('神奈川県', '横浜市', '西区2-2-2', NULL),
('', '名古屋市', '中区3-3-3', NULL),
(NULL, NULL, NULL, 'XYZマンション');

スマートな住所表示:

SELECT
    id,
    COALESCE(
        NULLIF(prefecture, '') || 
        COALESCE(' ' || NULLIF(city, ''), '') || 
        COALESCE(' ' || NULLIF(street, ''), '') || 
        COALESCE(' ' || NULLIF(building, ''), ''),
        '住所未登録'
    ) AS 完全住所
FROM addresses;

3. デフォルト値の設定

ユーザー設定にデフォルト値を適用:

-- ユーザー設定テーブル
CREATE TABLE user_settings (
    user_id INTEGER PRIMARY KEY,
    theme VARCHAR(20),
    language VARCHAR(10),
    notifications BOOLEAN,
    items_per_page INTEGER
);

INSERT INTO user_settings VALUES
(1, 'dark', 'ja', true, 50),
(2, NULL, 'en', NULL, NULL),
(3, 'light', NULL, false, 100);

デフォルト値を適用:

SELECT
    user_id,
    COALESCE(theme, 'light') AS テーマ,
    COALESCE(language, 'ja') AS 言語,
    COALESCE(notifications, true) AS 通知,
    COALESCE(items_per_page, 20) AS 表示件数
FROM user_settings;

COALESCEと他の関数の使い分け

COALESCE vs CASE文

CASE文での書き方(長い):

SELECT 
    CASE 
        WHEN mobile_phone IS NOT NULL THEN mobile_phone
        WHEN office_phone IS NOT NULL THEN office_phone
        WHEN home_phone IS NOT NULL THEN home_phone
        ELSE '電話番号なし'
    END AS 連絡先
FROM employees;

COALESCEでの書き方(短い):

SELECT 
    COALESCE(mobile_phone, office_phone, home_phone, '電話番号なし') AS 連絡先
FROM employees;

COALESCEの方が圧倒的に簡潔

COALESCE vs NULLIF

NULLIFは逆の動作をします:

-- NULLIFは、2つの値が同じならNULLを返す
SELECT NULLIF('同じ', '同じ');  -- 結果: NULL
SELECT NULLIF('違う', '別');    -- 結果: '違う'

-- 組み合わせて使うと強力!
-- 空文字をNULLに変換してから、デフォルト値を設定
SELECT COALESCE(NULLIF(column_name, ''), 'デフォルト値');

COALESCE vs GREATEST/LEAST

-- COALESCEは最初の非NULL値
SELECT COALESCE(NULL, 10, 20, 5);  -- 結果: 10

-- GREATESTは最大値(NULLがあると結果もNULL)
SELECT GREATEST(10, 20, 5);  -- 結果: 20
SELECT GREATEST(NULL, 10, 20);  -- 結果: NULL

-- 組み合わせて使う
SELECT GREATEST(COALESCE(col1, 0), COALESCE(col2, 0), COALESCE(col3, 0));

パフォーマンスを考慮した使い方

インデックスとの関係

注意点: COALESCEを使うと、インデックスが効かなくなる場合があります。

-- インデックスが効かない例
SELECT * FROM employees 
WHERE COALESCE(mobile_phone, office_phone) = '090-1234-5678';

-- インデックスが効く書き方
SELECT * FROM employees 
WHERE mobile_phone = '090-1234-5678' 
   OR (mobile_phone IS NULL AND office_phone = '090-1234-5678');

大量データでの注意点

-- 非効率な書き方(全行でCOALESCE実行)
SELECT COUNT(*) 
FROM large_table 
WHERE COALESCE(status, 'pending') = 'active';

-- 効率的な書き方(部分インデックス使用)
CREATE INDEX idx_active_status ON large_table(status) 
WHERE status = 'active';

SELECT COUNT(*) 
FROM large_table 
WHERE status = 'active';

よくある間違いと対処法

間違い1:空文字とNULLの混同

-- 問題のあるデータ
INSERT INTO test_table (name) VALUES 
(NULL),    -- NULL
(''),      -- 空文字
(' ');     -- スペース

-- 空文字はNULLじゃない!
SELECT COALESCE(name, 'デフォルト') FROM test_table;
-- 結果: 'デフォルト', '', ' '

-- 正しい対処法
SELECT COALESCE(NULLIF(TRIM(name), ''), 'デフォルト') FROM test_table;
-- 結果: 'デフォルト', 'デフォルト', 'デフォルト'

間違い2:データ型の不一致

-- エラーになる例
SELECT COALESCE(NULL, 'テキスト', 123);
-- ERROR: COALESCE型が一致しません

-- 正しい書き方(型を合わせる)
SELECT COALESCE(NULL, 'テキスト', '123');
-- または
SELECT COALESCE(NULL::TEXT, 'テキスト', 123::TEXT);

間違い3:全部NULLの場合

-- 全部NULLだと結果もNULL
SELECT COALESCE(NULL, NULL, NULL);
-- 結果: NULL

-- 必ず最後にデフォルト値を入れる
SELECT COALESCE(col1, col2, col3, 'デフォルト値');

応用テクニック:上級者への道

動的なデフォルト値

-- 現在の日付をデフォルトに
SELECT COALESCE(created_at, CURRENT_DATE) AS 作成日;

-- 他のカラムの値をデフォルトに
SELECT COALESCE(nickname, first_name, 'ゲスト') AS 表示名;

サブクエリと組み合わせ

-- 部署の平均給与をデフォルトに
SELECT 
    name,
    COALESCE(
        salary, 
        (SELECT AVG(salary) FROM employees WHERE dept_id = e.dept_id)
    ) AS 給与
FROM employees e;

配列でのCOALESCE

-- 配列要素のNULL処理
SELECT COALESCE(array_column[1], array_column[2], 'デフォルト');

-- 配列全体のNULL処理
SELECT COALESCE(array_column, ARRAY['デフォルト1', 'デフォルト2']);

JSONでのCOALESCE

-- JSONフィールドのNULL処理
SELECT COALESCE(
    json_data->>'email',
    json_data->>'alternative_email',
    'no-email@example.com'
) AS メールアドレス;

実務でよく使うパターン集

レポート生成

-- 月次売上レポート
SELECT 
    month,
    COALESCE(SUM(sales_amount), 0) AS 売上合計,
    COALESCE(AVG(sales_amount), 0) AS 平均売上,
    COALESCE(COUNT(NULLIF(sales_amount, 0)), 0) AS 売上件数
FROM monthly_sales
GROUP BY month;

ユーザー表示名の生成

-- 優先順位: ニックネーム → フルネーム → メールアドレスの@前 → 'ユーザー'
SELECT 
    COALESCE(
        nickname,
        first_name || ' ' || last_name,
        SPLIT_PART(email, '@', 1),
        'ユーザー'
    ) AS 表示名
FROM users;

ステータス管理

-- ステータスの優先順位付け
SELECT 
    order_id,
    COALESCE(
        CASE WHEN cancelled_at IS NOT NULL THEN 'キャンセル済' END,
        CASE WHEN shipped_at IS NOT NULL THEN '発送済' END,
        CASE WHEN paid_at IS NOT NULL THEN '支払済' END,
        '処理中'
    ) AS ステータス
FROM orders;

まとめ:COALESCEでNULL処理をマスター!

今日学んだポイントを振り返りましょう:

COALESCE = 最初の非NULL値を返す関数 ✅ 基本構文COALESCE(値1, 値2, ..., デフォルト値)CASE文より簡潔に書ける ✅ 計算や文字列結合でNULL対策必須 ✅ 空文字とNULLは違うので注意 ✅ データ型を統一する必要がある ✅ インデックスへの影響を考慮

COALESCEは、PostgreSQLでNULL処理をする時の最強の味方です。

最初は「NULL処理って面倒…」と感じるかもしれません。でも、COALESCEを使いこなせば、エレガントで読みやすいSQLが書けるようになります。

今日から早速、あなたのSQLにCOALESCEを取り入れてみてください。NULL値に悩まされる日々から、きっと解放されるはずです!

Happy Coding!


次のステップにおすすめ:

  • PostgreSQLのNULL関連関数まとめ
  • CASE文の高度な使い方
  • PostgreSQLパフォーマンスチューニング

コメント

タイトルとURLをコピーしました