¿Cómo emular "insertar ignorar" y "en actualización de clave duplicada" (combinación de sql) con postgresql?

140

Algunos servidores SQL tienen una característica que INSERTse omite si viola una restricción de clave principal / única. Por ejemplo, MySQL tiene INSERT IGNORE.

¿Cuál es la mejor manera de emular INSERT IGNOREy ON DUPLICATE KEY UPDATEcon PostgreSQL?

gpilotino
fuente
Ver también: stackoverflow.com/questions/5269590/…
Dave Jarvis el
66
a partir del 9.5, es posible de forma nativa: stackoverflow.com/a/34639631/4418
warren
Emular MySQL: ON DUPLICATE KEY UPDATEen PgSQL 9.5 todavía es algo imposible, porque el ON CLAUSEequivalente de PgSQL requiere que proporcione el nombre de la restricción, mientras que MySQL podría capturar cualquier restricción sin la necesidad de definirlo. Esto me impide "emular" esta función sin tener que volver a escribir consultas.
NeverEndingQueue

Respuestas:

35

Intenta hacer una ACTUALIZACIÓN. Si no modifica ninguna fila, eso significa que no existía, entonces haga una inserción. Obviamente, haces esto dentro de una transacción.

Por supuesto, puede envolver esto en una función si no desea poner el código adicional en el lado del cliente. También necesita un bucle para la rara condición de carrera en ese pensamiento.

Hay un ejemplo de esto en la documentación: http://www.postgresql.org/docs/9.3/static/plpgsql-control-structures.html , ejemplo 40-2 justo en la parte inferior.

Esa suele ser la forma más fácil. Puedes hacer algo de magia con las reglas, pero es probable que sea mucho más complicado. Recomendaría el enfoque de envoltura en función sobre eso cualquier día.

Esto funciona para una sola fila, o pocas filas, valores. Si está tratando con grandes cantidades de filas, por ejemplo, de una subconsulta, es mejor dividirla en dos consultas, una para INSERTAR y otra para ACTUALIZAR (como una unión / subselección apropiada, por supuesto, no es necesario escribir su principal filtrar dos veces)

Magnus Hagander
fuente
44
"Si se trata de grandes cantidades de filas", ese es exactamente mi caso. Quiero actualizar / insertar filas de forma masiva y con mysql puedo hacer esto con solo UNA consulta sin ningún bucle. Ahora me pregunto si esto también es posible con postgresql: usar solo una consulta para actualizar O insertar de forma masiva. Usted dice: "es mejor dividirlo en dos consultas, una para INSERTAR y otra para ACTUALIZAR", pero ¿cómo puedo hacer una inserción que no arroje errores en claves duplicadas? (es decir, "INSERT IGNORE")
gpilotino
44
Magnus quería decir que usaba una consulta como esta: "iniciar transacción; crear tabla temporal temporaria_tabla como select * de la prueba donde es falsa; copiar temporaria_tabla desde 'data_file.csv'; bloquear tabla de prueba; actualizar conjunto de pruebas datos = temporaria_table.data desde temporaria_tabla donde test.id = temporary_table.id; inserte en test select * from temporary_table donde id no está en (select id from test) como un "
Tometzky
25
Actualización: con PostgreSQL 9.5 esto ahora es tan simple como INSERT ... ON CONFLICT DO NOTHING;. Consulte también la respuesta stackoverflow.com/a/34639631/2091700 .
Alphaaa
Importante, el estándar SQL noMERGE es un complemento seguro de concurrencia, a menos que tome una primera. La gente lo usa de esa manera, pero está mal. LOCK TABLE
Craig Ringer
1
Con v9.5 ahora es una característica 'nativa', así que por favor revise el comentario de @Alphaaa (solo anunciando el comentario que anuncia la respuesta)
Camilo Delvasto
178

Con PostgreSQL 9.5, esta es ahora una funcionalidad nativa (como MySQL ha tenido durante varios años):

INSERTAR ... EN CONFLICTO NO HAGA NADA / ACTUALIZAR ("UPSERT")

9.5 brinda soporte para operaciones "UPSERT". INSERT se extiende para aceptar una cláusula ON CONFLICT DO UPDATE / IGNORE. Esta cláusula especifica una acción alternativa a tomar en caso de una posible violación duplicada.

...

Otro ejemplo de nueva sintaxis:

INSERT INTO user_logins (username, logins)
VALUES ('Naomi',1),('James',1) 
ON CONFLICT (username)
DO UPDATE SET logins = user_logins.logins + EXCLUDED.logins;
madriguera
fuente
100

Editar: en caso de que se haya perdido la respuesta de Warren, PG9.5 ahora tiene esto de forma nativa; hora de actualizar!


Sobre la base de la respuesta de Bill Karwin, para explicar cómo se vería un enfoque basado en reglas (transferencia desde otro esquema en el mismo DB y con una clave principal de varias columnas):

