La UPDATE
sintaxis requiere para nombrar explícitamente las columnas de destino. Posibles razones para evitarlo:
- Tiene muchas columnas y solo desea acortar la sintaxis.
- No sabes nombres de columna excepto las columnas únicas.
"All columns"
tiene que significar "todas las columnas de la tabla de destino" (o al menos "columnas principales de la tabla" ) en orden coincidente y tipo de datos coincidente. De lo contrario, tendría que proporcionar una lista de nombres de columnas de destino de todos modos.
Tabla de prueba:
CREATE TABLE tbl (
id int PRIMARY KEY
, text text
, extra text
);
INSERT INTO tbl AS t
VALUES (1, 'foo')
, (2, 'bar');
1. DELETE
&INSERT
en una sola consulta en su lugar
Sin conocer ningún nombre de columna excepto id
.
Solo funciona para "todas las columnas de la tabla de destino" . Si bien la sintaxis incluso funciona para un subconjunto principal, las columnas sobrantes en la tabla de destino se restablecerían a NULL con DELETE
y INSERT
.
UPSERT (INSERT ... ON CONFLICT ...
) es necesario para evitar problemas de simultaneidad/bloqueo bajo carga de escritura simultánea, y solo porque no existe una forma general de bloquear filas que aún no existen en Postgres (bloqueo de valor ).
Su requisito especial solo afecta a la UPDATE
parte. Las posibles complicaciones no se aplican donde existen las filas se ven afectadas. Esos están bloqueados correctamente. Simplificando un poco más, puede reducir su caso a DELETE
y INSERT
:
WITH data(id) AS ( -- Only 1st column gets explicit name!
VALUES
(1, 'foo_upd', 'a') -- changed
, (2, 'bar', 'b') -- unchanged
, (3, 'baz', 'c') -- new
)
, del AS (
DELETE FROM tbl AS t
USING data d
WHERE t.id = d.id
-- AND t <> d -- optional, to avoid empty updates
) -- only works for complete rows
INSERT INTO tbl AS t
TABLE data -- short for: SELECT * FROM data
ON CONFLICT (id) DO NOTHING
RETURNING t.id;
En el modelo MVCC de Postgres, un UPDATE
es básicamente lo mismo que DELETE
y INSERT
de todos modos (excepto en algunos casos de esquina con concurrencia, actualizaciones HOT y valores de columnas grandes almacenados fuera de línea). Como desea reemplazar todas las filas de todos modos, simplemente elimine las filas en conflicto antes de INSERT
. Las filas eliminadas permanecen bloqueadas hasta que se confirma la transacción. El INSERT
solo puede encontrar filas en conflicto para valores clave que no existían anteriormente si una transacción simultánea los inserta simultáneamente (después de DELETE
, pero antes de INSERT
).
En este caso especial, perdería valores de columna adicionales para las filas afectadas. No se planteó ninguna excepción. Pero si las consultas de la competencia tienen la misma prioridad, eso no es un problema:la otra consulta ganó por algunas filas Además, si la otra consulta es un UPSERT similar, su alternativa es esperar a que esta transacción se confirme y luego se actualice de inmediato. "Ganar" podría ser una victoria pírrica.
Acerca de las "actualizaciones vacías":
- ¿Cómo puedo (o puedo) SELECCIONAR DISTINTO en varias columnas?
¡No, mi consulta debe ganar!
Vale, tú lo pediste:
WITH data(id) AS ( -- Only 1st column gets explicit name!
VALUES -- rest gets default names "column2", etc.
(1, 'foo_upd', NULL) -- changed
, (2, 'bar', NULL) -- unchanged
, (3, 'baz', NULL) -- new
, (4, 'baz', NULL) -- new
)
, ups AS (
INSERT INTO tbl AS t
TABLE data -- short for: SELECT * FROM data
ON CONFLICT (id) DO UPDATE
SET id = t.id
WHERE false -- never executed, but locks the row!
RETURNING t.id
)
, del AS (
DELETE FROM tbl AS t
USING data d
LEFT JOIN ups u USING (id)
WHERE u.id IS NULL -- not inserted !
AND t.id = d.id
-- AND t <> d -- avoid empty updates - only for full rows
RETURNING t.id
)
, ins AS (
INSERT INTO tbl AS t
SELECT *
FROM data
JOIN del USING (id) -- conflict impossible!
RETURNING id
)
SELECT ARRAY(TABLE ups) AS inserted -- with UPSERT
, ARRAY(TABLE ins) AS updated -- with DELETE & INSERT;
¿Cómo?
- Los
data
del 1er CTE solo proporciona datos. Podría ser una mesa en su lugar. - El 2º CTE
ups
:UPSERT. Filas conid
en conflicto no se modifican, pero también bloquean . - El 3er CTE
del
elimina las filas en conflicto. Permanecen bloqueados. - El 4º CTE
ins
inserta filas completas . Solo permitido para la misma transacción - El SELECT final es solo para que la demostración muestre lo que sucedió.
Para verificar si hay actualizaciones vacías, haga una prueba (antes y después) con:
SELECT ctid, * FROM tbl; -- did the ctid change?
La comprobación (comentada) de cualquier cambio en la fila AND t <> d
funciona incluso con valores NULL porque estamos comparando dos valores de fila escritos de acuerdo con el manual:
dos valores de campo NULL se consideran iguales y un NULL se considera mayor que uno que no es NULL
2. SQL dinámico
Esto también funciona para un subconjunto de columnas iniciales, conservando los valores existentes.
El truco es dejar que Postgres construya la cadena de consulta con los nombres de las columnas de los catálogos del sistema de forma dinámica y luego ejecutarla.
Ver respuestas relacionadas para el código:
-
Actualice varias columnas en una función de activación en plpgsql
-
Actualización masiva de todas las columnas
-
SQL actualiza campos de una tabla a partir de campos de otra