2016-10-14 2 views
5

У меня есть следующие вложенные типы, определенные в Postgres:Преобразование JSON в вложен 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

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" 
+0

Хотя я, вероятно, не хочу полагаться на plpython для этой проблемы, этот ответ показывает, что существует общее решение, и что postgres будут иметь всю информацию о типе, необходимую для ее реализации. –

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 
); 
+0

Это будет работать, но для больших или более глубоко вложенных объектов я бы хотел избежать повторения всех столбцов и их порядка в составном типе. –

+0

Я боюсь, что у вас нет выбора. – klin

+0

Чтобы предоставить более контекст для использования вложенных типов, это будут возвращаемые значения хранимой процедуры или запроса, базовые таблицы будут хорошо нормализованы. Zalando делает что-то подобное на стороне клиента в своей java-sproc-wrapper (https://github.com/zalando-incubator/java-sproc-wrapper/#type-mapping), но сначала нужно извлечь весь тип информацию из базы данных. Путем сопоставления типов с и из json реализация может быть намного проще. –

0

Это, кажется, решена в Postgres 10. Поискав в release notes для json_populate_record показывает следующее изменение:

Марка json_populate_record() и связанные с ними функции процесса JSON массивы и объекты рекурсивно (Никита Глухов)

С этим изменением поля типа массива в целевом типе SQL правильно преобразуются из массивов JSON, а поля составного типа правильно преобразуются из объектов JSON. Раньше такие случаи терпели бы неудачу, потому что текстовое представление значения JSON было бы подано в array_in() или record_in(), и его синтаксис не соответствовал бы ожиданиям тех функций ввода.

Смежные вопросы