我在评估相关子查询时遇到了麻烦。一个例子是在SELECT中使用相关子查询,因此不需要GROUP BY:
考虑关系:
Movies : Title, Director Length
Schedule : Theatre, Title
我有以下查询
SELECT S.Theater, MAX(M.Length)
FROM Movies M JOIN Schedule S ON M.Title=S.Title
GROUP BY S.Theater
这是每个影院正在播放的最长的电影。这是不使用GROUP BY的相同查询:
SELECT DISTINCT S.theater,
(SELECT MAX(M.Length)
FROM Movies M
WHERE M.Title=S.Title)
FROM Schedule S
但我不明白它是如何运作的。
如果有人能给我一个关于如何评估相关子查询的例子,我会很感激。
谢谢:)
答案 0 :(得分:1)
从概念的角度来看,假设数据库在没有子查询的情况下遍历结果的每一行:
SELECT DISTINCT S.Theater, S.Title
FROM Schedule S
然后,对于每一个,为你运行子查询:
SELECT MAX(M.Length)
FROM Movies M
WHERE M.Title = (whatever S.Title was)
将其作为值放入。实际上,它(概念上)与使用函数不同:
SELECT DISTINCT S.Theater, SUBSTRING(S.Title, 1, 5)
FROM Schedule S
只是这个函数对另一个表执行查询。
但我在概念上说。数据库可能正在将相关查询优化为更像连接的东西。无论它在内部对性能有什么影响,但对于理解这个概念并不重要。
但是,它可能无法返回您期望的结果。考虑以下数据(对不起sqlfiddle似乎是错误的atm):
CREATE TABLE Movies (
Title varchar(255),
Length int(10) unsigned,
PRIMARY KEY (Title)
);
CREATE TABLE Schedule (
Title varchar(255),
Theater varchar(255),
PRIMARY KEY (Theater, Title)
);
INSERT INTO Movies
VALUES ('Star Wars', 121);
INSERT INTO Movies
VALUES ('Minions', 91);
INSERT INTO Movies
VALUES ('Up', 96);
INSERT INTO Schedule
VALUES ('Star Wars', 'Cinema 8');
INSERT INTO Schedule
VALUES ('Minions', 'Cinema 8');
INSERT INTO Schedule
VALUES ('Up', 'Cinema 8');
INSERT INTO Schedule
VALUES ('Star Wars', 'Cinema 6');
然后这个查询:
SELECT DISTINCT
S.Theater,
(
SELECT MAX(M.Length)
FROM Movies M
WHERE M.Title = S.Title
) AS MaxLength
FROM Schedule S;
你会得到这个结果:
+----------+-----------+
| Theater | MaxLength |
+----------+-----------+
| Cinema 6 | 121 |
| Cinema 8 | 91 |
| Cinema 8 | 121 |
| Cinema 8 | 96 |
+----------+-----------+
正如您所看到的,它不是GROUP BY的替代品(您仍然可以使用GROUP BY),它只是为每一行运行子查询。 DISTINCT只会从结果中删除重复项。它没有给出最长的"每个剧院,它只是给每个与剧院名称相关的独特电影长度。
PS:您可能会使用某种ID列来识别电影,而不是使用连接中的标题。这样一来,如果必须修改电影的名称,它只需要在一个地方改变,而不是整个时间表。另外,加入ID号而不是字符串会更快。
答案 1 :(得分:1)
概念上......
要理解这一点,首先忽略关于相关子查询的位。
考虑这样一个语句的操作顺序:
SELECT t.foo FROM mytable t
MySQL准备一个空结果集。结果集中的行将包含一列,因为SELECT列表中有一个表达式。从mytable中检索一行。 MySQL在结果集中添加一行,使用mytable行中foo列的值,将其分配给结果集中的foo列。获取下一行,重复相同的过程,直到没有更多的行要从表中获取。
非常简单的东西。但忍受我。
考虑这个陈述:
SELECT t.foo AS fi, 'bar' AS fo FROM mytable t
MySQL进程以同样的方式。准备一个空的结果集。结果集中的行此次将具有两个列。第一列的名称为fi(因为我们为其指定了名称为fi的别名)。结果集行中的第二列将命名为fo,因为(再次)我们分配了一个别名。
现在我们从mytable中蚀刻一行,并在结果集中插入一行。 foo列的值进入列名fi,文字字符串' bar'进入名为fo的列。继续获取行并在结果集中插入行,直到不再需要获取行。
不太难。
接下来,请考虑这个声明,看起来有点棘手:
SELECT t.foo AS fi, (SELECT 'bar') AS fo FROM mytable t
同样的事情再次发生。空结果集。行有两列,名称为fi和fo。
从mytable中获取一行,并在结果集中插入一行。 foo的值进入列fi(就像之前一样。)这就是它变得棘手的地方......对于结果集中的第二列,MySQL 在parens中执行查询。在这种情况下,它是一个非常简单的查询,我们可以很容易地测试它,看看它返回什么。从该查询中获取结果并将其分配给fo列,并将该行插入结果集。
还在我身边吗?
SELECT t.foo AS fi, (SELECT q.tip FROM bartab q LIMIT 1) AS fo FROM mytable
这开始看起来更复杂。但它并没有那么多不同。同样的事情再次发生。准备空结果集。行将有两列,一个名称为fi,另一列名为fo。从mytable中获取一行。从foo列获取值,并将其分配给结果行中的fi列。对于fo列,执行查询,并将查询结果分配给fo列。将结果行插入结果集。从mytable中获取另一行,重复该过程。
我们应该停下来注意一些事情。 MySQL在SELECT列表中对该查询很挑剔。真的很挑剔。 MySQL对此有限制。查询必须只返回一列。并且它不能返回多行。
在最后一个示例中,对于插入结果集的行,MySQL正在寻找 single 值以分配给fo列。当我们以这种方式思考时,有意义的是查询不能返回多个列...... MySQL会对第二列的值做什么?而且我们不希望返回多行是有意义的...... MySQL会对多行做什么?
MySQL将允许查询返回零行。当发生这种情况时,MySQL会为fo列分配一个NULL。
如果您对此有所了解,那么95%的方式可以理解相关的子查询。
让我们看另一个例子。我们的单行SQL有点不合适,所以我们只需添加一些换行符和空格,以便我们更轻松地使用它。额外的空格和换行不会改变我们陈述的含义。
SELECT t.foo AS fi
, ( SELECT q.tip
FROM bartab q
WHERE q.col = t.foo
ORDER BY q.tip DESC
LIMIT 1
) AS fo
FROM mytable t
好的,这看起来要复杂得多。但它真的吗?它又是相同的事物。准备一个空的结果集。行将有两列,fi和fo。从mytable中获取一行,并准备好一行插入结果集。复制foo列中的值,将其指定给fi列。对于fo列,执行查询,将查询返回的单个值带到fo列,然后将行推入结果集。从mytable中获取下一行,然后重复。
解释(finall!)关于"相关"。
的部分我们将运行该查询以获取fo列的结果。它包含来自外部表的列的引用。 t.foo
。在此示例中出现在WHERE子句中;它没有必要,它可以出现在声明的任何地方。
MySQL使用它做什么,当它运行该子查询时,将 foo列的值传入查询。如果我们刚从mytable获取的行在foo列中的值为42 ...该子查询等效于
SELECT q.tip
FROM bartab q
WHERE q.col = 42
ORDER BY q.tip DESC
LIMIT 1
但是由于我们没有传入42的字面值,我们传入的是来自外部查询中的行的值,我们的子查询返回的结果是"相关&## 34;到我们在外部查询中处理的行。
我们的子查询中可能要复杂得多,只要我们记住SELECT列表中有关子查询的规则...它必须返回一列,最多一行。它最多返回一个值。
相关子查询可以出现在SELECT列表以外的语句部分中,例如WHERE子句。同样的一般概念适用。对于由外部查询处理的每一行,该行中的列的值传递到到子查询。从子查询返回的结果是相关到外部查询中正在处理的行。
讨论省略了实际执行之前的所有步骤...将statament解析为标记,执行语法检查(关键字和标识符在正确的位置)。然后执行语义检查(mytable是否存在,用户是否具有select权限,mytable中是否存在列foo)。然后确定访问计划。并在执行中,获取所需的锁,等等。我们执行的每个语句都会发生这种情况。)
我们不会讨论我们可以使用相关子查询创建的各种可怕的性能问题。虽然前面的讨论应该提供一个线索。由于子查询是为每个行执行的,我们将其放入结果集中(如果它在我们的外部查询的SELECT列表中),或者正在为每个执行外部查询访问的行...如果外部查询返回40,000行,则意味着我们的相关子查询将被执行40,000次。所以我们最好确保子查询快速执行。即使它执行得很快,我们仍然会执行它40,000次。