पोस्टग्रैस: यदि पहले से मौजूद नहीं है तो INSERT


361

मैं एक पोस्टग्रेजुएट डेटाबेस में लिखने के लिए पायथन का उपयोग कर रहा हूं:

sql_string = "INSERT INTO hundred (name,name_slug,status) VALUES ("
sql_string += hundred + ", '" + hundred_slug + "', " + status + ");"
cursor.execute(sql_string)

लेकिन क्योंकि मेरी कुछ पंक्तियाँ समान हैं, मुझे निम्न त्रुटि मिलती है:

psycopg2.IntegrityError: duplicate key value  
  violates unique constraint "hundred_pkey"

जब तक यह पंक्ति पहले से मौजूद नहीं है तब तक मैं 'INSERT' कैसे लिख सकता हूं?

मैं इस तरह की सिफारिश की जटिल बयान देखा है:

IF EXISTS (SELECT * FROM invoices WHERE invoiceid = '12345')
UPDATE invoices SET billed = 'TRUE' WHERE invoiceid = '12345'
ELSE
INSERT INTO invoices (invoiceid, billed) VALUES ('12345', 'TRUE')
END IF

लेकिन सबसे पहले, क्या मुझे जो ज़रूरत है, उसके लिए यह ओवरकिल है और दूसरी बात, मैं उनमें से एक को एक साधारण स्ट्रिंग के रूप में कैसे निष्पादित कर सकता हूं?


56
भले ही आप इस मुद्दे को कैसे हल करें, आपको अपनी क्वेरी को इस तरह उत्पन्न नहीं करना चाहिए। अपनी क्वेरी में मापदंडों का उपयोग करें और मूल्यों को अलग से पास करें; देखें stackoverflow.com/questions/902408/…
थॉमस वाउचर

3
अपवाद को क्यों न पकड़ें और इसे अनदेखा करें?
मैथ्यू मिशेल

5
9.5 के अनुसार (वर्तमान में बीटा 2 पर) फ़ीचर की तरह एक नया अपग्रेड होता है, देखें: postgresql.org/docs/9.5/static/sql-insert.html#SQL-ON-CONFLICT
एक्ज़ेकिएल मोरेनो

2
क्या आपने इसके लिए एक उत्तर स्वीकार करने पर विचार किया है? =]
रीलेक्चुअल

जवाबों:


512

9.5 (2016-01-07 के बाद से जारी) पोस्टग्रैस एक "अपर्चर" कमांड प्रदान करता है , जिसे INSERT पर ON CONFLICT क्लॉज के रूप में भी जाना जाता है :

INSERT ... ON CONFLICT DO NOTHING/UPDATE

यह समवर्ती ऑपरेशन का उपयोग करते समय आप कई सूक्ष्म समस्याओं को हल कर सकते हैं, जो कुछ अन्य उत्तर प्रस्तावित करते हैं।


14
9.5 जारी किया गया।
भाग्योदयकाल

2
PostTreSQL 9.5 से पहले @TusharJain आप एक "पुराने जमाने" UPSERT (CTE के साथ) कर सकते हैं, लेकिन आप दौड़ की स्थिति के साथ समस्याओं का अनुभव कर सकते हैं और यह 9.5 शैली के रूप में प्रदर्शन नहीं करेगा। यदि आप विवरणों के बारे में अधिक पढ़ना चाहते हैं, तो कुछ लिंक सहित इस ब्लॉग पर (ऊपर नीचे क्षेत्र में) अद्यतन करने के बारे में एक अच्छा विवरण है।
स्काईगार्ड

16
उन लोगों के लिए, यहाँ दो सरल उदाहरण हैं। (1) INSERT यदि अन्य मौजूद नहीं है - INSERT INTO distributors (did, dname) VALUES (7, 'Redline GmbH') ON CONFLICT (did) DO NOTHING;(2) INSERT मौजूद नहीं है तो UPDATE - INSERT INTO distributors (did, dname) VALUES (5, 'Gizmo Transglobal'), (6, 'Associated Computing, Inc') ON CONFLICT (did) DO UPDATE SET dname = EXCLUDED.dname;ये उदाहरण मैनुअल से हैं - postgresql.org/docs/9.5/static/sql-insert.html
AnnieFromTaiwan

