Bir kaydı bir boole sütunu için gerçek bir değere, diğer tümlerinde de yanlış bir değere sahip olmaya nasıl zorlayabilirim?


20

Bir tablodaki yalnızca bir kaydın, bu tabloya erişebilecek diğer sorgular veya görünümler için "varsayılan" değer olarak kabul edilmesini zorunlu kılmak istiyorum.

Temel olarak, bu sorgu her zaman tam olarak bir satır döneceğini garanti etmek istiyorum:

SELECT ID, Zip 
FROM PostalCodes 
WHERE isDefault=True

Bunu SQL'de nasıl yapabilirim?


1
"Bu sorgu her zaman tam olarak bir satır döneceğini garanti etmek istiyorum" - her zaman? Ne zaman PostalCodesboş olacak? Bir satır zaten bir özelliğe sahipse, aynı SQL deyimi içinde başka bir satır (varsa) true değerine ayarlanmadığı sürece false değerine ayarlanması engellenir mi? Sıfır satırlar işlem sınırları arasındaki özelliğe sahip olabilir mi? Tablodaki son satırın bu özelliğe sahip olması ve silinmesi engellenmeli mi? Deneyim bana "tam olarak bir sıra garanti" nin gerçekte farklı bir şey ifade etme eğiliminde olduğunu, genellikle sadece "en fazla tek sıra" anlamına geldiğini söylüyor.
oneday29

Yanıtlar:


8

Düzenleme: Bu MySQL bilmeden önce SQL Server'a yöneliktir

Bunu yapmanın 4 5 yolu vardır : en çok istenenden en az istenene doğru

Yine de hepsi için geçerli değilse, hangi RDBMS'yi bilmiyoruz

  1. En iyi yol filtrelenmiş bir dizindir. Bu, benzersizliği korumak için DRI kullanır.

  2. Benzersiz bir şekilde hesaplanan sütun (bkz. Jack Douglas'ın cevabı) (düzenleme 2 ile eklendi)

  3. DRI kullanarak filtrelenmiş bir dizin gibi dizinlenmiş / materyalize görünüm

  4. Tetik (diğer cevaplara göre)

  5. Bir UDF ile kısıtlamayı kontrol edin. Bu , eşzamanlılık ve anlık görüntü yalıtımı için güvenli değildir . Bkz Tek İki Üç Dört

Saklı yordam değil, "bir varsayılan" gereksiniminin tabloda olduğunu unutmayın. Saklı yordam, bir udf ile kontrol kısıtlamasıyla aynı eşzamanlılık sorunlarına sahip olacaktır

Not: SO'ya birçok kez sordu:


gbn - filtrelenmiş dizin / DRI çözümü gibi görünüyor SQL 2008 sadece ben seçenek 2 denemek için atlayacak gibi görünüyor
emaynard

10

Not: Bu cevap, askerin MySQL'e özel bir şey istediği anlaşılmadan verildi. Bu yanıt SQL Server'a karşı önyargılıdır.

Bir uygula FİYATLARI bir çağıran tabloya kısıtlamayı UDF ve emin onun dönüş değeri <= 1. UDF sadece NEREDE tablodaki satırları sayabilir yapar isDefault = TRUE. Bu, tabloda 1'den fazla varsayılan satır olmamasını sağlayacaktır.

Bu UDF'nin çok hızlı çalıştığından emin olmak için sütuna bir bitmap veya filtrelenmiş dizin isDefault(platforomunuza bağlı olarak) eklemelisiniz.

Uyarılar

  • Çek kısıtlamalarının belirli sınırlamaları vardır . Yani, bu satırlar henüz bir işlemde işlem görmemiş olsalar bile, değiştirilen her satır için çağrılırlar. Bu nedenle, bir işlemi başlatırsanız, varsayılan olarak yeni bir satır ayarlayın ve ardından eski satırın ayarını kaldırın, çek kısıtlamanız şikayet edecektir. Bunun nedeni, yeni satırı varsayılan yaptıktan, şimdi iki varsayılan olduğunu bulup başarısız olduktan sonra yürütülecek olmasıdır. Bu durumda çözüm, bu işi bir işlemde yapsanız bile, yeni bir varsayılan belirlemeden önce varsayılan satırı ayarladığınızdan emin olmaktır.
  • Gibi Gbn belirttiği , SQL Server'da anlık görüntü yalıtımı kullanıyorsanız UDF'ler tutarsız sonuçlar döndürebilir. Ancak, satır içi bir UDF bu sorunla karşılaşmaz ve yalnızca sayı yapan bir UDF satır içi işlem yapabildiğinden, bu burada bir sorun değildir.
  • Bir veritabanı motorunun işlem gücünün gücü, bir kerede veri kümeleri üzerinde çalışma yeteneğindedir. UDF destekli bir kontrol kısıtlamasıyla motor, değiştirilen her satıra bu UDF'yi seri olarak uygulamakla sınırlı olacaktır. Kullanım durumunuzda, PostalCodestablo için toplu güncellemeler yapmanız olası değildir , ancak bu, set tabanlı etkinliğin olası olduğu durumlar için bir performans endişesi olmaya devam etmektedir.

Tüm bu uyarılar göz önüne alındığında, gbn'nin kontrol kısıtlaması yerine filtrelenmiş benzersiz bir dizin önerisini kullanmanızı öneririm .


4

İşte Saklı Yordam (MySQL Dialect):

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 ;

Tablonuzun temiz olduğundan ve saklı yordamın çalıştığından emin olmak için, Kimlik 200'ün varsayılan olduğunu varsayarak şu adımları çalıştırın:

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;

Saklı Yordam yerine, Tetiklemeye ne dersiniz?

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 ;

Tablonuzun temiz olduğundan ve tetikleyicinin çalıştığından emin olmak için, Kimlik 200'ün varsayılan olduğunu varsayarak şu adımları çalıştırın:

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

Kuralları uygulamak için bir tetikleyici kullanabilirsiniz. Bir UPDATE veya INSERT deyimi isDefault değerini True olarak ayarladığında, tetikleyicideki SQL diğer tüm satırları Yanlış olarak ayarlayabilir.

Bunu uygularken başka durumları da dikkate almanız gerekir. Örneğin, ve UPDATE veya INSERT kümelerinde birden fazla satır Varsayılan Değeri Doğru olarak ayarlarsa ne olur? Kuralı ihlal etmemek için hangi kurallar geçerli olacak?

Ayrıca, herhangi bir temerrütün olmadığı durum mümkün müdür? Bir güncelleme isDefault öğesini True değerinden False değerine ayarladığında ne olur?

Kuralları tanımladıktan sonra, bunları tetikleyicide oluşturabilirsiniz.

Daha sonra, başka bir cevapta belirtildiği gibi, kuralların uygulanmasına yardımcı olmak için bir kontrol kısıtlaması uygulamak istiyorsunuz.


3

Aşağıda örnek bir tablo ve veriler hazırlanmıştır:

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 bayrağını ayarlamak ve temel tabloyu değiştirmek için izinleri kaldırmak için saklı bir yordam oluşturursanız, bunu aşağıdaki sorgu ile zorlayabilirsiniz. Her seferinde tablo taraması gerekir.

DECLARE @Id INT
SET @Id = 10

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

Tablonun boyutuna bağlı olarak, IsDefault (DESC) üzerindeki bir dizin, tam bir taramadan kaçınarak aşağıdaki sorgulardan biriyle veya her ikisiyle sonuçlanabilir.

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)

