SQL JOIN-WHERE子句与ON子句


684

阅读后,这不是Explicit与Implicit SQL Joins的重复。答案可能是相关的(甚至是相同的),但问题是不同的。


有什么区别,每个应该有什么区别?

如果我正确理解该理论,则查询优化器应该可以互换使用。


2
仅为了将来的读者和您的信息,您应该阅读sql执行的顺序。这将帮助您更精确地了解潜在差异。
Rahul Neekhra

Answers:


868

它们不是同一件事。

考虑以下查询:

SELECT *
FROM Orders
LEFT JOIN OrderLines ON OrderLines.OrderID=Orders.ID
WHERE Orders.ID = 12345

SELECT *
FROM Orders
LEFT JOIN OrderLines ON OrderLines.OrderID=Orders.ID 
    AND Orders.ID = 12345

第一个将返回订单及其订单号的行(如果有)12345。第二个将返回所有订单,但只有订单12345将具有与其关联的任何行。

使用INNER JOIN,子句实际上等效。但是,仅仅因为它们在功能上相同而产生相同的结果,并不意味着这两种子句具有相同的语义。


83
将where子句放在内部联接的“ on”子句中,您会得到更好的性能吗?
FistOfFury 2012年

96
@FistOfFury Sql Server使用查询优化器过程来编译和评估您的代码,以产生最佳的执行计划。它不是完美的,但是在大多数情况下都没有关系,您将以任何一种方式获得相同的执行计划。
乔尔·科洪

18
在Postgres中,我注意到它们并不等效,并导致了不同的查询计划。如果使用ON,则导致使用物化。如果使用WHERE,则使用哈希。物化的情况更糟,其成本比哈希值高10倍。这是使用一组ID而不是单个ID。
JamesHutchison

13
@JamesHutchison很难根据这样观察到的行为做出可靠的性能概括。一天是正确的,第二天往往是错误的,因为这是实现的详细信息,而不是记录的行为。数据库团队一直在寻找提高优化器性能的地方。如果ON行为无法改善以匹配WHERE,我会感到惊讶。它甚至有可能不会在发行说明中的任何地方显示出来的版本比类似“一般性能改进等版本。
乔尔Coehoorn

4
@FiHoran这不是Sql Server的工作方式。统计信息显示它会有所帮助时,它将根据WHERE子句中的项目积极地进行预过滤。
乔尔·科恩霍恩

362
  • 内连接无关紧要
  • 外连接事项

    一个。WHERE条款:加入。加入后将过滤记录。

    b。ON子句- 加入之前。记录(来自右表)将在加入之前被过滤。结果可能为空(由于OUTER连接)。



示例:考虑下表:

    1. documents:
     | id    | name        |
     --------|-------------|
     | 1     | Document1   |
     | 2     | Document2   |
     | 3     | Document3   |
     | 4     | Document4   |
     | 5     | Document5   |


    2. downloads:
     | id   | document_id   | username |
     |------|---------------|----------|
     | 1    | 1             | sandeep  |
     | 2    | 1             | simi     |
     | 3    | 2             | sandeep  |
     | 4    | 2             | reya     |
     | 5    | 3             | simi     |

a)内部WHERE子句:

  SELECT documents.name, downloads.id
    FROM documents
    LEFT OUTER JOIN downloads
      ON documents.id = downloads.document_id
    WHERE username = 'sandeep'

 For above query the intermediate join table will look like this.

    | id(from documents) | name         | id (from downloads) | document_id | username |
    |--------------------|--------------|---------------------|-------------|----------|
    | 1                  | Document1    | 1                   | 1           | sandeep  |
    | 1                  | Document1    | 2                   | 1           | simi     |
    | 2                  | Document2    | 3                   | 2           | sandeep  |
    | 2                  | Document2    | 4                   | 2           | reya     |
    | 3                  | Document3    | 5                   | 3           | simi     |
    | 4                  | Document4    | NULL                | NULL        | NULL     |
    | 5                  | Document5    | NULL                | NULL        | NULL     |

  After applying the `WHERE` clause and selecting the listed attributes, the result will be: 

   | name         | id |
   |--------------|----|
   | Document1    | 1  |
   | Document2    | 3  | 

