98

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

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))

但是,这会进行全表扫描,而且速度不是很快。我尝试使用一个函数创建一个 GIN 索引names_as_array(artists),并使用了'ARTIST NAME' = ANY names_as_array(artists),但是没有使用该索引并且查询实际上要慢得多。

4

1 回答 1

174

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“包含”运算符,可以使用 GIN 索引。(不为json,只为jsonb!)

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

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

相同的查询。

目前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);

询问:

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;

如果名称高度重复,则效率更高。

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)';

创建此功能索引

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'));

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

函数波动的注意事项

IMMUTABLE即使json_array_elements() 不是,您也可以声明您的函数。
大多数JSON功能曾经是 only 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%';

功能索引仅适用于IMMUTABLE功能。

于 2013-08-23T14:46:15.617 回答