2

Я (еще) новый для postgresql и jsonb. Я пытаюсь выбрать некоторые записи из подзапроса и застрять. Мой столбец данных выглядит следующим образом (jsonb):JSONB: более одной строки, возвращаемой подзапросом, используемым как выражение

{"people": [{"age": "50", "name": "Bob"}], "another_key": "no"} 
{"people": [{"age": "73", "name": "Bob"}], "another_key": "yes"} 

И вот мой запрос. Я хочу, чтобы выбрать все имена, которые являются «Боб», возраст которых превышает 30:

SELECT * FROM mytable 
WHERE (SELECT (a->>'age')::float 
     FROM (SELECT jsonb_array_elements(data->'people') as a 
      FROM mytable) as b 
     WHERE a @> json_object(ARRAY['name', 'Bob'])::jsonb 
    ) > 30; 

Я получаю ошибку:

more than one row returned by a subquery used as an expression 

Я не совсем понимаю. Если я сделаю простую замену (только для тестирования), я могу это сделать:

SELECT * FROM mytable 
WHERE (50) > 30 -- 50 is the age of the youngest Bob 

и это возвращает обе строки.

+2

вопросом, как это должно обеспечить точное определение таблицы, показывающее все соответствующие столбцы, типы данных и ограничения - то, что вы получаете с '\ d mytable' в psql или допустимым скриптом CREATE TABLE. –

ответ

3

ошибка означает только то, что он говорит:

more than one row returned by a subquery used as an expression

выражение в предложении WHERE ожидает одно значение (так же, как вы замещенное в своем добавленным тесте), но ваш SubQ uery возвращается несколько строк. jsonb_array_elements() - функция, возвращающая набор.

Предполагая, что это определение таблицы:

CREATE TABLE mytable (
    id serial PRIMARY KEY 
, data jsonb 
); 

массив JSON для "people" не имел бы смысл, если бы не мог быть несколько людей внутри. Ваши примеры только с одним человеком вводят в заблуждение.Некоторые более подробные данные испытаний:

INSERT INTO mytable (data) 
VALUES 
    ('{"people": [{"age": "55", "name": "Bill"}], "another_key": "yes"}') 
, ('{"people": [{"age": "73", "name": "Bob"}], "another_key": "yes"}') 
, ('{"people": [{"age": "73", "name": "Bob"} 
       ,{"age": "77", "name": "Udo"}], "another_key": "yes"}'); 

В третьей строке есть два человека.

Я предлагаю запрос с LATERAL присоединиться:

SELECT t.id, p.person 
FROM mytable t 
    , jsonb_array_elements(t.data->'people') p(person) -- implicit LATERAL 
WHERE (t.data->'people') @> '[{"name": "Bob"}]' 
AND p.person->>'name' = 'Bob' 
AND (p.person->>'age')::int > 30; 

Первое условие WHEREWHERE (t.data->'people') @> '[{"name": "Bob"}]' логически лишними, но это повышает производительность за счет устранения нерелевантные строки рано: даже не unnest JSON массивы без "Bob" в Это.

Для больших столов это много более эффективно с соответствующим индексом. Если запустить этот вид запроса регулярно, вы должны иметь один:

CREATE INDEX mytable_people_gin_idx ON mytable 
USING gin ((data->'people') jsonb_path_ops); 

Связанных с более подробным объяснением:

+0

Это имеет смысл, но я теряюсь на «первом условии WHERE t.data @> '{" people ": [{" name ":" Bob "}]}'" ... вы имеете в виду " WHERE (t.data -> 'people') @> '[{"name": "Bob"}]' "? Какой способ более эффективен? –

+1

@kristen: Это был артефакт. Любое выражение хорошее. Важная часть - соответствовать индексу. Я переключил индекс на более узкий индекс выражения (меньше, быстрее, более специализированный) и забыл обновить текст. Исправлено. –

-1

Правильный запрос для примера следующее:

SELECT * 
FROM mytable 
WHERE (data #> '{people,0}' ->>'name') = 'Bob' 
AND (data #> '{people,0}' ->>'age')::integer > 30 

(обратите внимание, что значение «людей» является массивом).

+1

Я думаю, что мне нужен подзапрос b/c. Я не знаю, где в моем списке «Люди» будет «Боб» ... Думаю, мне нужно будет использовать jsonb_array_elements b/c этого. –

1

В вашем подзапроса:

SELECT (a->>'age')::float 
FROM (SELECT jsonb_array_elements(data->'people') as a 
     FROM mytable) as b 
WHERE a @> json_object(ARRAY['name', 'Bob'])::jsonb 

Вы выбрали все строки mytable снова. Вот почему ваш подзапрос возвращает несколько значений.

Если вы хотите выбрать строки из своей таблицы, содержащие элемент, который удовлетворяет определенным условиям, то в ваших условиях не переустанавливайте из этой таблицы; использовать строку, которую вы уже выбрали в вашем внешнем запросе:

SELECT * FROM mytable 
WHERE exists(SELECT 1 
    FROM (SELECT jsonb_array_elements(data->'people') as person) as x 
    WHERE person @> '{"name": "Bob"}' 
    AND (person->>'age')::float > 30) 

Насколько я могу сказать, что странный синтаксис двойного подзапроса необходимо. Обратите внимание, что data - это внешний запрос.

Если вы хотите, чтобы выбрать все объекты JSON из "people" полей, которые удовлетворяют заданным условиям, то просто агрегировать все эти "people" элементы и фильтровать их:

SELECT person 
FROM (SELECT jsonb_array_elements(data->'people') as person 
     FROM mytable) as x 
WHERE person @> '{"name": "Bob"}' 
AND (person->>'age')::float > 30 
Смежные вопросы

 Смежные вопросы