select 语句中的 PostgreSQL 子查询(不合逻辑)错误与连接等效工作?
PostgreSQL subquery (illogical) bug in select statement with join equivalent working?
我在 Postgres 9.5.1 中遇到了一些有趣的错误
我有 2 个 table 包含相关数据 contacts (id, name)
和 jobs (id, contact_id, name)
。
我不确定这个查询的有效性(鉴于后面解释的奇怪行为)。
-- get unassigned contacts
select * from contacts where id not in (select contact_id from jobs);
编辑:以下案例是我试图分析问题的方式。查看 post 的末尾和注释以了解查询不正确的原因。
在没有工作的情况下测试联系人 id=20 时,我得到了一些 (IMO) 奇怪的结果(select 查询和等效联接之间的结果没有 table 差异) .
首先,我需要声明一些先决条件(步骤 A)。
接下来,我使用 join 显示结果(步骤 B)。
最后,我使用子查询显示结果(步骤 D)。
(步骤C是D的补充要求,只是在这里强调我发现的奇怪之处)。
A-0。检查两个 tables 中是否有数据:OK
select count(distinct id) from contacts;
--> returns 10100
select count(distinct id) from jobs;
--> returns 12000
select count(distinct id) from contacts where id in (select contact_id from jobs);
--> returns 10000
A-1。在 id=20 的 table 联系人中获取姓名:OK
select name from contacts where id=20;
--> returns "NAME"
A-3。检查联系人 ID=20 不在 table 工作中:好的
select id from jobs where contact_id=20;
--> returns nothing (0 row)
乙。获取联系人 ID=20 的名称和(空)工作 ID 并加入:OK
select c.id, c.name, j.id
from contacts c
left join jobs j
on j.contact_id=c.id
where c.id=20;
--> returns 20, "NAME", <NULL>
C。仅当在工作中分配时才获取联系人 ID=20:OK
select name from contacts where id in (select contact_id from jobs) and id=20;
--> returns nothing (0 row); (that's the expected result)
D.仅当未在工作中分配时才获取联系人 ID=20:KO
select name from contacts where id not in (select contact_id from jobs) and id=20;
--> returns nothing (0 row); (that's not the expected result - "NAME")
有趣的结论
C 和 D 查询得到相同的结果。
从逻辑上讲,这可能意味着在 pgsql 中:
id NOT IN (..values..) == id IN (..values..)
FALSE == TRUE
"Postgres guru" 能给我一个很好的解释吗?还是我应该打电话给 FBI?
结语
按照答案
我的查询
select * from contacts where id not in (select contact_id from jobs);
不正确,因为 NOT IN
无法处理 NULL 值。因此,不正确 selector 检查值的(不)存在性。
参见 NULL values inside NOT IN clause。
正确的查询如下:
-- to get unassigned contacts
select * from contacts c where not exists (select 1 from jobs where contact_id=c.id);
对于指定的 id :
select * from contacts c where not exists (select 1 from jobs where contact_id=c.id) and id=20;
这个查询也有效:
select * from contacts where id not in (select contact_id from jobs where contact_id is not null);
您看到的是 null
安全问题。如果 not in
子查询返回的任何值是 null
,则忽略所有其他值。我们说 not in
不是 null 安全的。
假设子查询 returns: (1, 2, null)
。 not in
条件变为:
id <> 1 and id <> 2 and id <> null
前两个条件为真,但最后一个条件为 unknown
,它污染了整个谓词,即 returns unknown
。结果,所有行都被逐出。
这是通常不鼓励使用 not in
的原因之一。您可以简单地用 not exists
:
重写它
select name
from contacts c
where c.id = 20 and not exists(select 1 from jobs j where j.contact_id = c.id);
我在 Postgres 9.5.1 中遇到了一些有趣的错误
我有 2 个 table 包含相关数据 contacts (id, name)
和 jobs (id, contact_id, name)
。
我不确定这个查询的有效性(鉴于后面解释的奇怪行为)。
-- get unassigned contacts
select * from contacts where id not in (select contact_id from jobs);
编辑:以下案例是我试图分析问题的方式。查看 post 的末尾和注释以了解查询不正确的原因。
在没有工作的情况下测试联系人 id=20 时,我得到了一些 (IMO) 奇怪的结果(select 查询和等效联接之间的结果没有 table 差异) .
首先,我需要声明一些先决条件(步骤 A)。 接下来,我使用 join 显示结果(步骤 B)。 最后,我使用子查询显示结果(步骤 D)。 (步骤C是D的补充要求,只是在这里强调我发现的奇怪之处)。
A-0。检查两个 tables 中是否有数据:OK
select count(distinct id) from contacts;
--> returns 10100
select count(distinct id) from jobs;
--> returns 12000
select count(distinct id) from contacts where id in (select contact_id from jobs);
--> returns 10000
A-1。在 id=20 的 table 联系人中获取姓名:OK
select name from contacts where id=20;
--> returns "NAME"
A-3。检查联系人 ID=20 不在 table 工作中:好的
select id from jobs where contact_id=20;
--> returns nothing (0 row)
乙。获取联系人 ID=20 的名称和(空)工作 ID 并加入:OK
select c.id, c.name, j.id
from contacts c
left join jobs j
on j.contact_id=c.id
where c.id=20;
--> returns 20, "NAME", <NULL>
C。仅当在工作中分配时才获取联系人 ID=20:OK
select name from contacts where id in (select contact_id from jobs) and id=20;
--> returns nothing (0 row); (that's the expected result)
D.仅当未在工作中分配时才获取联系人 ID=20:KO
select name from contacts where id not in (select contact_id from jobs) and id=20;
--> returns nothing (0 row); (that's not the expected result - "NAME")
有趣的结论
C 和 D 查询得到相同的结果。
从逻辑上讲,这可能意味着在 pgsql 中:
id NOT IN (..values..) == id IN (..values..)
FALSE == TRUE
"Postgres guru" 能给我一个很好的解释吗?还是我应该打电话给 FBI?
结语
按照答案
我的查询
select * from contacts where id not in (select contact_id from jobs);
不正确,因为 NOT IN
无法处理 NULL 值。因此,不正确 selector 检查值的(不)存在性。
参见 NULL values inside NOT IN clause。
正确的查询如下:
-- to get unassigned contacts
select * from contacts c where not exists (select 1 from jobs where contact_id=c.id);
对于指定的 id :
select * from contacts c where not exists (select 1 from jobs where contact_id=c.id) and id=20;
这个查询也有效:
select * from contacts where id not in (select contact_id from jobs where contact_id is not null);
您看到的是 null
安全问题。如果 not in
子查询返回的任何值是 null
,则忽略所有其他值。我们说 not in
不是 null 安全的。
假设子查询 returns: (1, 2, null)
。 not in
条件变为:
id <> 1 and id <> 2 and id <> null
前两个条件为真,但最后一个条件为 unknown
,它污染了整个谓词,即 returns unknown
。结果,所有行都被逐出。
这是通常不鼓励使用 not in
的原因之一。您可以简单地用 not exists
:
select name
from contacts c
where c.id = 20 and not exists(select 1 from jobs j where j.contact_id = c.id);