Как выполнить запрос в качестве аргумента в функции PL / pg SQL, которая возвращает JSON? - PullRequest
0 голосов
/ 18 июня 2020

Я пытаюсь выполнить выбор, используя уже созданную функцию, которая возвращает JSON, который объединяется в массив JSON, но у меня возникает проблема в строке, где я выполняю предложение SELECT. Таблицы, при запросе которых возникают проблемы:

create table orders (
    order_id bigserial not null, -- this is Primary Key     
    total double precision,
    order_date timestamp,
    user_id bigint -- references `users` table 
);

create table order_item
(
    order_item_id bigserial not null, --primary key
    amount integer,
    book_id bigint, -- FK which references `book` table 
    order_id bigint -- FK which references `orders` table
);

Мой запрос выглядит так в PL / pg SQL:

create or replace function public.get_order_by_order_id(o_id bigint) returns json as
$BODY$
DECLARE
    order_items json;
    found_order "vertx-jooq-cr".public.orders;
    found_user json;
begin
    -- other queries left out for code brevity
    
    select json_agg(x) INTO order_items 
    from (select public.get_orderitem_by_oi_id(
        select oi.order_item_id -- this is where PROBLEM occurs !!!
        from public.order_item AS oi
        where oi.order_id = o_id)
    ) x;

    return (select json_build_object(
        'order_id', found_order.order_id,
        'total_price', found_order.total,
        'order_date', found_order.order_date,
        'user', found_user,
        'order_items', order_items
    ));
end
$BODY$
language 'plpgsql';

... а вот функция get_orderitem_by_oi_id(order_item_id::bigint) (которая работает правильно и возвращает JSON): В строке, где помещается комментарий « - здесь ПРОБЛЕМА !!! », я получаю сообщение об ошибке / предупреждение в DataGrip , в котором говорится:

')' или ORDER ожидался, получил 'select'

Вот как мой JSON должен выглядеть:

{
  "order_id": 21,
  "total_price": 89.92,
  "order_date": "2020-05-03 00:00:00",
  "order_items": [
    {
      "order_item_id": 32,
      "amount": 3,
      "book": {
        "book_id": 2,
        "title": "Murder on the Orient Express",
        "price": 19.98,
        "amount": 151,
        "deleted": false,
        "authors": [
          {
            "author_id": 1,
            "first_name": "Agatha",
            "last_name": "Christie"
          }
        ],
        "categories": [
          {
            "category_id": 9,
            "name": "Crime",
            "deleted": false
          }
        ]
      },
      "order_id": 21,
      "total_order_item_price": 59.94
    },
    {
      "order_item_id": 31,
      "amount": 2,
      "book": {
        "book_id": 5,
        "title": "Harry Potter and the Prisoner of Azkaban",
        "price": 14.99,
        "amount": 85,
        "deleted": false,
        "authors": [
          {
            "author_id": 4,
            "first_name": "JK",
            "last_name": "Rowling"
          }
        ],
        "categories": [
          {
            "category_id": 3,
            "name": "Tragedy",
            "deleted": false
          }
        ]
      },
      "order_id": 21,
      "total_order_item_price": 29.98
    }
  ],
  "user": {
    "user_id": 1,
    "username": "test"
  }
}

My вопрос в том, можно ли запросить адекватный order_item_id ( from order_item table) с использованием функции get_orderitem_by_oi_id(order_item_id::bigint), передав адекватный order_id в запросе, и если нет, есть ли другой подходящий способ добиться этого? Приветствуется любая помощь в решении этой проблемы.

PS Версия PostgreSQL - 11,8

UPDATE1 :

Я отредактировал свою функцию Pl / pg SQL и теперь выглядит так:

create or replace function public.get_order_by_order_id(o_id bigint) returns json as $BODY$
DECLARE
    order_items json;
    found_order "vertx-jooq-cr".public.orders;
    found_user json;
    _item_id bigint; -- left it here from @Adrian Klaver's 1st version answer
    _oitems_ids bigint[];
    item_recs RECORD;
