psycopg2: एक क्वेरी के साथ कई पंक्तियाँ डालें


141

मुझे एक क्वेरी के साथ कई पंक्तियों को सम्मिलित करने की आवश्यकता है (पंक्तियों की संख्या स्थिर नहीं है), इसलिए मुझे इस तरह क्वेरी को निष्पादित करने की आवश्यकता है:

INSERT INTO t (a, b) VALUES (1, 2), (3, 4), (5, 6);

एक ही रास्ता मुझे पता है

args = [(1,2), (3,4), (5,6)]
args_str = ','.join(cursor.mogrify("%s", (x, )) for x in args)
cursor.execute("INSERT INTO t (a, b) VALUES "+args_str)

लेकिन मुझे कुछ सरल तरीका चाहिए।

जवाबों:


219

मैंने एक प्रोग्राम बनाया जिसमें एक शहर में एक सर्वर में कई लाइनें सम्मिलित हैं।

मुझे पता चला कि इस विधि का उपयोग करने की तुलना में लगभग 10 गुना तेज था executemany। मेरे मामले tupमें लगभग 2000 पंक्तियों वाला एक टपल है। इस विधि का उपयोग करते समय लगभग 10 सेकंड लगे:

args_str = ','.join(cur.mogrify("(%s,%s,%s,%s,%s,%s,%s,%s,%s)", x) for x in tup)
cur.execute("INSERT INTO table VALUES " + args_str) 

और इस विधि का उपयोग करते समय 2 मिनट:

cur.executemany("INSERT INTO table VALUES(%s,%s,%s,%s,%s,%s,%s,%s,%s)", tup)

15
अभी भी लगभग दो साल बाद बहुत प्रासंगिक है। आज एक अनुभव बताता है कि जितनी पंक्तियों को आप बढ़ाना चाहते हैं, उतना ही बेहतर होगा कि executeरणनीति का उपयोग किया जाए । मैंने लगभग 100x का स्पीडअप देखा इसके लिए धन्यवाद!
रोब वाट्स

4
शायद executemanyप्रत्येक डालने के बाद एक कमिट चलता है। यदि आप इसके बजाय एक लेन-देन में पूरी बात लपेटते हैं, तो शायद वह चीजों को तेज कर देगा?
रिचर्ड

4
बस इस सुधार की पुष्टि खुद की। जो मैंने psycopg2 पढ़ा executemanyहै, वह कुछ भी इष्टतम नहीं करता है, बस लूप करता है और कई executeकथन करता है । इस विधि का उपयोग करते हुए, एक दूरस्थ सर्वर में एक 700 पंक्ति सम्मिलित 60s से <2s पर गई।
नेल्सन

5
हो सकता है कि मैं पागल हो रहा हूं, लेकिन +ऐसा लगता है कि यह sql इंजेक्शन तक खुल सकता है, इसलिए मुझे लगता है कि मुझे @Clodoaldo Neto execute_values()समाधान अधिक सुरक्षित है।
विल मुन्न

26
यदि कोई व्यक्ति निम्न त्रुटि का सामना करता है: [टाइप करें: अनुक्रम आइटम 0: अपेक्षित str उदाहरण, बाइट्स पाया गया] इस कमांड को बजाय [args_str = ',' चलाएँ। join (cur.mogrify ("(% s,% s"), x। ) .decode ("utf-8") x के लिए tup में)]
mrt

147

Psycopg 2.7 में नई execute_valuesविधि :

data = [(1,'x'), (2,'y')]
insert_query = 'insert into t (a, b) values %s'
psycopg2.extras.execute_values (
    cursor, insert_query, data, template=None, page_size=100
)

Psycopg 2.6 में इसे करने का पायथोनिक तरीका:

data = [(1,'x'), (2,'y')]
records_list_template = ','.join(['%s'] * len(data))
insert_query = 'insert into t (a, b) values {}'.format(records_list_template)
cursor.execute(insert_query, data)

स्पष्टीकरण: यदि सम्मिलित किए जाने वाले डेटा को टुपल्स की सूची के रूप में दिया गया है

data = [(1,'x'), (2,'y')]

