在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索引。 (不适用于json
types,只适用于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索引仅适用于IMMUTABLE
function。