begin
    -- other necessary queries left out for code brevity

    FOR item_recs IN SELECT oi.order_item_id into _item_id -- gives an error
        FROM public.order_item AS oi WHERE oi.order_id = o_id
    
    LOOP
        --- Will need to modify to get your final JSON structure.
        SELECT json_agg(x) INTO order_items
        FROM (SELECT public.get_orderitem_by_oi_id(item_recs.order_item_id)) x;
    END LOOP;

    return (select json_build_object(
        'order_id', found_order.order_id,
        'total_price', trunc(found_order.total::double precision::text::numeric, 2),
        'order_date', found_order.order_date,
        'user', found_user,
        'order_items', item_recs -- updated value from 'order_items'
    ));
end
$BODY$
language 'plpgsql';

Ошибка, которую я получаю при выполнении функции:

ОШИБКА: невозможно открыть запрос SELECT в качестве курсора КОНТЕКСТ: PL / pg SQL функция get_order_by_order_id (bigint) строка 21 в FOR по строкам SELECT SQL state: 42P11

К сожалению, у меня нет опыта работы с курсорами в PostgreSQL и PL / pg SQL (начал изучать PL / pg SQL менее чем через неделю go). Есть идеи, на что указывает эта ошибка и как ее исправить? Заранее спасибо.

UPDATE2: Я отредактировал свой запрос (точнее, FOR-L OOP часть), выполнил его и получил в результате:

{
  "order_id": 1069,
  "total_price": 136.94,
  "order_date": "2020-06-10T19:57:40.562",
  "user": 3,
  "order_items": {
    "order_item_id": 2042
  }
}

Кстати, у order_id есть два элемента order_items с идентификатором order_item_id 2041 и 2042, что означает, что только второй элемент order_item «пойман». Также неполный объект JSON строится из функции public.get_orderitem_by_oi_id(oi_id **bigint**). Любой совет, как это исправить?

UPDATE3 : В комментариях я должен подчеркнуть, что public.get_orderitem_by_oi_id(_item_id) function RETURNS JSON type, и вот моя обновленная функция, которая теперь выглядит так:

create or replace function public.get_order_by_order_id6(o_id bigint) returns json as $BODY$
DECLARE
    order_items json;
    found_order "vertx-jooq-cr".public.orders;
    found_user json;
    _item_id bigint;
    _oitems_ids bigint[];
    item_recs RECORD;
begin
    select * into found_order
    from "vertx-jooq-cr".public.orders
    where order_id = o_id;
    -- other queries left out for code brevity

    FOR _item_id IN SELECT DISTINCT oi.order_item_id
        FROM public.order_item AS oi WHERE oi.order_id = o_id
    LOOP
        --- Will need to modify to get your final JSON structure.
        SELECT json_agg(x) INTO order_items
        FROM (SELECT public.get_orderitem_by_oi_id(_item_id)) x;
    END LOOP;

    return (select json_build_object(
        'order_id', found_order.order_id,
        'total_price', trunc(found_order.total::double precision::text::numeric, 2),
        'order_date', found_order.order_date,
        'user', found_user,
        'order_items', order_items
    ));
end
$BODY$
language 'plpgsql';

... и это результат Я получаю (в JSON, конечно):

{
  "order_id": 1069,
  "total_price": 136.93,
  "order_date": "2020-06-10T19:57:40.562",
  "user": {
    "user_id": 3,
    "username": "mica"
  },
  "order_items": [
    {
      "get_orderitem_by_oi_id": { -- for some reason it INSERTS function name HERE!!!
        "order_item_id": 2042,
        "amount": 2,
        "book": {
          "book_id": 8,
          "title": "The Lord of the Rings",
          "price": 23.5,
          "amount": 298,
          "is_deleted": false,
          "authors": [
            {
              "author_id": 3,
              "first_name": "JRR",
              "last_name": "Tolkien"
            }
          ],
          "categories": [
            {
              "category_id": 9,
              "name": "Crime",
              "is_deleted": false
            }
          ]
        },
        "order_id": 1069,
        "total_order_item_price": 59.96
      }
    }
  ]
}

