ฉันสามารถเพิ่มข้อ จำกัด ที่ไม่ซ้ำใครเพื่อละเว้นการละเมิดที่มีอยู่ได้หรือไม่?


39

ฉันมีตารางซึ่งขณะนี้มีค่าซ้ำกันในคอลัมน์

ฉันไม่สามารถลบรายการซ้ำที่ผิดพลาดเหล่านี้ได้ แต่ฉันต้องการป้องกันไม่ให้เพิ่มค่าที่ไม่ซ้ำกันเพิ่มเติม

ฉันสามารถสร้างสิ่งUNIQUEที่ไม่ตรวจสอบความสอดคล้องที่มีอยู่ได้หรือไม่

ฉันลองใช้NOCHECKแล้วแต่ไม่สำเร็จ

ในกรณีนี้ฉันมีตารางที่ผูกข้อมูลสิทธิ์การใช้งานกับ "CompanyName"

แก้ไข:การมีหลายแถวที่มี "CompanyName" เดียวกันนั้นเป็นข้อมูลที่ไม่ดี แต่เราไม่สามารถลบหรืออัปเดตข้อมูลซ้ำเหล่านั้นได้ในขณะนี้ วิธีหนึ่งคือการINSERTใช้ s ที่เก็บไว้ซึ่งจะล้มเหลวในการทำซ้ำ ... ถ้าเป็นไปได้ที่ SQL จะตรวจสอบความเป็นเอกลักษณ์ของตัวเองมันจะดีกว่า

ข้อมูลนี้ถูกสอบถามโดยชื่อ บริษัท สำหรับรายการซ้ำที่มีอยู่สองสามรายการนี้จะหมายความว่ามีการส่งคืนและแสดงแถวจำนวนมาก ... แม้ว่าจะเป็นความผิด แต่ก็เป็นที่ยอมรับในกรณีการใช้งานของเรา เป้าหมายคือการป้องกันในอนาคต ดูเหมือนว่าฉันจากความคิดเห็นที่ฉันต้องทำตรรกะนี้ในขั้นตอนการจัดเก็บ


คุณได้รับอนุญาตให้เปลี่ยนตาราง (เพิ่มอีกหนึ่งคอลัมน์)?
ypercubeᵀᴹ

@percube น่าเสียดายที่ไม่ใช่
Matthew

คำตอบ:


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 . . ฉันให้สูตรทางเลือกสำหรับการสร้างดัชนีในทุกสิ่งยกเว้นค่าที่ซ้ำกันที่มีอยู่
Gordon Linoff

1
เพื่อให้การทำงานเป็นไปอย่างต่อเนื่องเราจะต้องตรวจสอบให้แน่ใจว่ามีการละเว้นรายการหนึ่ง ID สำหรับแต่ละค่าคีย์ที่แตกต่างกันซึ่งมีการทำซ้ำและจะต้องตรวจสอบให้แน่ใจว่าหากรายการที่ละเว้นโดยเจตนาออกจากรายการ รายการที่มีคีย์เท่ากับจะถูกลบออกจากรายการ
supercat

@supercat . . ฉันเห็นด้วย. การรักษาดัชนีให้สอดคล้องกับการอัพเดตและการลบทั้งหมดเป็นสิ่งที่ท้าทายมากขึ้นเพราะคุณไม่สามารถสร้างดัชนีในทริกเกอร์ได้อีก ไม่ว่าในกรณีใดฉันมีความประทับใจจาก OP ว่าข้อมูล - หรืออย่างน้อยก็ซ้ำกัน - ไม่เปลี่ยนแปลงบ่อยนักถ้าเลย
Gordon Linoff

ทำไมไม่แยกรายการของค่าแทนที่จะเป็นรายการของรหัส จากนั้นคุณไม่จำเป็นต้องยกเว้นหนึ่ง ID ต่อค่าซ้ำกันจากรายการของ ID ที่ยกเว้น
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
ยกเว้นว่าเขาไม่สามารถเพิ่มคอลัมน์ลงในตาราง
Aaron Bertrand

