检查 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数组也是空的,你能帮忙 @Bravoselect * 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 数组是不是包含字符串的主要内容,如果未能解决你的问题,请参考以下文章