【问题标题】:Postgres Return jsonb values as jsonb array?Postgres将json值作为json数组返回?
【发布时间】:2019-01-27 03:47:41
【问题描述】:

tl;dr -- 有没有办法从 postgres 中的 jsonb 对象获取值作为 jsonb_array?


尝试在 postgres 中使用递归 cte 来展平任意深度的树结构,如下所示:

{
  "type": "foo",
  "properties": {...},
  "children": [
    "type": "bar",
    "properties": {...},
    "children": [
      "type": "multivariate",
      "variants": {
        "arbitrary-name": {
          properties: {...},
          children: [...],
        },
        "some-other-name": {
          properties: {...},
          children: [...],
        },
        "another": {
          properties: {...},
          children: [...],
        }
      }
    ]
  ]
}

通常关注this post,但是我一直在处理type: "multivariate" 节点,我真正想要的基本上是jsonb_agg(jsonb_object_values(json_object -> 'variants'))

更新:

抱歉,我显然应该包含我尝试过的查询:

WITH RECURSIVE tree_nodes (id, json_element) AS (
  -- non recursive term
  SELECT
    id, node
  FROM trees

  UNION

  -- recursive term
  SELECT
    id,
    CASE
    WHEN jsonb_typeof(json_element) = 'array'
      THEN jsonb_array_elements(json_element)
    WHEN jsonb_exists(json_element, 'children')
      THEN jsonb_array_elements(json_element -> 'children')
    WHEN jsonb_exists(json_element, 'variants')
      THEN (select jsonb_agg(element.value) from jsonb_each(json_element -> 'variants') as element)
    END AS json_element
  FROM
    tree_nodes
  WHERE
    jsonb_typeof(json_element) = 'array' OR jsonb_typeof(json_element) = 'object'
)

select * from tree_nodes;

架构只是一个 id 和一个 jsonb node

此查询给出错误:

ERROR:  set-returning functions are not allowed in CASE
LINE 16:       THEN jsonb_array_elements(json_element -> 'children')
                    ^
HINT:  You might be able to move the set-returning function into a LATERAL FROM item.

我只想要Object.values(json_element -> 'variants') ????

更新 2:

再读一遍后,我意识到这是一个问题,因为我使用的是最新版本的 PostgreSQL (10.3),它显然不再允许从 CASE 语句返回一个集合,这有点像让这种树扁平化方法能够正常工作。在 PostgreSQL 的最新版本中可能有一些方法可以实现相同的目标,但我不知道该怎么做。

【问题讨论】:

  • 如果可以的话,发布一些您尝试过的架构和查询。
  • 你想要一个数组中的'variants'的三个子元素?还是想要数组中的“属性”/“子项”对?
  • 天哪,现在我在吃完晚饭后再次查看它,我意识到不是variants 导致了问题,而是children 的数组。我列出的文章中的示例在 Postgres 10 中根本不可能吗??
  • @S-Man 我希望节点展平以便返回具有列 tree_idnode 列(节点是具有 typepropertieschildren 属性的 jsonb 对象)
  • @Steve 请提供预期输出的样本。应该展平哪个部分以及变体会发生什么情况?

标签: postgresql recursion


【解决方案1】:

db<>fiddle

用更多的子元素和更深的结构(更多的嵌套元素)扩展了测试数据:

