it-swarm.asia

Bagaimana cara memaksa satu rekaman untuk memiliki nilai sebenarnya untuk kolom boolean, dan semua yang lain nilai palsu?

Saya ingin menegakkan bahwa hanya satu rekaman dalam tabel dianggap sebagai nilai "default" untuk kueri atau tampilan lain yang mungkin mengakses tabel itu.

Pada dasarnya, saya ingin menjamin bahwa permintaan ini akan selalu mengembalikan tepat satu baris:

SELECT ID, Zip 
FROM PostalCodes 
WHERE isDefault=True

Bagaimana saya melakukannya dalam SQL?

20
emaynard

Sunting: ini ditujukan untuk SQL Server sebelum kita tahu MySQL

Ada 4 5 cara untuk melakukan ini: dalam urutan yang paling diinginkan hingga yang tidak diinginkan

Kami tidak tahu apa RDBMS ini jika untuk jadi tidak semua mungkin berlaku

  1. Cara terbaik adalah indeks yang difilter. Ini menggunakan DRI untuk mempertahankan keunikan.

  2. Kolom yang dihitung dengan keunikan (lihat jawaban Jack Douglas) (ditambah dengan edit 2)

  3. Tampilan terindeks/terwujud yang seperti indeks yang difilter menggunakan DRI

  4. Pemicu (sesuai jawaban lain)

  5. Periksa kendala dengan UDF. Ini tidak aman untuk isolasi concurrency dan snapshot. Lihat SatDuaTigaEmpat

Perhatikan bahwa persyaratan untuk "satu standar" ada di atas meja, bukan prosedur tersimpan. Prosedur tersimpan akan memiliki masalah konkurensi yang sama dengan kendala cek dengan udf

Catatan: ditanyakan pada SO berkali-kali:

8
gbn

Catatan: Jawaban ini diberikan sebelum jelas penanya menginginkan sesuatu yang spesifik MySQL. Jawaban ini bias terhadap SQL Server.

Menerapkan tanda centang pada tabel yang memanggil UDF dan memastikan kembalinya nilainya <= 1. UDF hanya dapat menghitung baris pada tabel MANA isDefault = TRUE. Ini akan memastikan bahwa tidak ada lebih dari 1 baris default dalam tabel.

Anda harus menambahkan indeks bitmap atau difilter pada kolom isDefault (tergantung platforom Anda) untuk memastikan UDF ini berjalan sangat cepat.

Peringatan

  • Periksa kendala memiliki pastibatasan . Yaitu, mereka dipanggil untuk setiap baris yang dimodifikasi, bahkan jika baris-baris itu belum dilakukan dalam suatu transaksi. Jadi, jika Anda memulai transaksi, atur baris baru sebagai default, lalu hapus baris lama, kendala pemeriksaan Anda akan dikeluhkan. Itu karena akan dijalankan setelah Anda menjadikan baris baru sebagai default, temukan sekarang ada dua default, dan gagal. Solusinya kemudian adalah memastikan Anda menghapus baris default terlebih dahulu sebelum menetapkan default baru, bahkan jika Anda melakukan pekerjaan ini dalam transaksi.
  • As gbntunjukkan , UDFs dapat mengembalikan hasil yang tidak konsisten jika Anda menggunakan isolasi snapshot di SQL Server. Namun, UDF inline tidak mengalami masalah ini, dan karena UDF yang hanya menghitung mampu inline, ini bukan masalah di sini.
  • Kekuatan kekuatan pemrosesan mesin basis data adalah kemampuannya untuk bekerja pada set data sekaligus. Dengan kendala pemeriksaan yang didukung UDF, mesin akan dibatasi untuk menerapkan UDF ini secara berurutan untuk setiap baris yang dimodifikasi. Dalam kasus penggunaan Anda, Anda tidak mungkin melakukan pembaruan massal pada tabel PostalCodes, namun hal ini tetap menjadi masalah kinerja untuk situasi di mana aktivitas berbasis set mungkin dilakukan.