b)内部JOIN条款

  SELECT documents.name, downloads.id
  FROM documents
    LEFT OUTER JOIN downloads
      ON documents.id = downloads.document_id
        AND username = 'sandeep'

For above query the intermediate join table will look like this.

    | id(from documents) | name         | id (from downloads) | document_id | username |
    |--------------------|--------------|---------------------|-------------|----------|
    | 1                  | Document1    | 1                   | 1           | sandeep  |
    | 2                  | Document2    | 3                   | 2           | sandeep  |
    | 3                  | Document3    | NULL                | NULL        | NULL     |
    | 4                  | Document4    | NULL                | NULL        | NULL     |
    | 5                  | Document5    | NULL                | NULL        | NULL     |

Notice how the rows in `documents` that did not match both the conditions are populated with `NULL` values.

After Selecting the listed attributes, the result will be: 

   | name       | id   |
   |------------|------|
   |  Document1 | 1    |
   |  Document2 | 3    | 
   |  Document3 | NULL |
   |  Document4 | NULL | 
   |  Document5 | NULL | 

40
海事组织这是最好的答案,因为它清楚地表明了其他流行答案的“幕后”情况。
psrpsrpsr

1
优秀的解释..做得好!-很好奇,您是怎么做到的intermediate join table?一些“解释”命令?
曼努埃尔·乔丹

1
@ManuelJordan不,这只是为了说明。与创建中间表相比,数据库可能执行的性能更高。
Sandeep Jindal 19'Oct

可以理解,我假设可能使用了第三个工具。
曼努埃尔·乔丹

145

INNER JOINs上它们是可互换的,优化器将随意重新排列它们。

OUTER JOINs上,它们不一定是可互换的,这取决于它们所依赖的连接的哪一侧。

我根据可读性将它们放在任何一个地方。



在Where子句中可能更清楚了,尤其是在Linq-To-Entities lambda表达式中Orders.Join( OrderLines, x => x.ID, x => OrderID, (o,l) => new {Orders = o, Lines = l}).Where( ol => ol.Orders.ID = 12345)
Triynko 2015年

49

我这样做的方式是:

  • ON如果要执行连接操作,则始终将连接条件放在子句中INNER JOIN。因此,请勿在ON子句中添加任何WHERE条件,而应将它们放在WHERE子句中。

  • 如果您正在执行LEFT JOIN,则将任何WHERE条件添加到ON联接右侧表的子句中。这是必须的,因为添加引用联接右侧的WHERE子句会将联接转换为INNER JOIN。

    当您要查找不在特定表中的记录时,就是一个例外。您可以通过以下方式将对RIGHT JOIN表中唯一标识符(永远不会为NULL)的引用添加到WHERE子句中:WHERE t2.idfield IS NULL。因此,唯一应在联接右侧引用表的地方是查找那些不在表中的记录。


8
到目前为止,这是我读过的最好的答案。一旦您的大脑了解到左连接返回左表中的所有行,并且稍后必须对其进行过滤,则完全有意义。
尼克·拉尔森

如果您将具有可为空列的表外部联接,那么您仍可以将该列“处”为null而无需使其成为内部联接?那不是完全寻找不是仅在特定表中的记录。您可以寻找1.不存在2.根本没有价值。
附近

我的意思是,您可以同时寻找:“ 1。不存在2.根本没有价值”。这适用于该字段不是idfield的情况。
附近

当我遇到这种情况时:在没有紧急联系的情况下寻找包括新生(尚未输入数据)在内的参与者。
附近

30

在内部联接上,它们的含义相同。但是,如果将联接条件放在WHERE vs ON子句中,则在外部联接中将得到不同的结果。看看这个相关的问题这个答案(由我自己)。

我认为最习惯的做法是始终将联接条件放在ON子句中(除非它是外部联接,并且您实际上确实希望将其放在where子句中),因为这样会使阅读查询的人更清楚联接表的条件是什么,它还有助于防止WHERE子句长数十行。


