Perché devo trasmettere NULL al tipo di colonna?


10

Ho un aiutante che sta generando del codice per eseguire aggiornamenti in blocco per me e genera SQL che assomiglia a questo:

(Entrambi i campi attivo e core sono di 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;

Tuttavia non riesce con:

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

Posso farlo funzionare aggiungendo ::booleanai null, ma sembra strano, perché NULL è considerato di tipo TEXT?

Inoltre, è un po 'complicato eseguire il cast perché richiederebbe un po' di rielaborazione del codice per sapere a quale tipo dovrebbe essere eseguito il NULL (l'elenco di colonne e valori è attualmente generato automaticamente da un semplice array di oggetti JSON) .

Perché è necessario ed esiste una soluzione più elegante che non richiede al codice di generazione di conoscere il tipo di NULL?

Se è pertinente, sto usando sequelize su Node.JS per fare questo, ma sto ottenendo anche lo stesso risultato nel client della riga di comando di Postgres.

Risposte:


16

Questa è una scoperta interessante. Normalmente, un NULL non ha un tipo di dati assunto, come puoi vedere qui:

SELECT pg_typeof(NULL);

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

Questo cambia quando viene visualizzata una VALUEStabella:

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

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

Questo comportamento è descritto nel codice sorgente su 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ì, il codice sorgente PostgreSQL è relativamente facile da capire e la maggior parte dei luoghi, grazie a commenti eccellenti.)

La via d'uscita, tuttavia, potrebbe essere la seguente. Diciamo che stai sempre generando VALUESche corrispondono a tutte le colonne di una determinata tabella (vedi la seconda nota sotto per altri casi). Dal tuo esempio, un piccolo trucco potrebbe eventualmente aiutare:

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

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

Qui si usano le espressioni di riga espresse nel tipo di tabella e quindi estratte in una tabella.

Sulla base di quanto sopra, UPDATEpotresti sembrare

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;

Appunti:

  • Ho rimosso le doppie virgolette per una migliore leggibilità umana, ma puoi mantenerle mentre aiutano a generare nomi (di colonna).
  • se hai bisogno solo di un sottoinsieme delle colonne, puoi creare tipi personalizzati per questo scopo. Usali come faresti sopra (dove uso il tipo creato automaticamente con la tabella, tenendo la struttura delle righe di quest'ultimo).

Guarda tutto lavorando su dbfiddle .


Grazie, questo è interessante, tuttavia, per me, il codice sopra riportato produce Cannot cast type boolean to bigint in column 1(l'errore indica la frase :: tra la prima dichiarazione dei campi)
ChristopherJ

1
@ChristopherJ la risposta presuppone che la tabella chiamata fieldsabbia 3 colonne, (active, core, id)con tipi booleani, booleani e int / bigint. La tua tabella ha più colonne o tipi diversi o le colonne sono definite in ordine diverso?
ypercubeᵀᴹ

Ah, vedo, grazie, sì, ci sono più colonne e in ordine diverso. Capito grazie
ChristopherJ,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.