用于在 JSON 数组中查找属性组合的索引

Ken*_* Li 5 postgresql index array json

基于这个关于 SO 的相关问题

如果您只想要简单的匹配,这很好用。假设在 JSON 数据中tracks同时具有ArtistTitle字段。所以我们有类似的东西:

INSERT INTO tracks (id, data)
VALUES (1, '[{"artist": "Simple Plan", "title": "Welcome to My Life"}]');
Run Code Online (Sandbox Code Playgroud)

我们像在原始问题中一样创建索引:

CREATE INDEX tracks_artists_gin_idx ON tracks
USING GIN (json2arr(data, 'artist'));

CREATE INDEX tracks_title_gin_idx ON tracks
USING GIN (json2arr(data, 'title'));
Run Code Online (Sandbox Code Playgroud)

所以现在我们有两个字段要匹配。如您所见,如果我们执行以下原始查询(经过非常简单的修改):

SELECT *
FROM   tracks
WHERE  '{"ARTIST NAME"}'::text[] <@ (json2arr(data, 'artist'))
AND    '{"TITLE"}'::text[]       <@ (json2arr(data, 'title'))
Run Code Online (Sandbox Code Playgroud)

这将给出错误的答案,因为 JSON 数组中的艺术家和标题数组的索引不必匹配此查询以匹配 JSON 中的某些内容。执行此查询的正确方法是什么,以便我们可以获得所需的精确匹配?是否json2arr()需要改变?

编辑:为什么这是错误的

假设我们的表有如下记录:

INSERT INTO tracks (id, data)  VALUES
  (1, '[{"artist": "Simple Plan",    "title": "Welcome to My Life"}]')
, (2, '[{"artist": "Another Artist", "title": "Welcome to My Life"},
        {"artist": "Simple Plan",    "title": "Perfect"}]');
Run Code Online (Sandbox Code Playgroud)

如果您查询如下:

SELECT *
FROM   tracks
WHERE  '{"Simple Plan"}'::text[]        <@ (json2arr(data, 'artist'))
AND    '{"Welcome to my Life"}'::text[] <@ (json2arr(data, 'title'))
Run Code Online (Sandbox Code Playgroud)

两条记录都会匹配(记录 1 和 2),即使您真的只想要第一条记录。

Erw*_*ter 4

为什么?

明确要求artist和必须在 JSON 数组的同一title元素中匹配,这一要求不会反映在您的查询中,该查询会查找至少一个元素与 匹配且另一个元素(可能是不同的元素)与 . 匹配的所有行。artisttitle

第一种情况的示例数据不确定,因为对于单个数组元素,查询不能以这种方式失败。不过,你的第二个例子很好地说明了这种情况。

Postgres 9.3 中的解决方案

有多种方法可以解决这个问题。一种方法是将每个 json 数组元素转换为由和组成的复合类型SQL 数组,并将整个类型匹配为一个。artisttitle

另一种方法是保留像现在这样的索引(或者甚至是跨越两个表达式的单个组合:(json2arr(data, 'artist'), json2arr(data, 'title'))。您当前的查询标识可能的data匹配项。为所有已识别的候选者取消嵌套 json 数组,并检查两者artist和是否在同一元素title上匹配。可能或可能效率不够。

jsonb在 Postgres 9.4 中更好地使用

我不会详细介绍/pg 9.3,因为具有高级索引功能的Postgres 9.4json的发布基本上解决了这个问题。通过列上的本机 GIN 索引,可以更简单、更高效地实现这一点。有多种选择。针对当前案例进行优化:jsonbjsonb

CREATE TABLE tracks (id serial, data jsonb);
Run Code Online (Sandbox Code Playgroud)
INSERT INTO tracks (id, data)  VALUES
  (1, '[{"artist": "Simple Plan",    "title": "Welcome to My Life"}]')
, (2, '[{"artist": "Another Artist", "title": "Welcome to My Life"},
        {"artist": "Simple Plan",    "title": "Perfect"}]');
Run Code Online (Sandbox Code Playgroud)

指数:

CREATE INDEX tracks_data_gin_idx ON tracks USING gin (data jsonb_path_ops);
Run Code Online (Sandbox Code Playgroud)

询问:

SELECT * FROM tracks
WHERE  data @> '[{"artist": "Simple Plan", "title": "Welcome to My Life"}]';
Run Code Online (Sandbox Code Playgroud)

JSON 值中的属性序列和无关紧要的空格对于jsonb. 我将 jsonb 的详细信息添加到 SO 上引用的答案中: