根据不同表中的列从SELECT中删除行

时间:2011-01-12 06:59:57

标签: sql mysql database join rdbms

我正在寻找一种方法来根据另一个表的行中的某些值从一个表的SELECT中过滤掉行。

我正在尝试下面的示例结构。我有一个博客文章内容表(每篇博文一行),另一个关于帖子的元数据表(每个键值对一行;每行有一个列与博客文章关联;每行多行博客文章)。我希望仅在posts metadata中没有行时才提取metadata.pid=posts.pid AND metadata.k='optout'行。也就是说,对于下面的示例结构,我只想返回posts.id=1行。

(基于我的尝试)JOIN不会最终删除包含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'

2 个答案:

答案 0 :(得分:8)

听起来你想要LEFT JOIN,然后检查联接表的值是NULL的结果,表明没有这样的连接记录。

例如:

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

这将从表posts中选择任何没有对应的metadata行且价值为k = 'optout'的行。

编辑:值得注意的是,这是左连接的关键属性,不适用于常规连接;左连接将始终返回第一个表中的值,即使连接表中不存在匹配值,也允许您根据缺少这些行执行选择。

编辑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");

这个DOES产生一个结果集:

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

INNER JOINLEFT JOIN之间的区别在于,如果来自BOTH联接表的行匹配,INNER JOIN将仅返回结果。在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 |
+-----+-------+--------------+------+------+------+

希望清除它!加入是一件值得学习的好事,完全理解何时使用哪一个是一件非常好的事情。

答案 1 :(得分:3)

您可以尝试类似

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