13
एक कैविएट / साइड इफेक्ट है। अनुक्रम स्तंभ (सीरियल या बिगसेरियल) वाली तालिका में, भले ही कोई पंक्ति सम्मिलित न की गई हो, प्रत्येक आवेषण प्रयास में अनुक्रम को बढ़ाया जाता है।
ग्रेज़गोरज़ लुसीस्वो

2
इसे जारी करने की ओर इशारा करने के बजाय INSERT प्रलेखन से जोड़ना बेहतर होगा। डॉक्टर लिंक: postgresql.org/docs/9.5/static/sql-insert.html
borjagvo

379

जब तक यह पंक्ति पहले से मौजूद नहीं है तब तक मैं 'INSERT' कैसे लिख सकता हूं?

PostgreSQL में सशर्त INSERT करने का एक अच्छा तरीका है:

INSERT INTO example_table
    (id, name)
SELECT 1, 'John'
WHERE
    NOT EXISTS (
        SELECT id FROM example_table WHERE id = 1
    );

चेतावनी यह दृष्टिकोण के लिए 100% विश्वसनीय नहीं है समवर्ती हालांकि, लिखने आपरेशनों। वहाँ के बीच एक बहुत छोटे रेस स्थिति है SELECTमें NOT EXISTSशामिल हो जाते हैं विरोधी अर्द्ध और INSERTखुद। यह ऐसी परिस्थितियों में विफल हो सकता है


यह कितना सुरक्षित है कि "नाम" -फील्ड में एक अद्वितीय बाधा है? क्या यह अद्वितीय-उल्लंघन के साथ कभी विफल होगा?
अग्नसाफ्ट 19

2
यह ठीक काम करता है। एकमात्र समस्या युग्मन है जो मुझे लगता है: क्या होगा यदि कोई तालिका को संशोधित करता है जैसे कि अधिक कॉलम अद्वितीय हैं। उस स्थिति में सभी लिपियों को संशोधित किया जाना चाहिए। यह करने के लिए एक और अधिक सामान्य तरीका था, तो यह अच्छा होगा ...
विलेम वैन ओन्सेम

1
क्या RETURNS idउदाहरण के लिए इसका उपयोग संभव है idकि क्या डाला गया है या नहीं?
ओलिवियर पोंस

2
@OlivierPons हाँ, यह संभव है। RETURNING idऔर क्वेरी में जोड़ें और यह एक नई पंक्ति आईडी या कुछ भी नहीं लौटाएगा, अगर कोई पंक्ति नहीं डाली गई है।
एलेक्सएम

4
मैंने इसे अविश्वसनीय माना है। ऐसा प्रतीत होता है कि पोस्टग्रेज कभी-कभी इंसर्ट को अंजाम देता है, इससे पहले कि वह सलेक्ट करता है और मैं डुप्लिकेट कीज़ के उल्लंघन को समाप्त करता हूं, हालांकि रिकॉर्ड अभी तक डाला नहीं गया है। CONFLICT के साथ संस्करण => 9.5 उपयोग करने का प्रयास करें।
माइकल सिल्वर

51

एक दृष्टिकोण आपके सभी डेटा को सम्मिलित करने के लिए एक गैर-विवश (कोई अद्वितीय अनुक्रमणिका) तालिका बनाने के लिए नहीं होगा और अपने सौ टेबल में अपना इंसर्ट करने के लिए इससे अलग चयन करें।

