【问题标题】:Postgres JSONB select json objects as columnsPostgres JSONB 选择 json 对象作为列
【发布时间】:2021-04-26 18:47:12
【问题描述】:

该表有一个 id 和 step_data 列。

id | step_data
--------------
a1 | {...}
a2 | {...}
a3 | {...}

其中step_data是如下嵌套结构,其中元数据的实际键可以在events对象中。

{
  "events": [
    {
      "timestamp": "2021-04-07T17:46:13.739Z",
      "meta": [
        {
          "key": "length",
          "value": "0.898"
        },
        {
          "key": "height",
          "value": "607023104"
        },
        {
          "key": "weight",
          "value": "33509376"
        }
      ]
    },
    {
      "timestamp": "2021-04-07T17:46:13.781Z",
      "meta": [
        {
          "key": "color",
          "value": "0.007"
        },
        {
          "key": "count",
          "value": "641511424"
        },
        {
          "key": "age",
          "value": "0"
        }
      ]
    }
  ]
}

我可以很容易地提取像length 这样的一个字段。

select cast(metadata ->> 'value' as double precision) as length,
       id
from (
         select jsonb_array_elements(jsonb_array_elements(step_data #> '{events}') #> '{meta}') metadata,
                id
         from table
     ) as parsed_keys
where metadata @> '{
  "key": "length"
}'::jsonb
id length
a1 0.898
a2 0.800

但我真正需要的是从几个已知键中提取元数据作为列,例如lengthcolor。不确定如何在使用jsonb_array_elements() 拆分数组后有效地获取另一列。

有没有一种有效的方法来做到这一点,而不必再次致电jsonb_array_elements() 并加入每个人?例如,结果集如下所示。

id length color weight
a1 0.898 0.007 33509376
a2 0.800 1.000 15812391

使用 Postgres 11.7。

【问题讨论】:

  • 如果meta 数组元素中的键不是唯一的怎么办?例如。你有多个color?像这样的东西:pastebin.com/m5Lbn3c6
  • @a_horse_with_no_name 使用 PG 11.7 顺便说一句。并且查询可以假设每个 JSON 结构只有一个键。如果有多个,那么让查询快速失败会很好。

标签: sql json postgresql crosstab


【解决方案1】:

对于 Postgres 11,我只能考虑取消嵌套两个级别,然后聚合回一个键/值对,您可以从中提取所需的键:

select t.id, 
       (x.att ->> 'length')::numeric as length,
       (x.att ->> 'color')::numeric as color,
       (x.att ->> 'weight')::numeric as weight
from the_table t
  cross join lateral (
     select jsonb_object_agg(m.item ->> 'key', m.item -> 'value') as att
     from jsonb_array_elements(t.step_data -> 'events') as e(event)
      cross join jsonb_array_elements(e.event -> 'meta') as m(item)
     where m.item ->> 'key' in ('color', 'length', 'weight')
  ) x
;  

使用 Postgres 12 你可以写得更简单一些:

select t.id, 
       jsonb_path_query_first(t.step_data, '$.events[*].meta[*] ? (@.key == "length").value') #>> '{}' as length,
       jsonb_path_query_first(t.step_data, '$.events[*].meta[*] ? (@.key == "color").value') #>> '{}' as color,
       jsonb_path_query_first(t.step_data, '$.events[*].meta[*] ? (@.key == "weight").value') #>> '{}' as weight
from the_table t
;  

【讨论】:

    【解决方案2】:

    crosstab()

    适用于任何 Postgres 版本。

    您可以将结果输入crosstab() 函数以旋转结果。您需要安装附加模块tablefunc。如果您不熟悉,请先阅读此处的基本说明:

    SELECT *
    FROM   crosstab(
       $$
       SELECT id, metadata->>'key', metadata ->>'value'
       FROM  (SELECT id, jsonb_array_elements(jsonb_array_elements(step_data -> 'events') -> 'meta') metadata FROM tbl) AS parsed_keys
       ORDER  BY 1
       $$
     , $$VALUES ('length'), ('color'), ('weight')$$
       ) AS ct (id text, length float, color float, weight float);
    

    dbfiddle here(演示全部)

    应该提供最佳性能,尤其是对于许多列。

    注意我们不需要显式转换为double precision (float)。 crosstab() 处理text 无论如何输入,结果被强制转换为列定义列表中给定的类型。

    如果其中一个键应出现多次,则 最后 行获胜。 (不会引发错误,就像您似乎更喜欢的那样。)您可以向 $1 中的查询添加确定性排序顺序,以最后​​对首选行进行排序。示例:获取每个键的最小值:

    ORDER BY 1, 2, 3 DESC
    

    带有FILTER 子句的条件聚合

    对于 Postgres 9.4 或更高版本。

    见:

    SELECT id
         , min(val) FILTER (WHERE key = 'length') AS length
         , min(val) FILTER (WHERE key = 'color') AS color
         , min(val) FILTER (WHERE key = 'weight') AS weight
    FROM (
       SELECT id, metadata->>'key' AS key, (metadata ->>'value')::float AS val
       FROM  (SELECT id, jsonb_array_elements(jsonb_array_elements(step_data -> 'events') -> 'meta') metadata FROM tbl) AS parsed_keys
       ) sub
    GROUP  BY id;
    

    在 Postgres 12 或更高版本中,我会将性能与 jsonb_path_query_first() 进行比较。 a_horse provided a solution.

    【讨论】:

    • 感谢您将这个 dbfiddle 放在一起。
    【解决方案3】:

    此查询是一个选项:

    SELECT id,
      MAX(CASE WHEN metadata->>'key' = 'length' THEN  metadata->>'value' END) AS length,
      MAX(CASE WHEN metadata->>'key' = 'color' THEN  metadata->>'value' END) AS color,
      MAX(CASE WHEN metadata->>'key' = 'weight' THEN  metadata->>'value' END) AS weight
    
    FROM (SELECT id, jsonb_array_elements(jsonb_array_elements(step_data #> '{events}') #> '{meta}') as metadata 
          FROM table t) AS aux
       
    GROUP BY id;
    

    【讨论】:

      猜你喜欢
      • 2021-05-20
      • 1970-01-01
      • 2021-02-28
      • 1970-01-01
      • 1970-01-01
      • 2020-10-13
      • 2022-01-06
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多