मैं एक पंक्ति कैसे डालूं जिसमें एक विदेशी कुंजी हो?


54

PostgreSQL v9.1 का उपयोग करना। मेरे पास निम्न तालिकाएँ हैं:

CREATE TABLE foo
(
    id BIGSERIAL     NOT NULL UNIQUE PRIMARY KEY,
    type VARCHAR(60) NOT NULL UNIQUE
);

CREATE TABLE bar
(
    id BIGSERIAL NOT NULL UNIQUE PRIMARY KEY,
    description VARCHAR(40) NOT NULL UNIQUE,
    foo_id BIGINT NOT NULL REFERENCES foo ON DELETE RESTRICT
);

कहते हैं कि पहली तालिका fooइस तरह से आबाद है:

INSERT INTO foo (type) VALUES
    ( 'red' ),
    ( 'green' ),
    ( 'blue' );

क्या तालिका barको संदर्भित करके पंक्तियों को आसानी से सम्मिलित करने का कोई तरीका है foo? या मुझे इसे दो चरणों में करना चाहिए, पहले उस fooप्रकार को देखकर जो मैं चाहता हूं, और फिर एक नई पंक्ति सम्मिलित barकरूं?

यहाँ छद्म कोड का एक उदाहरण दिखाया गया है जो मैं उम्मीद कर रहा था कि यह किया जा सकता है:

INSERT INTO bar (description, foo_id) VALUES
    ( 'testing',     SELECT id from foo WHERE type='blue' ),
    ( 'another row', SELECT id from foo WHERE type='red'  );

जवाबों:


67

आपका सिंटैक्स लगभग अच्छा है, उप-क्षेत्रों के आसपास कुछ कोष्ठकों की आवश्यकता है और यह काम करेगा:

INSERT INTO bar (description, foo_id) VALUES
    ( 'testing',     (SELECT id from foo WHERE type='blue') ),
    ( 'another row', (SELECT id from foo WHERE type='red' ) );

SQL-Fiddle पर परीक्षण किया गया

एक और तरीका है, छोटे वाक्यविन्यास के साथ यदि आपके पास डालने के लिए बहुत सारे मान हैं:

WITH ins (description, type) AS
( VALUES
    ( 'more testing',   'blue') ,
    ( 'yet another row', 'green' )
)  
INSERT INTO bar
   (description, foo_id) 
SELECT 
    ins.description, foo.id
FROM 
  foo JOIN ins
    ON ins.type = foo.type ;

इसे कुछ बार पढ़ना शुरू किया, लेकिन मैं अब समझता हूं कि दूसरा समाधान आपने प्रदान किया है। मुझें यह पसंद है। सिस्टम के पहली बार सामने आने पर कुछ ज्ञात मूल्यों के साथ मेरे डेटाबेस को बूटस्ट्रैप करने के लिए इसका उपयोग करना।
स्टीफन

37

सादा INSERT

INSERT INTO bar (description, foo_id)
SELECT val.description, f.id
FROM  (
   VALUES
      (text 'testing', text 'blue')  -- explicit type declaration; see below
    , ('another row', 'red' )
    , ('new row1'   , 'purple')      -- purple does not exist in foo, yet
    , ('new row2'   , 'purple')
   ) val (description, type)
LEFT   JOIN foo f USING (type);
  • इसके LEFT [OUTER] JOINबजाय [INNER] JOINइसका मतलब यह है कि जब कोई मैच val नहीं मिलता है तो पंक्तियों को गिराया नहीं जाता है foo। इसके बजाय, के NULLलिए दर्ज किया गया है foo_id

  • VALUESसबक्वेरी में अभिव्यक्ति के रूप में ही करता है @ ypercube के CTE। सामान्य टेबल एक्सप्रेशंस अतिरिक्त सुविधाएँ प्रदान करते हैं और बड़े प्रश्नों में पढ़ने में आसान होते हैं, लेकिन वे अनुकूलन बाधाओं के रूप में भी सामने आते हैं। जब उपर्युक्त में से किसी की आवश्यकता नहीं होती है, तो उपश्रेणी आम तौर पर थोड़ी तेज़ होती है।

  • idस्तंभ नाम के रूप में एक व्यापक प्रसार विरोधी पैटर्न है। होना चाहिए foo_idऔर bar_idया कुछ भी वर्णनात्मक। तालिकाओं के एक समूह में शामिल होने पर, आप सभी नाम वाले कई स्तंभों के साथ समाप्त होते हैं id...

  • सादे textया varcharइसके बजाय पर विचार करें varchar(n)। यदि आपको वास्तव में लंबाई प्रतिबंध लगाने की आवश्यकता है, तो एक CHECKबाधा जोड़ें :

  • आपको स्पष्ट प्रकार की जातियों को जोड़ने की आवश्यकता हो सकती है। चूंकि VALUESअभिव्यक्ति सीधे एक तालिका (जैसे INSERT ... VALUES ...) से जुड़ी नहीं है , इसलिए प्रकार व्युत्पन्न नहीं किए जा सकते हैं और डिफ़ॉल्ट डेटा प्रकार का उपयोग स्पष्ट प्रकार की घोषणा के बिना किया जाता है, जो सभी मामलों में काम नहीं कर सकता है। यह पहली पंक्ति में करने के लिए पर्याप्त है, बाकी लाइन में गिर जाएंगे।

