Làm cách nào để buộc một bản ghi có giá trị thực cho cột boolean và tất cả các bản ghi khác có giá trị sai?


20

Tôi muốn thực thi rằng chỉ một bản ghi trong bảng được coi là giá trị "mặc định" cho các truy vấn hoặc chế độ xem khác có thể truy cập vào bảng đó.

Về cơ bản, tôi muốn đảm bảo rằng truy vấn này sẽ luôn trả về chính xác một hàng:

SELECT ID, Zip 
FROM PostalCodes 
WHERE isDefault=True

Làm thế nào tôi có thể làm điều đó trong SQL?


1
"Tôi muốn đảm bảo rằng truy vấn này sẽ luôn trả về chính xác một hàng" - luôn luôn? Còn khi nào PostalCodesthì trống? Nếu một hàng đã có thuộc tính, thì nó có bị ngăn không được đặt thành false trừ khi một hàng khác (nếu tồn tại) được đặt thành true trong cùng một câu lệnh SQL không? Hàng không có thể có tài sản giữa các ranh giới giao dịch? Hàng cuối cùng trong bảng có nên bị buộc phải có tài sản và bị ngăn không bị xóa không? Kinh nghiệm cho tôi biết rằng "đảm bảo chính xác một hàng" có xu hướng có nghĩa là một cái gì đó khác trong thực tế, thường chỉ đơn giản là "nhiều nhất một hàng".
onedaywhen

Câu trả lời:


8

Chỉnh sửa: điều này được hướng đến SQL Server trước khi chúng ta biết MySQL

4 5 cách để làm điều này: theo thứ tự mong muốn nhất đến mong muốn ít nhất

Chúng tôi không biết RDBMS này là gì nếu không phải tất cả có thể áp dụng

  1. Cách tốt nhất là một chỉ mục được lọc. Điều này sử dụng DRI để duy trì tính độc đáo.

  2. Cột được tính toán với tính duy nhất (xem câu trả lời của Jack Douglas) (được thêm bằng cách chỉnh sửa 2)

  3. Một khung nhìn được lập chỉ mục / cụ thể hóa giống như một chỉ mục được lọc bằng DRI

  4. Kích hoạt (theo câu trả lời khác)

  5. Kiểm tra ràng buộc với UDF. Điều này không an toàn cho cách ly đồng thời và chụp nhanh. Xem một hai ba bốn

Lưu ý rằng yêu cầu "một mặc định" nằm trên bảng, không phải là thủ tục được lưu trữ. Quy trình được lưu trữ sẽ có cùng các vấn đề tương tranh như ràng buộc kiểm tra với udf

Lưu ý: đã hỏi về SO nhiều lần:


gbn - có vẻ như chỉ mục được lọc / giải pháp DRI chỉ là SQL 2008 nên có vẻ như tôi sẽ bỏ qua để thử tùy chọn 2.
emaynard

10

Lưu ý: Câu trả lời này đã được đưa ra trước khi rõ ràng người hỏi muốn một cái gì đó cụ thể của MySQL. Câu trả lời này thiên về SQL Server.

Áp dụng ràng buộc KIỂM TRA cho bảng gọi UDF và đảm bảo giá trị trả về của nó là <= 1. UDF chỉ có thể đếm các hàng trong bảng WHERE isDefault = TRUE. Điều này sẽ đảm bảo rằng không có nhiều hơn 1 hàng mặc định trong bảng.

Bạn nên thêm một bitmap hoặc chỉ mục được lọc trên isDefaultcột (tùy thuộc vào chỉ số của bạn) để đảm bảo UDF này chạy rất nhanh.

Hãy cẩn thận

  • Kiểm tra các hạn chế có những hạn chế nhất định . Cụ thể, chúng được gọi cho mỗi hàng được sửa đổi, ngay cả khi những hàng đó chưa được cam kết trong một giao dịch. Vì vậy, nếu bạn bắt đầu một giao dịch, hãy đặt một hàng mới thành mặc định và sau đó bỏ đặt hàng cũ, ràng buộc kiểm tra của bạn sẽ khiếu nại. Đó là bởi vì nó sẽ thực thi sau khi bạn đặt hàng mới thành mặc định, tìm thấy bây giờ có hai mặc định và không thành công. Giải pháp sau đó là đảm bảo bạn bỏ đặt hàng mặc định trước khi đặt mặc định mới, ngay cả khi bạn đang thực hiện công việc này trong một giao dịch.
  • Như gbn đã chỉ ra , UDF có thể trả về kết quả không nhất quán nếu bạn đang sử dụng cách ly ảnh chụp nhanh trong SQL Server. Tuy nhiên, UDF nội tuyến không gặp phải vấn đề này và vì UDF chỉ có số lượng là có khả năng nội tuyến, nên đây không phải là vấn đề ở đây.
  • Sức mạnh của sức mạnh xử lý của công cụ cơ sở dữ liệu là khả năng hoạt động trên các bộ dữ liệu cùng một lúc. Với một ràng buộc kiểm tra được hỗ trợ bởi UDF, động cơ sẽ bị giới hạn trong việc áp dụng UDF này cho tất cả các hàng được sửa đổi. Trong trường hợp sử dụng của bạn, không chắc là bạn sẽ thực hiện cập nhật hàng loạt cho PostalCodesbảng, tuy nhiên điều này vẫn là mối quan tâm về hiệu suất đối với những tình huống có khả năng hoạt động dựa trên tập hợp.

