はじめに
こんにちは。小川です。
以前から気になっていた「SQLアンチパターン」という本を読みました。
とても良い本で今まで気づかずにやっていたことがSQLアンチパターンなんだと知ることができました。
勉強になったので、学んだことを言語化して量が多いのでいくつかの記事に分けて紹介していこうと思います。
今回は第Ⅲ部のクエリのアンチパターンについて記載します。
この記事は「SQLアンチパターン」の気づき(クエリ2)の続きになります。
17章.スパゲッティクエリ
アンチパターン:複雑な問題をワンステップで解決しようとする
SQLは非常に表現力に優れた言語で、1つのクエリで全てのタスクを処理することもできます。
だからと言って、SQLは一つのクエリで全てを解決することを強制しませんし、ときにはそれが良くないアイデアである場合もあります。
デメリット
1つのクエリですべてを処理しようとすると以下の問題があります。
- デカルト積が生じてしまうことがある
- 2つのテーブルがあった場合、それらのテーブルから総当たりとなる集合をデカルト積と呼びます
- デカルト積は2つのテーブルの関係を定義しない場合、つまりWHERE句やJOIN句での結合の失敗時に発生しやすい
- 1つのクエリで全てを処理しようとする試みは、複数のテーブルの関係が曖昧になり、意図しない結合に発展しやすい
- クエリの記述や修正、デバックが難しくなる
解決策:分割統治を行う
- ワンステップずつ
- 意図しないデカルト積が生じているテーブル間に論理的な結合条件が見つからない場合、単に条件がそもそも存在しない可能性もある。デカルト積を避けるには、幾つかのクエリに分割する必要がある
- UNIONを用いる
- 複数のクエリの結果は、UNIONによって一つの結果セットにまとめることができる
- 各サブクエリの結果を合わせたものが、クエリの結果として出力される
- UNIONは、両方のサブクエリの列に互換性があるときにのみ使用できる
- CASE式とSUM関数を組み合わせる
- 条件ごとの集約を一つのクエリでシンプルに行うために、CASE式とSUM関数を組み合わせる方法がよく使われる
- SQLを用いたSQLの自動的な記述
- 複雑なSQLクエリを分割すると、データの値によってわずかに異なる、似たようなクエリをいくつも作成することがある
- コード生成 (コンパイルや実行が可能なコードをコードから出力する技法) を用いれば、そういった繰り返しを避けることができる
18章.インプリシットカラム(暗黙の列)
アンチパターン
インプリシットカラムとは、暗黙的な列指定を不適切に使用したために問題が発生するアンチパターンです。
プログラマーが入力を手間だと感じる例の1つが、SQLクエリですべての列名を指定することです。
SELECT bug_id, date_reported, summary, description, resolution,
reported_by, assigned_to, verified_by, status, priority, hours
FROM Bugs;
ワイルドカード(*)を使うとクエリを簡潔に書くことができます。
SELECT *
FROM Bugs;
また、INSERT文を書く際も、列を明示的に指定せずに省略できます。
-- Before
INSERT INTO Accounts (account_id, account_name, first_name, last_name, email,
password_hash, portrait_image, hourly_rate) VALUES (DEFAULT,
'bkarwin', 'Bill', 'Karwin', 'bill@example.com', SHA2('xyzzy', 256), NULL, 49.95);
-- After
INSERT INTO Accounts VALUES (DEFAULT,
'bkarwin', 'Bill', 'Karwin', 'bill@example.com', SHA2('xyzzy', 256), NULL, 49.95);
しかし、ワイルドカードや暗黙的な列の指定には様々な問題があります。
問題
- リファクタリングにおける問題
- 先ほどのBugsテーブルにdate_dueカラムを追加します
ALTER TABLE Bugs ADD COLUMN date_due DATE;
- そうすると、VALUESの列数が一致しないためINSERTでエラーを返すようになります
- 隠れた代償
- ワイルドカードはパフォーマンスにも悪影響を及ぼします
解決策:列名を明示的に指定する
ワイルドカードを使わず、明示的に列を書きましょう。
SELECT bug_id, date_reported, summary, description, resolution,
reported_by, assigned_to, verified_by, status, priority, hours
FROM Bugs;
INSERT INTO Accounts (account_name, first_name, last_name, email,
password_hash, portrait_image, hourly_rate)
VALUES ('bkarwin', 'Bill', 'Karwin', 'bill@example.com',
SHA2('xyzzy', 256), NULL, 49.95);
まとめ
今回は第Ⅲ部のクエリのアンチパターンについて一部まとめてみました。
この記事はこの本を読んで、私がこういう理解をしましたということを言語化してみました。
とても勉強になる本なので皆様もぜひ読んでみてください。
以上、小川でした。