SQL join:where子句与on子句

时间:2008-12-09 20:14:52

标签: sql join where-clause on-clause

阅读后,Explicit vs Implicit SQL Joins的副本。 答案可能是相关的(甚至相同),但问题是不同的。


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

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

21 个答案:

答案 0 :(得分:758)

他们不是一回事。

考虑以下问题:

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,条款有效等效。然而,仅仅因为它们在功能上是相同的,因为它们产生相同的结果,并不意味着这两种从句具有相同的语义。

答案 1 :(得分:257)

  • 对于内连接无关紧要
  • 外部联接的事项

    一个。 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     |

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 | 

答案 2 :(得分:140)

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

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

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

答案 3 :(得分:40)

我这样做的方式是:

  • 如果您正在执行ON,请始终将连接条件放在INNER JOIN子句中。因此,不要在ON子句中添加任何WHERE条件,将它们放在WHERE子句中。

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

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

答案 4 :(得分:30)

在内连接上,它们的意思相同。但是,在外连接中将获得不同的结果,具体取决于是否将连接条件放在WHERE与ON子句中。请查看this related questionthis answer(由我)。

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

答案 5 :(得分:21)

This article清楚地解释了差异。它还解释了“ON joined_condition vs WHERE joined_condition或joined_alias为null”。

WHERE子句过滤FROM子句的结果以及JOIN,而ON子句用于在FROM和JOIN表之间生成表结果。

  1. 如果要生成连接两个表的表结果,则应该使用ON子句来确定表的连接方式。当然,例如,如果是INNER JOIN,这也可以过滤原始表中的行。
  2. 如果要过滤连接双方的产品,则应使用WHERE子句。

答案 6 :(得分: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    |       |
+-------+-------------+------+-----+---------+-------+

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)

查询" 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'将与它有任何相关的行。

答案 7 :(得分:8)

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

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

答案 8 :(得分:1)

我认为这是连接序列效应。 在左上角连接的情况下,SQL首先执行左连接,然后执行筛选。 在downer的情况下,首先找到Orders.ID = 12345,然后再加入。

答案 9 :(得分:1)

在SQL中,'WHERE'和'ON'子句是一种条件状态,但它们之间的主要区别在于,'Where'子句在Select / Update Statements中用于指定条件,而'' ON'Clause在Joins中使用,在连接表之前验证或检查记录是否在目标和源表中匹配

例如: - 'WHERE'

SELECT * FROM employee WHERE employee_id=101

例如: - '开'

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

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

希望我已经回答了你的问题。 恢复任何澄清。

答案 10 :(得分:1)

对于内部联接,WHEREON可以互换使用。实际上,可以在相关子查询中使用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)
...
)

这是(恕我直言)对人类完全混淆,很容易忘记将table1链接到任何东西(因为“驱动程序”表没有“on”条款),但它是合法的。

答案 11 :(得分:1)

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

因此,如果您所关注的列不是这些索引列之一,那么我怀疑将它保存在WHERE中会更好。

所以你使用索引列加入,然后在JOIN之后在无索引列上运行条件。

答案 12 :(得分:1)

通常,一旦两个表已经连接,就会在WHERE子句中处理过滤。虽然您可能希望在加入表之前过滤其中的一个或两个表,但这是可能的。 即,where子句适用于整个结果集,而on子句仅适用于有问题的连接。

答案 13 :(得分:1)

您要合并数据还是过滤数据?

出于可读性考虑,将这些用例分别隔离到ON和WHERE最为有意义。

  • 将数据连接为开
  • 在WHERE中过滤数据

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

从性能上来说,您应该看不出任何区别,尽管不同类型的SQL有时对查询计划的处理方式也不同,因此值得尝试¯\_(ツ)_/¯(请注意缓存会影响查询速度)

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

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

答案 14 :(得分:0)

我认为可以通过logical order of operations in SQL(最好是简化的)来最好地解释这种区别:

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

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

I've explained the following example more in depth in this blog post。运行此查询时:

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_ID将是NULL WHERE子句将过滤这样的行。结果是这样的:

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

简而言之

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

答案 15 :(得分:0)

让我们考虑一下这些表:

A

id | SomeData

B

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.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过滤了整个结果,因此删除了{em> A中不在B中的部分B.id_A = NULL

答案 16 :(得分: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”子句更清楚。

答案 17 :(得分:0)

从字面上看,它们是等效的

在大多数开源数据库中(最著名的示例,在 MySql postgresql 中),查询计划是出现在 Access Path中的经典算法的一种变体关系数据库管理系统中的选择(Selinger等,1979)。这种方法有两种类型的条件

  • 条件引用单个表(用于过滤)
  • 引用两个表的
  • 条件(被视为 join条件,无论出现在何处

特别是在MySql中,通过跟踪优化器,您可以看到自己join .. on条件在解析期间被替换为等效的{{1} } 条件。在postgresql中也会发生类似的事情(尽管无法通过日志查看它,但是您必须阅读源代码描述)。

无论如何,要点是,在解析/查询重写阶段,两个语法变体之间的差异丢失了,甚至还没有达到查询计划和执行阶段。因此,毫无疑问,它们在性能上是否相等,它们在进入执行阶段很久之前就变得相同

您可以使用where来验证它们是否生成相同的计划。例如,在postgres中,即使您未在任何地方使用explain语法,该计划也会包含一个join子句。

Oracle和SQL Server不是开源的,但是据我所知,它们是基于等效规则的(类似于关系代数中的规则),并且在两种情况下它们也产生相同的执行计划。

很明显,对于外部联接,这两种语法样式 不相同,因为必须使用join..on语法

答案 18 :(得分:0)

为了添加到 Joel Coehoorn 的响应中,我将添加一些特定于 sqlite 的优化信息(其他 SQL 风格的行为可能有所不同)。在原始示例中,LEFT JOIN 具有不同的结果,具体取决于您使用的是 JOIN ON ... WHERE 还是 JOIN ON ... AND。下面是一个稍微修改的例子来说明:

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

对比

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

现在,原始答案指出,如果您使用普通内连接而不是左连接,则两个查询的结果将相同,但执行计划会有所不同。我最近意识到两者之间的语义差异在于前者强制查询优化器使用与ON子句关联的索引,而后者允许优化器选择其中的任何索引ON ... AND 子句,取决于它认为最有效的方式。

有时,优化器会猜错,您会想要强制执行某个执行计划。在这种情况下,假设 SQLite 优化器错误地得出执行此连接的最快方法是使用 Orders.Username 上的索引,当您从经验测试中知道 Orders.ID 上的索引将提供您的查询速度更快。

在这种情况下,前一个 JOIN ON ... WHERE 语法本质上允许您强制ID 参数上进行主要连接操作,对 {{1} 进行二次过滤仅在主连接完成后执行。相比之下,Username 语法允许优化器选择是使用 JOIN ON ... AND 上的索引还是 Orders.ID 上的索引,并且理论上有可能选择最终速度较慢的那个。< /p>

答案 19 :(得分:-1)

一个。 WHERE 子句:加入后,记录将被过滤。

B. ON 子句 - 在加入之前,记录(来自右表)将被过滤。

答案 20 :(得分:-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才能让它发挥作用。

希望得到这个帮助。