तब यह पहले से ही सटीक आवश्यक प्रारूप में है

  1. क्लॉज का valuesसिंटैक्स insertरिकॉर्ड की सूची की अपेक्षा करता है

    insert into t (a, b) values (1, 'x'),(2, 'y')

  2. Psycopgएक अजगर tupleको एक पोस्टग्रैस्कल के लिए अनुकूल करता है record

एकमात्र आवश्यक कार्य मानसोपग द्वारा भरे जाने के लिए रिकॉर्ड सूची टेम्पलेट प्रदान करना है

# We use the data list to be sure of the template length
records_list_template = ','.join(['%s'] * len(data))

और इसे insertक्वेरी में रखें

insert_query = 'insert into t (a, b) values {}'.format(records_list_template)

insert_queryआउटपुट प्रिंट करना

insert into t (a, b) values %s,%s

अब सामान्य Psycopgतर्क प्रतिस्थापन के लिए

cursor.execute(insert_query, data)

या बस परीक्षण क्या सर्वर को भेजा जाएगा

print (cursor.mogrify(insert_query, data).decode('utf8'))

आउटपुट:

insert into t (a, b) values (1, 'x'),(2, 'y')

1
इस विधि का प्रदर्शन cur.copy_from के साथ कैसे तुलना करता है?
माइकल सुनार

1
यहाँ एक बेंचमार्क के साथ एक जिस्ट है । 10 मीटर रिकॉर्ड के साथ मेरी मशीन पर लगभग 6.5X तेजी से copy_from तराजू।
जोसेफ शील्ड

अच्छा लगता है - मुझे लगता है कि आपके पास आवेषण_क्वरी की आपकी प्रारंभिक परिभाषा के अंत में एक भटका हुआ है (जब तक कि आप इसे टपल बनाने की कोशिश नहीं कर रहे थे)?
डेडकोड

2
का उपयोग करते हुए execute_valuesमैं अपने सिस्टम चल रहा है 1k रिकॉर्ड पर एक मिनट 128k रिकॉर्ड तक एक मिनट प्राप्त करने में सक्षम था
Conrad.Dean

66

Psycopg2 2.7 के साथ अपडेट करें:

executemany()इस सूत्र में बताया गया है कि क्लासिक @ ant32 के कार्यान्वयन (जिसे "तह" कहा जाता है) की तुलना में लगभग 60 गुना धीमा है: https://www.postgresql.org/message-id/20170130215151.GA7081%40d7676.aryehleib.com

इस कार्यान्वयन को psycopg2 में 2.7 संस्करण में जोड़ा गया था और कहा जाता है execute_values():

from psycopg2.extras import execute_values
execute_values(cur,
    "INSERT INTO test (id, v1, v2) VALUES %s",
    [(1, 2, 3), (4, 5, 6), (7, 8, 9)])

पिछला उत्तर:

कई पंक्तियों को सम्मिलित करने के लिए, psycopg2 का उपयोग करने की तुलना में मल्टीक्स VALUESसिंटैक्स का उपयोग execute()लगभग 10x तेज है executemany()। दरअसल, executemany()सिर्फ कई व्यक्तिगत INSERTबयानों को चलाता है ।

@ एंट 32 का कोड पायथन 2 में पूरी तरह से काम करता है। लेकिन पायथन 3 में, cursor.mogrify()बाइट्स देता है, cursor.execute()या तो बाइट्स या स्ट्रिंग्स लेता है, और उदाहरण की ','.join()उम्मीद करता है str

तो पायथन 3 में आपको जोड़कर @ ant32 का कोड संशोधित करना पड़ सकता है .decode('utf-8'):

args_str = ','.join(cur.mogrify("(%s,%s,%s,%s,%s,%s,%s,%s,%s)", x).decode('utf-8') for x in tup)
cur.execute("INSERT INTO table VALUES " + args_str)

या केवल बाइट्स का उपयोग करके ( b''या के साथ b""):

args_bytes = b','.join(cur.mogrify("(%s,%s,%s,%s,%s,%s,%s,%s,%s)", x) for x in tup)
cur.execute(b"INSERT INTO table VALUES " + args_bytes) 

26

