检查 Postgres JSON 数组是不是包含字符串

Posted

技术标签:

【中文标题】检查 Postgres JSON 数组是不是包含字符串【英文标题】:Check if a Postgres JSON array contains a string检查 Postgres JSON 数组是否包含字符串 【发布时间】:2013-11-24 09:26:17 【问题描述】:

我有一张表来存储关于我的兔子的信息。它看起来像这样:

create table rabbits (rabbit_id bigserial primary key, info json not null);
insert into rabbits (info) values
  ('"name":"Henry", "food":["lettuce","carrots"]'),
  ('"name":"Herald","food":["carrots","zucchini"]'),
  ('"name":"Helen", "food":["lettuce","cheese"]');

我应该如何找到喜欢胡萝卜的兔子?我想出了这个:

select info->>'name' from rabbits where exists (
  select 1 from json_array_elements(info->'food') as food
  where food::text = '"carrots"'
);

我不喜欢那个查询。真是一团糟。

作为一名全职养兔人,我没有时间更改我的数据库架构。我只想好好喂养我的兔子。有没有更易读的方式来做这个查询?

【问题讨论】:

有趣的问题。我玩过它,但后来我突然意识到,我不确定你所说的“更好”是什么意思。你用什么标准来判断你的答案?可读性?效率?其他? @DavidS:(我更新了问题。)我更喜欢可读性而不是效率。我当然不希望有什么比全表扫描更好的了,因为我的架构是固定的。 【参考方案1】:

从 PostgreSQL 9.4 开始,您可以使用 ? operator:

select info->>'name' from rabbits where (info->'food')::jsonb ? 'carrots';

如果您改用 jsonb 类型,您甚至可以在 "food" 键上索引 ? 查询:

alter table rabbits alter info type jsonb using info::jsonb;
create index on rabbits using gin ((info->'food'));
select info->>'name' from rabbits where info->'food' ? 'carrots';

当然,作为全职养兔人,您可能没有时间这样做。

更新:以下是在 1,000,000 只兔子的桌子上的性能改进演示,其中每只兔子喜欢两种食物,其中 10% 喜欢胡萝卜:

d=# -- Postgres 9.3 solution
d=# explain analyze select info->>'name' from rabbits where exists (
d(# select 1 from json_array_elements(info->'food') as food
d(#   where food::text = '"carrots"'
d(# );
 Execution time: 3084.927 ms

d=# -- Postgres 9.4+ solution
d=# explain analyze select info->'name' from rabbits where (info->'food')::jsonb ? 'carrots';
 Execution time: 1255.501 ms

d=# alter table rabbits alter info type jsonb using info::jsonb;
d=# explain analyze select info->'name' from rabbits where info->'food' ? 'carrots';
 Execution time: 465.919 ms

d=# create index on rabbits using gin ((info->'food'));
d=# explain analyze select info->'name' from rabbits where info->'food' ? 'carrots';
 Execution time: 256.478 ms

【讨论】:

如何获取json中food数组不为空的行,例如如果我们可以考虑,它们是JSON,其中food数组也是空的,你能帮忙 @Bravo select * from rabbits where info->'food' != '[]'; 如果您需要选择整数而不是字符串/文本,有人知道这是如何工作的吗? @Rotareti 您可以使用@> operator:create table t (x jsonb); insert into t (x) values ('[1,2,3]'), ('[2,3,4]'), ('[3,4,5]'); select * from t where x @> '2';。注意'2'是一个JSON数字;不要被引号误导。 info使用JSONB,使用where info @> '"food":["carrots"]'不是更好吗?这使用 info 列上的 GIN 索引,而使用 ->(如 ext->'hobbies' @> '"eating"')会阻止它。这意味着不需要索引 JSON 键,只需对整个列进行一次索引(当然,如果“包含”操作就足够了)。【参考方案2】:

您可以使用@> 运算符来执行此操作,例如

SELECT info->>'name'
FROM rabbits
WHERE info->'food' @> '"carrots"';

【讨论】:

这在 item 为 null 时也很有用 确保你注意“胡萝卜”周围的' 勾号...如果你把它们排除在外,它就会中断,即使你正在检查一个整数。 (花了 3 个小时试图找到一个整数,通过将' 包裹在数字周围来让它神奇地工作) @skplunkerin 应该是json值,用'打勾组成字符串,因为对于JSONB类型的SQL来说,一切都是字符串。例如,布尔值:'true',字符串:'"example"',整数:'123'【参考方案3】:

不是更聪明,而是更简单:

select info->>'name' from rabbits WHERE info->>'food' LIKE '%"carrots"%';

【讨论】:

如果你有胡萝卜作为子字符串的记录,还有其他上下文,比如:"name":"Henry", "food":["lettuce","foocarrots"] 这个答案的简单性不仅对我有所帮助,而且与 PostgreSQL 文档保持一致。但是,我必须删除双引号 ('"') 才能使其正常工作。注意:看来第一个字符需要是通配符 ('%') 才能使用它们。... WHERE info->>'food' LIKE '%carrots%';【参考方案4】:

一个小的变化,但没有什么新的事实。真是少了一个功能……

select info->>'name' from rabbits 
where '"carrots"' = ANY (ARRAY(
    select * from json_array_elements(info->'food'))::text[]);

【讨论】:

【参考方案5】:

不是更简单,而是更智能:

select json_path_query(info, '$ ? (@.food[*] == "carrots")') from rabbits

【讨论】:

以上是关于检查 Postgres JSON 数组是不是包含字符串的主要内容,如果未能解决你的问题,请参考以下文章

如何检查 Postgres 中是不是存在 json 键?

如何检查 JSON 对象数组是不是包含数组中定义的值?

Postgres选择包含json的json数组中的位置

检查 Json 数组是不是包含逻辑应用程序中的对象

在postgres 9.5中插入包含json对象的数组作为行

检查一个元素是不是包含在MySql中一个json列的值(数组)中