web-dev-qa-db-fra.com

Pourquoi dois-je convertir NULL en type de colonne?

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 ::boolean aux valeurs nulles, mais cela semble étrange, pourquoi NULL est-il considéré 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 de génération 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.

10
ChristopherJ

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 quand une table VALUES 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#l0137 :

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

Cependant, la sortie pourrait être la suivante. Supposons que vous générez toujours VALUES qui correspondent à toutes les colonnes d'une table donnée (voir la deuxième note ci-dessous pour les 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 expressions de ligne transtypées en type de table, puis les extrayez dans une table.

Sur la base de ce qui précède, votre UPDATE pourrait 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 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 .

16
dezso