如何使用 pl/pgsql 将具有动态元素名称的 JSON 数据转换为行?

Posted

技术标签:

【中文标题】如何使用 pl/pgsql 将具有动态元素名称的 JSON 数据转换为行?【英文标题】:How to convert JSON data with dynamic element names into rows using pl/pgsql? 【发布时间】:2018-06-13 17:10:59 【问题描述】:

我需要从 pl/pgsql 函数中的 restful 服务获取 json。 (我无法控制restful webservice。它是由其他人发布的)。我可以获得 json,但无法将其转换为行(用于插入表)。它的简化格式如下。每个 GUID 都是随机的,我以前不知道它们的内容。当我从文本(使用 ::json)转换它时,我没有收到任何错误,所以它是有效的 json。

    l_json := '"GUID-0001":"Id":"1","Field1":"aaa1","Field2":"bbb1", "GUID-0002":"Id":"2","Field1":"aaa2","Field2":"bbb2"'::json;

我尝试了几个 Postgresql json 函数,但每次都遇到不同的错误。即当我使用 json_array_elements() 函数时,我得到“错误:无法在非数组上调用 json_array_elements”。当我尝试 json_each_text() 函数时,我得到“错误:查询没有结果数据的目的地”

我需要如下结果集:

GUID     | Id | Field1 | Field2
---------+----+--------+-------
GUID-0001| 1  |   aaa1 |   bbb1
GUID-0002| 1  |   aaa2 |   bbb2

【问题讨论】:

您是否要为insert“将其转换为行”?此外,您有一个嵌套对象,因此您无法仅使用一个 pg json 函数获得所需的结果集。 我想将结果集插入到表中作为示例结果集。我愿意使用每个 json 函数来实现目标,但我仍然无法弄清楚,因为我对 postgresql 和 json 都是新手。 dbfiddle.uk/… - 这可以给你一个方向。但是,如果您对 postgresql 和 json 都很陌生,那么 IMO 会遇到更多问题。你至少需要学习一些基础知识。 @Abelisto 您的评论是最接近的解决方案。因为我还没有任何声誉,所以我无法投票给你的评论。谢谢! 【参考方案1】:

您可以使用jsonb_object_keys() 获取所有密钥,并使用它来访问 JSON 中的字段:

with data(doc) as (
  values ('"GUID-0001":"Id":"1","Field1":"aaa1","Field2":"bbb1", "GUID-0002":"Id":"2","Field1":"aaa2","Field2":"bbb2"'::jsonb)
)
select t.uid, 
       d.doc -> t.uid ->> 'Id' as id,
       d.doc -> t.uid ->> 'Field1' as column1,
       d.doc -> t.uid ->> 'Field2' as column2
from data d, jsonb_object_keys(doc) as t(uid);

返回:

uid       | id | column1 | column2
----------+----+---------+--------
GUID-0001 | 1  | aaa1    | bbb1   
GUID-0002 | 2  | aaa2    | bbb2   

您可以将其放入接受 jsonb 作为参数的函数中:

create or replace function store_json(p_doc jsonb)
  returns void
as
$$
  insert into the_table (guid, id, column1, column2)
  select t.uid, 
         (d.doc -> t.uid ->> 'Id')::int,
         d.doc -> t.uid ->> 'Field1',
         d.doc -> t.uid ->> 'Field2'
  from (select p_doc) as d(doc), 
       jsonb_object_keys(doc) as t(uid);
$$
language sql;

【讨论】:

我仍然不能投票,但这解决了我的问题。谢谢!【参考方案2】:

我认为解决这个问题的最简单方法是在插入之前稍微调整一下 json,然后使用 json_populate_recordset 将 json 转换为行。

在您的应用代码中将外部对象转换为数组,然后将相关对象内的 GUID-000x 值移动到 GUID 键下,如下所示:

[
  
    "GUID": "GUID-0001",
    "Id": "1",
    ...
  ,
  
    "GUID": "GUID-0002",
    "Id": "1",
    ...
  
  ...
]

我不知道您的应用代码使用什么语言,但我假设您可以使用某种形式的 reduce 来完成这项工作。

一旦你的数据格式正确,你就可以像这样使用json_populate_recordset

insert into your_table
    select GUID, Id, Field1, Field2
  from json_populate_recordset(null::your_table, _your_modified_json_from_above)
;

json_populate_recordset 基本上采用您的 json 并将键与your_table 上定义的列匹配,并相应地添加值。关键是您的对象键必须与列名完全匹配,并且您的值需要匹配(或能够被强制转换以匹配)在这些列上定义的数据类型。

【讨论】:

我无法控制 restful web 服务。它是第三方网络服务。 (我也将此信息添加到我的问题中)。所以我只能在下载后处理json。如果有办法改变json内容,我会做的 所以您没有调用此服务的应用程序代码? 我正在使用 postgresql 扩展 github.com/pramsey/pgsql-http 来获取 JSON。这部分工作完美。如果有办法,我只想能够使用 pl/pgsql 将这个 json 数据转换为带有行的 SELECT 语句。

以上是关于如何使用 pl/pgsql 将具有动态元素名称的 JSON 数据转换为行?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 PL/pgSQL 中的动态选择查询中使用迭代器变量?

Pl/Pgsql,将数组参数传递给 INSERT

如何在 pl/pgsql 中声明具有特定列名的行文字?

PL/pgSQL 从表中动态复制数据

在 PL/PGSQL 动态 SQL 内部函数中引用局部变量

PL/pgSQL 代码的问题