INSERT उसी समय FK पंक्तियों को याद कर रहा है

यदि आप fooकिसी एकल SQL कथन में मक्खी पर गैर-मौजूद प्रविष्टियाँ बनाना चाहते हैं, तो CTE साधन हैं:

WITH sel AS (
   SELECT val.description, val.type, f.id AS foo_id
   FROM  (
      VALUES
         (text 'testing', text 'blue')
       , ('another row', 'red'   )
       , ('new row1'   , 'purple')
       , ('new row2'   , 'purple')
      ) val (description, type)
   LEFT   JOIN foo f USING (type)
   )
, ins AS ( 
   INSERT INTO foo (type)
   SELECT DISTINCT type FROM sel WHERE foo_id IS NULL
   RETURNING id AS foo_id, type
   )
INSERT INTO bar (description, foo_id)
SELECT sel.description, COALESCE(sel.foo_id, ins.foo_id)
FROM   sel
LEFT   JOIN ins USING (type);

नोट करने के लिए दो नई डमी पंक्तियाँ डालें। दोनों बैंगनी हैं , जो fooअभी तक अस्तित्व में नहीं है। पहले कथन की आवश्यकता बताने के लिए दो पंक्तियाँ ।DISTINCTINSERT

चरण-दर-चरण स्पष्टीकरण

  1. 1 CTE selइनपुट डेटा की कई गुना पंक्तियाँ प्रदान करता है। सबक्वेरी valके साथ VALUESअभिव्यक्ति स्रोत के रूप में एक मेज या सबक्वेरी साथ प्रतिस्थापित किया जा सकता है। इसके तत्काल बाद LEFT JOINकरने के लिए fooसंलग्न करने के लिए foo_idके लिए पहले से मौजूद typeपंक्तियों। अन्य सभी पंक्तियों को foo_id IS NULLइस तरह से मिलता है ।

  2. दूसरा CTE अलग-अलग नए प्रकारों ( ) को insसम्मिलित करता है , और नई जनरेट करता है - साथ में पंक्तियों को सम्मिलित करने के लिए वापस जुड़ता है।foo_id IS NULLfoofoo_idtype

  3. अंतिम बाहरी INSERTअब प्रत्येक पंक्ति के लिए foo.id सम्मिलित कर सकता है: या तो पहले से मौजूद प्रकार, या इसे चरण 2 में डाला गया था।

कड़ाई से बोलते हुए, दोनों आवेषण "समानांतर में" होते हैं, लेकिन चूंकि यह एक एकल कथन है, डिफ़ॉल्ट FOREIGN KEYबाधाओं की शिकायत नहीं होगी। डिफ़ॉल्ट रूप से कथन के अंत में संदर्भात्मक अखंडता लागू की जाती है।

Postgres 9.3 के लिए SQL फिडेल(9.1 में वही काम करता है।)

यदि आप इन प्रश्नों में से कई को समवर्ती रूप से चलाते हैं, तो एक छोटी दौड़ की स्थिति है। के तहत और अधिक संबंधित प्रश्न पढ़ें यहाँ और यहाँ और यहाँ । वास्तव में केवल भारी समवर्ती लोड के तहत होता है, यदि कभी भी। एक अन्य जवाब में विज्ञापित कैशिंग समाधानों की तुलना में, मौका सुपर-टिनी है

बार-बार उपयोग के लिए कार्य

बार-बार उपयोग के लिए मैं एक SQL फ़ंक्शन बनाऊंगा जो कि अभिव्यक्ति के unnest(param)स्थान पर पैरामीटर के रूप में रिकॉर्ड की एक सरणी लेता है और उपयोग करता VALUESहै।

या, यदि अभिलेखों के सरणियों के लिए सिंटैक्स आपके लिए बहुत गड़बड़ है, तो पैरामीटर के रूप में एक अल्पविराम से अलग स्ट्रिंग का उपयोग करें _param। उदाहरण के लिए:

