JSONB:用作表达式的子查询返回的多行
JSONB: more than one row returned by a subquery used as an expression
我(仍然)是 postgresql 和 jsonb 的新手。我正在尝试 select 来自子查询的一些记录,但卡住了。我的数据列如下所示 (jsonb):
{"people": [{"age": "50", "name": "Bob"}], "another_key": "no"}
{"people": [{"age": "73", "name": "Bob"}], "another_key": "yes"}
这是我的查询。我想 select 所有 "Bob" 年龄大于 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
并且 returns 两行。
您的示例的正确查询如下:
SELECT *
FROM mytable
WHERE (data #> '{people,0}' ->>'name') = 'Bob'
AND (data #> '{people,0}' ->>'age')::integer > 30
(注意"people"的值是一个数组)
在您的子查询中:
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
您 select 重新编辑了 mytable
的所有行。这就是为什么你的子查询 returns 多个值。
如果您希望 table 中的 select 行包含满足特定条件的元素,那么在您的条件下,不要重新select 来自那个 table;使用您已经在外部查询中 select 编辑的行:
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
来自外部查询。
如果您想要 select 来自 "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
错误的意思就是它所说的:
more than one row returned by a subquery used as an expression
WHERE
子句中的表达式需要一个值(就像您在添加的测试中替换的一样),但是您的子查询 returns 多个 行。 jsonb_array_elements()
是集合返回函数。
假设这个 table 定义:
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 数组在里面。
对于大 tables,这 多 匹配索引更有效。如果你经常运行这种查询,你应该有一个:
CREATE INDEX mytable_people_gin_idx ON mytable
USING gin ((data->'people') jsonb_path_ops);
相关,更多解释:
Index for finding an element in a JSON array
我(仍然)是 postgresql 和 jsonb 的新手。我正在尝试 select 来自子查询的一些记录,但卡住了。我的数据列如下所示 (jsonb):
{"people": [{"age": "50", "name": "Bob"}], "another_key": "no"}
{"people": [{"age": "73", "name": "Bob"}], "another_key": "yes"}
这是我的查询。我想 select 所有 "Bob" 年龄大于 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
并且 returns 两行。
您的示例的正确查询如下:
SELECT *
FROM mytable
WHERE (data #> '{people,0}' ->>'name') = 'Bob'
AND (data #> '{people,0}' ->>'age')::integer > 30
(注意"people"的值是一个数组)
在您的子查询中:
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
您 select 重新编辑了 mytable
的所有行。这就是为什么你的子查询 returns 多个值。
如果您希望 table 中的 select 行包含满足特定条件的元素,那么在您的条件下,不要重新select 来自那个 table;使用您已经在外部查询中 select 编辑的行:
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
来自外部查询。
如果您想要 select 来自 "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
错误的意思就是它所说的:
more than one row returned by a subquery used as an expression
WHERE
子句中的表达式需要一个值(就像您在添加的测试中替换的一样),但是您的子查询 returns 多个 行。 jsonb_array_elements()
是集合返回函数。
假设这个 table 定义:
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 数组在里面。
对于大 tables,这 多 匹配索引更有效。如果你经常运行这种查询,你应该有一个:
CREATE INDEX mytable_people_gin_idx ON mytable
USING gin ((data->'people') jsonb_path_ops);
相关,更多解释:
Index for finding an element in a JSON array