在 JSON 数组中查找元素的索引

Posted

技术标签:

【中文标题】在 JSON 数组中查找元素的索引【英文标题】:Index for finding an element in a JSON array 【发布时间】:2013-08-26 13:51:28 【问题描述】:

我有一张如下所示的表格:

CREATE TABLE tracks (id SERIAL, artists JSON);

INSERT INTO tracks (id, artists) 
  VALUES (1, '["name": "blink-182"]');

INSERT INTO tracks (id, artists) 
  VALUES (2, '["name": "The Dirty Heads", "name": "Louis Richards"]');

还有其他几列与此问题无关。将它们存储为 JSON 是有原因的。

我要做的是查找具有特定艺术家姓名(完全匹配)的曲目。

我正在使用这个查询:

SELECT * FROM tracks 
  WHERE 'ARTIST NAME' IN
    (SELECT value->>'name' FROM json_array_elements(artists))

例如

SELECT * FROM tracks
  WHERE 'The Dirty Heads' IN 
    (SELECT value->>'name' FROM json_array_elements(artists))

但是,这会进行全表扫描,而且速度不是很快。我尝试使用 names_as_array(artists) 函数创建 GIN 索引,并使用了 'ARTIST NAME' = ANY names_as_array(artists),但是没有使用索引,查询实际上慢得多。

【问题讨论】:

我根据这个问题提出了一个后续问题:dba.stackexchange.com/questions/71546/… 【参考方案1】:

jsonb 在 Postgres 9.4+ 中

二进制 JSON 数据类型 jsonb 在很大程度上改进了索引选项。您现在可以直接在 jsonb 数组上创建 GIN 索引:

CREATE TABLE tracks (id serial, artists jsonb);  -- !
CREATE INDEX tracks_artists_gin_idx ON tracks USING gin (artists);

不需要函数来转换数组。这将支持查询:

SELECT * FROM tracks WHERE artists @> '["name": "The Dirty Heads"]';

@>jsonb "contains" operator,可以使用GIN索引。 (不适用于json,仅适用于jsonb!)

或者你使用更专业的、非默认的 GIN 操作符类jsonb_path_ops 作为索引:

CREATE INDEX tracks_artists_gin_idx ON tracks
USING  gin (artists jsonb_path_ops);  -- !

相同的查询。

目前jsonb_path_ops 仅支持@> 运算符。但它通常更小更快。还有更多索引选项,details in the manual


如果artists 仅包含示例中显示的名称,则仅将 存储为 JSON 文本会更有效primitives 和多余的 key 可以是列名。

注意 JSON 对象和原始类型的区别:

Using indexes in json array in PostgreSQL
CREATE TABLE tracks (id serial, artistnames jsonb);
INSERT INTO tracks  VALUES (2, '["The Dirty Heads", "Louis Richards"]');

CREATE INDEX tracks_artistnames_gin_idx ON tracks USING gin (artistnames);

查询:

SELECT * FROM tracks WHERE artistnames ? 'The Dirty Heads';

? 不适用于对象 values,仅适用于 keys数组元素

或者:

CREATE INDEX tracks_artistnames_gin_idx ON tracks
USING  gin (artistnames jsonb_path_ops);

查询:

SELECT * FROM tracks WHERE artistnames @> '"The Dirty Heads"'::jsonb;

如果名称高度重复,效率会更高。

json 在 Postgres 9.3+ 中

这应该适用于IMMUTABLE function

CREATE OR REPLACE FUNCTION json2arr(_j json, _key text)
  RETURNS text[] LANGUAGE sql IMMUTABLE AS
'SELECT ARRAY(SELECT elem->>_key FROM json_array_elements(_j) elem)';

创建这个functional index

CREATE INDEX tracks_artists_gin_idx ON tracks
USING  gin (json2arr(artists, 'name'));

并使用这样的查询WHERE 子句中的表达式必须与索引中的表达式匹配:

SELECT * FROM tracks
WHERE  '"The Dirty Heads"'::text[] <@ (json2arr(artists, 'name'));

更新了 cmets 中的反馈。我们需要使用array operators来支持GIN索引。 在这种情况下为"is contained by" operator &lt;@

关于函数波动性的说明

即使json_array_elements() 不是 不是,你也可以声明你的函数IMMUTABLE。 大多数JSON 函数过去只有STABLE,而不是IMMUTABLE。 There was a discussion on the hackers list to change that. 现在大部分是IMMUTABLE。检查:

SELECT p.proname, p.provolatile
FROM   pg_proc p
JOIN   pg_namespace n ON n.oid = p.pronamespace
WHERE  n.nspname = 'pg_catalog'
AND    p.proname ~~* '%json%';

函数索引仅适用于 IMMUTABLE 函数。

【讨论】:

这不起作用,因为返回的SETOF 不能在索引中使用。删除它,我可以创建索引,但是查询计划程序不使用它。另外, json_array_elements 和 array_agg 都是IMMUTABLE @Tony:对不起,我把列名和键名混在一起了。修复并添加了更多内容。 @PyWebDesign:jsonb 包含查询通常必须与包含对象匹配相同的结构(因此在数组内搜索对象意味着您必须使用数组内的对象进行查询)。数组中的原始类型有一个特殊的例外;更多细节在这里:***.com/a/29947194/818187 @PyWebDesign:我现在看到,在一个示例中缺少数组层。固定的。该索引只会在足够大的表中使用,因此对于 Postgres 来说它比顺序扫描更便宜。 @PyWebDesign:在你的会话中运行SET enable_seqscan = off;(仅用于调试目的)***.com/questions/14554302/…。

以上是关于在 JSON 数组中查找元素的索引的主要内容,如果未能解决你的问题,请参考以下文章

如何在 int 数组中查找元素的索引?

查找某个元素在数组中对应的索引

编写在数字数组中查找元素索引的函数[重复]

FLASH AS3 二维数组如何查找某个元素的索引?

数组查找元素第一次出现的索引号

在 NumPy 数组中查找等于零的元素的索引