使用派生表的查询相对于不使用它们的查询有什么优势?

时间:2010-05-04 18:42:52

标签: sql sql-server tsql

我知道如何使用派生表,但我仍然无法看到使用它们的任何真正优势。

例如,在下面的文章http://techahead.wordpress.com/2007/10/01/sql-derived-tables/中,作者尝试使用派生表在没有示例的情况下使用派生表显示优势,我们希望生成一个显示总数的报表。命令每个客户在1996年订购,我们希望这个结果集包括所有客户,包括那些没有下订单的客户和那些从未下过任何订单的客户(他使用的是Northwind数据库)。

但是当我比较两个查询时,我没有看到使用派生表的查询的任何优点(如果没有别的,使用派生表似乎不会简化我们的代码,至少在这个例子中不是这样) :

常规查询:

SELECT C.CustomerID, C.CompanyName, COUNT(O.OrderID) AS TotalOrders
FROM Customers C LEFT OUTER JOIN Orders O ON
       C.CustomerID = O.CustomerID AND YEAR(O.OrderDate) = 1996
GROUP BY C.CustomerID, C.CompanyName

使用派生表进行查询:

SELECT C.CustomerID, C.CompanyName, COUNT(dOrders.OrderID) AS TotalOrders
FROM Customers C LEFT OUTER JOIN
        (SELECT * FROM Orders WHERE YEAR(Orders.OrderDate) = 1996) AS dOrders
     ON
        C.CustomerID = dOrders.CustomerID
GROUP BY C.CustomerID, C.CompanyName

也许这只是一个很好的例子,你能告诉我一个例子,派生表的好处更明显吗?

感谢名单

回复GBN:

  

在这种情况下,如果客户和产品之间没有关系,则无法捕获产品和订单聚合。

你能详细说明你到底是什么意思吗?以下查询不会生成与查询相同的结果集:

SELECT 
     C.CustomerID, C.CompanyName,
     COUNT(O.OrderID) AS TotalOrders,
     COUNT(DISTINCT P.ProductID) AS DifferentProducts 
FROM Customers C LEFT OUTER JOIN Orders O ON
       C.CustomerID = O.CustomerID AND YEAR(O.OrderDate) = 1996
   LEFT OUTER JOIN Products P ON 
       O.somethingID = P.somethingID  
GROUP BY C.CustomerID, C.CompanyName

回复CADE ROUX:

  

此外,如果表达式用于从具有大量共享中间计算的派生列派生列,则一组嵌套派生表或堆叠CTE是唯一的方法:

SELECT x, y, z1, z2
FROM (
    SELECT *
           ,x + y AS z1
           ,x - y AS z2
    FROM (
        SELECT x * 2 AS y
        FROM A
    ) AS A
) AS A

以下查询不会产生与上述查询相同的结果:

SELECT x, x * 2 AS y, x + x*2 AS z1, x - x*2 AS z2
FROM A

5 个答案:

答案 0 :(得分:5)

我通常使用派生表(或CTE,它有时是SQL 2005/2008中派生查询的有效替代方案)来简化读取和构建查询,或者在SQL不允许的情况下我要做一个特定的操作。

例如,如果没有派生表或CTE,您无法做到的事情之一是在WHERE子句中放置聚合函数。这不起作用:

SELECT  name, city, joindate
FROM    members 
        INNER JOIN cities ON cities.cityid = derived.cityid
WHERE   ROW_NUMBER() OVER (PARTITION BY cityid ORDER BY joindate) = 1

但这会奏效:

SELECT  name, city, joindate
FROM    
( 
    SELECT  name, 
            cityid,
            joindate,
            ROW_NUMBER() OVER (PARTITION BY cityid ORDER BY joindate) AS rownum 
    FROM    members 
) derived INNER JOIN cities ON cities.cityid = derived.cityid
WHERE   rn = 1

高级警告,特别是对于大规模分析

如果您正在处理相对较小的数据集(非千兆字节),您可能会在这里停止阅读。如果您正在使用千兆字节或数TB的数据并使用派生表,请继续阅读...

对于非常大规模的数据操作,有时最好是创建临时表而不是使用派生查询。如果SQL的统计信息表明派生的查询将返回比查询实际返回的行多得多的行,则可能会发生这种情况,这种情况比您想象的更频繁。主查询self-joins with a non-recursive CTE所在的查询也存在问题。

派生表也可能会生成意外的查询计划。例如,即使您在派生表中放置了严格的WHERE子句以使该查询具有选择性,SQL Server也可能会重新排序您的查询计划,以便在查询计划中评估您的WHERE子句。有关此问题的讨论和解决方法,请参阅此Microsoft Connect feedback

因此,对于性能密集型查询(尤其是100GB +表上的数据仓库查询),我总是喜欢对临时表解决方案进行原型设计,以确定您是否获得了比从派生表或CTE获得的更好的性能。这似乎是违反直觉的,因为您执行的I / O比理想的单一查询解决方案更多,但使用临时表可以完全控制所使用的查询计划以及每个子查询的评估顺序。有时,这可以将性能提高10倍或更多。

在我必须使用查询提示强制SQL执行我想要的操作的情况下,我也倾向于更喜欢临时表 - 如果SQL优化器已经“行为不端”,临时表通常是一种更明确的方式来强制它们按照你想要的方式行事。

