42
Programming in postgreSQL with PL/pgSQL Procedur al Language extension to postgreSQL

Programming in postgreSQL with PL/pgSQL ProceduralLanguageextension topostgreSQL

Embed Size (px)

Citation preview

Programming in postgreSQL

with PL/pgSQL

ProceduralLanguage extension

topostgreSQL

Consider the relation Point(X,Y)

Q: How would you query the coordinates of all points situated on the curve y=x2+3x+5 ?

A: Select x,y from point where y=x*x+3*x+5Q: How would you query the coordinates of all

pairs of points with a distance> 5 ? A: select p1.x, p1.y, p2.x, p2.y from point p1,

point p2where (p1.y-p2.y)*(p1.y-p2.y)+(p1.x-p2.x)*(p1.x-p2.x)>25

Consider the relation Point(X,Y)

Q: Suppose you have another relation, called edge(point1, point2). How would you query the coordinates of all points in the shortest path from (3,7) to (32,77)?

A: With standard SQL, you cannot..

PL/pgSQL• Allows using general programming tools

with SQL, for example: loops, conditions, functions, etc.

• This allows a lot more freedom than general SQL

• We write PL/pgSQL code in a regular file, for example firstPl.sql, and load it with \i in the psql console.

PL/pgSQL Blocks

PL/pgSQL code is built of Blocks, with a unique structure:

LABELDECLARE (optional)

/* Variable declaration*/BEGIN (mandatory)

/* Executable statements (what the block DOES!)*/

EXCEPTION (optional)/* Exception handling*/

END; (mandatory)LABEL

Labeling a function:

And at the end of the function:

Example:

Create [or replace] function funcName(varName1 varType1,varName2 varType2,…)

Returns returnVarType AS$$

$$ language plpgsql;

Create or replace function myMultiplication(var1 integer, var2 integer) returns integer as$$

BEGINreturn var1*var2;END;

$$language plpgsql

Alternatively, the return value and type can be declared as function parameters:

This allows returning more than one value without defining a record

Create [or replace] function funcName(varName1 varType1,varName2 varType2,…,out retVarName

retvarType) AS$$

Create or replace function myAddition(var1 integer, var2 integer, out addRes integer) returns integer as$

$BEGINaddRes:=var1+var2;END;

$$language plpgsql

Example:

Declare

The general syntax of a variable declaration is:name [CONSTANT] type [ NOT NULL] [DEFAULT := expression]

Examples:

user_id integer; quantity numeric(5); url varchar(20); myrow tablename%ROWTYPE;

ExampleCreate or replace function addTax(price real, OUT res1real) as $$beginres1:= price*1.155;end;$$ language plpgsql;

first.sql:

In the psql console write:

\i first.sql

Then you can use the function:

Insert into pricesTable values(addTax(20));

or

Select (addTax(20));

Declaring Variables with the %TYPE Attribute

Examples

DECLARE sname Sailors.sname%TYPE; fav_boat VARCHAR(30); my_fav_boat fav_boat%TYPE := 'Pinta';...

Accessing column sname in table Sailors

Accessing a variable

Declaring Variables with the %ROWTYPE Attribute

Declare a variable with the type of a ROW of a table.

And how do we access the fields in reserves_record?

reserves_record Reserves%ROWTYPE;

reserves_record.sid=9;

Reserver_record.bid=877;

Accessing table Reserves

Select into

• If select returns more than one result, the first row will be taken, or nulls if no rows were returned

• Notice that unless ‘Order by’ was specified, the first row is not well defined

Create or replace function mySp(var1 integer) returns integer as$$

declaresp_var sportsman%rowtype;BEGINselect * into sp_var from sportsman;return sp_var.age*var1;END;

$$language plpgsql

Select into strict

• In this case, if more or less than one row is returned, a run-time error will occur

Create or replace function mySp(var1 integer) returns integer as$$

declaresp_var sportsman%rowtype;BEGINselect * into strict sp_var from sportsman;return sp_var.age*var1;END;

$$language plpgsql

select myMult(ms.*) from multipliers ms where ms.mult>100 order by mult asc;

What does this return?

