ข้อ จำกัด เฉพาะตามเงื่อนไข


95

ฉันมีสถานการณ์ที่ฉันต้องบังคับใช้ข้อ จำกัด ที่ไม่ซ้ำกันกับชุดของคอลัมน์ แต่สำหรับค่าคอลัมน์เดียวเท่านั้น

ตัวอย่างเช่นฉันมีตารางเช่น Table (ID, Name, RecordStatus)

RecordStatus สามารถมีได้เพียงค่า 1 หรือ 2 (ใช้งานอยู่หรือถูกลบ) และฉันต้องการสร้างข้อ จำกัด เฉพาะบน (ID, RecordStatus) เฉพาะเมื่อ RecordStatus = 1 เนื่องจากฉันไม่สนใจว่าจะมีระเบียนที่ถูกลบหลายรายการที่เหมือนกันหรือไม่ ID.

นอกเหนือจากการเขียนทริกเกอร์ฉันสามารถทำได้หรือไม่?

ฉันใช้ SQL Server 2005


1
การออกแบบนี้เป็นความเจ็บปวดที่พบบ่อย คุณได้พิจารณาเปลี่ยนการออกแบบหรือไม่เพื่อให้บันทึกที่ 'ลบ' ในเชิงแนวคิดถูกลบออกจากตารางจริงและอาจย้ายไปที่ตาราง 'เก็บถาวร' หรือไม่?
2552

1
... เนื่องจากไม่สามารถเขียนข้อ จำกัด ที่ไม่ซ้ำกันในการบังคับใช้คีย์ธรรมดาควรถือเป็น 'กลิ่นรหัส', IMO หากคุณไม่สามารถเปลี่ยนการออกแบบ (SQL DDL) ได้เนื่องจากตารางอื่น ๆ อ้างอิงตารางนี้ฉันจะเดิมพันว่า SQL DML ของคุณได้รับผลเช่นกันเช่นคุณต้องอย่าลืมเพิ่ม ... AND Table.RecordStatus = 1 ' กับเงื่อนไขการค้นหาส่วนใหญ่และเงื่อนไขการเข้าร่วมที่เกี่ยวข้องกับตารางนี้และพบข้อบกพร่องเล็กน้อยเมื่อถูกละเว้นในบางโอกาสอย่างหลีกเลี่ยงไม่ได้
onedaywhen

คำตอบ:


38

เพิ่มข้อ จำกัด ในการตรวจสอบเช่นนี้ ความแตกต่างคือคุณจะคืนค่าเป็นเท็จหาก Status = 1 และ Count> 0

http://msdn.microsoft.com/en-us/library/ms188258.aspx

CREATE TABLE CheckConstraint
(
  Id TINYINT,
  Name VARCHAR(50),
  RecordStatus TINYINT
)
GO

CREATE FUNCTION CheckActiveCount(
  @Id INT
) RETURNS INT AS BEGIN

  DECLARE @ret INT;
  SELECT @ret = COUNT(*) FROM CheckConstraint WHERE Id = @Id AND RecordStatus = 1;
  RETURN @ret;

END;
GO

ALTER TABLE CheckConstraint
  ADD CONSTRAINT CheckActiveCountConstraint CHECK (NOT (dbo.CheckActiveCount(Id) > 1 AND RecordStatus = 1));

INSERT INTO CheckConstraint VALUES (1, 'No Problems', 2);
INSERT INTO CheckConstraint VALUES (1, 'No Problems', 2);
INSERT INTO CheckConstraint VALUES (1, 'No Problems', 2);
INSERT INTO CheckConstraint VALUES (1, 'No Problems', 1);

INSERT INTO CheckConstraint VALUES (2, 'Oh no!', 1);
INSERT INTO CheckConstraint VALUES (2, 'Oh no!', 2);
-- Msg 547, Level 16, State 0, Line 14
-- The INSERT statement conflicted with the CHECK constraint "CheckActiveCountConstraint". The conflict occurred in database "TestSchema", table "dbo.CheckConstraint".
INSERT INTO CheckConstraint VALUES (2, 'Oh no!', 1);

SELECT * FROM CheckConstraint;
-- Id   Name         RecordStatus
-- ---- ------------ ------------
-- 1    No Problems  2
-- 1    No Problems  2
-- 1    No Problems  2
-- 1    No Problems  1
-- 2    Oh no!       1
-- 2    Oh no!       2

ALTER TABLE CheckConstraint
  DROP CONSTRAINT CheckActiveCountConstraint;

DROP FUNCTION CheckActiveCount;
DROP TABLE CheckConstraint;

ฉันดูข้อ จำกัด ในการตรวจสอบระดับตาราง แต่ไม่ได้ดูว่ามีวิธีใดในการส่งผ่านค่าที่แทรกหรืออัปเดตไปยังฟังก์ชันคุณรู้วิธีการหรือไม่
np-hard

