The following two tabs change content below.
アバター画像

メンバー

福岡市博多区でWebシステム開発の受託・ラボ型SES・web集客サービス、3つのWebサービスを提供中

業務システムを触っていると、テーブルは増える、仕様は変わる、ロジックは継ぎ足される、
その結果「なんでこのSQLこんなに重いの???」と腕を組む瞬間、ありませんか?

最初は誰かの応急処置だったSQLが、気づけば業務ロジック付きSQLに成長していたり、ORが継ぎ足されすぎてインデックスが効かなくなったり、VIEW が多段化して誰も正体を追えなくなったり。。。

実際に現場のSQLは、「とりあえず動くから」「この記法のほうが早いと思ったから」「プログラムでも書けるがSQLで処理を書いてしまおう」など小さな判断(大体は善意が込められている)が積み重なって、結果的に負債化していきます。

そして、SQLを夢中で書いている時に、これから起こりうる問題について初期段階では気づきづらいものです。

数万件のデータの操作が軽快に動いていても、数百万、数千万に膨れたときに、レスポンスが何倍にも遅くなることも珍しくありません。

だからこそ避けたいのが、その場しのぎで動くけど、未来で壊れるSQLーーー

つまりSQLアンチパターンです。

この記事では見落とされがちなSQLアンチパターンを厳選し、危険な罠をどう解決していくかを解説していきます。

なぜ業務システムはSQLアンチパターンに陥りやすいのか?

業務システムのプロジェクトに携わっている方はお分かりだと思いますが、数多くの部門だったり領域が絡んできます。例えば「受発注」「在庫」「会計」「製造管理」などなど。

さらに、仕様なんてものは一発で決まることはほぼほぼ無く、仕様変更が頻繁に発生します。
その結果、開発者は「既存 SQL に少し手を加えるだけで済ませたい」という心理になりがちです。

しかし、この場当たり的な修正こそが SQL アンチパターンの原因となってしまうのです。

いくつか例を↓に記載しておきます。

・WHEREの条件が増え続ける

UNIONが継ぎ足される

CASE WHENが複雑化する

このようにその場しのぎの積み重ねで「気づけば1,000行のクエリになっていた」というケースは、業務システムに携わっていると結構目の当たりにします。

パフォーマンスと可読性のトレードオフが生む罠

SQL において、「速く動くかき方」と「読みやすい書き方」は必ずしも一致しません。(むしろ両立は難しい)

そのため、開発者は短期的なパフォーマンス改善を優先し、読みやすさ・責務分離・将来の変更容易性を犠牲にしがちです。

結果として、

・改修時に誰も読めない

業務ロジックの所在が不明

・表示やAPI側のコードと分離不能

といった長期的な負債が積み上がります。

現場で最も多発する SQLアンチパターン5選

では実際に業務システムで特に発生頻度が高い 5つのアンチパターンを解説していきます。

ロジック入りSQL(Fat SQL)の地獄化

もっとも多いのが 業務ロジックが SQL 側に吸収されてしまうパターンです。

たとえば、こんな巨大 SQL を見たことはありませんか?

SELECT
    CASE 
        WHEN o.status = '1' THEN '出荷準備'
        WHEN o.status = '2' THEN '出荷済'
        WHEN o.status = '3' THEN '納品完了'
    END AS STATUS_LABEL,
    ...
FROM ORDERS o
LEFT JOIN CUSTOMERS c ON o.customer_id = c.id
WHERE
    o.deleted_flg = 0
    AND (o.status IN ('1','2','3'))
    AND (
        (o.is_urgent = 1 AND o.shipped_date >= CURRENT_DATE - INTERVAL '7 DAY')
        OR
        (o.is_urgent = 0 AND o.shipped_date >= CURRENT_DATE - INTERVAL '30 DAY')
    )
UNION
SELECT ...
UNION
SELECT ...

現場でよくある典型的な Fat SQL の特徴は、

