Una pregunta muy frecuente aquí es cómo hacer un upsert, que es lo que MySQL llama INSERT ... ON DUPLICATE UPDATE
y el estándar admite como parte de la MERGE
operación.
Dado que PostgreSQL no lo admite directamente (antes de la página 9.5), ¿cómo se hace esto? Considera lo siguiente:
CREATE TABLE testtable (
id integer PRIMARY KEY,
somedata text NOT NULL
);
INSERT INTO testtable (id, somedata) VALUES
(1, 'fred'),
(2, 'bob');
Ahora imagine que usted quiere "upsert" las tuplas (2, 'Joe')
, (3, 'Alan')
, por lo que los nuevos contenidos de la tabla serían:
(1, 'fred'),
(2, 'Joe'), -- Changed value of existing tuple
(3, 'Alan') -- Added new tuple
De eso es de lo que habla la gente cuando habla de un upsert
. De manera crucial, cualquier enfoque debe ser seguro en presencia de múltiples transacciones que trabajen en la misma mesa , ya sea mediante el bloqueo explícito o defendiéndose de las condiciones de carrera resultantes.
Este tema se discute ampliamente en Insertar, ¿en actualizaciones duplicadas en PostgreSQL? , pero se trata de alternativas a la sintaxis de MySQL, y ha crecido un poco de detalles no relacionados con el tiempo. Estoy trabajando en respuestas definitivas.
Estas técnicas también son útiles para "insertar si no existe, de lo contrario no hacer nada", es decir, "insertar ... ignorar clave duplicada".
fuente
Respuestas:
9.5 y más reciente:
PostgreSQL 9.5 y soporte más reciente
INSERT ... ON CONFLICT UPDATE
(yON CONFLICT DO NOTHING
), es decir, upsert.Comparación con
ON DUPLICATE KEY UPDATE
.Explicación rápida .
Para el uso, consulte el manual , específicamente la cláusula conflict_action en el diagrama de sintaxis y el texto explicativo .
A diferencia de las soluciones para 9.4 y versiones anteriores que se proporcionan a continuación, esta función funciona con múltiples filas en conflicto y no requiere bloqueo exclusivo o un ciclo de reintento.
El commit que agrega la característica está aquí y la discusión sobre su desarrollo está aquí .
Si está en 9.5 y no necesita ser compatible con versiones anteriores, puede dejar de leer ahora .
9.4 y mayores:
PostgreSQL no tiene ninguna función incorporada
UPSERT
(oMERGE
), y hacerlo de manera eficiente frente al uso concurrente es muy difícil.Este artículo analiza el problema con detalles útiles .
En general, debe elegir entre dos opciones:
Bucle de reintento de fila individual
El uso de upserts de fila individuales en un bucle de reintento es la opción razonable si desea que muchas conexiones intenten simultáneamente realizar inserciones.
La documentación de PostgreSQL contiene un procedimiento útil que le permitirá hacer esto en un bucle dentro de la base de datos . Protege contra actualizaciones perdidas e inserta carreras, a diferencia de la mayoría de las soluciones ingenuas. Sin
READ COMMITTED
embargo, solo funcionará en modo y solo es seguro si es lo único que haces en la transacción. La función no funcionará correctamente si los disparadores o las teclas únicas secundarias causan violaciones únicas.Esta estrategia es muy ineficiente. Siempre que sea práctico, debe poner en cola el trabajo y hacer un upsert masivo como se describe a continuación.
Muchos intentos de solución a este problema no consideran las reversiones, por lo que resultan en actualizaciones incompletas. Dos transacciones corren entre sí; uno de ellos con éxito
INSERT
s; el otro obtiene un error de clave duplicada y lo hace en suUPDATE
lugar. LosUPDATE
bloques que esperanINSERT
que retrocedan o se comprometan. Cuando se revierte, laUPDATE
nueva comprobación de la condición coincide con cero filas, por lo que a pesar de lasUPDATE
confirmaciones, en realidad no ha realizado la recuperación que esperaba. Debe verificar el recuento de filas de resultados y volver a intentarlo cuando sea necesario.Algunas soluciones intentadas tampoco logran considerar las carreras SELECT. Si intentas lo obvio y simple:
luego, cuando dos se ejecutan a la vez, hay varios modos de falla. Uno es el problema ya discutido con una nueva verificación de actualización. Otro es donde ambos
UPDATE
al mismo tiempo, coinciden con cero filas y continúan. Luego ambos hacen laEXISTS
prueba, que ocurre antes delINSERT
. Ambos obtienen cero filas, por lo que ambos hacen elINSERT
. Uno falla con un error de clave duplicada.Es por eso que necesita un bucle de reintento. Puede pensar que puede evitar errores clave duplicados o actualizaciones perdidas con SQL inteligente, pero no puede. Debe verificar los recuentos de filas o manejar errores clave duplicados (según el enfoque elegido) y volver a intentarlo.
Por favor, no presente su propia solución para esto. Al igual que con la cola de mensajes, probablemente esté mal.
Upsert a granel con cerradura
A veces, desea realizar una inserción ascendente masiva, donde tiene un nuevo conjunto de datos que desea fusionar en un conjunto de datos existente más antiguo. Esto es mucho más eficiente que las filas superiores individuales y debe preferirse siempre que sea práctico.
En este caso, normalmente sigue el siguiente proceso:
CREATE
unaTEMPORARY
mesaCOPY
o inserte en masa los nuevos datos en la tabla temporalLOCK
la mesa de destinoIN EXCLUSIVE MODE
. Esto permite otras transaccionesSELECT
, pero no realiza ningún cambio en la tabla.Realice uno
UPDATE ... FROM
de los registros existentes utilizando los valores de la tabla temporal;Haga una
INSERT
de las filas que aún no existen en la tabla de destino;COMMIT
, soltando la cerradura.Por ejemplo, para el ejemplo dado en la pregunta, usando valores múltiples
INSERT
para llenar la tabla temporal:Lectura relacionada
MERGE
en el wiki de PostgreSQL¿Qué hay de
MERGE
?El estándar SQL en
MERGE
realidad tiene una semántica de concurrencia mal definida y no es adecuado para la inserción sin bloquear primero una tabla.Es una declaración OLAP realmente útil para la fusión de datos, pero en realidad no es una solución útil para la inserción segura de concurrencia. Hay muchos consejos para las personas que usan otros DBMS para usar
MERGE
en los upserts, pero en realidad está mal.Otros DB:
INSERT ... ON DUPLICATE KEY UPDATE
en MySQLMERGE
de MS SQL Server (pero vea más arriba sobreMERGE
problemas)MERGE
de Oracle (pero vea más arriba sobreMERGE
problemas)fuente
MERGE
para SQL Server y Oracle son incorrectas y propensas a las condiciones de carrera, como se indicó anteriormente. Tendrá que examinar cada DBMS específicamente para averiguar cómo manejarlos, realmente solo puedo ofrecer consejos sobre PostgreSQL. La única forma de hacer una inserción segura de varias filas en PostgreSQL será si se agrega soporte para la inserción nativa al servidor central.Estoy tratando de contribuir con otra solución para el problema de inserción única con las versiones anteriores a 9.5 de PostgreSQL. La idea es simplemente intentar realizar primero la inserción y, en caso de que el registro ya esté presente, actualizarlo:
Tenga en cuenta que esta solución solo se puede aplicar si no hay eliminaciones de filas de la tabla .
No sé sobre la eficiencia de esta solución, pero me parece bastante razonable.
fuente
insert on update
Aquí hay algunos ejemplos para
insert ... on conflict ...
( pág. 9.5+ ):fuente
SQLAlchemy upsert para Postgres> = 9.5
Dado que la publicación grande anterior cubre muchos enfoques SQL diferentes para las versiones de Postgres (no solo no 9.5 como en la pregunta), me gustaría agregar cómo hacerlo en SQLAlchemy si está utilizando Postgres 9.5. En lugar de implementar su propio upsert, también puede usar las funciones de SQLAlchemy (que se agregaron en SQLAlchemy 1.1). Personalmente, recomendaría usar estos, si es posible. No solo por conveniencia, sino también porque le permite a PostgreSQL manejar cualquier condición de carrera que pueda ocurrir.
Publicación cruzada de otra respuesta que di ayer ( https://stackoverflow.com/a/44395983/2156909 )
SQLAlchemy es compatible
ON CONFLICT
ahora con dos métodoson_conflict_do_update()
yon_conflict_do_nothing()
:Copiando de la documentación:
http://docs.sqlalchemy.org/en/latest/dialects/postgresql.html?highlight=conflict#insert-on-conflict-upsert
fuente
Probado en Postgresql 9.3
fuente
SERIALIZABLE
aislamiento, obtendría un aborto con una falla de serialización, de lo contrario, probablemente obtendría una violación única. No reinventes upsert, la reinvención será incorrecta. UsoINSERT ... ON CONFLICT ...
. Si su PostgreSQL es demasiado viejo, actualícelo.INSERT ... ON CLONFLICT ...
no está destinado a la carga masiva. Desde su publicación,LOCK TABLE testtable IN EXCLUSIVE MODE;
dentro de un CTE es una solución para obtener cosas atómicas. No ?insert ... where not exists ...
o similar, por supuesto.Dado que esta pregunta se cerró, estoy publicando aquí sobre cómo lo haces usando SQLAlchemy. A través de la recursividad, vuelve a intentar una inserción o actualización masiva para combatir las condiciones de carrera y los errores de validación.
Primero las importaciones
Ahora un par de funciones auxiliares
Y finalmente la función upsert
Así es como lo usas
La ventaja que tiene sobre esto
bulk_save_objects
es que puede manejar relaciones, verificación de errores, etc. en la inserción (a diferencia de las operaciones masivas ).fuente
SERIALIZABLE
transacciones y manejar fallas de serialización, pero es lento. Necesita manejo de errores y un bucle de reintento. Vea mi respuesta y la sección de "lectura relacionada" en ella.