Search

MERGE Incorrectly Reports Unique Key Violations by Paul White NZ

Active

13
0
Sign in
to vote
Type: Bug
ID: 773895
Opened: 12/9/2012 10:15:33 AM
Access Restriction: Public
0
Workaround(s)
4
User(s) can reproduce this bug
A MERGE statement can fail, and incorrectly report a unique key violation when:

* The target table uses a unique filtered index; and
* No key column of the filtered index is updated; and
* A column from the filtering condition is updated; and
* Transient key violations are possible

The optimizer incorrectly chooses a narrow update plan where a wide plan with split/sort/collapse is required to avoid transient nonclustered index key violations.

Forcing a wide update plan with TF 8670 (or by adding all columns referenced in the index filter predicates to the index *keys*) avoids this problem.

There is a reproduction script in the details section below, and a fuller discussion at http://sqlblog.com/blogs/paul_white/archive/2012/12/10/merge-bug-with-filtered-indexes.aspx
Details (expand)

Product Language

English

Version

SQL Server 2012 - Enterprise Edition

Category

SQL Engine

Operating System

Not Applicable

Operating System Language

Not Applicable

Steps to Reproduce

-- Merge target table
CREATE TABLE #Target
(
    pk         integer NOT NULL,
    ak         character(1) NOT NULL,
    status_code character(1) NOT NULL,

    PRIMARY KEY (pk)
);

-- Filtered unique index
CREATE UNIQUE INDEX uq1
ON #Target (ak)
INCLUDE (status_code)
WHERE status_code = 'a';

-- Changes to apply
CREATE TABLE #Changes
(
    pk         integer NOT NULL,
    status_code character(1) NOT NULL,

    PRIMARY KEY (pk)
);

-- Sample data
INSERT #Target
    (pk, ak, status_code)
VALUES
    (1, 'A', 'a'),
    (2, 'B', 'a'),
    (3, 'C', 'a'),
    (4, 'A', 'd');

-- Sample changes
INSERT #Changes
    (pk, status_code)
VALUES
    (1, 'd'),
    (4, 'a');

-- Succeeds
MERGE #Target AS t
USING #Changes AS c ON
    c.pk = t.pk
WHEN MATCHED
    AND c.status_code <> t.status_code
    THEN UPDATE SET status_code = c.status_code;

-- Reverse the changes
TRUNCATE TABLE #Changes;

INSERT #Changes
    (pk, status_code)
VALUES
    (1, 'a'),
    (4, 'd');

-- Fails with unique key violation
MERGE #Target AS t
USING #Changes AS c ON
    c.pk = t.pk
WHEN MATCHED
    AND c.status_code <> t.status_code
    THEN UPDATE SET status_code = c.status_code;

-- Equivalent UPDATE succeeds
BEGIN TRANSACTION;

UPDATE t
SET status_code = c.status_code
FROM #Target AS t
JOIN #Changes AS c ON
    t.pk = c.pk
WHERE
    c.status_code <> t.status_code;

-- Reset to test MERGE again
ROLLBACK TRANSACTION;

-- Merge succeeds with a *wide update plan*
-- Split/Filter/Sort/Collapse needed!
MERGE #Target AS t
USING #Changes AS c ON
    c.pk = t.pk
WHEN MATCHED
    AND c.status_code <> t.status_code
    THEN UPDATE SET status_code = c.status_code
OPTION (QUERYTRACEON 8790);

-- Tidy up
DROP TABLE #Changes, #Target;

Actual Results

Msg 2601, Level 14, State 1, Line 1
Cannot insert duplicate key row in object 'dbo.#Target' with unique index 'uq1'. The duplicate key value is (A).
The statement has been terminated.

Expected Results

Successful execution

Platform

X64

Virtualization

 
File Attachments
0 attachments
Sign in to post a comment.
Posted by Microsoft on 1/4/2013 at 4:41 PM
Hi Paul White NZ ,
    
thanks for taking the time to share your feedback, this is really important to us.
We will investigate the issue and get back to you.

Best regards
Jean-Yves Devant 

Program Manager Servicing and Lifecycle Experience of High Availability Technologies in SQL Server
Sign in to post a workaround.