CASE WHEN で業務ロジックが埋まり、変更が非常に難しい
UNION が増えすぎてどれが本体の処理か分からなくなる
・アプリ側で持つべき判定を SQL が抱え込む
・結果的に SQL とアプリの責務が曖昧化する

SQL は「データ抽出のため」の言語であり、「業務ロジックの中心を置く場所でない」と肝に銘じて起きましょう。

無限OR条件とインデックス全無視問題

以下のような SQL を書いたことはありませんか?

WHERE
    status = 'Open'
    OR status = 'Delivered'
    OR status = 'Ready'
    OR status = 'Suspended'

OR が増えるほど PostgreSQL はインデックスを使わなくなり全件スキャンするケースが多発します。

業務システムではステータス種別が増え続けるため、「とりあえず OR を追加して対応する」という行為が負債化しがちです。

改善の基本は、

・IN句を使って書き換える
・ステータスを ENUM や別テーブルで管理
・業務ロジック側で分類処理を行う

など、SQL の責務をシンプルに保つことです。

カンマ区切りや JSON を1列に詰め込む“非正規化の暴走”

業務システムでは「柔軟に項目を増やしたい」という要求が強く、そのために 1カラムに複数値を詰めるアンチパターンがよく発生します。

例:

'A,B,C' のような CSV
・JSON を1行に詰める
・配列文字列を LIKE 検索で扱う

しかしこれは、

インデックスが効かない
JOIN できない
・データの正当性を保てない

といった、極めて重大な問題を引き起こします。

柔軟性よりも 正規化・テーブル分割を優先することが長期的な安定運用の鍵になります。

VIEW・UNION乱用によるブラックボックス化

業務システムでは「表示のためのVIEW」を作る文化が強く、そのうち VIEW → VIEW → VIEW という多重構造が誕生します。

すると、

元データを追えない(追うのに時間がかかる)
・結合構造が不明
・WHERE を加える度に超巨大化
・実行計画が読めなくなる

特に PostgreSQL では VIEW が生クエリ展開されるため、元の SQL が複雑だとパフォーマンスが大きく劣化します。

SELECT * と曖昧な JOIN によるスキーマ依存の破壊

以下が典型例となります。

SELECT *
FROM orders o
JOIN customers c ON o.customer_id = c.id

一見シンプルですが、

・列追加するとアプリ側が壊れる
・JOIN した全カラムを無駄に返す
・ネットワーク負荷が増える
・SQL の可読性が下がる

特に業務システムでは列数が多く、SELECT * は非常に危険です。(一つ一つ列数を記載するのは面倒なので”*”でまとめたくなる気持ちは痛いほどわかりますが。。。)

さらに、曖昧な JOIN(キーの方向が不明確)もデータ不整合や重複行の原因となります。

アンチパターンの紹介はここで終わりますが、ほかにも気になる方がいれば以下の文献を参考にしてもいいかもしれません。

SQLアンチパターンの書籍はこちら

SQLアンチパターンを避けるための実践的アプローチ

最初の方でもお話しましたがアンチパターンは、多くの場合悪意ではなく善意から始まります。

「とりあえず動かしたい」「既存を壊したくない」「時間がないから応急処置で…」そんな小さな理由から、少しずつ問題が積み重なっていきます。

でも、それらの積み重ねが長期的な負債になり、最終的には自分たちが苦しむ未来を作り上げてしまうんですよね。

だからこそ、SQLはできる限りシンプルに、役割を明確にし、ロジックとデータ抽出の責務を分けて書くことを意識する。

そして何より「このSQL、半年後の自分が見ても理解できるか?」を常に問いながら書くことが大切です。

SQLはシステムの動脈みたいなもので、ここが詰まるとアプリ全体が動かなくなります。逆に言えば、ここを健全に保てればシステムは驚くほど安定します。今日できる小さな改善を積み重ねることで、未来の自分もチームも確実に楽になります。

「動けばいいSQL」ではなく、「未来の自分に感謝されるSQL」を、ぜひ一緒に目指していきましょう。