2011-01-12 18 views
5

別のテーブルの行の特定の値に基づいて、あるテーブルのSELECTの行をフィルタリングする方法を探しています。異なるテーブルの列に基づいてSELECTから行を削除する

私は以下の構造例を試しています。私は、ブログ投稿のコンテンツ(ブログ記事ごとに1行)のテーブルと、投稿に関するメタデータの別のテーブル(キーと値のペアごとに1つの行、ブログの投稿と関連付ける列を持つ各行、ブログ記事)。 metadataに行が存在しない場合に限り、postsの行を取得したい場合は、metadata.pid=posts.pid AND metadata.k='optout'となります。つまり、以下の構造例では、posts.id=1の行を戻したいだけです。 のメタデータの他の行は、それが結果になることを意味するので、いくつかのメタデータを持つ投稿を削除しないでください。metadata.k='optout'。メタデータの行を持つすべての単一pid引き起こし、

mysql> select posts.* from posts where pid = any (select pid from metadata where k = 'optout'); 
+-----+-------+--------------+ 
| pid | title | content  | 
+-----+-------+--------------+ 
| 2 | Bar | More content | 
| 3 | Baz | Something | 
+-----+-------+--------------+ 
2 rows in set (0.00 sec) 

...しかしpid != any (...)を使用すると、私のポスト内のすべての行3を与える:

mysql> select * from posts; 
+-----+-------+--------------+ 
| pid | title | content  | 
+-----+-------+--------------+ 
| 1 | Foo | Some content | 
| 2 | Bar | More content | 
| 3 | Baz | Something | 
+-----+-------+--------------+ 
3 rows in set (0.00 sec) 

mysql> select * from metadata; 
+------+-----+--------+-----------+ 
| mdid | pid | k  | v   | 
+------+-----+--------+-----------+ 
| 1 | 1 | date | yesterday | 
| 2 | 1 | thumb | img.jpg | 
| 3 | 2 | date | today  | 
| 4 | 2 | optout | true  | 
| 5 | 3 | date | tomorrow | 
| 6 | 3 | optout | true  | 
+------+-----+--------+-----------+ 
6 rows in set (0.00 sec) 

は、サブクエリは、私が何をしたいの逆数を与えることができますここで、 k!='optout'

答えて

8

LEFT JOINを実行し、結合されたテーブルの値がNULLである結果を確認して、結合されたレコードが存在しないことを確認してください。例えば

SELECT * FROM posts 
LEFT JOIN metadata ON (posts.pid = metadata.pid AND metadata.k = 'optout') 
WHERE metadata.mdid IS NULL; 

これは、該当する行がmetadatak = 'optout'の値に存在しないため、テーブルpostsからすべての行を選択します。

編集:これは左結合の重要なプロパティであり、通常結合では機能しません。結合されたテーブルに一致する値が存在しない場合でも、左側の結合は常に最初のテーブルから値を返し、その行が存在しないことに基づいて選択を実行できるようにします。

編集2:のは、(私は明確にするためINNER JOINと呼ぶが、MySQLに交換可能である)JOINLEFT JOINに関して、ここで何が起こっているのか明確にしましょう。

は、これら2つのクエリのいずれかを実行したとします

SELECT posts.*, metadata.mdid, metadata.k, metadata.v 
FROM posts 
INNER JOIN metadata ON posts.pid = metadata.pid; 

または

SELECT posts.*, metadata.mdid, metadata.k, metadata.v 
FROM posts 
LEFT JOIN metadata ON posts.pid = metadata.pid; 

両方のクエリは次の結果セットを生成:

+-----+-------+--------------+------+-------+-----------+ 
| pid | title | content  | mdid | k  | v   | 
+-----+-------+--------------+------+-------+-----------+ 
| 1 | Foo | Some content | 1 | date | yesterday | 
| 1 | Foo | Some content | 2 | thumb | img.jpg | 
+-----+-------+--------------+------+-------+-----------+ 

今、我々が変更と仮定してみましょうを言及された "optout"のための余分な基準を追加するためのクエリ。まず、INNER JOIN

SELECT posts.*, metadata.mdid, metadata.k, metadata.v 
FROM posts 
LEFT JOIN metadata ON (posts.pid = metadata.pid AND metadata.k = "optout"); 

これは、結果セットを生成します::LEFT JOINにすることを変え、今

Empty set (0.00 sec) 

:予想通り

SELECT posts.*, metadata.mdid, metadata.k, metadata.v 
FROM posts 
INNER JOIN metadata ON (posts.pid = metadata.pid AND metadata.k = "optout"); 

、これは結果を返しません

+-----+-------+--------------+------+------+------+ 
| pid | title | content  | mdid | k | v | 
+-----+-------+--------------+------+------+------+ 
| 1 | Foo | Some content | NULL | NULL | NULL | 
+-----+-------+--------------+------+------+------+ 

INNER JOINLEFT JOINの違いは、INNER JOINは、BOTH結合テーブルの行が一致した場合にのみ結果を返すことです。 LEFT JOINでは、結合するものが見つかったかどうかにかかわらず、最初のテーブルの一致する行が常に返されます。多くの場合、どちらを使用するかは関係ありませんが、予期しない結果が得られないように正しいものを選択することが重要です。

したがって、この場合には、提案のクエリ:それをクリアし

+-----+-------+--------------+------+------+------+ 
| pid | title | content  | mdid | k | v | 
+-----+-------+--------------+------+------+------+ 
| 1 | Foo | Some content | NULL | NULL | NULL | 
+-----+-------+--------------+------+------+------+ 

願わくば:

SELECT posts.*, metadata.mdid, metadata.k, metadata.v 
LEFT JOIN metadata ON (posts.pid = metadata.pid AND metadata.k = 'optout') 
WHERE metadata.mdid IS NULL; 

は、上記と同じ結果セットを返します!結合は、いつどれが非常に良いものであるかを完全に理解して、学ぶことが大変です。

+0

ような何かを試すことができますが、サブクエリは、メタデータの行と一致するので、metadata.mdidはそうではなく、nullであります選択されません。しかし、optoutのないポストでは、サブクエリはローにマッチしないので、右側にはヌルが埋め込まれるため、where句は真です。 – alxndr

+1

私は、結合がどのように働いているのか、それに伴う灰色の部分をクリアするべきであるという答えに別のセクションを追加しました。希望が助けてくれる! – futureal

3

あなたがOptOutのとポストのため...だから、私はこれを取得する場合、私は見てみましょう

select p.* 
from posts p 
where NOT EXISTS (
         select pid 
         from metadata 
         where k = 'optout' 
         and  pid = p.pid 
        ) 
+0

うわー、ありがとう。存在しない人に読んでもらいたい。 – alxndr

+0

FYIは、36000行で他の応答の原因にチェックマークを付けると、左の結合は0.1秒早くなります... – alxndr

+1

小さな結果セットでは、2つのクエリがほぼ同じパフォーマンスを発揮します。しかし、サブクエリで 'EXISTS'または' NOT EXISTS'を使用する場合は、サブクエリテーブルを計算して一時テーブルにコピーする必要があります。結果セットが大きくなるにつれて、パフォーマンス上のボトルネックになる可能性があります。私は完全にそれを避けるつもりはありません、時々複雑な結合よりも読んだり理解することがずっと楽になります。最終的に生成される結果セットのタイプを知っていればいいだけです。 – futureal

関連する問題