How to enable index seex with this case

  • Table

    CREATE TABLE [dbo].[A](

    [K] [int] NOT NULL,

    [V] [varchar](50) NOT NULL,

    CONSTRAINT [PK_A] PRIMARY KEY CLUSTERED

    (

    [K] ASC,

    [V] ASC

    )

    SQL

    DECLARE @a int = 1 --or NULL,...

    SELECT * FROM A WHERE @a is null or A.K = @a

    I can't enable index seek in my SQL. How can I enable it?

  • Are you saying that the execution plan shows a Clustered Index Scan rather than a Clustered Index Seek?

    This is a common problem with catch all queries.

    Even though this parameter is static throughout the query, the IS NULL condition is evaluated for each row and as such the index can not satisfy this.

    There's some good articles here with a few options.:

    http://blogs.msdn.com/bartd/archive/2009/05/03/sometimes-the-simplest-solution-isn-t-the-best-solution-the-all-in-one-search-query.aspx

    http://sqlinthewild.co.za/index.php/2009/03/19/catch-all-queries/

    None of them are without problems in my opinion. It's something SQL Server could optimise better internally when static parameters are being evaluated.

    With all of the methods, you have to be careful about the plans that will be cached in the stored procedure as there needs to be wildly different plans for returning 1 row compared to returning the entire table.

  • HowardW (3/3/2010)


    It's something SQL Server could optimise better internally when static parameters are being evaluated.

    How?

    There's one fundamental rule that the optimiser has to follow. Plans must be safe for reuse. Remember those execution plans are cached and reused and any matching query will use the cached execution plan.

    Consider if the first execution was with a non-null parameter and SQL generated an execution plan that involved an index seek, then the second execution the parameter was null. How would that exec plan be usable?

    Gail Shaw
    Microsoft Certified Master: SQL Server, MVP, M.Sc (Comp Sci)
    SQL In The Wild: Discussions on DB performance with occasional diversions into recoverability

    We walk in the dark places no others will enter
    We stand on the bridge and no one may pass
  • I don't disagree that it's difficult given SQL's architecture, but in this case (and it's not an unusual case) you're better off if plans aren't reused at all.

    It was a throw away comment really and I didn't phrase it very well, but it's not impossible for the optimiser to have a special case for search condition=variable or variable is NULL

  • HowardW (3/3/2010)


    I don't disagree that it's difficult given SQL's architecture, but in this case (and it's not an unusual case) you're better off if plans aren't reused at all.

    It was a throw away comment really and I didn't phrase it very well, but it's not impossible for the optimiser to have a special case for search condition=variable or variable is NULL

    It does on SQL 2008.

    If you specify OPTION(RECOMPILE) then the safe plan rule is ignored and optimal exec plans are generated for this form of query. It's not on all the builds of 2008, a bug was found early that caused the wrong plan to be used when two queries of the same form ran at the same time. I can't recall what build the feature was removed in and what, if any, it was put back.

    Gail Shaw
    Microsoft Certified Master: SQL Server, MVP, M.Sc (Comp Sci)
    SQL In The Wild: Discussions on DB performance with occasional diversions into recoverability

    We walk in the dark places no others will enter
    We stand on the bridge and no one may pass
  • Thanks Gail, good to know. FYI, I tried this scenario in my local 2008 SP1 instance and it doesn't appear to change the plan that's produced compared to 2k5

  • Yeah, that's due to the incorrect results bug I mentioned. The functionality was removed quite early, pre SP1. I have demoed this on 2008 RTM and there's a very obvious difference in plans just by adding the recompile hint. Pity about the bug, I honestly don't recall exactly what build it was remove in and when (if) it got fixed.

    Gail Shaw
    Microsoft Certified Master: SQL Server, MVP, M.Sc (Comp Sci)
    SQL In The Wild: Discussions on DB performance with occasional diversions into recoverability

    We walk in the dark places no others will enter
    We stand on the bridge and no one may pass
  • GilaMonster (3/3/2010)


    Yeah, that's due to the incorrect results bug I mentioned. The functionality was removed quite early, pre SP1. I have demoed this on 2008 RTM and there's a very obvious difference in plans just by adding the recompile hint. Pity about the bug, I honestly don't recall when (if) it got fixed.

    The fix was to revert to 2005 behaviour - a real fix is in the 'too hard' basket I think.

    All fixed from SP1 Cumulative Update 5.

    See http://www.sommarskog.se/dyn-search-2008.html#SPandCUs

  • The article you quoted seems to suggest that CU5 includes a 'real' fix:

    http://support.microsoft.com/kb/976603/

    Microsoft have now released this fix which is a thorough solution to this problem, which resolves the original incorrect results problem, but also allows the use of the enhanced functionality of the OPTION RECOMPILE syntax introduced in SQL Server 2008.

    Which sounds like good news to me and it should be in SP2

  • HowardW (3/3/2010)


    The article you quoted seems to suggest that CU5 includes a 'real' fix:

    So it does. Never noticed that before. Good news!

  • That's good to know.

  • nguyennd (3/2/2010)


    Table

    CREATE TABLE [dbo].[A](

    [K] [int] NOT NULL,

    [V] [varchar](50) NOT NULL,

    CONSTRAINT [PK_A] PRIMARY KEY CLUSTERED

    (

    [K] ASC,

    [V] ASC

    )

    SQL

    DECLARE @a int = 1 --or NULL,...

    SELECT * FROM A WHERE @a is null or A.K = @a

    I can't enable index seek in my SQL. How can I enable it?

    You have to break it up, then conditionally execute the proper code. Here's an example:

    CREATE PROCEDURE dbo.MyProcNoParam AS

    select * from A

    GO

    CREATE PROCEDURE dbo.MyProcWithParam (

    @a int)

    AS

    select * from A where K = @a

    GO

    CREATE PROCEDURE dbo.MyMainProc (

    @a int = NULL)

    AS

    if @a IS NULL execute dbo.MyProcNoParam

    else execute dbo.MyProcWithParam @a

    GO

    -- finally, run it like this

    declare @a int

    -- set @a = 1

    execute dbo.MyMainProc @a

    Wayne
    Microsoft Certified Master: SQL Server 2008
    Author - SQL Server T-SQL Recipes


    If you can't explain to another person how the code that you're copying from the internet works, then DON'T USE IT on a production system! After all, you will be the one supporting it!
    Links:
    For better assistance in answering your questions
    Performance Problems
    Common date/time routines
    Understanding and Using APPLY Part 1 & Part 2

  • Just to demonstrate the advantage of OPTION (RECOMPILE):

    Note: SQL Server 2008 SP1 CU5 (build 10.00.2746) or later required

    USE tempdb;

    GO

    -- Test table

    CREATE TABLE [dbo].[A]

    (

    [K] [int] NOT NULL,

    [V] [varchar](50) NOT NULL,

    CONSTRAINT [PK_A] PRIMARY KEY CLUSTERED ([K] ASC, [V] ASC)

    )

    -- Add 10,000 rows of data

    INSERT dbo.A WITH (TABLOCK)

    (K, V)

    SELECT Numbers.n,

    Data.V

    FROM (

    SELECT TOP (10000)

    ROW_NUMBER() OVER (

    ORDER BY (SELECT 0))

    FROM master.sys.columns C1,

    master.sys.columns C2,

    master.sys.columns C3

    ) Numbers (n)

    CROSS

    APPLY (

    SELECT REPLICATE(

    CHAR(Numbers.n % 26 + 65)

    , Numbers.n % 50 + 1)

    ) Data (V);

    -- =====

    -- TESTS

    -- =====

    DECLARE @a INTEGER;

    SET @a = 1;

    -- Index seek

    SELECT A.K,

    A.V

    FROM A

    WHERE @a IS NULL

    OR A.K = @a

    ORDER BY

    A.K ASC

    OPTION (RECOMPILE);

    GO

    DECLARE @a INTEGER;

    SET @a = NULL;

    -- Index scan

    SELECT A.K,

    A.V

    FROM A

    WHERE @a IS NULL

    OR A.K = @a

    ORDER BY

    A.K ASC

    OPTION (RECOMPILE);

    GO

    -- Index seek

    DECLARE @a INTEGER;

    SET @a = 6543;

    SELECT A.K,

    A.V

    FROM A

    WHERE @a IS NULL

    OR A.K = @a

    ORDER BY

    A.K ASC

    OPTION (RECOMPILE);

    GO

    DROP TABLE dbo.A;

    Paul

  • As an alternative for those not using SQL Server 2008 SP1 CU5 or working with SQL Server 2005, you can duplicate the query each with a different condition and UNION ALL the results together, i.e.

    SELECT

    A.K, A.V

    FROM

    A

    WHERE

    @a IS NULL

    UNION ALL

    SELECT

    A.K, A.V

    FROM

    A

    WHERE

    @a IS NOT NULL AND A.K = @a

    ORDER BY

    A.K ASC

    This results in a query plan with a Clustered Index Scan and a Clustered Index Seek which are merged together. Notice that in SQL Server 2008 these operands have an (actual) Number of Executions property. Depending on the value of @a either one will be 0 or 1. Ok, it's a little bit ugly to duplicate the query and it only works for a single parameter but it works in SQL Server 2005 as well. Although there's only a property Estimated Number of Executions (which are both 1) you can see it works by the Actual Number of Rows property, the line thickness in the execution plan and the number of logical reads.

    Peter

  • Peter Brinkhaus (3/4/2010)


    As an alternative for those not using SQL Server 2008 SP1 CU5 or working with SQL Server 2005, you can duplicate the query each with a different condition and UNION ALL the results together, i.e.

    SELECT

    A.K, A.V

    FROM

    A

    WHERE

    @a IS NULL

    UNION ALL

    SELECT

    A.K, A.V

    FROM

    A

    WHERE

    @a IS NOT NULL AND A.K = @a

    ORDER BY

    A.K ASC

    This results in a query plan with a Clustered Index Scan and a Clustered Index Seek which are merged together. Notice that in SQL Server 2008 these operands have an (actual) Number of Executions property. Depending on the value of @a either one will be 0 or 1. Ok, it's a little bit ugly to duplicate the query and it only works for a single parameter but it works in SQL Server 2005 as well. Although there's only a property Estimated Number of Executions (which are both 1) you can see it works by the Actual Number of Rows property, the line thickness in the execution plan and the number of logical reads.

    Peter

    A better evidence that only one branch of the execution plan is executed is the occurrence of a special Filter operand with a Startup Expression Predicate. If the startup expression evaluates to false then the branch is not executed at all. I cannot find anything about it in BOL but it seems to work that way (see http://www.fotia.co.uk/fotia/Blog/AllFiredUp...html).

    Peter

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

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