WITH CommentTree(comment_id, bug_id,parent_id, author, comment, depth)AS (SELECT *, 0 AS depth FROM CommentsWHERE parent_id IS NULLUNION ALLSELECT c.*, ct.depth+1 AS depth FROM CommentTree ctJOIN Comments c ON ct.comment_id = c.parent_id)SELECT * FROM CommentTree WHERE bug_id = 1234;アンチパターンを用いても良い場合共通テーブル式(CTE:common table expression)を使って再帰クエリを書ける場合
CREATE TABLE Bugs(bug_id SERIAL PRIMARY KEY,date_reported DATE NOT NULL,summary VARCHAR(80) NOT NULL,status VARCHAR(10) NOT NULL,hours NUMERIC(9,2),INDEX (bug_id),INDEX (summary),INDEX (hours),INDEX (bug_id, date_reported, status));インデックスショットガン(闇雲インデックス)闇雲にインデックスを定義してしまう
CREATE TABLE Bugs(bug_id SERIAL PRIMARY KEY,-- 他の列. . .assigned_to BIGINT UNSIGNED NOT NULL,hours NUMERIC(9,2) NOT NULL,FOREIGN KEY (assigned_to) REFERENCES Accounts(account_id));INSERT INTO Bugs (assigned_to, hours) VALUES (-1, -1);SELECT AVG( hours ) AS average_hours_per_bug FROM BugsWHERE hours <> -1;フィア・オブ・ジ・アンノウン(恐怖のunknown)NULLを嫌う → おかしなことに
58.
解決策: NULL を一意な値として使うSELECT* FROM Bugs WHERE assigned_to IS NULL;SELECT * FROM Bugs WHERE assigned_to IS NOT NULL;SELECT * FROM Bugs WHERE assigned_to IS NULL ORassigned_to <> 1;SELECT * FROM Bugs WHERE assigned_to IS DISTINCT FROM 1;IS DISTINCT FROM ならプリペアドステートメントにも使える!
59.
SELECT p.product_id,MAX(b.date_reported) ASlatest,b.bug_idFROM Bugs b INNER JOIN BugsProducts p USING (bug_id)GROUP BY p.product_id;アンビギュアスグループ(曖昧なグループ)MAX(date_reported)のbug_idが返るとは限らない
60.
解決策: 曖昧でない列を使うSELECT m.product_id,m.latest, MAX(b1.bug_id) ASlatest_bug_idFROM Bugs b1 INNER JOIN(SELECT product_id, MAX(date_reported) AS latestFROM Bugs b2 INNER JOIN BugsProducts USING (bug_id)GROUP BY product_id) mON b1.date_reported = m.latestGROUP BY m.product_id, m.latest;相関サブクエリ導出テーブルJOIN の使用などの手段がある(例は導出テーブル)
61.
SELECT * FROMBugs ORDER BY RAND() LIMIT 1;ランダムセレクション非決定性を持つ式によってソートを行ってしまう
62.
解決策: 特定の順番に依存しないSELECT b1.*FROMBugs AS b1INNER JOIN (SELECT CEIL(RAND() * (SELECT MAX(bug_id) FROMBugs)) AS bug_id) AS b2 ON b1.bug_id >= b2.bug_idORDER BY b1.bug_idLIMIT 1;様々なテクニック(例は欠番の穴の後にある番号を取得するSQL)
63.
SELECT * FROMBugsWHERE description LIKE '%one%';プアマンズ・サーチエンジン(貧者のサーチエンジン)あいまい検索にパターンマッチ述語を使用してしまう
SELECT p.product_id,COUNT(f.bug_id) AScount_fixed,COUNT(o.bug_id) AS count_openFROM BugsProducts pINNER JOIN Bugs f ON p.bug_id = f.bug_id AND f.status = 'FIXED'INNER JOIN BugsProducts p2 USING (product_id)INNER JOIN Bugs o ON p2.bug_id = o.bug_id AND o.status = 'OPEN'WHERE p.product_id = 1GROUP BY p.product_id;スパゲッティクエリ複雑な問題をワンステップで解決しようとする
66.
解決策: 分割統治を行う(SELECT p.product_id,'FIXED' AS status, COUNT(f.bug_id) AS bug_countFROM BugsProducts pLEFT OUTER JOIN Bugs f ON p.bug_id = f.bug_id AND f.status = 'FIXED'WHERE p.product_id = 1GROUP BY p.product_id)UNION ALL(SELECT p.product_id, 'OPEN' AS status, COUNT(o.bug_id) AS bug_countFROM BugsProducts pLEFT OUTER JOIN Bugs o ON p.bug_id = o.bug_id AND o.status = 'OPEN'WHERE p.product_id = 1GROUP BY p.product_id)ORDER BY bug_count DESC;ワンステップずつ解く必要なら UNION 等
CREATE TABLE Accounts(account_id SERIAL PRIMARY KEY,account_name VARCHAR(20) NOT NULL,email VARCHAR(100) NOT NULL,password VARCHAR(30) NOT NULL);INSERT INTO Accounts (account_id, account_name, email, password)VALUES (123, 'billkarwin', 'bill@example.com', 'xyzzy');リーダブルパスワード(読み取り可能パスワード)パスワードを平文で格納してしまう
<?php$project_name = $_REQUEST["name"];$sql= "SELECT * FROM Projects WHERE project_name = '$project_name'";↓http://bugs.example.com/project/view.php?name=O'Hare↓SELECT * FROM Projects WHERE project_name = 'O'Hare';SQLインジェクション未検証の入力をクエリにつなげて実行してしまう