为什么两个表之间的完全外连接的结果中存在 NULL?

问题描述

我试图从两个表中获取唯一值,这两个表都只有一个名为 domain 的列。

DDL:

create table domains_1 (domain varchar);
create table domains_2 (domain varchar);

DML:

insert into domains_1 values ('example_1.com'),('example_2.com');
insert into domains_2 values ('example_2.com'),('example_3.com');

有几种方法可以做到这一点,我决定使用全外连接。

select case when a.domain is null then b.domain
            when b.domain is null then a.domain
       end as unique_domains
from domains_1 as a full outer join domains_2 as b on a.domain = b.domain;

令我惊讶的是,结果中除了唯一域之外还有 null

enter image description here

我可以再添加一层 select 来排除空值:

select * from
(select case when a.domain is null then b.domain
            when b.domain is null then a.domain
       end as unique_domains
from domains_1 as a full outer join domains_2 as b on a.domain = b.domain) t
where unique_domains is not null;

这个 null 如何首先出现在结果中? 有没有更好的方法可以从结果中删除 null

解决方法

您的 CASE 表达式没有 ELSE,因此默认为 null:

case when a.domain is null then b.domain
     when b.domain is null then a.domain
     ELSE NULL -- implicitly
end as unique_domains

值 'e​​xample_2.com' 具有匹配项,因此 a.domain 和 b.domain 等于 ''example_2.com'' 并且不为空。因此,WHEN 不匹配,ELSE NULL 被应用。

至于“更好的方法”:我可能会使用

select coalesce(a.domain,b.domain) as domain
from domains_1 as a full outer join domains_2 as b on a.domain = b.domain
where a.domain is null or b.domain is null;
,

CASE 列表中的 SELECT 表达式不能删除行(就像您希望的那样)。这必须发生在 JOINWHERE 子句中。

由于您的列名可以方便地对齐,因此请在 join 子句中使用 USING 关键字以简化工作。

要获得“唯一域”(包括示例中的 'example_2.com'):

SELECT domain
FROM   domains_1
FULL   JOIN domains_2 USING (domain);

要分别获取其他表中没有匹配项的域(在您的示例中不包括'example_2.com'):

SELECT domain
FROM   domains_1 a
FULL   JOIN domains_2 b USING (domain)
WHERE  a.domain IS NULL OR b.domain IS NULL;

dbfiddle here

The manual:

[...] USING 意味着每对等效列中只有一个将包含在连接输出中,而不是两者都包含。

但是您仍然可以通过表限定来引用每个源列,正如演示的那样。

还有其他各种查询技术可以消除另一个表中匹配的行:

值得注意的是,除非另一个表中有匹配项,否则上述查询都不会删除每个表内的重复项。

第二个查询的花哨等价物,但每个表中没有可能的重复项:

(TABLE domains_1 EXCEPT TABLE domains_2)
UNION ALL
(TABLE domains_2 EXCEPT TABLE domains_1);

在删除结果中剩余的重复项之前,此变体仅杀死另一个表中每个匹配项的一个重复项。略有不同,但:

(TABLE domains_1 EXCEPT ALL TABLE domains_2)
UNION
(TABLE domains_2 EXCEPT ALL TABLE domains_1);

关于简短的语法: