Successfully reported this slideshow.
Your SlideShare is downloading. ×

SQLアンチパターン読書会 「スパゲッティクエリ」

Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Loading in …3
×

Check these out next

1 of 20 Ad

More Related Content

Similar to SQLアンチパターン読書会 「スパゲッティクエリ」 (20)

Recently uploaded (20)

Advertisement

SQLアンチパターン読書会 「スパゲッティクエリ」

  1. 1. SQLアンチパターン読書会 17章:スパゲッティクエリ 2014/4/3(木) @makopi23
  2. 2. イメージ 複雑に絡み合い、解読や修正が困難なSQLクエリ
  3. 3. 3  17.1 目的: SQLクエリの数を減らす  SQLプログラマーが最も多く直面する問題 どのようにして目の前の仕事を1つのクエリで実現するか  解決策はシンプルにしたい タスクを1つのクエリで解決することで、クエリを「優雅に」 「効率的に」書くことを目的とする。
  4. 4. 4  17.2 アンチパターン  SQLは非常に表現力に優れた言語・・・ ➢ 1つのクエリやステートメントで多くのことを実現できる ➢ だからといって、1つのクエリですべてのタスクを処理 することを強制するものではない 複雑な問題をワンステップで解決しようとする複雑な問題をワンステップで解決しようとする
  5. 5. 5  17.2.1 意図に反した結果 (1/5) ● デカルト積 (Cartesian product) ➢ クエリで指定する2つのテーブルが関連(リレーション シップ)を制限する条件を持たないときに生まれる。 ➢ この条件がないと、2つのテーブルを結合することに よって、1つのテーブルの各行が、もう1つのテーブルの すべてのテーブルのすべての行とペアになってしまう。 ➢ いわゆる、直積集合 (Cross Join)。
  6. 6. 6  17.2.1 意図に反した結果 (2/5) 同じテーブル2つに対し、 prodcut_idをキーにJOIN・・・ ⇒ 直積(デカルト積)が発生 その製品の修正済みのバグ数が11件、 未修整のバグが7件であるはずなのに、 両方とも 77件 (= 11件 × 7件)となっている。 ■ Spaghetti-Query/anti/cartesian.sql
  7. 7. 7  17.2.1 意図に反した結果 (3/5) SELECT p.product_id, COUNT(f.bug_id) AS count_fixed, COUNT(o.bug_id) AS count_open FROM BugsProducts p INNER 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 = 1 GROUP BY p.product_id; FROM BugsProducts p INNER JOIN Bugs f ON p.bug_id = f.bug_id AND f.status = 'FIXED' INNER JOIN Bugs o ON p2.bug_id = o.bug_id AND o.status = 'OPEN' INNER JOIN BugsProducts p2 ●BugsProducts p ●Bugs f ➢ bug_idで結合 ➢ statusが'FIXED' ●BugsProducts p2 ●Bugs o ➢ bug_idで結合 ➢ statusが'OPEN' ■ Spaghetti-Query/anti/cartesian.sql FIXEDとOPEN のバグの組み合 わせを制限する 条件なし。。。
  8. 8. 8 11行の修正済み(FIXED)バグが、 7行の未修整(OPEN)バグと すべてペアに 11行の修正済み(FIXED)バグが、 7行の未修整(OPEN)バグと すべてペアに
  9. 9. 9 ■ cartesian-no-group.sql の Select句を Select * に変更した結果  17.2.1 意図に反した結果 (5/5)
  10. 10. 10  17.2.2 さらなる弊害 ● 1つのクエリで複数のタスクを行おうとすると・・・ ➢ 意図しない結果が導かれる ➢ クエリの記述や修正、デバッグが難しくなる ➢ 実行時のコストが上がる (手の込んだSQLは最適化処理などが難しくなる)
  11. 11. 11  17.3 アンチパターンの見つけ方 ● SUM関数やCOUNT関数の結果があり得ないくら いに大きくなってるのはなぜ? ● このお化けみたいに複雑SQLクエリを書くのに、丸 1日かかったよ! ● このレポート出力には、もう何も追加できない。この SQLクエリを書き直すのは手間がかかりすぎる ● このクエリに、もう1つDISTINCTを追加してみよう ● (実行時間が長すぎる!)
  12. 12. 12 17.4 アンチパターンを用いてもよい場合 ● 単一のクエリをデータソースに結び付けてアプリケーション にデータを表示するような、プログラミングフレームワーク やビジュアルコンポーネントライブラリ、レポートツールを 使っている場合 ➢ ただ、レポートの要件が1つのSQLクエリで実現するにはあ まりにも複雑な場合は、レポートを複数作成した方がよいか もしれない ● 複数の結果を1つのソート順で表示させるために、1つの クエリから複雑な結果を得たい場合 ➢ SQLクエリでは、ソート順を簡単に指定できる ➢ 複数のクエリ結果をソートするするには、アプリケーション コードを書くよりもDBでソートした方が効率的な場合が多い
  13. 13. 13  17.5 解決策:分割統治を行う ● 節約の原則(the law of parsimony) ➢ まったく同じ予測をする2つの競合する理論があるとき は、単純な方が優れている。 ● この原則をSQLに当てはめると・・・ ➢ まったく同じ結果セットを生む2つのクエリを選択できる 場合は、単純なクエリを選ぶべき。
  14. 14. 14  17.5.1 ワンステップずつ (1/2) SELECT p.product_id, COUNT(f.bug_id) AS count_fixed FROM BugsProducts p LEFT OUTER JOIN Bugs f ON p.bug_id = f.bug_id AND f.status = 'FIXED' WHERE p.product_id = 1 GROUP BY p.product_id; SELECT p2.product_id, COUNT(o.bug_id) AS count_open FROM BugsProducts p2 LEFT OUTER JOIN Bugs o ON p2.bug_id = o.bug_id AND o.status = 'OPEN' WHERE p2.product_id = 1 GROUP BY p2.product_id; FROM BugsProducts p INNER JOIN Bugs f ON p.bug_id = f.bug_id AND f.status = 'FIXED' INNER JOIN Bugs o ON p2.bug_id = o.bug_id AND o.status = 'OPEN' INNER JOIN BugsProducts p2 SELECT p.product_id, COUNT(f.bug_id) AS count_fixed, COUNT(o.bug_id) AS count_open WHERE p.product_id = 1 GROUP BY p.product_id; USING (product_id) デカルト積を避けるために、クエリを分割デカルト積を避けるために、クエリを分割 Spaghetti-Query/anti/cartesian.sql Spaghetti-Query/soln/split-query.sql
  15. 15. 15  17.5.1 ワンステップずつ (2/2) ● クエリ分割は様々なメリットをもたらす ➢ デカルト積が生じない。 ➢ 新たな要件が追加された場合、すでに複雑なクエリをさ らに複雑にするより、単純なクエリを新たに書く方がは るかに簡単。 ➢ 一般的に、SQLエンジンは複雑なクエリよりも単純なク エリの方がスムーズかつ確実に実行できる。 ➢ コードレビューなどでは、シンプルな複数のクエリを説明 する方が、1つの複雑なクエリを説明するより簡単。
  16. 16. 16  17.5.2 UNIONを用いる 複数のクエリの結果は、UNIONによって1つの結果セットにまとめられる。 2つのサブクエリの結果を区別するための 列として、status列を使用している。 UNIONは、両方のサブクエリの列に互換性が あるときにのみ使用できる。
  17. 17. 17  17.5.3 CASE式とSUM関数を組み合わせる 条件ごとの集約を1つのクエリでシンプルに行うために、CASE式とSUM 関数を組み合わせる方法がよく使われる。
  18. 18. 18  17.5.4 上司の問題を解決する 最前の解決策は、上司から求められたタスクを分割して処理すること。 ■誰かが取り扱っている製品の数: SELECT COUNT(*) AS how_many_products FROM Products; ■バグを修正した開発者の数: SELECT COUNT(DISTINCT assigned_to) AS how_many_developers FROM Bugs WHERE status = 'FIXED'; ■開発者1人あたりの平均バグ修正数: SELECT AVG(bugs_per_developer) AS average_bugs_per_developer FROM (SELECT dev.account_id, COUNT(*) AS bugs_per_developer FROM Bugs b INNER JOIN Accounts dev ON b.assigned_to = dev.account_id WHERE b.status = 'FIXED' GROUP BY dev.account_id) t; ■修正したバグの中で顧客から報告されたバグの数: SELECT COUNT(*) AS how_many_customer_bugs FROM Bugs b INNER JOIN Accounts cust ON b.reported_by = cust.account_id WHERE b.status = 'FIXED' AND cust.email NOT LIKE '%@example.com';
  19. 19. 19  17.5.5 SQLを用いたSQLの自動的な記述 ■ コラム: 複数のUPDATEステートメント生成 【例】 列 last_used の値を、各コンピュータが使用された最新の日付に設定する: SELECT CONCAT('UPDATE Inventory ' ' SET last_used = ''', MAX(u.usage_date), '''', ' WHERE inventory_id = ', u.inventory_id, ';') AS update_statement FROM ComputerUsage u GROUP BY u.inventory_id; ● 複雑なSQLクエリを分割すると、データの値によっ てわずかに異なる、似たようなクエリをいくつも生 成することがある。 ➢ それは煩わしいので、「コード生成」を行いましょう。 ➢ 「コード生成」は、新しいコードを手で書くには非常に労 力がかかるような場面で効果的。
  20. 20. 20 まとめ SQLSQLでは、1行のコードで複雑な問題を解決できでは、1行のコードで複雑な問題を解決でき ると思える場合があります。ると思える場合があります。 しかし、状況に応じてクエリを分割することも検討しかし、状況に応じてクエリを分割することも検討 するようにしましょう。するようにしましょう。

×