PL/SQL
Many DBMS have their own procedural languages which extend SQL and allow us to write procedural code (if statement, loops...) that can be executed within the database.
- π΅ Oracle: PL/SQL (Procedural Language for SQL)
- π° PostgreSQL: PL/pgSQL (similar to PL/SQL)
- π Microsoft SQL Server: T-SQL (Transact-SQL)
- ...
This course covers PL/SQL and PL/pgSQL.
DBMS-specific
PL/SQL-specific
You need to enable server output to see prints.
set serveroutput on
show errors -- call it each time you got an error
To print something, use:
dbms_output.put_line('something');
You also need to add a /
after the END;
of a block:
[...]
END;
/
In Oracle, raising your own exceptions is done using:
exception_name EXCEPTION;
PRAGMA exception_init(exception_name, code);
raise_application_error(code, "message");
PL/pgSQL-specific
To print something, you need to use raise notice
.
RAISE NOTICE 'something';
RAISE NOTICE 'Some message with a parameter: %', 5;
RAISE NOTICE '... % ... % ...', 5, 7;
Block of code
The usual block of code:
DECLARE
-- declare variables
BEGIN
-- instructions
-- (if applicable) COMMIT or ROLLBACK
EXCEPTION
-- handle exceptions
END;
- DECLARE is optional. You can declare variables here, one per line, with a semicolon at the end of each line.
- BEGIN is the code itself.
- EXCEPTION is an optional block to handle unexpected behaviors, which are mostly errors.
β οΈ If you update records (INSERT, UPDATE, ...), then you must COMMIT
(=save) or ROLLBACK
(=cancel).
Variables
Variables are needed to store the result of an SQL request, otherwise they are used to store constants.
The syntax is (v_ is optional, "v" stands for variable):
v_name [CONSTANT] type [NOT NULL] [ := expression ]
βοΈ Use some_table.some_attribute%type
or v_name%type
to use the type of some attribute or another variable.
βοΈ Use the type record
to store any one-line SQL result. Use some_table%ROWTYPE
to ensure the result is from one specific table.
Examples π₯
-- constant
v_five CONSTANT integer := 5;
-- not null
v_real real NOT NULL := 3.0;
-- simple variable
v_date date;
-- array
v_array int array[3] := '{0,1,2}';
Control-flow structures
Usual branching statement.
IF condition THEN
-- ELSIF another_condition THEN
-- ELSE THEN
END IF;
Execute instructions based on a variable
's value.
-- IF ELSEIF ... ELSE
CASE variable
WHEN value1 THEN instructions -- variable == value1
WHEN value2 THEN instructions -- ...
ELSE instructions -- default
END CASE;
Do something
until condition
is satisfied.
LOOP
-- something
EXIT WHEN condition
END LOOP;
While condition
is not satisfied, do something
.
WHILE condition LOOP
-- something
END LOOP;
Loops using indexes.
-- for i in seq / for (i=min; i<max; i++){}
FOR i IN min AND max LOOP some_code END LOOP;
-- reverse loop
FOR i IN REVERSE min AND max LOOP some_code END LOOP;
SQL SELECT
To exploit the result of a SELECT, you need to store it. You must first declare variables, then you can use them as follows:
SQL requests returning zero or one rows
-- one variable per selected field
SELECT 42, 'Hello' INTO v_id, v_name FROM /* ... */;
-- using a variable of type "record"
SELECT * INTO v_record FROM /* ... */;
SQL requests returning n rows
We move the cursor to iterate the result.
-- use IS or FOR, can use CURSOR before v_cursor
-- use v_cursor%ROWCOUNT to the number of rows
v_cursor CURSOR IS sql_request;
In the code, you can use a FOR..LOOP
.
FOR v_entry IN v_cursor LOOP
-- use "v_entry.XXX" to access the RECORD fields
END LOOP;
Cursors do not raise exceptions. Use these:
v_cursor%FOUND -- true if there are still rows, NULL if fetch was never called
v_cursor%NOTFOUND -- true if no more rows, NULL if fetch never called
v_cursor%ISOPEN -- true if the cursor is open
Manually open, move, and close the cursor
OPEN v_cursor;
LOOP
FETCH v_cursor INTO v_entry; -- store in v_entry
EXIT WHEN v_cursor%NOTFOUND;
-- v_entry.XXX to access the RECORD fields
-- you may use the MOVE clause
END;
CLOSE v_cursor;
Exceptions
An exception is a signal. Most of the time, it's raised because an error occurred. You can handle it in the block exception, but if you didn't, then the signal will be sent upward (=to the caller). If no one catches it, the request crashes with an error.
You can raise an exception with RAISE
RAISE 'same as raise notice format' [, values]
RAISE '...' USING ERRCODE = 'error_code'
RAISE SOME_EXCEPTION
RAISE SOME_EXCEPTION USING MESSAGE = 'additional message'
Some examples
RAISE 'Error: no records matching id=%', id;
RAISE DIVISION_BY_ZERO;
You can catch an exception, in EXCEPTION
, with WHEN
. You can make multiples of them, for each exception.
WHEN NO_DATA_FOUND THEN some_code;
WHEN DIVISION_BY_ZERO THEN some_code; -- ZERO_DIVIDE in Oracle
WHEN UNIQUE_VIOLATION THEN some_code;
-- you tried a select into, with a request
-- returning more than one row.
WHEN TOO_MANY_ROWS THEN some_code;
-- every exception, fallback
WHEN OTHERS THEN some_code;
You got a lot of predefined exceptions, check your database documentation.
Functions
You can create functions using:
CREATE OR REPLACE FUNCTION name(args)
RETURNS r_type -- AS $$
-- declare
BEGIN
-- code
-- RETURN result; (if any)
END; -- $$ LANGUAGE plpgsql;
To use a function: SELECT some_function()
.
Args
-
args
is a list of arguments :arg1 [IN/OUT] type1[, ...]
. -
IN
andOUT
are optional. By default, all parameters areIN
. ParametersIN
are read-only, write thoseOUT
must be written to. -
$n
is an alias to the nth argument ($1 == the first one).
Result
-
r_type
is a return type such asVOID
,INT
...
βοΈ When using PL/pgSQL, uncomment "AS $$
" and "$$ LANGUAGE plpgsql;
". Both $$
are matching, if you change one, change the other.
Triggers
It's possible to call a trigger
function when events (insert, update, delete...) occur. π₯ Usages: calculate derived fields, archiving...
CREATE TRIGGER trigger_name
BEFORE /* or AFTER */ some_event ON some_table
-- FOR EACH ROW
-- WHEN some_condition
-- Then:
-- some code (ex: BEGIN ... END)
-- or
-- EXECUTE PROCEDURE function(args);
- Triggers are either executed
BEFORE
orAFTER
an event. - Events are
INSERT
,UPDATE
,UPDATE(column)
orUPDATE OF column
,DELETE
... Separate events usingOR
such asINSERT OR UPDATE
. - Add
OR REPLACE
afterCREATE
to update a trigger. - Add
FOR EACH ROW
to execute the code for each updated row instead of once for the whole query. - Use
WHEN
to add another condition to execute the trigger
The code executed by the trigger has access to two pseudo records: NEW and OLD. The code returns RETURN NEW
by default (type=TRIGGER).
βοΈ Returning NULL
cancels INSERT/UPDATE/DELETE.
β οΈ Do not commit/rollback in a commit, or it will be done twice.
π₯ Some rename OLD/NEW
to :old/:new
using:
REFERENCING OLD :old NEW :new
To delete a trigger:
DROP TRIGGER trigger_name ON some_table;
DROP TRIGGER IF EXISTS trigger_name ON some_table;
Procedures
Procedures are not available in PL/pgSQL. They are similar to functions, but they
- π₯― don't return a result
- 𧳠write any result in variables marked
OUT
- π can be called outside a SELECT statement
To execute a procedure, you can use:
procedure_name(args);
execute procedure_name(args);
call procedure_name(args);
CREATE OR REPLACE PROCEDURE
procedure_name(arg1 IN type, arg2 OUT type) IS
-- variables
BEGIN
-- code
arg2 := arg1 -- write in arg2 the result
END;
/ -- compile
π» To-do π»
Stuff that I found, but never read/used yet.
- INSTEAD OF
-
a = 5
ora := 5
in PL/SQL