ChatGPT解决这个技术问题 Extra ChatGPT

加入与子查询

我是一个老式的 MySQL 用户,并且一直更喜欢 JOIN 而不是子查询。但是现在每个人都使用子查询,我讨厌它;我不知道为什么。

我缺乏理论知识来自己判断是否有任何区别。子查询是否与 JOIN 一样好,因此无需担心?

子查询有时很棒。他们在 MySQL 中表现不佳。不要使用它们。
我一直认为子查询在某些数据库技术中被隐式地作为连接执行。
子查询并不总是很糟糕,当加入相当大的表时,首选的方法是从那个大表中进行子选择(限制行数)然后加入。
“现在每个人都使用子查询”[需要引用]

M
Marcelo Cantos

子查询是解决“从 A 获取事实,以来自 B 的事实为条件”形式的问题的逻辑正确方法。在这种情况下,将 B 粘贴在子查询中比进行联接更符合逻辑。从实际意义上讲,它也更安全,因为您不必因为与 B 的多次匹配而对从 A 获取重复的事实保持谨慎。

然而,实际上,答案通常归结为性能。一些优化器在给定连接与子查询时会吃柠檬,而另一些优化器会以另一种方式吸柠檬,这是特定于优化器、特定于 DBMS 版本和特定于查询的。

从历史上看,显式连接通常会获胜,因此连接更好的既定智慧,但优化器一直在变得更好,所以我更喜欢首先以逻辑连贯的方式编写查询,然后在性能限制允许的情况下进行重组。


很好的答案。我还要补充一点,开发人员(尤其是业余的)并不总是精通 SQL。
+1 为这个问题寻找一些合乎逻辑的解释很长一段时间,这只是我觉得合乎逻辑的答案
@Marcelo Cantos,您能否举例说明您的陈述“从实际意义上讲,这也更安全,因为由于与 B 的多次比赛,您不必谨慎地从 A 获取重复的事实。”?我发现这很有见地,但有点太抽象了。谢谢。
@JinghuiNiu 买贵重物品的顾客:select custid from cust join bought using (custid) where price > 500。如果客户购买了多件昂贵的商品,您将获得双倍收益。要解决此问题,select custid from cust where exists (select * from bought where custid = cust.custid and price > 500)。您可以改用 select distinct …,但对于优化器或评估器来说,它通常需要更多的工作。
@MatTheWhale 是的,我使用了过于简单的答案,因为我很懒惰。在真实场景中,您会从 cust 中提取更多列,而不仅仅是 custid。
h
htoip

在大多数情况下,JOIN 比子查询更快,并且子查询更快的情况很少见。

JOIN 中,RDBMS 可以创建一个更适合您的查询的执行计划,并且可以预测应该加载哪些数据以进行处理并节省时间,这与子查询不同,子查询将运行所有查询并将所有数据加载到做处理。

子查询的好处是它们比 JOIN 更具可读性:这就是大多数 SQL 新手更喜欢它们的原因;这是简单的方法;但是在性能方面,JOINS 在大多数情况下都更好,即使它们也不难阅读。


是的,因此大多数数据库将其作为优化步骤包括在分析查询时将子查询转换为连接。
对于所提出的问题,这个答案有点过于简单。正如您所说:某些子查询可以,某些子查询不行。答案并不能真正帮助区分两者。 (“非常罕见”也取决于您的数据/应用程序)。
你能用文档参考或测试结果证明你的任何观点吗?
我在包含对上层查询的反向引用的子查询方面取得了非常好的经验,特别是当涉及到超过 100,000 的行数时。事情似乎是内存使用和对交换文件的分页。连接会产生大量数据,这些数据可能不适合内存,必须分页到交换文件中。在这种情况下,与连接相比,像 select * from a where a.x = (select b.x form b where b.id = a.id) 这样的小型子选择的查询时间非常小。这是一个非常具体的问题,但在某些情况下,它会使您从数小时缩短到数分钟。
我有使用 Oracle 的经验,我可以说,如果您没有对它们进行任何过滤或排序,子查询在大型表上会好得多。
s
simhumileco

取自 MySQL 手册 (13.2.10.11 Rewriting Subqueries as Joins):

LEFT [OUTER] JOIN 可以比等效的子查询更快,因为服务器可能能够更好地优化它——这一事实并非仅针对 MySQL 服务器。

