Чи можу я додати унікальне обмеження, яке ігнорує існуючі порушення?


40

У мене є таблиця, яка наразі має стовпці значень у стовпці.

Я не можу видалити ці помилкові дублікати, але я хотів би не допустити додавання додаткових унікальних значень.

Чи можу я створити UNIQUEте, що не перевіряє на відповідність?

Я спробував використовувати, NOCHECKале не вдався.

У цьому випадку у мене є таблиця, яка пов'язує інформацію про ліцензування з "CompanyName"

EDIT: Наявність декількох рядків із тим самим "CompanyName" - це погані дані, але ми не можемо видалити або оновити ці копії наразі. Один із підходів полягає в тому, INSERTщоб використовувати S збережену процедуру, яка не вдасться до дублікатів ... Якщо б SQL можна було перевірити унікальність самостійно, це було б краще.

Ці дані запитуються за назвою компанії. Для кількох існуючих дублікатів це буде означати, що кілька рядків повертаються та відображаються ... Хоча це неправильно, у нашому випадку це прийнятно. Мета - запобігти це в майбутньому. Мені здається, що з коментарів я маю виконувати цю логіку в збережених процедурах.


Чи дозволено вам змінити таблицю (додати ще один стовпець)?
ypercubeᵀᴹ

@ypercube, на жаль, ні.
Метью

Відповіді:


33

Відповідь «так». Це можна зробити за допомогою відфільтрованого індексу (див. Тут документацію).

Наприклад, ви можете:

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

Це створює унікальний індекс, лише на нових рядках, а не на старих рядках. Ця конкретна формулювання дозволить дублювати наявні значення.

Якщо у вас є лише кілька копій, ви можете зробити щось на кшталт:

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

2
Від того, чи це добре, буде залежати від того, чи повинні "старі" існуючі елементи перешкоджати створенню нових елементів з однаковим значенням.
supercat

1
@supercat. . . Я дав альтернативну формулювання для побудови індексу на всьому, крім існуючих повторюваних значень.
Гордон Лінофф

1
Щоб остання працювала, треба було б переконатися, що один випущений зі списку один ідентифікатор для кожного окремого ключового значення, що мав дублікати, і також повинен був би переконатися, що якщо елемент, який був навмисно опущений зі списку, буде видалений із таблиці , елемент із рівним ключем буде видалений зі списку.
supercat

@supercat. . . Я згоден. Підтримувати показник послідовним для оновлень та видалень стає ще більш складним завданням, оскільки ви не можете створити індекс за допомогою тригера. У будь-якому випадку у мене склалось враження, що дані - або, принаймні, дублікати - не змінюються часто, якщо взагалі.
Гордон Лінофф

Чому б не виключити список значень замість списку ідентифікаторів? Тоді вам не доведеться виключати один ідентифікатор на дублюване значення зі списку виключених ідентифікаторів
JMD Coalesce

23

Так, ви можете це зробити.

Ось таблиця з дублікатами:

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

Нехай ігнорують існуючі та гарантують, що нові дублікати не можуть бути додані:

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

Спробуємо це рішення:

-- 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
За винятком того, що він не може додати стовпчик до таблиці.
Аарон Бертран

3
Мені подобається, як ця відповідь перетворює, як значення NULL нестандартно трактуються з унікальним обмеженням у щось корисне. Хитрий трюк.
ypercubeᵀᴹ

@ ypercubeᵀᴹ, не могли б ви пояснити, що є нестандартним щодо обробки NULL в унікальних обмеженнях? Чим він відрізняється від того, що ви очікували? Дякую!
Noach

1
@Noach в SQL Server, UNIQUEобмеження в стовпчику, що зводиться нанівець, гарантує наявність максимум одного NULLзначення. Стандарт SQL (і майже всі інші SQL СУБД) говорять, що він повинен допускати будь-яку кількість NULLзначень (тобто обмеження має ігнорувати нульові значення).
ypercubeᵀᴹ

@ ypercubeᵀᴹ Отже, щоб реалізувати це в інших СУБД, нам просто потрібно використовувати DEFAULT 0, а не NULL. Правильно?
Noach

16

Фільтрований унікальний індекс - це геніальна ідея, але він має незначний недолік - незалежно від того, використовуєте ви WHERE identity_column > <current value>умову чи WHERE identity_column NOT IN (<list of ids for duplicate values here>).

При першому підході ви все одно зможете вставити дублікати даних у майбутньому, дублікати існуючих (зараз) даних. Наприклад, якщо у вас зараз (навіть лише один) рядок CompanyName = 'Software Inc.', індекс не забороняє вставляти ще один рядок з такою ж назвою компанії. Це заборонить, лише якщо спробувати двічі.

З другим підходом відбувається поліпшення, вищезгадане не вийде (що добре.) Однак ви все одно зможете вставити більше дублікатів або існуючих дублікатів. Наприклад, якщо у вас зараз (два або більше) рядків CompanyName = 'DoubleData Co.', індекс не забороняє вставляти ще один рядок з такою ж назвою компанії. Це заборонить, лише якщо спробувати двічі.

(Оновлення) Це можна виправити, якщо для кожного дублюючого імені ви зберігаєте зі списку виключень один ідентифікатор. Якщо, як у наведеному вище прикладі, є 4 рядки з дублікатами CompanyName = DoubleData Co.та ідентифікаторами 4,6,8,9, у списку виключень має бути лише 3 з цих ідентифікаторів.

При другому підході ще одним недоліком є ​​громіздка умова (наскільки громіздка залежить від того, скільки дублікатів є в першу чергу), оскільки SQL-Server, здається, не підтримує NOT INоператора в WHEREчастині відфільтрованих індексів. Див. SQL-Fiddle . Замість цього WHERE (CompanyID NOT IN (3,7,4,6,8,9))вам доведеться мати щось на зразок WHERE (CompanyID <> 3 AND CompanyID <> 7 AND CompanyID <> 4 AND CompanyID <> 6 AND CompanyID <> 8 AND CompanyID <> 9)я не впевнений, чи є наслідки для ефективності з такою умовою, якщо у вас є сотні дублюючих імен.


Інше рішення (подібне до @Alex Kuznetsov) - додати ще один стовпець, заповнити його ранговими номерами та додати унікальний індекс, що включає цей стовпець:

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

Тоді вставлення рядка з повторюваним іменем не вдасться через DEFAULT 1властивість та унікальний індекс. Це все ще не на 100% бездоганний (у той час як Алекс). Дублікати все одно проскакують, якщо Rnявно встановлено у INSERTвиписці або якщо Rnзначення зловмисно оновлюються.

SQL-Fiddle-2


-2

Іншою альтернативою є написання скалярної функції, яка перевіряє, чи існує значення вже в таблиці, а потім викликає цю функцію з обмеження перевірки.

Це зробить жахливі речі для виконання.



Окрім питань, на які звернув увагу Аарон, у відповіді не пояснено, як можна обмежити це обмеження, тому він ігнорує існуючі дублікати.
ypercubeᵀᴹ

-2

Я шукаю те саме - створіть непорушний унікальний індекс, щоб існуючі погані дані ігнорувалися, але нові записи не можуть бути дублікатами нічого, що вже існує.

Читаючи цей потік, мені здається, що кращим рішенням є написання тригера, який буде перевіряти [вставлений] проти батьківської таблиці на наявність дублікатів, і якщо між цими таблицями існують дублікати, ROLLBACK TRAN.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.