嵌套选择还是加入?

时间:2013-01-06 16:39:53

标签: sql sql-server

这是我存储的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 Selects是否优于LEFT/Right JOINS还是我应该使用JOINS

表相册:

enter image description here

表NestedAlbums:

enter image description here

5 个答案:

答案 0 :(得分:8)

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

SQL Server可能需要使用子查询版本向计划添加Assert,该子查询版本验证子查询最多只返回一行(除非这由唯一索引保证)。这可能会限制可用的可能转换。有关详情,请参阅Scalar Subqueries

另外(尽管与您的问题中的示例无关,因为两个子查询都不同)写入显式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)) 

您可能会注意到SELECT列表中仍有子查询,但CASE ... EXISTS will be implemented efficiently as a semi join

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

答案 1 :(得分:3)

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

在您的情况下,结果将在相册中每行有一行。 SELECT子句中的标量子查询无法更改此设置。如果子查询碰巧返回多行,SQL Server将抛出异常。它永远不会添加到结果中。

当您将此逻辑联接移至FROM子句时,重新定义您的初始基数。 Album中每行不再是一行,Album LEFT OUTER JOIN AblumPictures ON...中每行不一行等。如果在Album中每行产生多行,SQL Server将抛出异常,因为它做了subselect。相反,它会在结果中添加行。

因此,在这方面,子查询可以更好地表达意图,更好的工作是主动保护您免受违反该意图的数据:“每个专辑给我一行,每张专辑包含此处的URL,来自那里的嵌套ID“等。

HOWEVER ,从功能上讲,存在一个巨大的缺点:标量子查询无法返回整个元组。您已经完成了所有这些工作来编写子查询,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的相同计划。

答案 2 :(得分:2)

这是基于Martin建议使用OUTER JOIN的猜测查询。我相信当他说“明确”加入时,他指的是选择那种技术而不是技术类型的加入。下面的查询假设您只会在搜索单个相册ID时从NestedAlbums和AlbumCover表中返回一行 - 如果不是这样,您将获得“重复行”并且必须向一个添加更多条件连接子句删除它们。此查询还假定AlbumCover位于“相册”表中。如果没有,则必须将文字a.AlbumCover更改为ac.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))

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

答案 3 :(得分:1)

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

其他人已经指出,执行查询可能会有也可能没有性能点击(对于@Martin Smith来说,像往常一样,指出隐藏但非常相关的细节),这些答案就是你的'在这里寻找......但是花一两分钟考虑未来的可怜开发人员,他们必须弄清楚被抛弃的代码实际上是在尝试做什么。毕竟,可能是你......

答案 4 :(得分:0)

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

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

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