所以子查询可能比 LEFT [OUTER] JOIN 慢,但在我看来,它们的优势在于可读性略高。


@user1735921 IMO 这取决于...通常,代码的可读性非常重要,因为它对于以后的管理非常重要...让我们记住 Donald Knuth 的名言:“过早的优化是一切的根源编程中的邪恶(或至少大部分)”。然而,自然有一些编程领域的性能是最重要的......理想情况下,当一个人成功地与另一个人协调时:)
在更复杂的查询中,我发现连接比子查询更容易阅读。子查询在我脑海里变成了一碗面条。
@user1735921 当然,尤其是当查询变得如此复杂以至于它做错了事情并且您花了一天时间修复它时……像往常一样,两者之间有一个平衡。
@user1735921 仅当性能提升值得增加未来所需的维护时间时
我认为Joinsub query的语法不同,可读性我们无法比较,只要你精通SQL语法,它们的可读性都更高。性能更重要。
T
Trendfischer

在 2010 年,我会加入这个问题的作者并强烈投票给 JOIN,但有更多的经验(尤其是在 MySQL 方面)我可以说:是的,子查询可以更好。我在这里阅读了多个答案;一些声明的子查询更快,但它缺乏一个很好的解释。我希望我能提供这个(非常)迟到的答案:

首先说最重要的:子查询有不同的形式

第二个重要声明:大小很重要

如果您使用子查询,您应该知道 DB-Server 如何执行子查询。特别是如果子查询被评估一次或每一行!另一方面,现代 DB-Server 能够进行很多优化。在某些情况下,子查询有助于优化查询,但较新版本的 DB-Server 可能会使优化过时。

选择字段中的子查询

SELECT moo, (SELECT roger FROM wilco WHERE moo = me) AS bar FROM foo

请注意,对于来自 foo 的每个结果行都会执行一个子查询。
尽可能避免这种情况;它可能会大大减慢您对大型数据集的查询。但是,如果子查询没有对 foo 的引用,它可以由 DB-server 优化为静态内容,并且只能评估一次。

Where 语句中的子查询

SELECT moo FROM foo WHERE bar = (SELECT roger FROM wilco WHERE moo = me)

如果幸运的话,数据库会在内部将其优化为 JOIN。如果没有,您的查询在大型数据集上将变得非常非常慢,因为它将为 foo 中的每一行执行子查询,而不仅仅是像 select-type 中的结果。

Join 语句中的子查询

SELECT moo, bar 
  FROM foo 
    LEFT JOIN (
      SELECT MIN(bar), me FROM wilco GROUP BY me
    ) ON moo = me

这是有趣的。我们将 JOIN 与子查询结合起来。在这里,我们得到了子查询的真正力量。想象一下 wilco 中包含数百万行但只有几个不同的 me 的数据集。我们现在有一个较小的临时表来连接,而不是连接一个巨大的表。根据数据库大小,这可能会导致查询速度更快。您可以使用 CREATE TEMPORARY TABLE ...INSERT INTO ... SELECT ... 获得相同的效果,这可能会为非常复杂的查询提供更好的可读性(但可以将数据集锁定在可重复的读取隔离级别)。

嵌套子查询

SELECT VARIANCE(moo)
  FROM (
    SELECT moo, CONCAT(roger, wilco) AS bar
      FROM foo
      HAVING bar LIKE 'SpaceQ%'
  ) AS temp_foo
  GROUP BY moo

您可以在多个级别中嵌套子查询。如果您必须对结果进行分组或更改,这可以帮助处理庞大的数据集。通常 DB-Server 会为此创建一个临时表,但有时您不需要对整个表进行一些操作,只需要对结果集进行一些操作。根据表的大小,这可能会提供更好的性能。

结论

子查询不能替代 JOIN,您不应像这样使用它们(尽管可能)。在我看来,子查询的正确使用是作为 CREATE TEMPORARY TABLE ... 的快速替换。一个好的子查询以您无法在 JOINON 语句中完成的方式减少数据集。如果子查询具有关键字 GROUP BYDISTINCT 之一,并且最好不位于选择字段或 where 语句中,那么它可能会大大提高性能。


