【问题标题】:How to change the structure of jsonb data stored in Postgres如何更改存储在 Postgres 中的 jsonb 数据的结构
【发布时间】:2021-08-13 13:36:14
【问题描述】:

我在 Postgres 中有 jsonb 数据,如下所示:-

{"DestinationLists": [{"name": "TV3/TVNZ/CHOICE", "destinations": [183, 165]}]}

我正在尝试编写脚本/查询以将 Postgres 中此 json 的结构更改为:-

{
  "DestinationLists": [
    {"name": "TV3/TVNZ/CHOICE", "destinations": [{"Id":183}{"Id":165}]}
   ]
}

有可能吗?我如何做到这一点?

编辑:我正在尝试使用新结构更新表中存在的每一行数据

这就是表格的样子:-

Id  JsonValue
1   {"DestinationLists": [{"name": "TV3/TVNZ/CHOICE", "destinations": [183, 165]}]}

【问题讨论】:

    标签: sql json postgresql


    【解决方案1】:

    您可以使用 postgresql 中的一些 json functions 来实现这一点。

    在下面使用 db-fiddle 的示例中,我包含了一些额外的测试数据。

    架构 (PostgreSQL v13)

    CREATE TABLE my_table (
      "dest" json
    );
    
    INSERT INTO my_table
      ("dest")
    VALUES
      ('{"DestinationLists": [{"name": "TV3/TVNZ/CHOICE", "destinations": [183, 165]}]}'),
      ('{"DestinationLists": [{"name": "SecondTest", "destinations": [103, 105]},{"name": "ThirdTest", "destinations": [3, 5]}]}');
    

    查询 #1

    WITH expanded_data AS (
        SELECT
            dest::text, 
            json_build_object(
                'name',
                 dl->>'name',
                 'destinations',
                 json_agg(
                   json_build_object('Id',dld::text::int)
                 )
            ) as dest_list_item
        FROM
            my_table, 
            json_array_elements(dest->'DestinationLists') dl,
            json_array_elements(dl->'destinations') dld
        GROUP BY
            dest::text,dl->>'name'
    )
    SELECT
        json_build_object(
            'DestinationLists',
            json_agg(dest_list_item)
        ) as new_dest
    FROM
        expanded_data
    GROUP BY
        dest::text;
    
    new_dest
    {"DestinationLists":[{"name":"ThirdTest","destinations":[{"Id":3},{"Id":5}]},{"name":"SecondTest","destinations":[{"Id":103},{"Id":105}]}]}
    {"DestinationLists":[{"name":"TV3/TVNZ/CHOICE","destinations":[{"Id":183},{"Id":165}]}]}

    View on DB Fiddle

    编辑 1

    为响应您的编辑,下面的代码可用作语句的更新。注意。 CTE 也可以重写为子查询。请看下面的例子:

    架构 (PostgreSQL v13)

    CREATE TABLE my_table (
      id bigserial,
      "dest" jsonb
    );
    
    INSERT INTO my_table
      ("dest")
    VALUES
      ('{"DestinationLists": [{"name": "TV3/TVNZ/CHOICE", "destinations": [183, 165]}]}'),
      ('{"DestinationLists": [{"name": "SecondTest", "destinations": [103, 105]},{"name": "ThirdTest", "destinations": [3, 5]}]}');
    

    WITH expanded_data AS (
        SELECT
            id, 
            json_build_object(
                'name',
                 dl->>'name',
                 'destinations',
                 json_agg(
                   json_build_object('Id',dld::text::int)
                 )
            ) as dest_list_item
        FROM
            my_table, 
            jsonb_array_elements(dest->'DestinationLists') dl,
            jsonb_array_elements(dl->'destinations') dld
        GROUP BY
            id,dl->>'name'
    ), 
    new_json AS (
        SELECT
            id,
            json_build_object(
                'DestinationLists',
                json_agg(dest_list_item)
            ) as new_dest
        FROM
            expanded_data
        GROUP BY
            id
    )
    UPDATE my_table
    SET dest = new_json.new_dest
    FROM new_json
    WHERE my_table.id = new_json.id;
    

    之后

    SELECT * FROM my_table;
    
    id dest
    1 {"DestinationLists":[{"name":"TV3/TVNZ/CHOICE","destinations":[{"Id":183},{"Id":165}]}]}
    2 {"DestinationLists":[{"name":"SecondTest","destinations":[{"Id":103},{"Id":105}]},{"name":"ThirdTest","destinations":[{"Id":3},{"Id":5}]}]}

    View on DB Fiddle

    编辑 2

    此编辑响应某些目的地可能没有目的地并且因此可能不会更新的边缘情况。

    为了进行测试,添加了两条额外的记录,示例记录提供了两个命名的目的地列表,但只有一个有目的地,另一个有一个命名的目的地列表但没有目的地。

    更新确保如果没有更改,即存在没有目的地的命名目的地列表,这些目的地保持不变。它通过检查命名目标列表项的数量是否与空目标列表项的数量相同来确保这一点。由于所有内容都是空的,因此它会从更新中过滤掉这条记录,并减少数据库所需的更新次数。这方面的一个例子是记录号4

    修改了初始查询以适应这些空列表,因为它们正在使用之前的方法进行过滤。

    架构 (PostgreSQL v13)

    CREATE TABLE my_table (
      id bigserial,
      "dest" jsonb
    );
    
    INSERT INTO my_table
      ("dest")
    VALUES
      ('{"DestinationLists": [{"name": "TV3/TVNZ/CHOICE", "destinations": [183, 165]}]}'),
      ('{"DestinationLists": [{"name": "SecondTest", "destinations": [103, 105]},{"name": "ThirdTest", "destinations": [3, 5]}]}'),
      ('{"DestinationLists": [{"name": "TVNZ, Mediaworks, Choice", "destinations": []}, {"name": "TVNZ, Discovery", "destinations": [165, 183, 4155]}]}'),
      ('{"DestinationLists": [{"name": "Fourth Test", "destinations": []}]}');
    

    查询 #1

    SELECT '------ BEFORE -----';
    
    ?column?
    ------ BEFORE -----

    查询 #2

    SELECT * FROM my_table;
    
    id dest
    1 {"DestinationLists":[{"name":"TV3/TVNZ/CHOICE","destinations":[183,165]}]}
    2 {"DestinationLists":[{"name":"SecondTest","destinations":[103,105]},{"name":"ThirdTest","destinations":[3,5]}]}
    3 {"DestinationLists":[{"name":"TVNZ, Mediaworks, Choice","destinations":[]},{"name":"TVNZ, Discovery","destinations":[165,183,4155]}]}
    4 {"DestinationLists":[{"name":"Fourth Test","destinations":[]}]}

    查询 #3

    WITH expanded_data AS (
        SELECT
            id, 
            CASE
                WHEN COUNT(dld)=0 THEN 1
                ELSE 0
            END as name_has_empty_list_item,
            json_build_object(
                'name',
                 dl->>'name',
                 'destinations',
                  CASE 
                      WHEN 
                           COUNT(dld)=0 
                      THEN 
                           to_json(array[]::json[])
                      ELSE
                           json_agg(
                               json_build_object('Id',dld::text::int )
                           )
                  END
            ) as dest_list_item
        FROM
            my_table, 
            jsonb_array_elements(dest->'DestinationLists') dl
        LEFT JOIN
            jsonb_array_elements(dl->'destinations') dld ON 1=1
        GROUP BY
            id,dl->>'name'
    ), 
    new_json AS (
        SELECT
            id,
            COUNT(dest_list_item) as no_list_item,
            SUM(name_has_empty_list_item) as no_empty_list_item,
            json_build_object(
                'DestinationLists',
                json_agg(dest_list_item)
            ) as new_dest
        FROM
            expanded_data
        GROUP BY
            id
        HAVING
            SUM(name_has_empty_list_item) <> COUNT(dest_list_item)
        
    )
    SELECT * FROM new_json;
    
    id no_list_item no_empty_list_item new_dest
    1 1 0 {"DestinationLists":[{"name":"TV3/TVNZ/CHOICE","destinations":[{"Id":183},{"Id":165}]}]}
    2 2 0 {"DestinationLists":[{"name":"SecondTest","destinations":[{"Id":103},{"Id":105}]},{"name":"ThirdTest","destinations":[{"Id":3},{"Id":5}]}]}
    3 2 1 {"DestinationLists":[{"name":"TVNZ, Discovery","destinations":[{"Id":165},{"Id":183},{"Id":4155}]},{"name":"TVNZ, Mediaworks, Choice","destinations":[]}]}

    查询 #4

    WITH expanded_data AS (
        SELECT
            id, 
            CASE
                WHEN COUNT(dld)=0 THEN 1
                ELSE 0
            END as name_has_empty_list_item,
            json_build_object(
                'name',
                 dl->>'name',
                 'destinations',
                  CASE 
                      WHEN 
                           COUNT(dld)=0 
                      THEN 
                           to_json(array[]::json[])
                      ELSE
                           json_agg(
                               json_build_object('Id',dld::text::int )
                           )
                  END
            ) as dest_list_item
        FROM
            my_table, 
            jsonb_array_elements(dest->'DestinationLists') dl
        LEFT JOIN
            jsonb_array_elements(dl->'destinations') dld ON 1=1
        GROUP BY
            id,dl->>'name'
    ), 
    new_json AS (
        SELECT
            id,
            json_build_object(
                'DestinationLists',
                json_agg(dest_list_item)
            ) as new_dest
        FROM
            expanded_data
        GROUP BY
            id
        HAVING
            SUM(name_has_empty_list_item) <> COUNT(dest_list_item)
    )
    UPDATE my_table
    SET dest = new_json.new_dest
    FROM new_json
    WHERE my_table.id = new_json.id;
    

    没有要显示的结果。


    查询 #5

    SELECT '------ AFTER -----';
    
    ?column?
    ------ AFTER -----

    查询 #6

    SELECT * FROM my_table;
    
    id dest
    4 {"DestinationLists":[{"name":"Fourth Test","destinations":[]}]}
    1 {"DestinationLists":[{"name":"TV3/TVNZ/CHOICE","destinations":[{"Id":183},{"Id":165}]}]}
    2 {"DestinationLists":[{"name":"SecondTest","destinations":[{"Id":103},{"Id":105}]},{"name":"ThirdTest","destinations":[{"Id":3},{"Id":5}]}]}
    3 {"DestinationLists":[{"name":"TVNZ, Discovery","destinations":[{"Id":165},{"Id":183},{"Id":4155}]},{"name":"TVNZ, Mediaworks, Choice","destinations":[]}]}

    View on DB Fiddle

    让我知道这是否适合你。

    【讨论】:

    • 嗨,非常感谢您的回答,这与我正在寻找的内容非常接近,但这是在我尝试更新行时选择的数据
    • 嗨,我已经更新了答案,并包含了一个有效的数据库小提琴来回答您更新的问题。让我知道这是否适合您。
    • 查询忽略destinations数组为空的json对象,例如:{"DestinationLists": [{"name": "TVNZ, Mediaworks, Choice", "destinations": []}, {"name": "TVNZ, Discovery", "destinations": [165, 183, 4155]}]}你认为可以添加某种检查来处理这种情况吗?非常感谢大家的帮助
    • @FakharAhmadRasul 我已经使用附加示例记录通过第二次编辑更新了答案。让我知道这是否适合您
    • 我真的非常感谢你的帮助,我以前从未使用过 Postgresql,我不得不在生产数据库上更新这些数据。非常感谢
    猜你喜欢
    • 2019-08-13
    • 2019-08-07
    • 1970-01-01
    • 2016-07-24
    • 2016-07-04
    • 2010-09-26
    • 2019-03-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多