{
    "type": "foo", 
    "children": [
        {
            "type" : "bar1", 
            "children" : [{
                "type" : "blubb",
                "children" : [{
                    "type" : "multivariate",
                    "variants" : {
                        "blubb_variant1": {
                            "properties" : {
                                "blubb_v1_a" : 100
                            },
                            "children" : ["z", "y"]
                        },
                        "blubb_variant2": {
                            "properties" : {
                                "blubb_v2_a" : 300,
                                "blubb_v2_b" : 4200
                            },
                            "children" : []
                        }
                    }
                }]
            }]
        },
        {
            "type" : "bar2", 
            "children" : [{
                "type" : "multivariate",
                "variants" : {
                    "multivariate_variant1": {
                        "properties" : {
                            "multivariate_v1_a" : 1,
                            "multivariate_v1_b" : 2
                        },
                        "children" : [1,2,3]
                    },
                    "multivariate_variant2": {
                        "properties" : {
                            "multivariate_v2_a" : 3,
                            "multivariate_v2_b" : 42,
                            "multivariate_v2_d" : "fgh"
                        },
                        "children" : [4,5,6]
                    },
                    "multivariate_variant3": {
                        "properties" : {
                            "multivariate_v3_a" : "abc",
                            "multivariate_v3_b" : "def"
                        },
                        "children" : [7,8,9]
                    }
                }
            },
            {
                "type" : "blah",
                "variants" : {
                    "blah_variant1": {
                        "properties" : {
                            "blah_v1_a" : 1,
                            "blah_v1_b" : 2
                        },
                        "children" : [{
                            "type" : "blah_sub1",
                            "variants" : {
                                "blah_sub1_variant1" : {
                                    "properties" : {
                                        "blah_s1_v1_a" : 12345,
                                        "children" : ["a",1, "bn"]
                                    }
                                }
                            }
                        }]
                    },
                    "blah_variant2": {
                        "properties" : {
                            "blah_v2_a" : 3,
                            "blah_v2_b" : 42,
                            "blah_v2_c" : "fgh"
                        },
                        "children" : [4,5,6]
                    }
                }
            }]
        }
    ]
}

结果:

variants                 json                                                                                                                                                                                            
-----------------------  ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------  
"multivariate_variant1"  {"children": [1, 2, 3], "properties": {"multivariate_v1_a": 1, "multivariate_v1_b": 2}}                                                                                                         
"multivariate_variant2"  {"children": [4, 5, 6], "properties": {"multivariate_v2_a": 3, "multivariate_v2_b": 42, "multivariate_v2_d": "fgh"}}                                                                            
"multivariate_variant3"  {"children": [7, 8, 9], "properties": {"multivariate_v3_a": "abc", "multivariate_v3_b": "def"}}                                                                                                 
"blah_variant1"          {"children": [{"type": "blah_sub1", "variants": {"blah_sub1_variant1": {"properties": {"children": ["a", 1, "bn"], "blah_s1_v1_a": 12345}}}}], "properties": {"blah_v1_a": 1, "blah_v1_b": 2}}  
"blah_variant2"          {"children": [4, 5, 6], "properties": {"blah_v2_a": 3, "blah_v2_b": 42, "blah_v2_c": "fgh"}}                                                                                                    
"blubb_variant1"         {"children": ["z", "y"], "properties": {"blubb_v1_a": 100}}                                                                                                                                     
"blubb_variant2"         {"children": [], "properties": {"blubb_v2_a": 300, "blubb_v2_b": 4200}}                                                                                                                         
"blah_sub1_variant1"     {"properties": {"children": ["a", 1, "bn"], "blah_s1_v1_a": 12345}}   

查询:

WITH RECURSIVE json_cte(variants, json) AS (
    SELECT NULL::jsonb, json FROM (
        SELECT '{/*FOR TEST DATA SEE ABOVE*/}'::jsonb as json
    )s
    
    UNION
    
    SELECT  
        row_to_json(v)::jsonb -> 'key',                                -- D        
        CASE WHEN v IS NOT NULL THEN row_to_json(v)::jsonb -> 'value' ELSE c END  -- C
    FROM json_cte
         LEFT JOIN LATERAL jsonb_array_elements(json -> 'children') as c ON TRUE  -- A
         LEFT JOIN LATERAL jsonb_each(json -> 'variants') as v ON TRUE -- B
)
SELECT * FROM json_cte WHERE variants IS NOT NULL

WITH RECURSIVE 结构以递归方式检查元素。第一个UNION 部分是起点。第二部分是递归部分,最后一个计算用于下一步。

A:如果在当前 JSON 中存在 children 元素,则所有元素将扩展为每个子元素一行