我并不是说这是一个常见的情况 - 大多数情况下临时表解决方案至少会更糟糕,有时查询提示是唯一的追索权。但是,不要假设CTE或派生查询解决方案也是您最快的选择。测试,测试,测试!

答案 1 :(得分:5)

在您的示例中,派生表并非绝对必要。在许多情况下,您可能需要加入聚合或类似,并且派生表实际上是处理它的唯一方法:

SELECT *
FROM A
LEFT JOIN (
    SELECT x, SUM(y)
    FROM B
    GROUP BY x
) AS B
    ON B.x = A.x

此外,如果表达式用于从具有大量共享中间计算的派生列派生列,则一组嵌套派生表或堆叠CTE是唯一的方法:

SELECT x, y, z1, z2
FROM (
    SELECT *
           ,x + y AS z1
           ,x - y AS z2
    FROM (
        SELECT x * 2 AS y
        FROM A
    ) AS A
) AS A

就可维护性而言,使用堆叠的CTE或派生表(它们基本上是等效的)并且可以提供更易读和可维护的代码,以及促进剪切和粘贴重用和重构。优化器通常可以很容易地变平。

我通常使用堆叠CTE而不是嵌套以获得更好的可读性(相同的两个示例):

WITH B AS (
    SELECT x, SUM(y)
    FROM B
    GROUP BY x
)
SELECT *
FROM A
LEFT JOIN B
    ON B.x = A.x

WITH A1 AS (
    SELECT x * 2 AS y
    FROM A
)
,A2 AS (
    SELECT *
           ,x + y AS z1
           ,x - y AS z2
    FROM A1
)
SELECT x, y, z1, z2
FROM A2

关于您的问题:

SELECT x, x * 2 AS y, x + x*2 AS z1, x - x*2 AS z2 
FROM A 

这有x * 2代码重复3次。如果需要更改此业务规则,则必须在3个位置进行更改 - 注入缺陷的方法。只要你有中间计算需要保持一致并且只在一个地方定义,这就会变得复杂。

如果可以内联SQL Server的标量用户定义函数(或者如果它们执行得可行),这不会是一个问题,您可以简单地构建UDF来堆叠结果,优化器将消除冗余调用。不幸的是,SQL Server的标量UDF实现无法很好地处理大型行集。

答案 2 :(得分:3)

派生表通常会替换相关子查询,并且通常要快得多。

它们还可以用于极大地限制搜索大表的记录数量,从而也可以提高查询速度。

与所有潜在的性能改进技术一样,您需要进行测试以确定它们是否确实提高了性能。派生表几乎总是强大地胜过相关子查询,但有可能没有。

此外,有时您需要加入包含聚合计算的数据,如果没有派生表或CTE几乎是不可能的(在许多情况下,这是另一种编写派生tbale的方式)。

派生表也是我用于报告复杂数据以进行报告的最有用方法之一。您也可以使用表变量或临时表来分段执行此操作,但如果您不希望在过程步骤中看到代码,那么人们通常会在使用临时表计算出他们想要的内容后将它们更改为派生表。

从联合聚合数据是另一个需要派生表的地方。

答案 3 :(得分:1)

使用您的术语和示例派生表只是更复杂,没有任何优势。但是,有些东西需要派生表。这些可能是最复杂的CTE案例(如上所示)。但是,简单的连接可以证明派生表的必要性,你必须做的就是制作一个需要使用聚合的查询,这里我们使用配额查询的变体来证明这一点。

选择所有客户最昂贵的交易

SELECT transactions.*
FROM transactions
JOIN (
  select user_id, max(spent) AS spent
  from transactions
  group by user_id
) as derived_table
USING (
  derived_table.user_id = transaction.user_id
  AND derived_table.spent = transactions.spent
)

答案 4 :(得分:1)

在这种情况下,派生表允许在WHERE子句中使用YEAR(O.OrderDate) = 1996

在外部where子句中,它没用,因为它会将JOIN更改为INNER。

就个人而言,我更喜欢派生表(或CTE)构造,因为它将过滤器放入正确的位置

另一个例子:

SELECT
     C.CustomerID, C.CompanyName,
     COUNT(D.OrderID) AS TotalOrders,
     COUNT(DISTINCT D.ProductID) AS DifferentProducts
FROM
     Customers C
     LEFT OUTER JOIN
     (
     SELECT
        OrderID, P.ProductID
     FROM
        Orders O
        JOIN
        Products P ON O.somethingID = P.somethingID
     WHERE YEAR(Orders.OrderDate) = 1996
     ) D
     ON C.CustomerID = D.CustomerID
GROUP BY
     C.CustomerID, C.CompanyName

在这种情况下,如果客户和产品之间没有关系,则无法捕获产品和订单聚合。当然,这是做作的,但我希望我已经抓住了这个概念

编辑:

我需要在JOIN之前显式加入T1和T2到MyTable上。它确实发生了。派生的T1 / T2连接可以是对2个LEFT JOIN的不同查询,没有派生表。它经常发生

SELECT
     --stuff--
FROM
     myTable M1
     LEFT OUTER JOIN
     (
     SELECT
        T1.ColA, T2.ColB
     FROM
        T1
        JOIN
        T2 ON T1.somethingID = T2.somethingID
     WHERE
        --filter--
     ) D
     ON M1.ColA = D.ColA AND M1.ColB = D.ColB