=> CREATE OR REPLACE FUNCTION update_catalog() RETURNS trigger AS $$ BEGIN INSERT INTO operations(book_id, qty_change) VALUES (OLD.book_id, NEW.onhand_qty - coalesce(OLD.onhand_qty,0)); RETURN NEW; END; $$ VOLATILE LANGUAGE plpgsql;
CREATE FUNCTION
=> CREATE TRIGGER update_catalog_trigger INSTEAD OF UPDATE ON catalog_v FOR EACH ROW EXECUTE PROCEDURE update_catalog();
CREATE TRIGGER
Добавляем к таблице книг поле наличного количества. Важно: не добавлять фразу DEFAULT, иначе эта операция будет обновлять все строки таблицы, удерживая блокировку - это долго и не нужно.
=> ALTER TABLE books ADD COLUMN onhand_qty integer;
ALTER TABLE
Триггерная функция для обновления количества (предполагаем, что поле onhand_qty не может быть пустым):
=> CREATE OR REPLACE FUNCTION update_onhand_qty() RETURNS trigger AS $$ BEGIN UPDATE books SET onhand_qty = onhand_qty + NEW.qty_change WHERE book_id = NEW.book_id; RETURN NEW; END; $$ VOLATILE LANGUAGE plpgsql;
CREATE FUNCTION
Дальше все происходит внутри транзакции.
=> BEGIN;
BEGIN
Блокируем операции на время транзакции:
=> LOCK TABLE operations;
LOCK TABLE
Начальное заполнение:
=> UPDATE books b SET onhand_qty = ( SELECT coalesce(sum(qty_change),0) FROM operations o WHERE o.book_id = b.book_id );
UPDATE 6
Теперь, когда поле заполнено, задаем ограничения:
=> ALTER TABLE books ALTER COLUMN onhand_qty SET DEFAULT 0;
ALTER TABLE
=> ALTER TABLE books ALTER COLUMN onhand_qty SET NOT NULL;
ALTER TABLE
=> ALTER TABLE books ADD CHECK(onhand_qty >= 0);
ALTER TABLE
Универсальнее был бы AFTER-триггер, но он дороже, а мы знаем, что qty_change не изменяется.
=> CREATE TRIGGER update_onhand_qty_trigger BEFORE INSERT ON operations FOR EACH ROW EXECUTE PROCEDURE update_onhand_qty();
CREATE TRIGGER
Готово.
=> COMMIT;
COMMIT
Теперь books.onhand_qty обновляется, но представление catalog_v по-прежнему вызывает функцию для подсчета количества. Хоть в исходном запросе обращение к функции синтаксически не отличается от обращения к полю, запрос был запомнен в другом виде:
=> \d+ catalog_v
View "bookstore.catalog_v" Column | Type | Modifiers | Storage | Description --------------+---------+-----------+----------+------------- book_id | integer | | plain | title | text | | extended | onhand_qty | integer | | plain | display_name | text | | extended | authors | text | | extended | View definition: SELECT b.book_id, b.title, onhand_qty(b.*) AS onhand_qty, book_name(b.book_id, b.title) AS display_name, authors(b.*) AS authors FROM books b ORDER BY (book_name(b.book_id, b.title)); Triggers: update_catalog_trigger INSTEAD OF UPDATE ON catalog_v FOR EACH ROW EXECUTE PROCEDURE update_catalog()
Пересоздадим представление:
=> CREATE OR REPLACE VIEW catalog_v AS SELECT b.book_id, b.title, b.onhand_qty, book_name(b.book_id, b.title) AS display_name, b.authors FROM books b ORDER BY display_name;
CREATE VIEW
Теперь функцию можно удалить.
=> DROP FUNCTION onhand_qty(books);
DROP FUNCTION
Небольшая проверка:
=> SELECT * FROM catalog_v WHERE book_id = 1;
book_id | title | onhand_qty | display_name | authors ---------+-----------------------+------------+-------------------------------------+---------------------------- 1 | Сказка о царе Салтане | 19 | Сказка о царе Салтане. Пушкин А. С. | Пушкин Александр Сергеевич (1 row)
=> INSERT INTO operations(book_id, qty_change) VALUES (1,+10);
INSERT 0 1
=> SELECT * FROM catalog_v WHERE book_id = 1;
book_id | title | onhand_qty | display_name | authors ---------+-----------------------+------------+-------------------------------------+---------------------------- 1 | Сказка о царе Салтане | 29 | Сказка о царе Салтане. Пушкин А. С. | Пушкин Александр Сергеевич (1 row)
=> INSERT INTO operations(book_id, qty_change) VALUES (1,-100);
ERROR: new row for relation "books" violates check constraint "books_onhand_qty_check" DETAIL: Failing row contains (1, Сказка о царе Салтане, -71). CONTEXT: SQL statement "UPDATE books SET onhand_qty = onhand_qty + NEW.qty_change WHERE book_id = NEW.book_id" PL/pgSQL function update_onhand_qty() line 3 at SQL statement