web-dev-qa-db-ja.com

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

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)

しかし、もっと簡単な方法が欲しいです。

117
Sergey Fedoseev

別の都市にあるサーバーに複数の行を挿入するプログラムを作成しました。

この方法を使用すると、executemanyよりも約10倍高速であることがわかりました。私の場合、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)
198
ant32

新しい execute_valuesメソッド Psycopg 2.7で:

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

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はPython 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')
124
Clodoaldo Neto

psycopg2 2.7で更新:

このスレッドで説明されているように、古典的なexecutemany()は@ ant32の実装(「折り畳まれた」と呼ばれる)の約60倍遅い: https://www.postgresql.org/message-id/20170130215151。 GA7081%40deb76.aryehleib.com

この実装はバージョン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)])

前の回答:

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

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

したがって、Python 3では、.decode('utf-8')を追加して@ ant32のコードを変更する必要があります。

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) 
49

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

最後に紹介したい項目は、辞書を使用して複数の行を挿入する方法です。次のものがある場合:

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)

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

23
ptrn

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'))

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

21
Joseph Sheedy

これらの手法はすべて、Postgresの用語では「拡張挿入」と呼ばれ、2016年11月24日の時点で、psychogpg2の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を使用する必要があることに注意してください;)

6
J.J

ユーザーが指定したバッチサイズとpsycopg2を使用して、DBに行のリストを適切に挿入するには!

def get_batch(iterable, size=100):
    for i in range(0, len(iterable), size):
        yield iterable[i: i + size]


def insert_rows_batch(table, rows, batch_size=500, target_fields=None):
    """
    A utility method to insert batch of tuples(rows) into a table
    NOTE: Handle data type for fields in rows yourself as per your table 
    columns' type.

    :param table: Name of the target table
    :type table: str

    :param rows: The rows to insert into the table
    :type rows: iterable of tuples

    :param batch_size: The size of batch of rows to insert at a time
    :type batch_size: int

    :param target_fields: The names of the columns to fill in the table
    :type target_fields: iterable of strings
    """
    conn = cur = None
    if target_fields:
        target_fields = ", ".join(target_fields)
        target_fields = "({})".format(target_fields)
    else:
        target_fields = ''

    conn = get_conn() # get connection using psycopg2
    if conn:
        cur = conn.cursor()
    count = 0

    for mini_batch in get_batch(rows, batch_size):
        mini_batch_size = len(mini_batch)
        count += mini_batch_size
        record_template = ','.join(["%s"] * mini_batch_size)
        sql = "INSERT INTO {0} {1} VALUES {2};".format(
            table,
            target_fields,
            record_template)
        cur.execute(sql, mini_batch)
        conn.commit()
        print("Loaded {} rows into {} so far".format(count, table))
    print("Done loading. Loaded a total of {} rows".format(count))
    if cur:cur.close()
    if conn:conn.close()

UPSERT(Insert + Update)とバッチを使用するpostgresが必要な場合: postgres_utilities

2
MANU

別のニースで効率的なアプローチ-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 

注意:jsonをサポートするには、postgressが十分に新しい必要があります

1

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

明示的にbytse文字列に変換することは、コード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
jprockbelly

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)
0
Jeff Widman