The multiplication of the smallest mult which is larger than 100 by the age of the oldest sportsman whose age is less than 30

CREATE or replace FUNCTION myMult(t2_row multipliers) RETURNS real AS$$

declaret_row sportsman%rowtype;BEGIN

SELECT * INTO t_row FROM sportsman WHERE age<30 order by age desc;

RETURN t_row.age*t2_row.mult;END;

$$LANGUAGE plpgsql;

Checking if a row was returned

DeclaremyVar sportsman%rowtype;BeginSelect * into myVar from sportsman where age=4;If not found then…

ConditioningIF boolean-expression THEN statements END IF;

…IF v_age > 22 THEN UPDATE sportsman SET salary = salary+1000 WHERE sid = v_sid ;END IF;

Conditioning 2IF boolean-expression THEN statements ELSE statements END IF;

Conditioning 3

IF boolean-expression THEN statements ELSIF boolean-expression THEN statements ELSIF boolean-expression THEN statements… ELSE statements END IF;

Example

Select assessRate(6.7);

CREATE or replace FUNCTION assessRate(rating real) RETURNS text AS$$BEGINif rating>9 then return 'great;'elsif rating>7 then return 'good;'elsif rating>5 then return 'keep on working;'elsif rating>3 then return 'work harder;'!else return 'you can stop working;'end if;END;

$$LANGUAGE plpgsql;

Suppose we have the following table:

• Want to keep track of how many times users have run a PL/SQL block

• When the block is run, if user is already in table, increment num_run. Otherwise, insert user into table

create table mylog(who text, num_run integer

);

whonum_run

Peter3

John4

Moshe2

mylog

SolutionCREATE FUNCTION updateLogged()

RETURNS void AS $$ DECLAREcnt integer;BEGIN Select count(*) into cnt from mylog where who=user;If cnt>0 then update mylog

set num_run = num_run + 1 where who = user;

else insert into mylog values(user, 1);

end if; end;

$$LANGUAGE plpgsql;

Simple loop

LOOP statements END LOOP;

• Terminated by Exit or return• Exit: only causes termination of the loop • Can be specified with a condition:• Exit when …

ExamplesLOOP --some computations

IF count > 0 THEN EXIT ;END IF ;END LOOP;

LOOP --some computations

EXIT WHEN count > 0 ; END LOOP;

BEGIN --some computations

IF stocks > 100000 THEN EXIT ;END IF ;END;

Continue

• The next iteration of the loop is begunCreate or replace function myTest(var1 integer) returns integer as$$

DECLAREi integer;BEGINi:=1loopexit when i>var1i=i+1continue when i<20raise notice 'num is %',iend loopreturn i*var1END

$$language plpgsql

What does this print for select myTest(30)?20…31

While loopWHILE expression LOOP --statements END LOOP;

WHILE money_amount > 0 AND happiness<9 LOOP

--buy more END LOOP;

For loopFOR var IN [ REVERSE ] stRange ..endRange [ BY jumps ] LOOP statements END LOOP; FOR i IN 1..10 LOOP

RAISE NOTICE 'i is %', i ;END LOOP;

FOR i IN REVERSE 10..1 LOOP

--some computations here END LOOP;FOR i IN REVERSE 10..1 BY 2 LOOP RAISE NOTICE 'i is %', i ;END LOOP;

Looping Through Query Results FOR target IN query LOOP statements END LOOP;

CREATE or replace FUNCTION assessRates() RETURNS void AS$$DECLAREi record;BEGINfor i in select rating from ratings order by rating loopif i.rating>9 then raise notice 'great;'elsif i.rating>7 then raise notice 'good;'elsif i.rating>5 then raise notice 'keep on working;'elsif i.rating>3 then raise notice 'work harder;'!else raise notice 'you can stop working;'end if;end loop;END; $$ LANGUAGE plpgsql;

Trapping exceptionsDECLARE declarationsBEGIN statements EXCEPTION WHEN condition [ OR condition ... ] THEN

handler_statements WHEN condition [ OR condition ... ] THEN

handler_statements ... END;

Create or replace function errors(val integer) returns real as$$

