6

这是我存储的 Proc 的一个片段

SELECT  NULL AS StoryID
      , AlbumID
      , CAST(NULL as varchar) AS StoryTitle
      , AlbumName
      , (SELECT URL FROM AlbumPictures AS AlbumPictures_3 WHERE (AlbumID = Albums.AlbumID) AND (AlbumCover = 'True')) AS AlbumCover
      , Votes
      , CAST(NULL as Int) AS PictureId
      , 'albums' AS tableName
      , (SELECT NestedAlbums.AlbumID FROM NestedAlbums WHERE (AlbumID = Albums.AlbumID)) AS Flag
INTO #Results2
FROM Albums WHERE AlbumID IN (SELECT StringVal FROM funcListToTableInt(@whereAlbumID))

我在上面的查询中使用了嵌套选择。我很想知道是否Nested SelectsLEFT/Right JOINS我应该使用更好JOINS

表相册:

在此处输入图像描述

表嵌套专辑:

在此处输入图像描述

4

5 回答 5

8

一般来说,写一个明确的OUTER JOIN会更好。

SQL Server 可能需要将 Assert 添加到带有子查询版本的计划中,以验证子查询最多只返回一行(除非这是由唯一索引保证的)。这会限制可用的可能转换。有关此的更多信息,请参阅标量子查询

此外(尽管与您的问题中的示例无关,因为两个子查询都不同)作为显式编写JOIN允许您使用连接表中的多个列进行一次查找,而使用单独的类似子查询则不会(SQL Server 没有检测逻辑常见的子表达式)。

编辑:

在评论中进行讨论,例如

SELECT NULL                      AS StoryID,
       A.AlbumID,
       CAST(NULL AS VARCHAR(30)) AS StoryTitle,
       A.AlbumName,
       AP.URL                    AS AlbumCover,
       A.Votes,
       CAST(NULL AS INT)         AS PictureId,
       'albums'                  AS tableName,
       CASE
         WHEN EXISTS (SELECT *
                      FROM   NestedAlbums NA
                      WHERE  NA.AlbumID = A.AlbumID
                             AND ( AccountId = @AccountId )) THEN 1
         ELSE 0
       END                       AS Flag
INTO   #Results2
FROM   Albums A
       LEFT OUTER JOIN AlbumPictures AP
         ON ( AP.AlbumID = A.AlbumID )
            AND ( AP.AlbumCover = 'True' )
WHERE  A.AlbumID IN (SELECT StringVal
                     FROM   funcListToTableInt(@whereAlbumID)) 

您可能会注意到列表中仍然有一个子查询,SELECTCASE ... EXISTS 将作为半联接有效地实现

目前,您的查询假设每张专辑最多返回一个匹配行AlbumPictures,如果此假设不成立,则会出错。这改变了语义,因为不会返回错误,并且您将获得带有各种URLs 的多行。如果您不希望这种情况发生,您需要定义URL使用哪个并添加一个GROUP BY

于 2013-01-06T16:46:23.083 回答
4

第一个区别是,FROM子句控制结果的初始基数

在您的情况下,结果将在相册中每行一行。子句中的标量子查询SELECT不能改变这一点。如果子查询恰好返回多行,SQL Server 将抛出异常。它永远不会增加结果。

当您将此逻辑连接移至FROM子句时,您重新定义了初始基数。它不再是专辑中的每行一行,而是专辑中的每行一行Album LEFT OUTER JOIN AblumPictures ON...。如果这样在专辑中每行产生多行,SQL Server 将不会像对子选择那样抛出异常。相反,它将向结果中添加行。

因此,在这方面,子查询在表达意图方面做得更好,并且更好地主动保护您免受违反该意图的数据的影响:“给我每张专辑一行,对于每张专辑,包括此处的 URL,嵌套 ID从那里开始”等。

然而,从功能上讲,有一个巨大的缺点:标量子查询不能返回整个元组。您已经完成了编写子查询的所有工作,SQL Server 已经完成了执行它的所有工作,现在您仅限于这个单一的标量返回值!有时这很好,但有时你需要更多。当您需要更多时,您需要该FROM子句。

FROM与标量子查询等效的最接近的子句不是OUTER JOIN,而是奇妙的OUTER APPLYOUTER 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.

于 2013-01-06T18:20:39.937 回答
2

这是基于 Martin 建议使用 OUTER JOIN 的猜测查询。我相信当他说“显式”连接时,他指的是仅选择该技术而不是技术类型的连接。下面的查询假定您在搜索单个专辑 ID 时只会从 NestedAlbums 和 AlbumCover 表中取回一行 - 如果不是这样,您将获得“重复行”并且必须向其中添加更多条件的连接子句以删除它们。此查询还假定 AlbumCover 在 Albums 表中。如果没有,您将不得不将文本更改a.AlbumCoverac.AlbumCover

SELECT  NULL AS StoryID
      , a.AlbumID
      , CAST(NULL as varchar) AS StoryTitle
      , a.AlbumName
      , ac.URL AS AlbumCover
      , a.Votes
      , CAST(NULL as Int) AS PictureId
      , 'albums' AS tableName
      , na.AlbumID AS Flag
INTO #Results2
FROM Albums a
LEFT OUTER JOIN NestedAlbums na ON a.AlbumID = na.AlbumID
LEFT OUTER JOIN AlbumCover ac ON a.AlbumID = ac.AlbumID AND a.AlbumCover = 'True'
WHERE a.AlbumID IN (SELECT StringVal FROM funcListToTableInt(@whereAlbumID))

此查询从 Albums 获取结果,并包括 NestedAlbums 中具有匹配 AlbumID 的所有行,以及 AlbumCover 表中具有匹配 AlbumID 的任何行(但前提是 Albums 中的 AlbumCover 字段对于该行为“True”) . 因为我们选择了 LEFT OUTER JOIN 作为我们的运算符,如果 NestedAlbums 或 AlbumCover 没有匹配的行,SQL Server 将为这些字段返回 NULL,但无论如何查询都会返回这些行。如果连接是 INNER JOIN,那么如果连接表中没有匹配的行,则主表中的行也将被过滤掉。

于 2013-01-06T17:12:22.500 回答
1

对我来说,使用连接而不是嵌套选择编写的查询“更好”,因为它们更易于人们阅读和理解(考虑维护、支持、未来修改等)。所有的表连接逻辑都在一个地方(from子句),并且所有返回的列都在另一个位置(select子句),它只是让您更容易弄清楚发生了什么。

其他人指出,执行查询可能会或可能不会有性能命中(像往常一样,向@Martin Smith +1 以指出晦涩但非常相关的细节),而这些答案就是您正在寻找的在这里......但是花一两分钟时间考虑一下未来可怜的开发人员,他们必须弄清楚转储在他们身上的代码实际上是在做什么。毕竟,可能是你...

于 2013-01-06T17:11:40.927 回答
0

如果您考虑性能,在您的情况下,这两种方法几乎相同。(考虑到您的数据库中有良好的索引表)

如果需要太多的 JOIN,JOIN 更容易使用和高效。JOIN 可能会在加入时创建重复数据,但嵌套查询不会。

但最终性能将取决于您拥有的数据的数量和组织。您可以使用 SQL 性能监控工具根据您的数据进行验证。

于 2013-01-06T16:51:36.073 回答