बाधा - एक बूलियन पंक्ति सही है, अन्य सभी पंक्तियाँ झूठी हैं


14

मेरे पास एक कॉलम है: standard BOOLEAN NOT NULL

मैं एक पंक्ति ट्रू, और अन्य सभी गलत लागू करना चाहूंगा। इस बाधा के आधार पर कोई FK या कुछ और नहीं हैं। मुझे पता है कि मैं इसे plpgsql के साथ पूरा कर सकता हूं, लेकिन यह एक स्लेजहैमर की तरह लगता है। मैं एक CHECKया UNIQUEबाधा की तरह कुछ पसंद करेंगे । बेहतर सरल है।

एक पंक्ति सत्य होनी चाहिए, वे सभी गलत नहीं हो सकती हैं (इसलिए पहली पंक्ति में सम्मिलित सत्य होना होगा)।

पंक्ति को अद्यतन करने की आवश्यकता होगी, जिसका अर्थ है कि मुझे अपडेट होने तक बाधाओं की जांच करने के लिए इंतजार करना होगा, क्योंकि सभी पंक्तियों को पहले गलत और एक पंक्ति को सही पर सेट किया जा सकता है।

वहाँ के बीच एक FK है products.tax_rate_idऔर tax_rate.id, लेकिन यह डिफ़ॉल्ट या मानक कर की दर है, जो नए उत्पादों को बनाने को कम करने के उपयोगकर्ता चयन है साथ कोई संबंध नहीं है ..

अगर यह मायने रखता है तो PostgreSQL 9.5।

पृष्ठभूमि

तालिका कर की दर है। कर दरों में से एक डिफ़ॉल्ट है ( standardचूंकि डिफ़ॉल्ट एक Postgres कमांड है)। जब कोई नया उत्पाद जोड़ा जाता है, तो मानक कर दर उत्पाद पर लागू होती है। यदि कोई नहीं है standard, तो डेटाबेस को या तो एक अनुमान लगाना होगा या सभी प्रकार की अनावश्यक जांच करनी चाहिए। मैंने सोचा था कि सरल समाधान, यह सुनिश्चित करना था कि ए standard

ऊपर "डिफ़ॉल्ट" से, मेरा मतलब प्रेजेंटेशन लेयर (UI) से है। डिफ़ॉल्ट कर की दर को बदलने के लिए एक उपयोगकर्ता विकल्प है। मुझे या तो यह सुनिश्चित करने के लिए अतिरिक्त चेक जोड़ने की आवश्यकता है कि GUI / उपयोगकर्ता NULL को tax_rate_id सेट करने का प्रयास नहीं करता है, या फिर बस एक डिफ़ॉल्ट कर दर निर्धारित करता है।


तो क्या आपका जवाब आपके पास है?
इरविन ब्रान्डस्टेट्टर

हां मेरे पास मेरा जवाब है, आपके इनपुट के लिए बहुत बहुत धन्यवाद, @ErwinBrandstetter। मैं अब एक ट्रिगर की ओर झुक रहा हूं। यह मेरे अपने समय पर एक ओपन सोर्स प्रोजेक्ट है। जब मैं वास्तव में इसे लागू करता हूं, तो मैं स्वीकार किए गए उत्तर का उपयोग करूंगा।
theGtknerd

जवाबों:


15

भिन्न 1

चूँकि आपको केवल एक ही कॉलम की आवश्यकता है standard = true, अन्य सभी पंक्तियों में NULL के लिए मानक सेट करें। तब एक सादा UNIQUEअवरोध काम करता है, क्योंकि NULL मान इसका उल्लंघन नहीं करते हैं:

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)
);

DEFAULTएक वैकल्पिक अनुस्मारक है कि दर्ज की गई पहली पंक्ति डिफ़ॉल्ट बन जानी चाहिए। यह कुछ भी लागू नहीं कर रहा है। जब आप एक से अधिक पंक्ति सेट नहीं कर सकते standard = true, तब भी आप सभी पंक्तियाँ NULL सेट कर सकते हैं। एक ही तालिका में केवल बाधाओं के साथ इसे रोकने का कोई साफ तरीका नहीं है । CHECKबाधाएं अन्य पंक्तियों (गंदी चाल के बिना) पर विचार नहीं करती हैं।

सम्बंधित:

नवीनतम बनाना:

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

आदेश देने के लिए (जैसे कि बाधा केवल कथन के अंत में संतुष्ट है):

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

.. UNIQUEअड़चन तो होनी ही थी DEFERRABLE। देख:

यहाँ dbfiddle

भिन्न 2

एक है दूसरी तालिका की तरह एक ही पंक्ति के साथ:

इसे सुपरयुसर के रूप में बनाएँ:

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);

