繁体   English   中英

如何在 MySQL 中进行 FULL OUTER JOIN?

[英]How can I do a FULL OUTER JOIN in MySQL?

我想在 MySQL 中进行完全外部连接 这可能吗? MySQL 是否支持完全外连接

您在 MySQL 中没有完全连接,但您肯定可以模拟它们

对于从这个 Stack Overflow 问题转录的代码示例,您有:

有两个表 t1、t2:

SELECT * FROM t1
LEFT JOIN t2 ON t1.id = t2.id
UNION
SELECT * FROM t1
RIGHT JOIN t2 ON t1.id = t2.id

上面的查询适用于完全外连接操作不会产生任何重复行的特殊情况。 上面的查询依赖于UNION集合运算符来删除查询模式引入的重复行。 我们可以通过对第二个查询使用反连接模式来避免引入重复行,然后使用 UNION ALL 集合运算符来组合两个集合。 在更一般的情况下,全外连接会返回重复的行,我们可以这样做:

SELECT * FROM t1
LEFT JOIN t2 ON t1.id = t2.id
UNION ALL
SELECT * FROM t1
RIGHT JOIN t2 ON t1.id = t2.id
WHERE t1.id IS NULL

Pablo Santa Cruz给出的答案是正确的; 但是,如果有人偶然发现此页面并想要更多说明,这里有一个详细的分类。

示例表

假设我们有以下表格:

-- t1
id  name
1   Tim
2   Marta

-- t2
id  name
1   Tim
3   Katarina

内部连接

一个内部连接,像这样:

SELECT *
FROM `t1`
INNER JOIN `t2` ON `t1`.`id` = `t2`.`id`;

只会让我们看到出现在两个表中的记录,如下所示:

1 Tim  1 Tim

内连接没有方向(如左或右),因为它们是明确的双向的——我们需要两边都匹配。

外连接

另一方面,外连接用于查找在另一个表中可能不匹配的记录。 因此,您必须指定允许连接的哪一侧丢失记录。

LEFT JOINRIGHT JOINLEFT OUTER JOINRIGHT OUTER JOIN简写; 我将在下面使用它们的全名来强调外连接与内连接的概念。

左外连接

一个左外连接,像这样:

SELECT *
FROM `t1`
LEFT OUTER JOIN `t2` ON `t1`.`id` = `t2`.`id`;

...将从左表中获取所有记录,无论它们是否在右表中匹配,如下所示:

1 Tim   1    Tim
2 Marta NULL NULL

右外连接

一个右外连接,像这样:

SELECT *
FROM `t1`
RIGHT OUTER JOIN `t2` ON `t1`.`id` = `t2`.`id`;

...将从右表中获取所有记录,无论它们在左表中是否匹配,如下所示:

1    Tim   1  Tim
NULL NULL  3  Katarina

全外连接

完整的外连接将为我们提供来自两个表的所有记录,无论它们在另一个表中是否有匹配项,在没有匹配项的两侧都为 NULL。 结果如下所示:

1    Tim   1    Tim
2    Marta NULL NULL
NULL NULL  3    Katarina

然而,正如 Pablo Santa Cruz 指出的,MySQL 不支持这一点。 我们可以通过对左连接和右连接进行 UNION 来模拟它,如下所示:

SELECT *
FROM `t1`
LEFT OUTER JOIN `t2` ON `t1`.`id` = `t2`.`id`

UNION

SELECT *
FROM `t1`
RIGHT OUTER JOIN `t2` ON `t1`.`id` = `t2`.`id`;

您可以将UNION视为“运行这两个查询,然后将结果堆叠在一起”; 一些行将来自第一个查询,一些来自第二个查询。

需要注意的是,MySQL 中的UNION会消除完全重复:Tim 会出现在此处的两个查询中,但UNION的结果只列出他一次。 我的数据库专家同事认为不应依赖这种行为。 因此,为了更明确地说明它,我们可以在第二个查询中添加一个WHERE子句:

SELECT *
FROM `t1`
LEFT OUTER JOIN `t2` ON `t1`.`id` = `t2`.`id`

UNION