तो उच्च स्तर होगा। मुझे लगता है कि सभी तीन कॉलम मेरे उदाहरण में विशिष्ट हैं इसलिए चरण 3 में बदलाव के लिए NOT EXITS जॉइन केवल सौ टेबल में अद्वितीय कॉलम में शामिल होने के लिए।

  1. अस्थायी तालिका बनाएं। यहाँ डॉक्स देखें ।

    CREATE TEMPORARY TABLE temp_data(name, name_slug, status);
  2. अस्थायी तालिका में डेटा सम्मिलित करें।

    INSERT INTO temp_data(name, name_slug, status); 
  3. अस्थायी तालिका में कोई भी अनुक्रमणिका जोड़ें।

  4. मुख्य तालिका सम्मिलित करें।

    INSERT INTO hundred(name, name_slug, status) 
        SELECT DISTINCT name, name_slug, status
        FROM hundred
        WHERE NOT EXISTS (
            SELECT 'X' 
            FROM temp_data
            WHERE 
                temp_data.name          = hundred.name
                AND temp_data.name_slug = hundred.name_slug
                AND temp_data.status    = status
        );

3
यह सबसे तेज़ तरीका है जो मैंने बड़े पैमाने पर आवेषण करने के लिए पाया है जब मुझे नहीं पता कि क्या पंक्ति पहले से मौजूद है।
3

'X' चुनें? क्या कोई स्पष्ट कर सकता है? यह केवल एक सही कथन है: SELECT name,name_slug,statusया*
roberthuttinger

3
लुकअप सहसंबद्ध उपशम। 'X' को 1 या 'SadClown' में बदला जा सकता है। SQL के लिए कुछ होना आवश्यक है और 'X' का उपयोग करना एक सामान्य बात है। यह छोटा है और यह स्पष्ट करता है कि एक सहसंबद्ध सबक्वेरी का उपयोग किया जा रहा है और SQL की आवश्यकताओं को पूरा करता है।
कुबेरचुन

आपने "अपना सभी डेटा सम्मिलित करें (अस्थायी तालिका मानकर) का उल्लेख किया है और उससे अलग चयन करें"। उस मामले में, क्या यह नहीं होना चाहिए SELECT DISTINCT name, name_slug, status FROM temp_data?
गिब्ज़ ००

17

दुर्भाग्य से, PostgreSQLन तो समर्थन करता है और न MERGEही ON DUPLICATE KEY UPDATE, इसलिए आपको इसे दो वक्तव्यों में करना होगा:

UPDATE  invoices
SET     billed = 'TRUE'
WHERE   invoices = '12345'

INSERT
INTO    invoices (invoiceid, billed)
SELECT  '12345', 'TRUE'
WHERE   '12345' NOT IN
        (
        SELECT  invoiceid
        FROM    invoices
        )

आप इसे एक फंक्शन में लपेट सकते हैं:

CREATE OR REPLACE FUNCTION fn_upd_invoices(id VARCHAR(32), billed VARCHAR(32))
RETURNS VOID
AS
$$
        UPDATE  invoices
        SET     billed = $2
        WHERE   invoices = $1;

        INSERT
        INTO    invoices (invoiceid, billed)
        SELECT  $1, $2
        WHERE   $1 NOT IN
                (
                SELECT  invoiceid
                FROM    invoices
                );
$$
LANGUAGE 'sql';

और बस इसे कॉल करें:

SELECT  fn_upd_invoices('12345', 'TRUE')

1
वास्तव में, यह काम नहीं करता है: मैं INSERT INTO hundred (name, name_slug, status) SELECT 'Chichester', 'chichester', NULL WHERE 'Chichester' NOT IN (SELECT NAME FROM hundred);किसी भी समय कॉल कर सकता हूं , और यह पंक्ति सम्मिलित करता रहता है।
AP257

1
@ AP257 CREATE TABLE hundred (name TEXT, name_slug TEXT, status INT); INSERT INTO hundred (name, name_slug, status) SELECT 'Chichester', 'chichester', NULL WHERE 'Chichester' NOT IN (SELECT NAME FROM hundred); INSERT INTO hundred (name, name_slug, status) SELECT 'Chichester', 'chichester', NULL WHERE 'Chichester' NOT IN (SELECT NAME FROM hundred); SELECT * FROM hundred:। एक रिकॉर्ड है।
क्वासोई

