Актуализация:В PostgreSQL 9.4 това се подобрява много с въвеждането на to_json
, json_build_object
, json_object
и json_build_array
, въпреки че е многословен поради необходимостта от изрично именуване на всички полета:
select
json_build_object(
'id', u.id,
'name', u.name,
'email', u.email,
'user_role_id', u.user_role_id,
'user_role', json_build_object(
'id', ur.id,
'name', ur.name,
'description', ur.description,
'duty_id', ur.duty_id,
'duty', json_build_object(
'id', d.id,
'name', d.name
)
)
)
from users u
inner join user_roles ur on ur.id = u.user_role_id
inner join role_duties d on d.id = ur.duty_id;
За по-стари версии прочетете нататък.
Не е ограничено до един ред, просто е малко болезнено. Не можете да псевдоним съставните типове редове с помощта на AS
, така че трябва да използвате израз на подзаявка с псевдоним или CTE, за да постигнете ефекта:
select row_to_json(row)
from (
select u.*, urd AS user_role
from users u
inner join (
select ur.*, d
from user_roles ur
inner join role_duties d on d.id = ur.duty_id
) urd(id,name,description,duty_id,duty) on urd.id = u.user_role_id
) row;
произвежда чрез http://jsonprettyprint.com/:
{
"id": 1,
"name": "Dan",
"email": "[email protected]",
"user_role_id": 1,
"user_role": {
"id": 1,
"name": "admin",
"description": "Administrative duties in the system",
"duty_id": 1,
"duty": {
"id": 1,
"name": "Script Execution"
}
}
}
Ще искате да използвате array_to_json(array_agg(...))
когато имате връзка 1:много, между другото.
В идеалния случай горната заявка трябва да може да бъде написана като:
select row_to_json(
ROW(u.*, ROW(ur.*, d AS duty) AS user_role)
)
from users u
inner join user_roles ur on ur.id = u.user_role_id
inner join role_duties d on d.id = ur.duty_id;
... но ROW
на PostgreSQL конструкторът не приема AS
псевдоними на колони. За съжаление.
За щастие, те оптимизират същото. Сравнете плановете:
- Вложената версия на подзаявката; срещу
- Последният влага
ROW
версия на конструктор с премахнати псевдоними, така че да се изпълнява
Тъй като CTEs са оптимизационни огради, префразиране на вложената версия на подзаявка, за да се използват верижни CTE (WITH
изрази) може да не се представят толкова добре и няма да доведат до същия план. В този случай сте заседнали с грозно вложени подзаявки, докато не получим някои подобрения на row_to_json
или начин за замяна на имената на колоните в ROW
конструктор по-директно.
Както и да е, като цяло принципът е, че където искате да създадете json обект с колони a, b, c
, и бихте искали просто да напишете незаконния синтаксис:
ROW(a, b, c) AS outername(name1, name2, name3)
вместо това можете да използвате скаларни подзаявки, връщащи въведени от ред стойности:
(SELECT x FROM (SELECT a AS name1, b AS name2, c AS name3) x) AS outername
Или:
(SELECT x FROM (SELECT a, b, c) AS x(name1, name2, name3)) AS outername
Освен това, имайте предвид, че можете да съставите json
стойности без допълнително цитиране, напр. ако поставите изхода на json_agg
в рамките на row_to_json
, вътрешният json_agg
резултатът няма да бъде цитиран като низ, той ще бъде включен директно като json.
напр. в произволния пример:
SELECT row_to_json(
(SELECT x FROM (SELECT
1 AS k1,
2 AS k2,
(SELECT json_agg( (SELECT x FROM (SELECT 1 AS a, 2 AS b) x) )
FROM generate_series(1,2) ) AS k3
) x),
true
);
изходът е:
{"k1":1,
"k2":2,
"k3":[{"a":1,"b":2},
{"a":1,"b":2}]}
Имайте предвид, че json_agg
продукт, [{"a":1,"b":2}, {"a":1,"b":2}]
, не е екраниран отново, като text
би било.
Това означава, че можете да композирате json операции за конструиране на редове, не винаги е нужно да създавате изключително сложни съставни типове PostgreSQL, след което да извикате row_to_json
на изхода.