web-dev-qa-db-ja.com

選択結合と左結合内のサブクエリ

しばしば、他の関連しない行をフェッチするクエリで、いくつかの行の数を返さなければなりません。

たとえば、テーブルユーザーテーブルレビューとテーブル画像

User:
id
nickname

Review:
id
to_user_id
from_user_id
rating

Picture:
id:
user_id
url

1つのクエリで欲しいとしましょう。「指定された」userIdのニックネームとその画像のすべてのURL、およびそのユーザーをレビューした人の数を取得します。

このクエリを実行するときに私が最初に考える簡単な方法は次のとおりです。

SELECT
  u.nickname
  (SELECT count(*) FROM review WHERE to_user_id = u.id) as reviewCount,
  p.url
FROM user
LEFT JOIN picture ON p.user_id = u.id
WHERE 
  u.id = 1

これを行うもう1つの方法は、その副選択なしで、適​​切なuser_idでレビューテーブルを結合することです。

SELECT 
 u.nickname,
 r.reviewCount,
 p.url
FROM user u 
LEFT JOIN (
    SELECT to_user_id, count(*) reviewCount FROM review GROUP BY to_user_id
 ) r ON r.to_user_id = u.id
LEFT JOIN picture ON p.user_id = u.id 
WHERE u.id = 1;

私はdbクエリのパフォーマンスとチューニングの専門家ではありません。解決策が他よりも優れているかどうか誰かが私に説明できますか? (または他のより良い解決策がある場合)?

編集:申し訳ありません。最新のMySQLを使用しています

3
Alexis

使用しているRDBMSを指定していません。ここで書く内容のほとんどは完全に独立しているはずですが、ほとんどの場合MySQLでの経験があるため、異なるシステムで他の最適化が許可されている可能性があります。

(SELECT count(*) FROM review WHERE to_user_id = u.id) as reviewCount依存サブクエリです-結果の各行に対して実行されます。 1つの実行が高速であっても、潜在的に数千の実行が低速になる可能性があります。

JOINの1つは派生テーブルです。これは1回だけ実行され、一時テーブルにマテリアライズされてから、他のテーブルに結合されます。クエリが高速である((to_user_id)でインデックスを使用できる)場合は、問題ありません。ただし、この場合、実際には結果に表示されないユーザーについてもカウントされます。しかし、そこに条件をプッシュするだけです(GROUP BYの代わりにto_user_id = 1)。

しかし、物事をそれほど単純ではないようにするために、新しいバージョンにはいくつかの最適化が存在します。従属サブクエリは、MariaDB 10(およびIIRC MySQL 5.7でサブクエリキャッシュを使用して高速化できますが、確認しませんでした)。つまり、あなたの場合、結果のすべての行にはu.id = 1-> to_user_id = 1があり、サブクエリは実際には1回だけ実行され、キャッシュされた結果が使用されます。それが利用可能な場合、両方のバージョン間の違いは最小限になります。

個人的に私はほとんどの場合2番目のバージョンを好みますが、最初のバージョンの方が速い場合があります-JOINEDサブクエリの行を適切に制限するだけでは不十分なクエリがありましたが、従属サブクエリでは、実際に読み取られた一意の組み合わせはほとんどありません。

5
jkavalik