对于Sub-queries in the Join-statement:(1) 从子查询本身生成派生表可能需要很长时间。 (2) 生成的派生表没有索引。这两个单独可以显着减慢 SQL。
@jxc 我只能代表 MySQL (1) 它有一个类似于连接的临时表。时间取决于数据量。如果您不能使用子查询减少数据,请使用联接。 (2) 这是对的,这取决于你可以减少临时表中数据的因素。我有真实世界的案例,我可以将连接大小从几百万减少到几百,并使用子查询将查询时间从几秒(使用完整索引)减少到四分之一秒。
IMO:(1)这样的临时表(派生表)没有具体化,因此每次运行 SQL 时,都必须重新创建临时表,这可能非常昂贵并且是一个真正的瓶颈(即运行一个数百万的组记录数)(2)即使您可以将临时表的大小减少到 10 条记录,因为没有索引,这仍然意味着在 JOINing 其他表时可能查询比没有临时表多 9 倍的数据记录.顺便说一句,我的 db(MySQL) 之前遇到过这个问题,就我而言,在 SELECT list 中使用子查询可能会快得多。
@jxc 我不怀疑有很多示例,其中使用子查询不太理想。作为一种好的做法,您应该在优化之前对查询使用 EXPLAIN。使用旧的 set profiling=1,您可以很容易地看到临时表是否是瓶颈。甚至一个索引也需要处理时间,B-Trees 优化了对记录的查询,但是一个 10 记录的表可以比数百万记录的索引快得多。但这取决于多种因素,例如字段大小和类型。
使用 EXPLAIN 应该足以了解 DERIVED 表(从 FROM 列表中的子查询创建)如何影响查询。我在工作中经常使用子查询,只是想提一下 sub-queries in the Join-statement 可能没有你想象的那么有前途。临时表中减少的记录的成本可能比其收益大得多。还要记住,即使最终的 JOIN 可能花费更少的时间,扫描子查询中数百万条记录的时间仍然存在,并且需要为每个 SQL 运行计算。
F
Frank Heikens

使用 EXPLAIN 查看您的数据库如何对您的数据执行查询。这个答案中有一个巨大的“取决于”......

当 PostgreSQL 认为一个比另一个快时,它可以将一个子查询重写为一个连接或一个连接到一个子查询。这一切都取决于数据、索引、相关性、数据量、查询等。


这就是为什么 postgresql 如此优秀和有用的原因,它了解目标是什么,并将根据它认为更好的方式修复查询,并且 postgresql 非常擅长了解如何查看其数据
哇。我想不需要为我重写大量的查询! postgresql 的胜利。
我的 left joinsub query 慢得多,所以我认为它不会真正完成这项工作。
b
broadband

首先,要首先比较两者,您应该将查询与子查询区分开来:

一类子查询,总是有对应的用连接编写的等效查询 一类不能用连接重写的子查询

对于第一类查询,一个好的 RDBMS 会将连接和子查询视为等效的,并且会产生相同的查询计划。

这些天甚至mysql也这样做。

不过,有时它不会,但这并不意味着连接总是会赢——我在 mysql 中使用子查询提高了性能时遇到过一些情况。 (例如,如果有什么东西阻止 mysql planner 正确估计成本,并且如果 planner 没有看到 join-variant 和 subquery-variant 相同,那么子查询可以通过强制某个路径来胜过连接)。

结论是,如果您想确定哪一种性能更好,您应该测试联接和子查询变体的查询。

对于第二类,比较没有意义,因为这些查询不能使用连接重写,在这些情况下,子查询是完成所需任务的自然方式,您不应该歧视它们。


您能否提供一个使用无法转换为连接的子查询编写的查询示例(第二类,如您所说)?
p
pkaramol

我认为引用的答案中没有强调的是特定(用例)可能产生的重复和有问题的结果的问题。

(尽管马塞洛·坎托斯确实提到过)

我将引用斯坦福大学关于 SQL 的 Lagunita 课程中的示例。

学生桌

+------+--------+------+--------+
| sID  | sName  | GPA  | sizeHS |
+------+--------+------+--------+
|  123 | Amy    |  3.9 |   1000 |
|  234 | Bob    |  3.6 |   1500 |
|  345 | Craig  |  3.5 |    500 |
|  456 | Doris  |  3.9 |   1000 |
|  567 | Edward |  2.9 |   2000 |
|  678 | Fay    |  3.8 |    200 |
|  789 | Gary   |  3.4 |    800 |
|  987 | Helen  |  3.7 |    800 |
|  876 | Irene  |  3.9 |    400 |
|  765 | Jay    |  2.9 |   1500 |
|  654 | Amy    |  3.9 |   1000 |
|  543 | Craig  |  3.4 |   2000 |
+------+--------+------+--------+