'description1,type1;description2,type2;description3,type3'

फिर VALUESउपरोक्त कथन में अभिव्यक्ति को बदलने के लिए इसका उपयोग करें :

SELECT split_part(x, ',', 1) AS description
       split_part(x, ',', 2) AS type
FROM unnest(string_to_array(_param, ';')) x;


पोस्टग्रेज 9.5 में यूपीएसईआरटी के साथ कार्य

पैरामीटर पासिंग के लिए एक कस्टम रो टाइप बनाएं। हम इसके बिना कर सकते थे, लेकिन यह सरल है:

CREATE TYPE foobar AS (description text, type text);

समारोह:

CREATE OR REPLACE FUNCTION f_insert_foobar(VARIADIC _val foobar[])
  RETURNS void AS
$func$
   WITH val AS (SELECT * FROM unnest(_val))    -- well-known row type
   ,    ins AS ( 
      INSERT INTO foo AS f (type)
      SELECT DISTINCT v.type                   -- DISTINCT!
      FROM   val v
      ON     CONFLICT(type) DO UPDATE          -- type already exists
      SET    type = excluded.type WHERE FALSE  -- never executed, but lock rows
      RETURNING f.type, f.id
      )
   INSERT INTO bar AS b (description, foo_id)
   SELECT v.description, COALESCE(f.id, i.id)  -- assuming most types pre-exist
   FROM        val v
   LEFT   JOIN foo f USING (type)              -- already existed
   LEFT   JOIN ins i USING (type)              -- newly inserted
   ON     CONFLICT (description) DO UPDATE     -- description already exists
   SET    foo_id = excluded.foo_id             -- real UPSERT this time
   WHERE  b.foo_id IS DISTINCT FROM excluded.foo_id  -- only if actually changed
$func$  LANGUAGE sql;

कॉल करें:

SELECT f_insert_foobar(
     '(testing,blue)'
   , '(another row,red)'
   , '(new row1,purple)'
   , '(new row2,purple)'
   , '("with,comma",green)'  -- added to demonstrate row syntax
   );

समवर्ती लेनदेन के साथ पर्यावरण के लिए तेज और रॉक-ठोस।

उपरोक्त प्रश्नों के अलावा, यह ...

  • ... लागू होता है SELECTया INSERTपर foo: कोई भी type, जो FK तालिका में मौजूद नहीं है, अभी तक डाला गया है। पहले से मौजूद अधिकांश प्रकारों को मानते हुए। पूरी तरह से सुनिश्चित होने और दौड़ की शर्तों को पूरा करने के लिए, मौजूदा पंक्तियों की हमें ज़रूरत है (ताकि समवर्ती लेनदेन हस्तक्षेप न कर सकें)। यदि आपके मामले के लिए यह बहुत अधिक पागल है, तो आप इसे बदल सकते हैं:

      ON     CONFLICT(type) DO UPDATE          -- type already exists
      SET    type = excluded.type WHERE FALSE  -- never executed, but lock rows

    साथ में

      ON     CONFLICT(type) DO NOTHING
  • ... लागू होता है INSERTया UPDATE(सही "यूपीएसईआरटी") पर bar: यदि descriptionपहले से मौजूद है तो इसका typeअद्यतन किया जाता है:

      ON     CONFLICT (description) DO UPDATE     -- description already exists
      SET    foo_id = excluded.foo_id             -- real UPSERT this time
      WHERE  b.foo_id IS DISTINCT FROM excluded.foo_id  -- only if actually changed

    लेकिन केवल अगर typeवास्तव में बदलता है:

  • ... मानों को एक VARIADICपैरामीटर के साथ ही ज्ञात पंक्ति प्रकारों से गुजरता है । डिफ़ॉल्ट अधिकतम 100 मापदंडों पर ध्यान दें! की तुलना करें:

    कई पंक्तियों को पास करने के कई अन्य तरीके हैं ...

सम्बंधित:


आपके INSERT missing FK rows at the same timeउदाहरण में, क्या इसे लेन-देन में डालने से SQL सर्वर में दौड़ की स्थिति का खतरा कम होगा?
तत्व

1
@ एलिमेंट 11: उत्तर पोस्टग्रेज के लिए है, लेकिन जब से हम एक एकल एसक्यूएल कमांड के बारे में बात कर रहे हैं , यह किसी भी मामले में एकल लेनदेन है। एक बड़े लेनदेन के अंदर इसे निष्पादित करने से केवल संभावित दौड़ की स्थिति के लिए समय खिड़की बढ़ेगी । SQL सर्वर के लिए के रूप में: डेटा-संशोधित CTEs बिल्कुल (केवल SELECTएक WITHखंड के अंदर ) समर्थित नहीं हैं । स्रोत: एमएस प्रलेखन
इरविन ब्रान्डेसटेटर