B:如果当前 JSON 中有一个元素 variants,所有元素都将展开为一条记录。请注意,在示例中,一个 JSON 元素可以包含 variantschildren 元素。

C:如果有变体元素,那么扩展的记录将被转换回 json。生成的结构是{"key" : "name_of_variant", "value" : "json_of_variant"}values 将是下一次递归的 JSON(variants 的 JSON 可以有自己的 children 元素。这就是它起作用的原因)。否则扩展的children 元素将是下一个数据

D:如果有 variants 元素,则打印 key

【讨论】:

    【解决方案2】:

    FROM 子句中使用jsonb_each()SELECT 中的jsonb_agg(&lt;jsonb_each_alias&gt;.value),例如:

    select
        id,
        jsonb_agg(child.value)
    from
        (values
          (101, '{"child":{"a":1,"b":2}}'::jsonb),
          (102, '{"child":{"c":3,"d":4,"e":5}}'::jsonb
        )) as t(id, json_object), -- example table, replace values block with actual tablespec
        jsonb_each(t.json_object->'child') as child
    group by t.id
    

    如果您需要在jsonb_each 之前迭代更高级别的数组,您始终可以在FROM 中链接其他返回setof jsonb(例如jsonb_array_elements)的jsonb 函数;例如:

    select
        id,
        jsonb_agg(sets.value)
    from
        (values
          (101, '{"children":[{"a_key":{"a":1}},{"a_key":{"b":2}}]}'::jsonb),
          (102, '{"children":[{"a_key":{"c":3,"d":4,"e":5}},{"a_key":{"f":6}}]}'::jsonb
        )) as t(id, json_object), -- example table, replace values block with actual tablespec
        jsonb_array_elements(t.json_object->'children') elem,
        jsonb_each(elem->'a_key') as sets
    group by t.id;
    

    更新答案

    回答您关于需要遍历每个树节点的'children' 并提取'variants'的评论和问题编辑;我将通过将 CTE 分成多个阶段来实现这一点:

    with recursive
      -- Constant table for demonstration purposes only; remove this and replace below references to "objects" with table name
      objects(id, object) as (values
        (101, '{"children":[{"children":[{"variants":{"aa":11}},{"variants":{"ab":12}}],"variants":{"a":1}},{"variants":{"b":2}}]}'::jsonb),
        (102, '{"children":[{"children":[{"variants":{"cc":33,"cd":34,"ce":35}},{"variants":{"f":36}}],"variants":{"c":3,"d":4,"e":5}},{"variants":{"f":6}}]}'::jsonb)
      ),
      tree_nodes as ( -- Flatten the tree by walking all 'children' and creating a separate record for each root
        -- non-recursive term: get root element
        select
          o.id, o.object as value
        from
          objects o
        union all
        -- recursive term - get JSON object node for each child
        select
          n.id,
          e.value
        from
          tree_nodes n,
          jsonb_array_elements(n.value->'children') e
        where
          jsonb_typeof(n.value->'children') = 'array'
      ),
      variants as (
        select
          n.id,
          v.value
        from
          tree_nodes n,
          jsonb_each(n.value->'variants') v -- expand variants
        where
          jsonb_typeof(n.value->'variants') = 'object'
      )
    select
      id,
      jsonb_agg(value)
    from
      variants
    group by
      id
    ;
    

    这种将查询分解为操作“管道”的能力是我对 CTE 最喜欢的事情之一 - 它使查询更易于理解、维护和调试。

    【讨论】:

    • 这真的很酷!有点让我大吃一惊……我没有意识到你可以在FROM 子句中链接jsonb 这样的函数。但是我不太明白如何将其应用于我的问题。我的 json 中的对象有一个 children 数组或 variants 哈希中的命名子项。我想我有点想做(伪代码)SELECT id, (json.children || json.variants.values)
    猜你喜欢
    • 2017-01-20
    • 2014-12-16
    • 2016-11-22
    • 1970-01-01
    • 1970-01-01
    • 2017-03-09
    • 2018-05-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多