Tôi có thể thêm một ràng buộc duy nhất mà bỏ qua các vi phạm hiện có không?


40

Tôi có một bảng hiện có các giá trị trùng lặp trong một cột.

Tôi không thể loại bỏ các bản sao sai sót này nhưng tôi muốn ngăn các giá trị không duy nhất bổ sung được thêm vào.

Tôi có thể tạo một UNIQUEcái mà không kiểm tra sự tuân thủ hiện có không?

Tôi đã thử sử dụng NOCHECKnhưng không thành công.

Trong trường hợp này, tôi có một bảng liên kết thông tin cấp phép với "CompanyName"

EDIT: Có nhiều hàng có cùng "CompanyName" là dữ liệu xấu, nhưng chúng tôi không thể xóa hoặc cập nhật các bản sao đó vào lúc này. Một cách tiếp cận là INSERTsử dụng một thủ tục được lưu trữ sẽ thất bại cho các bản sao ... Nếu có thể có SQL, hãy tự mình kiểm tra tính duy nhất, điều đó sẽ tốt hơn.

Dữ liệu này được truy vấn bởi tên công ty. Đối với một vài bản sao hiện có, điều này có nghĩa là nhiều hàng được trả lại và hiển thị ... Trong khi điều này là sai, trong trường hợp sử dụng của chúng tôi có thể chấp nhận được. Mục tiêu là để ngăn chặn nó trong tương lai. Dường như với tôi từ các ý kiến ​​rằng tôi phải thực hiện logic này trong các thủ tục được lưu trữ.


Bạn có được phép thay đổi bảng (thêm một cột nữa không)?
ypercubeᵀᴹ

@ypercube tiếc là không.
Matthew

Câu trả lời:


33

Câu trả lời là "có". Bạn có thể làm điều này với một chỉ mục được lọc (xem ở đây để biết tài liệu).

Chẳng hạn, bạn có thể làm:

create unique index t_col on t(col) where id > 1000;

Điều này tạo ra một chỉ mục duy nhất, chỉ trên các hàng mới , thay vì trên các hàng cũ. Công thức đặc biệt này sẽ cho phép trùng lặp với các giá trị hiện có.

Nếu bạn chỉ có một số ít các bản sao, bạn có thể làm một cái gì đó như:

create unique index t_col on t(col) where id not in (<list of ids for duplicate values here>);

2
Việc điều đó có tốt hay không sẽ phụ thuộc vào việc các mặt hàng "cũ" có nên ngăn chặn việc tạo ra các mặt hàng mới có cùng giá trị hay không.
supercat

1
@siêu mèo . . . Tôi đã đưa ra một công thức thay thế để xây dựng chỉ mục trên tất cả mọi thứ trừ các giá trị trùng lặp hiện có.
Gordon Linoff

1
Để cái sau hoạt động, người ta sẽ phải đảm bảo rằng một cái được bỏ qua khỏi danh sách một id cho mỗi giá trị khóa riêng biệt có trùng lặp và cũng phải đảm bảo rằng nếu mục bị bỏ qua một cách có chủ ý khỏi danh sách đã bị xóa khỏi bảng , một mục có khóa bằng nhau sẽ bị xóa khỏi danh sách.
supercat

@siêu mèo . . . Tôi đồng ý. Giữ chỉ mục nhất quán cho các cập nhật và xóa tất cả khó khăn hơn vì bạn không thể tạo lại chỉ mục trong trình kích hoạt. Trong mọi trường hợp, tôi đã có ấn tượng từ OP rằng dữ liệu - hoặc ít nhất là các bản sao - không thay đổi thường xuyên, nếu có.
Gordon Linoff

Tại sao không loại trừ danh sách các giá trị thay vì danh sách ID? Sau đó, bạn không phải loại trừ một ID cho mỗi giá trị trùng lặp khỏi danh sách ID bị loại trừ
JMD Coalesce

23

Đúng, bạn có thể làm điều đó.

Đây là một bảng với các bản sao:

CREATE TABLE dbo.Party
  (
    ID INT NOT NULL
           IDENTITY ,
    CONSTRAINT PK_Party PRIMARY KEY ( ID ) ,
    Name VARCHAR(30) NOT NULL
  ) ;
GO

