Sql 基于其他表中的列从SELECT中删除行

Sql 基于其他表中的列从SELECT中删除行,sql,mysql,database,join,rdbms,Sql,Mysql,Database,Join,Rdbms,我正在寻找一种方法,根据另一个表的行中的某些值,从一个表的SELECT中筛选出行 我正在试验下面的示例结构。我有一个博客文章内容表(每个博客文章一行)和另一个关于文章的元数据表(每个键值对一行;每行有一列将其与博客文章关联;每个博客文章有许多行)。仅当元数据中不存在行时,我才想提取一行帖子,其中metadata.pid=posts.pid和metadata.k='optout'。也就是说,对于下面的示例结构,我只想返回posts.id=1行 (根据我的尝试)JOINs不会删除包含元数据的帖子,因

我正在寻找一种方法,根据另一个表的行中的某些值,从一个表的
SELECT
中筛选出行

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

(根据我的尝试)
JOIN
s不会删除包含元数据的帖子,因为该
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(…)
给出了post中的所有3行,因为每个
pid
都有一个元数据行,其中
k!='optout'

听起来像是要执行
左联接
,然后检查联接表的值为
NULL
的结果,表明不存在这样的联接记录

例如:

SELECT * FROM posts 
LEFT JOIN metadata ON (posts.pid = metadata.pid AND metadata.k = 'optout')
WHERE metadata.mdid IS NULL;
这将从表
posts
中选择没有相应
元数据
行且值为
k='optout'
的任何行

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

编辑2:让我们澄清一下关于
左连接
连接
(为了清晰起见,我称之为
内部连接
,但在MySQL中可以互换)的情况

假设您运行以下两个查询之一:

SELECT posts.*, metadata.mdid, metadata.k, metadata.v 
FROM posts 
INNER 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   |
+-----+-------+--------------+------+-------+-----------+
+-----+-------+--------------+------+------+------+
| pid | title | content      | mdid | k    | v    |
+-----+-------+--------------+------+------+------+
|   1 | Foo   | Some content | NULL | NULL | NULL |
+-----+-------+--------------+------+------+------+
现在,假设我们修改查询,为前面提到的“optout”添加额外的条件。首先,
内部联接

SELECT posts.*, metadata.mdid, metadata.k, metadata.v 
FROM posts 
INNER JOIN metadata ON (posts.pid = metadata.pid AND metadata.k = "optout");
SELECT posts.*, metadata.mdid, metadata.k, metadata.v 
FROM posts 
LEFT JOIN metadata ON (posts.pid = metadata.pid AND metadata.k = "optout");
正如预期的那样,这不会返回任何结果:

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");
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 |    1 | date  | yesterday |
|   1 | Foo   | Some content |    2 | thumb | img.jpg   |
+-----+-------+--------------+------+-------+-----------+
+-----+-------+--------------+------+------+------+
| 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;
将返回与上面相同的结果集:

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

希望这能把事情弄清楚!加入是一件非常值得学习的事情,充分了解何时使用哪一个是一件非常好的事情。

您可以尝试以下方法

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

仅供参考,在36000行上给另一个答案打勾,因为左连接快了0.1秒…在一个小的结果集上,两个查询的执行应该大致相同。但是,如果对子查询使用
存在
不存在
,则需要计算子查询表并将其复制到临时表中。因此,随着结果集的增长,它可能会成为一个很大的性能瓶颈。我不会完全避免它,有时它比复杂的连接更容易阅读/理解--你只需要知道它最终可能产生的结果集的类型。它当然比连接更容易理解,但这只是因为存在比连接更容易理解。。。很高兴知道瓶颈的可能性。让我看看我是否得到了这个。。。对于带有optout的post,子查询与元数据行匹配,因此metadata.mdid不为null,因此不会被选中。但是没有optout的帖子,子查询与行不匹配,因此右侧填充了null,因此where子句为true。我在答案中添加了另一个与连接如何工作相关的部分,这应该可以清除任何灰色区域。希望有帮助!