【问题标题】:Postgres jsonb search in array with greater operator (with jsonb_array_elements)Postgres jsonb 在具有更大运算符的数组中搜索(使用 jsonb_array_elements)
【发布时间】:2018-03-31 10:23:28
【问题描述】:

我尝试搜索解决方案,但没有找到任何适合我的案例...

这是数据库声明(简化):

CREATE TABLE documents (
    document_id int4 NOT NULL GENERATED BY DEFAULT AS IDENTITY,
    data_block jsonb NULL
);

这是一个插入的例子。

INSERT INTO documents (document_id, data_block)
VALUES(878979, 
    {"COMMONS": {"DATE": {"value": "2017-03-11"}},
     "PAYABLE_INVOICE_LINES": [
         {"AMOUNT": {"value": 52408.53}}, 
         {"AMOUNT": {"value": 654.23}}
     ]});
INSERT INTO documents (document_id, data_block)
VALUES(977656, 
    {"COMMONS": {"DATE": {"value": "2018-03-11"}},
     "PAYABLE_INVOICE_LINES": [
         {"AMOUNT": {"value": 555.10}}
     ]});

我想搜索其中一个 PAYABLE_INVOICE_LINES 的行值大于 1000.00 的所有文档

我的查询是

select *
from documents d
cross join lateral jsonb_array_elements(d.data_block -> 'PAYABLE_INVOICE_LINES') as pil 
where (pil->'AMOUNT'->>'value')::decimal >= 1000

但是,由于我想限制为 50 个文档,我必须对 document_id 进行分组并将结果限制为 50。

对于数百万个文档,这个查询非常昂贵...... 10 秒有 100 万个。

您有什么想法可以提高性能吗?

谢谢

【问题讨论】:

  • 我目前被困在 PG 9.3 上,所以还没有那种数据类型,但我曾短暂地参与过一个 PG 9.6 项目,我们将数据 blob 存储在 jsonb 字段中,您可以创建该字段中的值的索引,性能相当不错。如果您必须保持结构不变,也许这就是您应该研究的内容。

标签: postgresql jsonb


【解决方案1】:

where exists代替cross join lateral

select *
from documents d
where exists (
  select 1
  from jsonb_array_elements(d.data_block -> 'PAYABLE_INVOICE_LINES') as pil
  where (pil->'AMOUNT'->>'value')::decimal >= 1000)
limit 50;

更新

还有另一种方法,更复杂但也更有效。

创建从JSONB 数据返回最大值的函数,如下所示:

create function fn_get_max_PAYABLE_INVOICE_LINES_value(JSONB) returns decimal language sql as $$
  select max((pil->'AMOUNT'->>'value')::decimal)
  from jsonb_array_elements($1 -> 'PAYABLE_INVOICE_LINES') as pil $$

在这个函数上创建索引:

create index idx_max_PAYABLE_INVOICE_LINES_value
  on documents(fn_get_max_PAYABLE_INVOICE_LINES_value(data_block));

在查询中使用函数:

select *
from documents d
where fn_get_max_PAYABLE_INVOICE_LINES_value(data_block) > 1000
limit 50;

在这种情况下,将使用索引,并且在大量数据上查询会更快。

PS:通常limitorder by 配对。

【讨论】:

  • 没有理由相信将join 重写为exists 会做任何事情。但是在函数上创建索引应该会有所作为
  • @Andomar 也许你是对的,在我的快速测试中执行时间几乎相同:dbfiddle.uk/… 但是 IMO 使用 exists 使查询更加清晰。
  • 嗯,我只是在尝试我的数据库,现有的解决方案似乎非常有效。使用特定功能可能是更好的解决方案,但我必须为许多其他运算符(如 >=、
  • @Ryu "但我必须为许多其他操作员做这项工作" 它们上有三个函数和索引,可以让你的引擎更快:fn_get_max() returns numeric..., @ 987654335@ 和fn_get_array() returns numeric[]...; index on documents using gin(fn_get_array(data_block)); 因此,>= 运算符可以像where fn_get_max(data_block) > 1000 or array[1000] <@ fn_get_array(data_block) 那样实现
【解决方案2】:

分组和限制很容易:

select  document_id
from    documents d
cross join lateral 
        jsonb_array_elements(d.data_block -> 'PAYABLE_INVOICE_LINES') as pil 
where   (pil->'AMOUNT'->>'value')::decimal >= 1000
group by
        document_id
limit   50

如果您更频繁地查询此信息,您可以将文档列表和发票行存储在单独的表中。当您添加、修改或删除文档时,您还必须使单独的表格保持最新。但是查询常规表比查询 JSON 列要快得多。

【讨论】:

  • 当然可以这样分组。但是对数百万行进行分组非常慢,因为 PAYABLE_INVOICE_LINES 可能有数百行。
  • 而且改变结构不是一种选择,因为我们不知道。它可以从一种文档类型更改为另一种。我真的在此上下文中搜索查询的优化。
猜你喜欢
  • 2020-06-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-08-12
  • 2023-04-02
  • 2020-05-03
  • 1970-01-01
  • 2015-10-15
相关资源
最近更新 更多