-1

私は5つのテーブルを持っています。タグ、ストリーム、streams_tags、投稿、posts_tags。stream_tags と posts_tags は、タグ、ストリーム、および投稿の単なる結合テーブルです。

投稿テーブルから、streams_tags テーブルを介してストリームに関連付けられているすべてのタグを含むすべての投稿を選択したいと考えています。たとえば、ストリームに「猫、犬」というタグが関連付けられている場合、「猫、犬」というタグが関連付けられているすべての投稿が返されます。「猫、犬」タグが含まれていれば、「猫、犬」以外のタグが付いていても問題ありません。

タグ: ID | 名前

ストリーム: ID | 名前

ストリームタグ: id | ストリーム ID | tag_id

投稿: id | 名前

投稿タグ: id | post_id | tag_id

4

1 に答える 1

1

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 */ 
       )
于 2013-01-10T01:12:42.663 に答える