这似乎是我最近编写的查询中一个反复出现的问题,我想发现哪种查询样式:
更多信息:
我写的查询总是从一个基表拉出来,也会加入到其他几个表中;但是,连接的表通常具有垂直方向,其中外键被多次引用,具有唯一的"描述符"和#34;回应。" (参见:表#MovieDescriptions的示例。)
请使用以下SQL查询作为测试方案:
-- Drop temp tables if exist
IF OBJECT_ID('TempDB..#Movies','U') IS NOT NULL
DROP TABLE #Movies
IF OBJECT_ID('TempDB..#MovieDescriptions','U') IS NOT NULL
DROP TABLE #MovieDescriptions
-- Creating temp tables
CREATE TABLE #Movies
(
MovieID int IDENTITY(1,1),
MovieName varchar (100),
ReleaseYear datetime,
Director varchar (100)
)
CREATE TABLE #MovieDescriptions
(
MovieDescID int IDENTITY(1,1),
FK_MovieID varchar(100),
DescriptionType varchar(100),
DescriptionResponse varchar(100)
)
-- Inserting test data
INSERT INTO #Movies (MovieName, ReleaseYear, Director) VALUES ('Gone With the Wind', CONVERT(datetime,'12/15/1939'), 'Victor Fleming')
INSERT INTO #Movies (MovieName, ReleaseYear, Director) VALUES ('2001: A Space Odyssey', CONVERT(datetime,'01/01/1968'), 'Stanley Kubrick')
INSERT INTO #MovieDescriptions (FK_MovieID, DescriptionType, DescriptionResponse) VALUES ('1', 'Written By', 'Sideny Howard')
INSERT INTO #MovieDescriptions (FK_MovieID, DescriptionType, DescriptionResponse) VALUES ('1', 'Genre', 'Drama')
INSERT INTO #MovieDescriptions (FK_MovieID, DescriptionType, DescriptionResponse) VALUES ('1', 'Rating', 'G')
INSERT INTO #MovieDescriptions (FK_MovieID, DescriptionType, DescriptionResponse) VALUES ('2', 'Written By', 'Standley Kubrick')
INSERT INTO #MovieDescriptions (FK_MovieID, DescriptionType, DescriptionResponse) VALUES ('2', 'Genre', 'Sci-Fi')
INSERT INTO #MovieDescriptions (FK_MovieID, DescriptionType, DescriptionResponse) VALUES ('2', 'Rating', 'G')
-- Using subqueries
SELECT
MovieName,
ReleaseYear,
(SELECT DescriptionResponse
FROM #MovieDescriptions
WHERE FK_MovieID = #Movies.MovieID AND DescriptionType = 'Genre'
) AS Genre,
(SELECT DescriptionResponse
FROM #MovieDescriptions
WHERE FK_MovieID = #Movies.MovieID AND DescriptionType = 'Rating'
) AS Rating
FROM #Movies
-- Using aggregate functions
SELECT
MovieName,
ReleaseYear,
MAX(CASE WHEN md.DescriptionType = 'Genre' THEN DescriptionResponse END) AS Genre,
MAX(CASE WHEN md.DescriptionType = 'Rating' THEN DescriptionResponse END) AS Rating
FROM #Movies m
INNER JOIN #MovieDescriptions md
ON m.MovieID = md.FK_MovieID
GROUP BY MovieName, ReleaseYear
此外,如果有更好的方法可以选择此数据,那么这也会有所帮助。
答案 0 :(得分:1)
假设您的表被正确编入索引的更正常的设置,以及外键关系列具有匹配的数据类型(提示提示:它们当前不匹配,int
与varchar
) ,那么你应该总是发现你的第二个查询(连接+聚合)优于第一个(select
子句中的子查询)。少量数据可能不会引起注意。但是你的基表有越多的数据(#Movies
),差异就越明显。
原因很简单。在您的第一个查询中:
SELECT
MovieName,
ReleaseYear,
(SELECT DescriptionResponse
FROM #MovieDescriptions
WHERE FK_MovieID = #Movies.MovieID AND DescriptionType = 'Genre'
) AS Genre,
(SELECT DescriptionResponse
FROM #MovieDescriptions
WHERE FK_MovieID = #Movies.MovieID AND DescriptionType = 'Rating'
) AS Rating
FROM #Movies
如果#Movies
包含1000行,那么SQL Server别无选择,只能在#Movies
上执行一次全表扫描,并且对于1000行中的每一行,它都需要执行2个额外的查询在#MovieDescriptions
。实际上,您正在执行总共2001次查询。因为您的子查询位于SELECT
子句中,所以SQL Server别无选择,只能以这种方式执行查询。
另一方面,您的第二个查询:
SELECT
MovieName,
ReleaseYear,
MAX(CASE WHEN md.DescriptionType = 'Genre' THEN DescriptionResponse END) AS Genre,
MAX(CASE WHEN md.DescriptionType = 'Rating' THEN DescriptionResponse END) AS Rating
FROM #Movies m
INNER JOIN #MovieDescriptions md
ON m.MovieID = md.FK_MovieID
GROUP BY MovieName, ReleaseYear
由于您在此处使用联接,这为SQL Server提供了灵活性,以便找出加入#Movies
和#MovieDescriptions
数据的最有效方式。根据您的索引,过滤器,行数等,它可能决定进行散列连接,也许它将使用嵌套循环等。重点是SQL Server有更多选项,现在可以找出最佳方法减少2个表(和索引)中数据块读取的数量。
编辑:我还要补充一点,上面假设您从查询中返回 每个 行。如果查询返回数千行,但您只获取前10行,那么在某些情况下,第一个查询实际上可能优于第二个查询。这是因为子查询只会在行选择或获取时在行上执行。如果您从未获取某些行,则可能永远不会产生在这些未获取的行上执行子查询的成本。需要考虑的事情。
答案 1 :(得分:1)
我更喜欢这个版本的查询..
SELECT
MovieName,
ReleaseYear,
MAX(CASE WHEN md.DescriptionType = 'Genre' THEN DescriptionResponse END) AS Genre,
MAX(CASE WHEN md.DescriptionType = 'Rating' THEN DescriptionResponse END) AS Rating
FROM #Movies m
INNER JOIN #MovieDescriptions md
ON m.MovieID = md.FK_MovieID
GROUP BY MovieName, ReleaseYear
但是这涉及电影表格的排序,这种分类费用是总费用的63%,而且你只能获得所有列。
我使用Apply重写了这个版本,它没有排序,你不受限制地获取所有列。
select
* from
#movies m
cross apply
(
select
max(case when descriptiontype='genre' then descriptionresponse end) as genre,
max(case when descriptiontype='rating' then descriptionresponse end) as rating
from
#MovieDescriptions md
where md.fk_movieid=m.movieid)b
现在两个查询都没有索引..
第一个查询工作和扩展所需的索引是什么。
MovieName上的附加索引,ReleaseYear以避免排序并告诉SQLServer这些是唯一的...因为你已经拥有(假设)Movieid上的主键,还有一个索引可以避免排序,有些事我不知道&#39 ; t更喜欢,如果您需要更多来自第一个查询的列,则必须再次将其添加到索引..
对于第二个要更好地扩展的查询,
你需要一个关于FK_movieid的索引,我假设你已经拥有它...
PS:
我可能已经错过了许多东西,但我会继续我的询问..
答案 2 :(得分:0)
有很多方法可以从多个表中获取结果,例如嵌套/子查询,连接,聚合函数等。
但是当你需要来自n个表的结果时,联接比子查询更有效。
几张桌子可能无法观察到。但是,当您同时使用5个或6个表时,您将观察联接的执行方式。