2014-01-13 3 views
8

PostgreSQL 9.3에서 배열 내에 중첩 된 배열로 상당히 복잡한 JSON 객체를 저장하고 있습니다. 이 조각은 실제 데이터가 아니라 동일한 개념 보여PostgreSQL 9.3 이상에서 여러 수준으로 중첩 된 JSON 데이터를 쿼리하고 색인화하는 방법은 무엇입니까?

{ 
    "customerId" : "12345", 
    "orders" : [{ 
     "orderId" : "54321", 
     "lineItems" : [{ 
     "productId" : "abc", 
     "qty" : 3 
     }, { 
     "productId" : "def", 
     "qty" : 1 
     }] 
    } 
} 

내가 SQL 쿼리는 단지이 하나의 JSON 구조 내에서 ... lineItem 객체에서 작동 할 수있는 기능이 원하는,하지만 모든 JSON 객체에서 그 테이블 열에 예를 들어, 모든 구별 productId을 반환하는 SQL 쿼리와 그 총 합계는 qty입니다. 이러한 쿼리가 하루 종일 걸리는 것을 방지하려면 lineItem 또는 해당 하위 필드에 인덱스가 필요할 것입니다.

SELECT 
    line_item->>'productId' AS product_id, 
    SUM(CAST(line_item->>'qty' AS INTEGER)) AS qty_sold 
FROM 
    my_table, 
    json_array_elements(my_table.my_json_column->'orders') AS order, 
    json_array_elements(order->'lineItems') AS line_item 
GROUP BY product_id; 

을 그러나, 오히려이보다 더 깊은 한 단계 중첩 된 데이터 처리 원래의 StackOverflow의 질문 :

this StackOverflow question를 사용하여, 나는이 작동하는 쿼리를 작성하는 방법을 알아 냈어. 한 단계 더 깊은 다이빙에 추가 측면 조인을 추가하여 동일한 개념 (즉, FROM 절 내에서 "측면 조인")을 확장했습니다. 그러나 이것이 최상의 접근 방식인지 확실하지 않아서 제 질문의 첫 번째 부분은 무엇입니까? 수준의 임의의 숫자 인 JSON 객체에있는의 JSON 데이터를 쿼리하는 가장 좋은 방법은 무엇입니까?

두 번째 부분에서는 이러한 중첩 된 데이터에 대한 인덱스를 만들 때 this StackOverflow question은 한 수준 아래에만 중첩 된 데이터를 다시 처리합니다. 그러나, 나는 단지 완전히 잃어 버렸습니다. 머리 깊이를 더 깊게하는 방법을 생각해 보려고 노력했습니다. 누구든지 위의 lineItems과 같이 두 단계 이상 깊은 데이터를 색인하는 명확한 방법을 제안 할 수 있습니까? 무한 재귀 문제점을 해결하기위한

답변

2

, 각 테이블 행에 각 개별 JSON 요소에 사용하려면, recursive CTE를 사용해야합니다 :

WITH RECURSIVE 

raw_json as (

    SELECT 

    * 

    FROM 

    (VALUES 

    (1, 
    '{ 
    "customerId": "12345", 
    "orders": [ 
    { 
     "orderId": "54321", 
     "lineItems": [ 
     { 
      "productId": "abc", 
      "qty": 3 
     }, 
     { 
      "productId": "def", 
      "qty": 1 
     } 
     ] 
    } 
    ] 
}'::json), 

    (2, 
    '{ 
    "customerId": "678910", 
    "artibitraryLevel": { 
    "orders": [ 
     { 
     "orderId": "55345", 
     "lineItems": [ 
      { 
      "productId": "abc", 
      "qty": 3 
      }, 
      { 
      "productId": "ghi", 
      "qty": 10 
      } 
     ] 
     } 
    ] 
    } 
}'::json) 



) a(id,sample_json) 

), 


json_recursive as (

    SELECT 
    a.id, 
    b.k, 
    b.v, 
    b.json_type, 
    case when b.json_type = 'object' and not (b.v->>'customerId') is null then b.v->>'customerId' else a.customer_id end customer_id, --track any arbitrary id when iterating through json graph 
    case when b.json_type = 'object' and not (b.v->>'orderId') is null then b.v->>'orderId' else a.order_id end order_id, 
    case when b.json_type = 'object' and not (b.v->>'productId') is null then b.v->>'productId' else a.product_id end product_id 

    FROM 

    (

     SELECT 

     id, 
     sample_json v, 
     case left(sample_json::text,1) 
      when '[' then 'array' 
      when '{' then 'object' 
      else 'scalar' 
     end json_type, --because choice of json accessor function depends on this, and for some reason postgres has no built in function to get this value 
     sample_json->>'customerId' customer_id, 
     sample_json->>'orderId' order_id, 
     sample_json->>'productId' product_id 

     FROM 

     raw_json 
    ) a 
    CROSS JOIN LATERAL (

     SELECT 

     b.k, 
     b.v, 
     case left(b.v::text,1) 
      when '[' then 'array' 
      when '{' then 'object' 
      else 'scalar' 
     end json_type 


     FROM 

     json_each(case json_type when 'object' then a.v else null end) b(k,v) --get key value pairs for individual elements if we are dealing with standard object 

    UNION ALL 


     SELECT 

     null::text k, 
     c.v, 
     case left(c.v::text,1) 
      when '[' then 'array' 
      when '{' then 'object' 
      else 'scalar' 
     end json_type 


     FROM 

     json_array_elements(case json_type when 'array' then a.v else null end) c(v) --if we have an array, just get the elements and use parent key 


    ) b 


UNION ALL --recursive term 

    SELECT 
    a.id, 
    b.k, 
    b.v, 
    b.json_type, 
    case when b.json_type = 'object' and not (b.v->>'customerId') is null then b.v->>'customerId' else a.customer_id end customer_id, 
    case when b.json_type = 'object' and not (b.v->>'orderId') is null then b.v->>'orderId' else a.order_id end order_id, 
    case when b.json_type = 'object' and not (b.v->>'productId') is null then b.v->>'productId' else a.product_id end product_id 




    FROM 

    json_recursive a 
    CROSS JOIN LATERAL (

     SELECT 

     b.k, 
     b.v, 
     case left(b.v::text,1) 
      when '[' then 'array' 
      when '{' then 'object' 
      else 'scalar' 
     end json_type 


     FROM 

     json_each(case json_type when 'object' then a.v else null end) b(k,v) 


    UNION ALL 


     SELECT 

     a.k, 
     c.v, 
     case left(c.v::text,1) 
      when '[' then 'array' 
      when '{' then 'object' 
      else 'scalar' 
     end json_type 


     FROM 

     json_array_elements(case json_type when 'array' then a.v else null end) c(v) 

    ) b 

) 

그런 다음 임의의 ID로 중 하나를 수행 할 수 있습니다 합계 "수량". ..

SELECT 
    customer_id, 
    sum(v::text::integer) 

FROM 

    json_recursive 

WHERE 

    k = 'qty' 

GROUP BY 

    customer_id 

또는 당신이 "LINEITEM"개체를 얻을 당신이 원하는대로 그들을 조작 할 수 있습니다 :

SELECT 

    * 

FROM 

    json_recursive 

WHERE 

    k = 'lineItems' and json_type = 'object' 

는 색인에 관해서는, 당신은 당신의 원래 테이블의 각 행의 각 JSON 객체에 대한 고유 키를 반환하는 함수에 재귀 쿼리를 적용 할 수 그리고 그것은 당신의 JSON 열을 기능 인덱스를 만들 :

SELECT 

    array_agg(DISTINCT k) 

FROM 

    json_recursive 

WHERE 

    not k is null 
관련 문제