SQL JOIN和不同类型的JOIN

SQL JOIN and different types of JOINs

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


W3学校的插图:

INNER JOIN - Only records which match the condition in both tables

氧化镁

氧化镁

氧化镁


什么是SQL JOIN

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

不同的SQL JOIN是什么?

共有五个JOINs。它们是:

1
2
3
4
5
6
7
8
9
10
  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关键字(如INNEROUTERLEFT等)的JOININNER JOIN。换句话说,JOININNER JOIN的一种句法糖(见:连接和内部连接的区别)。

2。外部联接:

OUTER JOIN检索

或者,一个表中的匹配行和另一个表中的所有行或者,所有表中的所有行(是否匹配并不重要)。

外部连接有三种:

2.1左外接或左接

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

2.2右外接或右接

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

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

这种JOIN结合了LEFT OUTER JOINRIGHT OUTER JOIN。当条件满足时,返回任一表中的行,当不匹配时返回NULL值。

换言之,OUTER JOIN基于这样一个事实:只应列出其中一个表(右或左)中的匹配条目或两个表(满)中的匹配条目。

1
Note that `OUTER JOIN` IS a loosened form OF `INNER JOIN`.

号三。自然连接:

它基于两个条件:

  • 为了相等,在所有具有相同名称的列上生成JOIN
  • 从结果中删除重复列。
  • 这在本质上更具理论意义,因此(可能)大多数DBMS别费心支持这个。

    4。交叉联接:

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

    5。自连接:

    它不是JOIN的一种不同形式,而是表本身的JOIN(INNEROUTER等)。

    基于运算符的联接

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

  • Equi连接
  • 西塔连接
  • 1。Equi连接:

    对于任何JOIN类型(INNEROUTER等),如果我们只使用相等运算符(=),那么我们就说JOINEQUI JOIN

    2。Theta连接:

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

    Many consider both EQUI JOIN and Theta JOIN similar to INNER, OUTER
    etc JOINs. But I strongly believe that its a mistake and makes the
    ideas vague. Because INNER JOIN, OUTER JOIN etc are all connected with
    the tables and their data whereas EQUI JOIN and THETA JOIN are only
    connected with the operators we use in the former.

    Again, there are many who consider NATURAL JOIN as some sort of
    "peculiar" EQUI JOIN. In fact, it is true, because of the first
    condition I mentioned for NATURAL JOIN. However, we don't have to
    restrict that simply to NATURAL JOINs alone. INNER JOINs, OUTER JOINs
    etc could be an EQUI JOIN too.


    定义:

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

    连接类型:

    关系到RDBMS有5种类型的连接:

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

    • 自然连接:它是Equi连接的增强版本,其中选择操作省略重复列。有关实现,请参见内部联接

    • 非等参联接:与等参联接相反,联接条件使用的不是等量运算符(=),例如,!=、<=、>=、>、<或介于等之间。有关实现,请参阅内部联接。

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

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

    根据SQL关注点和高级,有三种类型的连接,所有RDBMS连接都可以使用这些类型的连接来实现。

  • 内部联接:合并(或组合)两个表中匹配的行。匹配是根据表的公共列及其比较操作来完成的。如果是基于等式的条件,那么:执行了equi-join,否则是非equi-join。

  • **外部联接:**它合并(或组合)两个表中的匹配行和具有空值的不匹配行。但是,可以自定义不匹配行的选择,例如,按子类型从第一个表或第二个表中选择不匹配行:左外部联接和右外部联接。

    2.1。左外部联接(又称左联接):返回两个表中匹配的行,并且仅从左表(即第一个表)中不匹配。

    2.2。右外部联接(也称为右联接):返回两个表中匹配的行,仅从右表中不匹配的行。

    2.3。完全外部联接(也称为外部联接):从两个表中返回匹配的和不匹配的。

  • 交叉联接:此联接不合并/组合,而是执行笛卡尔积。

  • enter image description here。注:自连接可以根据需要通过内部连接、外部连接和交叉连接实现,但表必须与自身连接。

    更多信息:

    示例:

    1.1:内部联接:Equi联接实现

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

    1.2:内部联接:自然联接实现

    1
    2
    3
    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:具有非等连接实现的内部连接

    1
    2
    SELECT *
     FROM Table1 A INNER JOIN Table2 B ON A.Pk <= B.Fk;

    1.4:内部连接和自连接

    1
    2
    SELECT *
     FROM Table1 A1 INNER JOIN Table1 A2 ON A1.Pk = A2.Fk;

    2.1:外部连接(完全外部连接)

    1
    2
    SELECT *
     FROM Table1 A FULL OUTER JOIN Table2 B ON A.Pk = B.Fk;

    2.2:左连接

    1
    2
    SELECT *
     FROM Table1 A LEFT OUTER JOIN Table2 B ON A.Pk = B.Fk;

    2.3:右连接

    1
    2
    SELECT *
     FROM Table1 A RIGHT OUTER JOIN Table2 B ON A.Pk = B.Fk;

    3.1:交叉连接

    1
    2
    SELECT *
     FROM TableA CROSS JOIN TableB;

    3.2:交叉连接自连接

    1
    2
    SELECT *
     FROM Table1 A1 CROSS JOIN Table1 A2;

    /或/或/

    1
    2
    SELECT *
     FROM Table1 A1,Table1 A2;


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

    • 它们只关注连接的基本形式
    • 他们(AB)使用维恩图,这是一个不准确的工具可视化连接(他们对联合更好)。

    我最近写了一篇关于这个主题的文章:关于SQL中连接表的许多不同方法的一个可能不完整、全面的指南,我将在这里总结。好的。首先,连接是笛卡尔积

    这就是为什么文氏图解释的如此不准确,因为一个连接在两个连接的表之间创建了笛卡尔积。维基百科很好地说明了这一点:好的。

    enter image description here。好的。

    笛卡尔产品的SQL语法是CROSS JOIN。例如:好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    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

    它将一个表中的所有行与另一个表中的所有行组合在一起:好的。

    资料来源:好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    +--------+   +------------+
    | DAY    |   | department |
    +--------+   +------------+
    | Jan 01 |   | Dept 1     |
    | Jan 02 |   | Dept 2     |
    | ...    |   | Dept 3     |
    | Jan 30 |   +------------+
    | Jan 31 |
    +--------+

    结果:好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    +--------+------------+
    | 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     |
    +--------+------------+

    如果我们只写一个逗号分隔的表列表,我们将得到相同的结果:好的。

    1
    2
    -- CROSS JOINing two tables:
    SELECT * FROM table1, table2

    。内部连接(Theta连接)

    INNER JOIN只是一个过滤后的CROSS JOIN,其中过滤谓词在关系代数中称为Theta。好的。

    例如:好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    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访问中)。好的。

    (查看文章中的结果示例)好的。Equi连接

    一种特殊的theta连接是equi连接,我们使用最多。谓词将一个表的主键与另一个表的外键联接起来。如果我们使用Sakila数据库进行说明,我们可以编写:好的。

    1
    2
    3
    4
    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

    这将所有演员与他们的电影结合起来。好的。

    或者,在某些数据库上:好的。

    1
    2
    3
    4
    SELECT *
    FROM actor
    JOIN film_actor USING (actor_id)
    JOIN film USING (film_id)

    USING()语法允许指定一列,该列必须出现在联接操作表的任一侧,并在这两列上创建一个相等谓词。好的。自然连接

    其他答案单独列出了这个"连接类型",但这没有意义。它只是equi-join的语法糖形式,这是theta-join或inner-join的一种特殊情况。自然联接只收集被联接的两个表共用的所有列,并联接这些列。由于意外匹配(如sakila数据库中的LAST_UPDATE列),这几乎没有什么用处。好的。

    语法如下:好的。

    1
    2
    3
    4
    SELECT *
    FROM actor
    NATURAL JOIN film_actor
    NATURAL JOIN film

    外部连接

    现在,OUTER JOININNER JOIN有点不同,因为它创建了几个笛卡尔积的UNION。我们可以写:好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    -- Convenient syntax:
    SELECT *
    FROM a LEFT JOIN b ON [cc lang="sql"]

    -- Cumbersome, equivalent syntax:
    SELECT a.*, b.*
    FROM a JOIN b ON [cc lang="sql"]
    UNION ALL
    SELECT a.*, NULL, NULL, ..., NULL
    FROM a
    WHERE NOT EXISTS (
      SELECT * FROM b WHERE [cc lang="sql"]
    )

    没有人想写后者,所以我们写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中有一些历史性的、不推荐使用的语法,在SQL标准有语法之前就已经支持OUTER JOIN:好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    -- 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实现了它)。你可以这样写:好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    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

    部分结果:好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    +--------+------------+------------+
    | 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中没有本机语法,不幸的是(就像下面的反连接)。但是我们可以使用IN()EXISTS()来寻找所有在电影中演出过的演员:好的。

    1
    2
    3
    4
    5
    6
    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中。您将看到数据库执行的是半联接操作,而不是EXISTS()谓词。好的。

    enter image description here。好的。反连接

    这与半连接正好相反(不过,注意不要使用NOT IN,因为它有一个重要的警告)好的。

    以下是所有没有电影的演员:好的。

    1
    2
    3
    4
    5
    6
    SELECT *
    FROM actor a
    WHERE NOT EXISTS (
      SELECT * FROM film_actor fa
      WHERE a.actor_id = fa.actor_id
    )

    有些人(尤其是mysql人)也会这样写反连接:好的。

    1
    2
    3
    4
    5
    SELECT *
    FROM actor a
    LEFT JOIN film_actor fa
    USING (actor_id)
    WHERE film_id IS NULL

    我认为历史原因是表演。好的。横向连接

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    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部电影。每当你需要一个顶级的N-per-something查询时,LATERAL JOIN将是你的朋友。如果您是SQL Server人员,那么您就知道这个名为APPLYJOIN类型。好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    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 JOINAPPLY表达式实际上是一个"相关子查询",可以产生多行。但是如果我们允许"相关子查询",我们也可以讨论…好的。多集

    这实际上是由Oracle和Informix实现的(据我所知),但它可以在PostgreSQL中使用数组和/或XML进行模拟,在SQL Server中使用XML进行模拟。好的。

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

    1
    2
    3
    4
    5
    6
    7
    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

    如您所见,连接的类型比通常提到的"无聊"的INNEROUTERCROSS JOIN更多。在我的文章中有更多的细节。请不要用维恩图来说明它们。好的。好啊。


    在我看来,我创造了一个比文字更好的例证:氧化镁


    我要推我的宠物尿:使用关键字。

    如果联接两侧的两个表都有正确命名的外键(即,相同的名称,而不仅仅是"id"),则可以使用它:

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

    我发现这非常实用,可读性强,而且使用频率不够。