psycopg2:1つのクエリで複数の行を挿入する


141

1つのクエリで複数の行を挿入する必要があるため(行数は一定ではありません)、次のようなクエリを実行する必要があります。

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
ほぼ2年後もなお非常に関連があります。今日の経験では、プッシュする行の数が増えるほど、execute戦略を使用するほうがよいことが示唆されています。これで100倍ぐらいスピードアップしました!
Rob Watts

4
おそらくexecutemany、各挿入後にコミットを実行します。代わりにすべてをトランザクションでラップすると、多分それは事態を早めるでしょうか?
リチャード

4
この改善を自分で確認したところです。私がpsycopg2を読んだことからexecutemany、最適なことは何も行われず、ループし、多くのexecuteステートメントを実行するだけです。この方法を使用すると、リモートサーバーへの700行の挿入は60秒から<2秒になりました。
ネルソン

5
多分私は偏執的ですが、+SQLインジェクションにつながる可能性があるようにクエリを連結すると、@ Clodoaldo Neto execute_values()ソリューションの方が安全だと思います。
Will Munn

26
誰かが次のエラーに遭遇した場合:[TypeError:シーケンス項目0:予期されるstrインスタンス、見つかったバイト]代わりにこのコマンドを実行します[args_str = '、'。join(cur.mogrify( "(%s、%s)"、x ).decode( "utf-8")for x in tup)]
mrt

146

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でそれを行うpythonicの方法:

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. PsycopgPython tupleをPostgresqlに適合させrecordます。

必要な作業は、psycopgが入力するレコードリストテンプレートを提供することだけです。

# 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とどのように比較されますか?
Michael Goldshteyn

1
これがベンチマークの要点です。copy_fromは、私のマシンでは10Mレコードで約6.5倍速くスケーリングします。
ジョセフシーディ2016

見栄えが良いです-insert_queryの最初の定義の最後に(タプルにしようとしているのでない限り)、あなたはstrayを持っていると思います。また、insert_queryの最初の定義でも%sの%の後にないためです。
デッドコード

2
を使用execute_valuesして、システムを1分間に1万レコードで、最大で1分間に
128万

66

psycopg2 2.7で更新します。

executemany()このスレッドで説明されているように、クラシックは@ ant32の実装(「フォールド」と呼ばれる)よりも約60倍遅くなります。ます。https

この実装は、バージョン2.7でpsycopg2に追加され、次のように呼び出され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)])

以前の回答:

複数行を挿入するには、複数行VALUES構文を使用すると、psycopg2を使用するexecute()よりも約10倍高速ですexecutemany()。実際、executemany()多くの個々のINSERTステートメントを実行するだけです。

@ ant32のコードはPython 2で完全に機能しcursor.mogrify()ますが、Python 3ではバイトを返し、cursor.execute()バイトまたは文字列を受け取り、インスタンスを','.join()期待しstrます。

したがって、Python 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

cursor.copy_fromは、一括挿入でこれまでに見つかった最速のソリューションです。これは、文字列を生成するイテレータがファイルのように読み取れるようにする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'))

この取るに足らないサイズのargsの場合、速度の違いはあまりありませんが、数千行以上を処理するときに大幅なスピードアップが見られます。また、巨大なクエリ文字列を作成するよりもメモリ効率が向上します。イテレータは一度に1つの入力レコードのみをメモリに保持します。この場合、ある時点で、クエリ文字列を作成することにより、PythonプロセスまたはPostgresでメモリが不足します。


3
次に、 copy_from / IteratorFileとクエリビルダーソリューションを比較するベンチマークを示します。copy_fromは、私のマシンでは10Mレコードで約6.5倍速くスケーリングします。
ジョセフシーディ2016

3
文字列やタイムスタンプなどをエスケープする必要がありますか?
CpILL 2017年

はい、適切な形式のTSVレコードがあることを確認する必要があります。
ジョセフシーディ2017年

24

Psycopg2のPostgresql.orgのチュートリアルページのスニペット(下を参照)

最後に、ディクショナリを使用して複数の行を挿入する方法について説明します。次の場合:

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

次のコマンドを使用すると、ディクショナリ内に3行すべてを簡単に挿入できます。

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

それは多くのコードを保存しませんが、間違いなくより良く見えます。


