SQL JOIN和不同类型的JOIN


Answers:


330

W3schools的插图:


INNER JOIN-仅在两个表中都符合条件的记录


左联接-表1中的所有记录以及与表2中的条件匹配的记录


RIGHT JOIN-表2中的所有记录与表1中符合条件的记录一起


FULL OUTER JOIN-左外连接和右外连接的组合,匹配ON子句,但保留两个表



27
@KNU w3fools应该从他们拍摄照片的想法中获得赞誉。请参见Jeff Atwood(是的,是SO的共同作者)的SQL连接可视化Ligaya Turmelle链接文章,其中Jeff得到了这个想法并将其扩展。
ypercubeᵀᴹ

2
如果不打扰,则@avi左右联接是相似的,这是联接所基于的主表。
2014年

2
@philipxy:这是一个奇怪的定义(即使您是正确的)。但是我宁愿相反,从交叉联接开始,然后在其之上“构建”内部联接。毕竟,仅仅交叉连接的概念使这些非正式且不准确的维恩图可视化无效……
Lukas Eder

1
这些图片似乎暗示联合与完全外部联接相同,交集与内部联接相同,据我所知这是不正确的。
mayyWOZ

1
@DevDave,因为违背普遍的信念-图片不值一千字。查看下一个答案。
hyankov

248

什么SQL JOIN

SQL JOIN 是从两个或多个数据库表中检索数据的方法。

SQL JOINs有什么不同?

总共有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,我们得到了两个表中都符合条件的所有记录,并且两个表中不匹配的记录都不会报告。

换句话说,INNER JOIN基于以下事实:仅应列出两个表中的匹配条目。

需要注意的是一个JOIN没有任何其他JOIN的关键字(如INNEROUTERLEFT,等)是INNER JOIN。换句话说,JOIN是的语法糖INNER JOIN(请参阅:JOIN和INNER JOIN之间的区别)。

2.外加入:

OUTER JOIN 检索

一个表中的匹配行与另一表中的所有行匹配,或者所有表中的所有行匹配(是否匹配)。

共有三种外连接:

2.1左外联接或左联接

此联接返回左表中的所有行以及右表中的匹配行。如果右表中没有匹配的列,则返回NULL值。

2.2正确的外部联接或正确的联接

JOIN将返回右表中的所有行以及左表中的匹配行。如果左表中没有匹配的列,则返回NULL值。

2.3完全外部联接或完全联接

JOIN结合LEFT OUTER JOINRIGHT OUTER JOIN。满足条件时,从任一表返回行,并返回NULL在不匹配时值。

换句话说,OUTER JOIN基于以下事实:仅应列出表之一(右或左)或表两者(全)中的匹配条目。

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

3.自然加入:

它基于两个条件:

  1. JOIN对所有与平等相同名称的列所做的。
  2. 从结果中删除重复的列。

这似乎本质上是理论上的,因此(可能)大多数DBMS甚至都不愿意为此提供支持。

4.交叉加入:

它是所涉及的两个表的笛卡尔积。一个的结果CROSS JOIN在大多数情况下都没有意义。而且,我们根本不需要(或者至少需要)。

5.自我加入:

这不是一个不同形式的JOIN,而它是一个JOININNEROUTER等)的表格的给自己。

基于运算符的联接

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

  1. 平等加入
  2. Theta JOIN

1. Equi JOIN:

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

2. Theta JOIN:

与此相同,EQUI JOIN但是它允许所有其他运算符,例如>,<,> =等。

许多人认为EQUI JOIN和Theta都JOIN类似于INNEROUTER etc JOIN。但是我坚信这是一个错误,并且使想法含糊。因为INNER JOINOUTER JOIN等全部与表及其数据,而连接EQUI JOINTHETA JOIN仅与我们前使用运营商连接。

同样,有许多人认为这NATURAL JOIN是某种“奇特的” EQUI JOIN。实际上,这是真的,因为我提到的第一个条件NATURAL JOIN。但是,我们不必将其仅局限于NATURAL JOINs。INNER JOINs,OUTER JOINs等也可以EQUI JOIN


2
有相对较新的LATERAL JOIN .. SELECT * FROM r1,LATERAL fx(r1)
Pavel Stehule