SELECT *
FROM `t1`
RIGHT OUTER JOIN `t2` ON `t1`.`id` = `t2`.`id`
WHERE `t1`.`id` IS NULL;

另一方面,如果您出于某种原因查看重复项,则可以使用UNION ALL

使用联合查询将删除重复项,这与从不删除任何重复项的完全外连接的行为不同:

[Table: t1]        [Table: t2]
value              value
-----------        -------
1                  1
2                  2
4                  2
4                  5

这是完整外连接的预期结果:

value | value
------+-------
1     | 1
2     | 2
2     | 2
Null  | 5
4     | Null
4     | Null

这是使用leftright joinunion 的结果

value | value
------+-------
Null  | 5
1     | 1
2     | 2
4     | Null

SQL小提琴

我建议的查询是:

select
    t1.value, t2.value
from t1
left outer join t2
  on t1.value = t2.value
union all      -- Using `union all` instead of `union`
select
    t1.value, t2.value
from t2
left outer join t1
  on t1.value = t2.value
where
    t1.value IS NULL

上述查询的结果与预期结果相同:

value | value
------+-------
1     | 1
2     | 2
2     | 2
4     | NULL
4     | NULL
NULL  | 5

SQL小提琴


@Steve Chambers[来自评论,非常感谢!]

注意:这可能是最佳解决方案,无论是为了效率还是生成与FULL OUTER JOIN相同的结果。 这篇博客文章也很好地解释了它 - 引用方法 2: “这正确处理重复的行并且不包含任何它不应该包含的内容。有必要使用UNION ALL而不是普通的UNION ,这将消除我想要的重复行保留。这在大型结果集上可能会更有效,因为不需要排序和删除重复项。”


我决定添加另一个来自全外连接可视化和数学的解决方案。 它并不比上面的好,但它更具可读性:

全外连接意味着(t1 ∪ t2) :所有在t1或在t2 (t1 ∪ t2) = (t1 ∩ t2) + t1_only + t2_only :所有在t1t2加上在t1中的所有不在t2和加所有t2不在t1

-- (t1 ∩ t2): all in both t1 and t2
select t1.value, t2.value
from t1 join t2 on t1.value = t2.value
union all  -- And plus
-- all in t1 that not exists in t2
select t1.value, null
from t1
where not exists( select 1 from t2 where t2.value = t1.value)
union all  -- and plus
-- all in t2 that not exists in t1
select null, t2.value
from t2
where not exists( select 1 from t1 where t2.value = t1.value)

SQL小提琴

MySQL 没有 FULL-OUTER-JOIN 语法。 您必须通过执行 LEFT JOIN 和 RIGHT JOIN 来模拟它,如下所示:

SELECT * FROM t1
LEFT JOIN t2 ON t1.id = t2.id
UNION
SELECT * FROM t1
RIGHT JOIN t2 ON t1.id = t2.id

但是 MySQL 也没有 RIGHT JOIN 语法。 根据 MySQL 的外连接简化,通过在查询中的FROMON子句中切换 t1 和 t2,将右连接转换为等效的左连接。 因此,MySQL 查询优化器将原始查询转换为以下内容 -

SELECT * FROM t1
LEFT JOIN t2 ON t1.id = t2.id
UNION
SELECT * FROM t2
LEFT JOIN t1 ON t2.id = t1.id

现在,按原样编写原始查询没有什么坏处,但是如果您有像 WHERE 子句这样的谓词,它是连接前谓词或ON子句上的 AND 谓词(连接期间谓词),那么你可能想看看魔鬼; 这是细节。

MySQL 查询优化器会例行检查谓词是否为空拒绝

Null-Rejected 定义和示例

现在,如果您已经完成了 RIGHT JOIN,但是在来自 t1 的列上使用 WHERE 谓词,那么您可能会面临遇到空拒绝场景的风险。

例如,查询

SELECT * FROM t1
LEFT JOIN t2 ON t1.id = t2.id
WHERE t1.col1 = 'someValue'
UNION
SELECT * FROM t1
RIGHT JOIN t2 ON t1.id = t2.id
WHERE t1.col1 = 'someValue'

由查询优化器转换为以下内容:

