本記事では、2025年7月28日に開催され、約1,500名が申し込んだイベント「t-wadaさんに聞く!SQLアンチパターン第2版 - 全27章まとめて紹介!」の内容をお届けします。イベントでは、書籍『SQLアンチパターン 第2版 ―データベースプログラミングで陥りがちな失敗とその対策』の出版を記念して、監訳者であるt-wada(和田卓人)さんをお招きし、本書の魅力についてご講演いただきました。ぜひ本編のアーカイブ動画とあわせてご覧ください。
t-wada:
本日は『SQLアンチパターン 第2版』という本についてお話しします。第2版も私が監訳しています。本日の講演では、先日の「Developers Summit 2025 Summer」で講演した内容をより厚く、各章の内容に踏み込んで説明します。
はじめに
時は2025年、世界はバイブコーディングの炎に包まれました。
AIと一緒にコードを書くにとどまらず、AI「が」コードを書く、システムを作る時代になり、多くの人が自然言語で指示してシステム開発をするようになってきました。皆さんもバイブコーディングやAIエージェントと一緒にコードを書くことを体験し、日々活用している方も多いと思います。
このバイブコーディングのトレンドには良いところもあれば、気をつけるべき点もあります。バイブコーディングを含むAIエージェントを使ったプログラミングは「投機的プログラミング」、揶揄して「ガチャ」とも呼ばれます。うまくいかなければ作り直しやコードを再生成する、そんな側面があります。
ですが、データベースはそうはいきません。UIは「作り直せば済む」場合もありますが、いったんデータベースに入ったデータは戻せません。将来に向けて一貫性のある構造でデータを保護・保持するにはどうするか、ここが難しいところです。
後からどうにかなるもの、挽回できるけど大変なもの、挽回できないもの――このあたりを見分けて温度差をつけていくのが、設計者としての腕が問われるところだと思います。
その意味で、データベース設計は後から挽回しにくいジャンルです。だからこそ、失敗しにくくするには他の人の失敗から学ぶのが近道です。すでに知られた落とし穴があるのなら、わざわざ同じ落とし穴に落ちにいく必要はありません。
そこで、落とし穴に名前を付けて回避していきましょう。これが『SQLアンチパターン』のテーマの1つです。
書籍『SQLアンチパターン』
まずはここに初版『SQLアンチパターン』の説明文を持ってきました。
本書はDB設計やSQL記述の際に避けるべき事柄を1章で1つ、25個紹介する書籍です。
リレーショナルデータベースを中心に据えたシステム開発には、様々な場面で陥りやすい失敗(アンチパターン)があります。
本書はデータベース論理設計、データベース物理設計、クエリの記述、アプリケーション開発という4つのカテゴリに分かれて、それぞれの分野におけるアンチパターンを紹介し、失敗を避けるためのより良い方法を紹介します。
このアンチパターン形式はとても優れていて、私自身も原書を読んで翻訳企画をオライリー・ジャパンに持っていきました。本書は2013年1月発売で、(2025年時点で)12年前になります。以来、ありがたいことに多くの方に評価いただき、読んでいただきました。
第2版の全体像
第2版(2025年7月発売)は48ページ増の全400ページ。新章が3つ加わり、多くの章末に短い「ミニ・アンチパターン」計15本が追加されました。
削減された章
実は減った章もあります。初版では、Active Record パターンがアンチパターン扱いされていました。第24章「マジック・ビーンズ」で、Rails/Laravelでよく使われるいわゆるActive Record パターンはアンチパターンである、と記されており、議論を呼びました。
ですが、第2版ではこの章はなくなりました。Active Record パターンはアンチパターンとは言い切れない、という扱いになったわけです。この12年で、Rails/LaravelなどのActive Record 系 ORMが設計のシンプルさで価値を発揮し、事業を支えてきた結果、“Active Record パターンの採用=アンチパターンではない”という整理に変わったと考えられます。
既存の章の改善内容と構成
既存の章も大幅改訂されています。本文もかなり改訂されていて、監訳時に原文の変更箇所をマークしていくのですが、その数は400〜450カ所ほどありました。コード例も、PHPからPythonに刷新されています。これは、Pythonを読める人のほうが多いと踏んでの変更ではないかと思っています。
大きな構成は初版から変わらず、4部構成です。論理設計、物理設計、クエリ、アプリケーション開発のアンチパターンという4つの部がメインパートです。
この本は『SQLアンチパターン』という書名なので、SQLの書き方のアンチパターンの本だと受け取る人が多いです。翻訳書はタイトルを変えられないので『SQLアンチパターン 第2版』としていますが、実はテーブル設計や物理設計、インデックス設計、SQLインジェクションやパスワード、スケールを伴うアプリケーションのアンチパターンも紹介しています。書名から受ける“SQLのアンチパターン”は、第3部のクエリのアンチパターンだけなんですね。
世の中で評価が最も高いのは、実は第Ⅰ部、データベースの論理設計のアンチパターンで、テーブル設計を間違うと後々まで痛手になる、という点を教えてくれていることが、多くの方に評価いただいた理由だと思っています。
第Ⅰ部 データベース論理設計のアンチパターン
第Ⅰ部、データベース論理設計のアンチパターンです。コードを書き始める前に、データベースに格納する情報を見極め、データの構成や関連付けを最善の方法で実現するための設計を行う必要があります。
12年で大きく変わった「ナイーブツリー」の内容
第2章の「ジェイウォーク」=交差テーブルを避けてカンマ区切りで入れてしまう、第3章の「ナイーブツリー」=ペアレントIDを持つ隣接リストでツリー構造を格納してしまう、といった章があります。ちなみに「ナイーブツリー」については、12年前の当時はCTE(Common Table Expression)による再帰SQLは標準化こそされていたものの、実装状況はベンダーごとにまちまちでした。そのため、ペアレントIDを持つ隣接リストによるツリー格納はアンチパターンとされていましたが、いまではほとんどのリレーショナルデータベースがCTEを実装しているため、第3章「ナイーブツリー」の内容は大きく更新されています。
かつては隣接リストはアンチパターンだったものの、CTEの普及により、現在は必ずしもアンチパターンとは限らない、という記述になっています。12年で大きく変わった点ですね。ほかにもIDや外部キーの扱いなどをこの章で取り上げています。
さらに、アンケートフォームのように自由度の高い、実行時に属性が決まる高自由度の設計をどう扱うか(第6章)。年ごとにデータが増えるからとテーブル名に年を付けてテーブルを分割してしまう(第9章)――よかれと思ってドツボにはまるケースなど、こうしたテーブル設計のアンチパターンが多数並んでいるのが第Ⅰ部です。
第Ⅱ部 データベース物理設計のアンチパターン
第Ⅱ部は物理設計のアンチパターンです。つまり、テーブルやインデックスの定義、データ型の決定、そういったところが関わっています。
ファイルをリレーショナルデータベースに格納すべきか?
第10章「ラウンディングエラー」は簡単に言うと、小数をデータベースに格納するためにFLOAT
やDOUBLE
を用いるアンチパターンです。よくやってしまいがちですが、これらはいわゆる浮動小数点数型なので正確な計算、特にお金の計算には全く向いていない。なので、10進数型を使いましょう、という話です。
第12章は「ファントムファイル」。画像ファイルやデータファイルをリレーショナルデータベースに入れるかどうか、という話です。入れない人が99%だと思いますが、著者は入れる派なんですよね。これにはメリット・デメリットがあります。「画像や動画は外置き」が前提になっていないかを点検するチェックリストとして読める章です。ちなみに、ここは監訳していてもいちばんモヤモヤする章です。
ただ、DBに入れておけばできることが、外に置くとできなくなることもあります。その失われる機能に対して対策はありますか? 例えばバックアップ、データ一貫性、トランザクションなど。そういったことを考えるきっかけになる章です。
第13章「インデックスショットガン」は、インデックスを貼ると速くなるからと、むやみにたくさん定義してしまう、という話です。
第Ⅲ部 クエリのアンチパターン
第Ⅲ部はクエリのアンチパターン。これは書名からもイメージがしやすい、SQLを書く際のアンチパターンです。SELECT
文のアンチパターンが一番多いですね。
NULLの扱いをきちんと理解しよう
第14章「フィア・オブ・ジ・アンノウン」はNULL
の扱いの章です。SQLのNULL
は、プログラミングのNULLの扱いよりもさらに込み入っています。多くのプログラミング言語が二値論理で評価されるのに対し、SQL は三値論理のため、NULL
はただの「値」ではありません。ここから多様な不具合が生じます。
とはいえ、LEFT JOIN
を使えばNULL
は必ず出てきてしまいます。テーブル/カラムの定義レベルでNULL
がなくても、クエリレベルでは結局出てくる。だからこそ、NULL
の扱いに慣れ、きちんと理解しましょう、というのが第14章の趣旨です。
第15章「アンビギュアスグループ」は、MySQLなどでGROUP BY
の扱いが“よしなに”されてしまい、バグを生むアンチパターン。第16章「ランダムセレクション」は、SQLの結果からランダムに1行をピックアップしたい時に、ORDER BY RAND()
を使うとレコード数が多い場合に大変なことになる、という話です。
第18章「スパゲッティクエリ」。プログラミングの世界では、関数を小さく切り分け、単機能のオブジェクトやクラスを組み合わせて複雑さに対処しますが、SQLになると突然「1個のSQLで一撃で片付けよう」という価値観になりがちです。一回で動くSQLクエリが至高のようになりがちで、そうすると1000行超の込み入ったクエリが出来上がってしまう。そういったことはやめよう、という話が第18章に書いてあります。
第19章「インプリシットカラム」は、本番でSELECT *
を使わないようにしよう、ワイルドカードのアスタリスク(*)は避けましょう、という内容です。
第Ⅳ部 アプリケーション開発のアンチパターン
SQLとデータベースだけではシステムにならず、他のプログラミング言語で開発されたアプリケーションと共に使用されます。その際に生じる失敗や落とし穴もたくさんあります。そうしたよくある失敗例を紹介していきます。
リーダブルなパスワードはやめよう
第20章「リーダブル・パスワード」は、リーダブル・コードはいいけど、リーダブル・パスワードはダメだよね、という話です。本来このような章はないほうがいいと思いますし、そもそもパスワードをデータベースに格納する時代ではないと私は思っています。それでも落とし穴に落ちる人がいるので、やはり第20章は必要です。
第21章は「SQLインジェクション」。いまだに脆弱性の横綱です。第22章には、いわゆる疑似キー(サロゲートキー)が連番でない、DELETEで連番が崩れるのが気になる、みたいな話も書かれています。
この部に新規書き下ろしの章が1つ入っています。第25章「スタンダード・オペレーティング・プロシージャ」では、バックエンドのロジックをストアドプロシージャで記述するのは、もうアンチパターン、という話です。Active Recordはアンチパターンではなくなりましたが、その代わりにストアドプロシージャが指定されました。さらに深掘りすると、“これまでそうしてきたから”という理由で開発標準を更新せず守り続けるのはやめよう、というメタなアンチパターンでもあります。
このタイトル自体も、ダブルどころかトリプルミーニング。訳は「さびついた開発標準」としました。procedureは「手続き」と「ストアドプロシージャ」、standardは「標準」など、いろいろ含意があります。
ここまでが、本書の骨組みである4部構成です。
第Ⅴ部 ボーナス:外部キーのミニ・アンチパターン、付録
追加で第Ⅴ部と付録があります。第Ⅴ部はボーナス編、外部キー(FOREIGN KEY)に関するミニ・アンチパターン集です。
外部キーの誤った使い方
著者のビル・カーウィンは、各所で外部キーに関する質問に数多く答えてきました。外部キーの定義方法や考え方、使い方の「よくある傾向」をコンサルタントとして熟知しており、そういった標準SQLの範囲で見られる外部キー誤用を第26章で扱っています。さらに、MySQLには外部キーが後から入った経緯がありMySQL特有の落とし穴も多いため、第27章で「MySQLにおける外部キーの誤った使い方」として独立させています。
付録A「正規化のルール」もあります。初版に続き、第2版にも収録されています。
正規化は、実はきちんと学んだことがない人も多いんですよね。現場ではなんとなく第三正規形にしているパターンが多い一方、改めて学ぼうとすると関係代数など数学の世界に入り、これがなかなか難しい。正規化に関するちょうどいいバランスの解説は意外と書籍に少ないのですが、この付録はちょうどいいレベルでまとまっています。本書のサンプルを使い、具体例で説明しているので、二重三重にわかりやすい付録です。社内読書会でも評価が高かったので、「付録A 正規化のルール」は“ただの付録”ではない、という目線でご覧いただければと思います。
章末に追加された、ミニ・アンチパターン
さらに、いくつかの章の末尾に、「ミニ・アンチパターン」という節が追加されています。ここでは新たな「よくある失敗」を簡潔に取り上げ、それを避けるための対処策も紹介しています。これらのミニ・アンチパターンはすべて新規書き下ろしで、各章末に収録しています。ぜひこちらも楽しみにしていただければと思います。
アンチパターンの具体例
ここからは、本書から1つだけサンプルの章を取り上げ、アンチパターンの具体例がどのように書かれているかをご覧いただきたいと思います。本書はパターン本なので、イントロダクションの章を除き、各章は同じ構成です。目的/アンチパターン/アンチパターンの見つけ方/アンチパターンを用いてもよい場合/解決策/ミニ・アンチパターン、という並びになっています。
ほぼすべての章がこの節構成になっていますが、もう1つ大事なことがあります。パターンであるからには名前が大事です。短い名前だけで意思疎通ができるからパターンなんです。デザインパターンも、名前だけで設計意図や背景、解決策が共有できるからこそ、ソフトウェアの歴史に大事な足跡を残してきたわけです。だから本書も、あえて翻訳せずカタカナ語のままにしています。
ジェイウォークのアンチパターン例
パターン名の役割:人に、そしてLLMに短い言葉で伝わる
第2章「ジェイウォーク(信号無視)」をサンプルとしてご覧ください。パターン名は人に伝わるだけでなく、これからの時代はLLMにも短い言葉で伝わることが大事です。たとえばClaudeに「ジェイウォーク・アンチパターンとは? その解決策は?」と質問すると、すでに学習済みの知見として、どんなもので、どんな解決策があるかが返ってきます。AIとその知見をやり取りできる、という話ですね。名前を覚えれば、背景や解決策で避けるべき問題点、あえてアンチパターンを踏んでもよい場合まで想起できる、というわけです。
アンチパターンの目的
アンチパターンには目的があります。タスク解決のために用いた手法が、かえって多くの問題を生じさせてしまうもの、と著者のビル・カーウィンは説明しています。平たく言うと、よかれと思ってやった解決策が裏目に出る――これがアンチパターンです。
たとえばジェイウォーク・アンチパターンの例では、開発者はバグ管理アプリの新機能を開発中で、製品の連絡窓口としてユーザーを1人指定する機能を作ります。初期仕様では各製品の連絡先は1人だけ。それならば、製品(プロダクト)テーブルに連絡先ユーザーのIDを外部キーとして持たせる。よくありますよね。ところが案の定、後から「1つの製品に複数ユーザーを連絡先として登録したい」という要望が出てきます。あるあるですよね。「なるべく短納期で」「UIの変更は最小限で」といったおまけがつくと、どうなるでしょうか。
最初は簡単だと思い、アカウントID(account_id
)を1件のみではなく、カンマ区切りのリストで複数格納できるように変更しました。そうはならんやろ、と思うかもしれませんが「1日でやって」「UI変更最小限で」と続くと、つい「カンマで入れるか」となってしまう。ありがちですよね。
アンチパターン
では、アンチパターンとはどんなものでしょうか。よく用いられる解決策が、その性質ゆえに予期せぬ結果を招き、かえって問題を増やしてしまう――その経緯を解説します。変更前のテーブル定義は想像どおり。プロダクトテーブルがあり、アカウントIDを外部キーで持っています。
では進めてみましょう。ここからがアンチパターンです。アカウントIDの型をVARCHAR
に変更し、カンマ区切りで複数のIDを格納できるようにしました。テーブル定義の変更を最小限に、よかれと思ってやりがちですよね。リレーショナルデータベースを知っている人なら突っ込みたくなりますが、知識や時間がないと、ついこうしてしまうのです。
何が起こるか。account_id=12
が関わる製品を検索するには単語境界を使った正規表現などに頼らざるを得ず、インデックスが効きません。
では、集約クエリの作成。つまり製品ごとに何人が関連付いているか? これをカンマ区切りの文字列からどう計算するのか、となると対応が難しくなってきますよね。
例えば「カンマ区切りの文字列の長さ − カンマを除いた文字列の長さ」でカンマの数を数え、それが人数なんだ、と考えるとバグになります。正しくは、その差にプラス1したものが関連している人数です。 著者のビル・カーウィンは、さまざまなひどい設計のデータベースを見てきたぶん、アンチパターン解決のテクニックに長けていますが、本来はそんなテクニック必要ないよ、という話なんです。
次は、特定の製品に関するアカウントの更新。コードを書かざるを得なかったり、アカウントIDのバリデーションが難しいですよね。
区切り文字はカンマですが、データにカンマが含まれる場合どうする?という問題は避けられません。データの中に入り得ない記号を区切り文字にしたい。けれど、その“入り得ない記号”を探さなきゃいけない、みたいなことがありますよね。 あとはリストの長さの制限です。アカウントIDの長さ次第で、関連付けられる人数が左右されます。初期メンバーはIDが2桁で多く入る一方、最近登録のユーザーは桁数が増えて付与できる人数が減る、といった制約が生じます。
「アンチパターンの見つけ方」
では、次の節「アンチパターンの見つけ方」です。各章のこの節が、かなり面白いです。
プロジェクトでアンチパターンが使われているとき、それを察知する手掛かりがあります。直面している問題の種類よりも、メンバー同士の何気ない会話にヒントが潜んでいます。チームの発言に「むむ…」と引っかかるキーワードが出てくるわけですよね。
例えばジェイウォーク・アンチパターンなら、こんなキーワードです。
「このリストでサポートしなければならない最大のエントリ数は?」
→ なぜ“最大”が気になるのか?
「SQLで単語境界一致させる方法、知ってる?」
→ 単語境界の正規表現を書こうとしているな。
「リストの要素に絶対使われない文字ってなんだっけ?」
→ 区切り文字を設計しようとしていて、嫌な予感がする。
こうした何気ない会話の中のキーワードからピンときて、「あ、何か悪いことが起ころうとしてるな」と心に黄色信号をともす。そんな感覚を持てるとよい、という話です。
「アンチパターンを用いてもよい場合」
次の節は「アンチパターンを用いてもよい場合」です。ルールには例外がつきものです。通常はアンチパターンでも、状況によっては適切だったり、打てる手の中で最善の場合もある。つまり、アンチパターン=絶対悪ではありません。
例えば、カンマ区切りのジェイウォークアンチパターン。非正規化の一環として採用するケースもあります。JSON型や配列型の利用も選択肢ではありますが、採用は十分に検討する必要があります。まずは正規化されたテーブル構造を検討し、どうしても難しい部分だけを限定的に非正規化しましょう、という話です。
ということで、この本は単なる「べからず集」ではありません。アンチパターン=「絶対そうするな」という本ではなく、パターン本です。つまりコンテキストがあります。コンテキストや制約が異なれば、導かれる解法も異なるわけです。
解決策
では、このジェイウォーク・アンチパターン、本来はどうすべきだったのでしょうか? アンチパターンに陥らないように、冒頭のタスクを解決する望ましい方法を紹介します。
最初の章だけあって単純です。プロダクトとアカウントが一対一から多対多になったなら、間に交差テーブルを入れましょう。基本中の基本ですが、ここからいきましょう。
交差テーブルContacts
を作成し、product_id
とaccount_id
を外部キーにします。参照整合性制約を設定し、組み合わせをINSERTしていく――王道ですが、王道がとても大事です。
何がうれしいか。特定のアカウントに関連する製品、特定の製品に関連するアカウントの検索が普通にできます。正規表現はもういらない、インデックスも効きます。JOIN
してWHERE
に指定するだけです。集約クエリも簡単で、もうカンマの数+1を数える必要はありません。シンプルに COUNT
すればいいんです。もともと目指していた世界です。
特定の製品に関するアカウントの更新(UPDATEやDELETE)も簡単です。交差テーブルにINSERT
すれば関連付けが生まれ、DELETE
すれば解消される。動作モデルが単純になります。
区切り文字の選択や、リストの長さの制限も不要です。行として個別に格納されるので区切り文字を気にする必要はありませんし、関連は交差テーブルに入るだけ追加できます。
ミニ・アンチパターン
いくつかの章末に「ミニ・アンチパターン」という節を追加しました。新たな「よくある失敗」を簡潔に取り上げ、避けるための手っ取り早い対処策も紹介しています。たとえば「CSV列を複数の行に分割する」。
ジェイウォーク・アンチパターンを使ってしまっていると、横持ちのカンマ区切り列を、あたかも交差テーブルのように縦持ちへ変換したくなります。WITH RECURSIVE の登場以降、カンマ区切りの列を再帰SQLで複数行に“見せかける”トリックが使えるようになりました。ビル・カーウィンもこうしたテクニックを示していますが、かっこいい反面、本来は不要です。ひどい設計をしているから超絶技巧でリカバーせざるを得ないのであって、交差テーブルを使っていれば不要。だからバッドパターン、すなわちアンチパターンなんですよね。カンマ区切り列を再帰SQLで複数行に見せかけるのは、本来やらなくていいアンチパターンです。
このミニ・アンチパターンの節には再帰SQLだけでなく、いろいろな超絶技巧も登場します。訳していて「すごいな」と思う一方で、「このすごさは必要ないな」とも思わせる、味わい深い内容です。
結論
第2章、ジェイウォークの結論ですが、1つ1つの値は個別の行と列に格納し、交差テーブルを使いましょう。普通ですが、普通が一番強いです。
愚者は経験に学び、賢者は歴史に学ぶ
ドイツの“鉄血宰相”ビスマルクの言葉で、「愚者は経験に学び、賢者は歴史に学ぶ」はよく引用されますよね。ただ、この日本語訳は意訳で、原文はもう少し長く、ニュアンスに富んでいます。
多くの人は自分の痛い経験から学ぶと考えがちですが、ビスマルクは、他人の失敗から先に学び、自分の失敗を回避するほうを好むと言っています。これは、この本のテーマそのものだと思うわけです。リレーショナルデータベースの落とし穴に落ちると痛いんですよね。リカバリーが難しいからです。
だからこそ、多くの人がよかれと思って落ちがちな穴に名前を付け、きちんと回避できるようになりましょう、というのが本書の大事なテーマです。本書の素晴らしいところは、よくある悪い方法を、悪いこととして名前を付けて整理してくれた点です。
この本は、実は社内勉強会にすごく向いています。理由は少なくとも2つあります。
ひとつ目は、この本は前から順番に読まなくていいんです。各章が独立しており、順不同で読める構成です。社内読書会あるあるとして、最初は人数が多く希望に満ちて進むのですが、各チームで忙しい時期があると欠席が続き、途中の1,2章を抜けると読書会への復帰が難しくなりがち。ですがこの本は各章が独立しているので途中離脱後の復帰もしやすい、というのがまず向いている点です。
もう1つ、この本は失敗に関する本です。社内ならではの失敗共有ができます。データベースの失敗はすごく痛いからこそ社外には出しにくいですよね。でも社内なら、ノウハウや「良かれと思ってこう設計してこういう目に遭った」を共有できる。『SQLアンチパターン』を読むと、「ズバリこのアンチパターン、我々(自社)も踏んでいた」といった議論ができ、本物のデータベース設計につながります。これはものすごく学びになります。社内ならではの失敗共有ができる、これも社内読書会に向いている理由です。
私の講演はこれで終わりです。ぜひ皆さんも第2版をお読みいただいて、社内外の知見共有に生かしていただければと思います。ご清聴ありがとうございました。
アーカイブ動画・発表資料
イベント本編は、アーカイブ動画を公開しています。また、当日の発表資料も掲載しています。あわせてご覧ください。
▼動画・資料はこちら
※動画の視聴にはFindyへのログインが必要です。