如何在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 JOIN
和RIGHT JOIN
是LEFT JOIN
和RIGHT 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
这是使用left
和right 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)
:全部在t1
或t2
(t1 ∪ t2) = (t1 ∩ t2) + t1_only + t2_only
:在t1
和t2
全部加上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的外连接简化 ,通过在查询中的FROM
和ON
子句中切换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
顺序是不同的,但否则匹配正确的答案。