11. Creating stored procedures and triggers

11.1. PostgreSQL
11.2. Oracle PL/SQL
11.3. Other DBMS

SQL Workbench/J will split statements based on the SQL terminator ; and send each statement unaltered to the DBMS.

When executing statements such as CREATE PROCEDURE which in turn contain valid SQL statements, delimited with a ; the SQL Workbench/J will send everything up to the first semicolon to the backend (because the ; terminates the SQL statement) In case of a CREATE PROCEDURE statement this will obviously result in an error as the statement is not complete.

To be able to run DDL statements with embedded ; characters, SQL Workbench/J needs to know where a statements ends. To specify the end of a statement with embedded ; the so called "alternate delimiter" is used. This chapter describes how the alternate delimiter is used by SQL Workbench/J

11.1. PostgreSQL

The body of a function in Postgres is a character literal. Because a delimiter inside a character literal does not define the end of the statement, no special treatment is needed for Postgres.

11.2. Oracle PL/SQL

This is an example of a CREATE PROCEDURE which will NOT work due to the embedded semicolon in the procedure source itself.

CREATE OR REPLACE FUNCTION proc_sample RETURN INTEGER
IS
  l_result INTEGER;
BEGIN
  SELECT max(col1) INTO l_result FROM sometable;
  RETURN l_result;
END;

When executing this script, Oracle would return an error because SQL Workbench/J will send everything up to the keyword INTEGER to the database. Obviously that fragment would not be correct.

The solution is to terminate the script with a character sequence that is called the "alternate delimiter" which can be defined in the connection profile. To be compatible with SQL Developer and SQL*Plus it is recommended to set the alternate delimiter to a forward slash (/).

The script needs to be written like this:

CREATE OR REPLACE FUNCTION proc_sample RETURN INTEGER
IS
  l_result INTEGER;
BEGIN
  SELECT max(col1) INTO l_result FROM sometable;
  RETURN l_result;
END;
/

Note the trailing forward slash (/) at the end in order to "turn on" the use of the alternate delimiter. If you run scripts with embedded semicolons and you get an error, please verify the setting for your alternate delimiter.

The standard delimiter (the semicolon) and the alternate delimiter can be mixed in a single script. Whenever a PL/SQL block (either a stored procedure or an anonymous block) is encountered, SQL Workbench/J expects the alternated delimiter to terminate that block. This follows the same rules as used in SQL*Plus.

The following script will therefore work when connected to an Oracle database:

drop table sometable cascade constraints;
create table sometable
(
  col1 integer not null
);

create or replace function proc_sample return integer
is
  l_result integer;
begin
  select max(col1) into l_result from sometable;
  return l_result;
end;
/

11.3. Other DBMS

When is the alternate delimiter used?

For all other DBMS, the use of the alternate delimiter is defined by the last delimiter used in the script.

As soon as the statement (or script) that you execute ends with the alternate delimiter, the alternate delimiter is used to separate all SQL statements. When you execute selected text from the editor, be sure to select the alternate delimiter as well, otherwise it will not be recognized (if the alternate delimiter is not selected, the statement to be executed does not end with the alternate delimiter).

This means a script must use the alternate delimiter for all statements in the script. The following script will not work, because the last statement is terminated with the alternate delimiter and thus SQL Workbench/J assumes all statements are delimited with that. As the CREATE TABLES statements are delimited with the standard delimiter, they are not recognized as a separate statement and thus the script is sent as a single statement to the server.

create table orders
(
  order_id    integer not null primary key,
  customer_id integer not null,
  product_id  integer not null,
  pieces      integer not null,
  order_date  date    not null
);

create table orders_audit_log
(
  order_id    integer not null,
  delete_date timestamp not null
);

create trigger orders_audit_log
  for orders
  before delete
as
begin
  insert into audit_log (id, delete_date) values (old.order_id, current_timestamp);
end;
/

The solution is to terminate every statement with the alternate delimiter:

create table orders
(
  order_id    integer not null primary key,
  customer_id integer not null,
  product_id  integer not null,
  pieces      integer not null,
  order_date  date    not null
)
/

create table orders_audit_log
(
  order_id    integer not null,
  delete_date timestamp not null
)
/

create trigger orders_audit_log
  for orders
  before delete
as
begin
  insert into audit_log (id, delete_date) values (old.order_id, current_timestamp);
end;
/