12

आप VALUES का उपयोग कर सकते हैं - पोस्टग्रेज में उपलब्ध:

INSERT INTO person (name)
    SELECT name FROM person
    UNION 
    VALUES ('Bob')
    EXCEPT
    SELECT name FROM person;

12
पर्सन से FECT नाम <--- क्या होगा यदि व्यक्ति में एक अरब पंक्तियाँ हैं?
हेन्ले चिउ

1
मुझे लगता है कि इस मुद्दे को हल करने के लिए यह एक अच्छा त्वरित तरीका है, लेकिन केवल तभी जब आप सुनिश्चित हों कि स्रोत तालिका कभी भी बड़ी नहीं होगी। मुझे एक तालिका मिली है जिसमें कभी भी 1000 से अधिक पंक्तियां नहीं होंगी, इसलिए मैं इस समाधान का उपयोग कर सकता हूं।
लियोनार्ड

वाह, यह वही है जो मुझे चाहिए था। मैं चिंतित था कि मुझे एक समारोह या एक अस्थायी तालिका बनाने की आवश्यकता होगी, लेकिन यह सब को रोकता है - धन्यवाद!
अमलगोविनस

8

मुझे पता है कि यह सवाल कुछ समय पहले का है, लेकिन सोचा कि यह किसी की मदद कर सकता है। मुझे लगता है कि ऐसा करने का सबसे आसान तरीका ट्रिगर है। उदाहरण के लिए:

Create Function ignore_dups() Returns Trigger
As $$
Begin
    If Exists (
        Select
            *
        From
            hundred h
        Where
            -- Assuming all three fields are primary key
            h.name = NEW.name
            And h.hundred_slug = NEW.hundred_slug
            And h.status = NEW.status
    ) Then
        Return NULL;
    End If;
    Return NEW;
End;
$$ Language plpgsql;

Create Trigger ignore_dups
    Before Insert On hundred
    For Each Row
    Execute Procedure ignore_dups();

इस कोड को एक psql प्रॉम्प्ट से निष्पादित करें (या हालांकि आप डेटाबेस पर सीधे प्रश्नों को निष्पादित करना पसंद करते हैं)। फिर आप पायथन से सामान्य रूप से सम्मिलित कर सकते हैं। उदाहरण के लिए:

sql = "Insert Into hundreds (name, name_slug, status) Values (%s, %s, %s)"
cursor.execute(sql, (hundred, hundred_slug, status))

ध्यान दें कि @Thomas_Wouters जैसा कि पहले ही उल्लेख किया गया है, उपरोक्त कोड स्ट्रिंग को समेटने के बजाय मापदंडों का लाभ उठाता है।


यदि कोई अन्य व्यक्ति डॉक्स से भी आश्चर्यचकित था : "पंक्ति-स्तर के ट्रिगर ने पहले निकाल दिए थे, तो ट्रिगर प्रबंधक को इस पंक्ति के शेष संचालन को छोड़ने के लिए संकेत देने के लिए अशक्त हो सकता है (यानी, बाद के ट्रिगर को निकाल नहीं दिया जाता है, और INERTERT / UPDATE / DELETE इस पंक्ति के लिए नहीं होता है)। यदि कोई गैर-मान लौटाया जाता है, तो ऑपरेशन उस पंक्ति मान के साथ आगे बढ़ता है। "
पीट

वास्तव में इस जवाब के लिए मैं देख रहा था। स्वच्छ कोड, चयन कथन के बजाय फ़ंक्शन + ट्रिगर का उपयोग कर। +1
जेसेक क्रेज़ीज़ी

