SlideShare a Scribd company logo
1 of 69
Download to read offline
SQLアンチパターン
~スパゲッティクエリ~
CB部ADチーム
板橋正之
2014/06/17
ある事柄を説明するために
必要以上に多くの実体を
仮定するべきではない
~オッカムのウィリアム
オッカムの剃刀
オッカムのウィリアムは
オッカム村生まれのウィリアムさん
ヴィンチ村生まれの
レオナルドさんと同じ意味
目的:
SQLクエリの数を減らす
アンチパターン:
複雑な問題をワンステップで
解決しようとする
解決策:
分割統治を行う
シンプル・イズ・ベスト
SQLコードを簡単に
もうちょっと説明します。
SQLのクエリも
プログラミングと一緒
1つのメソッドで多くの処理
→Bad!!
1つのSQLで複雑な問い合わせ
→Bad!!
複雑なクエリは
クエリの修正、変更、デバッグも
複雑に
クエリの数が減れば
パフォーマンスが上がる
→間違い
必ずしもパフォーマンスは
上がらないし、
逆に複雑すぎて最適化が
働かない可能性も
パフォーマンスチューニングも
難しい。
複雑なメソッドはコードレビューが
難しいのと一緒。
複雑すぎて自分で何を
やっているのかわからなくなる。
1週間後にそのSQLクエリ
読み解けますか?
意図に反した結果
以下の2つのテーブルからproduct_idが1の製品のバグが完了(FIXED) と
未修正(OPEN)の件数を問い合わせたい。
BugsProducts Bugs
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;
想定では以下のような結果をもとに件数が集計できるはず。
FIXEDの結果 OPENの結果
件数集計
現実
FIXEDの結果 OPENの結果
件数集計
• デカルト積(直積)
–2つのテーブルの間に関連を制限
する条件を持たないときに生まれ
る。
–この制限がないと1つのテーブル
の各行が、もう1つのテーブルのす
べての行と組合せになってしまう。
両方のテーブルの行のすべての組み合わせが
取得されます。
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;
①
②
①でstatusが'FIXED'
②でstatusが'OPEN'を求め
USINGで両者を関連付けているように見えて関連付けられていないため
①と②の2つの間に関連を制限する条件が持たないのでデカルト積が生じている。
アンチパターンの見つけ方
「ありえないくらいの行数が
返ってきてるんですけどー」
うん、直積(デカルト積)されて
結果が返ってきてるね。
複雑すぎてちゃんと結合条件
書けていないんじゃない?
「この複雑なSQLクエリを
書くのに、丸1日かかった
(ドヤァ!)」
そんなに時間かけるんだったら
もっと簡単にできる
アプローチ探したら?
「このレポート出力には、
もう何も追加できない。
SQLクエリを書き直すのは
手間がかかりすぎる!」
お前、
客先でも同じこと言えんのか?
「このクエリに、
もう1つDISTINCTを
追加してみよう(提案)」
直積で返ってくるのを集約して
ごまかしているだけだろ?
見た目は合うけど、
ソートや重複排除で
パフォーマンス最悪だよ?
アンチパターンを用いても
よい場合
プログラミングフレームワークや
ビジュアルコンポーネントライブラリ、
BIツール(安い)とか使ってる場合は
(仕方がないので)使ってもいいよ。
でも、複雑、難しいことを
無理やりやるより
別アプローチ探す方が
建設的だよ。
後、複数のクエリ結果をまとめて
ソートするような場合は
DBで行う方が効率的な場合が
多いかも。
解決策
ワンステップずつ
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 p.product_id, COUNT(o.bug_id) AS count_open
FROM BugsProducts p
LEFT OUTER JOIN Bugs o
ON (p.bug_id = o.bug_id AND o.status = 'OPEN')
WHERE p.product_id = 1
GROUP BY p.product_id;
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;
• 望まないデカルト積が生じない。
• 修正・変更が簡単。
• 一般的に単純なクエリの方がス
ムーズかつ確実に実行される。
• コードレビューも容易。
UNIONを用いる
(SELECT p.product_id, 'FIXED' AS status, COUNT(f.bug_id) AS bug_count
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)
UNION
(SELECT p.product_id, 'OPEN' AS status, COUNT(o.bug_id) AS bug_count
FROM BugsProducts p
LEFT OUTER JOIN Bugs o
ON (p.bug_id = o.bug_id AND o.status = 'OPEN')
WHERE p.product_id = 1
GROUP BY p.product_id)
ORDER BY bug_count DESC;
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;
• 2つのサブクエリの結果を合わせ
たもの
CASE式とSUM関数を
組み合わせる
SELECT p.product_id,
SUM(CASE b.status WHEN 'FIXED' THEN 1 ELSE 0 END) AS count_fixed,
SUM(CASE b.status WHEN 'OPEN' THEN 1 ELSE 0 END) AS count_open
FROM BugsProducts p
INNER JOIN Bugs b USING (bug_id)
WHERE p.product_id = 1
GROUP BY p.product_id;
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;
SELECT文の副問い合わせを
用いる
(SQLアンチパターンには
載っていない方法)
SELECT t1.product_id, count_fixed, count_open
FROM
(
SELECT p.product_id, COUNT(f.bug_id) AS count_fixed
FROM BugsProducts p INNER JOIN Bugs f
ON (p.bug_id = f.bug_id AND f.status = 'FIXED')
WHERE p.product_id = 1
GROUP BY p.product_id
) t1,
(
SELECT p2.product_id, COUNT(o.bug_id) AS count_open
FROM BugsProducts p2 INNER JOIN Bugs o
ON (p2.bug_id = o.bug_id AND o.status = 'OPEN')
WHERE p2.product_id = 1
GROUP BY p2.product_id
) t2
WHERE t1.product_id = t2.product_id;
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句の中にSELECT文を記
述して一時テーブルを作成して
関連を制限する条件で結合する。
• FROM句の中のSELECT文は実
行時にメモリ上等に生成され、イ
ンデックスなどがないため、大き
な結果が返る副問い合わせでは
注意が必要。
求められている問題を
解決する
誰も1つのレポートで
結果くれとは言ってないよ?
SQLを用いたSQLの自動的な記述
複雑なSQLクエリを分割すると
似たようなクエリをいくつも作成す
ることがあります。
よく似たクエリをいくつも書くのは
煩わしいので
シェルやマクロ、場合によっては
SQLクエリ自体でSQLクエリを
生成しましょう。
最新の日付で更新する
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文として実行する。
まとめ
SQLでは、1行のコードで複雑な問
題を解決できると思える場合があ
ります。
しかし、状況に応じてクエリを分割
することも検討するようにしましょう。
デカルト積(直積)がどうこうとかで
はなくて、まったく同じ結果セットを
生む2つのクエリを選択できる場合
は、単純なクエリを選ぶべきであ
るということ。
また、クエリの結果セット以前に目
的を達成するためには単純な方
法が優れていることがあるというこ
とです。
以上、
ご清聴ありがとうございました。

