Cours PGPLSQL
Cours PGPLSQL
• On the first call of a function in a session, the call handler will • The PL/pgSQL statement tree is very similar to a PostgreSQL
“compile” a function statement tree. execution tree.
• SQL queries in the function are just kept as a string at this point.
• The call handler then executes that statement tree.
The basic unit in any PL/pgSQL code is a BLOCK. All PL/pgSQL code
is composed of a single block or blocks that occur either sequentially
or nested within another block. There are two kinds of blocks:
• Anonymous blocks (DO)
• Generally constructed dynamically and executed only once by the
user. It is sort of a complex SQL statement
• Named blocks (Functions and Stored Procedures)
• Have a name associated with them, are stored in the database,
and can be executed repeatably, and can take in parameters
Structure of Anonymous Block Comments
DO $$
[ <<label>> ] • There are two types of comments in PL/pgSQL
DECLARE • -- starts a comment that extends to the end of the line
/* Declare section (optional). */ • /* multi-line comments */
BEGIN
/* Executable section (required). */ • Commenting is necessary to tell people what is intended and
why it was done a specific way
EXCEPTION
/* Exception handling section (optional). */
• Err on the side of too much commenting
END [ label ]
$$;
• Use variables for • Variables declared in the declarations section preceding a block
• Temporary storage of data are initialized to their default values every time the block is
• Manipulation of stored values entered, not only once per function call
• Re-usability
• Ease of maintenance
• Variables in a declaration section can shadow variables of the
• Declared in the declarative section within a block same name in an outer block. If the outer block is named with a
label, its variables are still available by specifying them as
v_last_name VARCHAR(15);
<label>.<varname>
Declarations %TYPE
%ROWTYPE Records
DECLARE
r record;
Variable Scope Qualify an Identifier
DO $$
DO $$ << mainblock >>
DECLARE DECLARE
quantity integer := 30; quantity integer := 30;
BEGIN BEGIN
RAISE NOTICE 'Quantity here is %', quantity; -- 30 RAISE NOTICE 'Quantity here is %', quantity; --30
quantity := 50; quantity := 50;
-- Create a subblock -- Create a subblock
DECLARE DECLARE
quantity integer := 80; quantity integer := 80;
BEGIN BEGIN
RAISE NOTICE 'Quantity here is %', quantity; -- 80 RAISE NOTICE 'Quantity here is %', [Link]; --50
END; RAISE NOTICE 'Quantity here is %', quantity; --80
RAISE NOTICE 'Quantity here is %', quantity; -- 50 END;
END RAISE NOTICE 'Quantity here is %', quantity; --50
$$; END
$$;
END [ label ]
$$ LANGUAGE plpgsql;
Function Example Dollar Quoting
CREATE FUNCTION get_user_count() • The tag $$ denotes the start and end of a string
RETURNS integer
AS $$
• Optionally can have a non-empty tag as part of the quote
DECLARE
• $_$
v_count integer; • $abc$
BEGIN • Can be used to prevent unnecessary escape characters
SELECT count(*) throughout the string
INTO v_count $function$
FROM users; BEGIN
RETURN ($1 ~ $q$[\t\r\n\v\\]$q$);
RETURN v_count; END;
END $function$
$$ LANGUAGE plpgsql;
RETURN v_count;
END
$$ LANGUAGE plpgsql;
IF-THEN
IF boolean-expression THEN
statements
• The logical flow of statements can be changed using conditional END IF;
IF statements and loop control structures
• Conditional Strucutres IF-THEN-ELSE
• Loop Structures
IF boolean-expression THEN
statements
ELSE
statements
END IF;
Nested IF Statements ELSIF Statements
• Used for complex conditionals • Each WHEN clause sequentially evaluated until a TRUE is
• Allows a variable to be tested for equality against a list of values evaluated
• Subsequent WHEN expressions are not evaluated
BEGIN
CASE status BEGIN
WHEN 'Pending' THEN RAISE NOTICE 'PENDING'; CASE
WHEN 'Accepted' THEN RAISE NOTICE 'ACCEPTED'; WHEN x BETWEEN 0 AND 10 THEN
WHEN 'Declined' THEN RAISE NOTICE 'DECLINED'; RAISE NOTICE 'Value is between zero and ten';
WHEN 'Blocked' THEN RAISE NOTICE 'BLOCKED'; WHEN x BETWEEN 11 AND 20 THEN
ELSE RAISE NOTICE 'UNKNOWN'; RAISE NOTICE 'Value is between eleven and twenty';
END CASE; END CASE;
END $$;
FOUND FOUND
• FOUND, which is of type boolean, starts out false within each DECLARE
PL/pgSQL function call v_first_name users.first_name%TYPE;
v_last_name users.last_name%TYPE;
• It is set by each of the following types of statements: BEGIN
SELECT first_name, last_name
• A SELECT INTO statement sets FOUND true if it returns a row, INTO v_first_name, v_last_name
false if no row is returned FROM users
• A PERFORM statement sets FOUND true if it produces (and WHERE user_id = 1;
discards) a row, false if no row is produced
• UPDATE, INSERT, and DELETE statements set FOUND true if at IF FOUND THEN
least one row is affected, false if no row is affected RAISE NOTICE 'User Found';
ELSE
• A FETCH statement sets FOUND true if it returns a row, false if no
RAISE NOTICE 'User Not Found';
row is returned.
• A FOR statement sets FOUND true if it iterates one or more times, END IF;
END
else false.
• If no label is given, the next iteration of the innermost loop is WHILE condition LOOP
begun statement1..;
• If WHEN is specified, the next iteration of the loop is begun only END LOOP;
if expression is true. Otherwise, control passes to the statement • Repeats a sequence of statements until the controlling condition
after CONTINUE is no longer TRUE
• CONTINUE can be used with all types of loops; it is not limited to
use with unconstrained loops. • Condition is evaluated at the beginning of each iteration
WHILE NOT done LOOP
LOOP
-- some computations here
-- some computations
END LOOP;
EXIT WHEN count > 100;
CONTINUE WHEN count < 50;
-- some computations for count IN [50 .. 100]
END LOOP;
• Uses the FOREACH statement • Use the SLICE syntax to iterate over multiple dimensions
DECLARE
DECLARE
users varchar[];
users varchar[] := ARRAY['Mickey', 'Donald', 'Minnie'];
v_dim varchar[];
v_user varchar;
BEGIN
BEGIN
users := ARRAY[ARRAY['Mickey', 'Donald'], ARRAY['Mouse', 'Duck']];
FOREACH v_user IN ARRAY users LOOP
FOREACH v_dim SLICE 1 IN ARRAY users LOOP
RAISE NOTICE 'User: %', v_user;
RAISE NOTICE 'Dimension: %', v_dim;
END LOOP;
END LOOP;
END
END
Nested Loops
• Nest loops to multiple levels
• Use labels to distinguish between blocks
• Exit the outer loop with the EXIT statement that references the
label
BEGIN Dynamic SQL
<<Outer_loop>>
LOOP
v_counter := v_counter + 1;
EXIT WHEN v_counter > 10; -- leaves both loops
<<Inner_loop>>
LOOP
EXIT Outer_loop WHEN total_done = 'YES';
-- leaves both loops
EXIT WHEN inner_done = 'YES';
-- leaves inner loop only
END LOOP Inner_loop;
END LOOP Outer_loop;
END
• A programming methodology for generating and running SQL • There is no plan caching for commands executed via EXECUTE
statements at run time • The command is planned each time it is run
Execute Using
RETURN v_count;
END
$$ LANGUAGE plpgsql;
Cursors Cursor Flow
Returning Scalars
• Simplest return type
CREATE FUNCTION get_connection_count()
RETURNS integer AS $$
DECLARE
PL/pgSQL Returning Data v_count integer;
BEGIN
SELECT count(*) INTO v_count
FROM pg_stat_activity;
RETURN v_count;
END
$$ LANGUAGE plpgsql;
SELECT get_connection_count();
get_connection_count
----------------------
11
(1 row)
Returning Nothing Returning Sets
• Some functions do not need a return value
• This is usually a maintenance function of some sort such as
creating partitions or data purging • Functions can return a result set
• Starting in PostgreSQL 11, Stored Procedures can be used in
these cases • Use SETOF
• Return VOID • Use RETURN NEXT
CREATE FUNCTION purge_log() • RETURN NEXT does not actually return from the function
RETURNS void AS • Successive RETURN NEXT commands build a result set
$$
BEGIN
DELETE FROM moderation_log • A final RETURN exits the function
WHERE log_date < now() - '90 days'::interval;
END
$$ LANGUAGE plpgsql;
Refcursors
CREATE FUNCTION active_info(OUT p_queries refcursor,
OUT p_locks refcursor)
AS $$
BEGIN
OPEN p_queries FOR SELECT runtime, pid, usename, waiting,
Handling Meta Information and Exceptions
substring(query,1,50) as query
FROM running_queries
ORDER BY 1 DESC;
DECLARE
• Several available values l_rows int;
• ROW_COUNT BEGIN
• RESULT_OID DELETE FROM moderation_log
• PG_CONTEXT WHERE log_date < now() - '90 days'::interval;
Exceptions Exceptions
PG_DATATYPE_NAME DECLARE
sql varchar;
MESSAGE_TEXT r record;
TABLE_NAME tbl_cursor CURSOR FOR SELECT schemaname, relname
SCHEMA_NAME FROM pg_stat_user_tables;
PG_EXCEPTION_DETAIL BEGIN
PG_EXCEPTION_HINT IF NOT EXISTS (SELECT 1 FROM pg_roles
WHERE rolname = p_role) THEN
PG_EXCEPTION_CONTEXT RAISE EXCEPTION 'Invalid Role: %', p_role;
END IF;
...
Exceptions
• TIP: Use exceptions only when necessary, there is a large
performance impact
• Sub transactions are created to handle the exceptions
CREATE FUNCTION t1() CREATE FUNCTION t2() PL/pgSQL Triggers
RETURNS void AS $$ RETURNS void AS $$
DECLARE DECLARE
i integer; i integer;
BEGIN BEGIN
i := 1; i := 1;
END EXCEPTION
$$ LANGUAGE plpgsql; WHEN OTHERS THEN
RETURN;
END
$$ LANGUAGE plpgsql;
• Event Triggers fire on DDL • Enforce referential integrity across nodes in a distributed
• CREATE, DROP, ALTER database
• Provide auditing
• Invalidate cache entries
• Insert • Before
• The trigger is fired before the change is made to the table
• Trigger can modify NEW values
• Update • Trigger can suppress the change altogether
• Delete • After
• The trigger is fired after the change is made to the table
• Truncate • Trigger sees final result of row
Arguments TG_OP
• TG_OP
• A string of INSERT, UPDATE, DELETE, or TRUNCATE telling for
which operation the trigger was fired
CREATE TABLE audit (
• TG_NAME event_time timestamp NOT NULL,
• Variable that contains the name of the trigger actually fired user_name varchar NOT NULL,
operation varchar NOT NULL,
old_row json,
• TG_WHEN new_row json
• A string of BEFORE, AFTER, or INSTEAD OF, depending on the );
trigger’s definition
• TG_LEVEL
• A string of either ROW or STATEMENT depending on the trigger’s
definition
TG_OP Arguments
CREATE OR REPLACE FUNCTION audit_trigger() RETURNS TRIGGER AS $$
BEGIN
IF (TG_OP = 'DELETE') THEN
• TG_TABLE_NAME
INSERT INTO audit VALUES
• The name of the table that caused the trigger invocation.
(CURRENT_TIMESTAMP, CURRENT_USER,TG_OP, row_to_json(OLD), null);
RETURN OLD;
ELSIF (TG_OP = 'UPDATE') THEN
• TG_RELNAME
INSERT INTO audit VALUES
• The name of the table that caused the trigger invocation
(CURRENT_TIMESTAMP, CURRENT_USER,TG_OP,
row_to_json(OLD), row_to_json(NEW));
RETURN NEW;
• TG_RELID
ELSIF (TG_OP = 'INSERT') THEN
• The object ID of the table that caused the trigger invocation
INSERT INTO audit VALUES
(CURRENT_TIMESTAMP, CURRENT_USER,TG_OP, null, row_to_json(NEW));
RETURN NEW;
• TG_TABLE_SCHEMA
END IF;
• The name of the schema of the table that caused the trigger
RETURN NULL; invocation
END;
$$ LANGUAGE plpgsql;
TG_TABLE_NAME TG_TABLE_NAME
• Table Partitioning
• TG_NARGS • Splitting what is logically one large table into smaller physical
• The number of arguments given to the trigger procedure in the pieces
CREATE TRIGGER statement
• TG_ARGV[]
• Used to:
• Increase performance
• The arguments from the CREATE TRIGGER statement • Archive data
• Storage tiering
Note: ON (action) CASCADE contraints can simplify this too. CREATE TRIGGER remove_cache_trigger
AFTER UPDATE OR DELETE ON users
FOR EACH ROW EXECUTE PROCEDURE remove_cache_trigger();
ddl_command_start ddl_command_end
• Fires for all event trigger command tags • Fires for all event trigger command tags
• Does not fire for shared objects such as databases and roles • The objects have been affected so the details of the command
can be obtained
• Does not fire for commands involving event triggers
sql_drop table_rewrite
[Link]
Understanding pg_event_trigger_ddl_commands Understanding pg_event_trigger_dropped_objects
• Returns a line for each DDL command executed
• Only valid inside a ddl_command_end trigger • Returns a line for each object dropped by the DDL executed
• Only valid inside a sql_drop trigger
Column Type
classid oid Column Type Column Type
objid oid
objsubid integer classid oid object_type text
command_tag text objid oid schema_name text
object_type text objsubid integer object_name text
schema_name text original bool object_identity text
object_identity text normal bool address_names text[]
in_extension bool is_temporary bool address_args text[]
command pg_ddl_command
• Follow good programming practices • Create and follow a consistent naming convention for objects
• Indent code consistantly • PostgreSQL is case insensitive so init cap does not work, use ”_”
• Comment code liberly to seperate words in names
• Code reuse/modularity practices are different than other • Prefix all parameter names with something like “p_”
programming languages • Prefix all variable names with something like “v_”
• Deep call stacks in PL/pgSQL can be performance intensive
Performance