2017-06-21 11 views
14

चलो कहते हैं कि मैं निम्नलिखित PostgreSQL तालिका करते हैं:PostgreSQL बाधा का उपयोग कर उपसर्गों

id | key 
---+-------- 
1 | 'a.b.c' 

मैं एक महत्वपूर्ण एक अन्य प्रमुख का एक उपसर्ग है कि साथ डालने रिकॉर्ड को रोकने के लिए की जरूरत है। लेकिन

  • 'a.b.b'

निम्नलिखित चाबियाँ नहीं स्वीकार किया जाना चाहिए: उदाहरण के लिए, मैं डालने के लिए सक्षम होना चाहिए

  • 'a.b'
  • 'a.b.c'
  • 'a.b.c.d'

क्या यह हासिल करने का कोई तरीका है - या तो एक बाधा या लॉकिंग तंत्र द्वारा (डालने से पहले अस्तित्व की जांच करें)?

उत्तर

11

यह समाधान PostgreSQL user-defined operators और बहिष्करण बाधाओं (base syntax, more details) पर आधारित है।

नोट: अधिक परीक्षण से पता चलता है कि यह समाधान काम नहीं करता है (अभी तक)। नीचे देखें।

  1. एक फ़ंक्शन है_common_prefix (टेक्स्ट, टेक्स्ट) जो आपको आवश्यकतानुसार तर्कसंगत रूप से गणना करेगा। फ़ंक्शन को IMMUTABLE के रूप में चिह्नित करें।

    ERROR: operator <~>(text,text) is not a member of operator family "text_ops" 
        DETAIL: The exclusion operator must be related to the index operator class for the constraint. 
    
    :

    CREATE OR REPLACE FUNCTION 
    has_common_prefix(text,text) 
    RETURNS boolean 
    IMMUTABLE STRICT 
    LANGUAGE SQL AS $$ 
        SELECT position ($1 in $2) = 1 OR position ($2 in $1) = 1 
    $$; 
    
  2. सूचकांक

    CREATE OPERATOR <~> (
        PROCEDURE = has_common_prefix, 
        LEFTARG = text, 
        RIGHTARG = text, 
        COMMUTATOR = <~> 
    ); 
    
  3. बनाएं बहिष्कार बाधा

    CREATE TABLE keys (key text); 
    
    ALTER TABLE keys 
        ADD CONSTRAINT keys_cannot_have_common_prefix 
        EXCLUDE (key WITH <~>); 
    

हालांकि के लिए एक ऑपरेटर बनाएँ, अंतिम बिंदु इस त्रुटि का उत्पादन

इसका कारण यह है एक सूचकांक PostgreSQL जरूरत तार्किक ऑपरेटरों शारीरिक अनुक्रमण तरीकों के साथ बंधे होने के लिए, संस्थाओं कैलेस "ऑपरेटर वर्ग" के माध्यम से बनाने के लिए। तो हम उस तर्क प्रदान करने की आवश्यकता:

CREATE OR REPLACE FUNCTION keycmp(text,text) 
RETURNS integer IMMUTABLE STRICT 
LANGUAGE SQL AS $$ 
    SELECT CASE 
    WHEN $1 = $2 OR position ($1 in $2) = 1 OR position ($2 in $1) = 1 THEN 0 
    WHEN $1 < $2 THEN -1 
    ELSE 1 
    END 
$$; 

CREATE OPERATOR CLASS key_ops FOR TYPE text USING btree AS 
    OPERATOR 3 <~> (text, text), 
    FUNCTION 1 keycmp (text, text) 
; 

ALTER TABLE keys 
    ADD CONSTRAINT keys_cannot_have_common_prefix 
    EXCLUDE (key key_ops WITH <~>); 

अब, यह काम करता है:

INSERT INTO keys SELECT 'ara'; 
INSERT 0 1 
INSERT INTO keys SELECT 'arka'; 
INSERT 0 1 
INSERT INTO keys SELECT 'barka'; 
INSERT 0 1 
INSERT INTO keys SELECT 'arak'; 
psql:test.sql:44: ERROR: conflicting key value violates exclusion constraint "keys_cannot_have_common_prefix" 
DETAIL: Key (key)=(arak) conflicts with existing key (key)=(ara). 
INSERT INTO keys SELECT 'bark'; 
psql:test.sql:45: ERROR: conflicting key value violates exclusion constraint "keys_cannot_have_common_prefix" 
DETAIL: Key (key)=(bark) conflicts with existing key (key)=(barka). 

नोट: अधिक परीक्षण से पता चलता इस समाधान अभी तक काम नहीं करता है: पिछले सम्मिलित करें विफल करना चाहिए।

