SQL join:where子句与on子句

BCS*_*BCS 616 sql join on-clause where-clause

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


有什么区别,应该分别做些什么?

如果我理解正确的理论,查询优化器应该能够互换使用.

Joe*_*orn 802

它们不是同一件事.

考虑这些查询:

SELECT *
FROM Orders
LEFT JOIN OrderLines ON OrderLines.OrderID=Orders.ID
WHERE Orders.ID = 12345
Run Code Online (Sandbox Code Playgroud)

SELECT *
FROM Orders
LEFT JOIN OrderLines ON OrderLines.OrderID=Orders.ID 
    AND Orders.ID = 12345
Run Code Online (Sandbox Code Playgroud)

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

有了INNER JOIN,条款实际上是等价的.然而,仅仅因为它们在功能上是相同的,因为它们产生相同的结果,并不意味着这两种子句具有相同的语义含义.

  • @FistOfFury Sql Server使用查询优化器过程来编译和评估您的代码,以产生最佳的执行计划.它并不完美,但大部分时间都没关系,你会得到相同的执行计划. (91认同)
  • 通过将where子句放在内部连接的"on"子句中,您会获得更好的性能吗? (72认同)
  • 在Postgres中,我注意到它们并不等同,导致了不同的查询计划.如果使用ON,则会导致使用实现.如果您使用WHERE,则使用哈希.具体化的情况比哈希的成本高10倍.这是使用一组ID而不是一个ID. (17认同)
  • @JamesHutchison很难根据观察到的行为进行可靠的性能概括.有一天真实的情况往往是错误的,因为这是一个实现细节而不是记录的行为.数据库团队一直在寻找提高优化器性能的地方.如果ON行为没有改善以匹配WHERE,我会感到惊讶.除了"一般性能改进"之外,它可能甚至不会出现在版本到版本的任何地方. (11认同)
  • @FiHoran这不是Sql Server的工作方式.当统计数据显示它有用时,它将根据WHERE子句中的项进行积极的预过滤. (4认同)
  • 但这两个查询之间难道没有重大区别吗?如果目标是限制左侧的行(这显然是本例中两个查询的想法 - 限制到特定的顺序),那么在 WHERE 子句中使用过滤器是有意义的,但如果您限制右侧(由 OrderLine 限制),那么这就是问题所在。我的直觉告诉我,在后一种情况下,限制右侧会导致较小的连接,从而提高性能。然而,正如@JoelCoehoorn 指出的那样,这可能是一个没有实际意义的问题。 (2认同)
  • “真的”很难注意到“WHERE”和“AND”之间的区别。*请*重新格式化查询以使其更加明显。 (2认同)
  • 这里我们真正忽略的是连接多个表的情况,这是一个更实际的场景。因此,如果我们不断减少组合结果的数量,而不是拥有一个非常臃肿的最终结果集,那么我们必须使用 WHERE 进行过滤,在这些情况下,尽快将条件放入 ON 是有意义的。 (2认同)

San*_*dal 303

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

    一个.WHERE条款:加入.加入发生后,将过滤记录.

    ON条款 - 加入之前.在加入之前将过滤记录(来自右表).这可能最终在结果中为null(因为OUTER join).



示例:请考虑以下表格:

    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     |
