PostgreSQL 9.2 row_to_json () iç içe birleştirmelerle


85

row_to_json()PostgreSQL 9.2'de eklenen işlevi kullanarak bir sorgunun sonuçlarını JSON ile eşlemeye çalışıyorum .

Birleştirilmiş satırları iç içe geçmiş nesneler olarak göstermenin en iyi yolunu bulmakta güçlük çekiyorum (1: 1 ilişkiler)

İşte denediğim şey (kurulum kodu: tablolar, örnek veriler ve ardından sorgu):

-- 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$$;

Sorgunun kendisi:

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()Kullanırsam, ortaya çıkan alanları bir alt nesneye ayırabileceğimi buldum , ancak tek bir düzeyle sınırlı görünüyor. AS XXXBu durumda ihtiyacım olduğunu düşündüğümden daha fazla ifade ekleyemiyorum .

Bana sütun adları verildi, çünkü uygun kayıt türüne, örneğin ::user_roleso tablonun sonuçları durumunda.

İşte bu sorgu şunu döndürür:

{
   "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"
         }
      }
   }
}

Yapmak istediğim şey, birleşimler için JSON üretmektir (yine 1: 1 iyidir) ve bunların katıldıkları ebeveynlerin alt nesneleri olarak temsil edilmesini sağlayabilirim, yani aşağıdaki gibi:

{
   "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"
         }
      }
   }
}

Herhangi bir yardım takdir edilmektedir. Okuduğunuz için teşekkürler.


1
Kurulum kodunda var. Ekler. Herkesin durumumu kopyalayabilmesi için her şeyi ayarlama zahmetine girdim.
dwerner

Yanıtlar:


164

Güncelleme: In PostgreSQL 9.4 Bu çok artırır tanıtımıyla to_json, json_build_object, json_objectvejson_build_array bunun nedeni açıkça tüm alanları isim ihtiyacına ayrıntılı olsa,:

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;

Daha eski sürümler için okumaya devam edin.


Tek bir sıra ile sınırlı değil, sadece biraz sancılı. Bileşik satır türlerini kullanarak takma ad veremezsiniz AS, bu nedenle efekti elde etmek için takma adlı bir alt sorgu ifadesi veya CTE kullanmanız gerekir:

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/ aracılığıyla şunları üretir :

{
  "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(...))1: çok ilişkiniz olduğunda kullanmak isteyeceksiniz , btw.

Yukarıdaki sorgu ideal olarak şu şekilde yazılabilmelidir:

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;

... ancak PostgreSQL'in ROWkurucusu ASsütun takma adlarını kabul etmez . Ne yazık ki.

Neyse ki, aynı şekilde optimize ediyorlar. Planları karşılaştırın:

CTE'ler optimizasyon çitleri olduğundan, iç içe geçmiş alt sorgu sürümünün zincirlenmiş CTE'leri ( WITHifadeler) kullanacak şekilde yeniden ifade edilmesi de işe yaramayabilir ve aynı planla sonuçlanmayabilir. Bu durumda, bir kurucuda row_to_jsonsütun adlarını ROWdaha doğrudan geçersiz kılmanın bir yolunu veya bazı iyileştirmeler elde edene kadar çirkin iç içe geçmiş alt sorgularla sıkışıp kalıyorsunuz .


Her neyse, genel olarak prensip, sütunlarla bir json nesnesi oluşturmak istediğinizde a, b, cve sadece yasadışı sözdizimini yazabilmeyi dilediğinizde şudur :

ROW(a, b, c) AS outername(name1, name2, name3)

bunun yerine, satır tipi değerleri döndüren skaler alt sorgular kullanabilirsiniz:

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

Veya:

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

Ayrıca, oluşturabilirsiniz unutmayın jsonbir çıktısını koyarsanız ek, örneğin alıntı olmadan değerleri json_aggbir dahilinde row_to_json, iç json_aggsonuç dize olarak aktardığı almazsınız, bu json olarak doğrudan dahil olacak.

örneğin rastgele örnekte:

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
);

çıktı:

{"k1":1,
 "k2":2,
 "k3":[{"a":1,"b":2}, 
 {"a":1,"b":2}]}

Olması gerektiği gibi, json_aggüründen [{"a":1,"b":2}, {"a":1,"b":2}]tekrar kaçılmadığını unutmayın text.

Bu, satırlar oluşturmak için json işlemleri oluşturabileceğiniz anlamına gelir , her zaman çok karmaşık PostgreSQL bileşik türleri oluşturmak row_to_jsonve ardından çıktıyı çağırmak zorunda değilsiniz .


2
Cevabınızı birkaç kez daha yükseltebilseydim, yapardım. Ayrıntıları ve 1: birçok ilişkiyle ilgili kısmı takdir ediyorum.
dwerner

7
@dwerner Yardımcı olmaktan memnunum. İyi bir soru yazmaya çabaladığınız için teşekkür ederiz; Ben çarpmak istiyorum bunu çok birkaç kez daha yukarı. Örnek veriler, Pg sürümü, beklenen çıktı, gerçek çıktı / hata; tüm kutuları işaretler ve anlaşılır ve anlaşılması kolaydır. Çok teşekkürler.
Craig Ringer

1
@muistooshort: Tipi sağlamak için geçici bir tablo da hizmet verir ve oturumun sonunda otomatik olarak silinir.
Erwin Brandstetter

1
9.4 örneği için çok teşekkür ederim. json_build_objecthayatımı çok daha kolaylaştıracak ama bir şekilde sürüm notlarını gördüğümde anlamadım. Bazen başlamak için somut bir örneğe ihtiyaç duyarsınız.
Jeff

1
Süper cevap - belgelerin json_build_objectbiraz daha vurgulanması gerektiğini kabul edin - bu gerçek bir oyun değiştirici.
bobmarksie

2

Bu çözümü ekliyorum çünkü kabul edilen yanıt N: N ilişkisini düşünmüyor. aka: nesne koleksiyonlarının koleksiyonları

Eğer N: N ilişkiniz varsa clausula witho sizin arkadaşınızdır. Örneğimde, aşağıdaki hiyerarşinin bir ağaç görünümünü oluşturmak istiyorum.

A Requirement - Has - TestSuites
A Test Suite - Contains - TestCases.

Aşağıdaki sorgu birleşimleri temsil eder.

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;

Birden fazla toplama yapamayacağınız için "WITH" kullanmanız gerekir.

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

Yaptığı şey, JSON nesnesini küçük öğe koleksiyonunda oluşturmak ve bunları her bir withcümle üzerinde toplamaktır .

Sonuç:

[
  {
    "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"
          }
        ]
      }
    ]
  }
]

1

Uzun vadede sürdürülebilirlik için önerim, sorgunuzun kaba versiyonunu oluşturmak için bir GÖRÜNÜM kullanmak ve ardından aşağıdaki gibi bir işlev kullanmaktır:

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;

Bu durumda, prominence.users nesnesi bir görünümdür. Kullanıcıları. * Seçtiğimden beri, görünümü bir kullanıcı kaydına daha fazla alan eklemek için güncellemem gerekirse bu işlevi güncellemem gerekmeyecek.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.