SQL JOIN和不同types的JOIN

我在SO和其他一些论坛上经历了许多线索。 所以我想我会总结“ 什么是SQL JOIN? ”和“ 什么是不同types的SQL JOIN? ”。

理论上更好的插图


INNER JOIN  - 只有符合两个表中条件的记录


LEFT JOIN  - 表1中的所有记录以及与表2中的条件相匹配的记录


RIGHT JOIN  - 来自表2的所有记录,以及来自表1的符合条件的记录


FULL OUTER JOIN(左外连接) - 左右外连接匹配ON子句,但保留两个表


什么是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关键字(如INNEROUTERLEFT等)是INNER JOIN 。 换句话说, JOININNER 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 JOINRIGHT OUTER JOIN 。 当条件满足时,它从任一表中返回行,当不匹配时返回NULL值。

换句话说, OUTER JOIN是基于这样一个事实:只有一个表格(RIGHT或LEFT)或表格(FULL)中的匹配条目应该列出。

 Note that `OUTER JOIN` is a loosened form of `INNER JOIN`. 

3.天然连接:

它基于两个条件:

  1. 在相同名称的所有列上进行JOIN
  2. 从结果中删除重复的列。

这似乎更多地是理论性的,因此(大概)大多数DBMS甚至不打扰这种支持。

4.交叉连接:

这是两个表格的笛卡尔积。 CROSS JOIN的结果在大多数情况下是没有意义的。 而且,我们根本不需要这个(或者至less需要精确的)。

5.自我join:

它不是一种不同的JOINforms,而是它自己的一个表( INNEROUTER等)。

基于运营商join

根据用于JOIN子句的运算符,可以有两种types的JOIN 。 他们是

  1. Equijoin
  2. Theta JOIN

1. Equi JOIN:

对于任何JOINtypes( INNEROUTER等),如果我们只使用相等运算符(=),那么我们说JOIN是一个EQUI JOIN

2. Theta JOIN:

这与EQUI JOIN相同,但允许所有其他操作符如>,<,> =等

许多人认为EQUI JOIN和Theta JOIN类似于INNEROUTERJOIN s。 但是我强烈地相信这是一个错误,使观点模糊。 因为INNER JOINOUTER JOIN等都与表格及其数据连接,因为EQUI JOINTHETA 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的连接进行连接。

  1. INNER-JOIN:它从两个表中合并(或组合)匹配的行。 匹配是基于表格的公共列和它们的比较操作完成的。 如果基于均值的条件则:EQUI-JOIN执行,否则为非EQUI-Join。

  2. ** 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):从两个表中返回匹配和不匹配。

  3. 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 JOININNER 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 ] JOINJOINexpression式的左表被添加到如上所示的联合中。
  • RIGHT [ OUTER ] JOIN :如上所示, JOINexpression式的右表被添加到联合中。
  • FULL [ OUTER ] JOINJOINexpression式的两个表都被添加到如上所示的联合中。

所有这些都可以与关键字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 JOINtypes

 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 JOINAPPLYexpression式实际上是一个“相关的子查询”,产生了几行。 但是,如果我们允许“相关的子查询”,我们也可以谈论…

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比通常提到的“无聊的” INNEROUTERCROSS JOIN 。 在我的文章更多细节 。 请停止使用维恩图表来说明它们。

在SQL服务器中,有不同types的JOINS。

  1. CROSS JOIN
  2. 内部联接
  3. 外部连接

外连接又分为3种types

  1. 左连接或左外连接
  2. 正确的join或右外部join
  3. 完全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) 

我觉得这非常实用,可读,而且不常用。