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