1
तुम भी साथ ऐसा कर सकते INSERT ... RETURNING \gsetमें psqlतो psql रूप में वापस आ मूल्यों का उपयोग :'variables', लेकिन यह केवल एक पंक्ति आवेषण के लिए काम करता है।
क्रेग रिंगर

@ErwinBrandstetter यह बहुत अच्छा है, लेकिन मैं यह सब समझने के लिए sql में बहुत नया हूं, क्या आप "INSERT लापता FK पंक्तियों को एक ही समय में" जोड़ने के लिए कुछ टिप्पणी जोड़ सकते हैं? भी, SQLFiddle काम कर उदाहरण के लिए धन्यवाद!
ग्लैंडन

@glallen: मैंने चरण-दर-चरण स्पष्टीकरण जोड़ा। संबंधित उत्तरों के कई लिंक और अधिक स्पष्टीकरण के साथ मैनुअल भी हैं। आपको यह समझने की आवश्यकता है कि क्वेरी क्या करती है या आप अपने सिर पर हो सकते हैं।
इरविन ब्रान्डेसटेटर

4

देखो। आपको मूल रूप से फू आईडी की आवश्यकता है जो उन्हें बार में डालें।

विशिष्ट, btw पोस्टग्रेज नहीं करता है। (और आपने इसे इस तरह टैग नहीं किया) - यह आमतौर पर SQL कैसे काम करता है। यहां कोई शॉर्टकट नहीं।

यद्यपि, एप्लिकेशन वार, आपके पास मेमोरी में foo आइटम का कैश हो सकता है। मेरी सारणी में प्रायः 3 अद्वितीय क्षेत्र होते हैं:

  • आईडी (पूर्णांक या कुछ) जो तालिका स्तर की प्राथमिक कुंजी है।
  • पहचानकर्ता, जो एक GUID है, जिसका उपयोग स्थिर ID अनुप्रयोग स्तर वार के रूप में किया जाता है (और URL में ग्राहक के संपर्क में हो सकता है)
  • कोड - एक स्ट्रिंग जो वहां हो सकती है और यदि यह वहां है तो अद्वितीय होना चाहिए (sql server: फ़िल्टर किए गए अद्वितीय सूचकांक को शून्य नहीं)। यह एक ग्राहक सेट पहचानकर्ता है।

उदाहरण:

  • खाता (एक ट्रेडिंग एप्लिकेशन में) -> आईडी विदेशी कुंजी के लिए एक इंट का उपयोग किया जाता है। -> पहचानकर्ता एक दिशानिर्देश है और वेब पोर्टल आदि में उपयोग किया जाता है - हमेशा स्वीकार किया जाता है। -> कोड मैन्युअल रूप से सेट किया गया है। नियम: एक बार सेट करने के बाद यह नहीं बदलता है।

जाहिर है जब आप किसी खाते से कुछ लिंक करना चाहते हैं - आपको पहले तकनीकी रूप से, आईडी प्राप्त करना होगा - लेकिन पहचानकर्ता और कोड दोनों को दिए जाने के बाद कभी नहीं बदलते हैं, मेमोरी केन में एक सकारात्मक कैश अधिकांश लुकअप को डेटाबेस से टकराने से रोकता है।


10
आप जानते हैं कि आप त्रुटि-प्रवण कैश से बचने के लिए RDBMS को एकल SQL स्टेटमेंट में आपके लिए लुकअप कर सकते हैं?
एरविन ब्रान्डस्टेट्टर

आप जानते हैं कि गैर-बदलते तत्वों को देखना त्रुटि प्रवण नहीं है? इसके अलावा, आमतौर पर, RDBMS लाइसेंस लागत के कारण स्केलेबल और खेल का सबसे महंगा तत्व नहीं है। जितना संभव हो उतना इसे से लोड लेना बिल्कुल बुरा नहीं है। इसके अलावा, कई ओआरएम समर्थन नहीं करते हैं जो इसके साथ शुरू करते हैं।
टॉमटॉम

14
गैर-बदलते तत्व? सबसे महंगा तत्व? लाइसेंसिंग लागत (PostgreSQL के लिए)? ORMs परिभाषित कर रहा है कि क्या है? नहीं मुझे उस सब के बारे में पता नहीं था।
इरविन ब्रान्डेसटेटर
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.