it-swarm.com.de

Warum muss ich NULL in einen Spaltentyp umwandeln?

Ich habe einen Helfer, der Code generiert, um Massenaktualisierungen für mich durchzuführen, und SQL generiert, das so aussieht:

(Sowohl das aktive als auch das Kernfeld sind vom Typ boolean)

UPDATE fields as t set "active" = new_values."active","core" = new_values."core"
FROM (values 
(true,NULL,3419),
(false,NULL,3420)
) as new_values("active","core","id") WHERE new_values.id = t.id;

Es schlägt jedoch fehl mit:

ERROR: column "core" is of type boolean but expression is of type text

Ich kann es zum Laufen bringen, indem ich ::boolean zu den Nullen, aber das scheint nur seltsam, warum wird NULL vom Typ TEXT betrachtet?

Das Casting ist auch etwas schwierig, da es eine ziemliche Überarbeitung des Codes erfordern würde, um zu wissen, in welchen Typ NULLs umgewandelt werden sollen (die Liste der Spalten und Werte wird derzeit automatisch aus einem einfachen Array von JSON-Objekten generiert). .

Warum ist dies notwendig und gibt es eine elegantere Lösung, bei der der generierende Code nicht erforderlich ist, um den Typ der NULL-Werte zu kennen?

Wenn es relevant ist, verwende ich dazu sequelize over Node.JS, erhalte aber auch das gleiche Ergebnis im Postgres-Befehlszeilenclient.

10
ChristopherJ

Dies ist ein interessanter Befund. Normalerweise hat ein NULL keinen angenommenen Datentyp, wie Sie hier sehen können:

SELECT pg_typeof(NULL);

 pg_typeof 
───────────
 unknown

Dies ändert sich, wenn eine VALUES -Tabelle ins Bild kommt:

SELECT pg_typeof(core) FROM (
    VALUES (NULL)
) new_values (core);

 pg_typeof 
───────────
 text

Dieses Verhalten wird im Quellcode unter https://doxygen.postgresql.org/parse__coerce_8c.html#l0137 beschrieben.

 /*
  * If all the inputs were UNKNOWN type --- ie, unknown-type literals ---
  * then resolve as type TEXT.  This situation comes up with constructs
  * like SELECT (CASE WHEN foo THEN 'bar' ELSE 'baz' END); SELECT 'foo'
  * UNION SELECT 'bar'; It might seem desirable to leave the construct's
  * output type as UNKNOWN, but that really doesn't work, because we'd
  * probably end up needing a runtime coercion from UNKNOWN to something
  * else, and we usually won't have it.  We need to coerce the unknown
  * literals while they are still literals, so a decision has to be made
  * now.
  */

(Ja, PostgreSQL-Quellcode ist dank hervorragender Kommentare an den meisten Orten relativ einfach zu verstehen.)

Der Ausweg könnte jedoch der folgende sein. Angenommen, Sie generieren immer VALUES, die mit allen Spalten einer bestimmten Tabelle übereinstimmen (siehe den zweiten Hinweis unten für andere Fälle). Aus Ihrem Beispiel könnte möglicherweise ein kleiner Trick helfen:

SELECT (x).* FROM (VALUES ((TRUE, NULL, 1234)::fields)) t(x);

 active │ core │  id  
────────┼──────┼──────
 t      │      │ 1234

Hier verwenden Sie Zeilenausdrücke , die in den Tabellentyp umgewandelt wurden, und extrahieren sie dann zurück in eine Tabelle.

Basierend auf dem oben Gesagten könnte Ihr UPDATE so aussehen

UPDATE fields AS t set active = (x).active, core = (x).core
FROM ( VALUES
           ((true, NULL, 3419)::fields),
           ((false, NULL, 3420)::fields)
     ) AS new_values(x) WHERE (x).id = t.id;

Anmerkungen:

  • Ich habe die doppelten Anführungszeichen entfernt, um die Lesbarkeit zu verbessern. Sie können sie jedoch beibehalten, da sie beim Generieren von (Spalten-) Namen hilfreich sind.
  • wenn Sie nur eine Teilmenge der Spalten benötigen, können Sie zu diesem Zweck benutzerdefinierte Typen erstellen . Verwenden Sie sie auf die gleiche Weise wie oben (wobei ich den automatisch mit der Tabelle erstellten Typ verwende, der die Zeilenstruktur der letzteren enthält).

Schau dir die ganze Sache an, an der gearbeitet wird dbfiddle .

16
dezso