第一个区别是,该FROM
子句控制结果的初始基数。
在您的情况下,结果将在相册中每行一行。子句中的标量子查询SELECT
不能改变这一点。如果子查询恰好返回多行,SQL Server 将抛出异常。它永远不会增加结果。
当您将此逻辑连接移至FROM
子句时,您重新定义了初始基数。它不再是专辑中的每行一行,而是专辑中的每行一行Album LEFT OUTER JOIN AblumPictures ON...
。如果这样在专辑中每行产生多行,SQL Server 将不会像对子选择那样抛出异常。相反,它将向结果中添加行。
因此,在这方面,子查询在表达意图方面做得更好,并且更好地主动保护您免受违反该意图的数据的影响:“给我每张专辑一行,对于每张专辑,包括此处的 URL,嵌套 ID从那里开始”等。
然而,从功能上讲,有一个巨大的缺点:标量子查询不能返回整个元组。您已经完成了编写子查询的所有工作,SQL Server 已经完成了执行它的所有工作,现在您仅限于这个单一的标量返回值!有时这很好,但有时你需要更多。当您需要更多时,您需要该FROM
子句。
FROM
与标量子查询等效的最接近的子句不是OUTER JOIN
,而是奇妙的OUTER APPLY
。OUTER APPLY
不是标量表达式:它返回整个元组和任意数量的行。
第一近似:
SELECT Albums.*, AlbumPictures.URL, NestedAlbums.AlbumID
FROM Albums
OUTER APPLY (
SELECT TOP (1) * FROM AlbumPictures
WHERE (AlbumID = Albums.AlbumID) AND (AlbumCover = 'True')
) AlbumPictures
OUTER APPLY (
SELECT TOP (1) * FROM NestedAlbums
WHERE (AlbumID = Albums.AlbumID)
) NestedAlbums
WHERE Albums.AlbumID IN (SELECT StringVal FROM funcListToTableInt(@whereAlbumID))
因此,凭借TOP (1)
,专辑仍然控制结果的初始基数。但是,我们现在可以访问相关表中的所有列,这太棒了。
然后,如果我们确信TOP (1)
没有必要——凭借键和索引,子查询只能返回一行——那么我们可以使用更简单的形式重写:
SELECT Albums.*, AlbumPictures.URL, NestedAlbums.AlbumID
FROM Albums
OUTER APPLY (
SELECT * FROM AlbumPictures
WHERE (AlbumID = Albums.AlbumID) AND (AlbumCover = 'True')
) AlbumPictures
OUTER APPLY (
SELECT * FROM NestedAlbums
WHERE (AlbumID = Albums.AlbumID)
) NestedAlbums
WHERE Albums.AlbumID IN (SELECT StringVal FROM funcListToTableInt(@whereAlbumID))
现在在逻辑上等同于OUTER JOIN
:
SELECT Albums.*, AlbumPictures.URL, NestedAlbums.AlbumID
FROM Albums
LEFT OUTER JOIN AlbumPictures
ON AlbumPictures.AlbumID = Albums.AlbumID
AND AlbumPictures.AlbumCover = 'True'
LEFT OUTER JOIN NestedAlbums
ON NestedAlbums.AlbumID = Albums.AlbumID
WHERE Albums.AlbumID IN (SELECT StringVal FROM funcListToTableInt(@whereAlbumID))
你有它。哪个更好?好吧,无论你做什么,请保持简单。
性能方面,一般来说,表格之间并没有太大的区别。您可以并排比较特定表和索引的执行计划。了解 SQL Server 如何重写逻辑等效查询是一次很好的学习体验。我希望看到OUTER APPLY
(w/o TOP (1)
) 和LEFT OUTER JOIN
.