JSONB:用作表达式的子查询返回的多行

时间:2015-06-16 02:10:28

标签: sql postgresql postgresql-9.4 jsonb set-returning-functions

我(仍)是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

并返回两行。

3 个答案:

答案 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”的值是一个数组)。