web-dev-qa-db-ja.com

PostgreSQL 9.2 row_to_json()でネストされた結合

PostgreSQL 9.2で追加されたrow_to_json()関数を使用して、クエリの結果をJSONにマップしようとしています。

結合された行をネストされたオブジェクト(1:1の関係)として表すための最良の方法を見つけるのに問題があります

私が試したものは次のとおりです(セットアップコード:テーブル、サンプルデータ、その後にクエリ):

_-- 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$$;
_

クエリ自体:

_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;
_

ROW()を使用すると、結果のフィールドを子オブジェクトに分離できますが、単一レベルに制限されているようです。この場合に必要だと思うので、これ以上_AS XXX_ステートメントを挿入できません。

そのテーブルの結果の場合、_::user_roles_などの適切なレコードタイプにキャストするため、列名が与えられます。

そのクエリが返すものは次のとおりです。

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

私がしたいのは、結合を追加できる方法で結合用のJSONを生成し(再び1:1で結構です)、それらを結合する親の子オブジェクトとして表すようにします、すなわち、次のように:

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

どんな助けも大歓迎です。読んでくれてありがとう。

67
dwerner

更新: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;
_

古いバージョンについては、読み進めてください。


1行に限らず、少し苦痛です。 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"
    }
  }
}
_

1対多の関係がある場合は、array_to_json(array_agg(...))を使用します。

上記のクエリは、理想的には次のように記述できます。

_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;
_

...しかし、PostgreSQLのROWコンストラクターはAS列エイリアスを受け入れません。悲しいかな。

ありがたいことに、彼らは同じように最適化します。計画を比較します。

CTEは最適化フェンスであるため、ネストされたサブクエリバージョンをチェーン化されたCTE(WITH式)を使用するように言い換えると、同様に機能しない場合があり、同じ計画にはなりません。この場合、_row_to_json_にいくつかの改善が得られるまで、またはROWコンストラクターの列名をより直接オーバーライドする方法が得られるまで、nestedいネストされたサブクエリに固執します。


とにかく、一般的に、原則は、列_a, b, c_を持つjsonオブジェクトを作成する場所であり、違法な構文を記述したいだけです。

_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のように再びエスケープされていないことに注意してください。

つまり、composejson操作で行を構築できます。非常に複雑なPostgreSQL複合型を作成してから_row_to_json_ onを呼び出す必要はありません出力。

124
Craig Ringer

長期にわたる保守性に関する私の提案は、VIEWを使用してクエリの大まかなバージョンを構築し、次に以下のような関数を使用することです。

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;

この場合、オブジェクトprominence.usersはビューです。 users。*を選択したため、ビューを更新してユーザーレコードにさらにフィールドを含める必要がある場合、この関数を更新する必要はありません。

1
Todd