Próbuję zmapować wyniki zapytania do formatu JSON za pomocą row_to_json()
funkcji, która została dodana w PostgreSQL 9.2.
Mam problem ze znalezieniem najlepszego sposobu przedstawienia połączonych wierszy jako obiektów zagnieżdżonych (relacje 1: 1)
Oto co wypróbowałem (kod konfiguracji: tabele, przykładowe dane, a następnie zapytanie):
-- some test tables to start out with:
create table role_duties (
id serial primary key,
name varchar
);
create table user_roles (
id serial primary key,
name varchar,
description varchar,
duty_id int, foreign key (duty_id) references role_duties(id)
);
create table users (
id serial primary key,
name varchar,
email varchar,
user_role_id int, foreign key (user_role_id) references user_roles(id)
);
DO $$
DECLARE duty_id int;
DECLARE role_id int;
begin
insert into role_duties (name) values ('Script Execution') returning id into duty_id;
insert into user_roles (name, description, duty_id) values ('admin', 'Administrative duties in the system', duty_id) returning id into role_id;
insert into users (name, email, user_role_id) values ('Dan', '[email protected]', role_id);
END$$;
Samo zapytanie:
select row_to_json(row)
from (
select u.*, ROW(ur.*::user_roles, ROW(d.*::role_duties)) 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;
Odkryłem, że jeśli użyję ROW()
, mogę oddzielić wynikowe pola na obiekt podrzędny, ale wydaje się, że jest to ograniczone do jednego poziomu. Nie mogę wstawić więcej AS XXX
stwierdzeń, ponieważ myślę, że powinienem potrzebować w tym przypadku.
Otrzymuję nazwy kolumn, ponieważ rzutuję na odpowiedni typ rekordu, na przykład z ::user_roles
, w przypadku wyników tej tabeli.
Oto, co zwraca to zapytanie:
{
"id":1,
"name":"Dan",
"email":"[email protected]",
"user_role_id":1,
"user_role":{
"f1":{
"id":1,
"name":"admin",
"description":"Administrative duties in the system",
"duty_id":1
},
"f2":{
"f1":{
"id":1,
"name":"Script Execution"
}
}
}
}
To, co chcę zrobić, to wygenerować JSON dla złączeń (znowu 1: 1 jest w porządku) w sposób, w którym mogę dodawać złączenia i przedstawiać je jako obiekty podrzędne rodziców, do których dołączają, tj. W następujący sposób:
{
"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"
}
}
}
}
Każda pomoc jest mile widziana. Dziękuje za przeczytanie.
Odpowiedzi:
Aktualizacja: W PostgreSQL 9.4 poprawia wiele z wprowadzeniem
to_json
,json_build_object
,json_object
ijson_build_array
, choć to gadatliwy ze względu na konieczność, aby wymienić wszystkie jawnie pola: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;
W przypadku starszych wersji czytaj dalej.
Nie ogranicza się do jednego rzędu, jest po prostu trochę bolesne. Nie możesz aliasować złożonych typów wierszy za pomocą
AS
, więc musisz użyć aliasu wyrażenia podzapytania lub CTE, aby osiągnąć efekt: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;
produkuje, za pośrednictwem http://jsonprettyprint.com/ :
Będziesz chciał użyć,
array_to_json(array_agg(...))
gdy masz relację 1: wiele, przy okazji.Najlepiej byłoby, gdyby powyższe zapytanie można było zapisać jako:
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;
... ale
ROW
konstruktor PostgreSQL nie akceptujeAS
aliasów kolumn. Niestety.Na szczęście optymalizują to samo. Porównaj plany:
ROW
wersja konstruktora z usuniętymi aliasami, więc jest wykonywanaPonieważ CTE są barierami optymalizacji, zmiana sformułowania wersji zagnieżdżonego podzapytania w celu użycia połączonych CTE (
WITH
wyrażeń) może nie działać tak dobrze i nie spowoduje tego samego planu. W tym przypadku utkniesz z brzydkimi zagnieżdżonymi podzapytaniami, dopóki nie uzyskamy pewnych ulepszeńrow_to_json
lub sposobu na nadpisanie nazw kolumn wROW
konstruktorze bardziej bezpośrednio.W każdym razie, ogólnie rzecz biorąc, zasada jest taka, że tam, gdzie chcesz utworzyć obiekt json z kolumnami
a, b, c
, i chciałbyś po prostu napisać niedozwoloną składnię:zamiast tego możesz użyć podzapytań skalarnych zwracających wartości wpisane w wierszach:
(SELECT x FROM (SELECT a AS name1, b AS name2, c AS name3) x) AS outername
Lub:
(SELECT x FROM (SELECT a, b, c) AS x(name1, name2, name3)) AS outername
Ponadto pamiętaj, że możesz komponować
json
wartości bez dodatkowego cudzysłowu, np. Jeśli umieścisz wyjście ajson_agg
wewnątrz arow_to_json
, wewnętrznyjson_agg
wynik nie zostanie cytowany jako łańcuch, zostanie włączony bezpośrednio jako json.np. w dowolnym przykładzie:
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 );
wynik to:
Zauważ, że
json_agg
produkt[{"a":1,"b":2}, {"a":1,"b":2}]
,, nie został ponownie zmieniony, tak jaktext
by to było.Oznacza to, że możesz komponować operacje json w celu tworzenia wierszy, nie zawsze musisz tworzyć bardzo złożone typy złożone PostgreSQL, a następnie wywoływać
row_to_json
dane wyjściowe.źródło
json_build_object
znacznie ułatwi mi życie, ale jakoś nie zauważyłem, kiedy zobaczyłem informacje o wydaniu. Czasami potrzebujesz konkretnego przykładu, aby zacząć.json_build_object
nieco więcej - to prawdziwa zmiana gry.Moja sugestia dotycząca łatwości utrzymania w dłuższej perspektywie to użycie VIEW do zbudowania przybliżonej wersji zapytania, a następnie użycie funkcji jak poniżej:
CREATE OR REPLACE FUNCTION fnc_query_prominence_users( ) RETURNS json AS $$ DECLARE d_result json; BEGIN SELECT ARRAY_TO_JSON( ARRAY_AGG( ROW_TO_JSON( CAST(ROW(users.*) AS prominence.users) ) ) ) INTO d_result FROM prominence.users; RETURN d_result; END; $$ LANGUAGE plpgsql SECURITY INVOKER;
W tym przypadku obiekt prominence.users jest widokiem. Ponieważ wybrałem użytkowników. *, Nie będę musiał aktualizować tej funkcji, jeśli będę musiał zaktualizować widok, aby uwzględnić więcej pól w rekordzie użytkownika.
źródło
Dodam to rozwiązanie, ponieważ przyjęta odpowiedź nie uwzględnia relacji N: N. aka: kolekcje zbiorów obiektów
Jeśli masz relacje N: N, klauzula
with
to twój przyjaciel. W moim przykładzie chciałbym zbudować widok drzewa o następującej hierarchii.Poniższe zapytanie reprezentuje sprzężenia.
SELECT reqId ,r.description as reqDesc ,array_agg(s.id) s.id as suiteId , s."Name" as suiteName, tc.id as tcId , tc."Title" as testCaseTitle from "Requirement" r inner join "Has" h on r.id = h.requirementid inner join "TestSuite" s on s.id = h.testsuiteid inner join "Contains" c on c.testsuiteid = s.id inner join "TestCase" tc on tc.id = c.testcaseid GROUP BY r.id, s.id;
Ponieważ nie możesz wykonać wielu agregacji, musisz użyć „Z”.
with testcases as ( select c.testsuiteid,ts."Name" , tc.id, tc."Title" from "TestSuite" ts inner join "Contains" c on c.testsuiteid = ts.id inner join "TestCase" tc on tc.id = c.testcaseid ), requirements as ( select r.id as reqId ,r.description as reqDesc , s.id as suiteId from "Requirement" r inner join "Has" h on r.id = h.requirementid inner join "TestSuite" s on s.id = h.testsuiteid ) , suitesJson as ( select testcases.testsuiteid, json_agg( json_build_object('tc_id', testcases.id,'tc_title', testcases."Title" ) ) as suiteJson from testcases group by testcases.testsuiteid,testcases."Name" ), allSuites as ( select has.requirementid, json_agg( json_build_object('ts_id', suitesJson.testsuiteid,'name',s."Name" , 'test_cases', suitesJson.suiteJson ) ) as suites from suitesJson inner join "TestSuite" s on s.id = suitesJson.testsuiteid inner join "Has" has on has.testsuiteid = s.id group by has.requirementid ), allRequirements as ( select json_agg( json_build_object('req_id', r.id ,'req_description',r.description , 'test_suites', allSuites.suites ) ) as suites from allSuites inner join "Requirement" r on r.id = allSuites.requirementid ) select * from allRequirements
To, co robi, to budowanie obiektu JSON w małej kolekcji elementów i agregowanie ich w każdej
with
klauzuli.Wynik:
źródło