申请表

(针对特定大学和专业的申请)

+------+----------+----------------+----------+
| sID  | cName    | major          | decision |
+------+----------+----------------+----------+
|  123 | Stanford | CS             | Y        |
|  123 | Stanford | EE             | N        |
|  123 | Berkeley | CS             | Y        |
|  123 | Cornell  | EE             | Y        |
|  234 | Berkeley | biology        | N        |
|  345 | MIT      | bioengineering | Y        |
|  345 | Cornell  | bioengineering | N        |
|  345 | Cornell  | CS             | Y        |
|  345 | Cornell  | EE             | N        |
|  678 | Stanford | history        | Y        |
|  987 | Stanford | CS             | Y        |
|  987 | Berkeley | CS             | Y        |
|  876 | Stanford | CS             | N        |
|  876 | MIT      | biology        | Y        |
|  876 | MIT      | marine biology | N        |
|  765 | Stanford | history        | Y        |
|  765 | Cornell  | history        | N        |
|  765 | Cornell  | psychology     | Y        |
|  543 | MIT      | CS             | N        |
+------+----------+----------------+----------+

让我们试着找出申请了CS专业的学生的GPA分数(不分大学)

使用子查询:

select GPA from Student where sID in (select sID from Apply where major = 'CS');

+------+
| GPA  |
+------+
|  3.9 |
|  3.5 |
|  3.7 |
|  3.9 |
|  3.4 |
+------+

此结果集的平均值为:

select avg(GPA) from Student where sID in (select sID from Apply where major = 'CS');

+--------------------+
| avg(GPA)           |
+--------------------+
| 3.6800000000000006 |
+--------------------+

使用连接:

select GPA from Student, Apply where Student.sID = Apply.sID and Apply.major = 'CS';

+------+
| GPA  |
+------+
|  3.9 |
|  3.9 |
|  3.5 |
|  3.7 |
|  3.7 |
|  3.9 |
|  3.4 |
+------+

此结果集的平均值:

select avg(GPA) from Student, Apply where Student.sID = Apply.sID and Apply.major = 'CS';

+-------------------+
| avg(GPA)          |
+-------------------+
| 3.714285714285714 |
+-------------------+

很明显,第二次尝试在我们的用例中产生了误导性的结果,因为它计算了重复项以计算平均值。同样明显的是,将 distinct 与基于连接的语句一起使用不会消除问题,因为它会错误地保留 3.9 分数的三分之一。正确的情况是考虑 3.9 分数的 两 (2) 次出现,因为我们实际上有 两 (2) 个学生的分数符合我们的查询标准。

似乎在某些情况下,除了任何性能问题之外,子查询是最安全的方法。


我认为你不能在这里使用子查询。这不是您在逻辑上可以使用其中任何一种的情况,但是由于它的技术实现,一个人给出了错误的答案。在这种情况下,您不能使用子查询,因为不属于 CS 的学生可以在 IN 分数列表中获得 3.9 分。一旦执行子查询,CS 的上下文就会丢失,这在逻辑上不是我们想要的。所以这不是一个可以使用任何一个的好例子。对于这个用例,子查询的使用在概念上/逻辑上是错误的,即使幸运的是它为不同的数据集提供了正确的结果。
U
Uğur Gümüşhan

MSDN Documentation for SQL Server says

许多包含子查询的 Transact-SQL 语句可以替代地表述为连接。其他问题只能通过子查询提出。在 Transact-SQL 中,包含子查询的语句与不包含子查询的语义等效版本之间通常没有性能差异。但是,在某些必须检查存在性的情况下,连接会产生更好的性能。否则,必须为外部查询的每个结果处理嵌套查询,以确保消除重复。在这种情况下,连接方法会产生更好的结果。

所以如果你需要类似的东西

select * from t1 where exists select * from t2 where t2.parent=t1.id

尝试使用 join 代替。在其他情况下,它没有区别。

我说:为子查询创建函数消除了混乱的问题,并允许您为子查询实现额外的逻辑。所以我建议尽可能为子查询创建函数。

代码中的混乱是一个大问题,该行业几十年来一直在努力避免它。


