我的慢速T-SQL查询需要完全重新思考

时间:2009-09-04 13:44:35

标签: sql sql-server sql-server-2005 tsql stored-procedures

下午好。我将把存储过程发布到它的整个荣耀中。随意把它撕成碎片。作者不介意。

DECLARE @itemTypeID INT
SELECT @itemTypeID=ItemTypeID FROM dbo.ItemTypes WHERE ItemTypeName = 'Advert'

BEGIN
 SELECT a.Active,
   a.ParentClass,
   a.Classification,
   a.Variant,
   FV."Full Views",
   PV."Print Views",
   EE."Email Enquiries",
   a.ItemRef,
   a.SiteID
 FROM 
 (
 SELECT DISTINCT i.ItemID,
   i.ItemRef,
   i.SiteID,
   i.ParentClass,
   i.Classification,
   i.Summary AS "Variant",       
   i.Active
 FROM Items i
 JOIN Actions a
 ON a.ItemID = i.ItemID
 JOIN ActionTypes at 
 ON a.ActionTypeID = at.ActionTypeID
 WHERE i.ItemTypeID = 1
 AND a.DateAndTime BETWEEN @startDate AND @endDate
 AND at.ActionTypeName IN ('Full view', 'Print view', 'Email enquiry')
 AND ((@siteID = -1) OR (i.SiteID = @siteID))
 AND ((@parentClass = '%') OR (i.ParentClass = @parentClass))
 AND ((@class = '%') OR (i.classification = @class))
 ) a LEFT JOIN
 (
 SELECT i.ItemID, 
   COUNT(*) AS "Full Views"
 FROM CustomerSites cs JOIN Items i
 ON cs.SiteID = i.SiteID
 JOIN Actions a
 ON a.ItemID = i.ItemID
 JOIN ActionTypes at 
 ON a.ActionTypeID = at.ActionTypeID
 JOIN Sites s
 ON cs.SiteID = s.SiteID
 WHERE a.DateAndTime BETWEEN @startDate AND @endDate
 AND i.ItemTypeID = @itemTypeID
 AND at.ActionTypeName = 'Full view'
 AND ((@customerID IS NULL) OR (cs.CustomerID = @customerID))
 AND ((@siteID = -1) OR (cs.SiteID = @siteID))
 AND ((@parentClass = '%') OR (i.ParentClass = @parentClass))
 AND ((@class = '%') OR (i.classification = @class))
 GROUP BY
   i.ItemID
 ) FV 
 ON a.ItemID = FV.ItemID
 LEFT JOIN
 (
 SELECT i.ItemID,
   COUNT(*) AS "Print Views"
 FROM CustomerSites cs JOIN Items i
 ON cs.SiteID = i.SiteID
 JOIN Actions a
 ON a.ItemID = i.ItemID
 JOIN ActionTypes at 
 ON a.ActionTypeID = at.ActionTypeID
 JOIN Sites s
 ON cs.SiteID = s.SiteID
 WHERE a.DateAndTime BETWEEN @startDate AND @endDate
 AND i.ItemTypeID = @itemTypeID
 AND at.ActionTypeName = 'Print view'
 AND ((@customerID IS NULL) OR (cs.CustomerID = @customerID))
 AND ((@siteID = -1) OR (cs.SiteID = @siteID))
 AND ((@parentClass = '%') OR (i.ParentClass = @parentClass))
 AND ((@class = '%') OR (i.classification = @class))
 GROUP BY
   i.ItemID
 ) PV 
 ON a.ItemID = PV.ItemID
 LEFT JOIN
 (
 SELECT i.ItemID,
   COUNT(*) AS "Email Enquiries"
 FROM CustomerSites cs JOIN Items i
 ON cs.SiteID = i.SiteID
 JOIN Actions a
 ON a.ItemID = i.ItemID
 JOIN ActionTypes at 
 ON a.ActionTypeID = at.ActionTypeID
 JOIN Sites s
 ON cs.SiteID = s.SiteID
 WHERE a.DateAndTime BETWEEN @startDate AND @endDate
 AND i.ItemTypeID = @itemTypeID
 AND at.ActionTypeName = 'Email enquiry'
 AND ((@customerID IS NULL) OR (cs.CustomerID = @customerID))
 AND ((@siteID = -1) OR (cs.SiteID = @siteID))
 AND ((@parentClass = '%') OR (i.ParentClass = @parentClass))
 AND ((@class = '%') OR (i.classification = @class))
 GROUP BY
   i.ItemID
 ) EE
 ON a.ItemID = EE.ItemID
 UNION
 SELECT '0','','','','','','','',''

