Why CTE (Common Table Expressions) in some cases slow down queries comparing to temporary tables in SQL Server

后端 未结 3 1070
予麋鹿
予麋鹿 2021-02-07 09:56

I have several cases where my complex CTE (Common Table Expressions) are ten times slower than the same queries using the temporary tables in SQL

3条回答
  •  忘掉有多难
    2021-02-07 10:35

    There can be several reason for Temp table performing better than CTE and vice versa depending upon specific Query and requirement.

    IMO in your case both the query are not optimize.

    Since CTE is evaluated every time it is referenced. so in your case

    SELECT a.MasterAccountId,
           ISNULL(t.BatchedOrders, 0) BatchedOrders,
           ISNULL(t.PendingOrders, 0) PendingOrders,
           ISNULL(av.AvailableStock, 0) AvailableOrders,
           ISNULL(av.AvailableBeforeCutOff, 0) AvailableCutOff,
           ISNULL(av.OrdersAnyStock, 0) AllOrders
    FROM MasterAccount a
    LEFT OUTER JOIN Available av ON av.MasterAccountId = a.MasterAccountId
    LEFT OUTER JOIN Totals t ON t.MasterAccountId = a.MasterAccountId
    WHERE a.IsActive = 1
    

    This query is showing High Cardinality estimate.MasterAccount table is evaluated multiple times.Due to this reason it is slow.

    In case of Temp table,

    SELECT a.MasterAccountId,
           ISNULL(t.BatchedOrders, 0) BatchedOrders,
           ISNULL(t.PendingOrders, 0) PendingOrders,
           ISNULL(av.AvailableStock, 0) AvailableOrders,
           ISNULL(av.AvailableBeforeCutOff, 0) AvailableCutOff,
           ISNULL(av.OrdersAnyStock, 0) AllOrders
    FROM MasterAccount a (NOLOCK)
    LEFT OUTER JOIN #Available av (NOLOCK) ON av.MasterAccountId = a.MasterAccountId
    LEFT OUTER JOIN Totals t (NOLOCK) ON t.MasterAccountId = a.MasterAccountId
    WHERE a.IsActive = 1
    

    Here #Available is already evaluated and result is store in temp table so MasterAccount table is join with Less resultset,thus Cardinality Estimate is less. similarly with #Orders table.

    Both CTE and Temp table query can be optimize in your case thus performance improved.

    So #Orders should be your base temp table and you should not use MasterAccount again later.you should use #Orders instead.

    INSERT INTO #Available
    SELECT  ma.MasterAccountId,
            SUM(IIF(ma.IsPartialStock = 1,  CASE WHEN sa.[Status] IN ('Full', 'Partial') THEN 1 ELSE 0 END, 
                                            CASE WHEN sa.[Status] = 'Full' THEN 1 ELSE 0 END)) AS AvailableStock,
            SUM(IIF(sa.[Status] IN ('Full', 'Partial', 'None'), 1, 0))  AS OrdersAnyStock, 
    
            SUM(IIF(sa.RequisitionDate < dbo.TicksToTime(ma.DailyOrderCutOffTime, @toDate),
                    IIF(ma.IsPartialStock = 1,  CASE WHEN sa.[Status] IN ('Full', 'Partial') THEN 1 ELSE 0 END, 
                                                CASE WHEN sa.[Status] = 'Full' THEN 1 ELSE 0 END), 0)) AS AvailableBeforeCutOff                             
    FROM #Orders ma (NOLOCK)
    INNER JOIN #StockAvailability2 sa ON sa.AccountNumber = dbo.fn_RemoveUnitPrefix(ma.TaskAccountId)
    GROUP BY ma.MasterAccountId, ma.IsPartialStock
    

    Here require column from MasterAcount table like ma.IsPartialStock etc should incorporated in #order table itself if possible.Hope my idea is clear.

    No need of MasterAccount table in in last query

    SELECT a.MasterAccountId,
           ISNULL(t.BatchedOrders, 0) BatchedOrders,
           ISNULL(t.PendingOrders, 0) PendingOrders,
           ISNULL(av.AvailableStock, 0) AvailableOrders,
           ISNULL(av.AvailableBeforeCutOff, 0) AvailableCutOff,
           ISNULL(av.OrdersAnyStock, 0) AllOrders
    FROM  #Available av 
    LEFT OUTER JOIN Totals t  ON t.MasterAccountId = av.MasterAccountId
    --WHERE a.IsActive = 1
    

    I think no need of Nolock hint in temp table.

提交回复
热议问题