在某些 RDBMS(例如 Oracle)中,用函数替换子查询在性能方面是一个非常糟糕的主意,所以我建议恰恰相反——尽可能使用子查询/连接而不是函数。
@FrankSchmitt 请用参考文献支持你的论点。
在某些情况下,即使您检查是否存在,您也应该使用子查询而不是联接:如果您检查 NOT EXISTS。出于各种原因,NOT EXISTS 胜过 LEFT OUTER JOIN:性能、故障安全(在可空列的情况下)和可读性。 sqlperformance.com/2012/12/t-sql-queries/left-anti-semi-join
a
akash kubavat

根据我对两种情况的观察,如果一个表的记录少于 100,000 条,那么连接将很快工作。

但是如果一个表有超过 100,000 条记录,那么子查询是最好的结果。

我有一张表,上面有我在查询下创建的 500,000 条记录,它的结果时间就像

SELECT * 
FROM crv.workorder_details wd 
inner join  crv.workorder wr on wr.workorder_id = wd.workorder_id;

结果:13.3 秒

select * 
from crv.workorder_details 
where workorder_id in (select workorder_id from crv.workorder)

结果:1.65 秒


我同意,有时打破查询也有效,当你有数百万条记录时,你不想使用连接,因为它们永远存在。而是在代码中处理它并在代码中映射更好。
绑定您的联接工作不够快,您可能缺少索引。查询分析器在比较实际性能方面非常有用。
比较返回不同结果的两个查询的性能有何意义?
是的,这些是不同的查询,但返回相同的结果
@anikislamshojib 仔细看看:我们在第一个语句中有 * 和两个表,但在第二个语句中只有一个表,所以 I 预计列数会有所不同。
A
Alexander Yancharuk

在来自旧 Mambo CMS 的非常大的数据库上运行:

SELECT id, alias
FROM
  mos_categories
WHERE
  id IN (
    SELECT
      DISTINCT catid
    FROM mos_content
  );

0 秒

SELECT
  DISTINCT mos_content.catid,
  mos_categories.alias
FROM
  mos_content, mos_categories
WHERE
  mos_content.catid = mos_categories.id;

~3 秒

EXPLAIN 显示他们检查的行数完全相同,但一个需要 3 秒,一个几乎是即时的。故事的道德启示?如果性能很重要(什么时候不重要?),请尝试多种方式,看看哪一种最快。

和...

SELECT
  DISTINCT mos_categories.id,
  mos_categories.alias
FROM
  mos_content, mos_categories
WHERE
  mos_content.catid = mos_categories.id;

0 秒

同样,结果相同,检查的行数相同。我的猜测是 DISTINCT mos_content.catid 比 DISTINCT mos_categories.id 花费的时间要长得多。


我想更多地了解您在最后一行中要指出的内容“我的猜测是 DISTINCT mos_content.catid 比 DISTINCT mos_categories.id 需要更长的时间才能弄清楚。” .你是说一个 id 应该只命名为 id 而不是命名为 catid ?尝试优化我的数据库访问,您的学习可能会有所帮助。
在这种情况下使用 SQL IN 是一种不好的做法,它并不能证明任何事情。
f
fico7489

一般规则是在大多数情况下连接速度更快 (99%)。

数据表越多,子查询越慢。

数据表越少,子查询的速度与连接的速度相同。

子查询更简单、更易于理解和更易于阅读。

大多数 Web 和应用程序框架及其“ORM”和“活动记录”生成带有子查询的查询,因为使用子查询更容易拆分职责、维护代码等。

对于较小的网站或应用程序,子查询是可以的,但对于较大的网站和应用程序,您通常必须重写生成的查询以加入查询,特别是如果查询在查询中使用许多子查询。

有人说“一些 RDBMS 可以将子查询重写为连接或连接到子查询,当它认为一个比另一个快时。”,但这种说法适用于简单的情况,当然不适用于具有子查询的复杂查询,这实际上会导致性能方面的问题。


> 但是这个语句适用于简单的情况,我知道它要么是一个可以被 RDBMS 重写为“JOIN”的简单情况,要么是一个非常复杂的情况,子查询在这里是合适的。 :-) 关于 ORM 的好点。我认为这是最大的影响。
S
Slava Rozhnev