मुझे यह उत्तर पसंद है, फ़ंक्शन और ट्रिगर का उपयोग करें। अब मैं फ़ंक्शंस और ट्रिगर्स का उपयोग करके गतिरोध को तोड़ने का एक और तरीका ढूंढता हूं ...
सुकमा सूत्र

7

PostgreSQL में क्वेरी के साथ सशर्त INSERT करने का एक अच्छा तरीका है: जैसे:

WITH a as(
select 
 id 
from 
 schema.table_name 
where 
 column_name = your_identical_column_value
)
INSERT into 
 schema.table_name
(col_name1, col_name2)
SELECT
    (col_name1, col_name2)
WHERE NOT EXISTS (
     SELECT
         id
     FROM
         a
        )
  RETURNING id 

7

ठीक यही समस्या मेरे सामने है और मेरा संस्करण 9.5 है

और मैं इसे SQL क्वेरी के साथ हल करता हूं।

INSERT INTO example_table (id, name)
SELECT 1 AS id, 'John' AS name FROM example_table
WHERE NOT EXISTS(
            SELECT id FROM example_table WHERE id = 1
    )
LIMIT 1;

आशा है कि संस्करण> = 9.5 के साथ एक ही समस्या वाले किसी व्यक्ति की मदद करेगा।

पढ़ने के लिए धन्यवाद।


5

INSERT .. जहां नहीं है वहाँ अच्छा तरीका है। और लेन-देन की स्थिति को "लिफाफे" से बचा जा सकता है:

BEGIN;
LOCK TABLE hundred IN SHARE ROW EXCLUSIVE MODE;
INSERT ... ;
COMMIT;

2

यह नियमों के साथ आसान है:

CREATE RULE file_insert_defer AS ON INSERT TO file
WHERE (EXISTS ( SELECT * FROM file WHERE file.id = new.id)) DO INSTEAD NOTHING

लेकिन यह समवर्ती लेखन के साथ विफल रहता है ...


1

सबसे ऊपर उठने के साथ दृष्टिकोण (जॉन डो से) किसी तरह मेरे लिए काम करता है, लेकिन मेरे मामले में अपेक्षित 422 पंक्तियों से मुझे केवल 180 मिलते हैं। मुझे कुछ भी गलत नहीं मिला और इसमें कोई त्रुटि नहीं है, इसलिए मैंने एक अलग की तलाश की सरल दृष्टिकोण।

मेरे लिए पूरी तरह से काम करने के IF NOT FOUND THENबाद उपयोग करना SELECT

(में वर्णित PostgreSQL प्रलेखन )

प्रलेखन से उदाहरण:

SELECT * INTO myrec FROM emp WHERE empname = myname;
IF NOT FOUND THEN
  RAISE EXCEPTION 'employee % not found', myname;
END IF;

1

psycopgs कर्सर वर्ग विशेषता है rowcount

यह रीड-ओनली विशेषता उन पंक्तियों की संख्या को निर्दिष्ट करती है जो अंतिम निष्पादित * () (DQL स्टेटमेंट्स जैसे SELECT) या प्रभावित (DML स्टेटमेंट जैसे UPDATE या INSERT के लिए) का उत्पादन करती हैं।

तो आप पहले UPDATE और INSERT की कोशिश कर सकते हैं, अगर rowcount 0 है।

लेकिन अपने डेटाबेस में गतिविधि के स्तर के आधार पर आप UPDATE और INSERT के बीच दौड़ की स्थिति पर चोट कर सकते हैं, जहाँ अंतरिम में एक और प्रक्रिया हो सकती है।


संभवतः लेन-देन में इन प्रश्नों को लपेटने से दौड़ की स्थिति समाप्त हो जाएगी।
डैनियल ल्यों

धन्यवाद, वास्तव में सरल और साफ समाधान
अलेक्जेंडर Malfait

1

आपका कॉलम "सौ" प्राथमिक कुंजी के रूप में परिभाषित किया गया लगता है और इसलिए अद्वितीय होना चाहिए जो ऐसा नहीं है। समस्या यह नहीं है, यह आपके डेटा के साथ है।

