如何在不使用 DISTINCT 的情况下识别无法重写为 JOIN 的相关子查询?

Rav*_*ell 5 join subquery

我对 SQL 查询调优相当陌生。我一直在尝试了解如何编写等效的查询。在浏览J. Widom 教授的斯坦福在线视频讲座时,她提到了一些子查询,如果不JOIN使用DISTINCT. 例如,看这个:

  1. 4:45 / 20:13 - GPA 示例

    SELECT GPA
    FROM Student
    WHERE sID in (select sID from Apply where major = 'CS');
    
    Run Code Online (Sandbox Code Playgroud)
  2. 6:39 / 20:13 - 学生申请 CS 而不是 EE

    SELECT sID, sName
    FROM Student
    WHERE sID IN (select sID from Apply where major = 'CS')
      AND sID NOT IN (select sID from Apply where major = 'EE');
    
    Run Code Online (Sandbox Code Playgroud)

我的问题是如何知道使用子查询编写的 SQL 语句是否将具有使用连接编写的等效语句。我很舒服,如果在答案中,有人喜欢使用关系代数表示法。

我在网上搜索了很多,找不到合适的答案。

样本数据

Schema 和表创建(对于 PostgreSQL)如下,

CREATE TEMP TABLE college AS
SELECT cname::text, state::text, enrollment::int
FROM ( VALUES
  ('Stanford', 'CA', 15000),
  ('Berkeley', 'CA', 36000),
  ('MIT', 'MA', 10000),
  ('Cornell', 'NY', 21000)
) AS College(cname, state, enrollment);

CREATE TEMP TABLE student AS
SELECT sid::int, sname::text, gpa::real, sizehs::int
FROM ( VALUES
  (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)
) AS Student(sid, sname, gpa, sizehs);

CREATE TEMP TABLE apply AS
SELECT sid::int, cname::text, major::text, decision::text
FROM ( VALUES
  (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')
) AS apply(sid, cname, major, decision);
Run Code Online (Sandbox Code Playgroud)

Eva*_*oll 1

我的问题是如何知道使用子查询编写的 SQL 语句是否具有使用联接编写的等效 SQL 语句。如果在答案中有人喜欢使用关系代数符号,我会感到很舒服。

好问题。

给出的第一个例子

那么让我们看看她给出的第一个问题..

SELECT GPA
FROM Student
WHERE sID in (SELECT sID from Apply where major = 'CS');
Run Code Online (Sandbox Code Playgroud)

然后,让我们看一下带有连接的类似查询,

SELECT *
FROM student
JOIN apply ON (student.sid = apply.sid AND major = 'CS');
Run Code Online (Sandbox Code Playgroud)

我故意更改了第一列和第二列中的列。这里的“GPA”,具体来说是student.GPA

如果如下,则相关子查询不能重写简单的 join

  1. SELECT语句具有聚合WHERE gpa in (SELECT max(gpa)..)(如果连接需要聚合或分组,则必须使用派生表或虚拟表:JOIN ( SELECT.. ) AS t.
  2. 如果JOIN上面的条件不是唯一的,我们假设有一个student匹配行同时具有公共sidmajor=CS。该假设失败的结果集可能会增长收缩。

你可以很容易地测试一下..

SELECT sid, count(*)
FROM apply
WHERE major='CS'
GROUP BY sid
HAVING count(*) > 1;
Run Code Online (Sandbox Code Playgroud)

这会返回两行,两个sid行都会在连接中被欺骗。

 sid | count 
-----+-------
 123 |     2
 987 |     2
Run Code Online (Sandbox Code Playgroud)

作为清晰度练习,您可以DISTINCT自己向下推,并且可以再次使用 重写JOIN

SELECT GPA
FROM student
JOIN (
  SELECT DISTINCT sid, major
  FROM apply
) AS apply
  ON (student.sid = apply.sid AND major = 'CS');
Run Code Online (Sandbox Code Playgroud)

甚至..

SELECT GPA
FROM student
JOIN (
  SELECT DISTINCT sid, major
  FROM apply
  WHERE major = 'CS'
) AS apply
  USING (sid);
Run Code Online (Sandbox Code Playgroud)

给出的第二个例子

第二个例子,

SELECT sID, sName
FROM Student
WHERE sID IN (select sID from Apply where major = 'CS')
  AND sID NOT IN (select sID from Apply where major = 'EE');
Run Code Online (Sandbox Code Playgroud)

正是第一个例子的受害者。但是,那NOT IN

  • 对于 -ness 来说并不重要,UNIQUE因为我们排除了结果。
  • 此外,将 重写NOT IN反连接( LEFT OUTER JOIN ... ON NULL) 可能会更快。
  • 它将更有效地处理 apply.sid IS NULL 的任何情况,包括没有的情况major = 'EE'

这是一个例子,

SELECT student.sID, sName
FROM Student
LEFT OUTER JOIN apply ON (student.sid = apply.sid AND major = 'EE')
WHERE student.sID IN (select sID from Apply where major = 'CS')
  AND apply.sid IS NULL;
Run Code Online (Sandbox Code Playgroud)

总之

  1. 连接(包括INNER JOINS)可以增加或减少结果集中的行数。
    1. 如果JOIN条件永远不为真,则 an 上的结果INNER JOIN将被修剪。
    2. 如果同一行的结果多次为 true,则返回的行数会增加。
  2. 但是,连接连接两个表。在t1 JOIN t2
    1. 如果t1、 和t2没有重复行,那么SELECT t1.*, t2.*应该没有重复行。有些东西应该有所不同。
    2. 如果 或t1具有t2重复行,则JOIN包含这些行的 a 将导致重复的输出行。
    3. 如果你SELECT是 的子集t1,或者 的子集t2(就像你上面选择 GPA 时所做的那样),那么可能会有一些看起来像是骗局的东西,尽管事实并非如此。如果您选择了,student.GPA, apply.*您就会看到欺骗的原因。

  • 我建议您删除 *“此外,将 NOT IN 重写为反连接(LEFT OUTER JOIN ... ON NULL)可能会更快。”* 部分。除非你想添加一篇关于效率、计划和查询重写的文章,以解释何时这是正确的、何时不是;) (2认同)