Dengan semua peringatan ini, saya sarankan menggunakan saran gbn dari indeks unik yang difilter alih-alih batasan pemeriksaan.

10
Nick Chammas

Berikut ini adalah Prosedur Tersimpan (Dialek 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 ;

Untuk memastikan meja Anda bersih dan prosedur tersimpan berfungsi, dengan anggapan ID 200 adalah default, jalankan langkah-langkah ini:

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;

Alih-alih Prosedur Tersimpan, bagaimana dengan Pemicu?

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 ;

Untuk memastikan meja Anda bersih dan pemicu berfungsi, dengan anggapan ID 200 adalah default, jalankan langkah-langkah ini:

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;
4
RolandoMySQLDBA

Anda bisa menggunakan pemicu untuk menerapkan aturan. Ketika pernyataan UPDATE atau INSERT menetapkan isDefault ke True, SQL di pemicu dapat mengatur semua baris lainnya ke False.

Anda harus mempertimbangkan situasi lain saat menerapkan ini. Misalnya, apa yang harus terjadi jika lebih dari satu baris masuk dan UPDATE atau INSERT menetapkan isDefault ke True? Aturan apa yang akan berlaku untuk menghindari melanggar aturan?

Juga, mungkinkah kondisi di mana tidak ada standar? Apa yang akan terjadi ketika pembaruan menyetel isDefault dari True ke False.

Setelah Anda menetapkan aturan, Anda bisa membuatnya di pelatuk.

Kemudian, seperti yang ditunjukkan dalam jawaban lain, Anda ingin menerapkan batasan pemeriksaan untuk membantu menegakkan aturan.

3
bobs

Berikut ini menyiapkan contoh tabel dan data:

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

Jika Anda membuat prosedur tersimpan untuk mengatur bendera IsDefault dan menghapus izin untuk mengubah tabel dasar, Anda bisa memberlakukan ini dengan kueri di bawah ini. Itu akan membutuhkan pemindaian tabel setiap kali.

DECLARE @Id INT
SET @Id = 10

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

Bergantung pada ukuran tabel, indeks pada IsDefault (DESC) dapat menghasilkan salah satu atau kedua pertanyaan di bawah ini menghindari pemindaian penuh.

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)

Jika Anda tidak dapat menghapus izin dari tabel dasar, perlu menegakkan integritas dengan cara lain dan menggunakan SQL2008, Anda dapat menggunakan indeks unik yang difilter:

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

Untuk MySQL yang tidak memiliki indeks filter, Anda dapat melakukan sesuatu seperti berikut ini. Ini mengeksploitasi fakta bahwa MySQL memungkinkan beberapa NULL dalam indeks unik, dan menggunakan tabel khusus dan kunci asing untuk mensimulasikan tipe data yang hanya dapat memiliki NULL dan 1 sebagai nilai.

Dengan solusi ini, alih-alih benar/salah, Anda hanya akan menggunakan 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`))*/

Satu-satunya hal yang bisa salah, saya pikir, adalah jika seseorang menambahkan baris ke tabel DefaultFlag. Saya pikir ini bukan masalah besar, dan Anda selalu dapat memperbaikinya dengan izin jika Anda benar-benar ingin aman.

1
djfm
SELECT ID, Zip FROM PostalCodes WHERE isDefault=True 

Ini pada dasarnya memberi Anda masalah karena Anda meletakkan bidang di tempat yang salah dan hanya itu.

Memiliki tabel 'Defaults', dengan PostalCode di dalamnya, yang berisi id yang Anda cari. Secara umum, ada baiknya untuk memberi nama tabel Anda sesuai dengan satu catatan, jadi nama tabel awal Anda akan lebih baik disebut 'Kode Pos'. Default akan menjadi tabel baris tunggal, sampai Anda perlu mengkhususkan standar Anda terhadap beberapa konfigurasi lain (seperti sejarah).

Kueri terakhir yang Anda inginkan adalah sebagai berikut:

select Zip from PostalCode p, Defaults d where p.ID=d.PostalCode;
0
Konchog