More Related Content

What's hot

MySQLで論理削除と正しく付き合う方法
MySQLで論理削除と正しく付き合う方法MySQLで論理削除と正しく付き合う方法
MySQLで論理削除と正しく付き合う方法yoku0825
 
オブジェクト指向の設計と実装の学び方のコツ
オブジェクト指向の設計と実装の学び方のコツオブジェクト指向の設計と実装の学び方のコツ
オブジェクト指向の設計と実装の学び方のコツ増田 亨
 
SQLチューニング入門 入門編
SQLチューニング入門 入門編SQLチューニング入門 入門編
SQLチューニング入門 入門編Miki Shimogai
 
世界一わかりやすいClean Architecture
世界一わかりやすいClean Architecture世界一わかりやすいClean Architecture
世界一わかりやすいClean ArchitectureAtsushi Nakamura
 
ふつうのRailsアプリケーション開発
ふつうのRailsアプリケーション開発ふつうのRailsアプリケーション開発
ふつうのRailsアプリケーション開発Takafumi ONAKA
 
エンジニアの個人ブランディングと技術組織
エンジニアの個人ブランディングと技術組織エンジニアの個人ブランディングと技術組織
エンジニアの個人ブランディングと技術組織Takafumi ONAKA
 
境界付けられたコンテキスト 概念編 (ドメイン駆動設計用語解説シリーズ)
境界付けられたコンテキスト 概念編 (ドメイン駆動設計用語解説シリーズ)境界付けられたコンテキスト 概念編 (ドメイン駆動設計用語解説シリーズ)
境界付けられたコンテキスト 概念編 (ドメイン駆動設計用語解説シリーズ)Koichiro Matsuoka
 
君はyarn.lockをコミットしているか?
君はyarn.lockをコミットしているか?君はyarn.lockをコミットしているか?
君はyarn.lockをコミットしているか?Teppei Sato
 
SQL大量発行処理をいかにして高速化するか
SQL大量発行処理をいかにして高速化するかSQL大量発行処理をいかにして高速化するか
SQL大量発行処理をいかにして高速化するかShogo Wakayama
 
ツール比較しながら語る O/RマッパーとDBマイグレーションの実際のところ
ツール比較しながら語る O/RマッパーとDBマイグレーションの実際のところツール比較しながら語る O/RマッパーとDBマイグレーションの実際のところ
ツール比較しながら語る O/RマッパーとDBマイグレーションの実際のところY Watanabe
 
例外設計における大罪
例外設計における大罪例外設計における大罪
例外設計における大罪Takuto Wada
 
