将 json 转换为嵌套的 postgres 复合类型
Posted
技术标签:
【中文标题】将 json 转换为嵌套的 postgres 复合类型【英文标题】:Converting json to nested postgres composite type 【发布时间】:2016-10-14 16:24:50 【问题描述】:我在 postgres 中定义了以下嵌套类型:
CREATE TYPE address AS (
name text,
street text,
zip text,
city text,
country text
);
CREATE TYPE customer AS (
customer_number text,
created timestamp WITH TIME ZONE,
default_billing_address address,
default_shipping_address address
);
现在想在一个存储过程中填充这些类型,该过程将 json 作为输入参数。这适用于***字段,输出显示了 postgres 复合类型的内部格式:
# select json_populate_record(null::customer, '"customer_number":"12345678"'::json)::customer;
json_populate_record
----------------------
(12345678,,,)
(1 row)
但是,postgres 不处理嵌套的 json 结构:
# select json_populate_record(null::customer, '"customer_number":"12345678","default_shipping_address":"name":"","street":"","zip":"12345","city":"Berlin","country":"DE"'::json)::customer;
ERROR: malformed record literal: ""name":"","street":"","zip":"12345","city":"Berlin","country":"DE""
DETAIL: Missing left parenthesis.
再次起作用的是,如果嵌套属性是 postgres 的内部格式,如下所示:
# select json_populate_record(null::customer, '"customer_number":"12345678","default_shipping_address":"(\"\",\"\",12345,Berlin,DE)"'::json)::customer;
json_populate_record
--------------------------------------------
(12345678,,,"("""","""",12345,Berlin,DE)")
(1 row)
有没有办法让 postgres 从嵌套的 json 结构转换为相应的复合类型?
【问题讨论】:
【参考方案1】:json_populate_record()
仅用于嵌套对象:
with a_table(jdata) as (
values
('
"customer_number":"12345678",
"default_shipping_address":
"name":"",
"street":"",
"zip":"12345",
"city":"Berlin",
"country":"DE"
'::json)
)
select (
jdata->>'customer_number',
jdata->>'created',
json_populate_record(null::address, jdata->'default_billing_address'),
json_populate_record(null::address, jdata->'default_shipping_address')
)::customer
from a_table;
row
--------------------------------------------
(12345678,,,"("""","""",12345,Berlin,DE)")
(1 row)
嵌套复合类型不是 Postgres(和任何 RDBMS)的设计目的。它们太复杂和麻烦了。 在数据库逻辑中,嵌套结构应该被维护为相关的表,例如
create table addresses (
address_id serial primary key,
name text,
street text,
zip text,
city text,
country text
);
create table customers (
customer_id serial primary key, -- not necessary `serial` may be `integer` or `bigint`
customer_number text, -- maybe redundant
created timestamp with time zone,
default_billing_address int references adresses(address_id),
default_shipping_address int references adresses(address_id)
);
有时在表格中使用嵌套结构是合理的,但在这些情况下使用jsonb
或hstore
似乎更方便和自然,例如:
create table customers (
customer_id serial primary key,
customer_number text,
created timestamp with time zone,
default_billing_address jsonb,
default_shipping_address jsonb
);
【讨论】:
这可行,但对于更大或更深的嵌套对象,我想避免在复合类型中重复所有列及其顺序。 恐怕你别无选择。 为了提供更多关于嵌套类型使用的上下文,这些将是存储过程或查询的返回值,基础表将被很好地规范化。 Zalando 在他们的 java-sproc-wrapper (github.com/zalando-incubator/java-sproc-wrapper/#type-mapping) 中在客户端做类似的事情,但这首先必须从数据库中提取所有类型信息。通过将类型映射到 json 和从 json 映射,实现可以简单得多。 那是误会。如果服务器必须响应结构化数据,它应该发送简单的 json,可以在客户端轻松解释,无需任何额外工具。我仍然看不出有任何理由使用嵌套复合类型。您无法将它们映射到服务器端的相关表中,因为这完全没用。好吧,我可以想象将嵌套复合类型用于常见的对象类型,但这需要创建特殊的工具来支持(组合、分解、分配、修改、比较)此类数据。【参考方案2】:plpython 救援:
create function to_customer (object json)
returns customer
AS $$
import json
return json.loads(object)
$$ language plpythonu;
例子:
select to_customer('
"customer_number":"12345678",
"default_shipping_address":
"name":"",
"street":"",
"zip":"12345",
"city":"Berlin",
"country":"DE"
,
"default_billing_address":null,
"created": null
'::json);
to_customer
--------------------------------------------
(12345678,,,"("""","""",12345,Berlin,DE)")
(1 row)
警告:从 python 构建返回的对象时,postgresql 需要将所有 null
值呈现为 None
(即不允许跳过不存在的空值),因此我们必须指定传入的所有空值json。例如不允许:
select to_customer('
"customer_number":"12345678",
"default_shipping_address":
"name":"",
"street":"",
"zip":"12345",
"city":"Berlin",
"country":"DE"
'::json);
ERROR: key "created" not found in mapping
HINT: To return null in a column, add the value None to the mapping with the key named after the column.
CONTEXT: while creating return value
PL/Python function "to_customer"
【讨论】:
虽然我可能不想依赖 plpython 来解决这个问题,但这个答案表明有一个通用的解决方案,并且 postgres 将拥有实现它所需的所有类型信息。【参考方案3】:这似乎在 Postgres 10 中得到解决。在 release notes 中搜索 json_populate_record
显示以下更改:
使 json_populate_record() 和相关函数递归处理 JSON 数组和对象 (Nikita Glukhov)
通过此更改,目标 SQL 类型中的数组类型字段从 JSON 数组正确转换,复合类型字段从 JSON 对象正确转换。以前,这种情况会失败,因为 JSON 值的文本表示将被提供给 array_in() 或 record_in(),并且其语法与这些输入函数所期望的不匹配。
【讨论】:
以上是关于将 json 转换为嵌套的 postgres 复合类型的主要内容,如果未能解决你的问题,请参考以下文章