psycopg2:使用一个查询插入多行

时间:2011-11-15 10:09:25

标签: python postgresql psycopg2

我需要用一个查询插入多行(行数不是常量),所以我需要像这样执行查询:

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)

但我想要一些更简单的方法。

16 个答案:

答案 0 :(得分:190)

我构建了一个程序,可以将多行插入到位于另一个城市的服务器上。

我发现使用这种方法比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)

答案 1 :(得分:119)

Psycopg 2.7中的新execute_values method

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. Psycopg将Python tuple调整为Postgresql record

  3. 唯一必要的工作是提供一个由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')
    

答案 2 :(得分:46)

使用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)])

上一个答案:

要插入多行,使用带VALUES的多行execute()语法比使用psycopg2 executemany()快约10倍。实际上,executemany()只会运行许多单独的INSERT语句。

@ ant32的代码在Python 2中完美运行。但是在Python 3中,cursor.mogrify()返回字节,cursor.execute()获取字节或字符串,','.join()期望str 1}}实例。

因此,在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) 

答案 3 :(得分:23)

来自Psycopg2教程页面{@ 3}}的摘录:

  

我想向您展示的最后一项是如何使用字典插入多行。如果您有以下内容:

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)

它不会节省太多代码,但它确实看起来更好。

答案 4 :(得分:21)

cursor.copy_from是迄今为止我发现的批量插入最快的解决方案。 Here's a gist我创建了一个名为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,它不会产生很大的速度差异,但是当我处理数千+行时,我看到了很大的加速。它也比构建一个巨大的查询字符串更有效。迭代器一次只能在内存中保存一条输入记录,在某些时候,您在Python进程或Postgres中通过构建查询字符串会耗尽内存。

答案 5 :(得分:6)

所有这些技术在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;)

答案 6 :(得分:3)

使用用户指定的批量大小和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()

如果你想在批量的postgres中使用UPSERT(插入+更新):postgres_utilities

答案 7 :(得分:1)

另一种优秀而有效的方法 - 将行插入作为1参数传递, 这是json对象的数组。

E.g。你传递参数:

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

答案 8 :(得分:1)

我几年来一直在使用ant32的答案。但是我发现在python 3中这是一个错误,因为mogrify返回一个字节字符串。

显式转换为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)

答案 9 :(得分:1)

执行接受元组数组

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

error FS0001: The type 'int -> seq<int>' is not compatible with the type 'seq<'a>'

答案 10 :(得分:0)

如果您正在使用SQLAlchemy,那么您不需要手工制作字符串,因为SQLAlchemy supports generating a multi-row VALUES clause for a single INSERT statement

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)

答案 11 :(得分:0)

上面(cursor.copyfrom)的@jopseph.sheedyhttps://stackoverflow.com/users/958118/joseph-sheedy)提供的https://stackoverflow.com/a/30721460/11100064解决方案确实快如闪电。

但是,他给出的示例不适用于具有任意多个字段的记录,并且花了我一些时间才弄清楚如何正确使用它。

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()填写字段值:{{ 1}}:

*list(r.values())) for r in records

gist here中的完整功能。

答案 12 :(得分:0)

自发布此问题以来,

execute_batch已添加到psycopg2。

它比execute_values慢,但使用起来更简单。

答案 13 :(得分:-1)

如果你想在一个插入statemens中插入多行(假设你没有使用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

答案 14 :(得分:-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)

答案 15 :(得分:-3)

最后在SQLalchemy1.2版本中,当使用use_batch_mode = True初始化引擎时,添加此新实现以使用psycopg2.extras.execute_batch()而不是executemany:

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直接组合在一起..

相关问题