नेस्टेड जॉन्स के साथ PostgreSQL 9.2 row_to_json ()


85

मैं उस row_to_json()फ़ंक्शन का उपयोग करके JSON को क्वेरी के परिणामों को मैप करने की कोशिश कर रहा हूं जो PostgreSQL 9.2 में जोड़ा गया था।

मुझे नेस्टेड ऑब्जेक्ट्स (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_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, इसलिए आपको प्रभाव प्राप्त करने के लिए किसी अन्य उपखंड अभिव्यक्ति या 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: यदि आप एक 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स्तंभ उपनाम को स्वीकार नहीं करते हैं । अफसोस की बात है।

शुक्र है, वे उसी का अनुकूलन करते हैं। योजनाओं की तुलना करें:

चूँकि CTEs अनुकूलन बाड़ हैं, जंजीर CTE ( WITHभाव) का उपयोग करने के लिए नेस्टेड सबक्वेरी संस्करण को रीफ़्रेश करने से भी प्रदर्शन नहीं हो सकता है, और उसी योजना में परिणाम नहीं होगा। इस मामले में आप बदसूरत नेस्टेड उप-प्रकारों के साथ फंस गए हैं, जब तक कि हम row_to_jsonएक ROWनिर्माणकर्ता में स्तंभ नामों को ओवरराइड करने के लिए कुछ सुधार या एक तरीका प्राप्त नहीं करते हैं।


वैसे भी, आम तौर पर, सिद्धांत यह है कि जहां आप कॉलम के साथ एक जोंस ऑब्जेक्ट बनाना चाहते हैं 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अतिरिक्त उद्धरण के बिना मूल्यों की रचना कर सकते हैं , उदाहरण के लिए यदि आप एक के 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
अगर मैं आपके उत्तर को एक दो बार और बढ़ा सकूं, तो मैं करूंगा। मैं विस्तार की सराहना करता हूं, और बिट के बारे में 1: कई रिश्ते।
dwerner

7
@dwerner मदद करने के लिए खुशी है। एक अच्छा प्रश्न लिखने का प्रयास करने के लिए धन्यवाद; मैं इसे कुछ और बार भी टक्कर देना चाहूंगा । नमूना डेटा, Pg संस्करण, अपेक्षित आउटपुट, वास्तविक आउटपुट / त्रुटि; सभी बक्से को टिक करता है, और स्पष्ट और समझने में आसान है। तो धन्यवाद।
क्रेग रिंगर

1
@ म्युइस्टोशॉर्ट: एक अस्थायी तालिका प्रकार कार्य करने के लिए भी प्रदान करता है, और सत्र के अंत में स्वचालित रूप से हटा दिया जाता है।
इरविन ब्रान्डेसटेटर

1
9.4 उदाहरण के लिए बहुत बहुत धन्यवाद। json_build_objectमेरे जीवन को बहुत आसान बनाने जा रहा है, लेकिन जब मैंने रिलीज़ नोट देखे तो किसी तरह मैंने इसे नहीं उठाया। कभी-कभी आपको शुरू करने के लिए बस एक ठोस उदाहरण की आवश्यकता होती है।
जेफ

1
सुपर जवाब - सहमत हूं कि प्रलेखन को json_build_objectथोड़ा और उजागर करना चाहिए - यह एक वास्तविक गेम चेंजर है।
bobmarksie

1

लंबे समय से अधिक समय तक बनाए रखने के लिए मेरा सुझाव अपनी क्वेरी के मोटे संस्करण का निर्माण करने के लिए एक दृश्य का उपयोग करना है, और फिर नीचे एक फ़ंक्शन का उपयोग करना है:

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 एक दृश्य है। चूंकि मैंने उपयोगकर्ताओं को चुना है। *, अगर मुझे उपयोगकर्ता रिकॉर्ड में अधिक फ़ील्ड शामिल करने के लिए दृश्य को अपडेट करने की आवश्यकता है, तो मुझे इस फ़ंक्शन को अपडेट नहीं करना होगा।


1

मैं इस समाधान को जोड़ रहा हूं क्योंकि स्वीकृत प्रतिक्रिया पर विचार नहीं करता है N: N संबंध। उर्फ: वस्तुओं के संग्रह का संग्रह

यदि आपके पास N: N संबंध है तो 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 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"
          }
        ]
      }
    ]
  }
]
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.