Với tất cả những cảnh báo này, tôi khuyên bạn nên sử dụng đề xuất của gbn về một chỉ mục duy nhất được lọc thay vì ràng buộc kiểm tra.


4

Đây là một thủ tục lưu trữ (phương ngữ 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 ;

Để đảm bảo bảng của bạn sạch sẽ và quy trình được lưu trữ đang hoạt động, giả sử ID 200 là mặc định, hãy chạy các bước sau:

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;

Thay vì Thủ tục lưu trữ, làm thế nào về Kích hoạt?

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 ;

Để đảm bảo bảng của bạn sạch sẽ và trình kích hoạt đang hoạt động, giả sử ID 200 là mặc định, hãy chạy các bước sau:

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

Bạn có thể sử dụng một kích hoạt để áp dụng các quy tắc. Khi một câu lệnh UPDATE hoặc INSERT đặt isDefault thành True, SQL trong trình kích hoạt có thể đặt tất cả các hàng khác thành Sai.

Bạn sẽ phải xem xét các tình huống khác khi áp dụng điều này. Ví dụ: điều gì sẽ xảy ra nếu có nhiều hơn một hàng trong và CẬP NHẬT hoặc INSERT đặt isDefault thành True? Những quy tắc nào sẽ được áp dụng để tránh phá vỡ quy tắc?

Ngoài ra, có thể là điều kiện không có mặc định? Điều gì sẽ xảy ra khi một bản cập nhật đặt isDefault từ True thành false.

Khi bạn xác định quy tắc, bạn có thể xây dựng chúng trong trình kích hoạt.

Sau đó, như được chỉ ra trong một câu trả lời khác, bạn muốn áp dụng một ràng buộc kiểm tra để giúp thực thi các quy tắc.


3

Sau đây thiết lập một bảng ví dụ và dữ liệu:

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

Nếu bạn tạo một thủ tục được lưu trữ để đặt cờ IsDefault và xóa quyền để thay đổi bảng cơ sở, bạn có thể thực thi điều này bằng truy vấn bên dưới. Nó sẽ yêu cầu quét bảng mỗi lần.

DECLARE @Id INT
SET @Id = 10

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

Tùy thuộc vào kích thước của bảng, một chỉ mục trên IsDefault (DESC) có thể dẫn đến một hoặc cả hai truy vấn bên dưới để tránh quét toàn bộ.

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)

Nếu bạn không thể xóa quyền khỏi bảng cơ sở, cần thực thi tính toàn vẹn bằng các phương tiện khác và đang sử dụng SQL2008, bạn có thể sử dụng một chỉ mục duy nhất được lọc:

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

1

Đối với MySQL không có các chỉ mục được lọc, bạn có thể làm một cái gì đó như sau. Nó khai thác thực tế rằng MySQL cho phép nhiều NULL trong các chỉ mục duy nhất và sử dụng bảng chuyên dụng và khóa ngoại để mô phỏng kiểu dữ liệu chỉ có thể có NULL và 1 làm giá trị.

Với giải pháp này, thay vì đúng / sai, bạn sẽ chỉ sử dụng 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`))*/

Điều duy nhất có thể sai, tôi nghĩ, là nếu ai đó thêm một hàng vào DefaultFlagbàn. Tôi nghĩ rằng đây không phải là một mối quan tâm lớn và bạn luôn có thể khắc phục nó bằng quyền nếu bạn thực sự muốn an toàn.


0
SELECT ID, Zip FROM PostalCodes WHERE isDefault=True 

Điều này về cơ bản mang đến cho bạn các vấn đề vì bạn đặt trường sai vị trí và đó là tất cả.

Có bảng 'Mặc định', có PostalCode, chứa id mà bạn đang tìm kiếm. Nói chung, cũng tốt khi đặt tên các bảng của bạn theo một bản ghi, vì vậy tên của bảng ban đầu của bạn sẽ tốt hơn được gọi là 'PostalCode'. Mặc định sẽ là một bảng hàng duy nhất, cho đến khi bạn cần chuyên môn hóa mặc định của mình đối với một số cấu hình khác (chẳng hạn như lịch sử).

Truy vấn cuối cùng mà bạn muốn là như sau:

select Zip from PostalCode p, Defaults d where p.ID=d.PostalCode;
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.