现在最终所有这一切都会返回一些记录以及针对它们发生特定操作的次数。

一个小子集看起来像。

项目描述查看已打印的电子邮件

Item1 Desc1 12 NULL 1
Item2 Desc2 NULL NULL 3
Item3 Desc3 5 6 2

希望你能看到发生了什么。

我想要针对特定​​网站的特定客户针对特定日期范围针对他们采取操作的项目列表,并且该查询应该可以在父类和分类上过滤。尼斯

第一个选择返回属于选择条件的所有不同项目。

其他3个查询都只是针对每个项目返回1种操作类型的计数。即使针对少量数据,查询也很慢。这永远不会生效,只是不起作用。

希望您能看到“作者”方式的错误并纠正他/她。

5 个答案:

答案 0 :(得分:4)

这是我的格式化样式的原始查询:

SELECT  
    a.Active
        ,a.ParentClass
        ,a.Classification
        ,a.Variant
        ,FV."Full Views"
        ,PV."Print Views"
        ,EE."Email Enquiries"
        ,a.ItemRef
        ,a.SiteID
    FROM (SELECT DISTINCT
              i.ItemID,
                  ,i.ItemRef
                  ,i.SiteID
                  ,i.ParentClass
                  ,i.Classification
                  ,i.Summary AS "Variant"
                  ,i.Active
              FROM Items              i
                  JOIN Actions        a ON a.ItemID = i.ItemID
                  JOIN ActionTypes   at ON a.ActionTypeID = at.ActionTypeID
              WHERE i.ItemTypeID = 1
                  AND a.DateAndTime BETWEEN @startDate AND @endDate
                  AND at.ActionTypeName IN ('Full view', 'Print view', 'Email enquiry')
                  AND ((@siteID = -1) OR (i.SiteID = @siteID))
                  AND ((@parentClass = '%') OR (i.ParentClass = @parentClass))
                  AND ((@class = '%') OR (i.classification = @class))
        ) a
        LEFT JOIN (SELECT  
                       i.ItemID
                           ,COUNT(*) AS "Full Views"
                       FROM CustomerSites          cs
                           JOIN Items               i ON cs.SiteID = i.SiteID
                           JOIN Actions             a ON a.ItemID = i.ItemID
                           JOIN ActionTypes        at ON a.ActionTypeID = at.ActionTypeID
                           JOIN Sites               s ON cs.SiteID = s.SiteID
                      WHERE a.DateAndTime BETWEEN @startDate AND @endDate
                          AND i.ItemTypeID = @itemTypeID
                          AND at.ActionTypeName = 'Full view'
                          AND ((@customerID IS NULL) OR (cs.CustomerID = @customerID))
                          AND ((@siteID = -1) OR (cs.SiteID = @siteID))
                          AND ((@parentClass = '%') OR (i.ParentClass = @parentClass))
                          AND ((@class = '%') OR (i.classification = @class))
                      GROUP BY i.ItemID
                  ) FV ON a.ItemID = FV.ItemID
        LEFT JOIN (SELECT
                       i.ItemID
                           ,COUNT(*) AS "Print Views"
                       FROM CustomerSites      cs 
                           JOIN Items           i ON cs.SiteID = i.SiteID
                           JOIN Actions         a ON a.ItemID = i.ItemID
                           JOIN ActionTypes    at ON a.ActionTypeID = at.ActionTypeID
                           JOIN Sites           s ON cs.SiteID = s.SiteID
                       WHERE a.DateAndTime BETWEEN @startDate AND @endDate
                           AND i.ItemTypeID = @itemTypeID
                           AND at.ActionTypeName = 'Print view'
                           AND ((@customerID IS NULL) OR (cs.CustomerID = @customerID))
                           AND ((@siteID = -1) OR (cs.SiteID = @siteID))
                           AND ((@parentClass = '%') OR (i.ParentClass = @parentClass))
                           AND ((@class = '%') OR (i.classification = @class))
                       GROUP BY i.ItemID
                  ) PV ON a.ItemID = PV.ItemID
        LEFT JOIN (SELECT
                       i.ItemID
                           ,COUNT(*) AS "Email Enquiries"
                       FROM CustomerSites   cs
                           JOIN Items        i ON cs.SiteID = i.SiteID
                           JOIN Actions      a ON a.ItemID = i.ItemID
                           JOIN ActionTypes at ON a.ActionTypeID = at.ActionTypeID
                           JOIN Sites        s ON cs.SiteID = s.SiteID
                       WHERE a.DateAndTime BETWEEN @startDate AND @endDate
                           AND i.ItemTypeID = @itemTypeID
                           AND at.ActionTypeName = 'Email enquiry'
                           AND ((@customerID IS NULL) OR (cs.CustomerID = @customerID))
                           AND ((@siteID = -1) OR (cs.SiteID = @siteID))
                           AND ((@parentClass = '%') OR (i.ParentClass = @parentClass))
                           AND ((@class = '%') OR (i.classification = @class))
                       GROUP BY i.ItemID
                  ) EE ON a.ItemID = EE.ItemID
