基于行的子查询的性能调优:LEFT OUTER JOIN和OUTER APPLY,替代方案?

时间:2014-01-16 17:02:54

标签: sql sql-server tsql reporting-services sql-server-2008-r2

特定查询(在Dynamics CRM 2011数据库上)的性能非常糟糕。由于它是一个规范化的数据模型,但需要对此数据进行展平视图(SSRS报告),我使用SELECT TOP(1)子查询做了很多(12)LEFT OUTER JOIN,例如:

LEFT JOIN Filterednew_rates FRates ON FRates.new_ratesid =
    (SELECT TOP (1)
        FRR.new_ratesid
     FROM Filterednew_rates FRR
     WHERE
        FRR.new_contractid = FContract.contractid
        AND FRR.statuscode <> 803270000 -- NOT Obsolete
     ORDER BY FRR.new_startdate DESC
    )

这适用于少量结果行(例如3行10秒),但我已经在约100个预期结果行上运行了45分钟(源数据量相同,只是不同的WHERE条款)。所以我开始寻找方法来“强制”SQL Server每行运行子查询(从逻辑上来说,这将是线性扩展)。

然后我阅读The power of T-SQL's APPLY operator并设法将上述内容更改为

OUTER APPLY (
    SELECT TOP (1)
        FRR.*
     FROM Filterednew_rates FRR
     WHERE
        FRR.new_contractid = FContract.contractid
        AND FRR.statuscode <> 803270000 -- NOT Obsolete
     ORDER BY FRR.new_startdate DESC
) AS FRates

使执行时间与结果记录的数量呈线性关系(100行约3:30分钟,3行约6秒)。不知怎的,这让SQL Server决定更好地改变查询执行计划!

在SQL中是否还有其他方法可以“扁平化”规范化数据模型而无需求助于Integration / Analysis Services?

编辑:

感谢输入@Aaron和@BAReese。我将尝试应用PIVOT / UNPIVOT和窗口函数,并报告查询性能差异。

并且受欢迎的请求,查询的更大部分。我试图对查询进行“匿名化”,因此实际的查询属性更具描述性。

OUTER APPLY (
    SELECT TOP (1)
        FCO.*
     FROM Filterednew_contractoption FCO
     WHERE
        FCO.new_contractid = FContract.contractid
        AND FCO.new_included = 1 -- Is Included
        AND FCO.new_optionidname = 'SomeOption1'
) AS FOptionSomeOption1
OUTER APPLY (
    SELECT TOP (1)
        FCO.*
     FROM Filterednew_contractoption FCO
     WHERE
        FCO.new_contractid = FContract.contractid
        AND FCO.new_included = 1 -- Is Included
        AND FCO.new_optionidname = 'SomeOption2'
) AS FOptionSomeOption2
OUTER APPLY (
    SELECT TOP (1)
        FCD.*
     FROM FilteredContractDetail FCD
     JOIN FilteredProduct FProd ON FCD.productid = FProd.productid
     WHERE
         FContract.contractid = FCD.contractid
         AND FCD.new_included = 1 -- Is Included
         AND FProd.productnumber IN ('COLDEL1', 'COLDEL2', 'COLDEL3', 'COLDEL4')
) AS FColDelContractDetail
LEFT JOIN FilteredProduct FColDelProduct ON FColDelContractDetail.productid = FColDelProduct.productid
OUTER APPLY (
    SELECT TOP (1)
        FCO.*
     FROM Filterednew_contractoption FCO
     JOIN Filterednew_contractdetail_new_contractoptions FCD_CO ON FCO.new_contractoptionid = FCD_CO.new_contractoptionid
     WHERE
        FCD_CO.contractdetailid = FColDelContractDetail.contractdetailid
        AND FCO.new_included = 1 -- Is Included
        AND FCO.new_optionidname LIKE 'Input1'
) AS FColDelInput1Option
OUTER APPLY (
    SELECT TOP (1)
        FCO.*
     FROM Filterednew_contractoption FCO
     JOIN Filterednew_contractdetail_new_contractoptions FCD_CO ON FCO.new_contractoptionid = FCD_CO.new_contractoptionid
     WHERE
        FCD_CO.contractdetailid = FColDelContractDetail.contractdetailid
        AND FCO.new_included = 1 -- Is Included
        AND FCO.new_optionidname LIKE 'Input2'
) AS FColDelInput2Option
OUTER APPLY (
    SELECT TOP (1)
        FCO.*
     FROM Filterednew_contractoption FCO
     JOIN Filterednew_contractdetail_new_contractoptions FCD_CO ON FCO.new_contractoptionid = FCD_CO.new_contractoptionid
     WHERE
        FCD_CO.contractdetailid = FColDelContractDetail.contractdetailid
        AND FCO.new_included = 1 -- Is Included
        AND FCO.new_optionidname LIKE 'Input3'
) AS FColDelInput3Option
OUTER APPLY (
    SELECT TOP (1)
        FCP.*
     FROM Filterednew_price FCP
     WHERE FCP.new_contractid = FContract.contractid
     AND FCP.statuscode <> 803270000 -- NOT Obsolete
     ORDER BY FCP.new_validfrom DESC
) AS FPrice
OUTER APPLY (
    SELECT TOP (1)
        FCFR.*
     FROM Filterednew_contractforecastresult FCFR
     WHERE FCFR.new_contractid = FContract.contractid
     ORDER BY FCFR.createdon DESC
) AS FForecastResult

1 个答案:

答案 0 :(得分:2)

由于您使用的是SQL Server,因此这是一个使用windowing functions来提高效率的绝佳机会。

这样的事情可能有助于它更快地运行:

LEFT JOIN
    (
    SELECT FRR.new_contractid, ROW_NUMBER() over(partition by FRR.new_contractid
                                            order by FRR.new_startdate DESC) as Last_ID
    FROM Filterednew_rates as FRR
    WHERE FRR.statuscode <> 803270000 -- NOT Obsolete
    ) AS FRates
    ON FRates.new_contractid = FContract.contractid
    and FRates.Last_ID = 1

这应该做的是允许派生表生成所有contractids的列表,但给出优先级列表。从理论上讲,它在服务器上会更容易,并且你不会在必要时多次访问该表。您可以做的另一件事是将 SET STATISTICS IO ON SET STATISTICS TIME ON 添加到查询的顶部(假设您正在SQL Server Management Studio中对此进行测试)。如果在SSMS中,您将在[Messages]选项卡上记录每个表的逻辑/物理读取次数,以及查询所花费的时间。