मेरा सुझाव है कि आप प्राथमिक कुंजी को व्यवस्थित करने के लिए सीरियल प्रकार के रूप में एक आईडी डालें


1

यदि आप कहते हैं कि आपकी कई पंक्तियाँ समान हैं तो आप कई बार जाँचना समाप्त कर देंगे। आप उन्हें भेज सकते हैं और डेटाबेस यह निर्धारित करेगा कि क्या सम्मिलित किया जाए या इस प्रकार से ON CONFLICT क्लॉज के साथ नहीं

  INSERT INTO Hundred (name,name_slug,status) VALUES ("sql_string += hundred  
  +",'" + hundred_slug + "', " + status + ") ON CONFLICT ON CONSTRAINT
  hundred_pkey DO NOTHING;" cursor.execute(sql_string);

0

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

sql = "INSERT INTO hundred (name,name_slug,status)"
sql += " ( SELECT " + hundred + ", '" + hundred_slug + "', " + status
sql += " FROM hundred"
sql += " WHERE name = " + hundred + " AND name_slug = '" + hundred_slug + "' AND status = " + status
sql += " HAVING COUNT(*) = 0 );"

-1

यहाँ एक जेनेरिक पायथन फ़ंक्शन है, जो एक टैबलनेम, कॉलम और वैल्यूज़ देता है, पोस्टग्रैक्क्वल के लिए अपग्रेड बराबर करता है।

आयात json

def upsert(table_name, id_column, other_columns, values_hash):

    template = """
    WITH new_values ($$ALL_COLUMNS$$) as (
      values
         ($$VALUES_LIST$$)
    ),
    upsert as
    (
        update $$TABLE_NAME$$ m
            set
                $$SET_MAPPINGS$$
        FROM new_values nv
        WHERE m.$$ID_COLUMN$$ = nv.$$ID_COLUMN$$
        RETURNING m.*
    )
    INSERT INTO $$TABLE_NAME$$ ($$ALL_COLUMNS$$)
    SELECT $$ALL_COLUMNS$$
    FROM new_values
    WHERE NOT EXISTS (SELECT 1
                      FROM upsert up
                      WHERE up.$$ID_COLUMN$$ = new_values.$$ID_COLUMN$$)
    """

    all_columns = [id_column] + other_columns
    all_columns_csv = ",".join(all_columns)
    all_values_csv = ','.join([query_value(values_hash[column_name]) for column_name in all_columns])
    set_mappings = ",".join([ c+ " = nv." +c for c in other_columns])

    q = template
    q = q.replace("$$TABLE_NAME$$", table_name)
    q = q.replace("$$ID_COLUMN$$", id_column)
    q = q.replace("$$ALL_COLUMNS$$", all_columns_csv)
    q = q.replace("$$VALUES_LIST$$", all_values_csv)
    q = q.replace("$$SET_MAPPINGS$$", set_mappings)

    return q


def query_value(value):
    if value is None:
        return "NULL"
    if type(value) in [str, unicode]:
        return "'%s'" % value.replace("'", "''")
    if type(value) == dict:
        return "'%s'" % json.dumps(value).replace("'", "''")
    if type(value) == bool:
        return "%s" % value
    if type(value) == int:
        return "%s" % value
    return value


if __name__ == "__main__":

    my_table_name = 'mytable'
    my_id_column = 'id'
    my_other_columns = ['field1', 'field2']
    my_values_hash = {
        'id': 123,
        'field1': "john",
        'field2': "doe"
    }
    print upsert(my_table_name, my_id_column, my_other_columns, my_values_hash)

-8

सरल में समाधान, लेकिन तुरंत नहीं।
यदि आप इस निर्देश का उपयोग करना चाहते हैं, तो आपको db में एक परिवर्तन करना होगा:

ALTER USER user SET search_path to 'name_of_schema';

इन परिवर्तनों के बाद "INSERT" सही ढंग से काम करेगा।

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