3
ฉันชอบวิธีที่คำตอบนี้เปลี่ยนวิธีการที่ค่า NULL จะได้รับการปฏิบัติในรูปแบบที่ไม่ได้มาตรฐานในข้อ จำกัด ที่ไม่ซ้ำใครเป็นสิ่งที่มีประโยชน์ เล่ห์เหลี่ยมเล่ห์เหลี่ยม
ypercubeᵀᴹ

@ ypercubeᵀᴹคุณสามารถอธิบายสิ่งที่ไม่เป็นมาตรฐานเกี่ยวกับการจัดการ NULL ในข้อ จำกัด ที่ไม่ซ้ำกันได้อย่างไร แตกต่างจากสิ่งที่คุณคาดหวังไว้อย่างไร ขอบคุณ!
Noach

1
@ ไม่มีใน SQL Server UNIQUEข้อ จำกัด ในคอลัมน์ nullable ทำให้แน่ใจว่ามีNULLค่าเดียวมากที่สุด มาตรฐาน SQL (และ SQL DBMS อื่น ๆ เกือบทั้งหมด) บอกว่าควรอนุญาตจำนวนNULLค่าใด ๆ(เช่นข้อ จำกัด ควรละเว้นค่า Null)
ypercubeᵀᴹ

@ ypercubeᵀᴹดังนั้นเมื่อต้องการใช้สิ่งนี้กับ DBMS อื่นเราเพียงแค่ใช้ 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.'ดัชนีจะไม่ห้ามการแทรกแถวอีกหนึ่งแถวที่มีชื่อ บริษัท เดียวกัน มันจะห้ามเท่านั้นถ้าคุณลองสองครั้ง

(อัปเดต)สิ่งนี้สามารถแก้ไขได้หากชื่อซ้ำกันทุกรายการคุณจะไม่อยู่ในรายการยกเว้นหนึ่ง ID ถ้าเช่นเดียวกับตัวอย่างข้างต้นมี 4 แถวที่ซ้ำกันCompanyName = DoubleData Co.และรหัส4,6,8,9รายการยกเว้นควรมีเพียง 3 จาก ID เหล่านี้

ด้วยวิธีที่สองข้อเสียเปรียบอีกประการหนึ่งคือสภาพที่ยุ่งยาก (จำนวนยุ่งยากนั้นขึ้นอยู่กับจำนวนที่ซ้ำกันที่มีอยู่ในตอนแรก) เนื่องจาก SQL-Server ดูเหมือนว่าจะไม่สนับสนุนNOT INผู้ประกอบการในWHEREส่วนของดัชนีกรอง ดูแบบ SQL ซอ แทนที่จะเป็นอย่าง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's) คือการเพิ่มคอลัมน์อื่นเติมด้วยหมายเลขอันดับและเพิ่มดัชนีที่ไม่ซ้ำซึ่งรวมถึงคอลัมน์นี้:

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-ซอ-2


-2

อีกทางเลือกหนึ่งคือการเขียนฟังก์ชั่นสเกลาร์ที่ตรวจสอบว่ามีค่าอยู่ในตารางแล้วเรียกใช้ฟังก์ชันนั้นจากข้อ จำกัด การตรวจสอบ

สิ่งนี้จะทำสิ่งที่น่ากลัวสำหรับการแสดง



นอกเหนือจากปัญหาที่แอรอนระบุแล้วคำตอบไม่ได้อธิบายว่าจะเพิ่มข้อ จำกัด การตรวจสอบนี้ได้อย่างไรเพื่อละเว้นการทำซ้ำที่มีอยู่
ypercubeᵀᴹ

-2

ฉันกำลังค้นหาสิ่งเดียวกัน - สร้างดัชนีเฉพาะที่ไม่น่าเชื่อถือดังนั้นข้อมูลที่ไม่ดีที่มีอยู่จะถูกเพิกเฉย แต่ระเบียนใหม่ไม่สามารถซ้ำซ้อนกับสิ่งที่มีอยู่แล้วได้

ในขณะที่อ่านหัวข้อนี้มาถึงฉันว่าทางออกที่ดีกว่าคือการเขียนทริกเกอร์ซึ่งจะตรวจสอบ [แทรก] กับตารางหลักเพื่อค้นหารายการที่ซ้ำกันและถ้ามีรายการที่ซ้ำกันอยู่ระหว่างตารางเหล่านั้น ROLLBACK TRAN

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.