По какой-то причине он продолжает получать ТОЛЬКО ПОСЛЕДНЮЮ запись выбранного order_itemS и продолжает вставлять имя функции, полученное в результате (указано под " - почему-то ВСТАВЛЯЕТ имя функции ЗДЕСЬ !!! " комментарий в коде JSON). Есть идеи, как агрегировать / собирать ВСЕ записи в order_items json переменную?

PS Я экспериментировал с отдельной функцией с FOR-L OOP где переменные одинаковы (кроме order_items, который имеет тип json [] ):

FOR _item_id IN SELECT DISTINCT oi.order_item_id FROM public.order_item AS oi WHERE oi.order_id = o_id
    LOOP
        order_items := order_items || json_build_object('order_item_id', _item_id);
    END LOOP;

.. и он дал ВСЕ идентификаторы для order_item (2041 и 2042, НЕ только 2042 как get_order_by_order_id6() функция).

UPDATE4: Вот функция get_orderitem_by_oi_id(order_item_id::bigint) (которая работает правильно и возвращает JSON):

create or replace function get_orderitem_by_oi_id(oi_id bigint) returns json
    language plpgsql
as
$FUNCTION$
declare
    found_oi "vertx-jooq-cr".public.order_item;
    book_json json;
    total_oi_price decimal;
    book_price double precision;
begin
    select * into found_oi
    from public.order_item AS oi2
    where oi2.order_item_id = oi_id;

    select public.get_book_by_book_id(public.order_item.book_id::bigint) into book_json
    from public.order_item
    where public.order_item.order_item_id = oi_id;

    select price into book_price
    from book AS b
    inner join public.order_item AS oi USING (book_id);

    total_oi_price = found_oi.amount * book_price;
    return (select json_build_object(
        'order_item_id', found_oi.order_item_id,
        'amount', found_oi.amount,
        'book', book_json,
        'order_id', found_oi.order_id,
        'total_order_item_price', trunc(total_oi_price::double precision::text::numeric, 2)
    ));
end
$FUNCTION$;

Ответы [ 3 ]

1 голос
/ 23 июня 2020

Получив полезные советы от @AdrianKlaver, я пришел к решению, объявив дополнительные необходимые переменные для обработки каждого order_item в FOR-L OOP и запросы для получения окончательного (необходимого) результата. Вот окончательное решение, которое работает так, как должно (внесены изменения вторым ответом @ AdrianKlaver):

create or replace function public.get_order_by_order_id8(o_id bigint) returns json as
$BODY$
DECLARE
    total_oi_price double precision;
    book_price double precision;
    total_price double precision;
    oi_amount integer;
    order_items json;
    item_recs RECORD;
    book_json json;
    single_order_item json;
    found_order public.orders;
    found_user json;
    item_array json[];
BEGIN
    select * into found_order
    from public.orders
    where order_id = o_id;

    select json_build_object('user_id', public.users.user_id, 'username', public.users.username)
    into found_user
    from public.users
    INNER JOIN public.orders as o USING (user_id)
    WHERE o.order_id = o_id;

    total_price = 0.00;

    FOR item_recs IN SELECT *
        FROM public.order_item AS oi WHERE oi.order_id = o_id
    LOOP
        select public.get_book_by_book_id(item_recs.book_id) into book_json;

        select price INTO book_price FROM book AS b WHERE b.book_id = item_recs.book_id;
        oi_amount = item_recs.amount;

        total_oi_price = book_price * oi_amount;

        SELECT json_build_object('order_item_id', item_recs.order_item_id,
        'amount', item_recs.amount,
        'book', book_json,
        'order_id', item_recs.order_id,
        'total_order_item_price', trunc(total_oi_price::double precision::text::numeric, 2)) INTO single_order_item;
        total_price := total_price + total_oi_price;
        item_array = array_append(item_array, single_order_item);
    END LOOP;
    order_items = array_to_json(item_array);

    return (select json_build_object(
        'order_id', found_order.order_id,
        'total_price', trunc(total_price::double precision::text::numeric, 2),
        'order_date', found_order.order_date,
        'user', found_user,
        'order_items', order_items
    ));

end;
$BODY$
LANGUAGE 'plpgsql';

... и вот результат JSON, который я получаю:

