SQL Fiddle のデモはこちら
このクエリは、指定された結果セットを返します。
SELECT p.id
, p.name
FROM posts p
WHERE NOT EXISTS
( SELECT 1
FROM streams_tags st
WHERE st.stream_id = 201 /* <-- specified stream_id value */
AND NOT EXISTS
( SELECT 1
FROM posts_tags pt
WHERE pt.tag_id = st.tag_id
AND pt.post_id = p.id
)
)
- からの各行について
posts
- テーブルをクエリし
streams_tags
て、指定された stream_id のすべてのタグを検索します
tag_id
テーブルから「欠落」が見つかった投稿を除外しposts_tags
ます(そのためpost
)
注: このクエリでは、存在しないストリーム、またはタグを持たないストリーム (つまりstreams_tags
、特定の に行がないstream_id
) がすべての投稿に一致します。(この動作が望ましくない場合は、少なくとも 1 つの一致するタグが必要になるように、述語を追加するようにクエリを変更できます。)
このクエリが何を行っているかを理解するには、最も外側のクエリを省略して、内側のクエリだけを見ると役に立ちます。(以下の例の id 値は、SQL Fiddle デモに読み込まれた値を参照しています。)
これがその内部クエリです。p.post_id 列への参照を削除し、リテラルに置き換えました。このクエリは、posts.id = 67 が、stream.id = 201 に対して (すべてのタグの一致に関して) 「一致」するかどうかを確認しています。
SELECT st.tag_id
FROM streams_tags st
WHERE st.stream_id = 201 /* <- specified stream_id */
AND NOT EXISTS
( SELECT 1
FROM posts_tags pt
WHERE pt.tag_id = st.tag_id
AND pt.post_id = 67 /* <- post we want to check for a match */
)
このクエリを実行して posts.id = 67 を確認すると、行が返されません。つまり、posts.id 67 は、指定された stream_id のすべてのタグに一致します。
posts.id = 68 を指定してもう一度実行すると、行が返されます。返される行は、streams_tag.tag_id
から「欠落している」値ですpost_tags
。
したがって、各 post_id に対してこのクエリを実行し、このクエリが行を返すかどうかを確認すると、指定された stream_id の stream_tags.tag_id に「すべて」一致する投稿を知ることができます。そして、それが最も外側のクエリが基本的に行っていることです...すべてのpost_idに対してこのクエリを実行します。
まったく異なるアプローチは、投稿で一致するタグの「数」を取得し、それをストリームのタグの数と比較することです。
SELECT p.id
, p.name
, sc.st_count AS st_count
FROM ( SELECT stc.stream_id
, COUNT(DISTINCT stc.tag_id) AS st_count
FROM streams_tags stc
WHERE stc.stream_id = 201
GROUP BY stc.stream_id
) sc
CROSS
JOIN posts p
LEFT
JOIN posts_tags pt
ON pt.post_id = p.id
LEFT
JOIN streams_tags st
ON st.tag_id = pt.tag_id
AND st.stream_id = sc.stream_id
GROUP
BY p.id
, p.name
HAVING COUNT(DISTINCT st.tag_id) >= sc.st_count
注: HAVING 句で使用できるように、stream_tags (特定の stream_id の) からタグの数を取得するには、クエリの SELECT リストにそれを含める必要があります。(他の代替手段は、そのサブクエリを HAVING 句に移動し、指定された stream_id 値をクエリで 2 回繰り返すことです...
SELECT p.id
, p.name
FROM posts p
LEFT
JOIN posts_tags pt
ON pt.post_id = p.id
LEFT
JOIN streams_tags st
ON st.tag_id = pt.tag_id
AND st.stream_id = 201 /* <- specified stream_id */
GROUP
BY p.id
, p.name
HAVING COUNT(DISTINCT st.tag_id) >=
( SELECT COUNT(DISTINCT stc.tag_id) AS st_count
FROM streams_tags stc
WHERE stc.stream_id = 201 /* <- specified stream_id */
)