26

这是一个非常常见的问题,因此此答案基于我写的这篇文章

表关系

考虑到我们有以下postpost_comment表:

<code> post </ code>和<code> post_comment </ code>表

post具有以下记录:

| id | title     |
|----|-----------|
| 1  | Java      |
| 2  | Hibernate |
| 3  | JPA       |

并且post_comment具有以下三行:

| id | review    | post_id |
|----|-----------|---------|
| 1  | Good      | 1       |
| 2  | Excellent | 1       |
| 3  | Awesome   | 2       |

SQL内部联接

SQL JOIN子句允许您关联属于不同表的行。例如,CROSS JOIN将创建一个笛卡尔乘积,其中包含两个联接表之间所有可能的行组合。

尽管CROSS JOIN在某些情况下很有用,但在大多数情况下,您希望根据特定条件连接表。而且,这就是INNER JOIN发挥作用的地方。

SQL INNER JOIN允许我们根据通过ON子句指定的条件来过滤联接两个表的笛卡尔积。

SQL INNER JOIN-处于“始终为真”的条件

如果提供“始终为真”的条件,则INNER JOIN将不会过滤联接的记录,并且结果集将包含两个联接表的笛卡尔乘积。

例如,如果我们执行以下SQL INNER JOIN查询:

SELECT
   p.id AS "p.id",
   pc.id AS "pc.id"
FROM post p
INNER JOIN post_comment pc ON 1 = 1

我们将获得postpost_comment记录的所有组合:

| p.id    | pc.id      |
|---------|------------|
| 1       | 1          |
| 1       | 2          |
| 1       | 3          |
| 2       | 1          |
| 2       | 2          |
| 2       | 3          |
| 3       | 1          |
| 3       | 2          |
| 3       | 3          |

因此,如果ON子句条件为“始终为true”,则INNER JOIN等效于CROSS JOIN查询:

SELECT
   p.id AS "p.id",
   pc.id AS "pc.id"
FROM post p
CROSS JOIN post_comment
WHERE 1 = 1
ORDER BY p.id, pc.id

SQL INNER JOIN-处于“始终为假”条件

另一方面,如果ON子句条件为“ always false”,则所有联接的记录将被过滤掉,结果集将为空。

因此,如果我们执行以下SQL INNER JOIN查询:

SELECT
   p.id AS "p.id",
   pc.id AS "pc.id"
FROM post p
INNER JOIN post_comment pc ON 1 = 0
ORDER BY p.id, pc.id

我们将不会得到任何结果:

| p.id    | pc.id      |
|---------|------------|

这是因为上面的查询等同于以下CROSS JOIN查询:

SELECT
   p.id AS "p.id",
   pc.id AS "pc.id"
FROM post p
CROSS JOIN post_comment
WHERE 1 = 0
ORDER BY p.id, pc.id

SQL INNER JOIN-使用外键和主键列的ON子句

最常见的ON子句条件是将子表中的Foreign Key列与父表中的Primary Key列匹配的条件,如以下查询所示:

SELECT
   p.id AS "p.id",
   pc.post_id AS "pc.post_id",
   pc.id AS "pc.id",
   p.title AS "p.title",
   pc.review  AS "pc.review"
FROM post p
INNER JOIN post_comment pc ON pc.post_id = p.id
ORDER BY p.id, pc.id

当执行上面的SQL INNER JOIN查询时,我们得到以下结果集:

| p.id    | pc.post_id | pc.id      | p.title    | pc.review |
|---------|------------|------------|------------|-----------|
| 1       | 1          | 1          | Java       | Good      |
| 1       | 1          | 2          | Java       | Excellent |
| 2       | 2          | 3          | Hibernate  | Awesome   |

因此,查询结果集中仅包含与ON子句条件匹配的记录。在我们的例子中,结果集包含所有post及其post_comment记录。post没有关联的行将post_comment被排除,因为它们不能满足ON子句条件。

同样,上述SQL INNER JOIN查询等效于以下CROSS JOIN查询:

SELECT
   p.id AS "p.id",
   pc.post_id AS "pc.post_id",
   pc.id AS "pc.id",
   p.title AS "p.title",
   pc.review  AS "pc.review"
FROM post p, post_comment pc
WHERE pc.post_id = p.id

未删除的行是满足WHERE子句的行,并且仅这些记录将包含在结果集中。这是可视化INNER JOIN子句工作方式的最佳方法。

| p.id | pc.post_id | pc.id | 标题| pc.review |
| ------ | ------------ | ------- || ------------ | --------- -|
| 1 | 1 | 1 | Java | 好
| 1 | 1 | 2 | Java | 优秀
| 1 | 2 | 3 | Java | 很棒 
| 2 | 1 | 1 | 休眠| 好 
| 2 | 1 | 2 | 休眠| 优秀
| 2 | 2 | 3 | 休眠| 很棒
| 3 | 1 | 1 | JPA | 好 
| 3 | 1 | 2 | JPA | 优秀 
| 3 | 2 | 3 | JPA | 很棒

结论

可以使用WHERE子句将INNER JOIN语句重写为CROSS JOIN,该子句与您在INNER JOIN查询的ON子句中使用的条件相同。

并不是说这仅适用于INNER JOIN,不适用于OUTER JOIN。


11

在左连接方面,where子句on子句之间有很大的区别。

这是示例:

mysql> desc t1; 
+-------+-------------+------+-----+---------+-------+
| Field | Type        | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id    | int(11)     | NO   |     | NULL    |       |
| fid   | int(11)     | NO   |     | NULL    |       |
| v     | varchar(20) | NO   |     | NULL    |       |
+-------+-------------+------+-----+---------+-------+

表t2的ID为fid。

mysql> desc t2;
+-------+-------------+------+-----+---------+-------+
| Field | Type        | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id    | int(11)     | NO   |     | NULL    |       |
| v     | varchar(10) | NO   |     | NULL    |       |
+-------+-------------+------+-----+---------+-------+
2 rows in set (0.00 sec)

查询“ on子句”:

mysql> SELECT * FROM `t1` left join t2 on fid = t2.id AND t1.v = 'K' 
    -> ;
+----+-----+---+------+------+
| id | fid | v | id   | v    |
+----+-----+---+------+------+
|  1 |   1 | H | NULL | NULL |
|  2 |   1 | B | NULL | NULL |
|  3 |   2 | H | NULL | NULL |
|  4 |   7 | K | NULL | NULL |
|  5 |   5 | L | NULL | NULL |
+----+-----+---+------+------+
5 rows in set (0.00 sec)

查询“ where子句”:

mysql> SELECT * FROM `t1` left join t2 on fid = t2.id where t1.v = 'K';
+----+-----+---+------+------+
| id | fid | v | id   | v    |
+----+-----+---+------+------+
|  4 |   7 | K | NULL | NULL |
+----+-----+---+------+------+
1 row in set (0.00 sec)

很明显,对于行t1.v ='K',第一个查询从t1返回一条记录,从t2返回其相关行(如果有)。

第二个查询从t1返回行,但仅对于t1.v ='K'将具有任何关联的行。


9

对于优化器而言,无论是使用ON还是WHERE定义连接子句都不会有所不同。

但是,恕我直言,我认为在执行连接时使用ON子句更加清晰。这样,您在查询中就有一个特定部分,该部分指示如何处理联接以及如何将其与其余WHERE子句混合在一起。


7

让我们考虑那些表:

一个

id | SomeData

id | id_A | SomeOtherData

id_A 成为表的外键 A

编写此查询:

SELECT *
FROM A
LEFT JOIN B
ON A.id = B.id_A;

将提供此结果:

/ : part of the result
                                       B
                      +---------------------------------+
            A         |                                 |
+---------------------+-------+                         |
|/////////////////////|///////|                         |
|/////////////////////|///////|                         |
|/////////////////////|///////|                         |
|/////////////////////|///////|                         |
|/////////////////////+-------+-------------------------+
|/////////////////////////////|
+-----------------------------+