13
尽管这似乎是合理的,但我认为以任何传达有用信息的方式都无法回答“什么是SQL连接”。总体而言,答案是为已经了解联接的人而不是向提出这些问题的人写的参考书。它还省略了引用,以支持其主张(在做出权威性回答时适当),并通过外部资源提供其他解释。如果您要写一个权威的答案来链接新的SQL用户,则可能需要填补一些空白,尤其是“什么是联接”部分。
Craig Ringer

你能提供一些例子吗?
2014年

67

定义:


JOINS是同时查询从多个表组合在一起的数据的方法。

联接的类型:


关于RDBMS,有5种类型的联接:

  • Equi-Join:根据相等条件组合来自两个表的公用记录。从技术上讲,联接是通过使用相等运算符(=)来比较一个表的主键值和另一个表的外键值而进行的,因此结果集包括两个表的公共(匹配)记录。有关实现,请参见INNER-JOIN。

  • Natural-Join: 它是Equi-Join的增强版本,其中SELECT操作省略重复的列。有关实现,请参见INNER-JOIN

  • 非等联接:与等联接相反,在等联接符使用等于条件运算符(=)的情况下,例如!=,<=,> =,>,<或BETWEEN等。有关实现的信息,请参阅INNER-JOIN。

  • 自联接::表与自身结合的联接的自定义行为;这是查询自引用表(或一元关系实体)时通常需要的。有关实现,请参见INNER-JOIN。

  • 笛卡尔积:它交叉合并两个表的所有记录,而没有任何条件。从技术上讲,它返回没有WHERE-Clause的查询的结果集。

根据SQL的关注和改进,共有3种类型的联接,并且使用这些类型的联接都可以实现所有RDBMS联接。

  1. INNER-JOIN:合并(或合并)来自两个表的匹配行。匹配是基于表的公共列及其比较操作完成的。如果基于相等条件,则:执行EQUI-JOIN,否则执行Non-EQUI-Join。

  2. 外部联接:合并(或合并)来自两个表的匹配行和具有NULL值的不匹配行。但是,可以自定义选择不匹配的行,例如,按以下子类型从第一张表或第二张表中选择不匹配的行:LEFT OUTER JOIN和RIGHT OUTER JOIN。

    2.1。LEFT外连接(又名LEFT-JOIN):仅从两个表返回匹配的行,而从LEFT表(即第一个表)返回不匹配的行。

    2.2。RIGHT外连接(又名RIGHT-JOIN):从两个表返回匹配的行,仅从RIGHT表返回不匹配的行。

    2.3。FULL OUTER JOIN(又名OUTER JOIN):从两个表中返回匹配的和不匹配的。

  3. 交叉联接:联接不合并/合并,而是执行笛卡尔积。

在此处输入图片说明 注意:根据需要,可以通过INNER-JOIN,OUTER-JOIN和CROSS-JOIN来实现自我联接,但是表必须与自身联接。

想要查询更多的信息:

例子:

1.1:INNER-JOIN:等值联接实现

SELECT  *
FROM Table1 A 
 INNER JOIN Table2 B ON A.<Primary-Key> =B.<Foreign-Key>;

1.2:INNER-JOIN:自然联接实现

Select A.*, B.Col1, B.Col2          --But no B.ForeignKeyColumn in Select
 FROM Table1 A
 INNER JOIN Table2 B On A.Pk = B.Fk;

1.3:使用NON-Equi-join实现的INNER-JOIN

Select *
 FROM Table1 A INNER JOIN Table2 B On A.Pk <= B.Fk;

1.4:使用自联接的INNER-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;

标签“ Table 1”和“ Table 2”及其下面的标签不合适,它们来自intersect/ except/的图示union;圆圈是left&所返回的行right join,如标号标签所示。AXB图片是胡说八道。cross join= inner join on 1=1&是第一个图的特殊情况。
philipxy

值得一提的是SQL-92定义了UNION JOIN。现在在SQL:2003中已作废。
穿刺者

40

有趣的是,大多数其他答案都遇到以下两个问题:

我最近写了一篇有关该主题的文章:关于在SQL中联接表的许多不同方法的可能不完整的综合指南,在这里我将对其进行总结。

