Передача записи в качестве аргумента функции PL/pgSQL

Сначала я действительно новичок в pl/pgsql. Это нужно для проекта.

Я застрял в этой (упрощенной) проблеме.

Моя схема db имеет отношение n к m (автор, книги, author_books)

Теперь я хочу иметь функцию insert/book pl/psgsql. (Я знаю, что все авторы, безусловно, уже находятся в таблице автора, поэтому я просто хочу передать их первичные ключи).

Этот контур функции - это то, что я имею в виду.

 create or replace function insert_book(book_to_insert book, authors integer[])
  returns void as $$
begin
    -- insert book into table books
    -- for each author add an entry to author_books table
end;
 $$ language plpgsql;

В качестве аргументов я решил передать запись типа книги и авторов, которые ее написали. Но как именно это будет работать? Я немного искал Google и не могу понять этого...

Вопрос 1: Является ли схема функции "правильной" /имеет смысл?

Вопрос 2: Как вставить книгу в книжную книгу? Должен ли я перебирать все поля книги (название, isbn, publisher,...) и добавлять их в инструкцию INSERT INTO или есть более "умный" способ?

Вопрос 3: Как я могу назвать свою функцию insert_book? Я нашел этот пример здесь (http://dbaspot.com/postgresql/206142-passing-record-function-argument-pl-pgsql.html), но это на самом деле не помогает мне. Для целей тестирования я использую оболочку, но позже мы будем использовать Java с JDBC.

Большое спасибо за вашу помощь.

Ответы

Ответ 1

Используя unnest() и изменяющий данные CTE (требуется Postgres 9.1 или новее), это может быть простой запрос SQL:

WITH x AS (SELECT '(1,foo_book)'::book AS _book
                , '{1,2,3}'::int[]     AS _authors)
   , y AS (
   INSERT INTO book  -- no column list, correct due to composite type
   SELECT (x._book).*
   FROM   x
   RETURNING book_id
   )
INSERT INTO author_book (book_id, author_id)
SELECT y.book_id, unnest(x._authors)
FROM   x,y;  -- CROSS JOIN ok, only 1 row for x and y

Первый CTE x предназначен только для упрощенного ввода данных и не является строго необходимым.

SQL Fiddle.

Что касается ваших вопросов:

Вопрос 1: Является ли схема функции "правильной" /имеет смысл?

Может быть проще передать базовые типы вместо составного типа book, но это совершенно правильный подход. Однако вы должны знать свой подход к синтаксису для сложных типов. Например, обратите внимание на скобки вокруг имени в моем примере: (x._book).*.

Функция plpgsql может выглядеть так:

CREATE OR REPLACE FUNCTION f_insert_book(_book book, _authors integer[])
   RETURNS void AS 
$func$
BEGIN
    WITH y AS (
        INSERT INTO book b
        SELECT (_book).*
        RETURNING b.book_id
        )
    INSERT INTO author_book (book_id, author_id)
    SELECT y.book_id, unnest(_authors)
    FROM   y;
END
$func$ LANGUAGE plpgsql;

Вопрос 2: Как вставить книгу в книжную книгу? (...) или есть "более умный" способ?

Более разумный способ - разложить составной тип на (variable_name).*.

Поскольку тип гарантированно соответствует table (будучи полученным из него), это один из редких случаев, когда это совершенно нормально, не предоставлять список столбцов для команды INSERT в сохраненном коде.

Вопрос 3: Как я могу назвать свою функцию insert_book?...

SELECT f_insert_book('(1,foo_book)'::book, '{1,2,3}'::int[]);

В других функциях plpgsql используйте PERFORM вместо SELECT, если вы не указали цель (INTO foo) для (несуществующих).

Ответ 2

Передача типа данных JSON (Postgresql 9.2 или новее):

CREATE OR REPLACE FUNCTION f_insert_book(_book json, _authors json)
   RETURNS void AS 
$$
BEGIN
-- insert book into table books
Insert into books values select * from json_populate_recordset(null:book, _book);
    -- for each author add an entry to author_books table
Insert into authors values select * from json_populate_recordset(null:authors, _authors);
end;
$$ language plpgsql;