CREATE RULE "my_table_on_duplicate_ignore" AS ON INSERT TO "my_table"
  WHERE EXISTS(SELECT 1 FROM my_table 
                WHERE (pk_col_1, pk_col_2)=(NEW.pk_col_1, NEW.pk_col_2))
  DO INSTEAD NOTHING;
INSERT INTO my_table SELECT * FROM another_schema.my_table WHERE some_cond;
DROP RULE "my_table_on_duplicate_ignore" ON "my_table";

Nota: La regla se aplica a todas las INSERToperaciones hasta que se descarta, por lo que no es muy ad hoc.

EoghanM
fuente
@sema quieres decir si another_schema.my_tablecontiene duplicados de acuerdo con las restricciones de my_table?
EoghanM
2
@EoghanM Probé la regla en postgresql 9.3 y aún podía insertar duplicados con varias instrucciones de inserción de fila como, por ejemplo, INSERT INTO "my_table" (a, b), (a, b); (Suponiendo que esa fila (a, b) todavía no existía en "my_table".)
sema
@sema, gotcha: eso debe significar que la regla se ejecuta al principio sobre todos los datos que se insertarán y no se volverá a ejecutar después de insertar cada fila. Un enfoque sería insertar sus datos en otra tabla temporal primero que no tenga restricciones, y luego hacerloINSERT INTO "my_table" SELECT DISTINCT ON (pk_col_1, pk_col_2) * FROM the_tmp_table;
EoghanM
@EoghanM Otro enfoque es relajar temporalmente las restricciones duplicadas y aceptar duplicados en la inserción, pero eliminar los duplicados después conDELETE FROM my_table WHERE ctid IN (SELECT ctid FROM (SELECT ctid,ROW_NUMBER() OVER (PARTITION BY pk_col_1,pk_col_2) AS rn FROM my_table) AS dups WHERE dups.rn > 1);
sema
Tengo el problema descrito por @sema. Si hago una inserción (a, b), (a, b), arroja un error. ¿Hay alguna forma de suprimir los errores, también en este caso?
Diogo Melo
35

Para aquellos de ustedes que tienen Postgres 9.5 o superior, la nueva sintaxis ON CONFLICT DOH NOTHING debería funcionar:

INSERT INTO target_table (field_one, field_two, field_three ) 
SELECT field_one, field_two, field_three
FROM source_table
ON CONFLICT (field_one) DO NOTHING;

Para aquellos de nosotros que tenemos una versión anterior, esta combinación correcta funcionará en su lugar:

INSERT INTO target_table (field_one, field_two, field_three )
SELECT source_table.field_one, source_table.field_two, source_table.field_three
FROM source_table 
LEFT JOIN target_table ON source_table.field_one = target_table.field_one
WHERE target_table.field_one IS NULL;
hanmari
fuente
El segundo enfoque no funciona cuando se hace una gran inserción en un entorno concurrente. Obtiene un Unique violation: 7 ERROR: duplicate key value violates unique constraintcuando se target_tableinsertó otra fila mientras se ejecutaba esta consulta, si sus claves, de hecho, se duplican entre sí. Creo que el bloqueo target_tableayudará, pero la concurrencia obviamente sufrirá.
G. Kashtanov
1
ON CONFLICT (field_one) DO NOTHINGEs la mejor parte de la respuesta.
Abel Callejo
24

Para obtener la lógica de ignorar inserción , puede hacer algo como a continuación. Descubrí que simplemente insertar desde una instrucción select de valores literales funcionó mejor, luego puede enmascarar las claves duplicadas con una cláusula NOT EXISTS. Para obtener la actualización en lógica duplicada, sospecho que sería necesario un bucle pl / pgsql.

INSERT INTO manager.vin_manufacturer
(SELECT * FROM( VALUES
  ('935',' Citroën Brazil','Citroën'),
  ('ABC', 'Toyota', 'Toyota'),
  ('ZOM',' OM','OM')
  ) as tmp (vin_manufacturer_id, manufacturer_desc, make_desc)
  WHERE NOT EXISTS (
    --ignore anything that has already been inserted
    SELECT 1 FROM manager.vin_manufacturer m where m.vin_manufacturer_id = tmp.vin_manufacturer_id)
)
Keyo
fuente
¿Qué pasa si tmp contiene una fila duplicada, que puede suceder?
Henley Chiu
Siempre puede seleccionar con la palabra clave distinta.
Keyo
55
Al igual que para su información, el truco "DONDE NO EXISTE" no funciona en varias transacciones porque las diferentes transacciones no pueden ver los datos recién agregados de las otras transacciones.
Dave Johansen
21
INSERT INTO mytable(col1,col2) 
    SELECT 'val1','val2' 
    WHERE NOT EXISTS (SELECT 1 FROM mytable WHERE col1='val1')
