Ich versuche, die Ergebnisse einer Abfrage mithilfe der row_to_json()
in PostgreSQL 9.2 hinzugefügten Funktion JSON zuzuordnen .
Ich habe Probleme, herauszufinden, wie verbundene Zeilen am besten als verschachtelte Objekte dargestellt werden können (1: 1-Beziehungen).
Folgendes habe ich versucht (Setup-Code: Tabellen, Beispieldaten, gefolgt von einer Abfrage):
-- 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$$;
Die Abfrage selbst:
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;
Ich habe festgestellt, dass ich bei Verwendung ROW()
die resultierenden Felder in ein untergeordnetes Objekt aufteilen könnte, aber es scheint auf eine einzelne Ebene beschränkt zu sein. Ich kann keine weiteren AS XXX
Anweisungen einfügen , da ich denke, dass ich sie in diesem Fall benötigen sollte.
Ich erhalte Spaltennamen, weil ich in den entsprechenden Datensatztyp umgewandelt habe, z. B. mit ::user_roles
im Fall der Ergebnisse dieser Tabelle.
Diese Abfrage gibt Folgendes zurück:
{
"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"
}
}
}
}
Was ich tun möchte, ist JSON für Joins zu generieren (wieder 1: 1 ist in Ordnung), so dass ich Joins hinzufügen und sie als untergeordnete Objekte der Eltern darstellen lassen kann, denen sie beitreten, dh wie folgt:
{
"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"
}
}
}
}
Jede Hilfe wird geschätzt. Danke fürs Lesen.
Antworten:
Update: In PostgreSQL 9.4 verbessert dies viel mit der Einführung von
to_json
,json_build_object
,json_object
undjson_build_array
, obwohl es ausführliche aufgrund der Notwendigkeit , alle Felder zu nennen ausdrücklich: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;
Lesen Sie für ältere Versionen weiter.
Es ist nicht auf eine einzelne Reihe beschränkt, es ist nur ein bisschen schmerzhaft. Sie können zusammengesetzte Zeilentypen nicht als Alias verwenden
AS
, daher müssen Sie einen Alias-Unterabfrageausdruck oder CTE verwenden, um den Effekt zu erzielen: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;
produziert über http://jsonprettyprint.com/ :
Sie werden verwenden wollen,
array_to_json(array_agg(...))
wenn Sie eine 1: viele-Beziehung haben, übrigens.Die obige Abfrage sollte idealerweise wie folgt geschrieben werden können:
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;
... aber der
ROW
Konstruktor von PostgreSQL akzeptiert keineAS
Spaltenaliasnamen. Traurig.Zum Glück optimieren sie das gleiche. Vergleichen Sie die Pläne:
ROW
Konstruktorversion, bei der die Aliase entfernt wurden, damit sie ausgeführt wirdDa CTEs Optimierungszäune sind, funktioniert die Neuformulierung der verschachtelten Unterabfrageversion zur Verwendung verketteter CTEs (
WITH
Ausdrücke) möglicherweise nicht so gut und führt nicht zum gleichen Plan. In diesem Fall bleiben Sie bei hässlichen verschachtelten Unterabfragen hängen, bis wir einige Verbesserungenrow_to_json
oder eine Möglichkeit erhalten, die Spaltennamen in einemROW
Konstruktor direkter zu überschreiben .Im Allgemeinen besteht das Prinzip darin, dass Sie dort, wo Sie ein JSON-Objekt mit Spalten erstellen möchten
a, b, c
und nur die unzulässige Syntax schreiben möchten:Sie können stattdessen skalare Unterabfragen verwenden, die zeilentypisierte Werte zurückgeben:
(SELECT x FROM (SELECT a AS name1, b AS name2, c AS name3) x) AS outername
Oder:
(SELECT x FROM (SELECT a, b, c) AS x(name1, name2, name3)) AS outername
Beachten Sie außerdem, dass Sie
json
Werte ohne zusätzliches Anführungszeichen erstellen können. Wenn Sie beispielsweise die Ausgabe von ajson_agg
in a einfügen,row_to_json
wird das innerejson_agg
Ergebnis nicht als Zeichenfolge in Anführungszeichen gesetzt, sondern direkt als json eingefügt.zB im willkürlichen Beispiel:
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 );
Die Ausgabe ist:
Beachten Sie, dass das
json_agg
Produkt[{"a":1,"b":2}, {"a":1,"b":2}]
nicht wie gewünscht erneuttext
maskiert wurde.Dies bedeutet, dass Sie JSON-Operationen erstellen können , um Zeilen zu erstellen. Sie müssen nicht immer äußerst komplexe PostgreSQL-Verbundtypen erstellen und dann
row_to_json
die Ausgabe aufrufen .quelle
json_build_object
wird mein Leben viel einfacher machen, aber irgendwie habe ich es nicht verstanden, als ich die Versionshinweise sah. Manchmal brauchen Sie nur ein konkretes Beispiel, um loszulegen.json_build_object
etwas mehr hervorheben sollte - es ist ein echter Game Changer.Mein Vorschlag für eine langfristige Wartbarkeit besteht darin, eine VIEW zu verwenden, um die grobe Version Ihrer Abfrage zu erstellen, und dann eine der folgenden Funktionen zu verwenden:
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;
In diesem Fall ist das Objekt prominence.users eine Ansicht. Da ich Benutzer ausgewählt habe. *, Muss ich diese Funktion nicht aktualisieren, wenn ich die Ansicht aktualisieren muss, um mehr Felder in einen Benutzerdatensatz aufzunehmen.
quelle
Ich füge diese Lösung hinzu, da die akzeptierte Antwort keine N: N-Beziehungen berücksichtigt. aka: Sammlungen von Sammlungen von Objekten
Wenn Sie N: N-Beziehungen haben, ist die Klausel
with
Ihr Freund. In meinem Beispiel möchte ich eine Baumansicht der folgenden Hierarchie erstellen.Die folgende Abfrage repräsentiert die Verknüpfungen.
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;
Da Sie nicht mehrere Aggregationen durchführen können, müssen Sie "WITH" verwenden.
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
Dabei wird das JSON-Objekt in einer kleinen Sammlung von Elementen erstellt und auf den einzelnen
with
Klauseln zusammengefasst.Ergebnis:
quelle