INSERT  INTO dbo.Party
        ( Name )
VALUES  ( 'Frodo Baggins' ),
        ( 'Luke Skywalker' ),
        ( 'Luke Skywalker' ),
        ( 'Harry Potter' ) ;
GO

Chúng ta hãy bỏ qua những cái hiện có và đảm bảo rằng không có bản sao mới nào có thể được thêm vào:

-- Add a new column to mark grandfathered duplicates.
ALTER TABLE dbo.Party ADD IgnoreThisDuplicate INT NULL ;
GO

-- The *first* instance will be left NULL.
-- *Secondary* instances will be set to their ID (a unique value).
UPDATE  dbo.Party
SET     IgnoreThisDuplicate = ID
FROM    dbo.Party AS my
WHERE   EXISTS ( SELECT *
                 FROM   dbo.Party AS other
                 WHERE  other.Name = my.Name
                        AND other.ID < my.ID ) ;
GO

-- This constraint is not strictly necessary.
-- It prevents granting further exemptions beyond the ones we made above.
ALTER TABLE dbo.Party WITH NOCHECK
ADD CONSTRAINT CHK_Party_NoNewExemptions 
CHECK(IgnoreThisDuplicate IS NULL);
GO

SELECT * FROM dbo.Party;
GO

-- **THIS** is our pseudo-unique constraint.
-- It works because the grandfathered duplicates have a unique value (== their ID).
-- Non-grandfathered records just have NULL, which is not unique.
CREATE UNIQUE INDEX UNQ_Party_UniqueNewNames ON dbo.Party(Name, IgnoreThisDuplicate);
GO

Hãy để chúng tôi kiểm tra giải pháp này:

-- cannot add a name that exists
INSERT  INTO dbo.Party
        ( Name )
VALUES  ( 'Frodo Baggins' );

Cannot insert duplicate key row in object 'dbo.Party' with unique index 'UNQ_Party_UniqueNewNames'.

-- cannot add a name that exists and has an ignored duplicate
INSERT  INTO dbo.Party
        ( Name )
VALUES  ( 'Luke Skywalker' );

Cannot insert duplicate key row in object 'dbo.Party' with unique index 'UNQ_Party_UniqueNewNames'.


-- can add a new name 
INSERT  INTO dbo.Party
        ( Name )
VALUES  ( 'Hamlet' );

-- but only once
INSERT  INTO dbo.Party
        ( Name )
VALUES  ( 'Hamlet' );

Cannot insert duplicate key row in object 'dbo.Party' with unique index 'UNQ_Party_UniqueNewNames'.

4
Ngoại trừ anh ta không thể thêm một cột vào bảng.
Aaron Bertrand

3
Tôi thích cách câu trả lời này biến cách các giá trị NULL được xử lý theo cách không chuẩn trong ràng buộc duy nhất thành một cái gì đó hữu ích. Thủ đoạn xảo quyệt.
ypercubeᵀᴹ

@ ypercubeᵀᴹ, bạn có thể giải thích điều gì không chuẩn về việc xử lý NULL trong các ràng buộc duy nhất không? Nó khác với những gì bạn mong đợi như thế nào? Cảm ơn!
Noach

1
@Noach trong SQL Server, một UNIQUEràng buộc trong một cột không thể đảm bảo rằng có nhiều nhất một NULLgiá trị. Tiêu chuẩn SQL (và hầu hết tất cả các DBMS SQL khác) nói rằng nó sẽ cho phép bất kỳ số lượng NULLgiá trị nào (tức là ràng buộc sẽ bỏ qua các giá trị null).
ypercubeᵀᴹ

@ ypercubeᵀᴹ Vì vậy, để thực hiện điều này trên một DBMS khác, chúng ta chỉ cần sử dụng DEFAULT 0 thay vì NULL. Chính xác?
Noach

16

Chỉ số duy nhất được lọc là một ý tưởng tuyệt vời nhưng nó có một nhược điểm nhỏ - bất kể bạn sử dụng WHERE identity_column > <current value>điều kiện hay WHERE identity_column NOT IN (<list of ids for duplicate values here>).

