How to do a FULL OUTER JOIN in MySQL?
我想在MySQL中做一个完整的外部连接。这有可能吗?mysql支持完全外部连接吗?
你在MySQL上没有完全的连接,但是你可以仿效它们。
对于从这个问题中转录的代码示例,您有:
有两张表T1、T2:
1 2 3 4 5 | SELECT * FROM t1 LEFT JOIN t2 ON t1.id = t2.id UNION SELECT * FROM t1 RIGHT JOIN t2 ON t1.id = t2.id |
上面的查询适用于完全外部联接操作不会产生任何重复行的特殊情况。上面的查询依赖于
1 2 3 4 5 6 | 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给出的答案是正确的;但是,如果有人无意中看到这个页面,想要更多的解释,这里有一个详细的细目。
示例表假设我们有以下表格:
1 2 3 4 5 6 7 8 9 | -- t1 id name 1 Tim 2 Marta -- t2 id name 1 Tim 3 Katarina |
内连接
内部连接,如:
1 2 3 | SELECT * FROM `t1` INNER JOIN `t2` ON `t1`.`id` = `t2`.`id`; |
将只获取出现在两个表中的记录,如下所示:
1 | 1 Tim 1 Tim |
内部连接没有方向(如左或右),因为它们是显式双向的-我们需要在两侧匹配。
外连接另一方面,外部联接用于查找在另一个表中可能不匹配的记录。因此,您必须指定允许联接的哪一侧有丢失的记录。
左外部连接,如:
1 2 3 | SELECT * FROM `t1` LEFT OUTER JOIN `t2` ON `t1`.`id` = `t2`.`id`; |
…将从左表中获取所有记录,不管它们是否在右表中匹配,如下所示:
1 2 | 1 Tim 1 Tim 2 Marta NULL NULL |
右外部联接
右外部连接,如:
1 2 3 | SELECT * FROM `t1` RIGHT OUTER JOIN `t2` ON `t1`.`id` = `t2`.`id`; |
…将从右表中获取所有记录,不管它们是否在左表中匹配,如下所示:
1 2 | 1 Tim 1 Tim NULL NULL 3 Katarina |
全外部连接
完整的外部联接将为我们提供来自两个表的所有记录,无论它们在另一个表中是否有匹配项,在没有匹配项的两侧都有空值。结果如下:
1 2 3 | 1 Tim 1 Tim 2 Marta NULL NULL NULL NULL 3 Katarina |
但是,正如pablo-santa-cruz指出的那样,mysql不支持这个。我们可以通过执行左联接和右联接的联合来模拟它,如下所示:
1 2 3 4 5 6 7 8 9 | 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`; |
您可以将
需要注意的是,MySQL中的
1 2 3 4 5 6 7 8 9 10 | 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; |
另一方面,如果您出于某种原因想要查看副本,可以使用
使用
1 2 3 4 5 6 7 | [TABLE: t1] [TABLE: t2] VALUE VALUE ------- ------- 1 1 2 2 4 2 4 5 |
这是
1 2 3 4 5 6 7 8 | VALUE | VALUE ------+------- 1 | 1 2 | 2 2 | 2 NULL | 5 4 | NULL 4 | NULL |
这是使用
1 2 3 4 5 6 | VALUE | VALUE ------+------- NULL | 5 1 | 1 2 | 2 4 | NULL |
我建议的问题是:
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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 |
上述查询结果与预期结果相同:
1 2 3 4 5 6 7 8 | VALUE | VALUE ------+------- 1 | 1 2 | 2 2 | 2 4 | NULL 4 | NULL NULL | 5 |
@Steve Chambers: [From comments, with many thanks!]
Note: This may be the best solution, both for efficiency and for generating the same results as aFULL OUTER JOIN . This blog post also explains it well - to quote from Method 2:"This handles duplicate rows correctly and doesn’t include anything it shouldn’t. It’s necessary to useUNION ALL instead of plainUNION , which would eliminate the duplicates I want to keep. This may be significantly more efficient on large result sets, since there’s no need to sort and remove duplicates."
我决定添加来自
Full outer join means
(t1 ∪ t2) : all int1 or int2
(t1 ∪ t2) = (t1 ∩ t2) + t1_only + t2_only : all in botht1 andt2 plus all int1 that aren't int2 and plus all int2 that aren't int1 :
1 2 3 4 5 6 7 8 9 10 11 12 13 | -- (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) |
MySQL没有完全外部联接语法。您必须通过执行左连接和右连接来模拟,如下所示-
1 2 3 4 5 | SELECT * FROM t1 LEFT JOIN t2 ON t1.id = t2.id UNION SELECT * FROM t1 RIGHT JOIN t2 ON t1.id = t2.id |
但是MySQL也没有正确的连接语法。根据mysql的外部连接简化,通过切换查询中
1 2 3 4 5 | SELECT * FROM t1 LEFT JOIN t2 ON t1.id = t2.id UNION SELECT * FROM t2 LEFT JOIN t1 ON t2.id = t1.id |
现在,编写原始查询没有什么坏处,但是假设您有像WHERE子句这样的谓词,它是before join谓词,或者是
如果谓词被空拒绝,MySQL查询优化器会定期检查它们。现在,如果您已经完成了正确的连接,但是使用了T1列上的WHERE谓词,那么您可能会遇到一个被空拒绝的场景。
例如,以下查询-
1 2 3 4 5 6 7 | 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' |
由查询优化器转换为以下内容-
1 2 3 4 5 6 7 | 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被定义为
任何被拒绝为空的外部联接(左、右、满)都将被MySQL转换为内部联接。
因此,您可能期望的结果可能与MySQL返回的结果完全不同。你可能认为这是MySQL正确连接的一个bug,但这不正确。这就是MySQL查询优化器的工作原理。因此,负责开发的开发人员在构建查询时必须注意这些细微差别。
上面的答案实际上都不正确,因为当存在重复值时,它们不遵循语义。
对于查询,例如(来自此副本):
1 | SELECT * FROM t1 FULL OUTER JOIN t2 ON t1.Name = t2.Name; |
正确的等效值为:
1 2 3 4 5 6 7 8 | 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; |
如果需要使用
在sqlite中,您应该这样做:
1 2 3 4 5 6 7 | 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的查询以获得更清晰的信息:
1 2 3 4 5 6 7 8 9 10 | -- 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 |
您可以执行以下操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | (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); |
您对交叉连接解决方案有何看法?
1 2 3 4 | SELECT t1.*, t2.* FROM table1 t1 INNER JOIN table2 t2 ON 1=1; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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 |
我修复了响应,并且工作包括所有行(基于pavle lekic的响应)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | ( 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中提到相同的字段名。
1 2 3 4 5 | 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标准称
即
也就是说,
"内部联接"和"外部联接"的区别是什么?:
(SQL Standard 2006 SQL/Foundation 7.7 Syntax Rules 1, General Rules 1 b, 3 c & d, 5 b.)
答:
1 | SELECT * FROM t1 FULL OUTER JOIN t2 ON t1.id = t2.id; |
可以如下重新创建:
1 2 3 4 | 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答案不包括基表具有重复项的边缘情况。
说明:
有一个边缘情况,一个联合或联合都不能覆盖。我们不能在MySQL上测试它,因为它不支持完整的外部连接,但是我们可以在支持它的数据库上说明这一点:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | 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 |
联合解决方案:
1 2 3 | 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 |
给出错误答案:
1 2 3 4 5 6 7 | id1??id2 NULL??3 NULL??4 1??NULL 2??NULL 5??5 6??6 |
联合所有解决方案:
1 2 3 | 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 |
也不正确。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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 |
鉴于此查询:
1 2 3 4 | 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; |
给出以下内容:
1 2 3 4 5 6 7 8 9 10 | id1??id2 1??NULL 2??NULL NULL??3 NULL??4 5??5 6??6 6??6 6??6 6??6 |
顺序不同,但与正确答案不符。