ฉันจะบังคับให้หนึ่งระเบียนมีค่าจริงสำหรับคอลัมน์บูลีนและค่าอื่น ๆ ทั้งหมดเป็นค่าเท็จได้อย่างไร


20

ฉันต้องการบังคับใช้ว่ามีเพียงระเบียนเดียวในตารางที่ถือว่าเป็นค่า "เริ่มต้น" สำหรับคิวรีหรือมุมมองอื่นที่อาจเข้าถึงตารางนั้น

โดยพื้นฐานแล้วฉันต้องการรับประกันว่าแบบสอบถามนี้จะส่งคืนแถวเดียวเสมอ:

SELECT ID, Zip 
FROM PostalCodes 
WHERE isDefault=True

ฉันจะทำเช่นนั้นใน SQL ได้อย่างไร


1
"ฉันต้องการรับประกันว่าแบบสอบถามนี้จะส่งคืนแถวเดียวเสมอ" - เสมอหรือไม่ PostalCodesจะว่างเปล่าเมื่อไหร่? หากแถวมีคุณสมบัติควรมีการป้องกันไม่ให้ถูกตั้งค่าเป็นเท็จเว้นแต่มีการตั้งค่าแถวอื่น (ถ้ามี) เป็นจริงภายในคำสั่ง SQL เดียวกันหรือไม่ แถวศูนย์จะมีคุณสมบัติระหว่างขอบเขตของธุรกรรมหรือไม่ แถวสุดท้ายในตารางควรถูกบังคับให้มีคุณสมบัติและป้องกันไม่ให้ถูกลบหรือไม่ ประสบการณ์บอกฉันว่า "รับประกันหนึ่งแถวอย่างแน่นอน" มีแนวโน้มที่จะหมายถึงบางสิ่งที่แตกต่างในความเป็นจริงมักจะ "ไม่เกินหนึ่งแถว"
onedaywhen

คำตอบ:


8

แก้ไข: นี่คือมุ่งสู่ SQL Server ก่อนที่เราจะรู้ MySQL

มี4 5 วิธีในการทำเช่นนี้: เรียงตามลำดับที่ต้องการมากที่สุดถึงต้องการน้อยที่สุด

เราไม่ทราบว่า RDBMS นี้จะทำอะไรแม้ว่าอาจจะไม่ใช่ทั้งหมดก็ตาม

  1. วิธีที่ดีที่สุดคือดัชนีที่ถูกกรอง สิ่งนี้ใช้ DRI เพื่อรักษาเอกลักษณ์

  2. คอลัมน์ที่คำนวณด้วยความเป็นเอกลักษณ์ (ดูคำตอบของ Jack Douglas) (เพิ่มโดยแก้ไข 2)

  3. มุมมองที่จัดทำดัชนี / ปรากฏให้เห็นซึ่งเป็นเหมือนดัชนีที่กรองแล้วโดยใช้ DRI

  4. ทริกเกอร์ (ตามคำตอบอื่น ๆ )

  5. ตรวจสอบข้อ จำกัด ด้วย UDF สิ่งนี้ไม่ปลอดภัยสำหรับการแยกพร้อมกันและสแน็ปช็อต เห็นหนึ่ง สอง สาม สี่

โปรดทราบว่าข้อกำหนดสำหรับ "หนึ่งค่าเริ่มต้น" อยู่บนตารางไม่ใช่ขั้นตอนที่เก็บไว้ กระบวนงานที่เก็บไว้จะมีปัญหาการเกิดพร้อมกันเช่นเดียวกับข้อ จำกัด การตรวจสอบกับ udf

หมายเหตุ: ถามหลายครั้งดังนั้น:


gbn - ดูเหมือนว่าดัชนี / โซลูชัน DRI ที่กรองแล้วเป็น SQL 2008 เท่านั้นดังนั้นดูเหมือนว่าฉันจะข้ามไปลองใช้ตัวเลือกที่ 2
emaynard

10

หมายเหตุ: คำตอบนี้ได้รับก่อนที่จะชัดเจนผู้ถามต้องการสิ่งที่เฉพาะเจาะจง MySQL คำตอบนี้มีอคติต่อ SQL Server