अब हमेशा मानक को इंगित करने वाली एक एकल पंक्ति होती है (इस साधारण मामले में भी सीधे मानक दर का प्रतिनिधित्व करती है)। केवल एक सुपरयुसर इसे तोड़ सकता था। आप एक ट्रिगर के साथ, उसे भी अस्वीकार कर सकते हैं BEFORE DELETE

यहाँ dbfiddle

सम्बंधित:

आप इसे वैरिएंट 1VIEW में देखने के लिए जोड़ सकते हैं :

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

उन सभी प्रश्नों में जहां आप चाहते हैं कि मानक दर है, taxrate_standard.taxrateसीधे (केवल) का उपयोग करें ।


आपने बाद में जोड़ा:

के बीच एक FK है products.tax_rate_idऔरtax_rate.id

एक गरीब आदमी का वेरिएंट 2 का कार्यान्वयनproducts मानक कर दर की ओर इशारा करते हुए (या किसी भी समान तालिका) में एक पंक्ति जोड़ने के लिए होगा ; एक डमी उत्पाद जिसे आप "मानक कर दर" कह सकते हैं - यदि आपका सेटअप इसे अनुमति देता है।

FK बाधाओं को संदर्भित अखंडता लागू करता है। इसे पूरा करने के tax_rate_id IS NOT NULLलिए, पंक्ति के लिए लागू करें (यदि यह सामान्य रूप से कॉलम के लिए ऐसा नहीं है)। और इसके विलोपन को अस्वीकार करें। दोनों को ट्रिगर में रखा जा सकता है। कोई अतिरिक्त तालिका नहीं, लेकिन कम सुरुचिपूर्ण और विश्वसनीय नहीं है।


2
अत्यधिक दो टेबल दृष्टिकोण की सलाह देते हैं। मैं उस विविधता में एक उदाहरण क्वेरी जोड़ने का सुझाव दूंगा ताकि ओपी CROSS JOINमानक के खिलाफ, LEFT JOINविशिष्ट के खिलाफ और फिर COALESCEदोनों के बीच कैसे देख सके।
jpmc26

2
+1, मुझे अतिरिक्त तालिका के बारे में एक ही विचार था लेकिन ठीक से उत्तर लिखने का समय नहीं था। पहली तालिका और के बारे में CONSTRAINT standard_only_1_true UNIQUE (standard): मुझे लगता है कि तालिका बड़ी नहीं होगी, इसलिए यह ज्यादा मायने नहीं रखता है, क्योंकि चूंकि बाधा पूरी मेज पर एक सूचकांक को परिभाषित करेगी, इसलिए WHERE (standard)कम जगह के उपयोग के साथ एक आंशिक अद्वितीय सूचकांक नहीं होगा ?
ypercube y

@ ypercube y: हाँ, पूरी मेज पर सूचकांक बड़ा है, इस संस्करण के लिए एक दोष है। लेकिन जैसा आपने कहा: यह स्पष्ट रूप से एक छोटी सी मेज है, इसलिए यह शायद ही मायने रखता है। मैं केवल बाधाओं के साथ सबसे सरल मानक समाधान के लिए लक्ष्य कर रहा था। अवधारणा के सुबूत। व्यक्तिगत रूप से, मैं jpmc26 के साथ हूं और दृढ़ता से पक्ष में हूं। 2.
इरविन ब्रान्डसेट्टर

9

आप एक फ़िल्टर किए गए इंडेक्स का उपयोग कर सकते हैं

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);
त्रुटि: डुप्लिकेट कुंजी मान अद्वितीय बाधा का उल्लंघन करता है "only_one_row_with_column_true_u/"
विवरण: कुंजी (foo) = (t) पहले से मौजूद है।

यहाँ dbfiddle


लेकिन जैसा कि आपने कहा, पहली पंक्ति सही होनी चाहिए, फिर आप एक CHECK बाधा का उपयोग कर सकते हैं, लेकिन एक फ़ंक्शन का उपयोग करके भी आप पहली पंक्ति को बाद में हटा सकते हैं।

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);
त्रुटि: संबंध के लिए नई पंक्ति "परीक्षण" चेक बाधा "ck_one_true" का उल्लंघन करता है
विवरण: असफल पंक्ति में (5, t) है।

select * from test;
आईडी | foo
-: | : -
 1 | टी  
 2 | च  
 3 | च  
 4 | च  
delete from test where id = 1;

यहाँ dbfiddle


पहली पंक्ति (foo सच है) यह सुनिश्चित करने के लिए आप पहले कभी ट्रिगर से जोड़कर इसे हल नहीं कर सकते।

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;

त्रुटि: पंक्ति को हटा नहीं सकते जहां फू सत्य है।

यहाँ dbfiddle

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.