Pourquoi dois-je convertir NULL en type de colonne?

10

J'ai un assistant qui génère du code pour faire des mises à jour en masse pour moi et génère du SQL qui ressemble à ceci:

(Les champs actifs et principaux sont de type 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;

Cependant, il échoue avec:

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

Je peux le faire fonctionner en ajoutant ::booleanaux valeurs nulles, mais cela semble étrange, pourquoi NULL est-il considéré comme de type TEXT?

De plus, il est un peu difficile à convertir car cela nécessiterait un peu de remaniement du code pour qu'il sache dans quel type il devrait convertir des valeurs NULL (la liste des colonnes et des valeurs est actuellement générée automatiquement à partir d'un simple tableau d'objets JSON) .

Pourquoi est-ce nécessaire et existe-t-il une solution plus élégante qui ne nécessite pas le code générateur pour connaître le type de NULL?

Si cela est pertinent, j'utilise sequelize sur Node.JS pour ce faire, mais j'obtiens également le même résultat dans le client de ligne de commande Postgres.

ChristopherJ
la source

Réponses:

16

C'est une découverte intéressante. Normalement, un NULL n'a pas de type de données supposé, comme vous pouvez le voir ici:

SELECT pg_typeof(NULL);

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

Cela change lorsqu'un VALUEStableau apparaît dans l'image:

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

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

Ce comportement est décrit dans le code source à https://doxygen.postgresql.org/parse__coerce_8c.html#l01373 :

 /*
  * 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.
  */

(Oui, le code source de PostgreSQL est relativement facile à comprendre et la plupart des endroits, grâce à d'excellents commentaires.)

La sortie pourrait cependant être la suivante. Disons que vous générez toujours VALUESqui correspondent à toutes les colonnes d'un tableau donné (voir la deuxième note ci-dessous pour d'autres cas). D'après votre exemple, une petite astuce pourrait éventuellement aider:

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

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

Ici, vous utilisez des expressions de ligne converties en type de table, puis les extrayez dans une table.

Sur la base de ce qui précède, votre UPDATEpourrait ressembler à

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;

Remarques:

  • J'ai supprimé les guillemets doubles pour une meilleure lisibilité humaine, mais vous pouvez les conserver car ils aident lors de la génération des noms (de colonne).
  • si vous n'avez besoin que d'un sous-ensemble de colonnes, vous pouvez créer des types personnalisés à cet effet. Utilisez-les de la même manière que vous le feriez ci-dessus (où j'utilise le type créé automatiquement avec la table, en maintenant la structure des lignes de cette dernière).

Regardez le tout travailler sur dbfiddle .

dezso
la source
Merci, c'est intéressant, cependant, pour moi, le code ci-dessus produit Cannot cast type boolean to bigint in column 1(l'erreur pointe au :: entre la première déclaration de champs)
ChristopherJ
1
@ChristopherJ la réponse suppose que la table appelée fieldsa 3 colonnes, (active, core, id)avec des types booléens, booléens et int / bigint. Votre table a-t-elle plus de colonnes ou de types différents ou les colonnes sont-elles définies dans un ordre différent?
ypercubeᵀᴹ
Ah je vois, merci, yep il y a plus de colonnes et dans un ordre différent. Je vous remercie
ChristopherJ