Pregunta

Hace

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.

¿Fue útil?

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:


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.

la documentación .

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ón UPDATE normales

Tiene algunas características interesantes también:

  • Puede tener una cláusula en su WHERE UPDATE (lo que le permite girar efectivamente ON CONFLICT UPDATE en ON 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 estaremos 3 porque eso es el valor actual de la tabla. Puede utilizar cualquiera o ambas expresiones en la cláusula SET y WHERE.

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:

  1. Copiar / nuevos datos actualizados en tabla temporal (seguro, o se puede hacer INSERT si el costo es aceptable)
  2. Adquirir Lock [opcional] (es preferible a bloqueos de tabla de asesoramiento, OMI)
  3. 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');
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top