Uppdatering:I PostgreSQL 9.4 förbättras detta mycket med introduktionen av to_json
, json_build_object
, json_object
och json_build_array
, även om det är utförligt på grund av behovet av att namnge alla fält uttryckligen:
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;
För äldre versioner, läs vidare.
Det är inte begränsat till en enda rad, det är bara lite smärtsamt. Du kan inte alias sammansatta radtyper med AS
, så du måste använda ett alias subquery-uttryck eller CTE för att uppnå effekten:
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;
producerar, via 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"
}
}
}
Du kommer att vilja använda array_to_json(array_agg(...))
när man har ett 1:många förhållande, btw.
Ovanstående fråga bör helst kunna skrivas som:
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;
... men PostgreSQL:s ROW
konstruktorn accepterar inte AS
kolumnalias. Tyvärr.
Tack och lov optimerar de detsamma. Jämför planerna:
- Den kapslade versionen av underfrågan; vs
- Den senare kapslade
ROW
konstruktorversionen med aliasen borttagna så att den körs
Eftersom CTE:er är optimeringsstängsel, omformulera den kapslade underfrågaversionen till att använda kedjade CTE:er (WITH
expressions) kanske inte fungerar lika bra och kommer inte att resultera i samma plan. I det här fallet har du typ fastnat med fula kapslade underfrågor tills vi får några förbättringar av row_to_json
eller ett sätt att åsidosätta kolumnnamnen i en ROW
konstruktör mer direkt.
Hur som helst, generellt sett är principen att där du vill skapa ett json-objekt med kolumner a, b, c
, och du önskar att du bara kunde skriva den olagliga syntaxen:
ROW(a, b, c) AS outername(name1, name2, name3)
du kan istället använda skalära underfrågor som returnerar radtypade värden:
(SELECT x FROM (SELECT a AS name1, b AS name2, c AS name3) x) AS outername
Eller:
(SELECT x FROM (SELECT a, b, c) AS x(name1, name2, name3)) AS outername
Tänk dessutom på att du kan skapa json
värden utan ytterligare citat, t.ex. om du sätter utdata från en json_agg
inom en row_to_json
, den inre json_agg
resultatet kommer inte att citeras som en sträng, det kommer att inkorporeras direkt som json.
t.ex. i det godtyckliga exemplet:
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
);
utgången är:
{"k1":1,
"k2":2,
"k3":[{"a":1,"b":2},
{"a":1,"b":2}]}
Observera att json_agg
produkt, [{"a":1,"b":2}, {"a":1,"b":2}]
, har inte escapets igen, som text
skulle vara.
Det betyder att du kan skriva json-operationer för att konstruera rader, du behöver inte alltid skapa enormt komplexa PostgreSQL-komposittyper och sedan anropa row_to_json
på utgången.