我想在MySQL中进行完全的外部连接。这可能吗?MySQL是否支持完全外部联接?
您在MySQL中没有完全联接,但您可以模拟它们。
对于从堆栈溢出问题中转录的代码示例,您有:
对于两个表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
巴勃罗·圣克鲁斯给出的答案是正确的;然而,如果有人无意中看到了这一页,并希望得到更多的澄清,这里有一个详细的分类。
示例表
假设我们有以下表格:
-- 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 JOIN和RIGHT JOIN是LEFT OUTER JOIN和RIGHT OUTER JON的简写;我将在下面使用它们的全名来强化外部连接与内部连接的概念。
左侧外部连接
左外连接,如下所示:
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。
在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
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中提到相同的字段名。
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
使用联合查询将删除重复项,这与从不删除任何重复项的完全外部联接的行为不同:
[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
这是使用左联合和右联合的结果:
value | value
------+-------
Null | 5
1 | 1
2 | 2
4 | Null
SQL Fiddle
我建议的查询是:
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
@史蒂夫·钱伯斯:(来自评论,非常感谢!)
注意:这可能是最佳的解决方案,既可以提高效率,也可以生成与FULL OUTER JOIN相同的结果。这篇博客文章也很好地解释了这一点,引用了方法2的话:“这可以正确处理重复的行,并且不包含任何不应该包含的内容。有必要使用UNION ALL而不是普通UNION,这将消除我想要保留的重复项。这在大型结果集上可能会更有效,因为不需要排序和删除重复项。”
我决定添加另一个来自完全外部连接可视化和数学的解决方案。它并不比上述内容更好,但更具可读性:
完全外部连接意味着(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
为了更清晰,我修改了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 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安全比较运算符<=>而不是=。
Use:
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;
这给了我们这样的答案:
id1 id2
1 NULL
2 NULL
NULL 3
NULL 4
5 5
6 6
6 6
6 6
6 6
UNION解决方案:
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
顺序不同,但在其他方面与正确答案匹配。
我修复了响应,工作包括所有行(基于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子句上的AND谓词,它是一个在连接期间的谓词,那么您可能需要看看魔鬼;这是详细的。
MySQL查询优化器定期检查谓词是否被null拒绝。
现在,如果您已经执行了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拒绝。
任何被null拒绝的外部联接(左、右、全)都会被MySQL转换为内部联接。
因此,您可能期望的结果可能与MySQL返回的结果完全不同。您可能会认为MySQL的RIGHT JOIN存在缺陷,但这是不对的。这就是MySQL查询优化器的工作方式。因此,负责开发人员在构建查询时必须注意这些细微差别。
您可以执行以下操作:
(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);
SQL标准表示,上的完全联接是行上的内部联接,所有不匹配的左表行由null扩展,所有右表行由nulls扩展。也就是说,行上的内部联接将左联接中的所有行联合在一起,而不是联合在一起的内部联接。
也就是说,行上的左联接和不在上的内联接中的行上的所有右联接。或者,如果您知道结果的内联接在某个特定的右表列中不能为空,那么“行上的右联接不在上内联接中”是右联接中的列,条件扩展为,并且该列为空。
也就是说,在并集上进行右连接,在行上进行所有适当的左连接。
来自“INNER JOIN”和“OUTER JOIN”之间的区别是什么?:
(SQL标准2006 SQL/Foundation 7.7语法规则1,通用规则1 b,3 c&d,5 b)
您可以只转换一个完整的外部联接,例如。
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)
或者,如果firsttable中至少有一列(比如foo)不为NULL,则可以执行以下操作:
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
推荐文章
- django test app error -在创建测试数据库时出现错误:创建数据库的权限被拒绝
- MySql: Tinyint (2) vs Tinyint(1) -有什么不同?
- 在Android SQLite中处理日期的最佳方法
- 如何在MySQL表中移动列?
- 在MySQL中选择最后一行
- 如何删除MySQL root密码
- 在SQL Server中查找重复的行
- 从DateTime中提取小时(SQL Server 2005)
- LEFT OUTER JOIN如何返回比左表中存在的记录更多的记录?
- 如何用SQL语句计算百分比
- Postgres唯一约束与索引
- SQL Server动态PIVOT查询?
- SQL Server: CROSS JOIN和FULL OUTER JOIN的区别是什么?
- MySQL对重复键更新在一个查询中插入多行
- 向现有表添加主键