Bisakah saya menambahkan batasan unik yang mengabaikan pelanggaran yang ada?


40

Saya memiliki tabel yang saat ini memiliki nilai duplikat di kolom.

Saya tidak dapat menghapus duplikat yang salah ini tetapi saya ingin mencegah nilai-nilai non-unik tambahan ditambahkan.

Bisakah saya membuat UNIQUEyang tidak memeriksa kepatuhan yang ada?

Saya sudah mencoba menggunakan NOCHECKtetapi tidak berhasil.

Dalam hal ini saya memiliki tabel yang mengaitkan informasi perizinan dengan "Nama Perusahaan"

EDIT: Memiliki beberapa baris dengan "CompanyName" yang sama adalah data yang buruk, tetapi kami tidak dapat menghapus atau memperbarui duplikat tersebut saat ini. Salah satu pendekatan adalah untuk memiliki INSERTpenggunaan prosedur tersimpan yang akan gagal untuk duplikat ... Jika mungkin memiliki SQL memeriksa keunikan sendiri, itu akan lebih baik.

Data ini dipertanyakan dengan nama perusahaan. Untuk beberapa duplikat yang ada, ini berarti bahwa beberapa baris dikembalikan dan ditampilkan ... Meskipun ini salah, ini dapat diterima dalam kasus penggunaan kami. Tujuannya adalah untuk mencegahnya di masa depan. Sepertinya saya dari komentar bahwa saya harus melakukan logika ini dalam prosedur yang tersimpan.


Apakah Anda diizinkan mengubah tabel (tambahkan satu kolom lagi)?
ypercubeᵀᴹ

@ypercube sayangnya tidak.
Matius

Jawaban:


33

Jawabannya iya". Anda dapat melakukan ini dengan indeks yang difilter (lihat di sini untuk dokumentasi).

Misalnya, Anda dapat melakukan:

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

Ini menciptakan indeks unik, hanya pada baris baru , bukan pada baris lama. Formulasi khusus ini akan memungkinkan duplikat dengan nilai yang ada.

Jika Anda hanya memiliki beberapa duplikat, Anda dapat melakukan sesuatu seperti:

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

2
Apakah itu bagus atau tidak, akan tergantung pada apakah barang "lama" yang ada harus mencegah pembuatan barang baru dengan nilai yang sama.
supercat

1
@supercat. . . Saya memberikan formulasi alternatif untuk membangun indeks pada segala sesuatu kecuali nilai duplikat yang ada.
Gordon Linoff

1
Agar yang terakhir berfungsi, orang harus memastikan bahwa satu dihilangkan dari daftar satu id untuk setiap nilai kunci berbeda yang memiliki duplikat, dan juga harus memastikan bahwa jika item yang sengaja dihilangkan dari daftar dihilangkan dari tabel , item dengan kunci yang sama akan dihapus dari daftar.
supercat

@supercat. . . Saya setuju. Menjaga agar indeks konsisten untuk pembaruan dan penghapusan jauh lebih menantang karena Anda tidak dapat membuat kembali indeks dalam pemicu. Bagaimanapun, saya mendapat kesan dari OP bahwa data - atau setidaknya duplikat - tidak sering berubah, jika sama sekali.
Gordon Linoff

Mengapa tidak mengecualikan daftar nilai alih-alih daftar ID? Maka Anda tidak perlu mengecualikan satu ID per nilai duplikat dari daftar ID yang dikecualikan
JMD Coalesce

23

Ya, Anda bisa melakukannya.

Ini adalah tabel dengan duplikat:

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

Mari kita abaikan yang sudah ada, dan pastikan tidak ada duplikat baru yang dapat ditambahkan:

-- 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

Mari kita coba solusi ini:

-- 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
Kecuali dia tidak bisa menambahkan kolom ke tabel.
Aaron Bertrand

3
Saya suka bagaimana jawaban ini mengubah cara nilai NULL diperlakukan dengan cara yang tidak standar dalam batasan unik menjadi sesuatu yang bermanfaat. Trik licik.
ypercubeᵀᴹ

@ ypercubeᵀᴹ, dapatkah Anda menjelaskan apa yang tidak standar tentang penanganan NULL dalam batasan unik? Apa bedanya dengan apa yang Anda harapkan? Terima kasih!
Noach