A中有什么但B中没有什么意味着B的值为空。


现在,让我们考虑中的特定部分B.id_A,并从之前的结果中突出显示它:

/ : part of the result
* : part of the result with the specific B.id_A
                                       B
                      +---------------------------------+
            A         |                                 |
+---------------------+-------+                         |
|/////////////////////|///////|                         |
|/////////////////////|///////|                         |
|/////////////////////+---+///|                         |
|/////////////////////|***|///|                         |
|/////////////////////+---+---+-------------------------+
|/////////////////////////////|
+-----------------------------+

编写此查询:

SELECT *
FROM A
LEFT JOIN B
ON A.id = B.id_A
AND B.id_A = SpecificPart;

将提供此结果:

/ : part of the result
* : part of the result with the specific B.id_A
                                       B
                      +---------------------------------+
            A         |                                 |
+---------------------+-------+                         |
|/////////////////////|       |                         |
|/////////////////////|       |                         |
|/////////////////////+---+   |                         |
|/////////////////////|***|   |                         |
|/////////////////////+---+---+-------------------------+
|/////////////////////////////|
+-----------------------------+

因为这会删除内部联接中不存在的值 B.id_A = SpecificPart


现在,让我们将查询更改为:

SELECT *
FROM A
LEFT JOIN B
ON A.id = B.id_A
WHERE B.id_A = SpecificPart;

现在的结果是:

/ : part of the result
* : part of the result with the specific B.id_A
                                       B
                      +---------------------------------+
            A         |                                 |
+---------------------+-------+                         |
|                     |       |                         |
|                     |       |                         |
|                     +---+   |                         |
|                     |***|   |                         |
|                     +---+---+-------------------------+
|                             |
+-----------------------------+

因为对整个结果进行了过滤以防止B.id_A = SpecificPart删除A中的部​​分B.id_A = NULL,而B中没有的部分


4

您是要联接数据还是过滤数据?

为了提高可读性,将这些用例分别隔离在ON和WHERE上是最有意义的。

  • 在ON中加入数据
  • 在WHERE中过滤数据

读取在WHERE子句中存在JOIN条件和过滤条件的查询会变得非常困难。

在性能方面,尽管不同类型的SQL有时对查询计划的处理方式有所不同,所以您应该不会有所不同,因此值得尝试¯\_(ツ)_/¯(请注意缓存会影响查询速度)

另外,正如其他人指出的那样,如果使用外部联接,则将过滤条件放在ON子句中将得到不同的结果,因为它仅影响其中一个表。

我在这里写了一篇更深入的文章:https : //dataschool.com/learn/difference-between-where-and-on-in-sql


2

在SQL中,“ WHERE”和“ ON”子句是条件状态子,但它们之间的主要区别在于,“选择/更新”语句中使用“ Where”子句来指定条件,而“ ON”子句在联接中使用,它在联接表之前验证或检查目标表和源表中的记录是否匹配

例如:-'WHERE'

SELECT * FROM employee WHERE employee_id=101

例如:-'ON'

有两个表employee和employee_details,匹配的列是employee_id。

SELECT * FROM employee 
INNER JOIN employee_details 
ON employee.employee_id = employee_details.employee_id

希望我回答了你的问题。还原以进行任何澄清。


但是您可以使用关键字WHERE代替ON,不是吗?sqlfiddle.com/#!2/ae5b0/14/0
Qwerty

1

我认为这是连接顺序的影响。在左上连接的情况下,SQL先执行左连接,然后再执行where过滤器。在较低的情况下,首先找到Orders.ID = 12345,然后再加入。


1

用于内部联接,WHERE并且ON可以互换使用。实际上,可以ON在相关子查询中使用。例如:

update mytable
set myscore=100
where exists (
select 1 from table1
inner join table2
on (table2.key = mytable.key)
inner join table3
on (table3.key = table2.key and table3.key = table1.key)
...
)

