Need help re-organizing query - if possible - so it is more efficient

  • I have the following query and was wondering if anyone can suggest a way to re-organize it so it is more efficient. My T-SQL skills need work... 😉

    The table has about 2.5 million rows, and there are no indexes on it, save for the PK defined on an identity column (not part of the query). I have identified a couple of indexes that would address the present query, but my objective here is more one of tuning the query itself, ie. re-writing it so it runs faster (if there is a way of doing so).

    Anyway, here it is. Any suggestions would be much appreciated:

    SELECT

    D.COID

    , MIN( D.COTID )

    FROM

    dbo.tblName AS D

    LEFT JOIN (

    SELECT

    COID

    FROM

    dbo.tblName

    WHERE

    CCBID = 100000 AND

    MCT = 'Y'

    GROUP BY

    COID

    ) AS D2

    ON D.COID= D2.COID

    WHERE

    D.CCBID = 100000 AND

    D2.COID IS NULL AND

    D.COID IS NOT NULL

    GROUP BY

    D.COID

    Here is the cardinality of the columns in the query (no. of rows commented out on the right):

    SELECT COUNT(*) FROM tblName --2402039

    SELECT COUNT(DISTINCT (COID)) FROM tblName --88589

    SELECT COUNT(DISTINCT (COTID)) FROM tblName --93500

    SELECT COUNT(DISTINCT (CCBID)) FROM tblName --136

    SELECT COUNT(DISTINCT (MCT)) FROM tblName --2

    There is only one index defined on the table; it is the PK, clustered, on the identity column.

    Any ideas?

    __________________________________________________________________________________
    SQL Server 2016 Columnstore Index Enhancements - System Views for Disk-Based Tables[/url]
    Persisting SQL Server Index-Usage Statistics with MERGE[/url]
    Turbocharge Your Database Maintenance With Service Broker: Part 2[/url]

  • First thing I see, do you reall need to do a left join to that sub-select. If I'm seeing the logic correctly, it's looking for any of the values that have MCT = 'Y' and then eliminating them. Why not simply have that in the WHERE statement of the main query and eliminate the sub-select entirely. That should cut the query execution cost in half.

    If COID is the primary key, then this statement, D.COID IS NOT NULL, is redundant. You can't have a null primary key.

    After that, I'd need to see the execution plan.

    "The credit belongs to the man who is actually in the arena, whose face is marred by dust and sweat and blood"
    - Theodore Roosevelt

    Author of:
    SQL Server Execution Plans
    SQL Server Query Performance Tuning

  • Try this

    SELECTcoID,

    MIN(cotID)

    FROMdbo.tblName

    WHEREccbID = 100000

    GROUP BYcoID

    HAVINGMAX(CASE WHEN mcT = 'Y' THEN 1 ELSE 0 END) = 0

    Also add a covering index on the table like this

    CREATE NONCLUSTERED INDEX IX_tblName ON dbo.tblName (ccbID, coID, cotID, mcT)


    N 56°04'39.16"
    E 12°55'05.25"

  • Grant, it is not enough with a WHERE clause.

    If the LEFT JOIN find a coID with any of it's record set to mcT = 'Y' the complete coID should be omitted, not just that particular record.


    N 56°04'39.16"
    E 12°55'05.25"

  • Grant Fritchey (9/4/2008)


    First thing I see, do you reall need to do a left join to that sub-select. If I'm seeing the logic correctly, it's looking for any of the values that have MCT = 'Y' and then eliminating them. Why not simply have that in the WHERE statement of the main query and eliminate the sub-select entirely. That should cut the query execution cost in half.

    If COID is the primary key, then this statement, D.COID IS NOT NULL, is redundant. You can't have a null primary key.

    After that, I'd need to see the execution plan.

    Thanks for the suggestion, I actually thought along the same lines yesterday, after I posted this. It's always good to get an independent confirmation though.

    The COID is not the primary key, so we are OK there.

    Here is what the query looks like after the changes:

    SELECT

    D.COID

    ,MIN( D.COTID )

    FROM

    dbo.tblName AS D

    LEFT JOIN

    dbo.tblName AS D2

    ON

    D.COID = D2.COID

    WHERE

    D.CCBID = 100000 AND

    D.CCBID = D2.CCBID AND

    D2.COID IS NULL AND

    D.COID IS NOT NULL AND

    D2.MCT = 'Y'

    GROUP BY

    D.COID

    I will look at the execution plans next and send an update...

    __________________________________________________________________________________
    SQL Server 2016 Columnstore Index Enhancements - System Views for Disk-Based Tables[/url]
    Persisting SQL Server Index-Usage Statistics with MERGE[/url]
    Turbocharge Your Database Maintenance With Service Broker: Part 2[/url]

  • You were using the LEFT JOIN to the derived table to test for existence of at least 1 record for a COID.

    Try using NOT EXISTS ?

    SELECT

    D.COID

    , MIN( D.COTID )

    FROM

    dbo.tblName AS D

    WHERE D.CCBID = 100000

    AND D.COID IS NOT NULL

    AND NOT EXISTS (

    SELECT *

    FROM dbo.tblName AS D2

    WHERE D2.CCBID = 100000

    AND D2.MCT = 'Y'

    AND D2.COID = D.COID

    )

    GROUP BY

    D.COID

  • Marios Philippopoulos (9/4/2008)


    SELECT

    D.COID

    ,MIN( D.COTID )

    FROM

    dbo.tblName AS D

    LEFT JOIN

    dbo.tblName AS D2

    ON

    D.COID = D2.COID

    WHERE

    D.CCBID = 100000 AND

    D.CCBID = D2.CCBID AND

    D2.COID IS NULL AND

    D.COID IS NOT NULL AND

    D2.MCT = 'Y'

    GROUP BY

    D.COID

    This will NOT produce the same result!

    You have a D2 in the WHERE clause, which makes the LEFT JOIN reduntant and act like an INNER JOIN instead.

    And you only omit records that have mct = 'Y'.

    Other records for same coID are still fetched.

    Test my suggestion posted earlier.


    N 56°04'39.16"
    E 12°55'05.25"

  • Peso (9/4/2008)


    Try this

    SELECTcoID,

    MIN(cotID)

    FROMdbo.tblName

    WHEREccbID = 100000

    AND coID IS NOT NULL

    GROUP BYcoID

    HAVINGMAX(CASE WHEN mcT = 'Y' THEN 1 ELSE 0 END) = 0


    N 56°04'39.16"
    E 12°55'05.25"

  • Peso (9/4/2008)


    Marios Philippopoulos (9/4/2008)


    SELECT

    D.COID

    ,MIN( D.COTID )

    FROM

    dbo.tblName AS D

    LEFT JOIN

    dbo.tblName AS D2

    ON

    D.COID = D2.COID

    WHERE

    D.CCBID = 100000 AND

    D.CCBID = D2.CCBID AND

    D2.COID IS NULL AND

    D.COID IS NOT NULL AND

    D2.MCT = 'Y'

    GROUP BY

    D.COID

    This will NOT produce the same result!

    You have a D2 in the WHERE clause, which makes the LEFT JOIN reduntant and act like an INNER JOIN instead.

    And you only omit records that have mct = 'Y'.

    Other records for same coID are still fetched.

    Test my suggestion posted earlier.

    Oops, thank you, that explains the weird execution plan I got! 🙂

    I will try your suggestion as soon as I finish something boring that has just come up, called work...

    Back shortly.

    __________________________________________________________________________________
    SQL Server 2016 Columnstore Index Enhancements - System Views for Disk-Based Tables[/url]
    Persisting SQL Server Index-Usage Statistics with MERGE[/url]
    Turbocharge Your Database Maintenance With Service Broker: Part 2[/url]

  • Grant Fritchey (9/4/2008)


    First thing I see, do you reall need to do a left join to that sub-select. If I'm seeing the logic correctly, it's looking for any of the values that have MCT = 'Y' and then eliminating them. Why not simply have that in the WHERE statement of the main query and eliminate the sub-select entirely. That should cut the query execution cost in half.

    If COID is the primary key, then this statement, D.COID IS NOT NULL, is redundant. You can't have a null primary key.

    After that, I'd need to see the execution plan.

    I can't post the exec plan as XML. It shows up as blank on the screen once posted.

    Is there another way?

    __________________________________________________________________________________
    SQL Server 2016 Columnstore Index Enhancements - System Views for Disk-Based Tables[/url]
    Persisting SQL Server Index-Usage Statistics with MERGE[/url]
    Turbocharge Your Database Maintenance With Service Broker: Part 2[/url]

  • Sure. Zip it up and attach it.

    "The credit belongs to the man who is actually in the arena, whose face is marred by dust and sweat and blood"
    - Theodore Roosevelt

    Author of:
    SQL Server Execution Plans
    SQL Server Query Performance Tuning

  • Save it to disk, zip it, and upload the zip file.

    😎

  • Here is the plan of the original query (pls see attached).

    I will now try the suggested modified query and will post the plan shortly.

    __________________________________________________________________________________
    SQL Server 2016 Columnstore Index Enhancements - System Views for Disk-Based Tables[/url]
    Persisting SQL Server Index-Usage Statistics with MERGE[/url]
    Turbocharge Your Database Maintenance With Service Broker: Part 2[/url]

  • It's just dawned on me that the original query makes no sense!

    The D and D2 tables are joined (left join) on the COID column, but then the filter "D2.CORPOID IS NULL AND D.CORPOID IS NOT NULL" negates the join! The query will never return any rows!

    I need to talk to the developer to understand what they wanted to get out of this query.

    Thanks all for your input!

    Here is the original query again:

    SELECT

    D.COID

    , MIN( D.COTID )

    FROM

    dbo.tblName AS D

    LEFT JOIN (

    SELECT

    COID

    FROM

    dbo.tblName

    WHERE

    CCBID = 100000 AND

    MCT = 'Y'

    GROUP BY

    COID

    ) AS D2

    ON D.COID= D2.COID

    WHERE

    D.CCBID = 100000 AND

    D2.COID IS NULL AND

    D.COID IS NOT NULL

    GROUP BY

    D.COID

    __________________________________________________________________________________
    SQL Server 2016 Columnstore Index Enhancements - System Views for Disk-Based Tables[/url]
    Persisting SQL Server Index-Usage Statistics with MERGE[/url]
    Turbocharge Your Database Maintenance With Service Broker: Part 2[/url]

  • >>but then the filter "D2.CORPOID IS NULL AND D.CORPOID IS NOT NULL" negates the join! The query will never return any rows!

    Nope, that's not true in this case.

    The check on D2.CORPOID IS NULL is how the query is implementing "NOT EXISTS" functionality.

    When you want to perform "NOT EXISTS" in SQL there are several options, here are 3 of them:

    1. NOT EXISTS ( subquery )

    2. NOT IN ( subquery )

    3. LEFT JOIN Table2 and check NULL on 1 of the columns in Table2

    The query is written to use method #3. The condition D2.CORPOID IS NULL evaluates to True in cases where the LEFT JOIN does not join to any matching rows.

Viewing 15 posts - 1 through 15 (of 35 total)

You must be logged in to reply to this topic. Login to reply