在 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 PostgreSQLCREATE 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 <@
。
关于函数波动性的说明
即使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 数组中查找元素的索引的主要内容,如果未能解决你的问题,请参考以下文章