कर्सर.कॉपी_फ्रॉम सबसे तेज समाधान है जो मैंने अब तक थोक आवेषण के लिए पाया है। यहाँ एक जिस्ट है जिसे IteratorFile नाम का एक वर्ग बनाया गया है जो एक इटरेटर उपज स्ट्रिंग्स को एक फाइल की तरह पढ़ने की अनुमति देता है। हम एक जनरेटर अभिव्यक्ति का उपयोग करके प्रत्येक इनपुट रिकॉर्ड को एक स्ट्रिंग में बदल सकते हैं। तो समाधान होगा

args = [(1,2), (3,4), (5,6)]
f = IteratorFile(("{}\t{}".format(x[0], x[1]) for x in args))
cursor.copy_from(f, 'table_name', columns=('a', 'b'))

आर्गन्स के इस तुच्छ आकार के लिए यह बहुत अधिक गति का अंतर नहीं करेगा, लेकिन हजारों + पंक्तियों के साथ काम करते समय मुझे बड़े स्पीडअप दिखाई देते हैं। यह एक विशाल क्वेरी स्ट्रिंग बनाने की तुलना में अधिक मेमोरी कुशल भी होगा। एक इटैलर कभी-कभी एक समय में केवल एक इनपुट रिकॉर्ड मेमोरी में रखता है, जहां कुछ बिंदु पर आप अपनी पायथन प्रक्रिया में मेमोरी से बाहर निकल जाएंगे या क्वेरी स्ट्रिंग का निर्माण करके पोस्टग्रेज में।


3
यहाँ एक बेंचमार्क है जो एक क्वेरी बिल्डर समाधान के साथ copy_from / IteratorFile की तुलना करता है। 10 मीटर रिकॉर्ड के साथ मेरी मशीन पर copy_from तराजू लगभग 6.5X।
जोसेफ शीदी

3
आप तार और टाइमस्टैम्प आदि से बचने के साथ चारों ओर डिक करना है?
CpILL

हां, आपको यह सुनिश्चित करना होगा कि आपके पास एक अच्छी तरह से गठित टीएसवी रिकॉर्ड हो।
जोसेफ शेयडी

24

Postgresql.org पर Psycopg2 के ट्यूटोरियल पेज से एक स्निपेट (नीचे देखें) :

एक अंतिम आइटम मैं आपको दिखाना चाहूंगा कि एक शब्दकोश का उपयोग करके कई पंक्तियों को कैसे सम्मिलित किया जाए। यदि आपके पास निम्नलिखित थे:

namedict = ({"first_name":"Joshua", "last_name":"Drake"},
            {"first_name":"Steven", "last_name":"Foo"},
            {"first_name":"David", "last_name":"Bar"})

आप आसानी से उपयोग करके शब्दकोश में सभी तीन पंक्तियों को सम्मिलित कर सकते हैं:

cur = conn.cursor()
cur.executemany("""INSERT INTO bar(first_name,last_name) VALUES (%(first_name)s, %(last_name)s)""", namedict)

यह ज्यादा कोड नहीं बचाता है, लेकिन यह निश्चित रूप से बेहतर दिखता है।


35
यह कई व्यक्तिगत INSERTबयान चलाएगा । उपयोगी, लेकिन एक एकल बहु- VALUEडी डालने के समान नहीं ।
क्रेग रिंगर

7

इन सभी तकनीकों को पोस्टग्रेज शब्दावली में 'एक्सटेंडेड इंसर्ट्स' कहा जाता है, और 24 नवंबर 2016 तक, यह साइकोपग 2 की एक्सेनीमनी () और इस धागे में सूचीबद्ध सभी अन्य विधियों की तुलना में एक टन तेज है (जो इस तक आने से पहले कोशिश की गई थी) जवाब)।

यहां कुछ कोड दिए गए हैं जो cur.mogrify का उपयोग करते हैं और अच्छा है और बस अपना सिर चारों ओर पाने के लिए है:

valueSQL = [ '%s', '%s', '%s', ... ] # as many as you have columns.
sqlrows = []
rowsPerInsert = 3 # more means faster, but with diminishing returns..
for row in getSomeData:
        # row == [1, 'a', 'yolo', ... ]
        sqlrows += row
        if ( len(sqlrows)/len(valueSQL) ) % rowsPerInsert == 0:
                # sqlrows == [ 1, 'a', 'yolo', 2, 'b', 'swag', 3, 'c', 'selfie' ]
                insertSQL = 'INSERT INTO "twitter" VALUES ' + ','.join(['(' + ','.join(valueSQL) + ')']*rowsPerInsert)
                cur.execute(insertSQL, sqlrows)
                con.commit()
                sqlrows = []