UNION
SELECT '0','','','','','','','',''

这应该有所帮助:

;WITH CustomerSitesCounts AS
(
SELECT
    at.ActionTypeName
        ,i.ItemID
        ,COUNT(*) AS "Print Views"
    FROM CustomerSites      cs 
        JOIN Items           i ON cs.SiteID = i.SiteID
        JOIN Actions         a ON a.ItemID = i.ItemID
        JOIN ActionTypes    at ON a.ActionTypeID = at.ActionTypeID
        JOIN Sites           s ON cs.SiteID = s.SiteID
    WHERE a.DateAndTime BETWEEN @startDate AND @endDate
        AND i.ItemTypeID = @itemTypeID
        AND at.ActionTypeName IN ( 'Print view','Full view','Email enquiry')
        AND ((@customerID IS NULL) OR (cs.CustomerID = @customerID))
        AND ((@siteID = -1) OR (cs.SiteID = @siteID))
        AND ((@parentClass = '%') OR (i.ParentClass = @parentClass))
        AND ((@class = '%') OR (i.classification = @class))
    GROUP BY at.ActionTypeName,i.ItemID
)
SELECT  
    a.Active
        ,a.ParentClass
        ,a.Classification
        ,a.Variant
        ,FV."Full Views"
        ,PV."Print Views"
        ,EE."Email Enquiries"
        ,a.ItemRef
        ,a.SiteID
    FROM (SELECT DISTINCT
              i.ItemID,
                  ,i.ItemRef
                  ,i.SiteID
                  ,i.ParentClass
                  ,i.Classification
                  ,i.Summary AS "Variant"
                  ,i.Active
              FROM Items              i
                  JOIN Actions        a ON a.ItemID = i.ItemID
                  JOIN ActionTypes   at ON a.ActionTypeID = at.ActionTypeID
              WHERE i.ItemTypeID = 1
                  AND a.DateAndTime BETWEEN @startDate AND @endDate
                  AND at.ActionTypeName IN ('Full view', 'Print view', 'Email enquiry')
                  AND ((@siteID = -1) OR (i.SiteID = @siteID))
                  AND ((@parentClass = '%') OR (i.ParentClass = @parentClass))
                  AND ((@class = '%') OR (i.classification = @class))
        ) a
        LEFT JOIN CustomerSitesCounts FV ON a.ItemID = FV.ItemID AND FV.ActionTypeName='Full view'
        LEFT JOIN CustomerSitesCounts PV ON a.ItemID = PV.ItemID AND PV.ActionTypeName='Print view'
        LEFT JOIN CustomerSitesCounts EE ON a.ItemID = EE.ItemID AND EE.ActionTypeName='Email enquiry'
