• While it's quite cool that there are CASTs that still allow seeks, in this case you would still want to look into avoiding the CAST.

    As discussed at http://dba.stackexchange.com/questions/34047/cast-to-date-is-sargable-but-is-it-a-good-idea, there are still things about that plan that are suboptimal.

    For one thing, the estimate for the number of rows coming out of the seek can be quite a bit more inaccurate when you do the CAST and force the dynamic seek pattern.

    The performance of the version with the CAST is also not quite as good, so I avoid it if at all possible.

    To illustrate:

    CREATE TABLE test_dates (some_datetime datetime);

    WITH n1(x) AS (SELECT 1 UNION ALL SELECT 1), -- 2 rows

    n2(x) AS (SELECT 1 FROM n1 a, n1 b), -- 4 rows

    n3(x) AS (SELECT 1 FROM n2 a, n2 b), -- 16 rows

    n4(x) AS (SELECT 1 FROM n3 a, n3 b), -- 256 rows

    n5(x) AS (SELECT 1 FROM n4 a, n4 b), -- 65536 rows

    tally(x) AS (SELECT TOP (10000) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM n5)

    INSERT INTO test_dates

    SELECT DATEADD(minute,x,'20130101')

    FROM tally;

    CREATE CLUSTERED INDEX CI_some_datetime ON test_dates (some_datetime);

    GO

    CREATE PROCEDURE test_no_cast

    AS

    DECLARE @datetime_bucket as datetime

    SELECT @datetime_bucket=some_datetime

    FROM test_dates

    WHERE some_datetime BETWEEN '20130101' AND '20130103';

    GO

    CREATE PROCEDURE test_sargable_cast

    AS

    DECLARE @datetime_bucket as datetime

    SELECT @datetime_bucket=some_datetime

    FROM test_dates

    WHERE CAST(some_datetime AS DATE) BETWEEN '20130101' AND '20130102';

    GO

    EXEC test_no_cast

    GO 1000

    EXEC test_sargable_cast

    GO 1000

    SELECT proc_name=object_name(object_id, database_id),

    total_logical_reads,

    total_worker_time,

    total_elapsed_time

    FROM sys.dm_exec_procedure_stats ps

    WHERE object_name(object_id, database_id) LIKE 'test%';

    DROP PROCEDURE test_no_cast, test_sargable_cast;

    DROP TABLE test_dates;

    On my instance the actual number of rows returned is 2880 for the non-CAST version and 2879 for the SARGable CAST (the two are not logically the same because the non-CAST version will return midnight of the upper boundary).

    The estimated rows for the non-CAST version is 2879.71, acceptably close 🙂

    The estimated rows for the SARGable CAST is 1439.86, rather off, especially if that under-estimate gets propagated through the rest of a more complex plan.

    Here are the performance results on my lab machine:

    proc_name total_logical_reads total_worker_time total_elapsed_time

    ------------------------------ -------------------- -------------------- --------------------

    test_sargable_cast 9000 994330 995330

    test_no_cast 9000 491606 492603

    While in absolute terms that's not a crushing difference for 1000 executions, it's a pretty significant percentage difference, and there's really no point conceding that performance just to use a CAST that happens to be able to use a seek, however nifty that might seem 🙂

    Especially when combined with the potential cardinality estimation problems, I'd avoid utilizing these SARGable CASTs unless there's a really good reason to use them.

    Cheers!