it-swarm.com.de

PostgreSQL 9.2 row_to_json () mit verschachtelten Joins

Ich versuche, die Ergebnisse einer Abfrage mit der in PostgreSQL 9.2 hinzugefügten Funktion row_to_json() auf JSON abzubilden.

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;

Wenn ich ROW() verwende, kann ich die resultierenden Felder in ein untergeordnetes Objekt aufteilen, aber es scheint auf eine einzelne Ebene beschränkt zu sein. Ich kann keine weiteren AS XXX - Anweisungen einfügen, wie ich es in diesem Fall für nötig halte.

Mir werden Spaltennamen gewährt, weil ich bei den Ergebnissen dieser Tabelle in den entsprechenden Datensatztyp umgewandelt habe, z. B. mit ::user_roles.

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"
         }
      }
   }
}

Ich möchte JSON für Joins generieren (wieder 1: 1 ist in Ordnung), indem ich Joins hinzufügen und sie als untergeordnete Objekte der übergeordneten Objekte darstellen lassen kann, denen sie beitreten, d. H. 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.

67
dwerner

Update: In PostgreSQL 9.4 verbessert sich dies erheblich mit der Einführung von to_json, json_build_object, json_object Und json_build_array Ausführlich, da alle Felder explizit benannt werden müssen:

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 ältere Versionen lesen Sie weiter.


Es ist nicht auf eine einzelne Reihe beschränkt, es ist nur ein bisschen schmerzhaft. Zusammengesetzte Zeilentypen können nicht mit AS aliasiert werden. Sie müssen daher einen aliasierten 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/ :

{
  "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"
    }
  }
}

Sie werden array_to_json(array_agg(...)) verwenden wollen, wenn Sie eine 1: many-Beziehung haben, übrigens.

Die obige Abfrage sollte idealerweise so 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 keine AS Spalten-Aliase. Traurig.

Zum Glück optimieren sie das Gleiche. Vergleichen Sie die Pläne:

Da CTEs Optimierungszäune sind, führt die Neuformulierung der verschachtelten Unterabfrageversion zur Verwendung verketteter CTEs (WITH Ausdrücke) möglicherweise nicht zu derselben Leistung und nicht zum gleichen Plan. In diesem Fall bleiben Sie bei hässlich verschachtelten Unterabfragen hängen, bis wir einige Verbesserungen an row_to_json Oder eine Möglichkeit erhalten, die Spaltennamen in einem ROW -Konstruktor direkter zu überschreiben.


Im Allgemeinen gilt jedoch das Prinzip, dass Sie ein JSON-Objekt mit Spalten a, b, c Erstellen und wünschen, Sie könnten einfach die unzulässige Syntax schreiben:

ROW(a, b, c) AS outername(name1, name2, name3)

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ätzliche Anführungszeichen erstellen können, z. Wenn Sie die Ausgabe eines json_agg in ein row_to_json einfügen, wird das innere json_agg - Ergebnis nicht als Zeichenfolge in Anführungszeichen gesetzt, sondern direkt als json eingefügt.

z.B. im beliebigen 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:

{"k1":1,
 "k2":2,
 "k3":[{"a":1,"b":2}, 
 {"a":1,"b":2}]}

Beachten Sie, dass das Produkt json_agg, [{"a":1,"b":2}, {"a":1,"b":2}], Nicht erneut maskiert wurde, wie dies bei text der Fall wäre.

Das heißt, Sie können compose json-Operationen ausführen, um Zeilen zu erstellen. Sie müssen nicht immer sehr komplexe zusammengesetzte PostgreSQL-Typen erstellen und dann row_to_json Für die Ausgabe aufrufen.

124
Craig Ringer

Mein Vorschlag für die langfristige Wartbarkeit ist, mit VIEW 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.

1
Todd