เอาล่ะฉันโพสต์สคริปต์ตัวอย่างที่จะช่วยให้คุณพิสูจน์สิ่งที่ฉันกำลังพูดถึง ฉันทดสอบแล้วและใช้งานได้ หากคุณดูสองบรรทัดที่แสดงความคิดเห็นคุณจะเห็นข้อความที่ฉันได้รับ โปรดทราบว่าในการใช้งานของฉันฉันเพียงตรวจสอบให้แน่ใจว่าคุณไม่สามารถเพิ่มรายการที่สองที่มีรหัสเดียวกันซึ่งใช้งานได้หากมีรายการที่ใช้งานอยู่แล้ว คุณสามารถแก้ไขตรรกะดังกล่าวได้หากมีการใช้งานอยู่คุณจะไม่สามารถเพิ่มรายการใด ๆ ที่มีรหัสเดียวกันได้ ด้วยรูปแบบนี้ความเป็นไปได้ไม่มีที่สิ้นสุด
D.Patrick

ฉันต้องการตรรกะเดียวกันในทริกเกอร์ "แบบสอบถามในฟังก์ชันสเกลาร์ ... สามารถสร้างปัญหาใหญ่ได้หากข้อ จำกัด CHECK ของคุณอาศัยแบบสอบถามและหากมีมากกว่าหนึ่งแถวได้รับผลกระทบจากการอัปเดตใด ๆ สิ่งที่เกิดขึ้นคือข้อ จำกัด จะถูกตรวจสอบหนึ่งครั้งสำหรับแต่ละแถวก่อนที่คำสั่งจะเสร็จสมบูรณ์ นั่นหมายความว่าคำสั่ง atomicity เสียและฟังก์ชันจะถูกเปิดเผยกับฐานข้อมูลในสถานะที่ไม่สอดคล้องกันผลลัพธ์ไม่สามารถคาดเดาได้และไม่ถูกต้อง " ดู: blogs.conchango.com/davidportas/archive/2007/02/19/…
onedaywhen

นั่นเป็นความจริงเพียงบางส่วนในวันนั้น ฐานข้อมูลทำงานอย่างสม่ำเสมอและคาดเดาได้ ข้อ จำกัด ของการตรวจสอบจะดำเนินการหลังจากเพิ่มแถวในตารางและก่อนที่ธุรกรรมจะถูกส่งโดย dbms และคุณสามารถไว้วางใจได้ บล็อกนั้นกำลังพูดถึงปัญหาที่ไม่เหมือนใครซึ่งคุณต้องดำเนินการตามข้อ จำกัด กับชุดของเม็ดมีดแทนที่จะใส่เพียงครั้งเดียว Ashish กำลังขอข้อ จำกัด ในการแทรกครั้งละหนึ่งครั้งและข้อ จำกัด นี้จะทำงานได้อย่างแม่นยำคาดการณ์ได้และสม่ำเสมอ ฉันขอโทษถ้าสิ่งนี้ฟังดูสั้นเกินไป ฉันหมดอักขระ
D.Patrick

3
ใช้งานได้ดีสำหรับส่วนแทรก แต่ดูเหมือนจะใช้ไม่ได้กับการอัปเดต EG การเพิ่มสิ่งนี้หลังจากที่เม็ดมีดอื่นทำงานที่นี่เมื่อฉันไม่ได้คาดหวัง ใส่ค่า CheckConstraint (1, 'No ProblemsA', 2); update CheckConstraint set Recordstatus = 1 โดยที่ name = 'No ProblemsA'
dwidel

152

ดูเถิดดัชนีที่กรองแล้ว จากเอกสาร (เน้นของฉัน):

ดัชนีที่กรองแล้วคือดัชนีที่ไม่เป็นคลัสเตอร์ที่ได้รับการปรับให้เหมาะสมโดยเฉพาะอย่างยิ่งที่จะครอบคลุมการสืบค้นที่เลือกจากชุดข้อมูลย่อยที่กำหนดไว้อย่างดี ใช้เพรดิเคตตัวกรองเพื่อจัดทำดัชนีส่วนหนึ่งของแถวในตาราง ดัชนีกรองที่ออกแบบมาอย่างดีสามารถปรับปรุงประสิทธิภาพการสืบค้นรวมทั้งลดค่าใช้จ่ายในการบำรุงรักษาดัชนีและการจัดเก็บเมื่อเทียบกับดัชนีแบบเต็มตาราง

และนี่คือตัวอย่างการรวมดัชนีเฉพาะกับเพรดิเคตตัวกรอง:

create unique index MyIndex
on MyTable(ID)
where RecordStatus = 1;

นี้เป็นหลักบังคับใช้เอกลักษณ์ของIDเมื่อเป็นRecordStatus1

