2011-01-12 47 views
5

我非常想找到一種方法,根據另一個表的行中的某些值過濾掉某個表的SELECT中的行。基於不同表中的列從SELECT中移除行

我正在試驗下面的示例結構。我有一張博客文章內容表(每篇博文一行)和另一張有關帖子的元數據表(每個鍵值對一行;每行有一列將其與博客文章相關聯;每行多行博客文章)。我想拉posts只有當metadata其中metadata.pid=posts.pid AND metadata.k='optout'沒有行。也就是說,對於下面的示例結構,我只想返回posts.id=1行。

(基於我試過的)JOIN s最終沒有刪除具有某些元數據的帖子,其中metadata.k='optout',因爲pid的另一行元數據意味着它將其納入結果中。

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) 

子查詢可以給我我想要的逆:

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,導致每一個pid有一個元數據行其中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; 

這將從對於沒有對應metadata一行的k = 'optout'一值存在表posts選擇任一行。

編輯:值得注意的是,這是一個左連接的關鍵屬性,並不適用於常規連接;即使連接的表中沒有匹配的值,左連接也會始終返回第一個表中的值,從而允許您根據缺少這些行來執行選擇。

編輯2:讓我們來澄清一下LEFT JOINJOIN(爲了清楚起見我們將其稱爲INNER JOIN,但在MySQL中可互換)。

假設您運行這兩個查詢:

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 
INNER JOIN metadata ON (posts.pid = metadata.pid AND metadata.k = "optout"); 

正如預期的那樣,這不返回任何結果:

Empty set (0.00 sec) 

現在,改變的是一個LEFT JOIN

SELECT posts.*, metadata.mdid, metadata.k, metadata.v 
FROM posts 
LEFT 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中,無論是否找到任何要加入的內容,都會返回第一個表中匹配的行。在很多情況下,使用哪一個並不重要,但是選擇正確的一個是非常重要的,以免意外得出意外的結果。

因此,在這種情況下,建議查詢:

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; 

將返回相同的結果如上設置:

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

希望這清除它!加入是一件了不起的事,要充分了解何時使用哪一件是件好事。

+0

因此,讓我看看,如果我得到這個......對於選擇禁用後,子查詢相匹配的元數據行,所以metadata.mdid不爲空,所以它沒有被選中。但是沒有退出的帖子,子查詢不匹配一行,所以右側用空值填充,所以where子句爲真。 – alxndr 2011-01-12 18:59:42

+1

我已經添加了另一節,以解釋如何加入工作,應該清除它的任何灰色地帶。希望有所幫助! – futureal 2011-01-12 21:08:33

3

你可以嘗試像

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