Почему мне нужно привести NULL к типу столбца?

10

У меня есть помощник, который генерирует некоторый код для массовых обновлений для меня и генерирует SQL, который выглядит следующим образом:

(И активные, и основные поля имеют тип 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;

Однако это не с:

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

Я могу заставить это работать, добавляя ::booleanк нулям, но это только кажется странным, почему NULL считается типом TEXT?

Кроме того, это немного сложно привести, потому что это потребовало бы немного переделки кода, чтобы он знал, к какому типу он должен приводить значения NULL (список столбцов и значений в настоящее время автоматически генерируется из простого массива объектов JSON) ,

Почему это необходимо и есть более элегантное решение, которое не требует, чтобы генерирующий код знал тип NULL?

Если это уместно, я использую sequelize для Node.JS, но также получаю тот же результат в клиенте командной строки Postgres.

ChristopherJ
источник

Ответы:

16

Это интересная находка. Обычно NULL не имеет предполагаемого типа данных, как вы можете видеть здесь:

SELECT pg_typeof(NULL);

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

Это изменяется, когда VALUESтаблица входит в картину:

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

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

Это поведение описано в исходном коде по адресу 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.
  */

(Да, исходный код PostgreSQL относительно прост для понимания и в большинстве мест благодаря отличным комментариям.)

Выход, однако, может быть следующим. Допустим, вы всегда генерируете, VALUESкоторые соответствуют всем столбцам данной таблицы (другие случаи приведены во втором примечании ниже). Из вашего примера может помочь маленький трюк:

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

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

Здесь вы используете выражения строк, приведенные к типу таблицы, а затем извлекаете их обратно в таблицу.

Исходя из вышеизложенного, ваш UPDATEможет выглядеть

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;

Ноты:

  • Я удалил двойные кавычки для лучшей читаемости человеком, но вы можете сохранить их, так как они помогают при генерации имен (столбцов).
  • если вам нужно только подмножество столбцов, вы можете создавать собственные типы для этой цели. Используйте их так же, как и выше (где я использую тип, автоматически созданный с таблицей, держа структуру строк последней).

Посмотрите на все это, работая над dbfiddle .

Dezso
источник
Спасибо, это интересно, однако для меня приведенный выше код производит Cannot cast type boolean to bigint in column 1(ошибка указывает на :: между первым полем оператора)
ChristopherJ
1
@ChristopherJ ответ предполагает, что вызываемая таблица fieldsимеет 3 столбца, (active, core, id)с типами boolean, boolean и int / bigint. В вашей таблице больше столбцов или разных типов, или столбцы определены в другом порядке?
ypercubeᵀᴹ
Ах, я вижу, спасибо, да, есть больше столбцов и в другом порядке. Получил это спасибо
ChristopherJ