Как передать тип таблицы с полем массива в функцию в postgresql

8

у меня есть стол под названием книга

CREATE TABLE book
(
  id smallint NOT NULL DEFAULT 0,       
  bname text,       
  btype text,
  bprices numeric(11,2)[],
  CONSTRAINT key PRIMARY KEY (id )
)

и функция save_book

CREATE OR REPLACE FUNCTION save_book(thebook book)
  RETURNS text AS
$BODY$
DECLARE 
myoutput text :='Nothing has occured';
BEGIN

    update book set 
    bname=thebook.bname,
    btype=thebook.btype,bprices=thebook.bprices  WHERE id=thebook.id;

    IF FOUND THEN
        myoutput:= 'Record with PK[' || thebook.id || '] successfully updated';
        RETURN myoutput;
    END IF;

    BEGIN
        INSERT INTO book values(thebook.id,thebook.bname,thebook.btype,
        thebook.bprices);
        myoutput:= 'Record successfully added';           
    END;
 RETURN myoutput;

    END;
$BODY$
  LANGUAGE plpgsql VOLATILE
  COST 100;

теперь, когда я вызываю функцию

SELECT save_book('(179,the art of war,fiction,{190,220})'::book);

я понял ошибку

ERROR: malformed array literal: "{190"
SQL state: 22P02
Character: 18

я не понимаю, потому что я не вижу ошибок в формате массива, любая помощь?