หลังจากการสร้างดัชนีนั้นการละเมิดเอกลักษณ์จะทำให้เกิดข้อผิดพลาด:

ข่าวสารเกี่ยวกับ 2601 ระดับ 14 สถานะ 1 บรรทัด 13
ไม่สามารถแทรกแถวคีย์ที่ซ้ำกันในออบเจ็กต์ 'dbo.MyTable' ด้วยดัชนีเฉพาะ 'MyIndex' ค่าคีย์ที่ซ้ำกันคือ (9999)

หมายเหตุ: ดัชนีที่กรองถูกนำมาใช้ใน SQL Server 2008 สำหรับ SQL Server เวอร์ชันก่อนหน้าโปรดดูคำตอบนี้


โปรดทราบว่า SQL Server ต้องการansi_paddingดัชนีที่กรองดังนั้นตรวจสอบให้แน่ใจว่าตัวเลือกนี้เปิดอยู่โดยการดำเนินการSET ANSI_PADDING ONก่อนที่จะสร้างดัชนีที่กรอง
naXa

10

คุณสามารถย้ายระเบียนที่ถูกลบไปยังตารางที่ไม่มีข้อ จำกัด และอาจใช้มุมมองที่มี UNION ของตารางทั้งสองเพื่อรักษาลักษณะของตารางเดียว


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

3

ทำได้แบบแฮ็กจริงๆ ...

สร้างมุมมองแผนผังบนโต๊ะของคุณ

สร้างมุมมองสิ่งที่เลือก * จากตาราง WHERE RecordStatus = 1

ตอนนี้สร้างข้อ จำกัด เฉพาะในมุมมองด้วยฟิลด์ที่คุณต้องการ

ข้อสังเกตประการหนึ่งเกี่ยวกับมุมมองแผนผังหากคุณเปลี่ยนตารางพื้นฐานคุณจะต้องสร้างมุมมองใหม่ gotchas มากมายเพราะเหตุนั้น


นี่เป็นคำแนะนำที่ดีทีเดียวไม่ใช่ว่า "แฮ็ก" นี่คือข้อมูลเพิ่มเติมเกี่ยวกับทางเลือกดัชนีที่กรองนี้
Scott Whitlock

เป็นความคิดที่ไม่ดีเลย คำถามไม่ใช่มัน
FabianoLothor

ฉันใช้มุมมองแผนผังหนึ่งครั้งและไม่เคยทำผิดซ้ำอีก พวกเขาสามารถเป็นความเจ็บปวดในการทำงานร่วมกับราชวงศ์ ไม่ใช่ว่าคุณต้องสร้างมุมมองใหม่หากคุณเปลี่ยนตารางพื้นฐาน - คุณอาจต้องทำเช่นนั้นสำหรับมุมมองทั้งหมดอย่างน้อยในเซิร์ฟเวอร์ SQL คุณไม่สามารถเปลี่ยนตารางได้โดยไม่ต้องทิ้งมุมมองก่อนซึ่งคุณอาจไม่สามารถทำได้หากไม่วางการอ้างอิงลงไปก่อน โอ้รวมทั้งพื้นที่จัดเก็บข้อมูลอาจเป็นปัญหาได้ - ไม่ว่าจะเป็นเพราะพื้นที่หรือเนื่องจากค่าใช้จ่ายที่เพิ่มในการแทรกและอัปเดต
MattW

1

เนื่องจากคุณจะอนุญาตให้มีการทำซ้ำข้อ จำกัด เฉพาะจะไม่ทำงาน คุณสามารถสร้างข้อ จำกัด การตรวจสอบสำหรับคอลัมน์ RecordStatus และกระบวนงานที่เก็บไว้สำหรับ INSERT ที่ตรวจสอบเรกคอร์ดที่ใช้งานอยู่ก่อนที่จะแทรก ID ที่ซ้ำกัน


1

หากคุณไม่สามารถใช้ NULL เป็น RecordStatus ตามที่ Bill แนะนำได้คุณสามารถรวมความคิดของเขากับดัชนีตามฟังก์ชันได้ สร้างฟังก์ชันที่ส่งคืนค่า NULL หาก RecordStatus ไม่ใช่ค่าใดค่าหนึ่งที่คุณต้องการพิจารณาในข้อ จำกัด ของคุณ (และ RecordStatus เป็นอย่างอื่น) และสร้างดัชนีเหนือสิ่งนั้น

ซึ่งจะมีข้อดีที่คุณไม่ต้องตรวจสอบแถวอื่น ๆ ในตารางอย่างชัดเจนในข้อ จำกัด ของคุณซึ่งอาจทำให้คุณมีปัญหาด้านประสิทธิภาพ

ฉันควรบอกว่าฉันไม่รู้จักเซิร์ฟเวอร์ SQL เลย แต่ฉันใช้แนวทางนี้ใน Oracle สำเร็จแล้ว


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