254

什么是 SQL JOIN,有哪些不同的类型?

4

6 回答 6

341

来自W3schools的插图:


INNER JOIN - 仅在两个表中匹配条件的记录


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


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


FULL OUTER JOIN - Left 和 Right Outer 的组合匹配 ON 子句但保留两个表


于 2013-11-30T09:34:42.553 回答
260

是什么SQL JOIN

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

有什么不同SQL JOIN

一共有五个JOINs。他们是 :

  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. 加入或内部加入:

在这种 aJOIN中,我们得到两个表中所有符合条件的记录,两个表中不匹配的记录不上报。

换句话说,INNER JOIN基于一个事实:只有两个表中的匹配条目应该被列出。

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

2.外连接:

OUTER JOIN检索

要么是一个表中的匹配行和另一个表中的所有行,要么是所有表中的所有行(无论是否匹配都无关紧要)。

外连接有三种:

2.1 左外连接或左连接

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

2.2 右外连接或右连接

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

2.3 FULL OUTER JOIN 或 FULL JOIN

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. 交叉连接:

它是所涉及的两个表的笛卡尔积。在大多数情况下,a 的结果是CROSS JOIN没有意义的。此外,我们根本不需要这个(或者至少需要,准确地说)。

5. 自加入:

它不是 的不同形式JOIN,而是表自身的JOIN( INNER, OUTER, 等)。

基于运算符的 JOIN

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

  1. 平等加入
  2. 西塔加入

1. 平等加入:

对于任何JOIN类型(INNER,OUTER等),如果我们只使用相等运算符 (=),那么我们说 the JOINis an EQUI JOIN

2. Theta 加入:

这是相同的,EQUI JOIN但它允许所有其他运算符,如 >、<、>= 等。

许多人认为EQUI JOIN和 Theta都JOIN类似于INNER, OUTER etc JOIN。但我坚信这是一个错误,使想法变得模糊。因为INNER JOIN, OUTER JOINetc 都与表及其数据相关,而EQUI JOINTHETA JOIN仅与我们在前者中使用的运算符相关。

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

于 2013-07-30T11:48:12.387 回答
72

定义:


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

联接类型:


关于 RDBMS,有 5 种连接类型:

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

  • Natural-Join: Equi-Join 的增强版,其中 SELECT 操作省略了重复列。实现见 INNER-JOIN

  • Non-Equi-Join:它是 Equi-join 的逆操作,其中加入条件不是使用相等运算符 (=),例如 !=、<=、>=、>、< 或 BETWEEN 等。有关实现,请参见 INNER-JOIN。

  • Self-Join: : 一种自定义的join行为,表与自身相结合;这通常用于查询自引用表(或一元关系实体)。有关实现,请参见 INNER-JOIN。

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

根据 SQL 的关注和进步,有 3 种类型的连接,所有 RDBMS 连接都可以使用这些类型的连接来实现。

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

  2. OUTER-JOIN:它合并(或组合)来自两个表的匹配行和具有 NULL 值的不匹配行。但是,可以自定义选择不匹配的行,例如,通过子类型从第一个表或第二个表中选择不匹配的行:LEFT OUTER JOIN 和 RIGHT OUTER JOIN。

    2.1。LEFT Outer JOIN(又名 LEFT-JOIN):仅返回两个表中匹配的行和 LEFT 表(即第一个表)中不匹配的行。

    2.2. RIGHT Outer JOIN(又名,RIGHT-JOIN):从两个表中返回匹配的行,并且仅从 RIGHT 表中返回不匹配的行。

    2.3. FULL OUTER JOIN (aka OUTER JOIN):从两个表中返回匹配和不匹配。

  3. CROSS-JOIN:此连接不合并/组合,而是执行笛卡尔积。

在此处输入图像描述 注意:Self-JOIN 可以根据需要通过 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:自然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:INNER-JOIN 与 NON-Equi-join 实现

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

1.4: INNER-JOIN 和 SELF-JOIN

Select *
 FROM Table1 A1 INNER JOIN Table1 A2 On A1.Pk = A2.Fk;

2.1:OUTER JOIN(全外连接)

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: CROSS JOIN-Self JOIN

Select *
 FROM Table1 A1 CROSS JOIN Table1 A2;

//或者//

Select *
 FROM Table1 A1,Table1 A2;
于 2014-01-13T06:57:31.797 回答
43

有趣的是,大多数其他答案都存在这两个问题:

我最近写了一篇关于该主题的文章:A可能不完整,全面指南,以多种不同的方式在 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)

AnINNER 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 JOIN因为它创建了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添加到联合中,如上所示。
  • 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“连接的另一侧”是否匹配任何内容。长话短说:这是为了填充报告中的稀疏数据。很有用!

半联接

严重地?没有其他答案得到这个?当然不是,因为不幸的是它在 SQL 中没有原生语法(就像下面的 ANTI JOIN 一样)。但是我们可以使用IN()andEXISTS()来查找所有在电影中扮演过的演员:

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

我认为历史原因是性能。

横向连接

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

它将找到每位演员收入最高的 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

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

多组

这仅由 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通常会被提及。更多细节在我的文章中。请停止使用维恩图来说明它们。

于 2017-04-21T17:08:07.767 回答
10

在我看来,我创建了一个比文字更能解释的插图: SQL Join表的解释

于 2015-08-12T22:22:18.377 回答
-3

我要激怒我了:USING 关键字。

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

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

我发现这非常实用、易读,而且使用得不够频繁。

于 2017-03-29T13:43:39.620 回答