Hi everybody,
I have a stored procedure I need to optimize. I am trying different variations of the same query and two same variations that should be identical in execution plans yield very different performance: first executes in less that a second, the second variation takes a minute or more.
This is the original code:
WITH Purchases AS ( SELECT i.descrip ,tr.date_time ,CAST(0 AS MONEY) AS LoadAmount ,CAST(0 AS MONEY) AS BonusAmount ,tr.extension AS PurchaseAmount ,sh.pmt_amt1 + sh.pmt_amt2 + sh.pmt_amt3 + sh.pmt_amt4 AS TotalPayments ,ROW_NUMBER() OVER ( PARTITION BY sh.sale_no ORDER BY CASE WHEN tr.extension = 0 THEN 1 ELSE 2 END ) AS RecNo ,tc.extension AS CardPayment ,tr.department ,tr.category ,tr.item ,tr.sale_no ,tr.operator ,tr.salespoint ,ISNULL(cc.card_id, SPACE(4)) AS card_id ,COALESCE(cc.card_type, SPACE(4)) AS card_type ,tr.init_price ,tr.quantity ,tr.mastertran ,tr.message ,@nInvoiceNo AS Invoice_No FROM dbo.tr_info tic JOIN dbo.transact tc ON tc.trans_no = tic.trans_no AND tc.department = '**TRANS**' AND tc.message NOT IN ( 'LOAD' ,'CASHOUT' ) JOIN dbo.sale_hdr sc ON sc.sale_no = tc.sale_no -- Transaction detail: JOIN dbo.sale_hdr sh ON sh.mastersale = sc.mastersale JOIN dbo.transact tr ON tr.sale_no = sh.sale_no AND tr.department <> '**TRANS**' -- AND tr.message NOT IN ('LOAD','CASHOUT') JOIN dbo.items i ON i.department = tr.department AND i.category = tr.category AND i.item = tr.item LEFT OUTER JOIN dbo.cc_trans cc ON cc.sale_no = tr.sale_no -- attempt to get credit card info for the Credit card the IHC was purchased / re-loaded AND DATALENGTH(cc.approval) > 0 AND cc.card_id <> RIGHT(RTRIM(@cCardNo), 4) WHERE tic.info_num = CAST(@nPassNo AS CHAR(17)) AND tic.info_type = 25 ) ,Loads AS ( SELECT i.descrip ,tr.date_time ,tr.extension AS LoadAmount ,COALESCE(BonusAmount, 0) AS BonusAmount ,CAST(0 AS MONEY) AS PurchaseAmount ,CAST(0 AS MONEY) AS CardPayment ,tr2.department ,tr2.category ,tr2.item ,tr.sale_no ,tr.operator ,tr.salespoint ,ISNULL(cc.card_id, SPACE(4)) AS card_id ,COALESCE(cc.card_type, SPACE(4)) AS card_type ,tr.init_price ,tr.quantity ,tr.mastertran ,tr.message ,@nInvoiceNo AS invoice_no FROM dbo.transact tr LEFT JOIN ( SELECT t.extension AS BonusAmount ,t.mastertran FROM dbo.transact t INNER JOIN dbo.tr_info ti ON t.trans_no = ti.trans_no AND ti.info_type = 90 AND ti.info_num = CAST(@nInvoiceNo AS CHAR(17)) WHERE t.invoice_no = @nInvoiceNo AND t.message = 'LOAD' ) Bonus ON tr.mastertran = Bonus.mastertran INNER JOIN dbo.transact tr2 ON tr.mastertran = tr2.mastertran AND tr2.department <> '**TRANS**' LEFT OUTER JOIN dbo.cc_trans cc ON cc.sale_no = tr2.sale_no -- attempt to get credit card info for the Credit card the IHC was purchased / re-loaded AND DATALENGTH(cc.approval) > 0 LEFT OUTER JOIN dbo.items i ON tr2.department = i.department AND tr2.category = i.category AND tr2.item = i.item WHERE tr.invoice_no = @nInvoiceNo AND tr.message IN ( 'LOAD' ,'CASHOUT' ) AND NOT EXISTS ( SELECT 1 FROM dbo.tr_info ti WHERE tr.trans_no = ti.trans_no AND ti.info_type = 90 AND ti.info_num = CAST(@nInvoiceNo AS CHAR(17)) ) ) SELECT descrip ,date_time ,LoadAmount ,BonusAmount ,PurchaseAmount ,CASE WHEN RecNo = 1 THEN CardPayment ELSE 0 END AS CardPayment ,department ,category ,item ,sale_no ,operator ,salespoint ,card_id ,card_type ,init_price ,quantity ,mastertran ,MESSAGE ,invoice_no FROM Purchases UNION ALL SELECT descrip ,date_time ,LoadAmount ,BonusAmount ,PurchaseAmount ,CardPayment ,department ,category ,item ,sale_no ,operator ,salespoint ,card_id ,card_type ,init_price ,quantity ,mastertran ,MESSAGE ,invoice_no FROM Loads ORDER BY date_time;
This is first variation - executes with the same speed:
IF OBJECT_ID('TempDB..#Purchases', N'U') IS NOT NULL DROP TABLE #Purchases; IF OBJECT_ID('TempDB..#Loads', N'U') IS NOT NULL DROP TABLE #Loads; SELECT tic.trans_no INTO #Purchases FROM dbo.tr_info tic WHERE tic.info_num = @cPassNo AND tic.info_type = 25; SELECT tr.date_time ,tr.extension AS LoadAmount ,tr.sale_no ,tr.operator ,tr.salespoint ,tr.init_price ,tr.quantity ,tr.mastertran ,tr.message INTO #Loads FROM dbo.transact tr WHERE tr.invoice_no = @nInvoiceNo AND tr.message IN ( 'LOAD' ,'CASHOUT' ) AND NOT EXISTS ( SELECT 1 FROM dbo.tr_info ti WHERE tr.trans_no = ti.trans_no AND ti.info_type = 90 AND ti.info_num = CAST(@nInvoiceNo AS CHAR(17)) ); WITH Purchases AS ( SELECT i.descrip ,tr.date_time ,CAST(0 AS MONEY) AS LoadAmount ,CAST(0 AS MONEY) AS BonusAmount ,tr.extension AS PurchaseAmount ,sh.pmt_amt1 + sh.pmt_amt2 + sh.pmt_amt3 + sh.pmt_amt4 AS TotalPayments ,ROW_NUMBER() OVER ( PARTITION BY sh.sale_no ORDER BY CASE WHEN tr.extension = 0 THEN 1 ELSE 2 END ) AS RecNo ,tc.extension AS CardPayment ,tr.department ,tr.category ,tr.item ,tr.sale_no ,tr.operator ,tr.salespoint ,ISNULL(cc.card_id, SPACE(4)) AS card_id ,COALESCE(cc.card_type, SPACE(4)) AS card_type ,tr.init_price ,tr.quantity ,tr.mastertran ,tr.message ,@nInvoiceNo AS Invoice_No FROM #Purchases tic INNER JOIN dbo.transact tc ON tic.trans_no = tc.trans_no
AND tc.department = '**TRANS**' AND tc.message NOT IN ( 'LOAD','CASHOUT')
JOIN dbo.sale_hdr sc ON sc.sale_no = tc.sale_no -- Transaction detail: JOIN dbo.sale_hdr sh ON sh.mastersale = sc.mastersale JOIN dbo.transact tr ON tr.sale_no = sh.sale_no AND tr.department <> '**TRANS**' -- AND tr.message NOT IN ('LOAD','CASHOUT') JOIN dbo.items i ON i.department = tr.department AND i.category = tr.category AND i.item = tr.item LEFT OUTER JOIN dbo.cc_trans cc ON cc.sale_no = tr.sale_no -- attempt to get credit card info for the Credit card the IHC was purchased / re-loaded AND DATALENGTH(cc.approval) > 0 AND cc.card_id <> RIGHT(RTRIM(@cCardNo), 4) ) ,Loads AS ( SELECT i.descrip ,tr.date_time ,tr.LoadAmount ,COALESCE(BonusAmount, 0) AS BonusAmount ,CAST(0 AS MONEY) AS PurchaseAmount ,CAST(0 AS MONEY) AS CardPayment ,tr2.department ,tr2.category ,tr2.item ,tr.sale_no ,tr.operator ,tr.salespoint ,ISNULL(cc.card_id, SPACE(4)) AS card_id ,COALESCE(cc.card_type, SPACE(4)) AS card_type ,tr.init_price ,tr.quantity ,tr.mastertran ,tr.message ,@nInvoiceNo AS invoice_no FROM #Loads tr LEFT JOIN ( SELECT t.extension AS BonusAmount ,t.mastertran FROM dbo.transact t INNER JOIN dbo.tr_info ti ON t.trans_no = ti.trans_no AND ti.info_type = 90 AND ti.info_num = CAST(@nInvoiceNo AS CHAR(17)) WHERE t.invoice_no = @nInvoiceNo AND t.message = 'LOAD' ) Bonus ON tr.mastertran = Bonus.mastertran INNER JOIN dbo.transact tr2 ON tr.mastertran = tr2.mastertran AND tr2.department <> '**TRANS**' LEFT OUTER JOIN dbo.cc_trans cc ON cc.sale_no = tr2.sale_no -- attempt to get credit card info for the Credit card the IHC was purchased / re-loaded AND DATALENGTH(cc.approval) > 0 LEFT OUTER JOIN dbo.items i ON tr2.department = i.department AND tr2.category = i.category AND tr2.item = i.item ) SELECT descrip ,date_time ,LoadAmount ,BonusAmount ,PurchaseAmount ,CASE WHEN RecNo = 1 THEN CardPayment ELSE 0 END AS CardPayment ,department ,category ,item ,sale_no ,operator ,salespoint ,card_id ,card_type ,init_price ,quantity ,mastertran ,MESSAGE ,invoice_no FROM Purchases UNION ALL SELECT descrip ,date_time ,LoadAmount ,BonusAmount ,PurchaseAmount ,CardPayment ,department ,category ,item ,sale_no ,operator ,salespoint ,card_id ,card_type ,init_price ,quantity ,mastertran ,MESSAGE ,invoice_no FROM Loads ORDER BY date_time;
And finally, this variation takes very long to execute:
IF OBJECT_ID('TempDB..#Purchases', N'U') IS NOT NULL DROP TABLE #Purchases; IF OBJECT_ID('TempDB..#Loads', N'U') IS NOT NULL DROP TABLE #Loads; SELECT tic.trans_no INTO #Purchases FROM dbo.tr_info tic WHERE tic.info_num = @cPassNo AND tic.info_type = 25; SELECT tr.date_time ,tr.extension AS LoadAmount ,tr.sale_no ,tr.operator ,tr.salespoint ,tr.init_price ,tr.quantity ,tr.mastertran ,tr.message INTO #Loads FROM dbo.transact tr WHERE tr.invoice_no = @nInvoiceNo AND tr.message IN ( 'LOAD' ,'CASHOUT' ) AND NOT EXISTS ( SELECT 1 FROM dbo.tr_info ti WHERE tr.trans_no = ti.trans_no AND ti.info_type = 90 AND ti.info_num = CAST(@nInvoiceNo AS CHAR(17)) ); WITH Purchases AS ( SELECT i.descrip ,tr.date_time ,CAST(0 AS MONEY) AS LoadAmount ,CAST(0 AS MONEY) AS BonusAmount ,tr.extension AS PurchaseAmount ,sh.pmt_amt1 + sh.pmt_amt2 + sh.pmt_amt3 + sh.pmt_amt4 AS TotalPayments ,ROW_NUMBER() OVER ( PARTITION BY sh.sale_no ORDER BY CASE WHEN tr.extension = 0 THEN 1 ELSE 2 END ) AS RecNo ,tc.extension AS CardPayment ,tr.department ,tr.category ,tr.item ,tr.sale_no ,tr.operator ,tr.salespoint ,ISNULL(cc.card_id, SPACE(4)) AS card_id ,COALESCE(cc.card_type, SPACE(4)) AS card_type ,tr.init_price ,tr.quantity ,tr.mastertran ,tr.message ,@nInvoiceNo AS Invoice_No FROM dbo.transact tc JOIN dbo.sale_hdr sc ON sc.sale_no = tc.sale_no -- Transaction detail: JOIN dbo.sale_hdr sh ON sh.mastersale = sc.mastersale JOIN dbo.transact tr ON tr.sale_no = sh.sale_no AND tr.department <> '**TRANS**' -- AND tr.message NOT IN ('LOAD','CASHOUT') JOIN dbo.items i ON i.department = tr.department AND i.category = tr.category AND i.item = tr.item LEFT OUTER JOIN dbo.cc_trans cc ON cc.sale_no = tr.sale_no -- attempt to get credit card info for the Credit card the IHC was purchased / re-loaded AND DATALENGTH(cc.approval) > 0 AND cc.card_id <> RIGHT(RTRIM(@cCardNo), 4)
WHERE tc.trans_no IN (select trans_no FROM #Purchases)
AND tc.department = '**TRANS**' AND tc.message NOT IN ( 'LOAD' ,'CASHOUT' )
) ,Loads AS ( SELECT i.descrip ,tr.date_time ,tr.LoadAmount ,COALESCE(BonusAmount, 0) AS BonusAmount ,CAST(0 AS MONEY) AS PurchaseAmount ,CAST(0 AS MONEY) AS CardPayment ,tr2.department ,tr2.category ,tr2.item ,tr.sale_no ,tr.operator ,tr.salespoint ,ISNULL(cc.card_id, SPACE(4)) AS card_id ,COALESCE(cc.card_type, SPACE(4)) AS card_type ,tr.init_price ,tr.quantity ,tr.mastertran ,tr.message ,@nInvoiceNo AS invoice_no FROM #Loads tr LEFT JOIN ( SELECT t.extension AS BonusAmount ,t.mastertran FROM dbo.transact t INNER JOIN dbo.tr_info ti ON t.trans_no = ti.trans_no AND ti.info_type = 90 AND ti.info_num = CAST(@nInvoiceNo AS CHAR(17)) WHERE t.invoice_no = @nInvoiceNo AND t.message = 'LOAD' ) Bonus ON tr.mastertran = Bonus.mastertran INNER JOIN dbo.transact tr2 ON tr.mastertran = tr2.mastertran AND tr2.department <> '**TRANS**' LEFT OUTER JOIN dbo.cc_trans cc ON cc.sale_no = tr2.sale_no -- attempt to get credit card info for the Credit card the IHC was purchased / re-loaded AND DATALENGTH(cc.approval) > 0 LEFT OUTER JOIN dbo.items i ON tr2.department = i.department AND tr2.category = i.category AND tr2.item = i.item ) SELECT descrip ,date_time ,LoadAmount ,BonusAmount ,PurchaseAmount ,CASE WHEN RecNo = 1 THEN CardPayment ELSE 0 END AS CardPayment ,department ,category ,item ,sale_no ,operator ,salespoint ,card_id ,card_type ,init_price ,quantity ,mastertran ,MESSAGE ,invoice_no FROM Purchases UNION ALL SELECT descrip ,date_time ,LoadAmount ,BonusAmount ,PurchaseAmount ,CardPayment ,department ,category ,item ,sale_no ,operator ,salespoint ,card_id ,card_type ,init_price ,quantity ,mastertran ,MESSAGE ,invoice_no FROM Loads ORDER BY date_time;So, why would moving conditions from JOIN into WHERE make the query execute such a long time?
For every expert, there is an equal and opposite expert. - Becker's Law
My blog
My TechNet articles