SQL JOIN和不同types的JOIN
我在SO和其他一些论坛上经历了许多线索。 所以我想我会总结“ 什么是SQL JOIN? ”和“ 什么是不同types的SQL JOIN? ”。
理论上更好的插图
什么是SQL JOIN
?
SQL JOIN
是一种从两个或多个数据库表中检索数据的方法。
什么是不同的SQL JOIN
?
总共有5个JOIN
。 他们是 :
1. JOIN or INNER JOIN 2. OUTER JOIN 2.1 LEFT OUTER JOIN or LEFT JOIN 2.2 RIGHT OUTER JOIN or RIGHT JOIN 2.3 FULL OUTER JOIN or FULL JOIN 3. NATURAL JOIN 4. CROSS JOIN 5. SELF JOIN
1. JOIN或INNER JOIN:
在这种JOIN
,我们得到了两个表中与条件匹配的所有logging,两个表中不匹配的logging都不会被报告。
换句话说, INNER JOIN
是基于以下唯一的事实:只有表格中的匹配条目才应该列出。
请注意, JOIN
没有任何其他JOIN
关键字(如INNER
, OUTER
, LEFT
等)是INNER JOIN
。 换句话说, JOIN
是INNER JOIN
的语法糖(参见: JOIN和INNER JOIN之间的区别 )。
2.外连接:
OUTER JOIN
检索
或者,一个表中的匹配行和另一个表中的所有行或者所有表中的所有行(无论是否存在匹配都无关紧要)。
外部join有三种:
2.1左外连接或左连接
该连接返回左表中的所有行,并与右表中的匹配行一起返回。 如果在右表中没有匹配的列,则返回NULL
值。
2.2 RIGHT OUTER JOIN或RIGHT JOIN
这个JOIN
返回来自右表的所有行以及来自左表的匹配行。 如果左表中没有匹配的列,则返回NULL
值。
2.3全外连接或全连接
这个JOIN
结合了LEFT OUTER JOIN
和RIGHT OUTER JOIN
。 当条件满足时,它从任一表中返回行,当不匹配时返回NULL
值。
换句话说, OUTER JOIN
是基于这样一个事实:只有一个表格(RIGHT或LEFT)或表格(FULL)中的匹配条目应该列出。
Note that `OUTER JOIN` is a loosened form of `INNER JOIN`.
3.天然连接:
它基于两个条件:
- 在相同名称的所有列上进行
JOIN
。 - 从结果中删除重复的列。
这似乎更多地是理论性的,因此(大概)大多数DBMS甚至不打扰这种支持。
4.交叉连接:
这是两个表格的笛卡尔积。 CROSS JOIN
的结果在大多数情况下是没有意义的。 而且,我们根本不需要这个(或者至less需要精确的)。
5.自我join:
它不是一种不同的JOIN
forms,而是它自己的一个表( INNER
, OUTER
等)。
基于运营商join
根据用于JOIN
子句的运算符,可以有两种types的JOIN
。 他们是
- Equijoin
- Theta JOIN
1. Equi JOIN:
对于任何JOIN
types( INNER
, OUTER
等),如果我们只使用相等运算符(=),那么我们说JOIN
是一个EQUI JOIN
。
2. Theta JOIN:
这与EQUI JOIN
相同,但允许所有其他操作符如>,<,> =等
许多人认为
EQUI JOIN
和ThetaJOIN
类似于INNER
,OUTER
等JOIN
s。 但是我强烈地相信这是一个错误,使观点模糊。 因为INNER JOIN
,OUTER JOIN
等都与表格及其数据连接,因为EQUI JOIN
和THETA JOIN
只与我们在前者使用的操作员连接。再一次,有很多人把
NATURAL JOIN
看作某种“奇特”的EQUI JOIN
。 事实上,这是事实,因为我提到的NATURAL JOIN
的第一个条件。 但是,我们不必将其仅限于NATURAL JOIN
s。INNER JOIN
s,OUTER JOIN
s等也可以是EQUI JOIN
。
定义:
连接可以同时查询多个表中的数据。
JOIN的types:
关注RDBMS有5种types的连接:
-
Equi-Join:根据相等条件组合两个表中的常用logging。 从技术上说,通过使用等号运算符(=)来比较一个表的PrimaryKey和另一个表的ForiegnKey值的值,因此结果集包括来自两个表的公共(匹配)logging。 有关实现,请参阅INNER-JOIN。
-
Natural-Join:它是Equi-Join的增强版本,其中SELECT操作省略了重复的列。 有关实现,请参阅INNER-JOIN
-
Non-Equi-Join:与Equi-join相反,其中连接条件不是等于运算符(=),例如!=,<=,> =,>,<或者BETWEEN等。实现参见INNER-JOIN。
-
自我join:自定义join的行为,其中表与自身结合; 这通常用于查询自引用表(或Unary关系实体)。 有关实现,请参阅INNER-JOIN。
-
笛卡儿积:它交叉结合两个表的所有logging,没有任何条件。 从技术上讲,它返回没有WHERE-Clause的查询的结果集。
根据SQL的关注和进步,有3种types的连接,所有的RDBMS连接都可以通过这些types的连接进行连接。
-
INNER-JOIN:它从两个表中合并(或组合)匹配的行。 匹配是基于表格的公共列和它们的比较操作完成的。 如果基于均值的条件则:EQUI-JOIN执行,否则为非EQUI-Join。
-
** OUTER-JOIN:**它合并(或组合)来自两个表的匹配行和具有NULL值的不匹配行。 但是,可以自定义select不匹配的行,例如,通过子types从第一个表或第二个表中select不匹配的行:LEFT OUTER JOIN和RIGHT OUTER JOIN。
2.1。 左外连接 (aka,LEFT-JOIN):从两个表中返回匹配的行,并仅从LEFT表(即第一个表)中取消configuration。
2.2。 右外连接 (又名,右连接):从两个表中返回匹配的行,并且仅从RIGHT表中不匹配。
2.3。 FULL OUTER JOIN (又名OUTER JOIN):从两个表中返回匹配和不匹配。
-
CROSS-JOIN:这个连接不会合并/组合,而是执行cartisian产品。
注意:根据需要,可以通过INNER-JOIN,OUTER-JOIN和CROSS-JOIN来实现自连接,但表必须与自身连接。
了解更多信息:
例子:
1.1:INNER-JOIN:Equi-join实现
SELECT * FROM Table1 A INNER JOIN Table2 B ON A.<PrimaryKey> =B.<ForeignKey>;
1.2:内部连接:自然连接实现
Select A.*, B.Col1, B.Col2 --But no B.ForiengKyeColumn in Select FROM Table1 A INNER JOIN Table2 B On A.Pk = B.Fk;
1.3:INNER-JOIN与非Eqijoin实现
Select * FROM Table1 A INNER JOIN Table2 B On A.Pk <= B.Fk;
1.4:内部join自join
Select * FROM Table1 A1 INNER JOIN Table1 A2 On A1.Pk = A2.Fk;
2.1:外连接(全外连接)
Select * FROM Table1 A FULL OUTER JOIN Table2 B On A.Pk = B.Fk;
2.2:左连接
Select * FROM Table1 A LEFT OUTER JOIN Table2 B On A.Pk = B.Fk;
2.3:右连接
Select * FROM Table1 A RIGHT OUTER JOIN Table2 B On A.Pk = B.Fk;
3.1:交叉连接
Select * FROM TableA CROSS JOIN TableB;
3.2:交叉连接 – 自我连接
Select * FROM Table1 A1 CROSS JOIN Table1 A2;
//要么//
Select * FROM Table1 A1,Table1 A2;
有趣的是,大多数其他答案遭受这两个问题:
- 他们只关注基本的连接forms
- 他们(AB)使用维恩图, 这是一个不准确的工具,可视化连接(他们对工会更好) 。
我最近写了一篇关于这个主题的文章: 一个可能不完整的,综合指南,SQL的JOIN表的许多不同的方法 ,我将在这里总结。
首先,JOIN是笛卡尔产品
这就是维恩图解释不准确的原因,因为JOIN在两个连接的表格之间创build了一个笛卡尔积 。 维基百科很好地说明了这一点:
笛卡尔积的SQL语法是CROSS JOIN
。 例如:
SELECT * -- This just generates all the days in January 2017 FROM generate_series( '2017-01-01'::TIMESTAMP, '2017-01-01'::TIMESTAMP + INTERVAL '1 month -1 day', INTERVAL '1 day' ) AS days(day) -- Here, we're combining all days with all departments CROSS JOIN departments
它将一个表中的所有行与另一个表中的所有行组合在一起:
资源:
+--------+ +------------+ | day | | department | +--------+ +------------+ | Jan 01 | | Dept 1 | | Jan 02 | | Dept 2 | | ... | | Dept 3 | | Jan 30 | +------------+ | Jan 31 | +--------+
结果:
+--------+------------+ | day | department | +--------+------------+ | Jan 01 | Dept 1 | | Jan 01 | Dept 2 | | Jan 01 | Dept 3 | | Jan 02 | Dept 1 | | Jan 02 | Dept 2 | | Jan 02 | Dept 3 | | ... | ... | | Jan 31 | Dept 1 | | Jan 31 | Dept 2 | | Jan 31 | Dept 3 | +--------+------------+
如果我们只写一个逗号分隔的表格列表,我们会得到相同的结果:
-- CROSS JOINing two tables: SELECT * FROM table1, table2
INNER JOIN(Theta-JOIN)
一个INNER JOIN
只是一个过滤的CROSS JOIN
,其中过滤谓词在关系代数中被称为Theta
。
例如:
SELECT * -- Same as before FROM generate_series( '2017-01-01'::TIMESTAMP, '2017-01-01'::TIMESTAMP + INTERVAL '1 month -1 day', INTERVAL '1 day' ) AS days(day) -- Now, exclude all days/departments combinations for -- days before the department was created JOIN departments AS d ON day >= d.created_at
请注意关键字INNER
是可选的(MS Access中除外)。
( 看结果的例子 )
EQUIjoin
一种特殊的Theta-JOIN是我们最常用的equi JOIN。 谓词将一个表的主键与另一个表的外键联接起来。 如果我们使用Sakila数据库进行说明,我们可以这样写:
SELECT * FROM actor AS a JOIN film_actor AS fa ON a.actor_id = fa.actor_id JOIN film AS f ON f.film_id = fa.film_id
这将所有演员与他们的电影结合起来
或者在一些数据库上:
SELECT * FROM actor JOIN film_actor USING (actor_id) JOIN film USING (film_id)
USING()
语法允许指定必须出现在JOIN操作表的任一侧的列,并在这两列上创build一个相等谓词。
NATURAL JOIN
其他答案已经分别列出了这个“JOINtypes”,但这没有任何意义。 这只是一个equi JOIN的语法糖forms,这是Theta-JOIN或INNER JOIN的特例。 NATURAL JOIN简单地收集所有正在连接的表所共有的列,并将这些列连接到USING()
。 由于偶然的匹配(如Sakila数据库中的LAST_UPDATE
列),这几乎不会有用。
这里是语法:
SELECT * FROM actor NATURAL JOIN film_actor NATURAL JOIN film
外部连接
现在, OUTER JOIN
与INNER JOIN
有些不同,因为它创build了几个笛卡尔产品的UNION
。 我们可以写:
-- Convenient syntax: SELECT * FROM a LEFT JOIN b ON <predicate> -- Cumbersome, equivalent syntax: SELECT a.*, b.* FROM a JOIN b ON <predicate> UNION ALL SELECT a.*, NULL, NULL, ..., NULL FROM a WHERE NOT EXISTS ( SELECT * FROM b WHERE <predicate> )
没有人想写后者,所以我们编写OUTER JOIN
(通常更好的数据库优化)。
像INNER
一样,这里的关键字OUTER
是可选的。
OUTER JOIN
有三种口味:
-
LEFT [ OUTER ] JOIN
:JOIN
expression式的左表被添加到如上所示的联合中。 -
RIGHT [ OUTER ] JOIN
:如上所示,JOIN
expression式的右表被添加到联合中。 -
FULL [ OUTER ] JOIN
:JOIN
expression式的两个表都被添加到如上所示的联合中。
所有这些都可以与关键字USING()
或NATURAL
(与最近的NATURAL FULL JOIN
实际上有一个真实世界的用例 )
其他语法
在Oracle和SQL Server中有一些历史的,不推荐使用的语法,在SQL标准有这样的语法之前已经支持OUTER JOIN
:
-- Oracle SELECT * FROM actor a, film_actor fa, film f WHERE a.actor_id = fa.actor_id(+) AND fa.film_id = f.film_id(+) -- SQL Server SELECT * FROM actor a, film_actor fa, film f WHERE a.actor_id *= fa.actor_id AND fa.film_id *= f.film_id
话虽如此,不要使用这种语法。 我只是在这里列出,所以你可以从旧的博客文章/遗产代码识别它。
分区OUTER JOIN
很less有人知道这一点,但SQL标准指定了分区OUTER JOIN
(和Oracle实现它)。 你可以写这样的东西:
WITH -- Using CONNECT BY to generate all dates in January days(day) AS ( SELECT DATE '2017-01-01' + LEVEL - 1 FROM dual CONNECT BY LEVEL <= 31 ), -- Our departments departments(department, created_at) AS ( SELECT 'Dept 1', DATE '2017-01-10' FROM dual UNION ALL SELECT 'Dept 2', DATE '2017-01-11' FROM dual UNION ALL SELECT 'Dept 3', DATE '2017-01-12' FROM dual UNION ALL SELECT 'Dept 4', DATE '2017-04-01' FROM dual UNION ALL SELECT 'Dept 5', DATE '2017-04-02' FROM dual ) SELECT * FROM days LEFT JOIN departments PARTITION BY (department) -- This is where the magic happens ON day >= created_at
结果的一部分:
+--------+------------+------------+ | day | department | created_at | +--------+------------+------------+ | Jan 01 | Dept 1 | | -- Didn't match, but still get row | Jan 02 | Dept 1 | | -- Didn't match, but still get row | ... | Dept 1 | | -- Didn't match, but still get row | Jan 09 | Dept 1 | | -- Didn't match, but still get row | Jan 10 | Dept 1 | Jan 10 | -- Matches, so get join result | Jan 11 | Dept 1 | Jan 10 | -- Matches, so get join result | Jan 12 | Dept 1 | Jan 10 | -- Matches, so get join result | ... | Dept 1 | Jan 10 | -- Matches, so get join result | Jan 31 | Dept 1 | Jan 10 | -- Matches, so get join result
这里的要点是,连接的分区一侧的所有行将在结果中结束,而不pipeJOIN
是否与“ JOIN
的另一侧”上的任何内容匹配。 长话短说:这是填补报告中稀疏的数据。 很有用!
半联接
真的吗? 没有其他答案得到这个? 当然不是, 因为在SQL中没有本地语法,不幸的是 (就像下面的ANTI JOIN一样)。 但是我们可以使用IN()
和EXISTS()
,例如查找所有在电影中演员的演员:
SELECT * FROM actor a WHERE EXISTS ( SELECT * FROM film_actor fa WHERE a.actor_id = fa.actor_id )
WHERE a.actor_id = fa.actor_id
谓词充当半连接谓词。 如果您不相信,请查看执行计划,例如在Oracle中。 您将看到数据库执行SEMI JOIN操作,而不是EXISTS()
谓词。
反联接
这与SEMI JOIN正好相反( 注意不要使用NOT IN
,因为它有一个重要的警告)
这里是所有没有电影的演员:
SELECT * FROM actor a WHERE NOT EXISTS ( SELECT * FROM film_actor fa WHERE a.actor_id = fa.actor_id )
有些人(特别是MySQL人)也写这样的ANTI JOIN:
SELECT * FROM actor a LEFT JOIN film_actor fa USING (actor_id) WHERE film_id IS NULL
我认为历史的原因就是performance。
横向联接
OMG,这个太酷了。 我是唯一一个提到它? 这是一个很酷的查询:
SELECT a.first_name, a.last_name, f.* FROM actor AS a LEFT OUTER JOIN LATERAL ( SELECT f.title, SUM(amount) AS revenue FROM film AS f JOIN film_actor AS fa USING (film_id) JOIN inventory AS i USING (film_id) JOIN rental AS r USING (inventory_id) JOIN payment AS p USING (rental_id) WHERE fa.actor_id = a.actor_id -- JOIN predicate with the outer query! GROUP BY f.film_id ORDER BY revenue DESC LIMIT 5 ) AS f ON true
它会发现每个演员的TOP 5收入电影。 每次你需要一个TOP-N-per-something的查询, LATERAL JOIN
都会成为你的朋友。 如果你是一个SQL Server的人,那么你就知道这个名字为APPLY
JOIN
types
SELECT a.first_name, a.last_name, f.* FROM actor AS a OUTER APPLY ( SELECT f.title, SUM(amount) AS revenue FROM film AS f JOIN film_actor AS fa ON f.film_id = fa.film_id JOIN inventory AS i ON f.film_id = i.film_id JOIN rental AS r ON i.inventory_id = r.inventory_id JOIN payment AS p ON r.rental_id = p.rental_id WHERE fa.actor_id = a.actor_id -- JOIN predicate with the outer query! GROUP BY f.film_id ORDER BY revenue DESC LIMIT 5 ) AS f
好的,也许这是作弊,因为一个LATERAL JOIN
或APPLY
expression式实际上是一个“相关的子查询”,产生了几行。 但是,如果我们允许“相关的子查询”,我们也可以谈论…
MULTISET
这只是由Oracle和Informix(据我所知)才真正实现,但是它可以在PostgreSQL中使用数组和/或XML来模拟,而在SQL Server中使用XML来模拟。
MULTISET
生成一个相关的子查询,并在外部查询中嵌套结果集合。 下面的查询select所有的演员,并为每个演员收集他们的电影在一个嵌套的集合:
SELECT a.*, MULTISET ( SELECT f.* FROM film AS f JOIN film_actor AS fa USING (film_id) WHERE a.actor_id = fa.actor_id ) AS films FROM actor
正如你所看到的,JOIN的types比通常提到的“无聊的” INNER
, OUTER
和CROSS JOIN
。 在我的文章更多细节 。 请停止使用维恩图表来说明它们。
在SQL服务器中,有不同types的JOINS。
- CROSS JOIN
- 内部联接
- 外部连接
外连接又分为3种types
- 左连接或左外连接
- 正确的join或右外部join
- 完全join或完全外部join
join或内部join
SELECT Name, Gender, Salary, DepartmentName FROM tblEmployee INNER JOIN tblDepartment ON tblEmployee.DepartmentId = tblDepartment.Id OR SELECT Name, Gender, Salary, DepartmentName FROM tblEmployee JOIN tblDepartment ON tblEmployee.DepartmentId = tblDepartment.Id
左join或左外join
SELECT Name, Gender, Salary, DepartmentName FROM tblEmployee LEFT OUTER JOIN tblDepartment ON tblEmployee.DepartmentId = tblDepartment.Id OR SELECT Name, Gender, Salary, DepartmentName FROM tblEmployee LEFT JOIN tblDepartment ON tblEmployee.DepartmentId = tblDepartment.Id
RIGHT JOIN或RIGHT OUTER JOIN
SELECT Name, Gender, Salary, DepartmentName FROM tblEmployee RIGHT OUTER JOIN tblDepartment ON tblEmployee.DepartmentId = tblDepartment.Id OR SELECT Name, Gender, Salary, DepartmentName FROM tblEmployee RIGHT JOIN tblDepartment ON tblEmployee.DepartmentId = tblDepartment.Id
完全连接或完全外部连接
SELECT Name, Gender, Salary, DepartmentName FROM tblEmployee FULL OUTER JOIN tblDepartment ON tblEmployee.DepartmentId = tblDepartment.Id OR SELECT Name, Gender, Salary, DepartmentName FROM tblEmployee FULL JOIN tblDepartment ON tblEmployee.DepartmentId = tblDepartment.Id
除此之外没有什么可说的:
优秀的答案和post! 我要推我的宠物peeve:USING关键字。
如果JOIN两边的表都有正确命名的外键(即相同的名称,不只是“ID”),那么可以使用这个:
SELECT ... FROM customers JOIN orders USING (customer_id)
我觉得这非常实用,可读,而且不常用。