Tengo dos columnas en la tabla col1
, col2
ambas están indexadas de forma única (col1 es única y también lo es col2).
Necesito insertar en esta tabla, usar la ON CONFLICT
sintaxis y actualizar otras columnas, pero no puedo usar ambas columnas en la conflict_target
cláusula.
Funciona:
INSERT INTO table
...
ON CONFLICT ( col1 )
DO UPDATE
SET
-- update needed columns here
Pero cómo hacer esto para varias columnas, algo como esto:
...
ON CONFLICT ( col1, col2 )
DO UPDATE
SET
....
postgresql
upsert
postgresql-9.5
Oto Shavadze
fuente
fuente
Respuestas:
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
Que dice la documentación
Esto da la impresión de que la siguiente consulta debería funcionar, pero no es así porque en realidad requeriría un índice único conjunto en col1 y col2. Sin embargo, tal índice no garantizaría que col1 y col2 sean únicos individualmente, que 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. Hay varias posibilidades. Por ejemplo, en la consulta Q1 anterior, ¿debería actualizarse postgresql
col1
cuando hay un conflictocol2
? Pero, ¿y si eso lleva a otro conflictocol1
? ¿Cómo se espera que postgresql maneje eso?Una solución
Una solución es combinar ON CONFLICT con UPSERT a la antigua .
CREATE OR REPLACE FUNCTION merge_db(key1 INT, key2 INT, data TEXT) RETURNS VOID AS $$ BEGIN LOOP -- first try to update the key UPDATE dupes SET col3 = data WHERE col1 = key1 and col2 = key2; IF found THEN RETURN; END IF; -- not there, so try to insert the key -- if someone else inserts the same key concurrently, or key2 -- already exists in col2, -- we could get a unique-key failure 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 -- Do nothing, and loop to try the UPDATE again. END; END; END LOOP; END; $$ LANGUAGE plpgsql;
Debería modificar la lógica de esta función almacenada para que actualice las columnas exactamente de la manera que desea. Invocarlo como
SELECT merge_db(3,2,'c'); SELECT merge_db(1,2,'d');
fuente
ON CONFLICT
requiere un índice único * para realizar la detección de conflictos. Entonces, solo necesita 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 puede utilizar restricciones de exclusión . Estas son restricciones un poco más generales que únicas. Suponga que su tabla tiene columnas para
id
yvalid_time
(yvalid_time
es atsrange
), y desea permitir duplicadosid
, pero no para períodos de tiempo superpuestos. Una restricción única no le ayudará, pero con una restricción de exclusión puede decir "excluir nuevos registros si sonid
iguales a los antiguosid
y también sevalid_time
superponenvalid_time
".fuente
ON CONFLICT
?on conflict
comando. El error es simplemente "la columna my_index_name no existe".En la actualidad es (parece) imposible. Ni la última versión de la
ON CONFLICT
sintaxis permite repetir la cláusula, ni con CTE es posible: no es posible romper el INSERT de ON CONFLICT para agregar más objetivos de conflicto.fuente
Si está utilizando postgres 9.5, puede utilizar el espacio EXCLUIDO.
Ejemplo tomado de Novedades de 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;
fuente
O Y
fuente
Vlad tuvo la idea correcta.
Primero debe crear una restricción única de tabla en las columnas
col1, col2
Luego, una vez que lo haga, puede hacer lo siguiente:INSERT INTO dupes values(3,2,'c') ON CONFLICT ON CONSTRAINT dupes_pkey DO UPDATE SET col3 = 'c', col2 = 2
fuente
Algo hacky, pero resolví esto concatenando los dos valores de col1 y col2 en una nueva columna, col3 (algo así como un índice de los dos) y comparándolo con eso. Esto solo funciona si necesita que coincida con AMBOS col1 y col2.
INSERT INTO table ... ON CONFLICT ( col3 ) DO UPDATE SET -- update needed columns here
Donde col3 = la concatenación de los valores de col1 y col2.
fuente
on conflict
.Por lo general, creo que puede generar una declaración con solo una
on conflict
que especifica la única restricción que es relevante para lo que está insertando.Porque normalmente, solo una restricción es la "relevante", a la vez. (Si hay muchos, entonces me pregunto si algo tiene un diseño extraño / extraño, hmm).
Ejemplo:
(Licencia: No CC0, solo CC-By)
// there're these unique constraints: // unique (site_id, people_id, page_id) // unique (site_id, people_id, pages_in_whole_site) // unique (site_id, people_id, pages_in_category_id) // and only *one* of page-id, category-id, whole-site-true/false // can be specified. So only one constraint is "active", at a time. val thingColumnName = thingColumnName(notfificationPreference) val insertStatement = s""" insert into page_notf_prefs ( site_id, people_id, notf_level, page_id, pages_in_whole_site, pages_in_category_id) values (?, ?, ?, ?, ?, ?) -- There can be only one on-conflict clause. on conflict (site_id, people_id, $thingColumnName) <—— look do update set notf_level = excluded.notf_level """ val values = List( siteId.asAnyRef, notfPref.peopleId.asAnyRef, notfPref.notfLevel.toInt.asAnyRef, // Only one of these is non-null: notfPref.pageId.orNullVarchar, if (notfPref.wholeSite) true.asAnyRef else NullBoolean, notfPref.pagesInCategoryId.orNullInt) runUpdateSingleRow(insertStatement, values)
Y:
La
on conflict
cláusula se genera dinámicamente, dependiendo de lo que estoy tratando de hacer. Si estoy insertando una preferencia de notificación, para una página, entonces puede haber un conflicto único en lasite_id, people_id, page_id
restricción. Y si estoy configurando preferencias de notificación, para una categoría, entonces sé que la restricción que puede violarse essite_id, people_id, category_id
.Entonces, puedo, y es muy probable que usted también, en su caso, generar la correcta
on conflict (... columns )
, porque sé lo que quiero hacer, y luego sé cuál de las muchas restricciones únicas es la que puede ser violada.fuente
ON CONFLICT es una solución muy torpe, ejecuta
UPDATE dupes SET key1=$1, key2=$2 where key3=$3 if rowcount > 0 INSERT dupes (key1, key2, key3) values ($1,$2,$3);
funciona en Oracle, Postgres y todas las demás bases de datos
fuente