Với cách tiếp cận đầu tiên, bạn vẫn có thể chèn dữ liệu trùng lặp trong tương lai, trùng lặp dữ liệu hiện tại (hiện tại). Ví dụ: nếu bây giờ bạn có (thậm chí chỉ một) hàng CompanyName = 'Software Inc.', chỉ mục sẽ không cấm chèn thêm một hàng có cùng tên công ty. Nó sẽ chỉ cấm nó nếu bạn thử hai lần.

Với cách tiếp cận thứ hai có một cải tiến, cách trên sẽ không hoạt động (điều này tốt.) Tuy nhiên, bạn vẫn có thể chèn thêm các bản sao hoặc các bản sao hiện có. Ví dụ: nếu bây giờ bạn có (hai hoặc nhiều) hàng CompanyName = 'DoubleData Co.', chỉ mục sẽ không cấm chèn thêm một hàng có cùng tên công ty. Nó sẽ chỉ cấm nó nếu bạn thử hai lần.

(Cập nhật) Điều này có thể được sửa nếu với mỗi tên trùng lặp, bạn tránh xa danh sách loại trừ một id. Nếu, giống như ví dụ trên, có 4 hàng có trùng lặp CompanyName = DoubleData Co.và ID 4,6,8,9, danh sách loại trừ chỉ nên có 3 trong số các ID này.

Với cách tiếp cận thứ hai, một nhược điểm khác là điều kiện cồng kềnh (mức độ cồng kềnh phụ thuộc vào số lượng trùng lặp ở vị trí đầu tiên), vì SQL-Server dường như không hỗ trợ NOT INtoán tử trong WHEREphần chỉ mục được lọc. Xem SQL-Fiddle . Thay vì WHERE (CompanyID NOT IN (3,7,4,6,8,9)), bạn sẽ phải có một cái gì đó giống như WHERE (CompanyID <> 3 AND CompanyID <> 7 AND CompanyID <> 4 AND CompanyID <> 6 AND CompanyID <> 8 AND CompanyID <> 9)tôi không chắc có liên quan đến hiệu quả với điều kiện như vậy không, nếu bạn có hàng trăm tên trùng lặp.


Một giải pháp khác (tương tự như @Alex Kuznetsov) là thêm một cột khác, điền vào đó một số thứ hạng và thêm một chỉ mục duy nhất bao gồm cột này:

ALTER TABLE Company
  ADD Rn TINYINT DEFAULT 1;

UPDATE x
SET Rn = Rnk
FROM
  ( SELECT 
      CompanyID,
      Rn,
      Rnk = ROW_NUMBER() OVER (PARTITION BY CompanyName 
                               ORDER BY CompanyID)
    FROM Company 
  ) x ;

CREATE UNIQUE INDEX CompanyName_UQ 
  ON Company (CompanyName, Rn) ; 

Sau đó, chèn một hàng với tên trùng lặp sẽ thất bại vì thuộc DEFAULT 1tính và chỉ mục duy nhất. Điều này vẫn không thể đánh lừa 100% (trong khi Alex là). Các bản sao vẫn sẽ trượt vào nếu Rnđược đặt rõ ràng trong INSERTcâu lệnh hoặc nếu các Rngiá trị được cập nhật độc hại.

SQL-Fiddle-2


-2

Một cách khác là viết hàm vô hướng để kiểm tra nếu một giá trị đã tồn tại trong bảng và sau đó gọi hàm đó từ một ràng buộc kiểm tra.

Điều này sẽ làm những điều khủng khiếp để thực hiện.



Bên cạnh các vấn đề được chỉ ra bởi Aaron, câu trả lời không giải thích làm thế nào có thể thêm ràng buộc kiểm tra này để nó bỏ qua các bản sao hiện có.
ypercubeᵀᴹ

-2

Tôi đang tìm kiếm tương tự - tạo một chỉ mục duy nhất không đáng tin cậy để dữ liệu xấu hiện có bị bỏ qua, nhưng các bản ghi mới không thể là bản sao của bất kỳ thứ gì đã tồn tại.

Trong khi đọc chủ đề này, tôi nhận thấy một giải pháp tốt hơn là viết một trình kích hoạt sẽ kiểm tra [chèn] vào bảng cha để tìm các bản sao và nếu có bất kỳ bản sao nào tồn tại giữa các bảng đó, ROLLBACK TRAN.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.