ใช้ข้อ จำกัด การตรวจสอบกับตารางที่เรียกใช้UDFและตรวจสอบให้แน่ใจว่าค่าที่ส่งคืนคือ <= 1 UDF สามารถนับแถวในตารางisDefault = TRUEได้ที่ใด สิ่งนี้จะช่วยให้มั่นใจได้ว่าจะมีแถวเริ่มต้นไม่เกิน 1 แถวในตาราง

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

คำเตือน

  • ตรวจสอบข้อ จำกัด มีบาง ข้อ จำกัด กล่าวคือพวกเขาถูกเรียกใช้สำหรับทุกแถวที่มีการแก้ไขแม้ว่าแถวเหล่านั้นยังไม่ได้รับการยืนยันในการทำธุรกรรม ดังนั้นหากคุณเริ่มทำธุรกรรมให้ตั้งค่าแถวใหม่ให้เป็นค่าเริ่มต้นจากนั้นยกเลิกการตั้งค่าแถวเก่าข้อ จำกัด การตรวจสอบของคุณจะบ่น นั่นเป็นเพราะมันจะทำงานหลังจากที่คุณสร้างแถวใหม่เป็นค่าเริ่มต้นค้นหาว่าตอนนี้มีค่าเริ่มต้นที่สองและล้มเหลว วิธีแก้ปัญหาคือเพื่อให้แน่ใจว่าคุณได้ยกเลิกการตั้งค่าแถวเริ่มต้นก่อนที่จะตั้งค่าเริ่มต้นใหม่แม้ว่าคุณจะทำงานนี้ในการทำธุรกรรม
  • ตามที่gbn ระบุไว้ UDF อาจส่งคืนผลลัพธ์ที่ไม่สอดคล้องกันหากคุณใช้การแยกสแน็ปช็อตใน SQL Server อย่างไรก็ตาม UDF แบบอินไลน์ไม่ได้พบปัญหานี้และเนื่องจาก UDF ที่เพิ่งนับมีความสามารถในแบบอินไลน์นี่จึงไม่ใช่ปัญหาที่นี่
  • จุดแข็งของพลังการประมวลผลของโปรแกรมฐานข้อมูลคือความสามารถในการทำงานกับชุดข้อมูลในครั้งเดียว ด้วยข้อ จำกัด การตรวจสอบที่สนับสนุน UDF เอ็นจิ้นจะถูก จำกัด ให้ใช้ UDF นี้กับทุก ๆ แถวที่มีการแก้ไข ในกรณีการใช้งานของคุณไม่น่าเป็นไปได้ที่คุณจะทำการอัปเดตจำนวนมากในPostalCodesตารางอย่างไรก็ตามสิ่งนี้ยังคงเป็นปัญหาด้านประสิทธิภาพสำหรับสถานการณ์เหล่านั้นที่มีกิจกรรมตามการตั้งค่า

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


4

นี่คือขั้นตอนการจัดเก็บ (ภาษาถิ่น MySQL):

DELIMITER $$
DROP PROCEDURE IF EXISTS SetDefaultForZip;
CREATE PROCEDURE SetDefaultForZip (NEWID INT)
BEGIN
    DECLARE FOUND_TRUE,OLDID INT;

    SELECT COUNT(1) INTO FOUND_TRUE FROM PostalCode WHERE isDefault = TRUE;
    IF FOUND_TRUE = 1 THEN
        SELECT ID INTO OLDID FROM PostalCode WHERE isDefault = TRUE;
        IF NEWID <> OLDID THEN
            UPDATE PostalCode SET isDefault = FALSE WHERE ID = OLDID;
            UPDATE PostalCode SET isDefault = TRUE  WHERE ID = NEWID;
        END IF;
    ELSE
        UPDATE PostalCode SET isDefault = TRUE WHERE ID = NEWID;
    END IF;
END;
$$
DELIMITER ;

เพื่อให้แน่ใจว่าตารางของคุณสะอาดและขั้นตอนการจัดเก็บทำงานอยู่สมมติว่า ID 200 เป็นค่าเริ่มต้นให้รันขั้นตอนเหล่านี้:

