Postgresql:如何索引对象列的 jsonb 数组

Arc*_*oob 6 postgresql performance postgresql-9.6 query-performance

我有一个表,其中有一个 jsonb 列,其中包含一个对象数组。

每一行看起来都是这样。

[{grade: 'A', subject: 'MATH'}, {grade: 'B', subject: 'PHY'}...]
Run Code Online (Sandbox Code Playgroud)

现在通过这篇文章查询它/sf/answers/2141445351/

问题来了,完成查询所有具有gradeIN (A, B, C) 的学生的计数至少需要 2.4 秒。

我想得到一些关于索引的帮助,因为我尝试过的索引没有做任何事情。

DROP INDEX idx_subjects_subject;
DROP INDEX idx_subjects_grade;
CREATE INDEX idx_subjects_subject ON results USING GIN((subjects-> 'subject'));
CREATE INDEX idx_subjects_grade ON results USING GIN((subjects-> 'grade'));
Run Code Online (Sandbox Code Playgroud)

还做了(单独):

DROP INDEX idx_subjects_standard;
CREATE INDEX idx_subjects_standard ON results USING GIN(subjects);
Run Code Online (Sandbox Code Playgroud)

我是这样问他们的。

SELECT COUNT(*)
FROM results
WHERE EXISTS 
    (
        SELECT 1 
        FROM jsonb_array_elements(subjects) AS j(data) 
        WHERE (data #>> '{subject}') LIKE '%MATH%' 
        AND 
        (data #>> '{grade}') IN ('A', 'B', 'C')
    )
AND
    "examYear" = '2010'
AND
    "examType" = 'CSEE'
;
Run Code Online (Sandbox Code Playgroud)

我也试过这样查询:

SELECT COUNT(*)
FROM results
WHERE EXISTS 
    (
    SELECT 1 
    FROM jsonb_array_elements(subjects) AS j(data) 
    WHERE data @> '{"subject": "B/MATH", "grade": "A"}'
    OR data @> '{"subject": "B/MATH", "grade": "B"}'
    OR data @> '{"subject": "B/MATH", "grade": "C"}'

    )
AND
    "examYear" = '2010'
AND
    "examType" = 'CSEE'
;
Run Code Online (Sandbox Code Playgroud)

但这给了我相反的效果(3 秒查询)。

这是我的解释分析块。

"Aggregate  (cost=1888617.19..1888617.20 rows=1 width=8) (actual time=2517.116..2517.117 rows=1 loops=1)"
"  ->  Bitmap Heap Scan on results  (cost=470767.18..1888021.96 rows=238090 width=0) (actual time=680.456..2514.633 rows=24002 loops=1)"
"        Recheck Cond: ("examYear" = '2010'::text)"
"        Rows Removed by Index Recheck: 557945"
"        Filter: (("examType" = 'CSEE'::text) AND (SubPlan 1))"
"        Rows Removed by Filter: 500054"
"        Heap Blocks: exact=40156 lossy=53452"
"        ->  Bitmap Index Scan on idx_results_subjects  (cost=0.00..470707.66 rows=528405 width=0) (actual time=672.375..672.375 rows=524056 loops=1)"
"              Index Cond: ("examYear" = '2010'::text)"
"        SubPlan 1"
"          ->  Function Scan on jsonb_array_elements j  (cost=0.00..2.13 rows=1 width=0) (actual time=0.003..0.003 rows=0 loops=458487)"
"                Filter: (((data #>> '{subject}'::text[]) ~~ '%MATH%'::text) AND ((data #>> '{grade}'::text[]) = ANY ('{A,B,C}'::text[])))"
"                Rows Removed by Filter: 8"
"Planning time: 0.126 ms"
"Execution time: 2517.145 ms"
Run Code Online (Sandbox Code Playgroud)

我在 postgresql: 9.6.1

Mad*_*ist 10

以下代码将能够在 jsonb 列上使用 GIN 索引:

SELECT COUNT(*) FROM results
WHERE subjects @> '[{"subject": "B/MATH", "grade": "A"}]'
Run Code Online (Sandbox Code Playgroud)

与您的示例不同的是,我不解压缩数组,我只是直接查询 jsonb 列。GIN 索引可用于 jsonb 列上的 @> 运算符,但不能用于任意函数。您最终使用了 @> 运算符,但不是在实际的 jsonb 列上,而是在您提取的 json 上。

在开头用通配符索引 LIKE 条件要困难得多。为此,您需要一个三元组索引,但我不知道如何将一个索引用于 jsonb 列中的数组内的数据(如果可能的话)。您应该强烈考虑以更正式的方式存储此信息,而根本不必使用 LIKE。

开头没有通配符的 LIKE'MATH%'在某些情况下可以使用 btree 索引(有关详细信息,请参阅https://www.postgresql.org/docs/9.5/static/indexes-types.html,您有为此要注意语言环境)。

如果这不是数组,而是 jsonb 列中的普通对象,则可以使用功能索引,例如

CREATE INDEX ON results((subject->>'grade'));
Run Code Online (Sandbox Code Playgroud)

这也许也可以通过 jsonb 中的数组实现,但我现在想不出一个合理的方法来做到这一点。

您当前的架构使所有内容的编写难度增加了一个数量级,并且很难甚至不可能正确利用索引。如果您有选择,请考虑将此数据存储在一个带有主题和成绩列的表中,这会使这个问题变得容易得多。