Declareval2 real;BEGINval2:=val/(val-1);return val2;Exceptionwhen division_by_zero thenraise notice 'caught a zero division;'return val2;End;

$$LANGUAGE plpgsql;

Errors and messages

RAISE DEBUGRAISE LOGRAISE INFORAISE NOTICERAISE WARNINGRAISE EXCEPTION.

Triggers• A trigger defines an action we want to take place

whenever some event has occurred.• Can execute before or after the triggering event• A triggering event can be an insert, update or

delete• The trigger can be defined to run once per

changed row or once per statement• The trigger function can be written in PL/pgSQL• The function must not take arguments and

returns type trigger• First we create a trigger function and then create

the trigger using create trigger

Triggers- cont.• Row-level before triggers are usually used to

modify or check the data that is changing• Row-level after triggers are usually used to

propagate the effect of the changes to other tables

• Pay attention to recursive trigger firing

CREATE TRIGGER name { BEFORE | AFTER } { event [ OR ... ] } ON table [ FOR EACH ROW |STATEMENT ] EXECUTE PROCEDURE funcname ( arguments )

Create trigger

CREATE TRIGGER emp_trig BEFORE INSERT OR UPDATE ON employee FOR EACH ROW EXECUTE PROCEDURE emp_trig_func() ;

Writing a trigger functionWhen a trigger is fired, several variables are

automatically created:• New• Old• TG_OP• …

CREATE FUNCTION toUpper() RETURNS trigger AS $$ BEGINnew.sname := UPPER(new.sname);END;$$ LANGUAGE plpgsql;

CREATE TRIGGER toUpperTrigBEFORE INSERT or UPDATE on sportsman

FOR EACH ROW execute procedure toUpper;)(

CREATE TABLE emp (empname text, salary integer, last_date timestamp, last_user text );

CREATE FUNCTION emp_stamp() RETURNS trigger AS $$ BEGIN -- Check that empname and salary are given IF NEW.empname IS NULL THEN RAISE EXCEPTION 'empname

cannot be null'; END IF; IF NEW.salary IS NULL THEN RAISE EXCEPTION '% cannot

have null salary', NEW.empname; END IF; IF NEW.salary < 0 THEN RAISE EXCEPTION '% cannot have a

negative salary', NEW.empname; END IF; NEW.last_date := current_timestamp; NEW.last_user := current_user; RETURN NEW; END; $$ LANGUAGE plpgsql;

CREATE TRIGGER emp_stamp BEFORE INSERT OR UPDATE ON emp FOR EACH ROW EXECUTE PROCEDURE emp_stamp() ;

CREATE TABLE emp ( empname text NOT NULL, salary integer );

CREATE TABLE emp_backup( operation char(1) NOT NULL, stamp timestamp NOT NULL, userid text NOT NULL, empname text NOT NULL, salary integer );

CREATE OR REPLACE FUNCTION process_emp_backup() RETURNS TRIGGER AS $$

BEGIN IF (TG_OP = 'DELETE') THEN INSERT INTO emp_backup SELECT 'D', now(), user, OLD.*; RETURN OLD; ELSIF (TG_OP = 'UPDATE') THEN INSERT INTO emp_backup SELECT 'U', now(), user, NEW.*; RETURN NEW; ELSIF (TG_OP = 'INSERT') THEN INSERT INTO emp_backup SELECT 'I', now(), user, NEW.*; RETURN NEW; END IF; RETURN NULL; END; $$ LANGUAGE plpgsql;

CREATE TRIGGER emp_backup AFTER INSERT OR UPDATE OR DELETE ON emp FOR EACH ROW EXECUTE PROCEDURE process_emp_backup() ;

Statement TriggerCREATE FUNCTION shabbat_trig_func() RETURNS trigger AS $$ BEGINif (TO_CHAR(current_date,'DY')='SAT') then

raise exception ‘no work on shabbat;’!end if;Return;END;

$$LANGUAGE plpgsql;

CREATE TRIGGER no_work_on_shabbat_trigBEFORE INSERT or DELETE or UPDATEon sportsman for each statement execute

procedure shabbat_trig_func;)(