我(仍)是postgresql和jsonb的新手。我试图从子查询中选择一些记录并被卡住。我的数据列看起来像这样(jsonb):
{"people": [{"age": "50", "name": "Bob"}], "another_key": "no"}
{"people": [{"age": "73", "name": "Bob"}], "another_key": "yes"}
这是我的疑问。我想选择年龄大于30岁的所有“Bob”名称:
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
并返回两行。
答案 0 :(得分:3)
错误意味着它的含义:
用作表达式
的子查询返回的多行
WHERE
子句中的表达式需要单个值(就像您在添加的测试中替换的那样),但子查询返回 多个 行。 jsonb_array_elements()
是一个返回集合的函数。
假设这个表定义:
CREATE TABLE mytable (
id serial PRIMARY KEY
, data jsonb
);
如果里面没有多个人,那么"people"
的JSON数组是没有意义的。只有一个人的例子会产生误导。一些更具启发性的测试数据:
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;
第一个WHERE
条件WHERE (t.data->'people') @> '[{"name": "Bob"}]'
在逻辑上是多余的,但它可以通过尽早消除不相关的行来帮助提高性能:甚至在没有"Bob"
的情况下也不会删除JSON数组。
对于大表,匹配索引的效率 效率更高。如果您经常运行此类查询,则应该有一个:
CREATE INDEX mytable_people_gin_idx ON mytable
USING gin ((data->'people') jsonb_path_ops);
相关,有更多解释:
答案 1 :(得分: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
来自外部查询。
如果您想从满足条件的"people"
字段中选择所有JSON对象,那么只需聚合所有"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
答案 2 :(得分:-1)
您的示例的正确查询如下:
SELECT *
FROM mytable
WHERE (data #> '{people,0}' ->>'name') = 'Bob'
AND (data #> '{people,0}' ->>'age')::integer > 30
(注意“people”的值是一个数组)。