Run Code Online (Sandbox Code Playgroud)

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  | 
Run Code Online (Sandbox Code Playgroud)

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 | 
Run Code Online (Sandbox Code Playgroud)

  • IMO这是最好的答案,因为它清楚地展示了其他流行答案的"幕后". (30认同)
  • 这是一个很好的答案,有正确的解释。不过我认为值得一提的是,大多数(如果不是全部)SQL 服务器实际上在应用“WHERE”条件之前不会像这样创建完整的中间表。他们都有优化!了解这一点非常重要,因为当您的查询包含具有数百万行的表的许多 JOINS 时,但您的“WHERE”条件将结果集限制为只有几行,请考虑创建这个大笛卡尔积中间的性能 -表只是丢弃 99.9% 的结果行可能会很可怕。:) 和误导。 (5认同)
  • @ManuelJordan 不,这只是为了解释。数据库可以执行比创建中间表更高效的操作。 (4认同)
  • 很好的解释......干得好!- 只是好奇你做了什么来获得“中间连接表”?一些“解释”命令? (3认同)
  • 我已将其制作为 [dbfiddle](https://dbfiddle.uk/91VQS9x0),以便任何人都可以玩弄它。 (3认同)

Cad*_*oux 141

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

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

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

  • [在SQL Server中有一个边缘情况,对于内部连接,它确实有所作为](http://stackoverflow.com/questions/4694281/is-it-better-to-do-an-equi-join-in-的从 - 子句 - 或-where子句/ 7967048#7967048) (5认同)

HLG*_*GEM 43

我这样做的方式是:

  • ON如果你正在做的话,总是把连接条件放在子句中INNER JOIN.因此,不要向ON子句添加任何WHERE条件,将它们放在WHERE子句中.

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

    例外情况是您查找不在特定表中的记录.您可以通过以下方式将对RIGHT JOIN表中的唯一标识符(不是NULL)的引用添加到WHERE子句中:WHERE t2.idfield IS NULL.因此,您应该在连接的右侧引用表的唯一时间是查找不在表中的那些记录.

  • 到目前为止,这是我读过的最好的答案.一旦你的大脑理解左连接**将返回左表中的所有行并且你必须稍后过滤它,那么完全有道理. (7认同)

mat*_*t b 30

在内连接上,它们意味着同样的事情.但是,在外连接中将获得不同的结果,具体取决于是否将连接条件放在WHERE与ON子句中.看看这个相关的问题这个答案(由我).

我认为最常见的做法是始终将连接条件放在ON子句中(除非它是外部连接,并且实际上确实需要在where子句中),因为它使任何读取查询的人都更清楚这些表的连接条件是什么,它还有助于防止WHERE子句长达数十行.


Vla*_*cea 22

本文清楚地解释了差异.它还解释了"ON joined_condition vs WHERE joined_condition或joined_alias为null".

WHERE子句过滤JOIN的左侧和右侧,而ON子句将始终仅过滤右侧.

  1. 如果你总是想要获取左侧行而只是在某些条件匹配时才加入,那么你应该使用ON子句.
  2. 如果要过滤连接双方的产品,则应使用WHERE子句.

  • 感谢你的回答。这是一本很好的书,但它讨论了除了这里提出的问题之外的所有内容 (6认同)

Cid*_*Cid 13

让我们考虑这些表:

一种

id | SomeData
Run Code Online (Sandbox Code Playgroud)

id | id_A | SomeOtherData
Run Code Online (Sandbox Code Playgroud)

id_A 作为表的外键 A

编写此查询:

SELECT *
FROM A
LEFT JOIN B
ON A.id = B.id_A;
Run Code Online (Sandbox Code Playgroud)

将提供这个结果:

/ : part of the result
                                       B
                      +---------------------------------+
            A         |                                 |
+---------------------+-------+                         |
|/////////////////////|///////|                         |
|/////////////////////|///////|                         |
|/////////////////////|///////|                         |
|/////////////////////|///////|                         |
|/////////////////////+-------+-------------------------+
|/////////////////////////////|
+-----------------------------+
Run Code Online (Sandbox Code Playgroud)

在 A 中但不在 B 中的内容意味着 B 有空值。


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

/ : part of the result
* : part of the result with the specific B.id_A
                                       B
                      +---------------------------------+
            A         |                                 |
+---------------------+-------+                         |
|/////////////////////|///////|                         |
|/////////////////////|///////|                         |
|/////////////////////+---+///|                         |
|/////////////////////|***|///|                         |
|/////////////////////+---+---+-------------------------+
|/////////////////////////////|
+-----------------------------+
Run Code Online (Sandbox Code Playgroud)

编写此查询:

SELECT *
FROM A
LEFT JOIN B
ON A.id = B.id_A
AND B.id_A = SpecificPart;
Run Code Online (Sandbox Code Playgroud)

将提供这个结果:

/ : part of the result
* : part of the result with the specific B.id_A
                                       B
                      +---------------------------------+
            A         |                                 |
+---------------------+-------+                         |
|/////////////////////|       |                         |
|/////////////////////|       |                         |
|/////////////////////+---+   |                         |
|/////////////////////|***|   |                         |
|/////////////////////+---+---+-------------------------+
|/////////////////////////////|
+-----------------------------+
Run Code Online (Sandbox Code Playgroud)

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


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

SELECT *
FROM A
LEFT JOIN B
ON A.id = B.id_A
WHERE B.id_A = SpecificPart;
Run Code Online (Sandbox Code Playgroud)

结果现在是:

/ : part of the result
* : part of the result with the specific B.id_A
                                       B
                      +---------------------------------+
            A         |                                 |
+---------------------+-------+                         |
|                     |       |                         |
|                     |       |                         |
|                     +---+   |                         |
|                     |***|   |                         |
|                     +---+---+-------------------------+
|                             |
+-----------------------------+
Run Code Online (Sandbox Code Playgroud)

因为整个结果进行筛选B.id_A = SpecificPart除去部件B.id_A IS NULL,即是在甲不在乙


Hri*_*hra 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    |       |
+-------+-------------+------+-----+---------+-------+
Run Code Online (Sandbox Code Playgroud)

fid是表t2的id.

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)
Run Code Online (Sandbox Code Playgroud)

查询"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)
Run Code Online (Sandbox Code Playgroud)

查询"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)
Run Code Online (Sandbox Code Playgroud)

很明显,第一个查询从行t1.v ='K'返回来自t1的记录及其从t2的依赖行(如果有的话).

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


Gra*_*erg 8

就优化器而言,无论是使用ON还是WHERE定义join子句,都不应该有所区别.

但是,恕我直言,我认为在执行连接时使用ON子句要清楚得多.这样,您有一个特定的查询部分,它指示如何处理连接而不是与其余的WHERE子句混合.


小智 6

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

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

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

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

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

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

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