【问题标题】:Postgres 9.4 jsonb array as tablePostgres 9.4 jsonb 数组作为表
【发布时间】:2015-08-29 21:50:56
【问题描述】:

我有一个 json 数组,其中包含大约 1000 个结构元素“oid:aaa, instance:bbb, value:ccc”。

{"_id": 37637070
, "data": [{"oid": "11.5.15.1.4", "value": "1", "instance": "1.1.4"}
         , {"oid": "11.5.15.1.9", "value": "17", "instance": "1.1.4"}
         , {"oid": "12.5.15.1.5", "value": "0.0.0.0", "instance": "0"}]}

oidinstance 在每个 json 数组中都是唯一的。如果我可以选择更改结构,我会将格式更改为 key:value:

{"11.5.15.1.4-1.1.4":"1", "11.5.15.1.9-1.1.4": "17", "12.5.15.1.5-0": "0.0.0.0"}

但是,如果我需要保留旧结构

  1. 从数组中获取特定oid 的最快方法是什么?

  2. 获取包含oidinstancevalue 3 列的表的最快方法是什么。或者甚至更好的是使用 oid+instance 作为列标题的数据透视表。

对于 2。我尝试了以下方法,但在大桌子上速度很慢:

select *
from (
   select a->>'oid' oid, a->>'instance' instance, a->>'value' value1, id
   from (
      select jsonb_array_elements(config#>'{data}')  a, id
      from configuration
      ) b
   ) c
where  oid = '1.3.6.1.4.1.7352.3.10.2.5.35.3' and instance = '0' and value1 <> '1';

【问题讨论】:

  • 您的查询可以像select a-&gt;&gt;'oid' oid, a-&gt;&gt;'instance' instance, a-&gt;&gt;'value' value1, id from ( select jsonb_array_elements(config#&gt;'{data}') a, id from configuration ) b where a-&gt;&gt;'instance' = '1.1.4' and a-&gt;&gt;'oid' = '11.5.15.1.4' and a-&gt;&gt;'value'&lt;&gt;'2'; 一样简单得多,但 PostgreSQL 将您的查询和此查询优化为同一事物。
  • 虽然如果在最里面的查询中您还使用config@&gt;'{"data":[{"oid":"11.5.15.1.4","instance":"0"}]}' 限制了结果行,它可能会更快,但不需要从jsonb_array_elements 中检查那么多行。跨度>
  • 你的表的定义应该在问题中。在 psql 中使用 \d configuration 会得到什么。或有效的CREATE TABLE 脚本。

标签: sql json postgresql indexing jsonb


【解决方案1】:

查询

您的表定义丢失。假设:

CREATE TABLE configuration (
  config_id serial PRIMARY KEY
, config jsonb NOT NULL
);

要查找给定oidinstancevalue 及其所在行:

SELECT c.config_id, d->>'value' AS value
FROM   configuration c
     , jsonb_array_elements(config->'data') d  -- default col name is "value"
WHERE  d->>'oid'      = '1.3.6.1.4.1.7352.3.10.2.5.35.3'
AND    d->>'instance' = '0'
AND    d->>'value'   <> '1'

这是一个隐含的LATERAL 加入。比较:

2) 获取包含oidinstancevalue. 3 列的表的最快方法是什么

我想使用jsonb_populate_recordset(),那么你可以在表定义中提供数据类型。假设所有人为text

CREATE TEMP TABLE data_pattern (oid text, value text, instance text);

也可以是持久化(非临时)表。这仅适用于当前会话。那么:

SELECT c.config_id, d.*
FROM   configuration c
     , jsonb_populate_recordset(NULL::data_pattern, c.config->'data') d

就是这样。重写了第一个查询:

SELECT c.config_id, d.*
FROM   configuration c
     , jsonb_populate_recordset(NULL::data_pattern, c.config->'data') d
WHERE  d.oid      = '1.3.6.1.4.1.7352.3.10.2.5.35.3'
AND    d.instance = '0'
AND    d.value   <> '1';

但这比第一个查询。更大表的性能关键是索引支持:

索引

您可以轻松索引标准化(翻译)表或您在问题中提出的替代布局。索引您的当前布局并不那么明显,但也是可能的。为了获得最佳性能,我建议在 data 键上使用 jsonb_path_ops 运算符类的功能索引。 Per documentation:

jsonb_opsjsonb_path_ops GIN 之间的技术差异 index 是前者为每个键创建独立的索引项 和数据中的值,而后者仅创建索引项 数据中的每个值。

应该会为性能创造奇迹

CREATE INDEX configuration_my_idx ON configuration
USING gin ((config->'data') jsonb_path_ops);

人们可能认为只有完全匹配的 JSON 数组元素才会起作用,例如:

SELECT * FROM configuration
WHERE  (config->'data') @> '[{"oid": "1.3.6.1.4.1.7352.3.10.2.5.35.3"
                            , "instance": "0", "value": "1234"}]';