ALTER TABLE PostalCode DROP INDEX isDefault_ndx;
UPDATE PostalCodes SET isDefault = FALSE;
ALTER TABLE PostalCode ADD INDEX isDefault_ndx (isDefault);
CALL SetDefaultForZip(200);
SELECT ID FROM PostalCodes WHERE isDefault = TRUE;

แทนที่จะเป็นขั้นตอนการจัดเก็บทริกเกอร์ล่ะ?

DELIMITER $$
CREATE TRIGGER postalcodes_bu BEFORE UPDATE ON PostalCodes FOR EACH ROW
BEGIN
    DECLARE FOUND_TRUE,OLDID INT;
    IF NEW.isDefault = TRUE THEN
        SELECT COUNT(1) INTO FOUND_TRUE FROM PostalCode WHERE isDefault = TRUE;
        IF FOUND_TRUE = 1 THEN
            SELECT ID INTO OLDID FROM PostalCode WHERE isDefault = TRUE;
            UPDATE PostalCodes SET isDefault = FALSE WHERE ID = OLDID;
        END IF;
    END IF;
END;
$$
DELIMITER ;

เพื่อให้แน่ใจว่าตารางของคุณสะอาดและทริกเกอร์ทำงานอยู่สมมติว่า ID 200 เป็นค่าเริ่มต้นให้รันขั้นตอนเหล่านี้:

DROP TRIGGER postalcodes_bu;
ALTER TABLE PostalCode DROP INDEX isDefault_ndx;
UPDATE PostalCodes SET isDefault = FALSE;
ALTER TABLE PostalCode ADD INDEX isDefault_ndx (isDefault);
DELIMITER $$
CREATE TRIGGER postalcodes_bu BEFORE UPDATE ON PostalCodes FOR EACH ROW
BEGIN
    DECLARE FOUND_TRUE,OLDID INT;
    IF NEW.isDefault = TRUE THEN
        SELECT COUNT(1) INTO FOUND_TRUE FROM PostalCode WHERE isDefault = TRUE;
        IF FOUND_TRUE = 1 THEN
            SELECT ID INTO OLDID FROM PostalCode WHERE isDefault = TRUE;
            UPDATE PostalCodes SET isDefault = FALSE WHERE ID = OLDID;
        END IF;
    END IF;
END;
$$
DELIMITER ;
UPDATE PostalCodes SET isDefault = TRUE WHERE ID = 200;
SELECT ID FROM PostalCodes WHERE isDefault = TRUE;

3

คุณสามารถใช้ทริกเกอร์เพื่อใช้กฎ เมื่อชุดคำสั่ง UPDATE หรือ INSERT คือ isDefault เป็น True SQL ในทริกเกอร์สามารถตั้งค่าแถวอื่นทั้งหมดเป็น False

คุณจะต้องพิจารณาสถานการณ์อื่นเมื่อใช้สิ่งนี้ ตัวอย่างเช่นจะเกิดอะไรขึ้นหากมีมากกว่าหนึ่งแถวในและตั้งค่า UPDATE หรือ INSERT isDefault เป็น True จะใช้กฎอะไรบ้างเพื่อหลีกเลี่ยงการทำผิดกฎ?

นอกจากนี้ยังเป็นไปได้ว่าสภาพที่ไม่มีค่าเริ่มต้น? จะเกิดอะไรขึ้นเมื่อการอัปเดตตั้งค่า isDefault จาก True เป็น False

เมื่อคุณกำหนดกฎคุณสามารถสร้างกฎเหล่านั้นในทริกเกอร์

จากนั้นตามที่ระบุไว้ในคำตอบอื่นคุณต้องการใช้ข้อ จำกัด การตรวจสอบเพื่อช่วยในการบังคับใช้กฎ


3

ต่อไปนี้ตั้งค่าตารางตัวอย่างและข้อมูล:

IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[MyTable]') AND type in (N'U'))
DROP TABLE [dbo].[MyTable]
GO

CREATE TABLE dbo.MyTable
(
    [id] INT IDENTITY(1,1) PRIMARY KEY CLUSTERED
    , [IsDefault] BIT DEFAULT 0
)
GO

INSERT dbo.MyTable DEFAULT VALUES
GO 100

หากคุณสร้างกระบวนงานที่เก็บไว้เพื่อตั้งค่าสถานะ IsDefault และลบสิทธิ์ในการเปลี่ยนตารางฐานคุณสามารถบังคับใช้กับแบบสอบถามด้านล่าง มันจะต้องสแกนตารางในแต่ละครั้ง