首先,JOIN是笛卡尔积

这就是为什么维恩图无法如此准确地解释它们的原因,因为JOIN会创建一个 在两个联接表之间笛卡尔积。维基百科很好地说明了这一点:

在此处输入图片说明

笛卡尔积的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

内联接(Theta-JOIN)

An 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中除外)。

有关结果示例,请参见文章

参加

特殊的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操作的表的任一侧出现的列,并在这两列上创建相等谓词。

自然加入

其他答案单独列出了此“ JOIN类型”,但这没有任何意义。它只是equi JOIN的语法糖形式,这是Theta-JOIN或INNER JOIN的特例。NATURAL JOIN只是收集要连接的两个表共有的所有列,然后将USING()这些列连接在一起。由于偶然的匹配(例如Sakila数据库中的LAST_UPDATE列),这几乎没有用。)。

语法如下:

SELECT *
FROM actor
NATURAL JOIN film_actor
NATURAL JOIN film

外连接

现在,它与创建多个笛卡尔积OUTER JOIN的过程有些不同。我们可以这样写:INNER JOINUNION

-- 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 ] JOINJOIN表达式的左表已添加到联合,如上所示。
  • RIGHT [ OUTER ] JOIN:将JOIN表达式的右表添加到联合,如上所示。
  • FULL [ OUTER ] JOINJOIN表达式的两个表都被添加到联合,如上所示。

所有这些都可以用关键字进行组合USING()NATURAL其实我已经有一个真正的世界用例的NATURAL FULL JOIN最近

替代语法

Oracle和SQL Server中有一些不推荐使用的历史性语法,OUTER JOIN在SQL标准对此具有语法之前就已经支持:

-- 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

很少有人知道这一点,但是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

这里的要点是,无论是否JOIN在“ 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

我认为历史原因是表现。

横向联接

天哪,这太酷了。我是唯一要提的人吗?这是一个很酷的查询:

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

它将发现每位演员的前5名创收电影。每次您需要进行TOP-N-per-something查询时,LATERAL JOIN将成为您的朋友。如果您是SQL Server使用者,那么您会JOIN在名称下知道此类型APPLY

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

好的,也许是作弊,因为a LATERAL JOINAPPLY表达式实际上是一个产生多个行的“相关子查询”。但是,如果我们允许“相关子查询”,那么我们也可以谈论...

多组

(据我所知)这仅是由Oracle和Informix真正实现的,但是可以在PostgreSQL中使用数组和/或XML以及在SQL Server中使用XML进行仿真。

MULTISET产生一个相关的子查询,并将结果集的行嵌套在外部查询中。下面的查询选择所有演员,并为每个演员将他们的电影收集在一个嵌套集合中:

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的类型不仅仅是“无聊的” INNER,“” OUTER,而且CROSS JOIN通常提到。我的文章中有更多细节。并且,请停止使用维恩图进行说明。


Equijoin是theta-join的特例,其中theta是相等的。Theta-join类似于内部联接的一种特殊情况,其中on是每个列中on的theta比较。在科德定义它们后的几十年中,一些教科书将θ连接误定义为类似于内部连接的概括。
philipxy

@philipxy:我应该更改我的答案中的任何具体内容吗?您可以建议进行修改...
Lukas Eder

10

我认为,我创建了一个比文字更能说明问题的插图: SQL Join表说明


@Niraj圆圈A和B不包含A和B的行。它们是从其他地方盲目复制而得不到的。交叉联接包含在内部联接的情况下,它是1 = 1的内部联接。图片的这些部分以什么方式“完美”?
philipxy

@philipxy对不起,但是我不介意从其他地方复制它。而且我不确定上图中哪个是不正确的。对我来说没关系。交叉连接在这里没有描述。它不包含在内部
联接中。.– Niraj

-3

我要推我的宠物:USING关键字。

如果JOIN两侧的两个表的外键都正确命名(即,相同的名称,而不仅仅是“ id”),则可以使用此名称:

SELECT ...
FROM customers JOIN orders USING (customer_id)

我发现这非常实用,易读,并且使用频率不够高。


2
这不能回答问题。它作为评论属于问题或答案之一。
TylerH
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.