1
@Noach di SQL Server, UNIQUEkendala dalam kolom nullable memastikan bahwa paling banyak NULLnilai tunggal . Standar SQL (dan hampir semua DBMS SQL lainnya) mengatakan bahwa ia harus mengizinkan sejumlah NULLnilai (yaitu batasannya harus mengabaikan nilai nol).
ypercubeᵀᴹ

@ ypercubeᵀᴹ Jadi untuk mengimplementasikan ini pada DBMS yang berbeda, kita hanya perlu menggunakan DEFAULT 0 daripada NULL. Benar?
Noach

16

Indeks unik yang difilter adalah ide yang brilian tetapi memiliki sedikit kerugian - tidak peduli apakah Anda menggunakan WHERE identity_column > <current value>kondisi atau WHERE identity_column NOT IN (<list of ids for duplicate values here>).

Dengan pendekatan pertama, Anda masih dapat memasukkan data duplikat di masa depan, duplikat data yang ada (sekarang). Misalnya, jika Anda memiliki (bahkan hanya satu) baris sekarang CompanyName = 'Software Inc.', indeks tidak akan melarang penyisipan satu baris lagi dengan nama perusahaan yang sama. Itu hanya akan melarangnya jika Anda mencoba dua kali.

Dengan pendekatan kedua ada peningkatan, hal di atas tidak akan bekerja (yang baik.) Namun, Anda masih dapat memasukkan lebih banyak duplikat atau duplikat yang ada. Misalnya, jika Anda memiliki (dua atau lebih) baris dengan sekarang CompanyName = 'DoubleData Co.', indeks tidak akan melarang penyisipan satu baris lagi dengan nama perusahaan yang sama. Itu hanya akan melarangnya jika Anda mencoba dua kali.

(Pembaruan) Ini dapat diperbaiki jika untuk setiap nama duplikat, Anda tetap keluar dari daftar pengecualian satu id. Jika, seperti contoh di atas, ada 4 baris dengan duplikat CompanyName = DoubleData Co.dan ID 4,6,8,9, daftar pengecualian harus hanya memiliki 3 ID ini.

Dengan pendekatan kedua kelemahan lain adalah kondisi rumit (berapa rumit tergantung pada berapa banyak duplikat ada di tempat pertama), karena SQL-Server tampaknya tidak mendukung NOT INoperator di WHEREbagian indeks yang difilter. Lihat SQL-Fiddle . Alih-alih WHERE (CompanyID NOT IN (3,7,4,6,8,9)), Anda harus memiliki sesuatu seperti WHERE (CompanyID <> 3 AND CompanyID <> 7 AND CompanyID <> 4 AND CompanyID <> 6 AND CompanyID <> 8 AND CompanyID <> 9)saya tidak yakin apakah ada implikasi efisiensi dengan kondisi seperti itu, jika Anda memiliki ratusan nama rangkap.


Solusi lain (mirip dengan @Alex Kuznetsov) adalah menambahkan kolom lain, mengisinya dengan nomor peringkat dan menambahkan indeks unik termasuk kolom ini:

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) ; 

Kemudian, memasukkan baris dengan nama duplikat akan gagal karena DEFAULT 1properti dan indeks unik. Ini masih tidak 100% sangat mudah (sementara Alex adalah). Duplikat akan tetap masuk jika Rnsecara eksplisit diatur dalam INSERTpernyataan atau jika Rnnilainya diperbarui secara jahat.

SQL-Fiddle-2


-2

Alternatif lain adalah menulis fungsi skalar yang memeriksa apakah suatu nilai sudah ada dalam tabel dan kemudian memanggil fungsi itu dari batasan cek.

Ini akan melakukan hal-hal mengerikan untuk kinerja.



Selain masalah yang ditunjukkan oleh Aaron, jawabannya tidak menjelaskan bagaimana kendala pemeriksaan ini dapat ditambahkan sehingga mengabaikan duplikat yang ada.
ypercubeᵀᴹ

-2

Saya mencari yang sama - buat indeks unik yang tidak bisa dipercaya sehingga data buruk yang ada diabaikan, tetapi catatan baru tidak dapat duplikat dari apa pun yang sudah ada.

Saat membaca utas ini, saya sadar bahwa solusi yang lebih baik adalah menulis pemicu yang akan memeriksa [dimasukkan] pada tabel induk untuk duplikat, dan jika ada duplikat di antara tabel tersebut, ROLLBACK TRAN.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.