psycopg2: inserta varias filas con una consulta
Necesito insertar varias filas con una consulta (el número de filas no es constante), por lo que necesito ejecutar una consulta como esta:
INSERT INTO t (a, b) VALUES (1, 2), (3, 4), (5, 6);
La única manera que sé es
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)
pero quiero una forma más sencilla.
Construí un programa que inserta varias líneas en un servidor que estaba ubicado en otra ciudad.
Descubrí que usar este método era aproximadamente 10 veces más rápido que executemany
. En mi caso tup
es una tupla que contiene alrededor de 2000 filas. Me llevó unos 10 segundos utilizar este método:
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)
y 2 minutos cuando se utiliza este método:
cur.executemany("INSERT INTO table VALUES(%s,%s,%s,%s,%s,%s,%s,%s,%s)", tup)
Nuevo execute_values
método en 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
)
La forma pitónica de hacerlo en 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)
Explicación: Si los datos a insertar se proporcionan como una lista de tuplas como en
data = [(1,'x'), (2,'y')]
entonces ya está en el formato requerido exacto como
la
values
sintaxis de lainsert
cláusula espera una lista de registros como eninsert into t (a, b) values (1, 'x'),(2, 'y')
Psycopg
adapta un Pythontuple
a un Postgresqlrecord
.
El único trabajo necesario es proporcionar una plantilla de lista de registros para que la complete psycopg.
# We use the data list to be sure of the template length
records_list_template = ','.join(['%s'] * len(data))
y colocarlo en la insert
consulta
insert_query = 'insert into t (a, b) values {}'.format(records_list_template)
Imprimir las insert_query
salidas
insert into t (a, b) values %s,%s
Ahora a la Psycopg
sustitución habitual de argumentos.
cursor.execute(insert_query, data)
O simplemente probar lo que se enviará al servidor.
print (cursor.mogrify(insert_query, data).decode('utf8'))
Producción:
insert into t (a, b) values (1, 'x'),(2, 'y')
Actualización con psycopg2 2.7:
El clásico executemany()
es aproximadamente 60 veces más lento que la implementación de @ant32 (llamada "plegada") como se explica en este hilo: https://www.postgresql.org/message-id/20170130215151.GA7081%40deb76.aryehleib.com
Esta implementación se agregó a psycopg2 en la versión 2.7 y se llama 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)])
Respuesta anterior:
Para insertar varias filas, usar la VALUES
sintaxis de varias filas con execute()
es aproximadamente 10 veces más rápido que usar psycopg2 executemany()
. De hecho, executemany()
simplemente ejecuta muchas INSERT
declaraciones individuales.
El código de @ant32 funciona perfectamente en Python 2. Pero en Python 3, cursor.mogrify()
devuelve bytes, cursor.execute()
toma bytes o cadenas y ','.join()
espera str
una instancia.
Entonces, en Python 3 es posible que necesites modificar el código de @ant32 agregando .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)
O usando bytes (con b''
o 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)