PostgreSQL
 sql >> Datenbank >  >> RDS >> PostgreSQL

Umwandlung des NULL-Typs beim Aktualisieren mehrerer Zeilen

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 als VALUES 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