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

Jef*_*ffS 74 sql postgresql indexing json jsonb

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

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"}]');
Run Code Online (Sandbox Code Playgroud)

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

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

我正在使用此查询:

SELECT * FROM tracks 
  WHERE 'ARTIST NAME' IN
    (SELECT value->>'name' FROM json_array_elements(artists))
Run Code Online (Sandbox Code Playgroud)

例如

SELECT * FROM tracks
  WHERE 'The Dirty Heads' IN 
    (SELECT value->>'name' FROM json_array_elements(artists))
Run Code Online (Sandbox Code Playgroud)

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

Erw*_*ter 122

jsonb 在Postgres 9.4+

使用新的二进制JSON数据类型jsonb,Postgres 9.4引入了大量改进的索引选项.您现在可以jsonb直接在数组上拥有GIN索引:

CREATE TABLE tracks (id serial, artists jsonb);
CREATE INDEX tracks_artists_gin_idx ON tracks USING gin (artists);
Run Code Online (Sandbox Code Playgroud)

无需转换数组的函数.这将支持查询:

SELECT * FROM tracks WHERE artists @> '[{"name": "The Dirty Heads"}]';
Run Code Online (Sandbox Code Playgroud)

@>是新的jsonb"包含"运算符,可以使用GIN索引.(不是类型json,只jsonb!)

或者jsonb_path_ops为索引使用更专业的非默认GIN运算符类:

CREATE INDEX tracks_artists_gin_idx ON tracks
USING  gin (artists jsonb_path_ops);
Run Code Online (Sandbox Code Playgroud)

相同的查询.

目前jsonb_path_ops仅支持@>运营商.但它通常更小更快.手册中有更多索引选项和详细信息.


如果 artists只保存示例中显示的名称,那么存储较少冗余的JSON值会更有效:只需将作为文本基元,冗余可以在列名中.

注意JSON对象和基本类型之间的区别:

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);
Run Code Online (Sandbox Code Playgroud)

查询:

SELECT * FROM tracks WHERE artistnames ? 'The Dirty Heads';
Run Code Online (Sandbox Code Playgroud)

?不适用于对象,只适用于数组元素.
或者(如果经常重复名称,效率更高):

CREATE INDEX tracks_artistnames_gin_idx ON tracks
USING  gin (artistnames jsonb_path_ops);
Run Code Online (Sandbox Code Playgroud)

查询:

SELECT * FROM tracks WHERE artistnames @> '"The Dirty Heads"'::jsonb;
Run Code Online (Sandbox Code Playgroud)

json 在Postgres 9.3+

这应该与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)';
Run Code Online (Sandbox Code Playgroud)

创建此功能索引:

CREATE INDEX tracks_artists_gin_idx ON tracks
USING  gin (json2arr(artists, 'name'));
Run Code Online (Sandbox Code Playgroud)

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

SELECT * FROM tracks
WHERE  '{"The Dirty Heads"}'::text[] <@ (json2arr(artists, 'name'));
Run Code Online (Sandbox Code Playgroud)

更新了评论中的反馈.我们需要使用数组运算符来支持GIN索引.
"包含由"运营商<@在这种情况下.

关于功能波动的说明

IMMUTABLE即使json_array_elements() 不是,也可以声明你的功能.
大多数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%';
Run Code Online (Sandbox Code Playgroud)

功能索引仅适用于IMMUTABLE函数.

  • @PyWebDesign:我现在看到,在一个例子中缺少数组层.固定.索引只会在一个足够大的表中使用,以便Postgres比顺序扫描更便宜. (3认同)
  • 这不起作用,因为返回`SETOF`不能在索引中使用.删除它,我可以创建索引,但查询规划器不使用它.此外,json_array_elements和array_agg都是`IMMUTABLE` (2认同)
  • @Tony:对不起,我正在混合列名和密钥名称.固定并添加更多. (2认同)
  • @PyWebDesign:在你的会话中运行`SET enable_seqscan = off;`(仅用于调试目的)http://stackoverflow.com/questions/14554302/postgres-query-optimization-forcing-a-index-scan/14555618#14555618. (2认同)