イミュータブルデータモデル(入門編)
イミュータブルデータモデル(入門編)イミュータブルデータモデル(入門編)
イミュータブルデータモデル(入門編)Yoshitaka Kawashima
 
初心者向けMongoDBのキホン!
初心者向けMongoDBのキホン!初心者向けMongoDBのキホン!
初心者向けMongoDBのキホン!Tetsutaro Watanabe
 
GraphQLのsubscriptionで出来ること
GraphQLのsubscriptionで出来ることGraphQLのsubscriptionで出来ること
GraphQLのsubscriptionで出来ることShingo Fukui
 
爆速クエリエンジン”Presto”を使いたくなる話
爆速クエリエンジン”Presto”を使いたくなる話爆速クエリエンジン”Presto”を使いたくなる話
爆速クエリエンジン”Presto”を使いたくなる話Kentaro Yoshida
 
Java ORマッパー選定のポイント #jsug
Java ORマッパー選定のポイント #jsugJava ORマッパー選定のポイント #jsug
Java ORマッパー選定のポイント #jsugMasatoshi Tada
 
Redisの特徴と活用方法について
Redisの特徴と活用方法についてRedisの特徴と活用方法について
Redisの特徴と活用方法についてYuji Otani
 
イミュータブルデータモデル(世代編)
イミュータブルデータモデル(世代編)イミュータブルデータモデル(世代編)
イミュータブルデータモデル(世代編)Yoshitaka Kawashima
 
PlaySQLAlchemy: SQLAlchemy入門
PlaySQLAlchemy: SQLAlchemy入門PlaySQLAlchemy: SQLAlchemy入門
PlaySQLAlchemy: SQLAlchemy入門泰 増田
 

What's hot (20)

MySQLで論理削除と正しく付き合う方法
MySQLで論理削除と正しく付き合う方法MySQLで論理削除と正しく付き合う方法
MySQLで論理削除と正しく付き合う方法
 
オブジェクト指向の設計と実装の学び方のコツ
オブジェクト指向の設計と実装の学び方のコツオブジェクト指向の設計と実装の学び方のコツ
オブジェクト指向の設計と実装の学び方のコツ
 
SQLチューニング入門 入門編
SQLチューニング入門 入門編SQLチューニング入門 入門編
SQLチューニング入門 入門編
 
世界一わかりやすいClean Architecture
世界一わかりやすいClean Architecture世界一わかりやすいClean Architecture
世界一わかりやすいClean Architecture
 
ふつうのRailsアプリケーション開発
ふつうのRailsアプリケーション開発ふつうのRailsアプリケーション開発
ふつうのRailsアプリケーション開発
 
エンジニアの個人ブランディングと技術組織
エンジニアの個人ブランディングと技術組織エンジニアの個人ブランディングと技術組織
エンジニアの個人ブランディングと技術組織
 
境界付けられたコンテキスト 概念編 (ドメイン駆動設計用語解説シリーズ)
境界付けられたコンテキスト 概念編 (ドメイン駆動設計用語解説シリーズ)境界付けられたコンテキスト 概念編 (ドメイン駆動設計用語解説シリーズ)
境界付けられたコンテキスト 概念編 (ドメイン駆動設計用語解説シリーズ)
 
君はyarn.lockをコミットしているか?
君はyarn.lockをコミットしているか?君はyarn.lockをコミットしているか?
君はyarn.lockをコミットしているか?
 
SQL大量発行処理をいかにして高速化するか
SQL大量発行処理をいかにして高速化するかSQL大量発行処理をいかにして高速化するか
SQL大量発行処理をいかにして高速化するか
 
分散トレーシング技術について(Open tracingやjaeger)
分散トレーシング技術について(Open tracingやjaeger)分散トレーシング技術について(Open tracingやjaeger)
分散トレーシング技術について(Open tracingやjaeger)
 
ツール比較しながら語る O/RマッパーとDBマイグレーションの実際のところ
ツール比較しながら語る O/RマッパーとDBマイグレーションの実際のところツール比較しながら語る O/RマッパーとDBマイグレーションの実際のところ
ツール比較しながら語る O/RマッパーとDBマイグレーションの実際のところ
 
例外設計における大罪
例外設計における大罪例外設計における大罪
例外設計における大罪
 
イミュータブルデータモデル(入門編)
イミュータブルデータモデル(入門編)イミュータブルデータモデル(入門編)
イミュータブルデータモデル(入門編)
 
初心者向けMongoDBのキホン!
初心者向けMongoDBのキホン!初心者向けMongoDBのキホン!
初心者向けMongoDBのキホン!
 
GraphQLのsubscriptionで出来ること
GraphQLのsubscriptionで出来ることGraphQLのsubscriptionで出来ること
GraphQLのsubscriptionで出来ること
 
