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);