Search code examples
sql-serverquery-performancesql-execution-plan

Table Scan very high "actual rows" when filter placed on different table


I have a query, that I did not write, that takes 2.5 minutes to run. I am trying to optimize it without being able to modify the underlying tables, i.e. no new indexes can be added.

During my optimization troubleshooting I commented out a filter and all of a sudden my query ran in .5 seconds. I have screwed with the formatting and placing of that filter and if it is there the query takes 2.5 minutes, without it .5 seconds. The biggest problem is that the filter is not on the table that is being table-scanned (With over 300k records), it is on a table with 300 records.

The "Actual Execution Plan" of both the 0:0:0.5 vs 0:2:30 are identical down to the exact percentage costs of all steps:

Execution Plan

The only difference is that on the table-scanned table the "Actual Number of Rows" on the 2.5 min query shows 3.7 million rows. The table only has 300k rows. Where the .5 sec query shows Actual Number of Rows as 2,063. The filter is actually being placed on the FS_EDIPartner table that only has 300 rows.

With the filter I get the correct 51 records, but it takes 2.5 minutes to return. Without the filter I get duplication, so I get 2,796 rows, and only take half a second to return.

I cannot figure out why adding the filter to a table with 300 rows and a correct index is causing the Table scan of a different table to have such a significant difference in actual number of rows. I am even doing the "Table scan" table as a sub-query to filter its records down from 300k to 17k prior to doing the join. Here is the actual query in its current state, sorry the tables don't make a lot of sense, I could not reproduce this behavior in test data.

SELECT dbo.FS_ARInvoiceHeader.CustomerID
    , dbo.FS_EDIPartner.PartnerID
    , dbo.FS_ARInvoiceHeader.InvoiceNumber
    , dbo.FS_ARInvoiceHeader.InvoiceDate
    , dbo.FS_ARInvoiceHeader.InvoiceType
    , dbo.FS_ARInvoiceHeader.CONumber
    , dbo.FS_EDIPartner.InternalTransactionSetCode
    , docs.DocumentName
    , dbo.FS_ARInvoiceHeader.InvoiceStatus
FROM  dbo.FS_ARInvoiceHeader 
    INNER JOIN dbo.FS_EDIPartner ON dbo.FS_ARInvoiceHeader.CustomerID = dbo.FS_EDIPartner.CustomerID
    LEFT JOIN (Select DocumentName
                FROM GentranDatabase.dbo.ZNW_Documents
                WHERE DATEADD(SECOND,TimeCreated,'1970-1-1') > '2016-06-01'
                    AND TransactionSetID = '810') docs on  dbo.FS_ARInvoiceHeader.InvoiceNumber = docs.DocumentName COLLATE Latin1_General_BIN
WHERE docs.DocumentName IS NULL
    AND dbo.FS_ARInvoiceHeader.InvoiceType = 'I'
    AND dbo.FS_ARInvoiceHeader.InvoiceStatus <> 'Y'
    --AND (dbo.FS_EDIPartner.InternalTransactionSetCode = '810') 
    AND (NOT (dbo.FS_ARInvoiceHeader.CONumber LIKE 'CB%')) 
    AND (NOT (dbo.FS_ARInvoiceHeader.CONumber LIKE 'DM%')) 
    AND InvoiceDate > '2016-06-01'

The Commented out line in the Where statement is the culprit, uncommenting it causes the 2.5 minute run.


Solution

  • Using @jeremy's comment as a guideline to point out the Actual Number of Rows was not my problem, but instead the number of executions, I figured out that the Hash Match was .5 seconds, the Nested loop was 2.5 minutes. Trying to force the Hash Match using Left HASH Join was inconsistent depending on what the other filters were set to, changing dates took it from .5 seconds, to 30 secs sometimes. So forcing the Hash (Which is highly discouraged anyway) wasn't a good solution. Finally I resorted to moving the poor performing view to a Stored Procedure and splitting out both of the tables that were related to the poor performance into Table Variables, then joining those table variables. This resulted in the most consistently good performance of getting the results. On average the SP returns in less than 1 second, which is far better than the 2.5 minutes it started at.

    @Jeremy gets the credit, but since his wasn't an answer, I thought I would document what was actually done in case someone else stumbles across this later.