UNION
SELECT '0','','','','','','','',''

答案 1 :(得分:3)

问题:

  1. “catch-all”类型查询无法优化。解决方案:参数化动态SQL。
  2. UNION强制排序。解决方案:改为使用UNION ALL(除非你真的需要它强制的隐式DISTINCT)。
  3. 这应该是最快的解决方案:

    DECLARE @sql AS VARCHAR(MAX)
    
    SELECT @sql = '
    ;WITH cteCommon as (
        SELECT 
            i.ItemID,
            ,i.ItemRef
            ,i.SiteID
            ,i.ParentClass
            ,i.Classification
            ,i.Summary
            ,i.Active
            ,at.ActionTypeName
        FROM Items              i
            JOIN Actions        a ON a.ItemID = i.ItemID
            JOIN ActionTypes   at ON a.ActionTypeID = at.ActionTypeID
        WHERE at.ActionTypeName IN (''Full view'', ''Print view'', ''Email enquiry'')
            --NOTE: if you max-out this date range, then you mant want to exclude it also, RBarryYoung
            AND a.DateAndTime BETWEEN @startDate AND @endDate 
            ' 
            + CASE @siteid WHEN -1 THEN '' ELSE 'AND (i.SiteID = @siteID) 
            ' END
            + CASE @parentClass WHEN '%' THEN '' ELSE 'AND (i.ParentClass = @parentClass)
            ' END
            + CASE @class WHEN '%' THEN '' ELSE 'AND (i.classification = @class)
            ' END
            + '
    )
    , cteA as (
        SELECT DISTINCT
              i.ItemID,
              ,i.ItemRef
              ,i.SiteID
              ,i.ParentClass
              ,i.Classification
              ,i.Summary AS "Variant"
              ,i.Active
          FROM cteA as i
          WHERE i.ItemTypeID = 1
    )
    , cteCountViews AS (
        SELECT  
            i.ItemID
            ,i.ActionType
            ,COUNT(*) AS "ViewCount"
        FROM cteCommon i
            JOIN CustomerSites       cs ON cs.SiteID = i.SiteID
            JOIN Sites               s ON cs.SiteID = s.SiteID
        WHERE i.ItemTypeID = @itemTypeID
            '
            + CASE WHEN @customerid IS NULL THEN '' ELSE '(cs.CustomerID = @customerID)' END
            + '
        GROUP BY i.ItemID
            ,i.ActionType
        )
    SELECT  
        a.Active
        ,a.ParentClass
        ,a.Classification
        ,a.Variant
        ,FV."Full Views"
        ,PV."Print Views"
        ,EE."Email Enquiries"
        ,a.ItemRef
        ,a.SiteID
    FROM cteA AS a
    LEFT JOIN (
            SELECT i.ItemID, ViewCount AS "Full Views"
            FROM cteCountViews i
            WHERE i.ActionTypeName = ''Full view''
        ) FV ON a.ItemID = FV.ItemID
    LEFT JOIN (
            SELECT i.ItemID, ViewCount AS "Print Views"
            FROM cteCountViews i
            WHERE i.ActionTypeName = ''Print view''
        ) PV ON a.ItemID = PV.ItemID
    LEFT JOIN (
            SELECT i.ItemID, ViewCount AS "Email Enquiries"
            FROM cteCountViews i
            WHERE i.ActionTypeName = ''Email enquiry''
          ) EE ON a.ItemID = EE.ItemID
    UNION ALL
    SELECT ''0'','''','''','''','''','''','''','''',''''
    '
    EXEC sp_ExecuteSQL @sql
        ,'@startdate DATETIME,@enddate DATETIME,@siteid INT,@parentclass VARCHAR(MAX),@class VARCHAR(MAX),@itemtypeid INT,@customerid INT'
        , @startdate, @enddate, @siteid, @parentclass, @class, @itemtypeid, @customerid
    

    注意:在带有JOIN的某些(类,站点等)上使用通配符可能会导致源行和大量结果集的交叉乘法。

