postgresql-用于构建json的查询

postgresql-用于构建json的查询,json,postgresql,jsonb,Json,Postgresql,Jsonb,正在运行:PostgreSQL 9.6.2 我将数据以键/值对的形式存储在一个表中。“键”实际上是一个json对象的路径,每个对象都是一个属性。例如,如果键是“cogs”、“props1”、“value”,那么json对象如下: { "cogs":{ "props1": { "value": 100 } } } 如果可能的话,我希望通过SQL查询以某种方式重建json对象。以下是测试数据集: drop table if exists test

正在运行:PostgreSQL 9.6.2

我将数据以键/值对的形式存储在一个表中。“键”实际上是一个json对象的路径,每个对象都是一个属性。例如,如果键是“cogs”、“props1”、“value”,那么json对象如下:

{
  "cogs":{
     "props1": {
       "value": 100    
      }
  }
}
如果可能的话,我希望通过SQL查询以某种方式重建json对象。以下是测试数据集:

drop table if exists test_table;
CREATE TABLE test_table
(
    id serial,
    file_id integer NOT NULL,
    key character varying[],
    value character varying,
    status character varying
)
WITH (
    OIDS = FALSE
)
TABLESPACE pg_default;

insert into test_table (file_id, key, value, status)
values (1, '{"cogs","description"}', 'some awesome cog', 'approved');
insert into test_table (file_id, key, value, status)
values (1, '{"cogs","display"}', 'Giant Cog', null);
insert into test_table (file_id, key, value, status)
values (1, '{"cogs","props1","value"}', '100', 'not verified');
insert into test_table (file_id, key, value, status)
values (1, '{"cogs","props1","id"}', 26, 'approved');
insert into test_table (file_id, key, value, status)
values (1, '{"cogs","props1","dimensions"}', '{"200", "300"}', null);
insert into test_table (file_id, key, value, status)
values (1, '{"cogs","props2","value"}', '200', 'not verified');
insert into test_table (file_id, key, value, status)
values (1, '{"cogs","props2","id"}', 27, 'approved');
insert into test_table (file_id, key, value, status)
values (1, '{"cogs","props2","dimensions"}', '{"700", "800"}', null);

insert into test_table (file_id, key, value, status)
values (1, '{"widgets","description"}', 'some awesome widget', 'approved');
insert into test_table (file_id, key, value, status)
values (1, '{"widgets","display"}', 'Giant Widget', null);
insert into test_table (file_id, key, value, status)
values (1, '{"widgets","props1","value"}', '100', 'not verified');
insert into test_table (file_id, key, value, status)
values (1, '{"widgets","props1","id"}', 28, 'approved');
insert into test_table (file_id, key, value, status)
values (1, '{"widgets","props1","dimensions"}', '{"200", "300"}', null);
insert into test_table (file_id, key, value, status)
values (1, '{"widgets","props2","value"}', '200', 'not verified');
insert into test_table (file_id, key, value, status)
values (1, '{"widgets","props2","id"}', 29, 'approved');
insert into test_table (file_id, key, value, status)
values (1, '{"widgets","props2","dimensions"}', '{"900", "1000"}', null);
我要查找的输出格式如下:

{
    "cogs": {
        "description": "some awesome cog",
        "display": "Giant Cog",
        "props1": {
            "value": 100,
            "id": 26,
            "dimensions": [200, 300]
        },
        "props2": {
            "value": 200,
            "id": 27,
            "dimensions": [700, 800]
        }
    },
    "widgets": {
        "description": "some awesome widget",
        "display": "Giant Widget",
        "props1": {
            "value": 100,
            "id": 28,
            "dimensions": [200, 300]
        },
        "props2": {
            "value": 200,
            "id": 29,
            "dimensions": [900, 1000]
        }
    }
}
我面临的一些问题:

  • “值”列可以容纳文本、数字和数组。无论出于何种原因,使用knex.js的服务器端代码将整数数组(即[100300])存储到postgres中,格式如下:{“100”,“300”}。我还需要确保将其提取为整数数组

  • 尽可能地使其动态化。也许是一个递归过程,以确定“关键”路径的深度。。。。而不是硬编码数组查找值

  • json_object_agg可以很好地将属性组合到一个对象中。但是,当遇到空值时,它会中断。因此,如果“key”列只有两个值(即“cogs”、“description”),并且我试图聚合一个长度为3的数组(即“cogs”、“props1”、“value”),它将中断,除非我只对长度为3的数组进行筛选

  • 保留输入的顺序@下面的klin解决方案非常棒,95%的效果都很好。但是我没有提到也保留了秩序


  • 动态解决方案需要一些工作

    首先,我们需要一个函数将文本数组和值转换为jsonb对象

    create or replace function keys_to_object(keys text[], val text)
    returns jsonb language plpgsql as $$
    declare
        i int;
        rslt jsonb = to_jsonb(val);
    begin
        for i in select generate_subscripts(keys, 1, true) loop
            rslt := jsonb_build_object(keys[i], rslt);
        end loop;
        return rslt;
    end $$;
    
    select keys_to_object(array['key', 'subkey', 'subsub'], 'value');
    
                  keys_to_object              
    ------------------------------------------
     {"key": {"subkey": {"subsub": "value"}}}
    (1 row)
    
    接下来是另一个合并jsonb对象的函数(请参阅)

    我们做到了:

    select jsonb_pretty(jsonb_merge_agg(keys_to_object(key, translate(value, '{}"', '[]'))))
    from test_table;
    
                     jsonb_pretty                 
    ----------------------------------------------
     {                                           +
         "cogs": {                               +
             "props1": {                         +
                 "id": "26",                     +
                 "value": "100",                 +
                 "dimensions": "[200, 300]"      +
             },                                  +
             "props2": {                         +
                 "id": "27",                     +
                 "value": "200",                 +
                 "dimensions": "[700, 800]"      +
             },                                  +
             "display": "Giant Cog",             +
             "description": "some awesome cog"   +
         },                                      +
         "widgets": {                            +
             "props1": {                         +
                 "id": "28",                     +
                 "value": "100",                 +
                 "dimensions": "[200, 300]"      +
             },                                  +
             "props2": {                         +
                 "id": "29",                     +
                 "value": "200",                 +
                 "dimensions": "[900, 1000]"     +
             },                                  +
             "display": "Giant Widget",          +
             "description": "some awesome widget"+
         }                                       +
     }
    (1 row)
    

    拜托,今晚我以你的名义喝了一杯凉的!令人惊讶的是,这是有效的!显然,我还有很多需要学习的地方:)在聚合期间是否可以保留行顺序?我一直在阅读自定义聚合,但仍然有点困惑,因此我在这里回答。不幸的是没有。根据JSON定义,名称/值对的集合是无序的。要存储有序的值列表,应该使用JSON数组。参见。Postgres JSONB不保证json集合中的任何顺序。但是,您可以尝试使用json而不是JSONB。在整个脚本中将
    jsonb
    替换为
    json
    ,并删除
    jsonb_pretty()
    (此函数没有json等效函数)。对象的顺序应该保持在表中,但这是json类型的未记录功能。进行了这些更改,可以确认它确实保留了顺序!太好了。读了很多,JSON类型的性能似乎比JSONB差。。。然而,在我的情况下,这是不明显的。谢谢你的帮助!
    create aggregate jsonb_merge_agg(jsonb)
    (
        sfunc = jsonb_merge,
        stype = jsonb
    );
    
    select jsonb_pretty(jsonb_merge_agg(keys_to_object(key, translate(value, '{}"', '[]'))))
    from test_table;
    
                     jsonb_pretty                 
    ----------------------------------------------
     {                                           +
         "cogs": {                               +
             "props1": {                         +
                 "id": "26",                     +
                 "value": "100",                 +
                 "dimensions": "[200, 300]"      +
             },                                  +
             "props2": {                         +
                 "id": "27",                     +
                 "value": "200",                 +
                 "dimensions": "[700, 800]"      +
             },                                  +
             "display": "Giant Cog",             +
             "description": "some awesome cog"   +
         },                                      +
         "widgets": {                            +
             "props1": {                         +
                 "id": "28",                     +
                 "value": "100",                 +
                 "dimensions": "[200, 300]"      +
             },                                  +
             "props2": {                         +
                 "id": "29",                     +
                 "value": "200",                 +
                 "dimensions": "[900, 1000]"     +
             },                                  +
             "display": "Giant Widget",          +
             "description": "some awesome widget"+
         }                                       +
     }
    (1 row)