El error que obtienes:
El comando ON CONFLICT DO UPDATE no puede afectar la fila por segunda vez
indica que está intentando cambiar la misma fila más de una vez en un solo comando. En otras palabras:tiene duplicados en (name, url, email)
en tus VALUES
lista. Doble los duplicados (si esa es una opción) y debería funcionar. Pero tendrá que decidir qué fila elegir de cada conjunto de duplicados.
INSERT INTO feeds_person (created, modified, name, url, email)
SELECT DISTINCT ON (name, url, email) *
FROM (
VALUES
('blah', 'blah', 'blah', 'blah', 'blah')
-- ... more
) v(created, modified, name, url, email) -- match column list
ON CONFLICT (name, url, email) DO UPDATE
SET url = feeds_person.url
RETURNING id;
Dado que usamos un VALUES
independiente expresión ahora, debe agregar conversiones de tipo explícitas para tipos no predeterminados. Me gusta:
VALUES
(timestamptz '2016-03-12 02:47:56+01'
, timestamptz '2016-03-12 02:47:56+01'
, 'n3', 'u3', 'e3')
...
Tu timestamptz
las columnas necesitan una conversión de tipo explícita, mientras que los tipos de cadena pueden operar con text
predeterminado . (Aún podría enviar a varchar(n)
inmediatamente).
Hay formas de determinar qué fila elegir de cada conjunto de duplicados:
- ¿Seleccionar la primera fila en cada grupo GROUP BY?
Tiene razón, no hay (actualmente) ninguna forma de ser excluido filas en RETURNING
cláusula. Cito la wiki de Postgres:
Tenga en cuenta que RETURNING
no hace visible el "EXCLUDED.*
" alias de UPDATE
(solo el genérico "TARGET.*
" alias es visible allí). Se cree que hacerlo crea una ambigüedad molesta para los casos simples y comunes [30] con poco o ningún beneficio. En algún momento en el futuro, podemos buscar una forma de exponer si RETURNING
-las tuplas proyectadas se insertaron y actualizaron, pero esto probablemente no necesite convertirse en la primera iteración confirmada de la característica [31].
Sin embargo , no debería estar actualizando filas que no deberían actualizarse. Las actualizaciones vacías son casi tan caras como las regulares y pueden tener efectos secundarios no deseados. Para empezar, no necesita estrictamente UPSERT, su caso se parece más a "SELECCIONAR o INSERTAR". Relacionado:
- ¿Es SELECCIONAR o INSERTAR en una función propensa a condiciones de carrera?
Uno una forma más limpia de insertar un conjunto de filas sería con CTE de modificación de datos:
WITH val AS (
SELECT DISTINCT ON (name, url, email) *
FROM (
VALUES
(timestamptz '2016-1-1 0:0+1', timestamptz '2016-1-1 0:0+1', 'n', 'u', 'e')
, ('2016-03-12 02:47:56+01', '2016-03-12 02:47:56+01', 'n1', 'u3', 'e3')
-- more (type cast only needed in 1st row)
) v(created, modified, name, url, email)
)
, ins AS (
INSERT INTO feeds_person (created, modified, name, url, email)
SELECT created, modified, name, url, email FROM val
ON CONFLICT (name, url, email) DO NOTHING
RETURNING id, name, url, email
)
SELECT 'inserted' AS how, id FROM ins -- inserted
UNION ALL
SELECT 'selected' AS how, f.id -- not inserted
FROM val v
JOIN feeds_person f USING (name, url, email);
La complejidad añadida debería pagar por tablas grandes donde INSERT
es la regla y SELECT
la excepción.
Originalmente, había agregado un NOT EXISTS
predicado en el último SELECT
para evitar duplicados en el resultado. Pero eso era redundante. Todas las CTE de una sola consulta ven las mismas instantáneas de las tablas. El conjunto devolvió con ON CONFLICT (name, url, email) DO NOTHING
es mutuamente excluyente con el conjunto devuelto después de INNER JOIN
en las mismas columnas.
Desafortunadamente, esto también abre una pequeña ventana para una condición de carrera . Si...
- una transacción concurrente inserta filas en conflicto
- no se ha comprometido todavía
- pero finalmente se compromete
... algunas filas pueden perderse.
Podría simplemente INSERT .. ON CONFLICT DO NOTHING
, seguido de un SELECT
separado consulta para todas las filas, dentro de la misma transacción para superar esto. Lo que a su vez abre otra pequeña ventana para una condición de carrera si las transacciones simultáneas pueden realizar escrituras en la tabla entre INSERT
y SELECT
(por defecto READ COMMITTED
nivel de aislamiento). Se puede evitar con REPEATABLE READ
aislamiento de transacciones (o más estricto). O con un bloqueo de escritura (posiblemente costoso o incluso inaceptable) en toda la tabla. Puede obtener cualquier comportamiento que necesite, pero puede haber un precio que pagar.
Relacionado:
- ¿Cómo usar RETURNING con ON CONFLICT en PostgreSQL?
- Retornar filas desde INSERT con ON CONFLICT sin necesidad de actualizar