【问题标题】:What are the advantages of a query using a derived table(s) over a query not using them?与不使用派生表的查询相比,使用派生表的查询有哪些优势?
【发布时间】:2015-01-28 00:11:29
【问题描述】:

我知道如何使用派生表,但我仍然看不出使用派生表有什么真正的好处。

例如,在下面的文章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

【问题讨论】:

    标签: sql sql-server tsql


    【解决方案1】:

    在您的示例中,派生表并非绝对必要。在很多情况下,您可能需要加入聚合或类似的数据,而派生表确实是处理这种情况的唯一方法:

    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 实现不能很好地处理大量行。

    【讨论】:

    • @AspOnMyNet 由于 DRY(不要重复自己)原则,问题是可维护性之一。
    【解决方案2】:

    我通常使用派生表(或 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
    

    高级警告,尤其是对于大规模分析

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

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

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

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

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

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

    【讨论】:

    • A) “例如(引用自 MSDN),如果没有派生表或 CTE,您不能做的一件事就是将聚合函数放在 WHERE 子句中。所以你可以像这样创建一个派生查询:“但是你的查询在 Where 子句中也没有聚合函数?! B)如果使用派生表,我们如何能够将聚合函数放在 Where 子句中,但如果不使用派生表,我们就不能将其放入(或者您的意思是派生表中的列可以显示聚合值和我们可以在主查询的 Where 子句中加入这一列——希望是有意义的)?
    • 好点。我添加了一个说明性示例来说明没有 CTE 或派生查询你不能做什么。
    • 您可以在临时表中添加索引以加快处理速度。
    【解决方案3】:

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

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

    与所有可能提高性能的技术一样,您需要测试它们是否确实提高了性能。派生表几乎总是会大大优于相关子查询,但也有可能不会。

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

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

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

    【讨论】:

      【解决方案4】:

      使用您的术语和示例,派生表只是更复杂,没有任何优势。但是,有些事情需要派生表。在最复杂的情​​况下,这些可能是 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
      )
      

      【讨论】:

      • 上面的例子有道理
      【解决方案5】:

      在这种情况下,派生表允许在 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 到 MyTable 之前显式地 JOIN T1 和 T2。它确实发生了。派生的 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
      

      【讨论】:

      • A)如果我可能会问……我的编辑中的查询会产生与您的第一个查询相同的结果吗? B) “我需要在 JOIN 到 MyTable 之前显式地 JOIN T1 和 T2。它确实发生了。派生的 T1/T2 联接可以是与没有派生表的 2 个 LEFT JOIN 不同的查询。它经常发生”嗯,我可以看到派生 T1/T2 连接的必要性,如果我们想将聚合函数放在外部查询的 WHERE 子句中,但除此之外,我不确定你是什么想传达?!
      猜你喜欢
      • 1970-01-01
      • 2021-11-03
      • 2014-09-17
      • 1970-01-01
      • 2021-12-14
      • 1970-01-01
      • 2012-08-06
      • 2016-01-05
      • 2015-10-16
      相关资源
      最近更新 更多