1. Additional video from last week
--1.Functions returning single atomic value without any database access (immutable)
--SQL簡單函數
CREATE OR REPLACE FUNCTION
add2a(a integer, b integer) RETURNS integer
AS $$
SELECT a + b;
$$ LANGUAGE 'sql';
--語言這里的sql是區分大小寫的
--使用如下
SELECT add2a(3, 5);
add2a
-------
8
(1 row)
--PLpgsql簡單函數
CREATE OR REPLACE FUNCTION
add2b(a integer, b integer) RETURNS integer
AS $$
BEGIN
RETURN a + b;
END;
$$ LANGUAGE 'plpgsql';
--語言這里的plpgsql同樣是區分大小寫的
--plpgsql寫法是以BEGIN開始,END;結束
--使用如下
SELECT add2b(3, 5);
add2b
-------
8
(1 row)
--2.Functions returning a single tuple with no database access
CREATE TYPE Pair AS (x integer, y integer);
CREATE OR REPLACE FUNCTION
mkpair(a integer, b integer) RETURNS Pair
AS $$
DECLARE
p Pair;
BEGIN
p.x := a;
p.y := b;
return p;
END;
$$ LANGUAGE 'plpgsql';
--使用如下
SELECT mkpair(3, 4);
mkpair
--------
(3,4)
(1 row)
SELECT * FROM mkpair(3, 4);
x | y
---+---
3 | 4
(1 row)
--3.Fuction returning a set of atomic values (SETOF)
CREATE OR REPLACE FUNCTION
seq(hi integer) RETURNS setof integer
AS $$
DECLARE
i integer;
BEGIN
i := 1;
while(i <= hi) loop
return next i;
i := i + 1;
end loop;
return;
END;
$$ LANGUAGE 'plpgsql';
--plpgsql使用古老的語言風格,assign是:=,循環不使用括號,而是用loop + end loop;
--使用如下
SELECT * FROM seq(5);
seq
-----
1
2
3
4
5
(5 rows)
--4.Function returning a set of tuples (i.e. table)
CREATE OR REPLACE FUNCTION
squares(hi integer) RETURNS SETOF Pair
AS $$
DECLARE
i integer;
p Pair;
BEGIN
FOR i in 1..hi loop
p.x := i;
p.y := i * i;
return next p;
end loop;
return;
END;
$$ LANGUAGE 'plpgsql';
--使用如下
SELECT * FROM squares(3);
x | y
---+---
1 | 1
2 | 4
3 | 9
(3 rows)
SELECT x AS "n", y as "n^2" FROM squares(5);
n | n^2
---+-----
1 | 1
2 | 4
3 | 9
4 | 16
5 | 25
(5 rows)
--5.Function that reads from the database and returns a set of atomic values
CREATE OR REPLACE FUNCTION
pizzaWith(_topping text) RETURNS setof text
AS $$
DECLARE
_p Pizzas;
BEGIN
for _p in
SELECT p.*
FROM Pizzas p
JOIN Has h ON h.pizza = p.id
JOIN Toppings t ON h.topping = t.id
WHERE t.name = _topping
loop
return next _p.name;
end loop;
END;
$$ LANGUAGE 'plpgsql';
--local variable命名以_開始,以區分database和local variable
--使用如下
SELECT * FROM pizzaWith('cheddar');
--6.Function that reads from the database and returns a set of tuples (i.e. a table)
CREATE OR REPLACE FUNCTION
pizzaWith1(_topping text) RETURNS setof Pizzas
AS $$
DECLARE
_p Pizzas;
BEGIN
for _p in
SELECT p.*
FROM Pizzas p
JOIN Has h ON h.pizza = p.id
JOIN Toppings t ON h.topping = t.id
WHERE t.name = _topping
loop
return next _p;
end loop;
END;
$$ LANGUAGE 'plpgsql';
--使用如下
SELECT * FROM pizzaWith1('cheddar');
2. Extending SQL
通常可以創建以下一些類型,一些可以用sql實現,但是很多用extending方式更加容易:
2.1 new data types
可以用sql實現,例如:
create domain Positive as integer check (value > 0);
create type Rating as enum ('poor','ok','excellent');
create type Pair as (x integer, y integer);
2.2 more operations/aggregates for use in queires
2.2.1 循環不可以用sql實現,例如:
-- Factorial functions
-- iterative (while)
create or replace function
fac(n integer) returns integer
as $$
declare
i integer;
f integer := 1;
begin
if (n < 1) then
raise exception 'Invalid fac(%)',n;
--n 表示string中%的位置的value
end if;
i := 2;
while (i <= n) loop
f := f * i;
i := i + 1;
end loop;
return f;
end;
$$ language plpgsql;
-- iterative (for)
create or replace function
facc(n integer) returns integer
as $$
declare
i integer;
f integer := 1;
begin
if (n < 1) then
raise exception 'Invalid fac(%)',n;
end if;
for i in 2 .. n loop
-- ..表示2 to n
f := f * i;
end loop;
return f;
end;
$$ language plpgsql;
-- recursive
create or replace function
facr(n bigint) returns bigint
as $$
begin
if (n < 1) then
raise exception 'Invalid fac(%)',n;
elsif (n = 1) then
return 1;
else
return n * facr(n-1);
end if;
end;
$$ language plpgsql;
2.2.2 現代query types
除了之前文章提到的select/project/join, aggregation, grouping的query方式,現代很多query分為兩種類型:
2.2.2.1 recursive,用來manage hierarchies, graphs
定義如下:
with recursive T(a1, a2, ...) as
(
non-recursive select
union
recursive select involving T
)
select ... from T where ...
要求:
The subqueries generating T cannot be arbirtrary
(1)non-recursive select:
does not refer to T
generates an initial set of tuples for T (initialisation)
(2)recursive select:
must be a query involving T
must include a where condition
must eventually return an empty result (termination)
例如:
with recursive nums(n) as (
select 1
union
select n+1 from nums where n < 100
)
select sum(n) from nums;
-- which produces ...
sum
------
5050
2.2.2.2 window,用來spread group-by summaries
將下例的分組用一個tuple顯示出來
select student, avg(mark)
from CourseEnrolments
group by student;
student | avg
----------+-------
46000936 | 64.75
46001128 | 73.50
下例 attach student's average mark to each enrolment,table中不存在avg這一列,但是用window的方式可以實現在每列數據后都加入該學生的avg。
select *, avg(mark)
over (partition by student)
--over相當于在哪里加入window信息,這里是在每一個student后面加入avg(mark)的信息
from CourseEnrolments;
student | course | mark | grade | stueval | avg
----------+--------+------+-------+---------+-------
46000936 | 11971 | 68 | CR | 3 | 64.75
46000936 | 12937 | 63 | PS | 3 | 64.75
46000936 | 12045 | 71 | CR | 4 | 64.75
46000936 | 11507 | 57 | PS | 2 | 64.75
46001128 | 12932 | 73 | CR | 3 | 73.50
46001128 | 13498 | 74 | CR | 5 | 73.50
46001128 | 11909 | 79 | DN | 4 | 73.50
46001128 | 12118 | 68 | CR | 4 | 73.50
練習題:Using window functions, write an SQL function to find students whose mark is < 60% of average mark for course.
create or replace function
under(integer) returns setof CourseEnrolments
as $$
select student,course,mark,grade,stueval
from CourseAverages
where course = $1 and mark < 0.6*avg
$$
language sql stable;
--stable含義: access the database without change it
-- Generate the CourseAverages table using window function
create view CourseAverages as
select student,course,mark,grade,stueval,avg(mark)
over (partition by course)
from CourseEnrolments;
--注意:CourseEnrolments的avg(mark)是一個學生所有學科的平均分,21行的partition by course則是根據課程的平均分
2.2.3 With queries
為了實現一個復雜的查詢,往往需要中間有很多小查詢,但往往并不需要這些中間查詢永久存在,所以可以用with queiries來臨時創建中間的view或其他步驟。例如:
with V as (select a,b,c from ... where ...),
W as (select d,e from ... where ...)
select V.a as x, V.b as y, W.e as z
from V join W on (v.c = W.d);
--或者
select V.a as x, V.b as y, W.e as z
from (select a,b,c from ... where ...) as V,
(select d,e from ... where ...) as W
where V.c = W.d;
2.2.4 Aggregates
Aggregates reduce a collection of values into a single result.例如count(tuples), sum(numbers)
AggState = initial state
for each item V {
# update AggState to include V
AggState = newState(AggState, V)
}
return final(AggState)
例如,table R是一系列a,b,c tuple組成的table,使用aggragate可以顯示精簡后的信息,如sum和count:
--table R
a | b | c
---+---+---
1 | 2 | x a | sum | count
1 | 3 | y ---+-----+-------
2 | 2 | z 1 | 5 | 2
2 | 1 | a 2 | 6 | 3
2 | 3 | b
select a,sum(b),count(*) from R group by a
a | sum | count
---+-----+-------
1 | 5 | 2
2 | 6 | 3
user-defined aggregates:
BaseType ... type of input values
StateType ... type of intermediate states
state mapping function: sfunc(state,value) → newState
[optionally] an initial state value (defaults to null)
[optionally] final function: ffunc(state) → result
CREATE AGGREGATE AggName(BaseType) (
sfunc = NewStateFunction,
stype = StateType,
initcond = InitialValue, --optional
finalfunc = FinalResFunction, --optional
sortop = OrderingOperator --optional
);
例如:
create aggregate myCount(anyelement) (
stype = int, -- the accumulator type
initcond = 0, -- initial accumulator value
sfunc = oneMore -- increment function
);
create function
oneMore(sum int, x anyelement) returns int
as $$
begin return sum + 1; end;
$$ language plpgsql;
例如:
create type IntPair as (x int, y int);
create function
AddPair(sum int, p IntPair) returns int
as $$
begin return p.x + p.y + sum; end;
$$ language plpgsql;
create aggregate sum2(IntPair) (
stype = int,
initcond = 0,
sfunc = AddPair
);
練習題:Define a concat aggregate that takes a column of string values and returns a comma-separated string of values
select count(*), concat(name) from Employee;
-- returns e.g.
count | concat
-------+----------------------
4 | John,Jane,David,Phil
2.3 more powerful constraint checking
包括attribute constraint,例如
age integer check (age > 15),
relation constraint和referential integrity,例如
create table Employee (
id integer primary key,
name varchar(40),
salary real,
age integer check (age > 15),
worksIn integer
references Department(id),
constraint PayOk check (salary > age*1000)
);
除此之外,還可以用assertion,但是assertion非常耗時耗能,每次改變table都一定要執行assertion,謹慎使用。
CREATE ASSERTION name CHECK (condition)
例如:
create assertion ClassSizeConstraint check (
not exists (
select c.id from Courses c, CourseEnrolments e
where c.id = e.course
group by c.id having count(e.student) > 9999
)
);
create assertion AssetsCheck check (
not exists (
select branchName from Branches b
where b.assets <>
(select sum(a.balance) from Accounts a
where a.branch = b.location)
)
);
2.4 event-based triggered actions
Triggers are procedures stored in the database and activated in response to database events (e.g. updates).
Triggers provide event-condition-action (ECA) programming:
--an event activates the trigger
--on activation, the trigger checks a condition
--if the condition holds, a procedure is executed (the action)
CREATE TRIGGER TriggerName
{AFTER|BEFORE} Event1 [ OR Event2 ... ]
[ FOR EACH ROW ]
ON TableName
[ WHEN ( Condition ) ]
Block of Procedural/SQL Code ;
Triggers can be activated BEFORE or AFTER the event.
If activated BEFORE, can affect the change that occurs:
--NEW contains "proposed" value of changed tuple
--modifying NEW causes a different value to be placed in DB
If activated AFTER, the effects of the event are visible:
--NEW contains the current value of the changed tuple
--OLD contains the previous value of the changed tuple
--constraint-checking has been done for NEW
Note: OLD does not exist for insertion; NEW does not exist for deletion.
例如insert:
create trigger X before insert on T Code1;
create trigger Y after insert on T Code2;
insert into T values (a,b,c,...);
sequence of events:
1.execute Code1 for trigger X
2.code has access to (a,b,c,...) via NEW
3.code typically checks the values of a,b,c,..
4.code can modify values of a,b,c,.. in NEW
5.DBMS does constraint checking as if NEW is inserted
6.if fails any checking, abort insertion and rollback
7.execute Code2 for trigger Y
8.code has access to final version of tuple via NEW
9.code typically does final checking, or modifies other tables in database to ensure constraints are satisfied
Reminder: there is no OLD tuple for an INSERT trigger.
例如update:
create trigger X before update on T Code1;
create trigger Y after update on T Code2;
update T set b=j,c=k where a=m;
sequence of events:
1.execute Code1 for trigger X
2.code has access to current version of tuple via OLD
3.code has access to updated version of tuple via NEW
4.code typically checks new values of b,c,..
5.code can modify values of a,b,c,.. in NEW
6.do constraint checking as if NEW has replaced OLD
7.if fails any checking, abort update and rollback
8.execute Code2 for trigger Y
9.code has access to final version of tuple via NEW
10.code typically does final checking, or modifies other tables in database to ensure constraints are satisfied
Reminder: both OLD and NEW exist in UPDATE triggers.
例如delete:
create trigger X before delete on T Code1;
create trigger Y after delete on T Code2;
delete from T where a=m;
sequence of events:
1.execute Code1 for trigger X
2.code has access to (a,b,c,...) via OLD
3.code typically checks the values of a,b,c,..
4.DBMS does constraint checking as if OLD is removed
5.if fails any checking, abort deletion (restore OLD)
6.execute Code2 for trigger Y
7.code has access to about-to-be-deleted tuple via OLD
8.code typically does final checking, or modifies other tables in database to ensure constraints are satisfied
Reminder: tuple NEW does not exist in DELETE triggers.
Triggers in PostgreSQL:
CREATE TRIGGER TriggerName
{AFTER|BEFORE} Event1 [OR Event2 ...]
ON TableName
[ WHEN ( Condition ) ]
FOR EACH {ROW|STATEMENT}
EXECUTE PROCEDURE FunctionName(args...);