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', 'someemail@gmail.com', 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语句,因为我认为我应该在这种情况下需要。

我提供了列名称,因为我转换为相应的loggingtypes,例如对于该表的结果,使用::user_roles

以下是查询返回的内容:

 { "id":1, "name":"Dan", "email":"someemail@gmail.com", "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":"someemail@gmail.com", "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_jsonjson_build_objectjson_objectjson_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; 

对于旧版本,请继续阅读。


它并不局限于单排,只是有点痛苦。 你不能使用AS别名复合行types,所以你需要使用别名子查询expression式或者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": "someemail@gmail.com", "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" } } } 

你会想要使用array_to_json(array_agg(...))当你有1:多的关系,顺便说一句。

上面的查询应该可以写成:

 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列别名。 可悲的是。

值得庆幸的是,他们对此也进行了优化。 比较计划:

  • 嵌套子查询版本 ; VS
  • 后者嵌套的ROW构造函数版本被移除,以便执行

由于CTE是优化栅栏,因此,对嵌套的子查询版本进行重新编译以使用链式CTE( WITHexpression式)可能无法执行,并且不会产生相同的计划。 在这种情况下,你会遇到丑陋的嵌套子查询,直到我们对row_to_json一些改进,或者更直接地重写ROW构造函数中的列名。


无论如何,一般来说,原则是,你要创build一个JSON对象与列a, b, c ,你希望你可以只写非法语法:

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

您可以改为使用返回行types值的标量子查询:

 (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_agg json值,例如,如果将json_agg的输出放在row_to_json ,那么内部的json_agg结果将不会被引用为string,而是直接作为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一样。

这意味着您可以编写 json操作来构造行,您并不总是必须创build非常复杂的PostgreSQL复合types,然后在输出上调用row_to_json

我长期以来对可维护性的build议是使用VIEW来构build查询的粗略版本,然后使用如下的函数:

 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; 

在这种情况下,对象突出。用户是一个视图。 由于我select了用户。*,如果需要更新视图以在用户logging中包含更多字段,则不必更新此function。

Interesting Posts