如何使用pl/pgsql将具有动态元素名称的JSON数据转换为行?
How to convert JSON data with dynamic element names into rows using pl/pgsql?
我需要在 pl/pgsql 函数中从 restful 服务获取 json。 (我无法控制 restful 网络服务。它是由其他人发布的)。我可以获得 json,但无法将其转换为行(用于插入到 table)。它的简化格式如下。每个 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() 函数时,我得到了 "ERROR: cannot call json_array_elements on a non-array"。当我尝试 json_each_text() 函数时,我得到了 "ERROR: query has no destination for result data"
我需要如下结果集:
GUID | Id | Field1 | Field2
---------+----+--------+-------
GUID-0001| 1 | aaa1 | bbb1
GUID-0002| 1 | aaa2 | bbb2
我认为解决这个问题最简单的方法是在插入之前稍微调整一下 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
上定义的列相匹配,并相应地添加值。问题是您的对象键必须与列名完全匹配,并且您的值需要匹配(或能够转换为匹配)在这些列上定义的数据类型。
您可以使用 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);
returns:
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;
我需要在 pl/pgsql 函数中从 restful 服务获取 json。 (我无法控制 restful 网络服务。它是由其他人发布的)。我可以获得 json,但无法将其转换为行(用于插入到 table)。它的简化格式如下。每个 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() 函数时,我得到了 "ERROR: cannot call json_array_elements on a non-array"。当我尝试 json_each_text() 函数时,我得到了 "ERROR: query has no destination for result data"
我需要如下结果集:
GUID | Id | Field1 | Field2
---------+----+--------+-------
GUID-0001| 1 | aaa1 | bbb1
GUID-0002| 1 | aaa2 | bbb2
我认为解决这个问题最简单的方法是在插入之前稍微调整一下 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
上定义的列相匹配,并相应地添加值。问题是您的对象键必须与列名完全匹配,并且您的值需要匹配(或能够转换为匹配)在这些列上定义的数据类型。
您可以使用 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);
returns:
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;