¿Por qué necesito convertir NULL al tipo de columna?

10

Tengo un ayudante que genera un código para hacer actualizaciones masivas para mí y genera SQL que se ve así:

(Tanto el campo activo como el núcleo son de tipo 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;

Sin embargo, falla con:

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

Puedo hacer que funcione agregando ::booleannulos, pero eso parece extraño, ¿por qué se considera NULL de tipo TEXT?

También es un poco difícil de emitir porque requeriría un poco de reelaboración del código para que sepa a qué tipo debe convertir NULL (la lista de columnas y valores se está generando automáticamente a partir de una simple matriz de objetos JSON) .

¿Por qué es esto necesario? ¿Existe una solución más elegante que no requiera el código generador para conocer el tipo de NULL?

Si es relevante, estoy usando la secuencia sobre Node.JS para hacer esto, pero también obtengo el mismo resultado en el cliente de línea de comandos de Postgres.

ChristopherJ
fuente

Respuestas:

16

Este es un hallazgo interesante. Normalmente, un NULL no tiene un tipo de datos asumido, como puede ver aquí:

SELECT pg_typeof(NULL);

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

Esto cambia cuando una VALUEStabla entra en escena:

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

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

Este comportamiento se describe en el código fuente en 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.
  */

(Sí, el código fuente de PostgreSQL es relativamente fácil de entender y la mayoría de los lugares, gracias a los excelentes comentarios).

La salida, sin embargo, podría ser la siguiente. Supongamos que siempre está generando una VALUEScoincidencia con todas las columnas de una tabla determinada (consulte la segunda nota a continuación para ver otros casos). A partir de su ejemplo, un pequeño truco podría ayudar:

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

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

Aquí usa expresiones de fila convertidas al tipo de tabla y luego las extrae de nuevo a una tabla.

Con base en lo anterior, UPDATEpodría verse como

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;

Notas:

  • Eliminé las comillas dobles para una mejor legibilidad humana, pero puede mantenerlas ya que ayudan a generar nombres (columnas).
  • Si solo necesita un subconjunto de las columnas, puede crear tipos personalizados para este propósito. Úselos de la misma manera que lo haría anteriormente (donde uso el tipo creado automáticamente con la tabla, manteniendo la estructura de filas de este último).

Mira todo el trabajo en dbfiddle .

dezso
fuente
Gracias, esto es interesante, sin embargo, para mí, el código anterior produce Cannot cast type boolean to bigint in column 1(el error apunta a :: entre la declaración de los primeros campos)
ChristopherJ
1
@ChristopherJ la respuesta supone que la tabla llamada fieldstiene 3 columnas, (active, core, id)con los tipos boolean, boolean e int / bigint. ¿Su tabla tiene más columnas o tipos diferentes o las columnas están definidas en un orden diferente?
ypercubeᵀᴹ
Ah, ya veo, gracias, sí, hay más columnas y en diferente orden. Gracias
ChristopherJ