(IMHO)完全使人感到困惑,并且很容易忘记链接table1到任何东西(因为“ driver”表没有“ on”子句),但这是合法的。


1

为了获得更好的性能,表应具有一个特殊的索引列以用于JOINS。

因此,如果您所依据的列不是那些索引列之一,那么我怀疑将其保留在WHERE中会更好。

因此,您可以使用索引列进行JOIN,然后在JOIN之后在无索引列上运行条件。


1

通常,一旦两个表已经连接,就在WHERE子句中处理过滤。有可能,尽管您可能想要在连接它们之前过滤一个或两个表。即,where子句适用于整个结果集,而on子句仅适用于有问题的联接。


事实并非如此,因为DBMS是“正常”优化的。
philipxy

1

我认为可以通过SQL逻辑逻辑顺序来最好地解释这种区别,即简化:

  • FROM (包括联接)
  • WHERE
  • GROUP BY
  • 集合体
  • HAVING
  • WINDOW
  • SELECT
  • DISTINCT
  • UNIONINTERSECTEXCEPT
  • ORDER BY
  • OFFSET
  • FETCH

联接不是select语句的子句,而是内的运算符FROM。这样,当逻辑处理到达ON子句时,属于相应JOIN运算符的所有子句在逻辑上 “已经发生” WHERE。这意味着,LEFT JOIN例如对于a而言,在WHERE应用该子句时,外部联接的语义已经发生。

我已经在此博客文章中更深入地解释了以下示例。运行此查询时:

SELECT a.actor_id, a.first_name, a.last_name, count(fa.film_id)
FROM actor a
LEFT JOIN film_actor fa ON a.actor_id = fa.actor_id
WHERE film_id < 10
GROUP BY a.actor_id, a.first_name, a.last_name
ORDER BY count(fa.film_id) ASC;

LEFT JOIN不会真的有什么有用的效果,因为即使一个演员没有在影片中扮演男主角将被过滤掉,因为它FILM_IDNULLWHERE条款将过滤这样的行。结果是这样的:

ACTOR_ID  FIRST_NAME  LAST_NAME  COUNT
--------------------------------------
194       MERYL       ALLEN      1
198       MARY        KEITEL     1
30        SANDRA      PECK       1
85        MINNIE      ZELLWEGER  1
123       JULIANNE    DENCH      1

即就像我们内部连接两个表一样。如果我们在ON子句中移动过滤谓词,则它现在成为外部联接的条件:

SELECT a.actor_id, a.first_name, a.last_name, count(fa.film_id)
FROM actor a
LEFT JOIN film_actor fa ON a.actor_id = fa.actor_id
  AND film_id < 10
GROUP BY a.actor_id, a.first_name, a.last_name
ORDER BY count(fa.film_id) ASC;

表示结果将包含没有电影或没有电影的演员 FILM_ID < 10

ACTOR_ID  FIRST_NAME  LAST_NAME     COUNT
-----------------------------------------
3         ED          CHASE         0
4         JENNIFER    DAVIS         0
5         JOHNNY      LOLLOBRIGIDA  0
6         BETTE       NICHOLSON     0
...
1         PENELOPE    GUINESS       1
200       THORA       TEMPLE        1
2         NICK        WAHLBERG      1
198       MARY        KEITEL        1

简而言之

从逻辑上讲,请始终将谓词放在最有意义的位置。


0

关于你的问题

只要服务器可以获取,内部连接的“ on”或“ where”都是相同的:

select * from a inner join b on a.c = b.c

select * from a inner join b where a.c = b.c

不是所有口译员都知道的“ where”选项,因此应该避免。当然,“ on”子句更清晰。


-5

这是我的解决方案。

SELECT song_ID,songs.fullname, singers.fullname
FROM music JOIN songs ON songs.ID = music.song_ID  
JOIN singers ON singers.ID = music.singer_ID
GROUP BY songs.fullname

必须GROUP BY得到它的工作。

希望能有所帮助。


10
在您还选择song_id和singers.fullname的同时,仅对songs.fullname进行分组将在大多数数据库中成为问题。
btilly 2011年
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.