Das UPDATE
Syntax erfordert Zielspalten explizit zu benennen. Mögliche Gründe, dies zu vermeiden:
- Sie haben viele Spalten und möchten nur die Syntax kürzen.
- Du weißt es nicht Spaltennamen mit Ausnahme der eindeutigen Spalte(n).
"All columns"
muss "alle Spalten der Zieltabelle" bedeuten (oder zumindest "führende Spalten der Tabelle" ) in übereinstimmender Reihenfolge und übereinstimmendem Datentyp. Andernfalls müssten Sie sowieso eine Liste mit Zielspaltennamen bereitstellen.
Testtabelle:
CREATE TABLE tbl (
id int PRIMARY KEY
, text text
, extra text
);
INSERT INTO tbl AS t
VALUES (1, 'foo')
, (2, 'bar');
1. DELETE
&INSERT
stattdessen in Einzelabfrage
Ohne irgendwelche Spaltennamen außer id
zu kennen .
Funktioniert nur für "alle Spalten der Zieltabelle" . Während die Syntax sogar für eine führende Teilmenge funktioniert, würden überzählige Spalten in der Zieltabelle mit DELETE
auf NULL zurückgesetzt und INSERT
.
UPSERT (INSERT ... ON CONFLICT ...
) wird benötigt, um Parallelitäts-/Sperrprobleme bei gleichzeitiger Schreiblast zu vermeiden, und nur, weil es keine allgemeine Möglichkeit gibt, noch nicht vorhandene Zeilen in Postgres zu sperren (Wertsperre ).
Ihre spezielle Anforderung betrifft nur das UPDATE
Teil. Mögliche Komplikationen gelten nicht, wenn vorhanden Reihen sind betroffen. Diese sind ordnungsgemäß verschlossen. Um es noch weiter zu vereinfachen, können Sie Ihren Fall auf DELETE
reduzieren und 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;
Im Postgres-MVCC-Modell ein UPDATE
ist weitgehend dasselbe wie DELETE
und INSERT
sowieso (mit Ausnahme einiger Eckfälle mit Parallelität, HOT-Updates und großen Spaltenwerten, die außerhalb der Reihe gespeichert werden). Da Sie sowieso alle Zeilen ersetzen möchten, entfernen Sie einfach widersprüchliche Zeilen vor dem INSERT
. Gelöschte Zeilen bleiben gesperrt, bis die Transaktion festgeschrieben wird. Das INSERT
möglicherweise nur widersprüchliche Zeilen für zuvor nicht vorhandene Schlüsselwerte finden, wenn eine gleichzeitige Transaktion sie gleichzeitig einfügt (nach dem DELETE
, aber vor dem INSERT
).
In diesem speziellen Fall würden Sie zusätzliche Spaltenwerte für betroffene Zeilen verlieren. Keine Ausnahme ausgelöst. Aber wenn konkurrierende Anfragen die gleiche Priorität haben, ist das kaum ein Problem:Die andere Anfrage hat für einige gewonnen Reihen. Wenn die andere Abfrage ein ähnlicher UPSERT ist, besteht die Alternative darin, auf die Festschreibung dieser Transaktion zu warten und sie dann sofort zu aktualisieren. "Gewinnen" könnte ein Pyrrhussieg sein.
Über "leere Updates":
- Wie wähle ich (oder kann ich) DISTINCT für mehrere Spalten aus?
Nein, meine Anfrage muss gewinnen!
OK, Sie haben danach gefragt:
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;
Wie?
- Die 1. CTE
data
liefert nur Daten. Könnte stattdessen eine Tabelle sein. - Die 2. CTE
ups
:UPSERT. Zeilen mit widersprüchlicherid
nicht verändert, sondern auch gesperrt . - Der 3. CTE
del
löscht widersprüchliche Zeilen. Sie bleiben gesperrt. - Der 4. CTE
ins
fügt ganze Zeilen ein . Nur für dieselbe Transaktion zulässig - Das letzte SELECT ist nur für die Demo, um zu zeigen, was passiert ist.
Um nach leeren Updates zu suchen, testen Sie (vorher und nachher) mit:
SELECT ctid, * FROM tbl; -- did the ctid change?
Die (auskommentierte) Prüfung auf Änderungen in der Zeile AND t <> d
funktioniert auch mit NULL-Werten, da wir laut Handbuch zwei typisierte Zeilenwerte vergleichen:
zwei NULL-Feldwerte werden als gleich angesehen, und eine NULL wird als größer als eine Nicht-NULL betrachtet
2. Dynamisches SQL
Dies funktioniert auch für eine Teilmenge führender Spalten, wobei vorhandene Werte erhalten bleiben.
Der Trick besteht darin, Postgres die Abfragezeichenfolge mit Spaltennamen aus den Systemkatalogen dynamisch erstellen und dann ausführen zu lassen.
Siehe verwandte Antworten für Code:
-
Aktualisieren Sie mehrere Spalten in einer Trigger-Funktion in plpgsql
-
Massenaktualisierung aller Spalten
-
SQL aktualisiert Felder einer Tabelle aus Feldern einer anderen