INSERT INTO keys SELECT 'a'; 
INSERT 0 1 
INSERT INTO keys SELECT 'ac'; 
ERROR: conflicting key value violates exclusion constraint "keys_cannot_have_common_prefix" 
DETAIL: Key (key)=(ac) conflicts with existing key (key)=(a). 
INSERT INTO keys SELECT 'ab'; 
INSERT 0 1 
+0

मैं इस तरह से जा रहा था, तो मैंने कंपनी पोस्टग्रेस को बहुत पुराना पाया, 'एक्स्क्लुड' बाधाओं का समर्थन नहीं करता! जो फ़ंक्शन आप चाहते हैं वह स्थिति ($ 2 में $ 1)> 0 या स्थिति ($ 1 में $ 2)> 0. –

+0

तालिका में कई रिकॉर्ड हो सकते हैं। क्या यह दृष्टिकोण किसी भी सूचकांक का उपयोग कर सकता है? –

+0

@ जुराज हां, इस एक्सक्लुड फीचर को हमेशा एक इंडेक्स की आवश्यकता होती है, इसलिए बाधा तेजी से होती है। बीटीडब्ल्यू - समाधान अब पूरा हो गया है तो कृपया इसका परीक्षण करें (9.1+ पर काम करना चाहिए) – filiprem

2

यहां एक जांच आधारित समाधान है - यह आपकी आवश्यकताओं को पूरा कर सकता है।

CREATE TABLE keys (id serial primary key, key text); 

CREATE OR REPLACE FUNCTION key_check(text) 
RETURNS boolean 
STABLE STRICT 
LANGUAGE SQL AS $$ 
    SELECT NOT EXISTS (
    SELECT 1 FROM keys 
     WHERE key ~ ('^' || $1) 
     OR $1 ~ ('^' || key) 
); 
$$; 

ALTER TABLE keys 
    ADD CONSTRAINT keys_cannot_have_common_prefix 
    CHECK (key_check(key)); 

पीएस। दुर्भाग्य से, यह एक बिंदु (बहु पंक्ति प्रविष्टियों) में विफल रहता है।

4

आप इस लक्ष्य को हासिल करने के लिए ltree मॉड्यूल का उपयोग कर सकते हैं, तो यह आपको श्रेणीबद्ध पेड़ की तरह संरचना बनाना चाहते करने देगा। पहिया को फिर से शुरू करने, जटिल नियमित अभिव्यक्तियों को बनाने और इसी तरह से रोकने से रोकने में भी मदद मिलेगी। आपको बस postgresql-contrib पैकेज स्थापित करने की आवश्यकता है। एक नज़र डालें:

--Enabling extension 
CREATE EXTENSION ltree; 

--Creating our test table with a pre-loaded data 
CREATE TABLE test_keys AS 
    SELECT 
     1 AS id, 
     'a.b.c'::ltree AS key_path; 

--Now we'll do the trick with a before trigger 
CREATE FUNCTION validate_key_path() RETURNS trigger AS $$ 
    BEGIN 

     --This query will do our validation. 
     --It'll search if a key already exists in 'both' directions 
     --LIMIT 1 because one match is enough for our validation :)  
     PERFORM * FROM test_keys WHERE key_path @> NEW.key_path OR key_path <@ NEW.key_path LIMIT 1; 

     --If found a match then raise a error   
     IF FOUND THEN 
      RAISE 'Duplicate key detected: %', NEW.key_path USING ERRCODE = 'unique_violation'; 
     END IF; 

     --Great! Our new row is able to be inserted  
     RETURN NEW; 
    END; 
$$ LANGUAGE plpgsql; 

CREATE TRIGGER test_keys_validator BEFORE INSERT OR UPDATE ON test_keys 
    FOR EACH ROW EXECUTE PROCEDURE validate_key_path();  

--Creating a index to speed up our validation...    
CREATE INDEX idx_test_keys_key_path ON test_keys USING GIST (key_path); 

--The command below will work  
INSERT INTO test_keys VALUES (2, 'a.b.b'); 

--And the commands below will fail 
INSERT INTO test_keys VALUES (3, 'a.b'); 
INSERT INTO test_keys VALUES (4, 'a.b.c'); 
INSERT INTO test_keys VALUES (5, 'a.b.c.d'); 

बेशक मैंने इस परीक्षा के लिए प्राथमिक कुंजी और अन्य बाधाओं को बनाने से परेशान नहीं किया। लेकिन ऐसा करने के लिए मत भूलना। इसके अलावा, वहाँ ltree मॉड्यूल पर बहुत अधिक है की तुलना में मैं, दिखा रहा हूँ अगर आप, अपने दस्तावेज़ों पर एक बार देख ले शायद आप वहाँ जवाब मिल जाएगा कुछ अलग की जरूरत है।

4

