テック技報最新号へ →
コード

Postgres: 空文字は NULL ではない。count(nullif(trim(col), '')) で「値あり」だけ数える

count(col) が飛ばすのは NULL だけで、'' や空白だけの文字列は「値あり」として数えられる。空文字を NULL に寄せてから数え、古い行も正規化しておく。

@amitsinha__07 · 投稿 2026-11-02
#postgres#sql#null#集計#データ品質

何が起きるか

書き込む側が「不明」を NULL ではなく '' で入れていると、集計がずれる。

  • count(country) が、空欄の行まで「入力済み」に数える。入力率のダッシュボードが実際より高く出る。
  • GROUP BY country で、NULL のグループと '' のグループが別々にでき、「不明」が2つの行に分かれる。' '(空白だけ)が混ざるとさらに増える。

やり方

集計では空白を落としたうえで空文字を NULL に変えてから数える。

sql
-- 値が入っている行だけ数える
SELECT count(nullif(trim(country), '')) AS filled,
       count(*)                          AS total
FROM users;

-- 「不明」を1つのグループにまとめる
SELECT coalesce(nullif(trim(country), ''), '(不明)') AS country,
       count(*)
FROM users
GROUP BY 1;
  • nullif(a, b) は a と b が等しければ NULL、違えば a を返す。
  • trim() で前後の空白を落とすので、' ' も空文字として扱える。

根本対策(既存データと今後の書き込み)

クエリごとに nullif を書くのは漏れやすいので、データそのものを揃える。

sql
-- 既存の行を NULL に寄せる(大きなテーブルは範囲を区切って少しずつ)
UPDATE users SET country = NULL WHERE trim(country) = '';

-- 今後 '' が入らないようにする
ALTER TABLE users
  ADD CONSTRAINT users_country_not_blank
  CHECK (country IS NULL OR trim(country) <> '') NOT VALID;
ALTER TABLE users VALIDATE CONSTRAINT users_country_not_blank;

制約を2段階で足す理由は Postgres: 大きなテーブルへの CHECK / 外部キーの追加は NOT VALID と VALIDATE の 2 段階に分ける を参照。

注意点

  • trim() が落とすのは既定では半角スペースだけ。タブや改行、全角スペースも空とみなすなら btrim(col, E' \t\r\n ') のように文字を指定するか、regexp_replace を使う。
  • 「空文字」と「未回答(NULL)」を意味として区別しているテーブルでは、まとめてはいけない。先に仕様を確認する。
  • Oracle は空文字を NULL として扱うが、Postgres・MySQL・SQLite は区別する。DB を移したときにこの違いで集計が変わることがある。
  • NULL の扱いの別の落とし穴は Postgres / SQL: サブクエリの NOT IN は NULL が1つあると0件になる。NOT EXISTS を使う。

出典

X の元の投稿を開く