我在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结构转换为相应的复合类型?
答案 0 :(得分:3)
仅对嵌套对象使用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
);
答案 1 :(得分: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)
警告:postgresql从python构建返回的对象时需要将所有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"
答案 2 :(得分:0)
这似乎在Postgres 10中得到解决。在json_populate_record
中搜索var needle = 234;
var haystack = 79234826;
var contains = haystack.ToString().Contains(needle.ToString());
会显示以下更改:
让json_populate_record()和相关函数递归处理JSON数组和对象(Nikita Glukhov)
通过此更改,目标SQL类型中的数组类型字段可以从JSON数组正确转换,并且可以从JSON对象正确转换复合类型字段。以前,这种情况会失败,因为JSON值的文本表示将被提供给array_in()或record_in(),并且其语法与这些输入函数所期望的不匹配。