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"
}
}
}
}
_
どんな助けも大歓迎です。読んでくれてありがとう。
更新: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
列エイリアスを受け入れません。悲しいかな。
ありがたいことに、彼らは同じように最適化します。計画を比較します。
ROW
constructor version エイリアスを削除して実行する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を呼び出す必要はありません出力。
長期にわたる保守性に関する私の提案は、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。*を選択したため、ビューを更新してユーザーレコードにさらにフィールドを含める必要がある場合、この関数を更新する必要はありません。