答案 2 :(得分:1)

首先,您可以避免使用3个绝对相同的子查询副本购买具有通用的子查询,并使用WITH语句以便您可以重复使用它。

然后,为什么在不需要时进行子选择。

然后再删除一些你不需要的连接(以及DISTINCT)。

你从这些方面得到了一些东西(当然没有经过测试):

DECLARE @itemTypeID INT
SELECT @itemTypeID=ItemTypeID FROM dbo.ItemTypes WHERE ItemTypeName = 'Advert'

BEGIN
    WITH ItemTypeSummary
    AS (SELECT  i.ItemID,
                at.ActionTypeName,
                COUNT(*) AS CNT
        FROM    Items i
        JOIN    Actions a
            ON  a.ItemID = i.ItemID
        JOIN    ActionTypes at 
            ON  a.ActionTypeID = at.ActionTypeID
            AND at.ActionTypeName IN ('Full view', 'Print view', 'Email enquiry')
        WHERE   a.DateAndTime BETWEEN @startDate AND @endDate
            --// not sure you need those below as they are all part of Items filter anyways in the main query
            /*
            AND i.ItemTypeID = @itemTypeID
            AND ((@customerID IS NULL) OR (cs.CustomerID = @customerID))
            AND ((@siteID = -1) OR (cs.SiteID = @siteID))
            AND ((@parentClass = '%') OR (i.ParentClass = @parentClass))
            AND ((@class = '%') OR (i.classification = @class))
            */
        GROUP BY    i.ItemID,
                    at.ActionTypeName
    )

    SELECT DISTINCT i.ItemID,
                    i.ItemRef,
                    i.SiteID,
                    i.ParentClass,
                    i.Classification,
                    i.Summary AS "Variant",
                    i.Active,
                    FV.CNT AS "Full views",
                    PV.CNT AS "Print views",
                    EE.CNT AS "Email enquiries"
    FROM        Items i
    JOIN        CustomerSites cs
            ON  cs.SiteID = i.SiteID
    LEFT JOIN   ItemTypeSummary FV
            ON  i.ItemID = FV.ItemID
            AND FV.ActionTypeName = 'Full view'
    LEFT JOIN   ItemTypeSummary PV
            ON  i.ItemID = PV.ItemID
            AND PV.ActionTypeName = 'Print view'
    LEFT JOIN   ItemTypeSummary EE
            ON  i.ItemID = EE.ItemID
            AND EE.ActionTypeName = 'Email enquiry'

    WHERE   i.ItemTypeID = @itemTypeID
        AND ((@customerID IS NULL) OR (cs.CustomerID = @customerID))
        AND ((@siteID = -1) OR (i.SiteID = @siteID))
        AND ((@parentClass = '%') OR (i.ParentClass = @parentClass))
        AND ((@class = '%') OR (i.classification = @class))
END

另外请记住,如果可以避免使用这些OR语句,那么使用过滤器 ALL或特定项的技巧并不是很酷,因为SQLServer将无法生成最佳执行计划。

答案 3 :(得分:1)

另一种选择是设置表变量或临时#Table来保存结果。在ItemID上添加唯一约束。

