Kısıtlama - bir boole satırı doğru, diğer tüm satırlar yanlış


14

Bir sütun var: standard BOOLEAN NOT NULL

Bir satır True ve diğer tüm Yanlışları uygulamak istiyorum. Bu kısıtlamaya bağlı olarak hiçbir FK veya başka bir şey değildir. Ben plpgsql ile başarabilirim biliyorum, ama bu bir balyoz gibi görünüyor. A CHECKveya UNIQUEkısıtlama gibi bir şeyi tercih ederim . Ne kadar basit o kadar iyi.

Bir satır True olmalıdır, hepsi False olamaz (bu nedenle eklenen ilk satır True olmalıdır).

Satırın güncellenmesi gerekecek, yani tüm satırlar önce False ve sonra bir satır True olarak ayarlanabileceğinden, güncellemeler yapılana kadar kısıtlamaları kontrol etmek için beklemek zorundayım.

Orada arasında bir FK olduğunu products.tax_rate_idve tax_rate.idancak yeni ürünler yaratma kolaylaştırmak için kullanıcı seçilebilir olduğu varsayılan veya standart vergi oranı ile hiçbir ilgisi yoktur ..

Önemli ise PostgreSQL 9.5.

Arka fon

Tablo vergi oranıdır. Vergi oranlarından biri varsayılan standarddeğerdir ( varsayılan olarak Postgres komutu olduğundan). Yeni bir ürün eklendiğinde, ürüne standart vergi oranı uygulanır. Hayır standardise, veritabanı ya bir tahmin yapmalı ya da her türlü gereksiz kontrol yapmalıdır. Basit çözüm, diye düşündüm, bir tane olduğundan emin olmaktı standard.

Yukarıdaki "varsayılan" ile, sunum katmanı (UI) için kastediyorum. Varsayılan vergi oranını değiştirmek için bir kullanıcı seçeneği vardır. Ben ya GUI / kullanıcı tax_rate_id NULL ayarlamak için denemek için değil, ya da sadece varsayılan bir vergi oranı ayarlamak için ekstra kontrol eklemek gerekir.


Cevabın var mı?
Erwin Brandstetter

Evet cevabım var, girişiniz için çok teşekkürler, @ErwinBrandstetter. Şimdilik bir tetikleyiciye yaslanıyorum. Bu benim zamanımda açık kaynak kodlu bir proje. Aslında uyguladığımda, kullandığım cevabı işaretleyeceğim.
TheGtknerd

Yanıtlar:


15

Değişke 1

Tüm ihtiyacınız olan tek bir sütun olduğundan standard = true, diğer tüm satırlarda standardı NULL olarak ayarlayın. Daha sonra UNIQUENULL değerleri ihlal etmediği için düz bir kısıtlama çalışır:

CREATE TABLE taxrate (
   taxrate int PRIMARY KEY
 , standard bool DEFAULT true
 , CONSTRAINT standard_true_or_null CHECK (standard) -- yes, that's the whole constraint
 , CONSTRAINT standard_only_1_true UNIQUE (standard)
);

DEFAULTgirilen ilk satırın varsayılan olması gerektiğini belirten isteğe bağlı bir hatırlatmadır. O değil uygulamak şey. Birden fazla satır standard = trueayarlayamasanız da, yine de tüm satırları NULL olarak ayarlayabilirsiniz. Bunu tek bir tablodaki kısıtlamalarla önlemenin temiz bir yolu yoktur . CHECKkısıtlamalar diğer satırları dikkate almaz (kirli numaralar olmadan).

İlişkili:

Güncellemek için:

BEGIN;
UPDATE taxrate SET standard = NULL WHERE standard;
UPDATE taxrate SET standard = TRUE WHERE taxrate = 2;
COMMIT;

Gibi bir komuta izin vermek için (kısıtlamanın yalnızca ifadenin sonunda yerine getirildiği durumlarda):

WITH kingdead AS (
   UPDATE taxrate
   SET standard = NULL
   WHERE standard
   )
UPDATE taxrate
SET standard = TRUE
WHERE taxrate = 1;

.. UNIQUEkısıtlamanın olması gerekirdi DEFERRABLE. Görmek:

dbfiddle burada

Değişke 2

Bir var ikinci bir tablo tek bir satır gibi olan:

Bunu süper kullanıcı olarak oluşturun:

CREATE TABLE taxrate (
   taxrate int PRIMARY KEY
);

CREATE TABLE taxrate_standard (
   taxrate int PRIMARY KEY REFERENCES taxrate
);

CREATE UNIQUE INDEX taxrate_standard_singleton ON taxrate_standard ((true));  -- singleton

REVOKE DELETE ON TABLE taxrate_standard FROM public;  -- can't delete

INSERT INTO taxrate (taxrate) VALUES (42);
INSERT INTO taxrate_standard (taxrate) VALUES (42);

