Insertar, en la actualización duplicado en PostgreSQL?
-
12-09-2019 - |
Pregunta
Varios meses he aprendido de una respuesta de desbordamiento de pila cómo realizar varias actualizaciones a la vez en MySQL utilizando la siguiente sintaxis:
INSERT INTO table (id, field, field2) VALUES (1, A, X), (2, B, Y), (3, C, Z)
ON DUPLICATE KEY UPDATE field=VALUES(Col1), field2=VALUES(Col2);
Ahora he cambiado a PostgreSQL y al parecer esto no es correcto. Se refiere a todas las tablas correctas, así que supongo que es una cuestión de diferentes palabras clave que se utiliza, pero no estoy seguro de que en la documentación de PostgreSQL esto está cubierto.
Para aclarar, quiero insertar varias cosas y si ya existen para actualizarlos.
Solución
PostgreSQL desde la versión 9.5 tiene UPSERT sintaxis, con < strong> cláusula ON CONFLICTO . con la siguiente sintaxis (similar a MySQL)
INSERT INTO the_table (id, column_1, column_2)
VALUES (1, 'A', 'X'), (2, 'B', 'Y'), (3, 'C', 'Z')
ON CONFLICT (id) DO UPDATE
SET column_1 = excluded.column_1,
column_2 = excluded.column_2;
Búsqueda de archivos de grupos de correo electrónico de PostgreSQL para "upsert" lleva a encontrar un ejemplo de hacer lo que posiblemente quiere hacer, en el manual de :
Ejemplo 38-2. Excepciones con ACTUALIZACIÓN / INSERT
Este ejemplo utiliza el manejo de excepciones para llevar a cabo ya sea UPDATE o INSERT, según sea apropiado:
CREATE TABLE db (a INT PRIMARY KEY, b TEXT);
CREATE FUNCTION merge_db(key INT, data TEXT) RETURNS VOID AS
$$
BEGIN
LOOP
-- first try to update the key
-- note that "a" must be unique
UPDATE db SET b = data WHERE a = key;
IF found THEN
RETURN;
END IF;
-- not there, so try to insert the key
-- if someone else inserts the same key concurrently,
-- we could get a unique-key failure
BEGIN
INSERT INTO db(a,b) VALUES (key, data);
RETURN;
EXCEPTION WHEN unique_violation THEN
-- do nothing, and loop to try the UPDATE again
END;
END LOOP;
END;
$$
LANGUAGE plpgsql;
SELECT merge_db(1, 'david');
SELECT merge_db(1, 'dennis');
Hay posiblemente un ejemplo de cómo hacer esto en grandes cantidades, utilizando las CTE en 9.1 y superiores, en el noreferrer hackers lista de correo :
WITH foos AS (SELECT (UNNEST(%foo[])).*)
updated as (UPDATE foo SET foo.a = foos.a ... RETURNING foo.id)
INSERT INTO foo SELECT foos.* FROM foos LEFT JOIN updated USING(id)
WHERE updated.id IS NULL;
de a_horse_with_no_name respuesta para un ejemplo más claro.
Otros consejos
Advertencia:. Esto no es seguro si se ejecuta desde varias sesiones a la vez (ver advertencias abajo)
Otra forma inteligente de hacer un "UPSERT" en PostgreSQL es hacer dos instrucciones UPDATE / INSERT secuenciales que están cada uno diseñado para tener éxito o no tener efecto.
UPDATE table SET field='C', field2='Z' WHERE id=3;
INSERT INTO table (id, field, field2)
SELECT 3, 'C', 'Z'
WHERE NOT EXISTS (SELECT 1 FROM table WHERE id=3);
El ACTUALIZACIÓN tendrá éxito si una fila con "id = 3" ya existe, de lo contrario, no tiene ningún efecto.
El INSERT sólo tendrá éxito si "= 3 id" no existe ya fila con.
Se pueden combinar estos dos en una sola cadena y ambos funcionar con una única instrucción SQL ejecutar desde la aplicación. Corriendo juntos en una sola transacción es muy recomendable.
Esto funciona muy bien cuando se ejecuta de manera aislada o en una tabla bloqueada, pero está sujeta a condiciones de carrera que quiere decir que todavía puede fallar con el error de clave duplicada si una fila se inserta al mismo tiempo, o podría terminar sin fila insertada cuando una fila se elimina simultáneamente. Una transacción SERIALIZABLE
en PostgreSQL 9.1 o superior manejará de forma fiable a costa de una muy alta tasa de error de serialización, lo que significa que tendrá que volver a intentar un montón. Ver qué se upsert tan complicado , que discute este caso con más detalle.
Este enfoque también es sujeta a actualizaciones perdidas de manera aislada read committed
a menos que la aplicación comprueba los recuentos de filas afectadas y verifica que sea la insert
o la update
afectada una fila .
Con PostgreSQL 9.1 esto se puede lograr usando un CTE grabable ( expresión de tabla común ) :
WITH new_values (id, field1, field2) as (
values
(1, 'A', 'X'),
(2, 'B', 'Y'),
(3, 'C', 'Z')
),
upsert as
(
update mytable m
set field1 = nv.field1,
field2 = nv.field2
FROM new_values nv
WHERE m.id = nv.id
RETURNING m.*
)
INSERT INTO mytable (id, field1, field2)
SELECT id, field1, field2
FROM new_values
WHERE NOT EXISTS (SELECT 1
FROM upsert up
WHERE up.id = new_values.id)
Vea estas entradas en el blog:
- Upserting vía que permita la escritura CTE
- ESPERAR 9.1 - ESCRIBIBLE CTE
- PORQUE ES UPSERT tan complicado?
Tenga en cuenta que esta solución hace no prevenir una violación de clave única, pero no es vulnerable a los cambios perdidos.
Vea la seguimiento por Craig Ringer en dba.stackexchange.com
En PostgreSQL 9.5 y posteriores puede utilizar INSERT ... ON CONFLICT UPDATE
.
A INSERT ... ON DUPLICATE KEY UPDATE
MySQL puede ser reformulada directamente a un ON CONFLICT UPDATE
. Ni es la sintaxis SQL estándar, los dos son extensiones de bases de datos específicas. hay buenas razones MERGE
no se utilizó para este , una nueva sintaxis no fue creado sólo por diversión. (Sintaxis de MySQL también tiene problemas que quiere decir que no se adoptó directamente).
por ejemplo. dada la configuración:
CREATE TABLE tablename (a integer primary key, b integer, c integer);
INSERT INTO tablename (a, b, c) values (1, 2, 3);
la consulta MySQL:
INSERT INTO tablename (a,b,c) VALUES (1,2,3)
ON DUPLICATE KEY UPDATE c=c+1;
se convierte en:
INSERT INTO tablename (a, b, c) values (1, 2, 10)
ON CONFLICT (a) DO UPDATE SET c = tablename.c + 1;
Las diferencias:
-
debe especificar el nombre de la columna (o el nombre de restricción única) a utilizar para el control de la unicidad. Esa es la
ON CONFLICT (columnname) DO
-
El
SET
palabra clave debe ser utilizado, como si esta fuera una declaraciónUPDATE
normales
Tiene algunas características interesantes también:
-
Puede tener una cláusula en su
WHERE
UPDATE
(lo que le permite girar efectivamenteON CONFLICT UPDATE
enON CONFLICT IGNORE
para ciertos valores) -
La propuesta para la inserción valores están disponibles como la
EXCLUDED
fila-variable, que tiene la misma estructura que la tabla de destino. Usted puede obtener los valores originales de la tabla utilizando el nombre de la tabla. Así que en este caso seráEXCLUDED.c
10
(porque eso es lo que tratamos de insertar) y"table".c
estaremos3
porque eso es el valor actual de la tabla. Puede utilizar cualquiera o ambas expresiones en la cláusulaSET
yWHERE
.
Para información sobre upsert ver Cómo upsert (Merge, INSERT ... EN ACTUALIZACIÓN duplicado) en PostgreSQL?
Yo estaba buscando lo mismo cuando llegué aquí, pero la falta de una función genérica "upsert" me botherd un poco, así que pensé que sólo podría pasar a la actualización e insertar SQL como argumentos en que la función forman el manual
que se vería así:
CREATE FUNCTION upsert (sql_update TEXT, sql_insert TEXT)
RETURNS VOID
LANGUAGE plpgsql
AS $$
BEGIN
LOOP
-- first try to update
EXECUTE sql_update;
-- check if the row is found
IF FOUND THEN
RETURN;
END IF;
-- not found so insert the row
BEGIN
EXECUTE sql_insert;
RETURN;
EXCEPTION WHEN unique_violation THEN
-- do nothing and loop
END;
END LOOP;
END;
$$;
y tal vez de hacer lo que inicialmente quería hacer, por lotes "upsert", podría utilizar Tcl para dividir los SQL_UPDATE y bucle las actualizaciones individuales, la exitosa Preformance será muy pequeño ve http://archives.postgresql.org/pgsql-performance/2006-04/msg00557.php
el costo más alto se está ejecutando la consulta de su código, en el lado de la base de datos del coste de ejecución es mucho más pequeño
No hay un comando simple de hacerlo.
El enfoque más correcto es función de uso, como el de docs .
Otra solución (aunque no tan seguro) es hacer la actualización con la devolución, compruebe qué filas eran actualizaciones, e inserte el resto de ellos
Algo a lo largo de las líneas de:
update table
set column = x.column
from (values (1,'aa'),(2,'bb'),(3,'cc')) as x (id, column)
where table.id = x.id
returning id;
ID suponiendo: 2 fue devuelto:
insert into table (id, column) values (1, 'aa'), (3, 'cc');
Por supuesto que va a rescatar a más pronto o más tarde (en el entorno concurrente), ya que no es clara condición de carrera aquí, pero por lo general va a trabajar.
Aquí hay un más largo y el artículo más completo sobre la tema.
En lo personal, he creado una "regla" adjunto a la instrucción de inserción. Supongamos que tenía una mesa "dns" que grabó éxitos dns por cliente en una base por-tiempo:
CREATE TABLE dns (
"time" timestamp without time zone NOT NULL,
customer_id integer NOT NULL,
hits integer
);
Usted quería ser capaz de volver a insertar filas con valores actualizados, o crearlos si no existen ya. Enchavetado en el customer_id y el tiempo. Algo como esto:
CREATE RULE replace_dns AS
ON INSERT TO dns
WHERE (EXISTS (SELECT 1 FROM dns WHERE ((dns."time" = new."time")
AND (dns.customer_id = new.customer_id))))
DO INSTEAD UPDATE dns
SET hits = new.hits
WHERE ((dns."time" = new."time") AND (dns.customer_id = new.customer_id));
Actualización: Esto tiene el potencial de fallar si inserciones simultáneas están sucediendo, ya que generará excepciones unique_violation. Sin embargo, la transacción no terminado va a continuar y tener éxito, y sólo tiene que repetir la transacción interrumpida.
Sin embargo, si hay un montón de inserciones que suceden todo el tiempo, se le quiere poner un bloqueo de tabla en torno a las declaraciones de inserción: PARTICIPACIÓN bloqueo de filas EXCLUSIVO evitará cualquier operación que podría insertar, eliminar o actualizar filas en la tabla de destino. Sin embargo, las actualizaciones que no actualizan la clave única son seguros, por lo que si no hay operación va a hacer esto, utilizar bloqueos de asesoramiento en su lugar.
Además, el comando COPY no utilizar las reglas, así que si vas a insertar con la copia, tendrá que utilizar disparadores en su lugar.
Me encargo "upsert" función anterior, si desea insertar y reemplazar:
`
CREATE OR REPLACE FUNCTION upsert(sql_insert text, sql_update text)
RETURNS void AS
$BODY$
BEGIN
-- first try to insert and after to update. Note : insert has pk and update not...
EXECUTE sql_insert;
RETURN;
EXCEPTION WHEN unique_violation THEN
EXECUTE sql_update;
IF FOUND THEN
RETURN;
END IF;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
ALTER FUNCTION upsert(text, text)
OWNER TO postgres;`
Y después de ejecutar, hacer algo como esto:
SELECT upsert($$INSERT INTO ...$$,$$UPDATE... $$)
Es importante poner el doble en dólares coma para evitar errores de compilador
- comprobar la velocidad ...
Al igual que en respuesta más popular, pero funciona un poco más rápido:
WITH upsert AS (UPDATE spider_count SET tally=1 WHERE date='today' RETURNING *)
INSERT INTO spider_count (spider, tally) SELECT 'Googlebot', 1 WHERE NOT EXISTS (SELECT * FROM upsert)
(fuente: http: //www.the-art-of- web.com/sql/upsert/ )
Tengo el mismo problema de la gestión de configuración de la cuenta en forma de pares de nombre y valor. Los criterios de diseño es que los diferentes clientes podrían tener diferentes configuraciones conjuntos.
Mi solución, similar al programa de trabajo conjunto es borrar y reemplazar a granel, lo que genera el registro de fusión dentro de su aplicación.
Esto es bastante prueba de balas, independiente de la plataforma y puesto que no son nunca más de unos 20 ajustes por cliente, esto es sólo 3 llamadas db carga bastante bajos -. Probablemente el método más rápido
La alternativa de actualizar filas individuales - la comprobación de excepciones a continuación, insertar - o alguna combinación de es el código horrible, lento y con frecuencia se rompe debido a que (como se mencionó anteriormente) el manejo de excepciones de SQL estándar no cambia de dB a dB - o incluso liberar para liberar .
#This is pseudo-code - within the application:
BEGIN TRANSACTION - get transaction lock
SELECT all current name value pairs where id = $id into a hash record
create a merge record from the current and update record
(set intersection where shared keys in new win, and empty values in new are deleted).
DELETE all name value pairs where id = $id
COPY/INSERT merged records
END TRANSACTION
Según el documentación de PostgreSQL de la declaración INSERT
, la manipulación el caso ON DUPLICATE KEY
no es compatible. Esa parte de la sintaxis es una extensión de MySQL propietario.
CREATE OR REPLACE FUNCTION save_user(_id integer, _name character varying)
RETURNS boolean AS
$BODY$
BEGIN
UPDATE users SET name = _name WHERE id = _id;
IF FOUND THEN
RETURN true;
END IF;
BEGIN
INSERT INTO users (id, name) VALUES (_id, _name);
EXCEPTION WHEN OTHERS THEN
UPDATE users SET name = _name WHERE id = _id;
END;
RETURN TRUE;
END;
$BODY$
LANGUAGE plpgsql VOLATILE STRICT
Yo uso esta combinación de función
CREATE OR REPLACE FUNCTION merge_tabla(key INT, data TEXT)
RETURNS void AS
$BODY$
BEGIN
IF EXISTS(SELECT a FROM tabla WHERE a = key)
THEN
UPDATE tabla SET b = data WHERE a = key;
RETURN;
ELSE
INSERT INTO tabla(a,b) VALUES (key, data);
RETURN;
END IF;
END;
$BODY$
LANGUAGE plpgsql
Para la fusión de pequeños conjuntos, usando la función anterior está muy bien. Sin embargo, si está fusionando grandes cantidades de datos, sugeriría mirar en http: //mbk.projects.postgresql .org
La mejor práctica actual que yo sepa es:
- Copiar / nuevos datos actualizados en tabla temporal (seguro, o se puede hacer INSERT si el costo es aceptable)
- Adquirir Lock [opcional] (es preferible a bloqueos de tabla de asesoramiento, OMI)
- Combinar. (La parte divertida)
ACTUALIZACIÓN devolverá el número de filas modificadas. Si utiliza JDBC (Java), a continuación, puede comprobar este valor 0 en contra y, si no hay filas se han visto afectados, INSERT fuego en su lugar. Si utiliza algún otro lenguaje de programación, tal vez el número de las filas modificadas todavía se puede obtener, la documentación de verificación.
Esto puede no ser tan elegante, pero usted tiene SQL mucho más simple que es más trivial para utilizar en el código de llamada. De otro modo, si se escribe el guión de diez líneas en PL / PSQL, probablemente debería tener una unidad de prueba de uno u otro tipo sólo para ella sola.
Editar Esto no funciona como se esperaba. A diferencia de la respuesta aceptada, esto produce violaciónes de clave única cuando dos procesos llaman repetidamente upsert_foo
al mismo tiempo.
Eureka! Me di cuenta de una manera de hacerlo en una consulta: utilizar UPDATE ... RETURNING
para probar si se vieron afectadas las filas:
CREATE TABLE foo (k INT PRIMARY KEY, v TEXT);
CREATE FUNCTION update_foo(k INT, v TEXT)
RETURNS SETOF INT AS $$
UPDATE foo SET v = $2 WHERE k = $1 RETURNING $1
$$ LANGUAGE sql;
CREATE FUNCTION upsert_foo(k INT, v TEXT)
RETURNS VOID AS $$
INSERT INTO foo
SELECT $1, $2
WHERE NOT EXISTS (SELECT update_foo($1, $2))
$$ LANGUAGE sql;
El UPDATE
tiene que ser hecho en un procedimiento separado, ya que, por desgracia, esto es un error de sintaxis:
... WHERE NOT EXISTS (UPDATE ...)
Ahora funciona como se desea:
SELECT upsert_foo(1, 'hi');
SELECT upsert_foo(1, 'bye');
SELECT upsert_foo(3, 'hi');
SELECT upsert_foo(3, 'bye');