{
  "order_id": 1069,
  "total_price": 136.94,
  "order_date": "2020-06-10T19:57:40.562",
  "user": {
    "user_id": 3,
    "username": "mica"
  },
  "order_items": [
    {
      "order_item_id": 2041,
      "amount": 3,
      "book": {
        "book_id": 6,
        "title": "The Da Vinci Code",
        "price": 29.98,
        "amount": 297,
        "is_deleted": false,
        "authors": [
          {
            "author_id": 8,
            "first_name": "William",
            "last_name": " Shakespeare"
          }
        ],
        "categories": [
          {
            "category_id": 10,
            "name": "Action",
            "is_deleted": false
          }
        ]
      },
      "order_id": 1069,
      "total_order_item_price": 89.94
    },
    {
      "order_item_id": 2042,
      "amount": 2,
      "book": {
        "book_id": 8,
        "title": "The Lord of the Rings",
        "price": 23.5,
        "amount": 298,
        "is_deleted": false,
        "authors": [
          {
            "author_id": 3,
            "first_name": "JRR",
            "last_name": "Tolkien"
          }
        ],
        "categories": [
          {
            "category_id": 9,
            "name": "Crime",
            "is_deleted": false
          }
        ]
      },
      "order_id": 1069,
      "total_order_item_price": 47
    }
  ]
}

Получается, что get_orderitem_by_oi_id() было достаточно и необходимый результат может быть без него .

1 голос
/ 23 июня 2020

Что я вижу в твоем ответе. Это:

        from public.order_item
        where public.order_item.order_item_id IN (item_recs.order_item_id);

должно быть сокращено до этого:

 select public.get_book_by_book_id(item_recs.book_id) into book_json;

, поскольку вы уже используете уникальную запись.

Это:

select amount INTO oi_amount FROM order_item AS oi WHERE oi.amount = item_recs.amount;

должно быть:

oi_amount = item_recs.amount;

, потому что запрос может выбрать более одной суммы из order_item, если имеется более одной записи с oi.amount = item_recs.amount. Также вы говорите, что item_recs.amount - это сумма, которую вы все равно ищете.

1 голос
/ 18 июня 2020

Вы не можете передать запрос функции в качестве аргумента. Что вы можете сделать, это что-то вроде:

DECLARE
    order_items json;
    found_order "vertx-jooq-cr".public.orders;
    found_user json;
    item_recs RECORD;

BEGIN

 FOR item_recs IN select oi.order_item_id
    from public.order_item AS oi where oi.order_id = o_id
 LOOP
   --- Will need to modify to get your final JSON structure.
   select json_agg(x) INTO order_items 
     from (select public.get_orderitem_by_oi_id(item_recs.order_item_id)
     ) x;
  END LOOP;
...

Вышеуказанное происходит отсюда:

https://www.postgresql.org/docs/12/plpgsql-control-structures.html#PLPGSQL -RECORDS-ITERATING

Альтернативный метод , без использования L OOP:

create table orders (
    order_id bigserial not null, -- this is Primary Key     
    total double precision,
    order_date timestamp,
    user_id bigint -- references `users` table 
);
create table order_item
(
    order_item_id bigserial not null, --primary key
    amount integer,
    book_id bigint, -- FK which references `book` table 
    order_id bigint -- FK which references `orders` table
);
insert into orders (total, order_date, user_id) values (100, '06/20/2020', 1);
insert into orders (total, order_date, user_id) values (250, '06/20/2020', 2);
insert into order_item values (1, 45, 1, 1);
insert into order_item values (2, 55, 1, 1);
insert into order_item values (3, 50, 3, 2);
insert into order_item values (4, 100, 3, 2);
insert into order_item values (5, 75, 3, 2);
insert into order_item values (6, 25, 3, 2);

CREATE OR REPLACE FUNCTION public.get_order_by_order_id(o_id bigint)
 RETURNS json
 LANGUAGE plpgsql
AS $function$
DECLARE
    order_items json;
    found_order RECORD;
    item_recs RECORD;

