Чому мені потрібно надати 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?

Крім того, це досить складно для передачі, тому що знадобиться зовсім небагато переробляти код, щоб він знав, до якого типу він повинен видавати NULLs (список стовпців і значень в даний час автогенерується з простого масиву об'єктів JSON) .

Чому це необхідно і чи існує більш елегантне рішення, яке не вимагає генеруючого коду, щоб знати тип NULL?

Якщо це доречно, я використовую секелізацію над Node.JS для цього, але я отримую такий же результат і в клієнті командного рядка Postgres.

Відповіді:


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 .


Дякую, це цікаво, однак, для мене наведений вище код Cannot cast type boolean to bigint in column 1(пункти помилок у :: між першим висловом поля)
ChristopherJ

1
@ChristopherJ у відповіді передбачається, що названа таблиця fieldsмає 3 стовпці (active, core, id)з типами boolean, boolean та int / bigint. Чи у вашій таблиці більше стовпців чи різних типів чи стовпці визначені в іншому порядку?
ypercubeᵀᴹ

Ну я бачу, спасибі, так, є більше стовпців і в іншому порядку. Отримав спасибі
ChristopherJ
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.