Запрос JSONB в PostgreSQL

14

У меня есть таблица, personsкоторая содержит два столбца, idи столбец на основе JSONB data(эта таблица была только что сделана в демонстрационных целях, чтобы поиграться с поддержкой JSON в PostgreSQL).

Теперь предполагается, что он содержит две записи:

1, { name: 'John', age: 30 }
2, { name: 'Jane', age: 20 }

Предположим, я хочу получить имя каждого человека старше 25 лет. Я попробовал:

select data->'name' as name from persons where data->'age' > 25

К сожалению, это приводит к ошибке. Я могу решить эту проблему, используя ->>вместо этого ->, но тогда сравнения не будут работать так, как ожидалось, поскольку сравниваются не числа, а их представления в виде строк:

select data->'name' as name from persons where data->>'age' > '25'

Затем я понял, что на самом деле могу решить проблему, используя ->и приведение к int:

select data->'name' as name from persons where cast(data->'age' as int) > 25

Это работает, но не очень приятно, что мне нужно знать фактический тип (тип ageдокумента JSON в numberлюбом случае таков, так почему же PostgreSQL не может выяснить это сам по себе?).

Затем я понял, что, если я вручную преобразую textв ::синтаксис, все тоже будет работать, как и ожидалось - хотя мы сейчас снова сравниваем строки.

select data->'name' as name from persons where data->'age'::text > '25'

Если я тогда попробую это с именем вместо возраста, это не сработает:

select data->'name' as name from persons where data->'name'::text > 'Jenny'

Это приводит к ошибке:

неверный синтаксис ввода для типа json

Совершенно очевидно, что я ничего не понимаю здесь. К сожалению, довольно сложно найти какие-либо реальные примеры использования JSON с PostgreSQL.

Есть намеки?

Голо Роден
источник
1
В data->'name'::text, вы 'name'приводите строку к тексту, а не результат. Вы не получите ошибку при сравнении с, '25'потому что 25это допустимый литерал JSON; но Jennyэто не так (хотя "Jenny"было бы).
chirlu
Спасибо, это решение :-). Я перепутал 'Jenny'с '"Jenny"'.
Голо Роден

Ответы:

14

Это не работает, потому что он пытается привести jsonbзначение к integer.

select data->'name' as name from persons where cast(data->'age' as int) > 25

Это на самом деле будет работать:

SELECT data->'name' AS name FROM persons WHERE cast(data->>'age' AS int) > 25;

Или короче:

SELECT data->'name' AS name FROM persons WHERE (data->>'age')::int > 25;

И это:

SELECT data->'name' AS name FROM persons WHERE data->>'name' > 'Jenny';

Похоже, путаница с двумя операторами ->и->> и приоритет оператора . Приведение ::связывается сильнее, чем операторы json (b).

Определить тип динамически

Это более интересная часть вашего вопроса:

в любом случае тип возраста в документе JSON - число, так почему же PostgreSQL не может выяснить это самостоятельно?

SQL является строго типизированным языком, он не позволяет вычислять integerодно и то же выражение в одной строке и textв следующей. Но так как вас интересует только booleanрезультат теста, вы можете обойти это ограничение с помощью CASEвыражения, которое разветвляется в зависимости от результата jsonb_typeof():

SELECT data->'name'
FROM   persons
WHERE  CASE jsonb_typeof(data->'age')
        WHEN 'number'  THEN (data->>'age')::numeric > '25' -- treated as numeric
        WHEN 'string'  THEN data->>'age' > 'age_level_3'   -- treated as text
        WHEN 'boolean' THEN (data->>'age')::bool           -- use boolean directly (example)
        ELSE FALSE                                         -- remaining: array, object, null
       END;

Нетипизированный строковый литерал справа от >оператора автоматически приводится к соответствующему типу значения слева. Если вы поместите туда типизированное значение, тип должен совпадать или вы должны явным образом привести его - если в системе не зарегистрировано адекватное неявное приведение.

Если вы знаете, что все числовые значения на самом деле integer, вы также можете:

... (data->>'age')::int > 25 ...
Эрвин Брандштеттер
источник
каково выражение ядра sqlalchemy для приведенного выше сравнения оператора select, например. s = выберите ([проблем]). ) ... Здесь выдает тип данных jsonb.c.data и сравнивает его с целочисленным типом
mtypes.c.id