这是我存储的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
?
表相册:
表NestedAlbums:
答案 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性能监视工具根据数据进行验证。