子查询通常用于将单行作为原子值返回,尽管它们可用于使用 IN 关键字将值与多行进行比较。在 SQL 语句中几乎任何有意义的点都允许使用它们,包括目标列表、WHERE 子句等。一个简单的子查询可以用作搜索条件。例如,在一对表之间:

SELECT title 
FROM books 
WHERE author_id = (
    SELECT id 
    FROM authors 
    WHERE last_name = 'Bar' AND first_name = 'Foo'
);

请注意,对子查询的结果使用普通值运算符需要只返回一个字段。如果您有兴趣检查一组其他值中是否存在单个值,请使用 IN:

SELECT title 
FROM books 
WHERE author_id IN (
    SELECT id FROM authors WHERE last_name ~ '^[A-E]'
);

这显然与 LEFT-JOIN 不同,您只想连接表 A 和 B 中的内容,即使连接条件在表 B 中找不到任何匹配的记录,等等。

如果您只是担心速度,您将不得不检查您的数据库并编写一个好的查询,看看性能是否有任何显着差异。


a
arun

MySQL版本:5.5.28-0ubuntu0.12.04.2-log

我也有这样的印象,在 MySQL 中,JOIN 总是比子查询好,但 EXPLAIN 是一种更好的判断方式。这是一个子查询比 JOIN 工作得更好的示例。

这是我的 3 个子查询的查询:

EXPLAIN SELECT vrl.list_id,vrl.ontology_id,vrl.position,l.name AS list_name, vrlih.position AS previous_position, vrl.moved_date 
FROM `vote-ranked-listory` vrl 
INNER JOIN lists l ON l.list_id = vrl.list_id 
INNER JOIN `vote-ranked-list-item-history` vrlih ON vrl.list_id = vrlih.list_id AND vrl.ontology_id=vrlih.ontology_id AND vrlih.type='PREVIOUS_POSITION' 
INNER JOIN list_burial_state lbs ON lbs.list_id = vrl.list_id AND lbs.burial_score < 0.5 
WHERE vrl.position <= 15 AND l.status='ACTIVE' AND l.is_public=1 AND vrl.ontology_id < 1000000000 
 AND (SELECT list_id FROM list_tag WHERE list_id=l.list_id AND tag_id=43) IS NULL 
 AND (SELECT list_id FROM list_tag WHERE list_id=l.list_id AND tag_id=55) IS NULL 
 AND (SELECT list_id FROM list_tag WHERE list_id=l.list_id AND tag_id=246403) IS NOT NULL 
ORDER BY vrl.moved_date DESC LIMIT 200;

解释显示:

+----+--------------------+----------+--------+-----------------------------------------------------+--------------+---------+-------------------------------------------------+------+--------------------------+
| id | select_type        | table    | type   | possible_keys                                       | key          | key_len | ref                                             | rows | Extra                    |
+----+--------------------+----------+--------+-----------------------------------------------------+--------------+---------+-------------------------------------------------+------+--------------------------+
|  1 | PRIMARY            | vrl      | index  | PRIMARY                                             | moved_date   | 8       | NULL                                            |  200 | Using where              |
|  1 | PRIMARY            | l        | eq_ref | PRIMARY,status,ispublic,idx_lookup,is_public_status | PRIMARY      | 4       | ranker.vrl.list_id                              |    1 | Using where              |
|  1 | PRIMARY            | vrlih    | eq_ref | PRIMARY                                             | PRIMARY      | 9       | ranker.vrl.list_id,ranker.vrl.ontology_id,const |    1 | Using where              |
|  1 | PRIMARY            | lbs      | eq_ref | PRIMARY,idx_list_burial_state,burial_score          | PRIMARY      | 4       | ranker.vrl.list_id                              |    1 | Using where              |
|  4 | DEPENDENT SUBQUERY | list_tag | ref    | list_tag_key,list_id,tag_id                         | list_tag_key | 9       | ranker.l.list_id,const                          |    1 | Using where; Using index |
|  3 | DEPENDENT SUBQUERY | list_tag | ref    | list_tag_key,list_id,tag_id                         | list_tag_key | 9       | ranker.l.list_id,const                          |    1 | Using where; Using index |
|  2 | DEPENDENT SUBQUERY | list_tag | ref    | list_tag_key,list_id,tag_id                         | list_tag_key | 9       | ranker.l.list_id,const                          |    1 | Using where; Using index |
+----+--------------------+----------+--------+-----------------------------------------------------+--------------+---------+-------------------------------------------------+------+--------------------------+