insertSQL = 'INSERT INTO "twitter" VALUES ' + ','.join(['(' + ','.join(valueSQL) + ')']*len(sqlrows))
cur.execute(insertSQL, sqlrows)
con.commit()

लेकिन यह ध्यान दिया जाना चाहिए कि यदि आप copy_from () का उपयोग कर सकते हैं, तो आपको copy_from का उपयोग करना चाहिए;)


मृतकों को ऊपर लाना, लेकिन पिछली कुछ पंक्तियों की स्थिति में क्या होता है? मुझे लगता है कि आप वास्तव में अंतिम शेष पंक्तियों पर फिर से अंतिम खंड चलाते हैं, इस मामले में आपके पास पंक्तियों की संख्या भी है?
mcpeterson

सही है, खेद है कि मैं यह करना भूल गया था कि जब मैंने उदाहरण लिखा था - मुझे बहुत बेवकूफ लगता है। ऐसा नहीं करने से लोगों को एक त्रुटि नहीं मिली होगी, जो मुझे चिंतित करती है कि कितने लोग समाधान को कॉपी / पेस्ट करते हैं और अपने व्यापार के बारे में जाते हैं ..... वैसे भी, बहुत आभारी mcpeterson - धन्यवाद!
जेजे

2

मैं कई वर्षों से ant32 के उत्तर का उपयोग कर रहा हूं। हालांकि मैंने पाया है कि अजगर 3 में एक त्रुटि हैmogrify एक बाइट स्ट्रिंग लौटाता है।

स्पष्ट रूप से बाइटिंग स्ट्रिंग्स को परिवर्तित करना कोड पायथन 3 को संगत बनाने का एक सरल उपाय है।

args_str = b','.join(cur.mogrify("(%s,%s,%s,%s,%s,%s,%s,%s,%s)", x) for x in tup) 
cur.execute(b"INSERT INTO table VALUES " + args_str)

1

एक और अच्छा और कुशल दृष्टिकोण - सम्मिलन के लिए पंक्तियों को 1 तर्क के रूप में पारित करना है, जो कि ऑब्जेक्ट्स की सरणी है।

जैसे आप तर्क दे रहे हैं:

[ {id: 18, score: 1}, { id: 19, score: 5} ]

यह सरणी है, जिसमें किसी भी वस्तु के अंदर की मात्रा हो सकती है। तब आपका SQL जैसा दिखता है:

INSERT INTO links (parent_id, child_id, score) 
SELECT 123, (r->>'id')::int, (r->>'score')::int 
FROM unnest($1::json[]) as r 

सूचना: जसन का समर्थन करने के लिए आपका पोस्टग्रेशन काफी नया होना चाहिए


1

@ Jopseph.sheedy ( https://stackoverflow.com/users/958118/joseph-sheedy ) ऊपर ( https://stackoverflow.com/a/30721460910006464 ) द्वारा प्रदान किया गया कर्सर . copyfrom समाधान वास्तव में तेज़ बिजली है।

हालांकि, वह जो उदाहरण देता है, वह किसी भी संख्या में फ़ील्ड के साथ रिकॉर्ड के लिए सामान्य रूप से उपयोग करने योग्य नहीं होता है और मुझे यह पता लगाने में समय लगता है कि कैसे सही तरीके से उपयोग किया जाए।

IteratorFile को इस तरह से टैब से अलग किए गए फ़ील्ड के साथ त्वरित करने की आवश्यकता होती है ( rयह उन डाइक की सूची है जहां प्रत्येक रिकॉर्ड एक रिकॉर्ड है):

    f = IteratorFile("{0}\t{1}\t{2}\t{3}\t{4}".format(r["id"],
        r["type"],
        r["item"],
        r["month"],
        r["revenue"]) for r in records)

फ़ील्ड की एक मनमानी संख्या के लिए सामान्यीकरण करने के लिए, हम पहले टैब और फ़ील्ड प्लेसहोल्डर्स की सही मात्रा के साथ एक लाइन स्ट्रिंग बनाएंगे: "{}\t{}\t{}....\t{}"और फिर .format()फ़ील्ड मानों को भरने के लिए उपयोग करें *list(r.values())) for r in records:

        line = "\t".join(["{}"] * len(records[0]))

        f = IteratorFile(line.format(*list(r.values())) for r in records)

