У нас есть таблица PostgreSQL со столбцом jsonb. Некоторые из узлов в json могут быть представлены как массив или объект на входе. Я пытаюсь написать запрос, который даст мне длину массива, если узел представляет собой массив, а размер массива больше 1
select
count(*) as policycount, policynumber
from
policymaster
where
jsonb_typeof((payload-> 'node1'::text) -> 'node2'::text) = 'array'
-- and jsonb_array_length((payload-> 'node1'::text) -> 'node2'::text) > 1
group by policynumber
order by 1 desc
Если я попытаюсь добавить
and jsonb_array_length((payload-> 'node1' -> 'node2') > 1
тогда я получаю
SQL Error [42601]: ERROR: syntax error at or near "group"
Position: 310
Если я попытаюсь
and jsonb_array_length((payload-> 'node1'::text) -> 'node2'::text) > 1
я получил
SQL Error [22023]: ERROR: cannot get array length of a non-array
Поскольку это смесь объекта и массива, проверка массива в
where
jsonb_typeof((payload-> 'node1'::text) -> 'node2'::text) = 'array'
тоже вроде не помогает
Как я могу получить только те записи, где node2 является массивом, а размер этого массива больше 1?
Подвопрос
Когда я выполнил запрос @jjanes как есть
select
count(*) as policycount, policynumber
from
policymaster
where
case when jsonb_typeof((payload-> 'node1'::text) -> 'node2'::text) = 'array'
then jsonb_array_length((payload-> 'node1'::text) -> 'node2'::text) > 1
else false end
group by policynumber
order by 1 desc ;
Результаты были пустыми.
Когда я изменил входные параметры для обоих вызовов функций ниже, это дало ожидаемые результаты.
select
count(*) as policycount, policynumber
from
policymaster
where
case when jsonb_typeof(payload-> 'node1' -> 'node2') = 'array'
then jsonb_array_length(payload-> 'node1' -> 'node2') > 1
else false end
group by policynumber
order by 1 desc ;
Я только начал работать над PostgreSQL, поэтому не совсем понимаю функции json/jsonb.
Из того, что я понял, часть ::text любого объекта jsonb преобразует его из jsonb в текст, но не уверен, как именно ведет себя эта часть.
(payload-> 'node1'::text) -> 'node2'::text)
Можете ли вы объяснить эту часть. Может быть, тогда это поможет мне понять, почему запрос с ::text для обоих этих узлов не работает в операторе case, но работает при использовании по отдельности по-разному.
Спасибо
Два запроса, которые вы даете в своем обновлении, дают мне идентичные результаты. (как надо). Вы не переводите jsonb в текст, вы переводите строку «node1» в текст. В этом нет необходимости, но и проблем быть не должно. (Некоторые клиенты при передаче связанного параметра хотят заранее знать тип этого параметра, поэтому в таких случаях может потребоваться приведение).



Наверное, это может помочь
with temp_cte as (
select * from policymaster
where jsonb_typeof(payload-> 'node1' -> 'node2')='array'
)
select
policynumber, count(*) as policycount
from
temp_cte
where
jsonb_array_length(payload-> 'node1' -> 'node2') > 1
group by policynumber
order by 1 desc
Вы не должны приводить массив как текст внутри функции jsonb_array_length.
Спасибо. Пробовал вышеописанное, но все равно получаю ту же ошибку SQL Error [22023]: ERROR: cannot get array length of a non-array. Любое другое предложение?
Странный. Я фильтрую эти записи в файле temp_cte. Итак, во втором запросе у нас есть только записи, где node2 — это массив.
Да, я тоже так думал. Но когда я его выполняю, я получаю эту ошибку
Синтаксическая ошибка возникает только потому, что ваши скобки не сбалансированы. Если вы исправите это, вы получите другую ошибку, которую вы получали.
Нет никакой гарантии, что простое использование AND приведет к короткому замыканию так, как вы хотите. Вы можете использовать CASE, чтобы заставить 2-й не выполняться, если 1-й не даст желаемого результата, как описано в документах:
select
count(*) as policycount, policynumber
from
policymaster
where
case when jsonb_typeof((payload-> 'node1'::text) -> 'node2'::text) = 'array'
then jsonb_array_length((payload-> 'node1'::text) -> 'node2'::text) > 1
else false end
group by policynumber
order by 1 desc ;
Причина, по которой CTE не работает в последних версиях, заключается в том, что планировщик просто включает CTE в остальную часть запроса и таким образом выдает тот же план. Вы можете предотвратить это с помощью with temp_cte as MATERIALIZED (
Спасибо. Он работает с некоторой модификацией и есть вопрос по этой части. Я обновил фактический вопрос с помощью подвопроса вместо того, чтобы пытаться уместить информацию в комментариях.
«Из того, что я понял, ::текстовая часть любого объекта jsonb» - у вас нет приведения
::textк значениям jsonb, они есть у литералов имени ключа.payload-> 'node1'::textто же, что иpayload-> ('node1'::text)- оба ненужны, просто напишитеpayload-> 'node1'.