Агрегирование ключей подзапроса для построения сложного объекта

#json #postgresql

#json #postgresql

Вопрос:

У меня есть таблица, которая описывает список продуктов, изготовленных для данного заказа, в которой каждая строка в этой таблице должна содержать идентификатор продукта и причину, по которой он был приобретен,

Я хотел бы создать ответ в формате json, который представляет собой массив местоположений, для которых предназначен заказанный продукт, внутри этого массива уникальных кодов продукта и с этим массивом причин, почему этот уникальный продукт был заказан.

Мне удалось определить только самую верхнюю часть моего запроса, но характер соединения и подвыбора запроса на самом деле ставит меня в затруднительное положение. Действительно ли такое возможно в plpgsql?

Кроме того, я хотел бы присоединиться к product_order.reason на product_order_reason.id и извлекаем longform_text внутри таблицы, связанной с этой строкой, но я полагаю, что важнее вообще получить возврат, и вот тут я оказался в тупике.

 product
id      |name                    |cost  |cost_rate|
--------|------------------------|------|---------|
WALLC   |Wall Clock              | 15.00|SINGLE   |
MIRR    |Mirror                  | 25.00|SINGLE   |
KEY     |Door Keys               |  5.00|SINGLE   |
KEYFOB  |Key Fob                 | 40.00|SINGLE   |



product_order
product_id|quantity|location  |quote_detail_quote_id               |is_primary_order|reason|
----------|--------|----------|------------------------------------|----------------|------|
MIRR      |       2|floor_0   |C7D33FED-CB15-5796-DC7D-A7BCEA8923C5|true            |     1|
KEYF      |       3|floor_0   |C7D33FED-CB15-5796-DC7D-A7BCEA8923C5|true            |     2|
WALLC     |       3|floor_1   |C7D33FED-CB15-5796-DC7D-A7BCEA8923C5|true            |     1|
WALLC     |       3|floor_1   |C7D33FED-CB15-5796-DC7D-A7BCEA8923C5|true            |     3|
product_order_reason
------------------------------------------------
id (varchar, pk) | shortform_text(varchar) | longform_text(varchar)
------------------------------------------------


id|shortform_text                       |longform_text                                        |
--|-------------------------------------|-----------------------------------------------------|
 1|Employee Room                        |Standard employee room with no window                |
 2|Meeting Room                         |Standard Meeting Room                            |
 3|Mirror                               |Additional Mirror Request         |


  
 create
or replace
function get_breakdown_v1_0_0(p_quote_id character varying,
p_location character varying,
p_product_code character varying) returns json language plpgsql as $function$ declare row_count smallint := 0;
begin
raise notice 'Location: %',
p_location;

raise notice 'Product: %',
p_product_code;
-- Perform santiy check on quote_id so that the json does not include a null result.
 select
    count(*) into
        strict row_count
    from
        quote_detail
    where
        quote_id = p_quote_id;

if row_count = 0 then raise 'Quote ID % not found',
p_quote_id
    using ERRCODE = '02000';
-- SQL standard no_data
 elseif row_count > 1 then raise 'Too many rows returned for ID %',
p_quote_id
    using ERRCODE = 'P0003';
-- PL/pgSQL too_many_rows
end if;
-- Returns an object comprised of unique values for locations, where not null and their associated products
 return (
select
    jsonb_build_object ('locations',jsonb_agg( jsonb_build_object( 'area', location, 'items', items)))
from
    (
    select
        location,
        jsonb_agg(jsonb_build_object ('code', product_id, 'reasons', reason)) as items 
    from
        product_order
    where
        (quote_detail_quote_id = p_quote_id)
        and (location = p_location
        or p_location is null)
        and (product_id = p_product_code
        or p_product_code is null)
    group by
        location) a );
end $function$ ;
  

Желаемый ответ;

         {
            "area": "floor_0",
            "items": [
                {
                    "code": "WALLC",
                    "reasons": [
                        {
                            "quantity": 2,
                            "reason_code": "Standard Employee Room"
                        },
                        {
                            "quantity": 2,
                            "reason_code": "Standard Cubicle"
                        }
                    ]
                },
                {
                    "code": "MIRR",
                    "reasons": [
                        {
                            "quantity": 3,
                            "reason_code": "Meeting Room"
                        }
                    ]
                }
            ]
        }]
  

Комментарии:

1. Не могли бы вы привести пример таблицы или ввода и текущего вывода, пожалуйста?

2. Готово! спасибо, что напомнили мне!

Ответ №1:

Хорошо, я думаю, у меня есть кое-что для вас. Идея состоит в том, чтобы создавать по одному из массивов за раз и передавать необходимую оставшуюся информацию во внешние запросы для дальнейшего построения массива. Вы можете добавить свои ограничения для quote_detail_quote_id , location и product_id к WHERE предложению самого внутреннего запроса.

SQLFiddle, чтобы показать это в действии.

Это может потребовать некоторого изучения:

 SELECT json_build_object('area', t3.location, 'items', t3.code_json)
FROM
(
    SELECT t2.location
           , array_to_json(array_agg(jsonb_build_object('code', t2.product_id, 'reasons', t2.qty_reason_json))) AS code_json
    FROM
    (
      SELECT t.location
             , t.product_id
             , array_to_json(array_agg(jsonb_build_object('quantity', t.quantity, 'reason_code', t.longform_text))) AS qty_reason_json
      FROM
      (
          SELECT po.product_id
                 , po.quantity
                 , po.location
                 , po.reason
                 , por.longform_text
          FROM product_order po
          JOIN product_order_reason por ON (por.id = po.reason) 
          WHERE quote_detail_quote_id = 'C7D33FED'
      ) t
      GROUP BY t.location, t.product_id
    ) t2
    GROUP BY t2.location
) t3
;
  

Комментарии:

1. Это действительно генерирует тело, как я и ожидал. Я хотел обернуть этот запрос вокруг ключа ‘locations’ для справки, это SQLFiddle , который я использовал для достижения этой цели, спасибо за вашу помощь.