将子系统拆分为单独的步骤。首先将要计算的记录INSERT到此表中。对每种视图类型的数据运行单独的UPDATE - 更新打印视图计数,更新全视图计数,更新电子邮件查询计数。然后返回结果。如有必要,将OR条件拆分为单独的查询。

这种方法会多次运行您的数据,但它会避免LEFT JOIN和未编入索引的子查询。

在我们的应用中,拥有多个步骤似乎比一个非常复杂的查询表现更好。您的结果可能会有所不同。

答案 4 :(得分:1)

改写为:

WITH base AS (
  SELECT i.ItemID,
         i.ItemRef,
         i.SiteID,
         i.ParentClass,
         i.Classification,
         i.Summary,
         i.Active,
         i.ItemTypeID,
         at.ActionTypeName 
    FROM ITEMS i
    JOIN ACTIONS a ON a.ItemID = i.ItemID
    JOIN ACTIONTYPES at ON at.ActionTypeID = a.ActionTypeID
   WHERE a.DateAndTime BETWEEN @startDate AND @endDate
     AND (@siteID = -1 OR i.SiteID = @siteID)
     AND (@parentClass = '%' OR i.ParentClass = @parentClass)
     AND (@class = '%' OR i.classification = @class)),
     items AS (
    SELECT b.ItemID,
           b.ItemRef,
           b.SiteID,
           b.ParentClass,
           b.Classification,
           b.Summary AS "Variant",
           b.Active
      FROM base b
     WHERE b.itemtypeid = 1
       AND b.actiontypename IN ('Full view', 'Print view', 'Email enquiry')
  GROUP BY i.ItemID, i.ItemRef, i.SiteID, i.ParentClass, i.Classification, i.Summary, i.Active),
     full_views AS (
    SELECT b.ItemID, 
           COUNT(*) AS num_full_Views
      FROM base b
      JOIN CUSTOMERSITES cs ON cs.siteid = b.siteid
      JOIN SITES s ON s.siteid = b.siteid
     WHERE b.itemtypeid = @itemTypeID
       AND b.ActionTypeName = 'Full view'
       AND (@customerID IS NULL OR cs.CustomerID = @customerID)
  GROUP BY b.itemid),
     print_views AS (
   SELECT b.ItemID, 
          COUNT(*) AS num_print_views
     FROM base b
     JOIN CUSTOMERSITES cs ON cs.siteid = b.siteid
     JOIN SITES s ON s.siteid = b.siteid
    WHERE b.itemtypeid = @itemTypeID
      AND b.ActionTypeName = 'Print view'
      AND (@customerID IS NULL OR cs.CustomerID = @customerID)
 GROUP BY b.itemid),
     email_queries AS (
 SELECT b.ItemID, 
        COUNT(*) AS num_email_enquiries
   FROM base b
   JOIN CUSTOMERSITES cs ON cs.siteid = b.siteid
  JOIN SITES s ON s.siteid = b.siteid
 WHERE b.itemtypeid = @itemTypeID
       AND b.ActionTypeName = 'Email enquiry'
       AND (@customerID IS NULL OR cs.CustomerID = @customerID)
  GROUP BY b.itemid)
   SELECT a.Active,
          a.ParentClass,
          a.Classification,
          a.Variant,
          ISNULL(fv.num_full_Views, 0) AS "Full Views",
          ISNULL(pv.num_print_views, 0) AS "Print Views",
          ISNULL(ee.num_email_enquiries, 0) AS "Email Enquiries",
          a.ItemRef,
          a.SiteID
     FROM items a
LEFT JOIN full_views fv ON fv.itemid = a.itemid
LEFT JOIN print_views pv ON pv.itemid = a.itemid
LEFT JOIN email_queries ee ON ee.itemid = a.itemid

为了获得更好的性能,我将其转换为动态SQL,以便删除这些参数检查:

AND (@siteID = -1 OR i.SiteID = @siteID)

...因为对sargability的负面影响。