与 JOIN 相同的查询是:

EXPLAIN SELECT vrl.list_id,vrl.ontology_id,vrl.position,l.name AS list_name, vrlih.position AS previous_position, vrl.moved_date 
FROM `vote-ranked-listory` vrl 
INNER JOIN lists l ON l.list_id = vrl.list_id 
INNER JOIN `vote-ranked-list-item-history` vrlih ON vrl.list_id = vrlih.list_id AND vrl.ontology_id=vrlih.ontology_id AND vrlih.type='PREVIOUS_POSITION' 
INNER JOIN list_burial_state lbs ON lbs.list_id = vrl.list_id AND lbs.burial_score < 0.5 
LEFT JOIN list_tag lt1 ON lt1.list_id = vrl.list_id AND lt1.tag_id = 43 
LEFT JOIN list_tag lt2 ON lt2.list_id = vrl.list_id AND lt2.tag_id = 55 
INNER JOIN list_tag lt3 ON lt3.list_id = vrl.list_id AND lt3.tag_id = 246403 
WHERE vrl.position <= 15 AND l.status='ACTIVE' AND l.is_public=1 AND vrl.ontology_id < 1000000000 
AND lt1.list_id IS NULL AND lt2.tag_id IS NULL 
ORDER BY vrl.moved_date DESC LIMIT 200;

输出是:

+----+-------------+-------+--------+-----------------------------------------------------+--------------+---------+---------------------------------------------+------+----------------------------------------------+
| id | select_type | table | type   | possible_keys                                       | key          | key_len | ref                                         | rows | Extra                                        |
+----+-------------+-------+--------+-----------------------------------------------------+--------------+---------+---------------------------------------------+------+----------------------------------------------+
|  1 | SIMPLE      | lt3   | ref    | list_tag_key,list_id,tag_id                         | tag_id       | 5       | const                                       | 2386 | Using where; Using temporary; Using filesort |
|  1 | SIMPLE      | l     | eq_ref | PRIMARY,status,ispublic,idx_lookup,is_public_status | PRIMARY      | 4       | ranker.lt3.list_id                          |    1 | Using where                                  |
|  1 | SIMPLE      | vrlih | ref    | PRIMARY                                             | PRIMARY      | 4       | ranker.lt3.list_id                          |  103 | Using where                                  |
|  1 | SIMPLE      | vrl   | ref    | PRIMARY                                             | PRIMARY      | 8       | ranker.lt3.list_id,ranker.vrlih.ontology_id |   65 | Using where                                  |
|  1 | SIMPLE      | lt1   | ref    | list_tag_key,list_id,tag_id                         | list_tag_key | 9       | ranker.lt3.list_id,const                    |    1 | Using where; Using index; Not exists         |
|  1 | SIMPLE      | lbs   | eq_ref | PRIMARY,idx_list_burial_state,burial_score          | PRIMARY      | 4       | ranker.vrl.list_id                          |    1 | Using where                                  |
|  1 | SIMPLE      | lt2   | ref    | list_tag_key,list_id,tag_id                         | list_tag_key | 9       | ranker.lt3.list_id,const                    |    1 | Using where; Using index                     |
+----+-------------+-------+--------+-----------------------------------------------------+--------------+---------+---------------------------------------------+------+----------------------------------------------+

rows 列的比较说明了差异,使用 JOIN 的查询使用 Using temporary; Using filesort

当然,当我运行这两个查询时,第一个在 0.02 秒内完成,第二个即使在 1 分钟后也没有完成,所以 EXPLAIN 正确解释了这些查询。

如果我在 list_tag 表上没有 INNER JOIN,即如果我删除

AND (SELECT list_id FROM list_tag WHERE list_id=l.list_id AND tag_id=246403) IS NOT NULL  

从第一个查询和相应地:

INNER JOIN list_tag lt3 ON lt3.list_id = vrl.list_id AND lt3.tag_id = 246403

从第二个查询中,然后 EXPLAIN 为两个查询返回相同数量的行,并且这两个查询运行同样快。


我有类似的情况,但加入的次数比你的多,将尝试解释一次
在 Oracle 或 PostgreSQL 中,我会尝试: AND NOT EXISTS (SELECT 1 FROM list_tag WHERE list_id=l.list_id AND tag_id in (43, 55, 246403))
V
Volodymyr