35
これにより、多くの個別のINSERTステートメントが実行されます。便利ですが、単一のmulti- VALUEdインサートと同じではありません。
クレイグリンガー

7

これらのテクニックはすべてPostgresの用語で「拡張挿入」と呼ばれ、2016年11月24日の時点で、psychopg2のexecutemany()およびこのスレッドにリストされている他のすべてのメソッド(これに行く前に試しました)よりも高速です。回答)。

以下に、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 2016年

正直、申し訳ありませんが、私が例を書いたときに忘れてしまったに違いありません。そうしないと、人々にエラーが発生することはなかったでしょう。そのため、何人がソリューションをコピー/貼り付けて、ビジネスに取り掛かったのか心配しました...とにかく、非常に感謝しているmcpeterson-ありがとうございます!
JJ

2

私は上記のant32の答えを数年間使用しています。ただしmogrify、バイト文字列を返すため、Python 3でエラーが発生することがわかりました。

明示的にバイト列に変換することは、コードをpython 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つの優れた効率的なアプローチ-挿入する行を1つの引数として渡すことです。これはjsonオブジェクトの配列です。

たとえば、引数を渡します:

[ {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 

通知:あなたのpostgressはjsonをサポートするのに十分に新しくなければなりません


1

上記の@ jopseph.sheedyhttps://stackoverflow.com/users/958118/joseph-sheedy)によって提供されるcursor.copyfromソリューション(https://stackoverflow.com/a/30721460/11100064)実際に雷高速です。

ただし、彼が提供する例は、任意の数のフィールドを持つレコードには一般的に使用できず、正しく使用する方法を理解するのに時間がかかりました。

IteratorFile rは、次のようなタブ区切りフィールドでインスタンス化する必要があります(各dictがレコードであるdictのリストです)。

    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のexecutemany()を使用しているため、この回答では、大規模なクエリのパフォーマンスに重大な問題が生じます。実行メソッドdocs.sqlalchemy.org/en/latest/orm/session_api.htmlを参照してください。
sage88 2017年

2
そうではないと思います。これを見てから少し時間が経ちましたが、IIRC、これは実際には行に単一の挿入ステートメントを構築していinsert_queryます。次に、単一の大規模な文字列でsession.execute()psycopg2のexecute()ステートメントを呼び出すだけです。したがって、「トリック」は最初に挿入ステートメントオブジェクト全体を構築しています。これを使用して一度に200,000行を挿入し、このコードを使用すると、通常のコードと比べてパフォーマンスが大幅に向上しましたexecutemany()
ジェフウィドマン2017年

1
リンクしたSQLAlchemyドキュメントには、これがどのように機能するかを正確に示すセクションがあり、「複数の値を渡すことは、従来のexecutemany()フォームを使用するのと同じではないことに注意することが重要です」と述べています。したがって、これが機能することを明示的に呼びかけています。
ジェフウィドマン2017年

1
私は修正された立場です。values()メソッドの使い方に気づきませんでした(これがないと、SQLAlchemyはexecutemanyを実行するだけです)。回答を編集して、そのドキュメントへのリンクを含めて投票を変更できるようにしますが、明らかにあなたはすでにそれを含めています。おそらく、これは、dictsのリストを指定して、execute()でinsert()を呼び出すことと同じではないことに言及しますか?
sage88 2017年

execute_valuesと比較してどのように機能しますか?
MrR

0

この質問が投稿されてから、execute_batchがpsycopg2に追加されました。

execute_valuesよりも低速ですが、使用は簡単です。


2
他のコメントを参照してください。psycopg2の方法がexecute_valuesある速くよりexecute_batch
Fierr

0

executemanyタプルの配列を受け入れる

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

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)

ご覧のとおり、実行されるクエリは1つだけです。

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エンジンが1つのコマンドを実行したことを意味します。内部的には、これは非常に非効率的なpsychopg2の実行を使用しています。実行メソッドdocs.sqlalchemy.org/en/latest/orm/session_api.htmlを参照してください。
sage88 2017年

-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バージョンでは、次のようにuse_batch_mode = Trueでエンジンを初期化するときに、executemanyではなくpsycopg2.extras.execute_batch()を使用するために、この新しい実装が追加されています。

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を一緒に送信したりする必要がなくなります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.