注意所提供值的 JSON 数组表示法(封闭[]),这是必需的。

但是具有键子集的数组元素也可以:

SELECT * FROM configuration
WHERE  (config->'data') @> '[{"oid": "1.3.6.1.4.1.7352.3.10.2.5.35.3"
                            , "instance": "0"}]'

困难的部分是合并您看似毫无疑问的添加谓词value &lt;&gt; '1'。必须注意将所有谓词应用于 same 数组元素。您可以将其与第一个查询结合使用:

SELECT c.*, d->>'value' AS value
FROM   configuration c
     , jsonb_array_elements(config->'data') d
WHERE  (config->'data') @> '[{"oid": "1.3.6.1.4.1.7352.3.10.2.5.35.3", "instance": "0"}]'
AND    d->>'oid'      = '1.3.6.1.4.1.7352.3.10.2.5.35.3'  -- must be repeated
AND    d->>'instance' = '0'                               -- must be repeated
AND    d->>'value'   <> '1'                               -- here we can rule out

瞧。

特殊索引

如果您的表很大,则索引大小可能是一个决定因素。您可以将此特殊解决方案的性能与功能索引进行比较:

此函数从给定的jsonb 值中提取 oid-instance 组合的 Postgres 数组:

CREATE OR REPLACE FUNCTION f_config_json2arr(_j jsonb)
  RETURNS text[] LANGUAGE sql IMMUTABLE AS
$func$
SELECT ARRAY(
   SELECT (elem->>'oid') || '-' || (elem->>'instance')
   FROM   jsonb_array_elements(_j) elem
   )
$func$

我们可以在此基础上建立一个功能索引:

CREATE INDEX configuration_conrfig_special_idx ON configuration
USING  gin (f_config_json2arr(config->'data'));

并以此为基础进行查询:

SELECT * FROM configuration
WHERE  f_config_json2arr(config->'data') @> '{1.3.6.1.4.1.7352.3.10.2.5.35.3-0}'::text[]

这个想法是索引应该小得多,因为它只存储没有键的组合值。 array containment operator @&gt; 本身的性能应该类似于 jsonb containment operator @&gt;。我不认为会有很大的不同,但我会很感兴趣哪个更快。

类似于此相关答案中的第一个解决方案(但更专业):

旁白:

  • 我不会使用 oid 作为列名,因为它也在 Postgres 中用于内部用途。
  • 如果可能,我会使用没有 JSON 的普通规范化表。

【讨论】:

  • 感谢您提供了一个很好的 jsonb_populate_recordset 用法示例。我很好奇性能优势。也许 OP 会分享他的测试结果。
  • 由于某种原因,建议的查询大约需要 20 秒,而以下查询需要 5 秒 - SELECT obj->>'oid', obj->>'value' FROM configuration r, jsonb_array_elements(r.config ->'data') obj WHERE obj->>'oid' = '3.10.2.5.35.3'
  • 非常感谢!一旦我添加了索引,性能就非常好。从几秒钟下降到不到一秒钟。我可以在条件 (config->'data') @> '[{"oid": "1.3.6.1.4.1.7352.3.10.2.5.35.3", "instance": "0"}]' 几个元素之间有 OR?
  • @user1409708:我建议你开始一个新问题。您始终可以链接到此以获取上下文。顺便说一句,您刚刚尝试了jsonb_path_ops 索引还是功能索引?
猜你喜欢
  • 1970-01-01
  • 2016-05-02
  • 2018-02-10
  • 1970-01-01
  • 1970-01-01
  • 2017-05-02
  • 2015-08-20
  • 1970-01-01
  • 2021-03-19
相关资源
最近更新 更多