यहाँ जिस्ट में पूरा कार्य ।


0

यदि आप SQLAlchemy का उपयोग कर रहे हैं, तो आपको स्ट्रिंग को हैंड-क्राफ्टिंग के साथ गड़बड़ करने की आवश्यकता नहीं है क्योंकि SQLAlchemy एक एकल स्टेटमेंट के लिए एक बहु-पंक्ति VALUESक्लॉज उत्पन्न करने का समर्थन करता हैINSERT :

rows = []
for i, name in enumerate(rawdata):
    row = {
        'id': i,
        'name': name,
        'valid': True,
    }
    rows.append(row)
if len(rows) > 0:  # INSERT fails if no rows
    insert_query = SQLAlchemyModelName.__table__.insert().values(rows)
    session.execute(insert_query)

हुड के तहत SQLAlchemy इस तरह के कॉल के लिए psychopg2 के निष्पादक () का उपयोग करता है और इसलिए इस उत्तर में बड़े प्रश्नों के लिए गंभीर प्रदर्शन मुद्दे होंगे। निष्पादित विधि देखें docs.sqlalchemy.org/en/latest/orm/session_api.html
ऋषि 88

2
मुझे नहीं लगता कि ऐसा है। जब से मैंने इसे देखा है यह थोड़ा सा है, लेकिन IIRC, यह वास्तव में insert_queryलाइन में एक एकल सम्मिलन विवरण का निर्माण कर रहा है । फिर, session.execute()केवल psycopg2 के execute()बयान को एक बड़े पैमाने पर स्ट्रिंग के साथ बुला रहा है । तो "चाल" पहले संपूर्ण सम्मिलन कथन ऑब्जेक्ट का निर्माण कर रहा है। मैं एक समय में 200,000 पंक्तियों को सम्मिलित करने के लिए इसका उपयोग कर रहा हूं और सामान्य की तुलना में इस कोड का उपयोग करते हुए बड़े पैमाने पर प्रदर्शन बढ़ता है executemany()
जेफ विडमैन

1
SQLAlchemy आपके द्वारा लिंक किए गए दस्तावेज़ में एक अनुभाग है जो दिखाता है कि यह कैसे काम करता है और यहां तक ​​कि कहता है: "यह ध्यान रखना आवश्यक है कि कई मानों को पारित करना पारंपरिक निष्पादक () फ़ॉर्म" का उपयोग करने के समान नहीं है। तो यह स्पष्ट रूप से कह रहा है कि यह काम करता है।
जेफ विडमैन

1
मुझे सही साबित होना है। मैंने आपके उपयोग के मूल्यों () विधि (SQLAlchemy के बिना बस निष्पाद्य करता है) पर ध्यान नहीं दिया। मैं उस डॉक्यूमेंट का लिंक शामिल करने के लिए उत्तर को संपादित करने के लिए कहूंगा ताकि मैं अपना वोट बदल सकूं, लेकिन जाहिर है कि आप इसे पहले ही शामिल कर चुके हैं। शायद उल्लेख करते हैं कि यह एक सम्मिलित रूप से कॉल करने के समान नहीं है () निष्पादन के साथ () dicts की सूची के साथ?
ऋषि 88

यह execute_values ​​की तुलना में कैसा प्रदर्शन करता है?
मिस्टर

0

यह सवाल पोस्ट किए जाने के बाद से psycopg2 में execute_batch जोड़ा गया है।

यह निष्पादित करने की तुलना में धीमा है, लेकिन उपयोग करने के लिए सरल है।


2
अन्य टिप्पणियाँ देखें। psycopg2 की विधि execute_valuesहै तेजी सेexecute_batch
Fierr

0

एग्जीमनी ट्यूपल्स की सरणी को स्वीकार करते हैं