Temel tablodan izinleri kaldıramıyorsanız, başka yollarla bütünlüğü zorlamanız gerekiyorsa ve SQL2008 kullanıyorsanız, filtrelenmiş benzersiz bir dizinden yararlanabilirsiniz:

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

1

Filtrelenmiş dizinleri olmayan MySQL için aşağıdakine benzer bir şey yapabilirsiniz. MySQL'in benzersiz dizinlerde birden fazla NULL'a izin verdiği gerçeğinden yararlanır ve yalnızca NULL ve 1 değerleri olarak kullanılabilen bir veri tipini simüle etmek için özel bir tablo ve yabancı bir anahtar kullanır.

Bu çözümle, true / false yerine 1 / NULL kullanırsınız.

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`))*/

Sanırım yanlış gidebilecek tek şey, birinin DefaultFlagmasaya bir satır eklemesi. Bunun büyük bir endişe olmadığını düşünüyorum ve gerçekten güvende olmak istiyorsanız her zaman izinlerle düzeltebilirsiniz.


0
SELECT ID, Zip FROM PostalCodes WHERE isDefault=True 

Bu aslında size problemler veriyordu çünkü tarlayı yanlış yere koydunuz ve hepsi bu.

Aradığınız kimliğe sahip PostalCode ile bir 'Varsayılanlar' tablosuna sahip olun. Genel olarak, tablolarınızı bir kayda göre adlandırmak da iyidir, bu nedenle ilk tablonuzun adı 'PostalCode' olarak adlandırılır. Varsayılanlarınızı başka bir yapılandırmaya (geçmiş gibi) göre özelleştirmeniz gerekene kadar varsayılanlar tek satırlık bir tablo olacaktır.

İstediğiniz son sorgu aşağıdaki gibidir:

select Zip from PostalCode p, Defaults d where p.ID=d.PostalCode;
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.