167 votos

Utilice múltiples conflict_target en la cláusula ON CONFLICT

Tengo dos columnas únicas por separado en una tabla: col1, col2. Ambas tienen un índice único (col1 es único y también lo es col2).

Necesito la sintaxis INSERT ... ON CONFLICT ... DO UPDATE, y actualizar otras columnas en caso de conflicto, pero no puedo usar ambas columnas como conflict_target.

Funciona así:

INSERT INTO table
...
ON CONFLICT ( col1 ) 
DO UPDATE 
SET 
-- actualizar las columnas necesarias aquí

Pero cómo hacer esto para varias columnas, algo así:

...
ON CONFLICT ( col1, col2 )
DO UPDATE 
SET 
....

Actualmente utilizando Postgres 9.5.

125voto

Paul A Jungwirth Puntos 3580

ON CONFLICT requiere un índice único* para realizar la detección de conflictos. Así que solo necesitas crear un índice único en ambas columnas:

t=# create table t (id integer, a text, b text);
CREATE TABLE
t=# create unique index idx_t_id_a on t (id, a);
CREATE INDEX
t=# insert into t values (1, 'a', 'foo');
INSERT 0 1
t=# insert into t values (1, 'a', 'bar') on conflict (id, a) do update set b = 'bar';
INSERT 0 1
t=# select * from t;
 id | a |  b  
----+---+-----
  1 | a | bar

* Además de los índices únicos, también puedes usar restricciones de exclusión. Estas son un poco más generales que las restricciones únicas. Supongamos que tu tabla tuviera columnas para id y valid_time (y valid_time es un tsrange), y quisieras permitir duplicados de id, pero no de períodos de tiempo superpuestos. Una restricción única no te ayudará, pero con una restricción de exclusión puedes decir "excluir nuevos registros si su id es igual a un id anterior y además su valid_time se superpone con su valid_time".

83voto

mrk Puntos 3

Una tabla de muestra y datos

CREATE TABLE dupes(col1 int primary key, col2 int, col3 text,
   CONSTRAINT col2_unique UNIQUE (col2)
);

INSERT INTO dupes values(1,1,'a'),(2,2,'b');

Reproduciendo el problema

INSERT INTO dupes values(3,2,'c')
ON CONFLICT (col1) DO UPDATE SET col3 = 'c', col2 = 2

Llamemos a esto Q1. El resultado es

ERROR:  llave duplicada viola la restricción de unicidad "col2_unique"
DETALLE:  La llave (col2)=(2) ya existe.

Lo que dice la documentación

conflict_target puede inferir un índice único. Al realizar la inferencia, consiste en una o más columnas indexadas y/o expresiones de índice y un predicado de índice opcional. Todos los índices únicos de table_name que, sin tener en cuenta el orden, contienen exactamente las columnas/expresiones conflict_target especificadas se infieren (se eligen) como índices árbitros. Si se especifica un predicado de índice, debe, como un requisito adicional para la inferencia, satisfacer los índices árbitros.

Esto da la impresión de que la siguiente consulta debería funcionar, pero no lo hace porque realmente requeriría un índice único conjunto en col1 y col2. Sin embargo, dicho índice no garantizaría que col1 y col2 fueran únicos individualmente, lo cual es uno de los requisitos del OP.

INSERT INTO dupes values(3,2,'c') 
ON CONFLICT (col1,col2) DO UPDATE SET col3 = 'c', col2 = 2

Llamemos a esta consulta Q2 (esto falla con un error de sintaxis)

¿Por qué?

Postgresql se comporta de esta manera porque lo que debería suceder cuando ocurre un conflicto en la segunda columna no está bien definido. Existen varias posibilidades. Por ejemplo, en la consulta Q1 anterior, ¿debería postgresql actualizar col1 cuando hay un conflicto en col2? Pero, ¿qué pasa si eso conduce a otro conflicto en col1? ¿Cómo se espera que postgresql maneje eso?

Una solución

Una solución es combinar ON CONFLICT con UPSERT de la vieja escuela.

CREATE OR REPLACE FUNCTION merge_db(key1 INT, key2 INT, data TEXT) RETURNS VOID AS
$$
BEGIN
    LOOP
        -- primero intentar actualizar la llave
        UPDATE dupes SET col3 = data WHERE col1 = key1 and col2 = key2;
        IF found THEN
            RETURN;
        END IF;

        -- si no está allí, intentar insertar la llave
        -- si alguien más inserta la misma llave concurrentemente o key2
        -- ya existe en col2,
        -- podríamos obtener un fallo de llave única
        BEGIN
            INSERT INTO dupes VALUES (key1, key2, data) ON CONFLICT (col1) DO UPDATE SET col3 = data;
            RETURN;
        EXCEPTION WHEN unique_violation THEN
            BEGIN
                INSERT INTO dupes VALUES (key1, key2, data) ON CONFLICT (col2) DO UPDATE SET col3 = data;
                RETURN;
            EXCEPTION WHEN unique_violation THEN
                -- No hacer nada y volver a intentar la actualización.
            END;
        END;
    END LOOP;
END;
$$
LANGUAGE plpgsql;

Necesitarías modificar la lógica de esta función almacenada para que actualice las columnas exactamente de la manera que desees. Llámala de la siguiente manera

SELECT merge_db(3,2,'c');
SELECT merge_db(1,2,'d');

10voto

Peter Krauss Puntos 1888

En la actualidad parece imposible. Ni siquiera la última versión de la etiqueta ON CONFLICT syntax permite repetir la cláusula, ni con CTE es posible: no es posible romper el INSERT de ON CONFLICT para agregar más conflictos objetivos.

5voto

user774082 Puntos 51

Si estás utilizando postgres 9.5 o posterior, puedes usar el espacio EXCLUDED.

Ejemplo tomado de Novedades en PostgreSQL 9.5:

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

2voto

Anatolii Stepaniuk Puntos 2108
EN CONFLICTO ( col1, col2 )
HACER ACTUALIZACIÓN 
ESTABLECER 

funciona bien. pero no debes actualizar col1, col2 en la sección SET.

Iteramos.com

Iteramos es una comunidad de desarrolladores que busca expandir el conocimiento de la programación mas allá del inglés.
Tenemos una gran cantidad de contenido, y también puedes hacer tus propias preguntas o resolver las de los demás.

Powered by:

X