usuario2342158
fuente
¿Cuál es el impacto de las transacciones múltiples que intentan hacer lo mismo? ¿Es posible que entre el lugar donde no existe la ejecución y la inserción que se ejecuta alguna otra transacción inserte una fila? Y si Postgres puede evitar eso, ¿no está postgres introduciendo un punto de sincronización en todas esas transacciones cuando llegan a esto?
Καrτhικ
Esto no funciona con transacciones múltiples, porque los datos recién agregados no son visibles para las otras transacciones.
Dave Johansen
12

Parece que PostgreSQL admite un objeto de esquema llamado regla .

http://www.postgresql.org/docs/current/static/rules-update.html

Puede crear una regla ON INSERTpara una tabla determinada, haciéndolo NOTHINGsi existe una fila con el valor de clave principal dado, o haciendo que haga una en UPDATElugar deINSERT si existe una fila con el valor de clave principal dado.

No lo he intentado yo mismo, así que no puedo hablar por experiencia ni ofrecer un ejemplo.

Bill Karwin
fuente
1
Si entendí bien, estas reglas son disparadores que se ejecutan cada vez que se llama a una instrucción. ¿Qué pasa si quiero aplicar la regla para una sola consulta? tengo que crear la regla y luego destruirla inmediatamente? (¿qué pasa con las condiciones de carrera?)
gpilotino
3
Sí, también tendría las mismas preguntas. El mecanismo de regla es lo más parecido que pude encontrar en PostgreSQL a INSERT IGNORE de MySQL o ON DUPLICATE KEY UPDATE. Si buscamos en Google "postgresql en la actualización de claves duplicadas", encontrará otras personas que recomiendan el mecanismo de la Regla, a pesar de que una Regla se aplicaría a cualquier INSERT, no solo de forma ad hoc.
Bill Karwin
44
PostgreSQL admite DDL transaccional, lo que significa que si crea una regla y la coloca dentro de una sola transacción, la regla nunca habrá sido visible fuera de (y por lo tanto nunca habrá tenido ningún efecto fuera de) esa transacción.
cdhowie
6

Como @hanmari mencionó en su comentario. cuando se inserta en tablas de postgres, el conflicto on (..) no hacer nada es el mejor código para no insertar datos duplicados .:

query = "INSERT INTO db_table_name(column_name)
         VALUES(%s) ON CONFLICT (column_name) DO NOTHING;"

La línea de código ON CONFLICT permitirá que la instrucción de inserción aún inserte filas de datos. El código de consulta y valores es un ejemplo de fecha insertada desde un Excel en una tabla db de postgres. Tengo restricciones agregadas a una tabla de postgres que uso para asegurarme de que el campo ID sea único. En lugar de ejecutar una eliminación en filas de datos que son iguales, agrego una línea de código sql que renumera la columna ID a partir de 1. Ejemplo:

q = 'ALTER id_column serial RESTART WITH 1'

Si mis datos tienen un campo de ID, no lo uso como ID principal / ID en serie, creo una columna de ID y la configuro en serie. Espero que esta información sea útil para todos. * No tengo título universitario en desarrollo / codificación de software. Todo lo que sé en codificación, lo estudio por mi cuenta.

Yankeeownz
fuente
¡Esto no funciona en índices compuestos únicos!
Nulik
4

Esta solución evita el uso de reglas:

BEGIN
   INSERT INTO tableA (unique_column,c2,c3) VALUES (1,2,3);
EXCEPTION 
   WHEN unique_violation THEN
     UPDATE tableA SET c2 = 2, c3 = 3 WHERE unique_column = 1;
END;

pero tiene un inconveniente de rendimiento (ver PostgreSQL.org ):

Un bloque que contiene una cláusula EXCEPTION es significativamente más costoso para entrar y salir que un bloque sin uno. Por lo tanto, no use EXCEPCIÓN sin necesidad.

Número cuatro
fuente
1

En forma masiva, siempre puede eliminar la fila antes de la inserción. La eliminación de una fila que no existe no causa un error, por lo que se omite de forma segura.

David Noriega
fuente
2
Este enfoque será bastante propenso a condiciones de carrera extrañas, no lo recomendaría ...
Steven Schlansker
1
+1 Esto es fácil y genérico. Si se usa con cuidado, esta puede ser una solución simple.
Wouter van Nifterick el
1
Tampoco funcionará cuando los datos existentes se hayan modificado después de la inserción (pero no en la clave duplicada) y queramos mantener las actualizaciones. Este es el escenario cuando hay scripts SQL que se escriben para varios sistemas ligeramente diferentes, como las actualizaciones de db que se ejecutan en sistemas de producción, control de calidad, desarrollo y prueba.
Hanno Fietz
1
La clave externa puede ser un problema si los crea con DEFERRABLE INITIALLY DEFERREDbanderas.
temoto
-1

Para los scripts de importación de datos, para reemplazar "SI NO EXISTE", en cierto modo, hay una formulación un poco incómoda que, sin embargo, funciona:

DO
$do$
BEGIN
PERFORM id
FROM whatever_table;

IF NOT FOUND THEN
-- INSERT stuff
END IF;
END
$do$;
analytik_work
fuente