중첩 조인이있는 PostgreSQL 9.2 row_to_json ()


85

row_to_json()PostgreSQL 9.2에 추가 된 함수를 사용하여 쿼리 결과를 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이 경우에 필요하다고 생각하는 문을 더 삽입 할 수 없습니다 .

예를 들어 ::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"
         }
      }
   }
}

도움을 주시면 감사하겠습니다. 읽어 주셔서 감사합니다.


1
설정 코드에 있습니다. 삽입물. 누구나 내 상황을 재현 할 수 있도록 모든 것을 설정하는 데 어려움을 겪었습니다.
dwerner

답변:


161

업데이트 :에서 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;

이전 버전의 경우 계속 읽으십시오.


단일 행에 국한되지 않고 약간 고통 스럽습니다. 를 사용하여 복합 행 유형의 별칭을 지정할 수 없으므로 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(...))일대 다 관계인 btw 를 사용하고 싶을 것 입니다.

위의 쿼리는 이상적으로 다음과 같이 작성할 수 있어야합니다.

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보다 직접적 으로 재정의하는 방법이나 개선 사항을 얻을 때까지 추악한 중첩 하위 쿼리에 갇혀 있습니다.


어쨌든 일반적으로 원칙은 열을 사용하여 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

또는:

(SELECT x FROM (SELECT a, b, c) AS x(name1, name2, name3)) AS outername

또한 json추가 인용없이 값을 작성할 수 있습니다 . 예를 들어 a의 출력을 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것입니다.

당신이 수있는이 수단을 구성하는 행을 구성하는 작업을 JSON, 당신은 항상 상당히 복잡한 PostgreSQL의 복합 형 다음 전화를 만들 필요가 없습니다 row_to_json출력에.


2
답변을 몇 번 더 찬성 할 수 있다면 그렇게 할 것입니다. 나는 세부 사항과 일대 다 관계에 대해 감사합니다.
dwerner

7
@dwerner 기꺼이 도와드립니다. 좋은 질문을 작성해 주셔서 감사합니다. 나는 범프 싶습니다 그것을 너무 몇 번을. 샘플 데이터, Pg 버전, 예상 출력, 실제 출력 / 오류; 모든 상자를 체크하고 명확하고 이해하기 쉽습니다. 감사합니다.
Craig Ringer

1
@muistooshort : 유형을 제공하는 임시 테이블도 제공하며 세션이 끝나면 자동으로 삭제됩니다.
Erwin Brandstetter 2014 년

1
9.4 예제에 감사드립니다. json_build_object내 삶을 훨씬 더 쉽게 만들어 줄 것이지만, 릴리즈 노트를봤을 때 어떻게 든 이해하지 못했습니다. 때로는 시작하기 위해 구체적인 예가 필요합니다.
Jeff

1
슈퍼 답변-문서가 json_build_object좀 더 강조되어야한다는 데 동의합니다 . 이것은 진정한 게임 체인저입니다.
bobmarksie

1

장기적으로 유지 관리에 대한 제 제안은 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. *를 선택했기 때문에 사용자 레코드에 더 많은 필드를 포함하도록보기를 업데이트해야하는 경우이 함수를 업데이트 할 필요가 없습니다.


1

수락 된 응답이 N : N 관계를 고려하지 않기 때문에이 솔루션을 추가하고 있습니다. 일명 : 개체 컬렉션 모음

N : N 관계가있는 경우 clausula with 는 당신의 친구입니다. 제 예에서는 다음 계층 구조의 트리 뷰를 작성하고 싶습니다.

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"를 사용해야합니다.

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

그것이하는 일은 작은 항목 컬렉션에서 JSON 객체를 빌드하고 각 항목을 집계하는 것입니다 with.

결과:

[
  {
    "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"
          }
        ]
      }
    ]
  }
]
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.