PostgreSQL 9.2 row_to_json() with nested joins
我正在尝试使用PostgreSQL 9.2中添加的
我无法找出将连接行表示为嵌套对象的最佳方法(1:1关系)
这是我尝试过的(设置代码:表格,示例数据,然后是查询):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | -- 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$$; |
查询本身:
1 2 3 4 5 6 7 | 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; |
我发现如果我使用
我获得了列名,因为我转换为适当的记录类型,例如使用
这是查询返回的内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | { "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很好),我可以添加连接,并将它们表示为它们加入的父对象的子对象,即如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | { "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中,通过引入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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 2 3 4 5 6 7 8 9 10 | 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/制作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | { "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:很多关系时,你会想要使用
理想情况下,上述查询应该写成:
1 2 3 4 5 6 | 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的
值得庆幸的是,他们优化了相同的。比较计划:
- 嵌套的子查询版本; VS
-
后者嵌套
ROW 构造函数版本,删除了别名,以便执行
因为CTE是优化围栏,所以将嵌套子查询版本重新构造为使用链式CTE(
无论如何,一般来说,原则是你想用列
1 | ROW(a, b, c) AS outername(name1, name2, name3) |
您可以改为使用返回行类型值的标量子查询:
1 | (SELECT x FROM (SELECT a AS name1, b AS name2, c AS name3) x) AS outername |
要么:
1 | (SELECT x FROM (SELECT a, b, c) AS x(name1, name2, name3)) AS outername |
另外,请记住,您可以在没有其他引用的情况下撰写
例如在任意的例子中:
1 2 3 4 5 6 7 8 9 | 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 ); |
输出是:
1 2 3 4 | {"k1":1, "k2":2, "k3":[{"a":1,"b":2}, {"a":1,"b":2}]} |
请注意,
这意味着您可以组合json操作来构造行,您不必总是创建非常复杂的PostgreSQL复合类型,然后在输出上调用
我对长期可维护性的建议是使用VIEW构建查询的粗略版本,然后使用如下函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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是一个视图。由于我选择了用户。*,如果我需要更新视图以在用户记录中包含更多字段,我将不必更新此功能。