我需要用一个查询插入多行(行数不是常量),所以我需要像这样执行查询:
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)
但我想要一些更简单的方法。
答案 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')]
然后它已经是
所需的格式 values
子句的insert
语法需要一个记录列表,如
insert into t (a, b) values (1, 'x'),(2, 'y')
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')
答案 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.sheedy(https://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直接组合在一起..