BEGIN
    SELECT
        order_id, order_date, total, user_id
    INTO
        found_order
    FROM
        orders
    WHERE
        order_id = o_id;

    SELECT
        json_agg(row_to_json(x))
    INTO
        order_items
    FROM
        (SELECT
            *
        FROM
            order_item
        WHERE order_id = o_id) AS x;



RETURN (select json_build_object(
        'order_id', found_order.order_id,
        'total_price', trunc(found_order.total::double precision::text::numeric, 2),
        'order_date', found_order.order_date,
        'user', found_order.user_id,
        'order_items', order_items
    ));
END;
$function$
;


select * from  get_order_by_order_id (1);
                                                                                              
 {"order_id" : 1,
  "total_price" : 100.00, 
  "order_date" : "2020-06-20T00:00:00", 
  "user" : 1, 
  "order_items" : [{"order_item_id":1,"amount":45,"book_id":1,"order_id":1}{"order_item_id":2,"amount":55,"book_id":1,"order_id":1}]}

ОБНОВЛЕНИЕ 22.06.2020 с массивом. Это общий пример c, поскольку у меня нет времени на сборку всей вашей установки, но я считаю, что он должен показать вам, что необходимо сделать. В вашем случае захватите вывод get_orderitem_by_oi_id (), чтобы добавить его в массив. В приведенном ниже файле order_item представлена ​​тестовая таблица, которую я установил для хранения двух элементов из "order_items" выше. Остальные данные - фиктивные значения. Итак:

CREATE OR REPLACE FUNCTION public.json_array_example(o_id bigint)
 RETURNS json
 LANGUAGE plpgsql
AS $function$
DECLARE
    order_items json;
    item_recs RECORD;
    item_array json[];

BEGIN
    FOR item_recs IN SELECT order_item FROM hold_json
    LOOP
        item_array =  array_append(item_array, item_recs.order_item);
        

    END LOOP;

    order_items = array_to_json(item_array);
RETURN (select json_build_object(
        'order_id', o_id,
        'total_price', trunc(100::double precision::text::numeric, 2),
        'order_date', '06/22/2020',
        'user', 2,
        'order_items', order_items
    ));
END;
$function$

Вывод:

json_array_example                                          
------------------------------------------------------------------------------------------------------
 {"order_id" : 1, "total_price" : 100.00, "order_date" : "06/22/2020", "user" : 2, "order_items" : [{+
       "order_item_id": 32,                                                                          +
       "amount": 3,                                                                                  +
       "book": {                                                                                     +
         "book_id": 2,                                                                               +
         "title": "Murder on the Orient Express",                                                    +
         "price": 19.98,                                                                             +
         "amount": 151,                                                                              +
         "deleted": false,                                                                           +
         "authors": [                                                                                +
           {                                                                                         +
             "author_id": 1,                                                                         +
             "first_name": "Agatha",                                                                 +
             "last_name": "Christie"                                                                 +
           }                                                                                         +
         ],                                                                                          +
         "categories": [                                                                             +
           {                                                                                         +
             "category_id": 9,                                                                       +
             "name": "Crime",                                                                        +
             "deleted": false                                                                        +
           }                                                                                         +
         ]                                                                                           +
       },                                                                                            +
       "order_id": 21,                                                                               +
       "total_order_item_price": 59.94                                                               +
     },{                                                                                             +
       "order_item_id": 31,                                                                          +
       "amount": 2,                                                                                  +
       "book": {                                                                                     +
         "book_id": 5,                                                                               +
         "title": "Harry Potter and the Prisoner of Azkaban",                                        +
         "price": 14.99,                                                                             +
         "amount": 85,                                                                               +
         "deleted": false,                                                                           +
         "authors": [                                                                                +
           {                                                                                         +
             "author_id": 4,                                                                         +
             "first_name": "JK",                                                                     +
             "last_name": "Rowling"                                                                  +
           }                                                                                         +
         ],                                                                                          +
         "categories": [                                                                             +
           {                                                                                         +
             "category_id": 3,                                                                       +
             "name": "Tragedy",                                                                      +
             "deleted": false                                                                        +
           }                                                                                         +
         ]                                                                                           +
       },                                                                                            +
       "order_id": 21,                                                                               +
       "total_order_item_price": 29.98                                                               +
     }]}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...