Şimdi her zaman standarda işaret eden tek bir satır vardır (bu basit durumda standart oranı doğrudan temsil eder). Sadece bir süper kullanıcı onu kırabilir. Bir tetikleyici ile buna da izin vermeyebilirsiniz BEFORE DELETE.

dbfiddle burada

İlişkili:

Varyant 1'deki ileVIEW aynı görmek için a ekleyebilirsiniz :

CREATE VIEW taxrate_combined AS
SELECT t.*, (ts.taxrate = t.taxrate) AS standard
FROM   taxrate t
LEFT   JOIN taxrate_standard ts USING (taxrate);

İstediğiniz tek şeyin standart ücret olduğu sorgularda (yalnızca) taxrate_standard.taxratedoğrudan kullanın.


Daha sonra eklediniz:

products.tax_rate_idVe arasında bir FK vartax_rate.id

Bir zavallı adamın uygulama varyantı 2'nin sadece bir satır eklemek olacaktır productsstandart vergi oranına işaret (veya herhangi bir benzer bir tablo); kukla bir ürün, "Standart vergi oranı" diyebilirsiniz - kurulumunuz buna izin veriyorsa.

FK kısıtlamaları referans bütünlüğünü zorunlu kılar. Tamamlamak tax_rate_id IS NOT NULLiçin satırı uygulayın (genel olarak sütun için durum böyle değilse). Ve silinmesine izin verme. Her ikisi de tetikleyicilere konulabilir. Ekstra tablo yok, ama daha az zarif ve güvenilir değil.


2
Son derece iki tablo yaklaşımı öneriyoruz. Ben de OP CROSS JOINstandart, karşı LEFT JOINbelirli ve daha sonra COALESCEikisi arasında nasıl görebilirsiniz görebilirsiniz bu varyasyon için bir örnek sorgu eklemenizi öneririm .
jpmc26

2
+1, ekstra tablo hakkında aynı fikri vardı ama düzgün bir cevap yazmak için zaman yoktu. İlk tablo ve hakkında CONSTRAINT standard_only_1_true UNIQUE (standard): Sanırım tablo büyük olmayacak bu yüzden çok önemli değil ama kısıtlama tüm tablo üzerinde bir dizin tanımlayacağından, WHERE (standard)daha az alan kullanan kısmi benzersiz bir dizin olmaz mı?
ypercubeᵀᴹ

@ ypercubeᵀᴹ: Evet, tüm tablodaki dizin daha büyük, bu varyant için bir dezavantaj. Ama dediğin gibi: Açıkçası küçük bir masa, bu yüzden önemli değil. Sadece kısıtlamaları olan en basit standart çözümü hedefliyordum. Kavramın ispatı. Şahsen, jpmc26 ile beraberim ve varyant 2'yi şiddetle destekliyorum
Erwin Brandstetter

9

Filtrelenmiş bir dizin kullanabilirsiniz

create table test
(
    id int primary key,
    foo bool
);
CREATE UNIQUE INDEX only_one_row_with_column_true_uix 
    ON test (foo) WHERE (foo);  --> where foo is true
insert into test values (1, false);
insert into test values (2, true);
insert into test values (3, false);
insert into test values (4, false);
insert into test values (5, true);
HATA: yinelenen anahtar değeri "only_one_row_with_column_true_uix" benzersiz sınırlamasını ihlal ediyor
DETAY: Anahtar (foo) = (t) zaten var.

dbfiddle burada


Ama dediğin gibi, ilk satır doğru olmalı, o zaman bir CHECK kısıtlaması kullanabilirsiniz, ancak bir işlevi kullanarak bile ilk satırı daha sonra silebilirsiniz.

create function check_one_true(new_foo bool)
returns int as
$$
begin
    return 
    (
        select count(*) + (case new_foo when true then 1 else 0 end)
        from test 
        where foo = true
    );
end
$$
language plpgsql stable;
alter table test 
    add constraint ck_one_true check(check_one_true(foo) = 1); 
insert into test values (1, true);
insert into test values (2, false);
insert into test values (3, false);
insert into test values (4, false);
insert into test values (5, true);
HATA: "test" ilişkisi için yeni satır, "ck_one_true" kontrol kısıtlamasını ihlal ediyor
DETAY: Arızalı satır (5, t) içerir.

select * from test;
id | foo
-: | : -
 1 | t  
 2 | f  
 3 | f  
 4 | f  
delete from test where id = 1;

dbfiddle burada


İlk satırın (foo true) hiçbir zaman silinmemesini sağlamak için SİLME ÖNCESİ tetikleyicisi ekleyerek çözebilirsiniz.

create function dont_delete_foo_true()
returns trigger as
$x$
begin
    if old.foo then
        raise exception 'Can''t delete row where foo is true.';
    end if;
    return old;
end;
$x$ language plpgsql;
create trigger trg_test_delete
before delete on test
for each row 
execute procedure dont_delete_foo_true();
delete from test where id = 1;

HATA: foo'nun geçerli olduğu satır silinemez.

dbfiddle burada

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.