Por que preciso converter NULL no tipo de coluna?

10

Eu tenho um ajudante que está gerando algum código para fazer atualizações em massa para mim e gera SQL que se parece com isso:

(Os campos ativo e principal são do 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;

No entanto, falha com:

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

Posso fazê-lo funcionar adicionando ::booleanaos valores nulos, mas isso parece estranho, por que NULL é considerado do tipo TEXT?

Também é um pouco difícil de converter, porque seria necessário um retrabalho do código para que ele soubesse para qual tipo deve converter NULLs (a lista de colunas e valores está sendo gerada automaticamente a partir de uma matriz simples de objetos JSON) .

Por que isso é necessário e existe uma solução mais elegante que não exija que o código gerador conheça o tipo de NULLs?

Se for relevante, estou usando sequelize no Node.JS para fazer isso, mas também estou obtendo o mesmo resultado no cliente de linha de comando do Postgres.

ChristopherJ
fonte

Respostas:

16

Esta é uma descoberta interessante. Normalmente, um NULL não tem um tipo de dados assumido, como você pode ver aqui:

SELECT pg_typeof(NULL);

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

Isso muda quando uma VALUEStabela entra em cena:

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

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

Esse comportamento é descrito no código-fonte em 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.
  */

(Sim, o código fonte do PostgreSQL é relativamente fácil de entender e na maioria dos lugares, graças a excelentes comentários.)

A saída, no entanto, pode ser a seguinte. Digamos que você esteja sempre gerando VALUESessa correspondência com todas as colunas de uma determinada tabela (consulte a segunda nota abaixo para outros casos). Do seu exemplo, um pequeno truque pode ajudar:

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

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

Aqui você usa expressões de linha convertidas para o tipo da tabela e depois as extrai de volta para uma tabela.

Com base no acima, você UPDATEpode se parecer com

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:

  • Eu removi as aspas duplas para melhor legibilidade humana, mas você pode mantê-las como elas ajudam na geração de nomes (colunas).
  • se você precisar de apenas um subconjunto das colunas, poderá criar tipos personalizados para essa finalidade. Use-os da mesma maneira que você usaria acima (onde eu uso o tipo criado automaticamente com a tabela, mantendo a estrutura de linhas da última).

Veja a coisa toda trabalhando no dbfiddle .

dezso
fonte
Obrigado, isso é interessante, no entanto, para mim, o código acima produz Cannot cast type boolean to bigint in column 1(os pontos de erro na :: entre a declaração primeiros campos)
ChristopherJ
11
@ChristopherJ, a resposta assume que a tabela chamada fieldspossui 3 colunas, (active, core, id)com os tipos booleano, booleano e int / bigint. Sua tabela possui mais colunas ou tipos diferentes ou as colunas são definidas em ordem diferente?
precisa saber é o seguinte
Ah entendo, obrigado, sim, existem mais colunas e em ordem diferente. Entendi obrigado
ChristopherJ