Filter Condition Not Applied to Clustered Columnstore Index in SQL Server

azure-sql-databasecolumnstoresql server

Using the below example, the predicates are the same, however the top statement (correctly) returns 0 rows, the bottom statement returns 1 – even though the predicates do NOT match:

declare @barcode nchar(22)=N'RECB012ZUKI449M1VBJZ'  
declare @tableId int = null
declare @total decimal(10, 2) = 5.17

SELECT 1
FROM
    [dbo].[transaction] WITH (INDEX([IX_Transaction_TransactionID_PaymentStatus_DeviceID_DateTime_All]))
WHERE
    Barcode = @barcode
    AND StatusID = 1
    AND TableID = @tableID
    AND @total <= Total

SELECT 1
FROM
    [dbo].[transaction] 
WHERE
    Barcode = @barcode
    AND StatusID = 1
    AND TableID = @tableID
    AND @total <= Total

Why could this be happening?

Further info:

  • The Non Clustered Index in the top statement is NOT filtered
  • CheckDB returns 0 issues
  • Server Version: Microsoft SQL Azure (RTM) - 12.0.2000.8 Dec 19 2018 08:43:17 Copyright (C) 2018 Microsoft Corporation

Paste the Plan link:

https://www.brentozar.com/pastetheplan/?id=S1w_rU68E

Further Info:

Have ran dbcc checktable ([transaction]) with all_errormsgs, extended_logical_checks, data_purity which indicates no issues.

I can reliably reproduce the issue against this table when restoring a backup of this database.

Best Answer

This bug does not require dropping or renaming columns.

You will also see the same behaviour for statusId = 100 which was never present in any version of the column.

Requirements

  • A clustered columnstore
  • Nonclustered b-tree index
  • A plan that performs a lookup on the columnstore with
    • Target row(s) in delta store
    • A pushed non-SARG predicate
    • A comparison with NULL using an equality test

Example

DROP TABLE IF EXISTS dbo.Example;
GO
CREATE TABLE dbo.Example
(
    c1 integer NOT NULL,
    c2 integer NULL,

    INDEX CCS CLUSTERED COLUMNSTORE,
    INDEX IX NONCLUSTERED (c1)
);
GO
INSERT dbo.Example
    (c1, c2)
VALUES
    (1, NULL);
GO
DECLARE @c2 integer = NULL;

-- Returns one row but should not
SELECT
    E.* 
FROM dbo.Example AS E 
    WITH (INDEX(IX))
WHERE
    E.c2 = @c2;

Any of the following will avoid the bug:

  • Moving rows out of the delta store using any method including reorganizing with the compress rowgroups option specified
  • Writing the predicate to explicitly reject = NULL
  • Enabling undocumented trace flag 9130 to avoid pushing the predicate into the lookup

db<>fiddle demo.


This bug was fixed in CU15 for SQL Server 2017 (and CU7 for SQL Server 2016 SP2):

FIX: Query against table with both clustered columnstore index and nonclustered rowstore index may return incorrect results in SQL Server 2016 and 2017