newBeer
❖ Week 04 Wednesday |
❖ Assignment 1 Testing |
This how we will test your submission:
createdb TempDB1 psql TempDB1 -f ass1.dump psql TempDB1 -f ass1.sql (your code) createdb TempDB2 psql TempDB2 -f unseen.dump psql TempDB2 -f ass1.sql (your code) ... run tests on TempDB1 and TempDB2 ... dropdb TempDB1 dropdb TempDB2
Make sure your code will work if loaded like this on vxdb2
Of course, not including unseen.dump
TempDB2
❖ More on PLpgSQL Functions |
Function definition ...
create or replace function FunctionName ( Parameters ) returns [setof] Type as $$ declare VariableDeclarations begin FunctionBody end; $$ language plpgsql;
Type can be
integer
real
text
char(8)
create table
create view
create type
❖ More on PLpgSQL Functions (cont) |
So far ...
record
if
for
while
return
return next
select
into
if not found then
raise notice
execute
❖ Missing Data |
Within functions, often use parameter values to find tuples
What to do if value leads to no match ...
select * into tup from R where name ~ Value if (not found) then handle case for no matching tuple else handle case for matching tuple end if
Alternative method (but requires two selects if there is a match)
select count(*) into nmatches from R where name ~ Value if (nmatches = 0) then ...
❖ Exercise: Missing Data |
Implement a PLpgSQL function
create or replace function tfnOf(name text) returns text as $$ ...
That takes a name as argument ...
Employees(tfn text primary key, name text, salary money, ...)
❖ A familiar database |
Recall the database for Assignment 1
Locations(id, country, region, metro, town) Styles(id, name, min_abv, max_abv) Ingredients(id, itype, name) Beers(id, name, brewed, style, abv, ibu, sold_in, ...) Contains(beer, ingredient) Brewed_by(beer, brewery)
❖ Exercise: A newBeer |
Write a PLpgSQL function on the Ass1 database
create or replace function addBeer(_name text, _brewery text, _style text, _abv float) returns integer as $$ ... $$ language plpgsql;
that takes the name, brewery, style and abv
Beers
Brewed_by
Notice
id
Beers.id
serial
❖ Dynamic Queries |
Queries can be "built" as strings of SQL code within functions
And then evaluated using execute
Including constant and identifiers in queries needs
quote_literal
quote_ident
qry := 'select * from '||quote_ident('beers')||
' where name = '||quote_literal('Fairy Floss');
-- which becomes ...
qry := 'select * from beers where name = ''Fairy Floss''';
❖ Dynamic Queries (cont) |
Query returning no results
perform 'update R set ...';
Query returning one result
execute 'Query' into Target;
Query returning multiple results
for Target in execute 'Query' loop ... process Target ... end loop;
❖ Exercise: Dynamic Queries |
Write a PLpgSQL function
create or replace function nTuples(tableName text) return integer as $$ ... $$ language plpgsql;
The function
dbPop()
❖ Exceptions |
Exceptional conditions can arise during function execution
PLpgSQL provides mechanisms
begin ... some code ... exceptionexceptionName then ... code to handle exception ... end; when
❖ Exercise: Exceptions |
Implement a "safe" division function
create or replace function div(n integer, m integer) returns integer
❖ Iteration in PLpgSQL |
Use SQL queries within function do as much work as possible
Don't do using code what you could do using SQL
E.g. use where
for T in select * from Table
loop
if T.x > 0 then
process T ...
end if;
end loop;
-- should be implemented as
for T in select * from Table where x > 0
loop
process T ...
end loop;
❖ Iteration in PLpgSQL (cont) |
E.g. use join
for T in select * from T1
loop
for S in select * from T2
loop
if T.x = S.y then ...
end loop;
end loop;
-- should be implemented as
for Res in
select * from R join S on x=y
loop
process Res ...
end loop;
❖ Iteration in PLpgSQL (cont) |
Example: function returning setof Beers
create or replace function bb(_patt text) returns setof Beers as $$ declare b Beers; begin for b in select * from Beers loop if b.name ~ _name then return next b; end if end loop; end; $$ language plpgsql;
vs
create or replace function bb(_patt text) returns setof Beers as $$ declare b Beers; begin for b in select * from Beers where name ~ _name loop return next b; end loop; end; $$ language plpgsql;
❖ Exercise: Poor use of loop |
Which of the following is the preferred way of filtering
for rec in select * from R loop if (rec.a = 5) then ... end if; end loop; OR for rec in select * from R where a = 5 loop ... end loop;
Why? Assume: |R| = 1000 tuples, 10 tuples with a=5
❖ Returning Multiple Results |
Already known: returning a set of values returns setof
What about a function that returns two different types of value?
E.g. withdrawal function returns new balance and a message
-- initial balance in account 777-432 is $2000 select withdrawal('777-432', 500, ...) -- returns 'Withdrawal ok' and 1500 select withdrawal('777-432', 10000, ...) -- returns 'Insufficient funds' and 1500
Could use OUT
❖ Returning Multiple Results (cont) |
Simple example of using OUT
create or replace function sum_n_product(x int, y int, OUT sum int, OUT prod int) as $$ begin sum := x + y; prod := x * y; end; $$ language plpgsql; db=# select sum_n_product(3,4) sum | prod -----+------ 7 | 12 (1 row)
Effectively creates an anonymous tuple type (sum,prod)
❖ Exercise: Withdrawal Function returning two values |
Show how the
withdrawal(Acct text, Amount money) ...
function could be implemented
OUT
❖ Developing PLpgSQL Functions |
Determine function signature
for Rec in Query loop ...
❖ Debugging PLpgSQL |
PLpgSQL does not have I/O functions
How to do debugging (see intermediate states)?
Use PostgreSQL logging statement
raise notice 'Message' [ , Expr1 , Expr2 , ... ]
Examples:
declare x integer; y float; ... raise notice 'x = %', x raise notice 'x = %, y = %', x, y raise notice 'x^2 = %', x*x
❖ Aggregates |
Aggregates reduce a collection of values to a single value
Example:
select avg(mark) from Enrolments where course='COMP3311'
How to achieve this? ... Maintain state, update value-by-value
State = initial state
for each tuple T in Query {
# update State to include T
State = updateState(State, T)
}
return makeFinal(State)
❖ Aggregates (cont) |
New aggregates are defined using CREATE AGGREGATE
CREATE AGGREGATE AggName(BaseType) ( stype = StateType, initcond = InitialValue, sfunc = UpdateStateFunction, finalfunc = MakeFinalFunction );
initcond
finalfunc
❖ Exercise: User-defined Aggregates |
count
select mycount(a) from R → 3 select mycount(b) from R → 3
product
select product(a) from R → 6
select cat(b) from R → 'this,is,fun'
create table R (a integer, b text); insert into R values (1,'this'), (2,'is'), (3,'fun');
Produced: 4 Oct 2023