我有一个看起来像这样的表:
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)
创建GIN索引,并使用'ARTIST NAME' = ANY names_as_array(artists)
,但是不使用索引,查询实际上要慢得多。
答案 0 :(得分:114)
jsonb
使用新的二进制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);
无需转换数组的函数。这将支持查询:
SELECT * FROM tracks WHERE artists @> '[{"name": "The Dirty Heads"}]';
@>
being the new jsonb
"contains" operator,可以使用GIN索引。 (不适用于json
类型,仅适用于jsonb
!)
或您为索引使用更专业的非默认GIN运算符类jsonb_path_ops
:
CREATE INDEX tracks_artists_gin_idx ON tracks
USING gin (artists jsonb_path_ops);
相同的查询。
目前jsonb_path_ops
仅支持@>
运营商。但它通常更小更快。还有更多索引选项,details in the manual。
如果 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;
Postgres 9.3 + 中的json
这适用于IMMUTABLE
function:
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)';
创建此functional index:
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'));
更新了评论中的反馈。我们需要使用array operators来支持GIN索引
在这种情况下"is contained by" operator <@
。
即使IMMUTABLE
不是,您也可以声明您的函数json_array_elements()
。
大多数JSON
函数过去只是STABLE
,而不是IMMUTABLE
。 There was a discussion on the hackers list to change that.现在大多数都是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
个函数。