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

110

我有一个看起来像这样的表格:

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),但是索引没有被使用,查询实际上变得更慢了。


我基于这个问题提出了一个后续问题:http://dba.stackexchange.com/questions/71546/index-for-finding-an-element-in-a-json-array-in-postgresql-with-multiple-json-f - Ken Li
1个回答

198

Postgres 9.4+ 中的 jsonb

二进制 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 "包含"运算符,可以使用GIN索引。(仅适用于jsonb,不适用于json!) 或者您可以使用更专业的非默认GIN运算符类jsonb_path_ops进行索引:
CREATE INDEX tracks_artists_gin_idx ON tracks
USING  gin (artists jsonb_path_ops);  -- !

同一个查询。

目前jsonb_path_ops仅支持@>运算符。但它通常更小、更快。有更多的索引选项,详见手册


如果列 artists 仅包含如示例中所示的名称,那么将值仅存储为JSON文本原语会更有效,并且冗余的可以是列名。
请注意JSON对象和基本类型之间的区别:
CREATE TABLE tracks (id serial, <b>artistnames</b> 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';

?不能用于对象的值,只能用于键和数组元素。

或者:

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

查询:

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

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

Postgres 9.3+中的json

这应该可以使用一个IMMUTABLE 函数

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'));

根据评论中的反馈进行更新。我们需要使用数组运算符来支持GIN索引。
在这种情况下,使用"包含于"运算符<@

关于函数不稳定性的注释

即使json_array_elements()不是不再是,您也可以将函数声明为IMMUTABLE
大多数JSON函数以前只有STABLE而不是IMMUTABLE在黑客列表上进行了讨论以改变这一点。现在大多数都是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函数。


2
这不起作用,因为返回SETOF不能在索引中使用。去掉它后,我可以创建索引,但查询规划器不使用它。此外,json_array_elements和array_agg都是“IMMUTABLE”。 - JeffS
2
@Tony:抱歉,我混淆了列名和键名。已修复并添加更多内容。 - Erwin Brandstetter
1
@PyWebDesign:jsonb包含查询通常必须与包含对象具有相同的结构匹配(因此在数组中搜索对象意味着您必须使用数组中的对象进行查询)。对于数组中的基本类型,有一个特殊的例外;更多详细信息请参见:https://dev59.com/eIrda4cB1Zd3GeqPKUl3#29947194 - potatosalad
3
我明白了,一个示例缺少数组层次结构,已经修复。索引仅在表格足够大以至于对Postgres来说比顺序扫描更便宜时才会使用。 - Erwin Brandstetter
3
在调试时运行以下命令 SET enable_seqscan = off;(仅限调试目的),参考链接为:https://dev59.com/BGUq5IYBdhLWcg3wXvWa#14555618。 - Erwin Brandstetter
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接