如何在MySQL中做一个完整的外部连接?

我想在MySQL中做一个完全的外连接。 这可能吗? MySQL是否支持完全外部联接?

你没有MySQL上的FULL JOIN,但你可以确定地模拟它们 。

对于从这个SO问题转录的代码SAMPLE,你有:

用两张表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 

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

  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 

帕布罗圣克鲁斯给出的答案是正确的。 但是,如果有人偶然发现这个网页,并希望得到更多的澄清,这里是详细的分类。

示例表

假设我们有以下表格:

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

只会得到我们两个表中出现的logging,像这样:

 1 Tim 1 Tim 

内部连接没有方向(像左或右),因为它们是明确的双向的 – 我们需要双方的匹配。

外连接

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

LEFT JOINRIGHT JOINLEFT JOINRIGHT JOIN连接的缩写; 我将用下面的全名来强化外连接和内连接的概念。

左外连接

左外连接,如下所示:

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

…将从左表中得到我们所有的logging,不pipe它们是否在右表中匹配,如下所示:

 1 Tim 1 Tim 2 Marta NULL NULL 

右外连接

正确的外部连接,如下所示:

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

…会从右表中得到我们所有的logging,不pipe它们是否在左表中匹配,如下所示:

 1 Tim 1 Tim NULL NULL 3 Katarina 

全外联接

一个完整的外连接会给我们两个表中的所有logging,不pipe他们是否在另一个表中有匹配,在两边都没有匹配的地方有NULL。 结果如下所示:

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

但是,正如Pablo Santa Cruz所指出的那样,MySQL不支持这一点。 我们可以通过左连接和右连接的联合来模拟它,如下所示:

 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

使用union查询将删除重复项,这是不同于full outer join的行为永远不会删除任何重复:

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

这是full outer join的预期结果:

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

这是使用leftright Join union

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

[SQL Fiddle]

我build议的查询是:

 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 Fiddle]


@Steve Chambers : [来自评论,非常感谢!]
注意:这可能是最好的解决scheme,既可以提高效率,又可以产生与全外连接相同的结果。 这个博客文章还解释了这一点 – 从方法2引用: “这正确处理重复的行,不包括任何它不应该的。有必要使用UNION ALL而不是普通的UNION ,这将消除我想要的重复保留,对于大的结果集,这可能会更有效率,因为不需要sorting和删除重复项。


我决定添加另外一个来自full outer join可视化和math的解决scheme,它不是上面更好,但更易读:

完全外连接意味着(t1 ∪ t2) :全部在t1t2
(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 Fiddle]

在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 t2.value IS NULL 

上面的答案都不是真正正确的,因为它们在重复值时不遵循语义。

对于(从这个重复的 )查询:

 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 -safe比较运算符<=>而不是=

 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 

你对这个解决scheme有什么看法?

 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 

Mysql本身不支持任何命名为FULL OUTER JOIN的命令。 支持的三个连接是INNER JOIN,LEFT JOIN和RIGHT JOIN。

但是,您可以通过使用Command UNION as实现完全外连接
(左连接查询)UNION(右连接查询)

例如,考虑下面的例子,我有两个表学生和标记。 要执行完整的外连接,我会执行下面的代码:

 SELECT * FROM students LEFT JOIN marks ON students.id = marks.id UNION ALL SELECT * FROM students RIGHT JOIN marks ON students.id = marks.id; 

我解决了这个问题,包括所有的行(基于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 ); 

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谓词,这是一个谓词连接谓词,那么你可能想看看魔鬼; 这是详细的。

MySql查询优化器定期检查谓词是否为空拒绝零拒绝的定义和例子 现在,如果你已经完成了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拒绝的外连接(left,right,full)都被MySql转换为内连接。

因此,您可能期望的结果可能与MySql返回的结果完全不同。 你可能会认为它是一个与MySql的RIGHT JOIN的错误,但那是不对的。 它只是MySql查询优化器的工作原理。 所以主pipe开发人员在构build查询时必须注意这些细微之处。

回答:

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

可以重新创build如下:

  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上testing它,因为它不支持FULL OUTER JOIN,但是我们可以在一个支持它的数据库上进行说明:

  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 

UNION解决scheme:

 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解决scheme:

 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 

顺序是不同的,但否则匹配正确的答案。

Interesting Posts