練習使用plpgsql建立trigger規則。
使用的材料與lab5一致:
ER Design
schema文件如下:
-- Beer rating database
create table Location (
id serial primary key,
state varchar(50), -- not every country has states
country varchar(50) not null
);
create table Taster (
id serial primary key,
family varchar(30), -- some people have only one name
given varchar(30) not null,
livesIn integer not null references Location(id)
);
create table Brewer (
id serial primary key,
name varchar(50) not null,
locatedIn integer not null references Location(id)
);
create table BeerStyle (
id serial primary key,
name varchar(30) not null
);
create table Beer (
id serial primary key,
name varchar(50) not null,
style integer not null references BeerStyle(id),
brewer integer not null references Brewer(id),
totRating integer default 0,
nRatings integer default 0,
rating float
);
create table Ratings (
taster integer not null references Taster(id),
beer integer not null references Beer(id),
score integer not null
constraint validRating
check (score >= 1 and score <= 5)
);
What is required is a series of semi-formal assertions:
for every Beer b (b.totRating = (sum(score) from Ratings where beer = b.id))
for every Beer b (b.nRatings = (count(score) from Ratings where beer = b.id))
for every Beer b (b.rating = b.totRating/b.nRatings, if b.nRatings > 0, null otherwise)
Write triggers and their associated PLpgSQL functions to maintain these assertions on the Beer table in response to all possible changes to the Ratings table.
Q1
insertion trigger
CREATE OR REPLACE FUNCTION insertRating()
returns trigger
AS $$
DECLARE
b Beer;
BEGIN
SELECT * into b FROM Beer WHERE id = New.beer;
b.nRatings := b.nRatings + 1;
b.totRating := b.totRating + new.score;
b.rating = b.totRating / b.nRatings;
UPDATE Beer
SET nRatings = b.nRatings,
totRating = b.totRating,
rating = b.rating
WHERE id = New.beer;
return new;
END;
$$ LANGUAGE plpgsql;
--新增的beer數據會使得評價數加1,總評分提高,該啤酒的評分重新計算
CREATE TRIGGER InsertRating after inser on Ratings
for each row execute procedure insertRating();
Q2
update trigger
CREATE OR REPLACE FUNCTION updateRating()
returns trigger
AS $$
DECLARE
nb Beer;
ob Beer;
BEGIN
SELECT * INTO nb FROM Beer WHERE id = New.beer;
--因為要更新,所以一定有new,存放在nb中
if (new.beer = old.beer) then
--如果更新的beer和過去的beer名字一致,則減去過去的評分加上現在的評分,重新計算rating即可
if(new.rating = old.rating) then
null;
else
nb.totRating := nb.totRating + new.score - old.score;
nb.rating := nb.totRating / nb.ratings;
else
--如果更新的beer和過去的beer名字不一致,那么過去的beer和新的beer數據都需要調整
SELECT * INTO ob FROM Beer WHERE id = old.beer;
ob.totRating := ob.totRating - old.score;
ob.nRatings := ob.nRatings - 1;
ob.rating := ob.totRating / ob.nRatings;
nb.totRating := nb.totRating + new.score;
nb.nRatings := nb.nRatings + 1;
nb.rating := nb.totRating / nb.nRatings;
UPDATE Beer
SET nRatings = ob.nRatings,
totRating = ob.totRating,
rating = ob.rating
WHERE id = old.beer;
END if;
UPDATE Beer
SET nRatings = nb.nRatings,
totRating = nb.totRating,
rating = nb.rating
WHERE id = new.beer;
return new;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER UpdateRating after update on Ratings
for each row execute procedure updateRating();
Q3
delete trigger
CREATE OR REPLACE FUNCTION deleteRating()
returns trigger
AS $$
DECLARE
b Beer;
BEGIN
SELECT * INTO b FROM Beer WHERE id = old.beer;
b.nRatings := b.nRatings - 1;
b.totRating := b.totRating - old.score;
if (b.nRatings = 0) then
b.rating := null;
else
b.rating := b.totRating / b.nRatings;
end if;
UPDATE Beer
SET nRatings = b.nRatings,
totRating = b.totRating,
rating = b.rating
WHERE id = old.beer;
return old;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER DeleteRating before delete on Ratings
for each row execute procedure deleteRating();