【问题标题】:Postgres JSONB Query and Index on Nested String ArrayPostgres JSONB 查询和索引嵌套字符串数组
【发布时间】:2023-03-05 13:32:01
【问题描述】:

我在思考如何制定查询并为以下情况提供适当的索引时遇到了一些麻烦。我有像这样用 JSON 表示的客户实体(仅保留相关属性):

{
"id": "50000",
"address": [
    {
        "line": [
            "2nd Main Street",
            "123 Harris Plaza"
        ],
        "city": "Boston",
        "state": "Massachusetts",
        "country": "US",
    },
    {
        "line": [
            "1st Av."
        ],
        "city": "Jamestown",
        "state": "Massachusetts",
        "country": "US",
    }
]

}

客户存储在以下客户表中:

CREATE TABLE Customer (
id BIGSERIAL PRIMARY KEY,
resource JSONB

);

我设法对资源列进行简单查询,例如像这样的投影查询有效(检索以“bo”开头的城市的所有小写地址行):

SELECT LOWER(jsonb_array_elements_text(jsonb_array_elements(c.resource#>'{address}') #> '{line}')) FROM Customer c, jsonb_array_elements(c.resource #> '{address}') a WHERE LOWER(a->>'city') LIKE 'bo%';

我在执行以下操作时遇到了麻烦:我的目标是查询至少有一个地址行以“12”开头的所有客户。不区分大小写是我的用例的要求。示例客户将匹配我的查询,因为第一个地址对象有一个以“12”开头的地址行。请注意,“line”是 JSON 字符串数组,而不是复杂对象。到目前为止,我能想到的最接近的事情是:

SELECT c.resource FROM Customer c, jsonb_array_elements(c.resource #> '{address}') a WHERE a->'line' ?| array['123 Harris Plaza'];

显然,这不是不区分大小写的 LIKE 查询。非常感谢有关如何制定查询和随附的 GIN 索引的任何帮助/指针。我的第一个查询已经选择了所有地址行作为文本,所以也许这可以在 GIN 索引中使用?

我正在使用 Postres 9.5,但如果这只能在更新的 Postgres 版本中实现,我很乐意升级。

【问题讨论】:

  • 您的示例是否区分大小写无关紧要,因为数字没有大小写。你能提供一个例子来证明这种区别吗?
  • 没错,这只是一个例子。案例确实对实际的真实数据很重要,主要是因为“line”数组确实包含如下非英语地址:“line”:[“Hauptstrasse 17”,“Erlenweg 82” " ]

标签: arrays postgresql indexing jsonb


【解决方案1】:

虽然 GIN 索引具有支持前缀匹配的机制,但该机制仅适用于 tsvector。 array_ops 没有连接它,json_ops 或 json_path_ops 也没有。因此,除非您想创建新的运算符类/族(或将数据规范化到单独的表中),否则您必须将数据硬塞到 tsvector 中。

这是一种粗略的方法,它没有考虑地址行可能包含文字单引号或其他有意义的字符的可能性:

create function addressline_tsvector(jsonb) returns tsvector immutable language SQL as $$ 
   select string_agg('''' || lower(value) || '''', ' ')::tsvector 
   from jsonb_array_elements($1->'address') a(a),
        jsonb_array_elements_text(a->'line') 
$$;

create index on customer using gin (addressline_tsvector(resource));

select * from customer where addressline_tsvector(resource) @@ lower('''2nd Main'':*')::tsquery;

鉴于您的示例表只有一行,除非您先set enable_seqscan = off,否则该索引可能不会实际使用。

【讨论】:

  • 这太完美了!我不知道 tsvector 类型,因此我从未想到过这种方法。我在更真实的数据集(100 万行,接近真实的数据)上尝试了您的解决方案,并且确实使用了索引。非常感谢!
猜你喜欢
  • 2023-04-07
  • 1970-01-01
  • 1970-01-01
  • 2018-02-10
  • 1970-01-01
  • 1970-01-01
  • 2018-08-27
  • 2022-01-02
  • 2017-01-28
相关资源
最近更新 更多