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

Index for finding an element in a JSON array

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

1
2
3
4
5
6
7
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是有原因的。

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

我正在使用此查询:

1
2
3
SELECT * FROM tracks
  WHERE 'ARTIST NAME' IN
    (SELECT value->>'name' FROM json_array_elements(artists))

例如

1
2
3
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),但是不使用索引,查询实际上要慢得多。


jsonb在Postgres 9.4+中

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

1
2
CREATE TABLE tracks (id serial, artists jsonb);
CREATE INDEX tracks_artists_gin_idx ON tracks USING gin (artists);

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

1
SELECT * FROM tracks WHERE artists @> '[{"name":"The Dirty Heads"}]';

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

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

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

相同的查询。

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

如果artists仅包含示例中显示的名称,则存储较少冗余的JSON值会更有效:只需将值作为文本基元,冗余键可以在列名中。

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

  • 在PostgreSQL中使用json数组中的索引
1
2
3
4
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);

查询:

1
SELECT * FROM tracks WHERE artistnames ? 'The Dirty Heads';

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

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

查询:

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

json在Postgres 9.3+中

这应该与IMMUTABLE函数一起使用:

1
2
3
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)';

创建此功能索引:

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

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

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

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

关于功能波动的说明

即使json_array_elements() 不是,您也可以声明您的函数IMMUTABLE
大多数json函数过去只是STABLE,而不是IMMUTABLE。讨论了黑客名单以改变这一点。现在大多数都是IMMUTABLE。检查:

1
2
3
4
5
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功能。