https://www.postgresqltutorial.com/postgresql-python/insert/

    """ array of tuples """
    vendor_list = [(value1,)]

    """ insert multiple vendors into the vendors table  """
    sql = "INSERT INTO vendors(vendor_name) VALUES(%s)"
    conn = None
    try:
        # read database configuration
        params = config()
        # connect to the PostgreSQL database
        conn = psycopg2.connect(**params)
        # create a new cursor
        cur = conn.cursor()
        # execute the INSERT statement
        cur.executemany(sql,vendor_list)
        # commit the changes to the database
        conn.commit()
        # close communication with the database
        cur.close()
    except (Exception, psycopg2.DatabaseError) as error:
        print(error)
    finally:
        if conn is not None:
            conn.close()

-1

यदि आप एक सम्मिलित स्थिति में कई पंक्तियों को सम्मिलित करना चाहते हैं (यह मानते हुए कि आप ORM का उपयोग नहीं कर रहे हैं) तो मेरे लिए अब तक का सबसे आसान तरीका शब्दकोशों की सूची का उपयोग करना होगा। यहाँ एक उदाहरण है:

 t = [{'id':1, 'start_date': '2015-07-19 00:00:00', 'end_date': '2015-07-20 00:00:00', 'campaignid': 6},
      {'id':2, 'start_date': '2015-07-19 00:00:00', 'end_date': '2015-07-20 00:00:00', 'campaignid': 7},
      {'id':3, 'start_date': '2015-07-19 00:00:00', 'end_date': '2015-07-20 00:00:00', 'campaignid': 8}]

conn.execute("insert into campaign_dates
             (id, start_date, end_date, campaignid) 
              values (%(id)s, %(start_date)s, %(end_date)s, %(campaignid)s);",
             t)

जैसा कि आप देख सकते हैं कि केवल एक क्वेरी निष्पादित की जाएगी:

INFO sqlalchemy.engine.base.Engine insert into campaign_dates (id, start_date, end_date, campaignid) values (%(id)s, %(start_date)s, %(end_date)s, %(campaignid)s);
INFO sqlalchemy.engine.base.Engine [{'campaignid': 6, 'id': 1, 'end_date': '2015-07-20 00:00:00', 'start_date': '2015-07-19 00:00:00'}, {'campaignid': 7, 'id': 2, 'end_date': '2015-07-20 00:00:00', 'start_date': '2015-07-19 00:00:00'}, {'campaignid': 8, 'id': 3, 'end_date': '2015-07-20 00:00:00', 'start_date': '2015-07-19 00:00:00'}]
INFO sqlalchemy.engine.base.Engine COMMIT

Sqlalchemy इंजन से लॉगिंग दिखाना केवल एक क्वेरी चलाना नहीं है, इसका मतलब है कि sqlalchemy इंजन एक कमांड चलाता है। हुड के तहत यह साइकोपग 2 के एक्ज़िमनी का उपयोग कर रहा है जो बहुत अक्षम है। निष्पादित विधि देखें docs.sqlalchemy.org/en/latest/orm/session_api.html
ऋषि 88

-3

Aiopg का उपयोग करना - नीचे का स्निपेट पूरी तरह से ठीक काम करता है

    # items = [10, 11, 12, 13]
    # group = 1
    tup = [(gid, pid) for pid in items]
    args_str = ",".join([str(s) for s in tup])
    # insert into group values (1, 10), (1, 11), (1, 12), (1, 13)
    yield from cur.execute("INSERT INTO group VALUES " + args_str)


-4

अंत में SQLalchemy1.2 वर्जन में, यह नया कार्यान्वयन psemopg2.extras.execute_batch () को निष्पादित करने के बजाय, जब आप अपने इंजन को use_batch_mode के साथ प्रारंभ करते हैं, तो इसे सही जोड़ा जाता है।

engine = create_engine(
    "postgresql+psycopg2://scott:tiger@host/dbname",
    use_batch_mode=True)

http://docs.sqlalchemy.org/en/latest/changelog/migration_12.html#change-4109

तब किसी को SQLalchmey का उपयोग करने के लिए sqla और psycopg2 के विभिन्न संयोजनों की कोशिश करने और एक साथ प्रत्यक्ष SQL को परेशान नहीं करना होगा।

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