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

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

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)创build一个GIN索引,并使用'ARTIST NAME' = ANY names_as_array(artists) ,但是不使用该索引,而且查询实际上显着较慢。

jsonb在Postgres 9.4+

使用新的二进制JSON数据typesjsonb ,Postgres 9.4引入了大量改进的索引选项 。 你现在可以直接在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”运算符 ,它可以使用GIN索引。 (不适用于jsontypes,只适用于jsonb !)

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

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

同样的查询。


如果 artists只保存示例中显示的名称,那么存储较less冗余的JSON值开始的效率会更高:仅作为文本基元和冗余可以在列名中。

请注意JSON对象和基本types之间的区别:

  • 在PostgreSQL中使用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); 

查询:

 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; 

jsonb_path_ops当前仅支持对@>运算符进行索引。
有更多的索引选项, 手册中的细节

json在Postgres 9.3+

这应该与IMMUTABLE 函数 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)'; 

创build这个function索引

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

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

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

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

function波动的注意事项

即使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%'; 

function索引仅适用于IMMUTABLEfunction。