SELECT * FROM t1
LEFT JOIN t2 ON t1.id = t2.id
WHERE t1.col1 = 'someValue'
UNION
SELECT * FROM t2
LEFT JOIN t1 ON t2.id = t1.id
WHERE t1.col1 = 'someValue'

所以表的顺序已经改变,但谓词仍然适用于 t1,但 t1 现在在 'ON' 子句中。 如果 t1.col1 被定义为NOT NULL列,那么这个查询将是null-rejected

任何拒绝空值的外连接(左、右、全)都会被 MySQL 转换为内连接。

因此,您可能期望的结果可能与 MySQL 返回的结果完全不同。 您可能认为这是 MySQL 的 RIGHT JOIN 的一个错误,但这是不对的。 它就是 MySQL 查询优化器的工作原理。 因此,负责的开发人员在构建查询时必须注意这些细微差别。

以前的答案实际上都不正确,因为当存在重复值时,它们不遵循语义。

对于诸如(来自此副本)之类的查询:

SELECT * FROM t1 FULL OUTER JOIN t2 ON t1.Name = t2.Name;

正确的等价物是:

SELECT t1.*, t2.*
FROM (SELECT name FROM t1 UNION  -- This is intentionally UNION to remove duplicates
      SELECT name FROM t2
     ) n LEFT JOIN
     t1
     ON t1.name = n.name LEFT JOIN
     t2
     ON t2.name = n.name;

如果您需要它来处理NULL值(这也可能是必要的),请使用NULL比较运算符<=>而不是=

在 SQLite 中你应该这样做:

SELECT * 
FROM leftTable lt 
LEFT JOIN rightTable rt ON lt.id = rt.lrid 
UNION
SELECT lt.*, rl.*  -- To match column set
FROM rightTable rt 
LEFT JOIN  leftTable lt ON lt.id = rt.lrid

为了更清晰,我修改了shA.t 的查询

-- t1 left join t2
SELECT t1.value, t2.value
FROM t1 LEFT JOIN t2 ON t1.value = t2.value   

    UNION ALL -- include duplicates

-- t1 right exclude join t2 (records found only in t2)
SELECT t1.value, t2.value
FROM t1 RIGHT JOIN t2 ON t1.value = t2.value
WHERE t1.value IS NULL 

您可以执行以下操作:

(SELECT 
    *
FROM
    table1 t1
        LEFT JOIN
    table2 t2 ON t1.id = t2.id
WHERE
    t2.id IS NULL)
UNION ALL
 (SELECT 
    *
FROM
    table1 t1
        RIGHT JOIN
    table2 t2 ON t1.id = t2.id
WHERE
    t1.id IS NULL);
SELECT
    a.name,
    b.title
FROM
    author AS a
LEFT JOIN
    book AS b
    ON a.id = b.author_id
UNION
SELECT
    a.name,
    b.title
FROM
    author AS a
RIGHT JOIN
    book AS b
    ON a.id = b.author_id

你可以只转换一个完整的外连接,例如

SELECT fields
FROM firsttable
FULL OUTER JOIN secondtable ON joincondition

进入:

SELECT fields
FROM firsttable
LEFT JOIN secondtable ON joincondition
UNION ALL
SELECT fields (replacing any fields from firsttable with NULL)
FROM secondtable
WHERE NOT EXISTS (SELECT 1 FROM firsttable WHERE joincondition)

或者,如果您至少有一个列,例如foo ,则在firsttable一个非空的表中,您可以执行以下操作:

SELECT fields
FROM firsttable
LEFT JOIN secondtable ON joincondition
UNION ALL
SELECT fields
FROM firsttable
RIGHT JOIN secondtable ON joincondition
WHERE firsttable.foo IS NULL

我修复了响应,并且工作包括所有行(基于 Pavle Lekic 的响应):

    (
    SELECT a.* FROM tablea a
    LEFT JOIN tableb b ON a.`key` = b.key
    WHERE b.`key` is null
    )
    UNION ALL
    (
    SELECT a.* FROM tablea a
    LEFT JOIN tableb b ON a.`key` = b.key
    where  a.`key` = b.`key`
    )
    UNION ALL
    (
    SELECT b.* FROM tablea a
    right JOIN tableb b ON b.`key` = a.key
    WHERE a.`key` is null
    );