आप ट्रिगर नीचे की कोशिश कर सकते हैं। कृपया ध्यान दें कि key एसक्यूएल आरक्षित शब्द है। तो मैं सुझाव दूंगा कि आप इसे अपनी तालिका में कॉलम नाम के रूप में उपयोग करने से बचें। मैं अपने परीक्षण के लिए भी तालिका वाक्य रचना बनाने के जोड़ लिया है:

CREATE TABLE my_table 
(myid INTEGER, mykey VARCHAR(50)); 

CREATE FUNCTION check_key_prefix() RETURNS TRIGGER AS $check_key_prefix$ 
    DECLARE 
    v_match_keys INTEGER; 
    BEGIN 
    v_match_keys = 0; 
    SELECT COUNT(t.mykey) INTO v_match_keys 
    FROM my_table t 
    WHERE t.mykey LIKE CONCAT(NEW.mykey, '%') 
    OR NEW.mykey LIKE CONCAT(t.mykey, '%'); 

    IF v_match_keys > 0 THEN 
     RAISE EXCEPTION 'Prefix Key Error occured.'; 
    END IF; 

    RETURN NEW; 
    END; 
$check_key_prefix$ LANGUAGE plpgsql; 

CREATE TRIGGER check_key_prefix 
BEFORE INSERT OR UPDATE ON my_table 
FOR EACH ROW 
EXECUTE PROCEDURE check_key_prefix(); 
0

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

केवल धारणा वहां मौजूद तालिका में कम से कम 1 पंक्ति है। (*)

तालिका:।

create table my_table 
(
    id integer primary key, 
    key varchar(100) 
); 
धारणा की वजह से

, हम कम से कम 1 पंक्ति होगा (*)

insert into my_table (id, key) values (1, 'a.b.c'); 

अब जादू एसक्यूएल। चाल डालने के लिए अपने कुंजी मान द्वारा p_key मूल्य की जगह है। मैंने जानबूझकर, उस कथन को संग्रहीत प्रक्रिया में नहीं रखा है। क्योंकि अगर आप इसे अपने आवेदन पक्ष में ले जाना चाहते हैं तो मैं इसे सीधे आगे बढ़ाना चाहता हूं। लेकिन आमतौर पर संग्रहीत प्रक्रिया में एसक्यूएल डाल बेहतर है।

insert into my_table (id, key) 
    select (select max(id) + 1 from my_table), p_key 
     from my_table 
     where not exists (select 'p' from my_table where key like p_key || '%' or p_key like key || '%') 
     limit 1; 

अब परीक्षण:

-- 'a.b.b' => Inserts 
insert into my_table (id, key) 
    select (select max(id) + 1 from my_table), 'a.b.b' 
     from my_table 
     where not exists (select 'p' from my_table where key like 'a.b.b' || '%' or 'a.b.b' like key || '%') 
     limit 1; 


-- 'a.b' => does not insert 
insert into my_table (id, key) 
    select (select max(id) + 1 from my_table), 'a.b' 
     from my_table 
     where not exists (select 'p' from my_table where key like 'a.b' || '%' or 'a.b' like key || '%') 
     limit 1; 


-- 'a.b.c' => does not insert 
insert into my_table (id, key) 
    select (select max(id) + 1 from my_table), 'a.b.c' 
     from my_table 
     where not exists (select 'p' from my_table where key like 'a.b.c' || '%' or 'a.b.c' like key || '%') 
     limit 1; 

-- 'a.b.c.d' does not insert 
insert into my_table (id, key) 
    select (select max(id) + 1 from my_table), 'a.b.c.d' 
     from my_table 
     where not exists (select 'p' from my_table where key like 'a.b.c.d' || '%' or 'a.b.c.d' like key || '%') 
     limit 1; 

(*) आप चाहें तो आप दोहरी तालिका की तरह एक Oracle शुरू करने से एकल पंक्ति के इस अस्तित्व से छुटकारा पा सकते हैं। यदि आप सम्मिलित कथन को संशोधित करना चाहते हैं तो सीधे आगे बढ़ें। अगर आप ऐसा करना चाहते हैं तो मुझे बताएं।

0

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

एक उच्च स्तर पर, इस दृष्टिकोण उपसर्गों की एक सूची में नीचे प्रत्येक कुंजी टूट जाता है और पाठकों-लेखक ताला शब्दों के कुछ इसी तरह लागू होता है: कुंजी के किसी भी संख्या कुंजियों में से कोई भी रूप के रूप में लंबे समय से एक उपसर्ग साझा कर सकते हैं के बराबर होती है उपसर्ग। इसे पूरा करने के लिए, उपसर्गों की सूची में एक ध्वज के साथ कुंजी शामिल है जो इसे टर्मिनल उपसर्ग के रूप में चिह्नित करती है।