DECLARE @Id INT
SET @Id = 10

UPDATE
    dbo.MyTable
SET
    IsDefault = CASE WHEN [id] = @Id THEN 1 ELSE 0 END 
GO

ขึ้นอยู่กับขนาดของตารางดัชนีใน IsDefault (DESC) อาจส่งผลให้หนึ่งหรือทั้งสองของแบบสอบถามด้านล่างหลีกเลี่ยงการสแกนแบบเต็ม

DECLARE @Id INT
SET @Id = 10

UPDATE
    dbo.MyTable
SET
    IsDefault = CASE WHEN [id] = @Id THEN 1 ELSE 0 END
FROM
    dbo.MyTable
WHERE
    [id] = @Id
OR  IsDefault = 1

UPDATE
    dbo.MyTable
SET
    IsDefault = CASE WHEN [id] = @Id THEN 1 ELSE 0 END
FROM
    dbo.MyTable
WHERE
    [id] = @Id
OR  ([id] != @Id AND IsDefault = 1)

หากคุณไม่สามารถลบการอนุญาตจากตารางพื้นฐานจำเป็นต้องบังคับใช้ integrity โดยวิธีอื่นและใช้ SQL2008 คุณสามารถใช้ดัชนีเฉพาะที่กรองแล้วได้:

CREATE UNIQUE INDEX IX_MyTable_IsDefault  ON dbo.MyTable (IsDefault) WHERE IsDefault = 1

1

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

ด้วยวิธีนี้แทนที่จะเป็นจริง / เท็จคุณจะใช้ 1 / NULL

CREATE TABLE DefaultFlag (id TINYINT UNSIGNED NOT NULL PRIMARY KEY);
INSERT INTO DefaultFlag (id) VALUES (1);

CREATE TABLE PostalCodes (
    ID INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
    Zip VARCHAR (32) NOT NULL,
    isDefault TINYINT UNSIGNED NULL DEFAULT NULL,
    CONSTRAINT FOREIGN KEY (isDefault) REFERENCES DefaultFlag (id),
    UNIQUE KEY (isDefault)
);

INSERT INTO PostalCodes (Zip, isDefault) VALUES ('123', 1   );
/* Only one row can be default */
INSERT INTO PostalCodes (Zip, isDefault) VALUES ('abc', 1   );
/* ERROR 1062 (23000): Duplicate entry '1' for key 'isDefault' */

/* MySQL allows multiple NULLs in unique indexes */
INSERT INTO PostalCodes (Zip, isDefault) VALUES ('456', NULL);
INSERT INTO PostalCodes (Zip, isDefault) VALUES ('789', NULL);

/* only NULL and 1 are admitted in isDefault thanks to foreign key */
INSERT INTO PostalCodes (Zip, isDefault) VALUES ('789', 2   );
/* ERROR 1452 (23000): Cannot add or update a child row: a foreign key constraint fails (`test`.`PostalCodes`, CONSTRAINT `PostalCodes_ibfk_1` FOREIGN KEY (`isDefault`) REFERENCES `DefaultFlag` (`id`))*/

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


0
SELECT ID, Zip FROM PostalCodes WHERE isDefault=True 

นี่เป็นสิ่งที่ทำให้คุณมีปัญหาเพราะคุณลงสนามในที่ที่ผิดและนั่นคือทั้งหมด

มีตาราง 'ค่าเริ่มต้น' โดยมีรหัสไปรษณีย์อยู่ในนั้นซึ่งมีรหัสที่คุณกำลังค้นหา โดยทั่วไปคุณควรตั้งชื่อตารางของคุณตามบันทึกเดียวดังนั้นชื่อของตารางเริ่มต้นของคุณน่าจะดีกว่าเรียกว่า 'รหัสไปรษณีย์' ค่าเริ่มต้นจะเป็นตารางแถวเดียวจนกว่าคุณจะต้องใช้ค่าเริ่มต้นของคุณกับการกำหนดค่าอื่น ๆ (เช่น historicalics)

แบบสอบถามสุดท้ายที่คุณต้องการมีดังนี้:

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