利用:

SELECT * FROM t1 FULL OUTER JOIN t2 ON t1.id = t2.id;

它可以重新创建如下:

 SELECT t1.*, t2.*
 FROM (SELECT * FROM t1 UNION SELECT name FROM t2) tmp
 LEFT JOIN t1 ON t1.id = tmp.id
 LEFT JOIN t2 ON t2.id = tmp.id;

使用 UNION 或 UNION ALL 答案不涵盖基表具有重复条目的边缘情况。

解释:

存在一个 UNION 或 UNION ALL 无法覆盖的边缘情况。 我们无法在 MySQL 上对此进行测试,因为它不支持完全外部联接,但我们可以在支持它的数据库上说明这一点:

 WITH cte_t1 AS
 (
     SELECT 1 AS id1
     UNION ALL SELECT 2
     UNION ALL SELECT 5
     UNION ALL SELECT 6
     UNION ALL SELECT 6
 ),
cte_t2 AS
(
     SELECT 3 AS id2
     UNION ALL SELECT 4
     UNION ALL SELECT 5
     UNION ALL SELECT 6
     UNION ALL SELECT 6
)
SELECT  *  FROM  cte_t1 t1 FULL OUTER JOIN cte_t2 t2 ON t1.id1 = t2.id2;

This gives us this answer:

id1  id2
1  NULL
2  NULL
NULL  3
NULL  4
5  5
6  6
6  6
6  6
6  6

联合解决方案:

SELECT  * FROM  cte_t1 t1 LEFT OUTER JOIN cte_t2 t2 ON t1.id1 = t2.id2
UNION    
SELECT  * FROM cte_t1 t1 RIGHT OUTER JOIN cte_t2 t2 ON t1.id1 = t2.id2

给出了错误的答案:

 id1  id2
NULL  3
NULL  4
1  NULL
2  NULL
5  5
6  6

UNION ALL 解决方案:

SELECT  * FROM cte_t1 t1 LEFT OUTER join cte_t2 t2 ON t1.id1 = t2.id2
UNION ALL
SELECT  * FROM  cte_t1 t1 RIGHT OUTER JOIN cte_t2 t2 ON t1.id1 = t2.id2

也是不对的。

id1  id2
1  NULL
2  NULL
5  5
6  6
6  6
6  6
6  6
NULL  3
NULL  4
5  5
6  6
6  6
6  6
6  6

而这个查询:

SELECT t1.*, t2.*
FROM (SELECT * FROM t1 UNION SELECT name FROM t2) tmp
LEFT JOIN t1 ON t1.id = tmp.id
LEFT JOIN t2 ON t2.id = tmp.id;

给出以下内容:

id1  id2
1  NULL
2  NULL
NULL  3
NULL  4
5  5
6  6
6  6
6  6
6  6

顺序不同,但在其他方面与正确答案匹配。

使用交叉连接解决方案:

SELECT t1.*, t2.*
FROM table1 t1
INNER JOIN table2 t2 
ON 1=1;

也可以,但是你必须在select中提到相同的字段名称。

SELECT t1.name, t2.name FROM t1
LEFT JOIN t2 ON t1.id = t2.id
UNION
SELECT t1.name, t2.name FROM t2
LEFT JOIN t1 ON t1.id = t2.id

SQL 标准说full join on是行inner join on并由空值扩展的union all不匹配的左表行union all由空值扩展的右表行。 即行inner join on union all left join on union all行但不是inner join on union all right join on union all行但不inner join on

left join onunion all right join on行不在inner join on 或者,如果你知道你的inner join on结果不能在一个特定的右表列空,则“ right join on行没有inner join on ”都行中right join onon条件的延长and该列is null

即类似地right join on union all适当的left join on行。

“INNER JOIN”和“OUTER JOIN”有什么区别?

(SQL Standard 2006 SQL/Foundation 7.7 Syntax Rules 1, General Rules 1 b, 3 c & d, 5 b.)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM