阅读后,这不是与 Explicit vs Implicit SQL Joins 的重复。答案可能相关(甚至相同),但问题却不同。
有什么区别,每个应该做什么?
如果我正确理解了理论,查询优化器应该能够互换使用两者。
它们不是同一件事。
考虑以下查询:
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
,子句实际上 等效。然而,仅仅因为它们在功能上是相同的,因为它们产生相同的结果,并不意味着这两种从句具有相同的语义。
对于内部连接无关紧要
外部连接的事项WHERE 子句:加入后。加入后将过滤记录。湾。 ON 子句 - 在加入之前。加入前将过滤记录(来自右表)。这可能最终在结果中为 null(因为 OUTER join)。
示例:考虑下表:
文档:id 名称 1 Document1 2 Document2 3 Document3 4 Document4 5 Document5 下载:id document_id 用户名 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'
对于上述查询,中间连接表将如下所示。
id(来自文档) name id(来自下载) 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
应用 WHERE
子句并选择列出的属性后,结果将是:
名称 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'
对于上述查询,中间连接表将如下所示。
id(来自文档) name id(来自下载) 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
请注意 documents
中与这两个条件都不匹配的行是如何用 NULL
值填充的。
选择列出的属性后,结果将是:
名称 id Document1 1 Document2 3 Document3 NULL Document4 NULL Document5 NULL
intermediate join table
?。一些“解释”命令?
WHERE
条件之前实际上并没有像这样创建完整的中间表。他们都有优化!知道这一点非常重要,因为当您的查询包含具有数百万行的表的许多 JOINS 时,但您的 WHERE
条件将结果集限制为仅几行时,考虑创建这个大笛卡尔积中间体的性能table 只是扔掉 99.9% 的结果行可能会很吓人。 :) 和误导。
在 INNER JOIN
上它们可以互换,优化器会随意重新排列它们。
在 OUTER JOIN
上,它们不一定可以互换,具体取决于它们所依赖的联接的哪一侧。
我根据可读性将它们放在任何一个地方。
Orders.Join( OrderLines, x => x.ID, x => OrderID, (o,l) => new {Orders = o, Lines = l}).Where( ol => ol.Orders.ID = 12345)
我这样做的方式是:
如果您正在执行 INNER JOIN,请始终将连接条件放在 ON 子句中。因此,不要在 ON 子句中添加任何 WHERE 条件,将它们放在 WHERE 子句中。
如果您正在执行 LEFT JOIN,请将任何 WHERE 条件添加到连接右侧表的 ON 子句。这是必须的,因为添加引用连接右侧的 WHERE 子句会将连接转换为 INNER JOIN。例外情况是当您查找不在特定表中的记录时。您可以通过这种方式将 RIGHT JOIN 表中对唯一标识符(永远不是 NULL)的引用添加到 WHERE 子句:WHERE t2.idfield IS NULL。因此,您应该引用连接右侧的表的唯一时间是查找那些不在表中的记录。
在内部连接中,它们的含义相同。但是,根据您是否将连接条件放在 WHERE 与 ON 子句中,您将在外连接中得到不同的结果。看看 this related question 和 this answer(我的)。
我认为养成始终将连接条件放在 ON 子句中的习惯是最有意义的(除非它是外连接并且您确实希望在 where 子句中使用它),因为它使阅读您的查询的任何人都更清楚连接表的条件是什么,它还有助于防止 WHERE 子句长达数十行。
表关系
考虑到我们有以下 post
和 post_comment
表:
https://i.stack.imgur.com/YjnW7.png
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
我们将获得 post
和 post_comment
记录的所有组合:
| p.id | pc.id |
|---------|------------|
| 1 | 1 |
| 1 | 2 |
| 1 | 3 |
| 2 | 1 |
| 2 | 2 |
| 2 | 3 |
| 3 | 1 |
| 3 | 2 |
| 3 | 3 |
因此,如果 ON 子句条件为“始终为真”,则 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“总是假”条件
另一方面,如果 ON 子句条件为“始终为假”,则所有连接的记录都将被过滤掉,结果集将为空。
因此,如果我们执行以下 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 子句条件是子表中的外键列与父表中的主键列匹配的条件,如以下查询所示:
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_comment
的 post
行被排除,因为它们不能满足 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 | p.title | pc.review | |------|------------|-------|-----------|-----------| | 1 | 1 | 1 | Java | Good | | 1 | 1 | 2 | Java | Excellent || 1 | 2 | 3 | Java | Awesome || 2 | 1 | 1 | Hibernate | Good || 2 | 1 | 2 | Hibernate | Excellent || 2 | 2 | 3 | Hibernate | Awesome || 3 | 1 | 1 | JPA | Good || 3 | 1 | 2 | JPA | Excellent || 3 | 2 | 3 | JPA | Awesome |
结论
INNER JOIN 语句可以重写为 CROSS JOIN,其 WHERE 子句与您在 INNER JOIN 查询的 ON 子句中使用的条件匹配。
并不是说这仅适用于 INNER JOIN,不适用于 OUTER JOIN。
当涉及到左连接时,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 的记录和来自 t2 的相关行,如果有的话,对于行 t1.v = 'K'。
第二个查询从 t1 返回行,但仅对于 t1.v = 'K' 将有任何关联的行。
让我们考虑这些表:
一个
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
过滤的,删除了部分 B.id_A IS NULL
,即在 A 中但不在 B 中的部分
就优化器而言,是否使用 ON 或 WHERE 定义连接子句应该没有区别。
但是,恕我直言,我认为在执行连接时使用 ON 子句要清楚得多。这样,您就有了查询的特定部分,该部分指示如何处理连接,而不是与其余的 WHERE 子句混合。
您是要加入数据还是过滤数据?
为了可读性,将这些用例分别隔离到 ON 和 WHERE 是最有意义的。
在 ON 中加入数据
在 WHERE 中过滤数据
读取 WHERE 子句中存在 JOIN 条件和过滤条件的查询会变得非常困难。
性能方面,您不应该看到差异,尽管不同类型的 SQL 有时会以不同的方式处理查询计划,因此值得尝试 ¯\_(ツ)_/¯
(请注意缓存会影响查询速度)
也正如其他人所指出的,如果您使用外连接,如果您将过滤条件放在 ON 子句中,则会得到不同的结果,因为它只影响其中一个表。
我在这里写了一篇更深入的帖子:https://dataschool.com/learn/difference-between-where-and-on-in-sql
在 SQL 中,'WHERE' 和 'ON' 子句是一种条件语句,但它们之间的主要区别在于,在 Select/Update 语句中使用 'Where' 子句来指定条件,而 'ON' 子句用于联接,在联接表之前验证或检查目标表和源表中的记录是否匹配
例如: - '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
希望我已经回答了你的问题。恢复任何澄清。
我认为这是连接序列效应。在左上连接的情况下,SQL 先做左连接,再做 where 过滤。在较弱的情况下,先找到 Orders.ID=12345,然后再加入。
对于内部连接,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)
...
)
这(恕我直言)对人类来说完全是一种困惑,而且很容易忘记将 table1
链接到任何东西(因为“驱动程序”表没有“on”子句),但它是合法的。
为了获得更好的性能,表应该有一个特殊的索引列用于 JOINS 。
因此,如果您条件的列不是那些索引列之一,那么我怀疑最好将其保留在 WHERE 中。
所以你 JOIN 使用索引列,然后在 JOIN 之后在无索引列上运行条件。
通常,一旦两个表已经连接,过滤就会在 WHERE 子句中处理。有可能,但您可能希望在加入其中一个或两个表之前对其进行过滤。即,where 子句适用于整个结果集,而 on 子句仅适用于相关连接。
我认为这种区别可以通过 logical order of operations in SQL 得到最好的解释,即简化:
FROM(包括连接)
在哪里
通过...分组
聚合
拥有
窗户
选择
清楚的
联合,相交,除了
订购方式
抵消
拿来
联接不是 select 语句的子句,而是 FROM
内的运算符。因此,当逻辑处理到达 WHERE
子句时,属于相应 JOIN
运算符的所有 ON
子句都“已经发生”逻辑。这意味着在 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
简而言之
总是把你的谓词放在最合乎逻辑的地方。
从字面上看,它们是等价的。
在大多数开源数据库(最著名的例子,在 MySql 和 postgresql 中)中,查询计划是出现在关系数据库管理系统中的访问路径选择中的经典算法的变体(Selinger 等人,1979 年)。在这种方法中,条件有两种类型
引用单个表的条件(用于过滤)
引用两个表的条件(视为连接条件,无论它们出现在哪里)
特别是在 MySql 中,您可以看到自己,通过跟踪优化器,join .. on
条件在解析期间被替换为等效的 where
条件。在 postgresql 中也会发生类似的事情(虽然无法通过日志查看,但您必须阅读源描述)。
无论如何,重点是,两种语法变体之间的差异在解析/查询重写阶段丢失了,甚至没有到达查询计划和执行阶段。因此,毫无疑问它们在性能方面是否相同,它们在到达执行阶段之前很久就变得相同。
您可以使用 explain
来验证它们是否生成相同的计划。例如,在 postgres 中,计划将包含一个 join
子句,即使您没有在任何地方使用 join..on
语法。
Oracle 和 SQL Server 不是开源的,但据我所知,它们是基于等价规则(类似于关系代数中的规则),并且它们在两种情况下也产生相同的执行计划。
显然,这两种语法风格对于外连接来说是不等价的,对于那些你必须使用 join ... on 语法的人来说
关于你的问题,
只要您的服务器可以获取它,内部连接上的“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”子句更加清晰。
一个。 WHERE 子句:加入后,会过滤记录。
湾。 ON 子句 - 在加入之前,将过滤记录(来自右表)。
为了添加到 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
子句关联的索引,而后者允许优化器选择 { 2} 子句,取决于它认为最有效的方式。
有时,优化器会猜错,您会想要强制执行某个执行计划。在这种情况下,假设 SQLite 优化器错误地断定执行此连接的最快方法是使用 Orders.Username
上的索引,而您从经验测试中知道 Orders.ID
上的索引会更快地传递您的查询。
在这种情况下,以前的 JOIN ON ... WHERE
语法本质上允许您强制在 ID
参数上发生主连接操作,而对 Username
的辅助过滤仅在主连接完成后执行.相比之下,JOIN ON ... AND
语法允许优化器选择是使用 Orders.ID
还是 Orders.Username
上的索引,理论上它可能会选择最终速度较慢的那个。
这是我的解决方案。
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
才能使其工作。
希望这有帮助。