द्वितीयक तालिका इस तरह दिखती है। हम ध्वज के लिए BOOLEAN की बजाय CHAR का उपयोग करते हैं क्योंकि बाद में हम एक बाधा जोड़ देंगे जो बूलियन कॉलम पर काम नहीं करता है।

CREATE TABLE prefixes (
    id INTEGER NOT NULL, 
    prefix TEXT NOT NULL, 
    is_terminal CHAR NOT NULL, 

    CONSTRAINT prefixes_id_fk 
    FOREIGN KEY (id) 
    REFERENCES your_table (id) 
    ON DELETE CASCADE, 

    CONSTRAINT prefixes_is_terminal 
    CHECK (is_terminal IN ('t', 'f')) 
); 

अब हम भी prefixes में पंक्तियों को सम्मिलित करने के your_table में डालने पर एक ट्रिगर निर्धारित करने होंगे ऐसी है कि

INSERT INTO your_table (id, key) VALUES (1, ‘abc'); 

का कारण बनता है

INSERT INTO prefixes (id, prefix, is_terminal) VALUES (1, 'a', ‘f’); 
INSERT INTO prefixes (id, prefix, is_terminal) VALUES (1, 'ab', ‘f’); 
INSERT INTO prefixes (id, prefix, is_terminal) VALUES (1, 'abc', ’t’); 

ट्रिगर समारोह कैसा लग सकता है इस। मैं केवल INSERT मामले को कवर कर रहा हूं, लेकिन UPDATE को संभालने के साथ-साथ पुराने उपसर्गों को हटाकर और फिर नए डालने के लिए फ़ंक्शन को बनाया जा सकता है। DELETE केस prefixes पर कैस्केडिंग विदेशी-कुंजी बाधा से ढका हुआ है।

CREATE OR REPLACE FUNCTION insert_prefixes() RETURNS TRIGGER AS $$ 
DECLARE 
    is_terminal CHAR := 't'; 
    remaining_text TEXT := NEW.key; 
BEGIN 
    LOOP 
    IF LENGTH(remaining_text) <= 0 THEN 
     EXIT; 
    END IF; 

    INSERT INTO prefixes (id, prefix, is_terminal) 
     VALUES (NEW.id, remaining_text, is_terminal); 

    is_terminal := 'f'; 
    remaining_text := LEFT(remaining_text, -1); 
    END LOOP; 

    RETURN NEW; 
END; 
$$ LANGUAGE plpgsql; 

हम इस फ़ंक्शन को सामान्य रूप से ट्रिगर के रूप में तालिका में जोड़ते हैं।

CREATE TRIGGER insert_prefixes 
AFTER INSERT ON your_table 
FOR EACH ROW 
    EXECUTE PROCEDURE insert_prefixes(); 

एक बहिष्करण बाधा और एक आंशिक अद्वितीय सूचकांक एक पंक्ति जहां is_terminal = ’t’ अपने is_terminal मूल्य की परवाह किए बिना एक ही उपसर्ग की एक और पंक्ति के साथ टकराते नहीं कर सकते हैं, और वहाँ is_terminal = ’t’ साथ केवल एक पंक्ति है कि कि लागू करेंगे:

ALTER TABLE prefixes ADD CONSTRAINT prefixes_forbid_conflicts 
    EXCLUDE USING gist (prefix WITH =, is_terminal WITH <>); 

CREATE UNIQUE INDEX ON prefixes (prefix) WHERE is_terminal = 't'; 

यह नई पंक्तियों को अनुमति देता है जो संघर्ष नहीं करते हैं लेकिन मल्टी-पंक्ति INSERT सहित संघर्ष करने वाले लोगों को रोकते हैं।

db=# INSERT INTO your_table (id, key) VALUES (1, 'a.b.c'); 
INSERT 0 1 

db=# INSERT INTO your_table (id, key) VALUES (2, 'a.b.b'); 
INSERT 0 1 

db=# INSERT INTO your_table (id, key) VALUES (3, 'a.b'); 
ERROR: conflicting key value violates exclusion constraint "prefixes_forbid_conflicts" 

db=# INSERT INTO your_table (id, key) VALUES (4, 'a.b.c'); 
ERROR: duplicate key value violates unique constraint "prefixes_prefix_idx" 

db=# INSERT INTO your_table (id, key) VALUES (5, 'a.b.c.d'); 
ERROR: conflicting key value violates exclusion constraint "prefixes_forbid_conflicts" 

db=# INSERT INTO your_table (id, key) VALUES (6, 'a.b.d'), (7, 'a'); 
ERROR: conflicting key value violates exclusion constraint "prefixes_forbid_conflicts" 
संबंधित मुद्दे