我试图使用PostgreSQL 9.2中添加的row_to_json()函数将查询结果映射到JSON.

我很难找到将连接行表示为嵌套对象(1:1关系)的最佳方法

以下是我try 过的(设置代码:表、示例数据,后跟查询):

-- 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条语句,因为我认为我在本例中应该需要这样做.

我得到了列名,因为我将该表的结果转换为适当的记录类型,例如::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中,这改进了很多with the introduction of to_json, json_build_object, json_object and 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;

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


它不仅限于一行,只是有点痛苦.不能使用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": "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(...)).

理想情况下,上述查询应该可以写成:

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构造函数中的列名的方法.


总之,一般来说,原则是要创建一个包含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合并.

e、 g.在任意示例中:

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一样再次逃逸.

这意味着您可以使用compose个json操作来构造行,而不必总是创建非常复杂的PostgreSQL复合类型,然后在输出上调用row_to_json.

Postgresql相关问答推荐

如何在查询空字段时设置pgtype.comb为默认值?

如何获取实例化视图的列级权限?

一列引用两个不同的表

使用 pgx 扫描范围类型

对表执行 DISABLE TRIGGER ALL 是否也会禁用 FK 指向该表?

在特定距离内创建点的唯一索引

使用psql的copy语句时如何压缩数据?

使用 GDB 调试器调试 AGE 代码的过程

为什么这里的默认格式不同? (ISO 8601 的两种不同实现)

从网址获取第一个字符串

如何从元组列表中 Select 与多列匹配的行?

带有 -C 选项的 pg_restore 不会创建数据库

Postgresql SERIAL 的工作方式是否不同?

Postgres 外键on update和on delete选项如何工作?

如何在 plpgsql 中使用记录类型变量?

如何禁用 postgresql缓存优化?

如何使用 Django Migrations 重新创建已删除的表?

如何将 Heroku PG 转储导入本地机器

用于将布尔列排序为 true、null、false 的 SQL

如何为 Postgresql 中的所有数据库创建具有只读权限的用户?