子查询能够即时计算聚合函数。例如,找到这本书的最低价格并获取所有以这个价格出售的书籍。 1)使用子查询:

SELECT titles, price
FROM Books, Orders
WHERE price = 
(SELECT MIN(price)
 FROM Orders) AND (Books.ID=Orders.ID);

2) 使用 JOIN

SELECT MIN(price)
     FROM Orders;
-----------------
2.99

SELECT titles, price
FROM Books b
INNER JOIN  Orders o
ON b.ID = o.ID
WHERE o.price = 2.99;

另一种情况:具有不同表的多个 GROUP BYstackoverflow.com/questions/11415284/… 子查询似乎严格来说更一般。另见 MySQL 人:dev.mysql.com/doc/refman/5.7/en/optimizing-subqueries.html | dev.mysql.com/doc/refman/5.7/en/rewriting-subqueries.html
-1 这具有误导性,因为您在两个示例中都使用了子查询并加入。您已将子查询拉出到第二个查询中以确定最低订单价格没有任何影响,因为数据库将执行完全相同的操作。另外,您不会使用子查询重写联接;两个查询都使用连接。子查询允许聚合函数是正确的,但这个例子并没有证明这一事实。
我同意大卫的观点,您可以使用 group by 来获得最低价格。
D
Duncan Babbage

仅当第二个连接表的数据明显多于主表时,才能看到差异。我有过如下经历...

我们有一个包含十万个条目的用户表,他们的会员数据(友谊)大约有 30 万个条目。这是一个加入声明,目的是获取朋友和他们的数据,但延迟很大。但它在成员表中只有少量数据的情况下工作正常。一旦我们将其更改为使用子查询,它就可以正常工作。

但与此同时,连接查询正在处理条目少于主表的其他表。

所以我认为连接和子查询语句工作正常,这取决于数据和情况。


b
broadband

这些天来,许多数据库可以优化子查询和连接。因此,您只需使用 explain 检查您的查询,看看哪个更快。如果性能差异不大,我更喜欢使用子查询,因为它们简单易懂。


D
Dharman

我不是关系数据库专家,所以对此持保留态度。

关于子查询与连接的一般概念是评估较大查询所采用的路径。

为了执行更大的查询,必须首先执行每个单独的子查询,然后将结果集存储为与更大查询交互的临时表。

这个临时表没有索引,因此,任何比较都需要扫描整个结果集。

相反,当您使用连接时,所有索引都在使用中,因此比较需要遍历索引树(或哈希表),这在速度方面要便宜得多。

现在,我不知道最流行的关系引擎的较新版本是否会反向执行评估,并将必要的元素加载到临时表中,作为一种优化方法。


K
Karoly Szabo

我只是在考虑同样的问题,但我在 FROM 部分使用子查询。我需要从大表连接和查询,“从”表有 2800 万条记录,但结果只有 128 条这么小的结果大数据!我正在使用 MAX() 函数。

首先,我使用 LEFT JOIN,因为我认为这是正确的方法,mysql 可以优化等。第二次只是为了测试,我重写以针对 JOIN 进行子选择。

LEFT JOIN 运行时间:1.12s SUB-SELECT 运行时间:0.06s

子选择比连接快 18 倍!就在 chokito adv 中。子选择看起来很糟糕,但结果......


A
Ayesha

这取决于几个因素,包括您正在运行的特定查询、数据库中的数据量。子查询首先运行内部查询,然后再次从结果集中过滤出实际结果。而在 join 中运行并一次产生结果。

最好的策略是你应该测试连接解决方案和子查询解决方案以获得优化的解决方案。


s
sam ruben

如果您想使用连接加快查询速度:

对于“inner join/join”,不要使用 where 条件,而是在“ON”条件下使用。例如:

     select id,name from table1 a  
   join table2 b on a.name=b.name
   where id='123'

 Try,

    select id,name from table1 a  
   join table2 b on a.name=b.name and a.id='123'

对于“左/右连接”,不要在“开”条件下使用,因为如果你使用左/右连接,它将获取任何一张表的所有行。所以,在“开”条件下不要使用它。所以,尝试使用“Where”条件


这取决于 SQL 服务器和查询的复杂性。许多 SQL 实现会优化这样的简单查询以获得最佳性能。也许提供一个示例服务器名称和版本,这种行为恰好可以改善答案?