带嵌套联接的PostgreSQL 9.2行到json()

带嵌套联接的PostgreSQL 9.2行到json(),json,postgresql,postgresql-9.2,scalar-subquery,Json,Postgresql,Postgresql 9.2,Scalar Subquery,我正在尝试使用PostgreSQL 9.2中添加的row\u to_JSON()函数将查询结果映射到JSON 我很难找到将连接行表示为嵌套对象(1:1关系)的最佳方法 以下是我尝试过的(设置代码:表、示例数据,后跟查询): 查询本身: select row_to_json(row) from ( select u.*, ROW(ur.*::user_roles, ROW(d.*::role_duties)) as user_role from users u inner

我正在尝试使用PostgreSQL 9.2中添加的
row\u to_JSON()
函数将查询结果映射到JSON

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

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

查询本身:

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()
,我可以将结果字段分离到一个子对象中,但它似乎仅限于一个级别。我无法将更多的
作为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中,这改进了很多,但由于需要明确命名所有字段,因此它很冗长:

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;
对于旧版本,请继续阅读


它不限于一行,只是有点痛苦。不能使用
作为
别名组合行类型,因此需要使用别名子查询表达式或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;
生产,通过:

顺便说一句,当您有1:many关系时,您将需要使用
array\u to\u json(array\u 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的
构造函数不接受
作为列别名。可悲的是

谢天谢地,他们优化出了同样的结果。比较计划:

  • 第二;vs
  • 后者删除了别名,以便执行
因为CTE是优化围栏,所以将嵌套子查询版本重新表述为使用链式CTE(
表达式)可能不会执行同样的操作,也不会产生相同的计划。在这种情况下,您会被难看的嵌套子查询所困扰,直到我们对
row\u to_json
进行了一些改进,或者找到了更直接地覆盖
row
构造函数中列名的方法


总之,一般来说,原则是要创建一个json对象,其中包含列
a、b、c
,并且希望只编写非法语法:

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
或:

此外,请记住,您可以编写
json
值,而无需额外引用,例如,如果您将
json\u agg
的输出放在
row\u-json
中,则内部的
json\u 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\u agg
产品,
[{“a”:1,“b”:2},{“a”:1,“b”:2}]
没有像
文本那样再次转义


这意味着您可以组合json操作来构造行,您不必总是创建非常复杂的PostgreSQL复合类型,然后在输出上调用
row\u to\u json

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

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;

在本例中,对象突出.users是一个视图。由于我选择了users.*,如果我需要更新视图以在用户记录中包含更多字段,我将不必更新此函数。

我添加此解决方案是因为接受的响应不考虑N:N关系。aka:对象集合的集合

如果你有N:N关系,那么从句
就是你的朋友。
在我的示例中,我想构建以下层次结构的树视图

A Requirement - Has - TestSuites
A Test Suite - Contains - TestCases.
下面的查询表示连接

SELECT reqId ,r.description as reqDesc ,array_agg(s.id)
            s.id as suiteId , s."Name"  as suiteName,
            tc.id as tcId , tc."Title"  as testCaseTitle

from "Requirement" r 
inner join "Has"  h on r.id = h.requirementid 
inner join "TestSuite" s on s.id  = h.testsuiteid
inner join "Contains" c on c.testsuiteid  = s.id 
inner join "TestCase"  tc on tc.id = c.testcaseid
  GROUP BY r.id, s.id;
由于不能进行多个聚合,因此需要使用“WITH”

它所做的是在项目的小集合中构建JSON对象,并使用
子句在每个
上聚合它们

结果:

[
  {
    "req_id": 1,
    "req_description": "<character varying>",
    "test_suites": [
      {
        "ts_id": 1,
        "name": "TestSuite",
        "test_cases": [
          {
            "tc_id": 1,
            "tc_title": "TestCase"
          },
          {
            "tc_id": 2,
            "tc_title": "TestCase2"
          }
        ]
      },
      {
        "ts_id": 2,
        "name": "TestSuite",
        "test_cases": [
          {
            "tc_id": 2,
            "tc_title": "TestCase2"
          }
        ]
      }
    ]
  },
  {
    "req_id": 2,
    "req_description": "<character varying> 2 ",
    "test_suites": [
      {
        "ts_id": 2,
        "name": "TestSuite",
        "test_cases": [
          {
            "tc_id": 2,
            "tc_title": "TestCase2"
          }
        ]
      }
    ]
  }
]
[
{
“请求id”:1,
“请求描述”:“请求”,
“测试套件”:[
{
"t_id":1,,
“名称”:“测试套件”,
“测试案例”:[
{
“tc_id”:1,
“tc_标题”:“测试用例”
},
{
“tc_id”:2,
“tc_标题”:“测试用例2”
}
]
},
{
"t_id":2,,
“名称”:“测试套件”,
“测试案例”:[
{
“tc_id”:2,
“tc_标题”:“测试用例2”
}
]
}
]
},
{
“请求id”:2,
“需求描述”:“2”,
“测试套件”:[
{
"t_id":2,,
“名称”:“测试套件”,
“测试案例”:[
{
“tc_id”:2,
“tc_标题”:“测试用例2”
}
]
}
]
}
]

它在设置代码中。插页。我费了好大劲才把一切都安排好,这样任何人都可以复制我的情况。如果我能再投你几次赞成票,我会的。“我很欣赏其中的细节,以及关于1:许多关系的一点。”德韦纳很乐意帮忙。感谢你努力写了一个好问题;我还想再把它往上撞几次。样本数据、Pg版本、预期输出、实际输出/错误;勾选所有方框,清晰易懂。所以谢谢。@muistooshort:一个提供类型的临时表也提供了服务,并在会话结束时自动删除。非常感谢9.4示例
json\u build\u object
将使我的生活变得更加轻松,但不知何故,当我看到发行说明时,我没有意识到这一点。有时候你只需要一个具体的例子就可以开始了。超级答案-同意文档应该突出显示
json\u build
A Requirement - Has - TestSuites
A Test Suite - Contains - TestCases.
SELECT reqId ,r.description as reqDesc ,array_agg(s.id)
            s.id as suiteId , s."Name"  as suiteName,
            tc.id as tcId , tc."Title"  as testCaseTitle

from "Requirement" r 
inner join "Has"  h on r.id = h.requirementid 
inner join "TestSuite" s on s.id  = h.testsuiteid
inner join "Contains" c on c.testsuiteid  = s.id 
inner join "TestCase"  tc on tc.id = c.testcaseid
  GROUP BY r.id, s.id;
with testcases as (
select  c.testsuiteid,ts."Name" , tc.id, tc."Title"  from "TestSuite" ts
inner join "Contains" c on c.testsuiteid  = ts.id 
inner join "TestCase"  tc on tc.id = c.testcaseid

),                
requirements as (
    select r.id as reqId ,r.description as reqDesc , s.id as suiteId
    from "Requirement" r 
    inner join "Has"  h on r.id = h.requirementid 
    inner join "TestSuite" s on s.id  = h.testsuiteid

    ) 
, suitesJson as (
 select  testcases.testsuiteid,  
       json_agg(
                json_build_object('tc_id', testcases.id,'tc_title', testcases."Title" )
            ) as suiteJson
    from testcases 
    group by testcases.testsuiteid,testcases."Name"
 ),
allSuites as (
    select has.requirementid,
           json_agg(
                json_build_object('ts_id', suitesJson.testsuiteid,'name',s."Name"  , 'test_cases', suitesJson.suiteJson )
            ) as suites
            from suitesJson inner join "TestSuite" s on s.id  = suitesJson.testsuiteid
            inner join "Has" has on has.testsuiteid  = s.id
            group by has.requirementid
),
allRequirements as (
    select json_agg(
            json_build_object('req_id', r.id ,'req_description',r.description , 'test_suites', allSuites.suites )
            ) as suites
            from allSuites inner join "Requirement" r on r.id  = allSuites.requirementid

)
 select * from allRequirements
[
  {
    "req_id": 1,
    "req_description": "<character varying>",
    "test_suites": [
      {
        "ts_id": 1,
        "name": "TestSuite",
        "test_cases": [
          {
            "tc_id": 1,
            "tc_title": "TestCase"
          },
          {
            "tc_id": 2,
            "tc_title": "TestCase2"
          }
        ]
      },
      {
        "ts_id": 2,
        "name": "TestSuite",
        "test_cases": [
          {
            "tc_id": 2,
            "tc_title": "TestCase2"
          }
        ]
      }
    ]
  },
  {
    "req_id": 2,
    "req_description": "<character varying> 2 ",
    "test_suites": [
      {
        "ts_id": 2,
        "name": "TestSuite",
        "test_cases": [
          {
            "tc_id": 2,
            "tc_title": "TestCase2"
          }
        ]
      }
    ]
  }
]