Indago
источник
Ваша функция выглядит немного усложненной для меня, но всегда. Буквальное массив должен быть заключен в одинарные кавычки, так что попробуйте следующее: ave_book((179,the art of war,fiction,'{190,220}')::book. Построенный ряд не нуждается в кавычках.
Дезсо
когда я запускаю это, я получаю ошибку ERROR: syntax error at or near "art"
indago
1
Отдельные строковые литералы должны быть заключены в кавычки.
Андрей М,
Извините, правильный ave_book((179, 'the art of war', 'fiction', '{190,220}')::book, как сказал Андрей.
Дезсо
@dezso: мне кажется ответ. :)
Андрей М,

Ответы:

7

Такие вещи становятся сложными. Я работаю над некоторыми смежными проектами прямо сейчас. Основной трюк заключается в том, что PostgreSQL использует формат, который использует двойные кавычки внутри в представлении кортежа для представления литеральных значений, поэтому:

SELECT save_book('(179,the art of war,fiction,"{190,220}")'::book);

должно сработать. По сути, уловка заключается в создании CSV и заключении в идентификаторы кортежей или массивов. Большая проблема в том, что вам приходится иметь дело с побегом (удваивать кавычки на каждом уровне по мере необходимости). Таким образом, следующее в точности эквивалентно:

SELECT save_book('(179,"the art of war","fiction","{""190"",""220""}")'::book);

Второй подход заключается в использовании конструктора строк:

SELECT save_book(row(179,'the art of war','fiction', array[190,220])::book);

Первое решение имеет очевидное преимущество, заключающееся в возможности использовать существующие программные среды для генерации и выхода из CSV. Второй самый чистый в SQL. Они могут быть смешаны и сопоставлены.

Крис Траверс
источник
+1 за использование конструктора массива, побег может быть кошмаром,
говорит Джек, попробуй topanswers.xyz
@Chris, первое решение кажется хорошим, и я думаю, что оно лучшее!
Индиго
@JackDouglas, одна вещь, на которую мы обращаем внимание, - это первый маршрут в LSMB именно потому, что мы можем использовать уже работающие фреймворки CSV.
Крис Трэверс
6

Если вы когда-нибудь задумывались о правильном синтаксисе для типа строки, спросите Postgres. Следует знать:

SELECT b FROM book b LIMIT 1;  -- or: WHERE id = 179;

Который вернет текстовое представление вашей строки в правильном формате:

(179,"the art of war",fiction,"{190,220}")
  • Значения столбцов представлены в виде списка без кавычек, разделенного запятыми, заключенного в паретезы.

  • Двойные кавычки используются вокруг значений, если возможна неоднозначность, включая текст с пробелами. Хотя в данном конкретном случае двойные кавычки "the art of war"необязательны, двойные кавычки "{190,220}"необходимы для массива.

Заключите строку в одинарные кавычки, измените и проверьте:

SELECT '(333,the art of war,fiction,"{191,220,235}")'::book

Функция рассмотрена

Рассмотрим то, что мы обсуждали в связанном, предыдущем вопросе:
проблема с составным типом в функции UPSERT

Отдельный блок ( BEGIN .. END;) только полезно , если вы хотите , чтобы поймать рейз может. Поскольку блок с исключением несет некоторые накладные расходы, имеет смысл иметь отдельный блок, который никогда не может быть введен:EXCEPTIONINSERT

CREATE OR REPLACE FUNCTION save_book(thebook book)
  RETURNS text AS
$BODY$
BEGIN
   UPDATE book
   SET    bname =   thebook.bname
         ,btype =   thebook.btype
         ,bprices = thebook.bprices
   WHERE  id = thebook.id;

   IF FOUND THEN
      RETURN format('Record with PK[%s] successfully updated', thebook.id);
   END IF;

   BEGIN
      INSERT INTO book SELECT (thebook).*;
      RETURN format('Record with PK[%s] successfully inserted', thebook.id);

   EXCEPTION WHEN unique_violation THEN
      UPDATE book
      SET    bname =   thebook.bname
            ,btype =   thebook.btype
            ,bprices = thebook.bprices
      WHERE  id = thebook.id;
   END;

   RETURN format('Record with PK[%s] successfully updated', thebook.id);
END
$BODY$  LANGUAGE plpgsql

Остальное упростить :

CREATE OR REPLACE FUNCTION save_book(thebook book)
  RETURNS text AS
$BODY$
BEGIN
   UPDATE book
   SET    bname =   thebook.bname
         ,btype =   thebook.btype
         ,bprices = thebook.bprices
   WHERE  id = thebook.id;

   IF FOUND THEN
      RETURN format('Record with PK[%s] successfully updated', thebook.id);
   END IF;

   INSERT INTO book SELECT (thebook).*;
   RETURN format('Record with PK[%s] successfully inserted', thebook.id);
END
$BODY$  LANGUAGE plpgsql

Я также упростил ваше INSERTутверждение. Можно безопасно пропустить список столбцов из INSERT при данных обстоятельствах.

Эрвин Брандштеттер
источник
3

Хотя я не вижу реального преимущества вашего решения, я имею в виду передачу строки функции вместо передачи отдельных значений, как в

CREATE OR REPLACE FUNCTION save_book2(
      integer
    , text
    , text
    , integer[]
)
RETURNS text AS
...

В любом случае, ваше решение также работает, если вы правильно вызываете функцию:

SELECT ave_book((179, 'the art of war', 'fiction', '{190,220}')::book);

То есть выражение записи не нуждается в кавычках, в то время как текстовые значения и литерал массива нужны.

Dezso
источник
Я сделал эту функцию для обработки всех вставок и обновлений, защищающих приложение от непосредственного обращения с таблицами. Как вы думаете, наличие этой функции не дает никаких преимуществ?
Индиго
Мои 0,02 доллара на дизайнерское решение. Я думаю, что у этого есть большое преимущество, если у вас есть код приложения, чтобы найти структуру вовлеченного типа и построить аргумент для вас. Это дает вам обнаруживаемый API, который очень полезен. Без этого, однако, это означает, что если ваша структура таблицы изменится, у вас появятся дополнительные места для внесения изменений, которые определенно не годятся. Я говорю, что это тот, кто двигает мое развитие в том направлении, куда вы идете, и в целом я думаю, что это хорошая идея. Это имеет опасности, хотя.
Крис Трэверс