爆速クエリエンジン”Presto”を使いたくなる話
爆速クエリエンジン”Presto”を使いたくなる話爆速クエリエンジン”Presto”を使いたくなる話
爆速クエリエンジン”Presto”を使いたくなる話
 
Java ORマッパー選定のポイント #jsug
Java ORマッパー選定のポイント #jsugJava ORマッパー選定のポイント #jsug
Java ORマッパー選定のポイント #jsug
 
Redisの特徴と活用方法について
Redisの特徴と活用方法についてRedisの特徴と活用方法について
Redisの特徴と活用方法について
 
イミュータブルデータモデル(世代編)
イミュータブルデータモデル(世代編)イミュータブルデータモデル(世代編)
イミュータブルデータモデル(世代編)
 
PlaySQLAlchemy: SQLAlchemy入門
PlaySQLAlchemy: SQLAlchemy入門PlaySQLAlchemy: SQLAlchemy入門
PlaySQLAlchemy: SQLAlchemy入門
 

Similar to SQLアンチパターン~スパゲッティクエリ

SQLアンチパターン読書会 「スパゲッティクエリ」
SQLアンチパターン読書会 「スパゲッティクエリ」SQLアンチパターン読書会 「スパゲッティクエリ」
SQLアンチパターン読書会 「スパゲッティクエリ」makopi 23
 
【SQiP2014】システム操作インターフェイス最適化によるテスト自動化ROI向上
【SQiP2014】システム操作インターフェイス最適化によるテスト自動化ROI向上【SQiP2014】システム操作インターフェイス最適化によるテスト自動化ROI向上
【SQiP2014】システム操作インターフェイス最適化によるテスト自動化ROI向上Tatsuya Ishikawa
 
2011年10月28日
2011年10月28日2011年10月28日
2011年10月28日nukaemon
 
もっと早く知りたかったプログラミング技法9選
もっと早く知りたかったプログラミング技法9選もっと早く知りたかったプログラミング技法9選
もっと早く知りたかったプログラミング技法9選Masayuki Akiyama
 
Ec cubeの基礎からcms連携まで
Ec cubeの基礎からcms連携までEc cubeの基礎からcms連携まで
Ec cubeの基礎からcms連携までMakoto Nishimura
 
One Time Binding & Digest Loop
One Time Binding & Digest LoopOne Time Binding & Digest Loop
One Time Binding & Digest LoopKon Yuichi
 
Try_to_writecode_practicaltest #atest_hack
Try_to_writecode_practicaltest #atest_hackTry_to_writecode_practicaltest #atest_hack
Try_to_writecode_practicaltest #atest_hackkimukou_26 Kimukou
 
関数型言語&形式的手法セミナー(3)
関数型言語&形式的手法セミナー(3)関数型言語&形式的手法セミナー(3)
関数型言語&形式的手法セミナー(3)啓 小笠原
 

Similar to SQLアンチパターン~スパゲッティクエリ (10)

SQLアンチパターン読書会 「スパゲッティクエリ」
SQLアンチパターン読書会 「スパゲッティクエリ」SQLアンチパターン読書会 「スパゲッティクエリ」
SQLアンチパターン読書会 「スパゲッティクエリ」
 
【SQiP2014】システム操作インターフェイス最適化によるテスト自動化ROI向上
【SQiP2014】システム操作インターフェイス最適化によるテスト自動化ROI向上【SQiP2014】システム操作インターフェイス最適化によるテスト自動化ROI向上
【SQiP2014】システム操作インターフェイス最適化によるテスト自動化ROI向上
 
2011年10月28日
2011年10月28日2011年10月28日
2011年10月28日
 
Ruby test double
Ruby test doubleRuby test double
Ruby test double
 
もっと早く知りたかったプログラミング技法9選
もっと早く知りたかったプログラミング技法9選もっと早く知りたかったプログラミング技法9選
もっと早く知りたかったプログラミング技法9選
 
Ec cubeの基礎からcms連携まで
Ec cubeの基礎からcms連携までEc cubeの基礎からcms連携まで
Ec cubeの基礎からcms連携まで
 
One Time Binding & Digest Loop
One Time Binding & Digest LoopOne Time Binding & Digest Loop
One Time Binding & Digest Loop
 
Try_to_writecode_practicaltest #atest_hack
Try_to_writecode_practicaltest #atest_hackTry_to_writecode_practicaltest #atest_hack
Try_to_writecode_practicaltest #atest_hack
 
Hadoop jobbuilder
Hadoop jobbuilderHadoop jobbuilder
Hadoop jobbuilder
 
関数型言語&形式的手法セミナー(3)
関数型言語&形式的手法セミナー(3)関数型言語&形式的手法セミナー(3)
関数型言語&形式的手法セミナー(3)
 

SQLアンチパターン~スパゲッティクエリ