Mit einem eigenständigen VALUES
Ausdruck PostgreSQL hat keine Ahnung, was die Datentypen sein sollten. Bei einfachen numerischen Literalen geht das System gerne von übereinstimmenden Typen aus. Aber mit anderen Eingaben (wie NULL
) müssten Sie explizit umwandeln - wie Sie bereits herausgefunden haben.
Sie können pg_catalog
abfragen (schnell, aber PostgreSQL-spezifisch) oder das information_schema
(langsames, aber Standard-SQL), um Ihre Anweisung mit geeigneten Typen herauszufinden und vorzubereiten.
Oder Sie können einen dieser einfachen "Tricks" anwenden (das Beste habe ich mir zum Schluss aufgehoben ):
0. Zeile mit LIMIT 0
auswählen , Zeilen mit UNION ALL VALUES
anhängen
UPDATE foo f
SET x = t.x
, y = t.y
FROM (
(SELECT pkid, x, y FROM foo LIMIT 0) -- parenthesis needed with LIMIT
UNION ALL
VALUES
(1, 20, NULL) -- no type casts here
, (2, 50, NULL)
) t -- column names and types are already defined
WHERE f.pkid = t.pkid;
Die erste Unterauswahl der Unterabfrage:
(SELECT x, y, pkid FROM foo LIMIT 0)
erhält Namen und Typen für die Spalten, aber LIMIT 0
verhindert, dass eine tatsächliche Zeile hinzugefügt wird. Nachfolgende Zeilen werden auf den nun wohldefinierten Zeilentyp gezwungen – und sofort geprüft, ob sie mit dem Typ übereinstimmen. Sollte eine subtile zusätzliche Verbesserung gegenüber Ihrer ursprünglichen Form sein.
Während Werte für alle bereitgestellt werden Spalten der Tabelle kann diese kurze Syntax für die erste Zeile verwendet werden:
(TABLE foo LIMIT 0)
Große Einschränkung :Postgres wandelt die Eingabeliterale der freistehenden VALUES
um Ausdruck sofort in einen "Best-Effort"-Typ umwandeln. Wenn es später versucht, in die angegebenen Typen des ersten SELECT
umzuwandeln , kann es bei manchen Typen bereits zu spät sein, wenn zwischen dem angenommenen Typ und dem Zieltyp keine eingetragene Zuordnung vorliegt. Beispiele:text
-> timestamp
oder text
-> json
.
Profi:
- Minimaler Overhead.
- Lesbar, einfach und schnell.
- Sie müssen nur die relevanten Spaltennamen der Tabelle kennen.
Contra:
- Die Typauflösung kann bei einigen Typen fehlschlagen.
1. Zeile mit LIMIT 0
auswählen , Zeilen mit UNION ALL SELECT
anhängen
UPDATE foo f
SET x = t.x
, y = t.y
FROM (
(SELECT pkid, x, y FROM foo LIMIT 0) -- parenthesis needed with LIMIT
UNION ALL SELECT 1, 20, NULL
UNION ALL SELECT 2, 50, NULL
) t -- column names and types are already defined
WHERE f.pkid = t.pkid;
Profi:
- Wie 0. , vermeidet aber eine fehlgeschlagene Typauflösung.
Contra:
UNION ALL SELECT
ist langsamer alsVALUES
Ausdruck für lange Zeilenlisten, wie Sie in Ihrem Test gefunden haben.- Ausführliche Syntax pro Zeile.
2. VALUES
Ausdruck mit spaltenweisem Typ
...
FROM (
VALUES
((SELECT pkid FROM foo LIMIT 0)
, (SELECT x FROM foo LIMIT 0)
, (SELECT y FROM foo LIMIT 0)) -- get type for each col individually
, (1, 20, NULL)
, (2, 50, NULL)
) t (pkid, x, y) -- columns names not defined yet, only types.
...
Im Gegensatz zu 0. dies vermeidet eine vorzeitige Typauflösung.
Die erste Zeile in den VALUES
Ausdruck ist eine Reihe von NULL
Werte, die den Typ für alle nachfolgenden Zeilen definieren. Diese führende Rauschzeile wird durch WHERE f.pkid = t.pkid
gefiltert später, so dass es nie das Licht der Welt erblickt. Für andere Zwecke können Sie die hinzugefügte erste Zeile mit OFFSET 1
eliminieren in einer Unterabfrage.
Profi:
- In der Regel schneller als 1. (oder sogar 0. )
- Kurze Syntax für Tabellen mit vielen Spalten, von denen nur wenige relevant sind.
- Sie müssen nur die relevanten Spaltennamen der Tabelle kennen.
Contra:
- Ausführliche Syntax für nur wenige Zeilen
- Schwer lesbar (IMO).
3. VALUES
Ausdruck mit Zeilentyp
UPDATE foo f
SET x = (t.r).x -- parenthesis needed to make syntax unambiguous
, y = (t.r).y
FROM (
VALUES
('(1,20,)'::foo) -- columns need to be in default order of table
,('(2,50,)') -- nothing after the last comma for NULL
) t (r) -- column name for row type
WHERE f.pkid = (t.r).pkid;
Sie kennen offensichtlich den Tabellennamen. Wenn Sie auch die Anzahl der Spalten und deren Reihenfolge kennen, können Sie damit arbeiten.
Für jede Tabelle in PostgreSQL wird automatisch ein Zeilentyp registriert. Wenn Sie mit der Anzahl der Spalten in Ihrem Ausdruck übereinstimmen, können Sie in den Zeilentyp der Tabelle umwandeln ('(1,50,)'::foo
), wodurch implizit Spaltentypen zugewiesen werden. Setzen Sie nichts hinter ein Komma, um einen NULL
einzugeben Wert. Fügen Sie für jede irrelevante abschließende Spalte ein Komma hinzu.
Im nächsten Schritt können Sie mit der demonstrierten Syntax auf einzelne Spalten zugreifen. Mehr über Feldauswahl im Handbuch.
Oder Sie könnten hinzufügen eine Reihe von NULL-Werten und verwenden Sie eine einheitliche Syntax für tatsächliche Daten:
...
VALUES
((NULL::foo)) -- row of NULL values
, ('(1,20,)') -- uniform ROW value syntax for all
, ('(2,50,)')
...
Profi:
- Am schnellsten (zumindest in meinen Tests mit wenigen Zeilen und Spalten).
- Kürzeste Syntax für wenige Zeilen oder Tabellen, wo Sie alle Spalten benötigen.
- Sie müssen die Spalten der Tabelle nicht buchstabieren - alle Spalten haben automatisch den passenden Namen.
Contra:
- Nicht so bekannte Syntax für die Feldauswahl aus Datensatz / Zeile / zusammengesetztem Typ.
- Sie müssen die Anzahl und Position der relevanten Spalten in der Standardreihenfolge kennen.
4. VALUES
Ausdruck mit zerlegt Zeilentyp
Wie 3. , aber mit zerlegten Zeilen in Standard-Syntax:
UPDATE foo f
SET x = t.x
, y = t.y
FROM (
VALUES
(('(1,20,)'::foo).*) -- decomposed row of values
, (2, 50, NULL)
) t(pkid, x, y) -- arbitrary column names (I made them match)
WHERE f.pkid = t.pkid; -- eliminates 1st row with NULL values
Oder, wieder mit einer führenden Reihe von NULL-Werten:
...
VALUES
((NULL::foo).*) -- row of NULL values
, (1, 20, NULL) -- uniform syntax for all
, (2, 50, NULL)
...
Vor- und Nachteile wie 3. , aber mit allgemein bekannterer Syntax.
Und Sie müssen Spaltennamen buchstabieren (falls Sie sie brauchen).
5. VALUES
Ausdruck mit Typen, die vom Zeilentyp abgerufen werden
Wie Unril kommentierte, können wir die Vorzüge von 2. kombinieren und 4. um nur eine Teilmenge von Spalten bereitzustellen:
UPDATE foo f
SET ( x, y)
= (t.x, t.y) -- short notation, see below
FROM (
VALUES
((NULL::foo).pkid, (NULL::foo).x, (NULL::foo).y) -- subset of columns
, (1, 20, NULL)
, (2, 50, NULL)
) t(pkid, x, y) -- arbitrary column names (I made them match)
WHERE f.pkid = t.pkid;
Vor- und Nachteile wie 4. , aber wir können mit jeder Teilmenge von Spalten arbeiten und müssen nicht die vollständige Liste kennen.
Zeigt auch eine kurze Syntax für das UPDATE
an selbst das ist praktisch für Fälle mit vielen Spalten. Verwandte:
- Massenaktualisierung aller Spalten
4. und 5. sind meine Favoriten.
db<>hier fummeln - alle demonstrieren