EDB Database Compatibility For Oracle Developers Guide v10
EDB Database Compatibility For Oracle Developers Guide v10
Developers Guide
November 1, 2017
Database Compatibility for Oracle Developers Guide
by EnterpriseDB Corporation
Copyright 2007 - 2017 EnterpriseDB Corporation. All rights reserved.
Table of Contents
1 Introduction ............................................................................................................................................ 9
1.1 Whats New ...................................................................................................................................10
1.2 Typographical Conventions Used in this Guide ............................................................................10
1.3 Configuration Parameters Compatible with Oracle Databases ......................................................11
1.3.1 edb_redwood_date .................................................................................................................12
1.3.2 edb_redwood_raw_names .....................................................................................................12
1.3.3 edb_redwood_strings .............................................................................................................13
1.3.4 edb_stmt_level_tx ..................................................................................................................15
1.3.5 oracle_home...........................................................................................................................16
1.4 About the Examples Used in this Guide ........................................................................................17
2 SQL Tutorial..........................................................................................................................................18
2.1 Getting Started ...............................................................................................................................18
2.1.1 Sample Database ....................................................................................................................19
2.1.1.1 Sample Database Installation ............................................................................................19
2.1.1.2 Sample Database Description ...........................................................................................19
2.1.2 Creating a New Table ............................................................................................................30
2.1.3 Populating a Table With Rows ..............................................................................................31
2.1.4 Querying a Table ...................................................................................................................32
2.1.5 Joins Between Tables ............................................................................................................34
2.1.6 Aggregate Functions ..............................................................................................................38
2.1.7 Updates ..................................................................................................................................40
2.1.8 Deletions ................................................................................................................................41
2.1.9 The SQL Language ................................................................................................................42
2.2 Advanced Concepts .......................................................................................................................43
2.2.1 Views .....................................................................................................................................43
2.2.2 Foreign Keys..........................................................................................................................45
2.2.3 The ROWNUM Pseudo-Column ...........................................................................................46
2.2.4 Synonyms ..............................................................................................................................48
2.2.5 Hierarchical Queries ..............................................................................................................52
2.2.5.1 Defining the Parent/Child Relationship ............................................................................53
2.2.5.2 Selecting the Root Nodes ..................................................................................................53
2.2.5.3 Organization Tree in the Sample Application ...................................................................53
2.2.5.4 Node Level ........................................................................................................................55
2.2.5.5 Ordering the Siblings ........................................................................................................56
2.2.5.6 Retrieving the Root Node with CONNECT_BY_ROOT .................................................57
2.2.5.7 Retrieving a Path with SYS_CONNECT_BY_PATH ......................................................61
2.2.6 Multidimensional Analysis ....................................................................................................63
2.2.6.1 ROLLUP Extension ..........................................................................................................65
2.2.6.2 CUBE Extension ...............................................................................................................68
2.2.6.3 GROUPING SETS Extension ...........................................................................................72
2.2.6.4 GROUPING Function .......................................................................................................78
2.2.6.5 GROUPING_ID Function ................................................................................................81
2.3 Profile Management ......................................................................................................................84
2.3.1 Creating a New Profile ..........................................................................................................85
2.3.1.1 Creating a Password Function...........................................................................................88
2.3.2 Altering a Profile ...................................................................................................................91
2.3.3 Dropping a Profile .................................................................................................................92
2.3.4 Associating a Profile with an Existing Role ..........................................................................93
2.3.5 Unlocking a Locked Account ................................................................................................95
2.3.6 Creating a New Role Associated with a Profile .....................................................................97
2.3.7 Backing up Profile Management Functions ...........................................................................99
2.4 Optimizer Hints ...........................................................................................................................100
1 Introduction
Database Compatibility for Oracle means that an application runs in an Oracle
environment as well as in the EDB Postgres Advanced Server (Advanced Server)
environment with minimal or no changes to the application code. Developing an
application that is compatible with Oracle databases in the Advanced Server requires
special attention to which features are used in the construction of the application. For
example, developing a compatible application means choosing compatible:
System and built-in functions for use in SQL statements and procedural logic.
Stored Procedure Language (SPL) when creating database server-side application
logic for stored procedures, functions, triggers, and packages.
Data types that are compatible with Oracle databases
SQL statements that are compatible with Oracle SQL
System catalog views that are compatible with Oracles data dictionary
For detailed information about the compatible SQL syntax, data types, and views, please
see the Database Compatibility for Oracle Developers Reference Guide.
The compatibility offered by the procedures and functions that are part of the Built-in
packages is documented in the Database Compatibility for Oracle Developers Built-in
Packages Guide.
For information about using the compatible tools and utilities (EDB*Plus, EDB*Loader,
DRITA, and EDB*Wrap) that are included with an Advanced Server installation, please
see the Database Compatibility for Oracle Developers Tools and Utilities Guide.
For applications written using the Oracle Call Interface (OCI), EnterpriseDBs Open
Client Library (OCL) provides interoperability with these applications. For detailed
information about using the Open Client Library, please see the EDB Postgres Advanced
Server OCI Connector Guide.
Advanced Server contains a rich set of features that enables development of database
applications for either PostgreSQL or Oracle. For more information about all of the
features of Advanced Server, please consult the user documentation available at the
EnterpriseDB website.
http://www.enterprisedb.com/products-services-training/products/documentation
In the following descriptions a term refers to any word or group of words which may be
language keywords, user-supplied values, literals, etc. A terms exact meaning depends
upon the context in which it is used.
Italic font introduces a new term, typically, in the sentence that defines it for the
first time.
Fixed-width (mono-spaced) font is used for terms that must be given
literally such as SQL commands, specific table and column names used in the
examples, programming language keywords, etc. For example, SELECT * FROM
emp;
Italic fixed-width font is used for terms for which the user must
substitute values in actual usage. For example, DELETE FROM table_name;
A vertical pipe | denotes a choice between the terms on either side of the pipe. A
vertical pipe is used to separate two or more alternative terms within square
brackets (optional choices) or braces (one mandatory choice).
Square brackets [ ] denote that one or none of the enclosed term(s) may be
substituted. For example, [ a | b ], means choose one of a or b or neither
of the two.
Braces {} denote that exactly one of the enclosed alternatives must be specified.
For example, { a | b }, means exactly one of a or b must be specified.
Ellipses ... denote that the proceeding term may be repeated. For example, [ a |
b ] ... means that you may have the sequence, b a a b a.
1.3.1 edb_redwood_date
When DATE appears as the data type of a column in the commands, it is translated to
TIMESTAMP(0) at the time the table definition is stored in the data base if the
configuration parameter edb_redwood_date is set to TRUE. Thus, a time component
will also be stored in the column along with the date. This is consistent with Oracles
DATE data type.
See the Database Compatibility for Oracle Developers Reference Guide for more
information about date/time data types.
1.3.2 edb_redwood_raw_names
For example, the following user name is created, and then a session is started with that
user.
When connected to the database as reduser, the following tables are created.
These names now match the case when viewed from the PostgreSQL pg_tables
catalog.
1.3.3 edb_redwood_strings
In Oracle, when a string is concatenated with a null variable or null column, the result is
the original string; however, in PostgreSQL concatenation of a string with a null variable
or null column gives a null result. If the edb_redwood_strings parameter is set to
TRUE, the aforementioned concatenation operation results in the original string as done
by Oracle. If edb_redwood_strings is set to FALSE, the native PostgreSQL behavior
is maintained.
The sample application introduced in the next section contains a table of employees. This
table has a column named comm that is null for most employees. The following query is
run with edb_redwood_string set to FALSE. The concatenation of a null column with
non-empty strings produces a final result of null, so only employees that have a
commission appear in the query result. The output line for all other employees is null.
EMPLOYEE COMPENSATION
----------------------------------
(14 rows)
The following is the same query executed when edb_redwood_strings is set to TRUE.
Here, the value of a null column is treated as an empty string. The concatenation of an
empty string with a non-empty string produces the non-empty string. This result is
consistent with the results produced by Oracle for the same query.
EMPLOYEE COMPENSATION
----------------------------------
SMITH 800.00
ALLEN 1,600.00 300.00
WARD 1,250.00 500.00
JONES 2,975.00
MARTIN 1,250.00 1,400.00
BLAKE 2,850.00
CLARK 2,450.00
SCOTT 3,000.00
KING 5,000.00
TURNER 1,500.00 .00
ADAMS 1,100.00
JAMES 950.00
FORD 3,000.00
MILLER 1,300.00
(14 rows)
1.3.4 edb_stmt_level_tx
In Oracle, when a runtime error occurs in a SQL command, all the updates on the
database caused by that single command are rolled back. This is called statement level
transaction isolation. For example, if a single UPDATE command successfully updates
five rows, but an attempt to update a sixth row results in an exception, the updates to all
six rows made by this UPDATE command are rolled back. The effects of prior SQL
commands that have not yet been committed or rolled back are pending until a COMMIT
or ROLLBACK command is executed.
In PostgreSQL, if an exception occurs while executing a SQL command, all the updates
on the database since the start of the transaction are rolled back. In addition, the
transaction is left in an aborted state and either a COMMIT or ROLLBACK command must
be issued before another transaction can be started.
Note: Use edb_stmt_level_tx set to TRUE only when absolutely necessary, as this
may cause a negative performance impact.
The following example run in PSQL shows that when edb_stmt_level_tx is FALSE,
the abort of the second INSERT command also rolls back the first INSERT command.
Note that in PSQL, the command \set AUTOCOMMIT off must be issued, otherwise
every statement commits automatically defeating the purpose of this demonstration of the
effect of edb_stmt_level_tx.
COMMIT;
SELECT empno, ename, deptno FROM emp WHERE empno > 9000;
In the following example, with edb_stmt_level_tx set to TRUE, the first INSERT
command has not been rolled back after the error on the second INSERT command. At
this point, the first INSERT command can either be committed or rolled back.
SELECT empno, ename, deptno FROM emp WHERE empno > 9000;
COMMIT;
A ROLLBACK command could have been issued instead of the COMMIT command in
which case the insert of employee number 9001 would have been rolled back as well.
1.3.5 oracle_home
Before creating a link to an Oracle server, you must direct Advanced Server to the correct
Oracle home directory. Set the LD_LIBRARY_PATH environment variable on Linux (or
PATH on Windows) to the lib directory of the Oracle client installation directory.
For Windows only, you can instead set the value of the oracle_home configuration
parameter in the postgresql.conf file. The value specified in the oracle_home
configuration parameter will override the Windows PATH environment variable.
When using a Linux service script to start Advanced Server, be sure LD_LIBRARY_PATH
has been set within the service script so it is in effect when the script invokes the pg_ctl
utility to start Advanced Server.
Substitute the name of the Windows directory that contains oci.dll for
lib_directory.
After setting the oracle_home configuration parameter, you must restart the server for
the changes to take effect. Restart the server from the Windows Services console.
Examples and output from examples are shown in fixed-width, blue font on
a light blue background.
During installation of the EDB Postgres Advanced Server the selection for
configuration and defaults compatible with Oracle databases must be chosen in
order to reproduce the same results as the examples shown in this guide. A default
compatible configuration can be verified by issuing the following commands in
PSQL and obtaining the same results as shown below.
SHOW edb_redwood_date;
edb_redwood_date
------------------
on
SHOW datestyle;
DateStyle
--------------
Redwood, DMY
SHOW edb_redwood_strings;
edb_redwood_strings
---------------------
on
The examples use the sample tables, dept, emp, and jobhist, created and
loaded when Advanced Server is installed. The emp table is installed with triggers
that must be disabled in order to reproduce the same results as shown in this
guide. Log onto Advanced Server as the enterprisedb superuser and disable
the triggers by issuing the following command.
The triggers on the emp table can later be re-activated with the following
command.
2 SQL Tutorial
This section is an introduction to the SQL language for those new to relational database
management systems. Basic operations such as creating, populating, querying, and
updating tables are discussed along with examples.
More advanced concepts such as view, foreign keys, and transactions are discussed as
well.
Each table is a named collection of rows. Each row of a given table has the same set of
named columns, and each column is of a specific data type. Whereas columns have a
fixed order in each row, it is important to remember that SQL does not guarantee the
order of the rows within the table in any way (although they can be explicitly sorted for
display).
Tables are grouped into databases, and a collection of databases managed by a single
Advanced Server instance constitutes a database cluster.
The tables and programs in the sample database can be re-created at any time by
executing the script, edb-sample.sql, located in the samples subdirectory of the
Advanced Server home directory.
Creates the sample tables and programs in the currently connected database
Grants all permissions on the tables to the PUBLIC group
The tables and programs will be created in the first schema of the search path in which
the current user has permission to create tables and procedures. You can display the
search path by issuing the command:
SHOW SEARCH_PATH;
Each employee has an identification number, name, hire date, salary, and manager. Some
employees earn a commission in addition to their salary. All employee-related
information is stored in the emp table.
The sample company is regionally diverse, so the database keeps track of the location of
the departments. Each company employee is assigned to a department. Each department
is identified by a unique department number and a short name. Each department is
associated with one location. All department-related information is stored in the dept
table.
The company also tracks information about jobs held by the employees. Some employees
have been with the company for a long time and have held different positions, received
raises, switched departments, etc. When a change in employee status occurs, the company
records the end date of the former position. A new job record is added with the start date
and the new job title, department, salary, and the reason for the status change. All
employee history is maintained in the jobhist table.
deptno
dept
dname
loc
emp jobhist
empno empno
startdate
ename
job enddate
mgr job
hiredate sal
sal comm
comm deptno
deptno chgdesc
--
-- Script that creates the 'sample' tables, views, procedures,
-- functions, triggers, etc.
--
-- Start new transaction - commit all or nothing
--
BEGIN;
/
--
-- Create and load tables used in the documentation examples.
--
-- Create the 'dept' table
--
CREATE TABLE dept (
deptno NUMBER(2) NOT NULL CONSTRAINT dept_pk PRIMARY KEY,
dname VARCHAR2(14) CONSTRAINT dept_dname_uq UNIQUE,
loc VARCHAR2(13)
);
--
-- Create the 'emp' table
--
CREATE TABLE emp (
empno NUMBER(4) NOT NULL CONSTRAINT emp_pk PRIMARY KEY,
ename VARCHAR2(10),
job VARCHAR2(9),
mgr NUMBER(4),
hiredate DATE,
sal NUMBER(7,2) CONSTRAINT emp_sal_ck CHECK (sal > 0),
comm NUMBER(7,2),
deptno NUMBER(2) CONSTRAINT emp_ref_dept_fk
REFERENCES dept(deptno)
);
--
-- Create the 'jobhist' table
--
CREATE TABLE jobhist (
empno NUMBER(4) NOT NULL,
startdate DATE NOT NULL,
enddate DATE,
job VARCHAR2(9),
sal NUMBER(7,2),
comm NUMBER(7,2),
deptno NUMBER(2),
chgdesc VARCHAR2(80),
CONSTRAINT jobhist_pk PRIMARY KEY (empno, startdate),
CONSTRAINT jobhist_ref_emp_fk FOREIGN KEY (empno)
REFERENCES emp(empno) ON DELETE CASCADE,
CONSTRAINT jobhist_ref_dept_fk FOREIGN KEY (deptno)
REFERENCES dept (deptno) ON DELETE SET NULL,
CONSTRAINT jobhist_date_chk CHECK (startdate <= enddate)
);
--
-- Create the 'salesemp' view
--
CREATE OR REPLACE VIEW salesemp AS
SELECT empno, ename, hiredate, sal, comm FROM emp WHERE job = 'SALESMAN';
--
-- Sequence to generate values for function 'new_empno'.
A new table is created by specifying the table name, along with all column names and
their types. The following is a simplified version of the emp sample table with just the
minimal information needed to define a table.
You can enter this into PSQL with line breaks. PSQL will recognize that the command is
not terminated until the semicolon.
White space (i.e., spaces, tabs, and newlines) may be used freely in SQL commands. That
means you can type the command aligned differently than the above, or even all on one
line. Two dashes ("--") introduce comments. Whatever follows them is ignored up to the
end of the line. SQL is case insensitive about key words and identifiers, except when
identifiers are double-quoted to preserve the case (not done above).
VARCHAR2(10) specifies a data type that can store arbitrary character strings up to 10
characters in length. NUMBER(7,2) is a fixed point number with precision 7 and scale 2.
NUMBER(4) is an integer number with precision 4 and scale 0.
Advanced Server supports the usual SQL data types INTEGER, SMALLINT, NUMBER,
REAL, DOUBLE PRECISION, CHAR, VARCHAR2, DATE, and TIMESTAMP as well as
various synonyms for these types.
If you dont need a table any longer or want to recreate it differently you can remove it
using the following command:
Note that all data types use rather obvious input formats. Constants that are not simple
numeric values usually must be surrounded by single quotes ('), as in the example. The
DATE type is actually quite flexible in what it accepts, but for this tutorial we will stick to
the unambiguous format shown here.
The syntax used so far requires you to remember the order of the columns. An alternative
syntax allows you to list the columns explicitly:
You can list the columns in a different order if you wish or even omit some columns, e.g.,
if the commission is unknown:
Many developers consider explicitly listing the columns better style than relying on the
order implicitly.
To retrieve data from a table, the table is queried. An SQL SELECT statement is used to
do this. The statement is divided into a select list (the part that lists the columns to be
returned), a table list (the part that lists the tables from which to retrieve the data), and an
optional qualification (the part that specifies any restrictions). The following query lists
all columns of all employees in the table in no particular order.
Here, * in the select list means all columns. The following is the output from this
query.
empno | ename | job | mgr | hiredate | sal | comm | deptno
-------+--------+-----------+------+--------------------+---------+---------+--------
7369 | SMITH | CLERK | 7902 | 17-DEC-80 00:00:00 | 800.00 | | 20
7499 | ALLEN | SALESMAN | 7698 | 20-FEB-81 00:00:00 | 1600.00 | 300.00 | 30
7521 | WARD | SALESMAN | 7698 | 22-FEB-81 00:00:00 | 1250.00 | 500.00 | 30
7566 | JONES | MANAGER | 7839 | 02-APR-81 00:00:00 | 2975.00 | | 20
7654 | MARTIN | SALESMAN | 7698 | 28-SEP-81 00:00:00 | 1250.00 | 1400.00 | 30
7698 | BLAKE | MANAGER | 7839 | 01-MAY-81 00:00:00 | 2850.00 | | 30
7782 | CLARK | MANAGER | 7839 | 09-JUN-81 00:00:00 | 2450.00 | | 10
7788 | SCOTT | ANALYST | 7566 | 19-APR-87 00:00:00 | 3000.00 | | 20
7839 | KING | PRESIDENT | | 17-NOV-81 00:00:00 | 5000.00 | | 10
7844 | TURNER | SALESMAN | 7698 | 08-SEP-81 00:00:00 | 1500.00 | 0.00 | 30
7876 | ADAMS | CLERK | 7788 | 23-MAY-87 00:00:00 | 1100.00 | | 20
7900 | JAMES | CLERK | 7698 | 03-DEC-81 00:00:00 | 950.00 | | 30
7902 | FORD | ANALYST | 7566 | 03-DEC-81 00:00:00 | 3000.00 | | 20
7934 | MILLER | CLERK | 7782 | 23-JAN-82 00:00:00 | 1300.00 | | 10
(14 rows)
You may specify any arbitrary expression in the select list. For example, you can do:
Notice how the AS clause is used to re-label the output column. (The AS clause is
optional.)
A query can be qualified by adding a WHERE clause that specifies which rows are wanted.
The WHERE clause contains a Boolean (truth value) expression, and only rows for which
the Boolean expression is true are returned. The usual Boolean operators (AND, OR, and
NOT) are allowed in the qualification. For example, the following retrieves the employees
in department 20 with salaries over $1000.00:
SELECT ename, sal, deptno FROM emp WHERE deptno = 20 AND sal > 1000;
You can request that the results of a query be returned in sorted order:
You can request that duplicate rows be removed from the result of a query:
job
-----------
ANALYST
CLERK
MANAGER
PRESIDENT
SALESMAN
(5 rows)
The following section shows how to obtain rows from more than one table in a single
query.
Thus far, our queries have only accessed one table at a time. Queries can access multiple
tables at once, or access the same table in such a way that multiple rows of the table are
being processed at the same time. A query that accesses multiple rows of the same or
different tables at one time is called a join query. For example, say you wish to list all the
employee records together with the name and location of the associated department. To
do that, we need to compare the deptno column of each row of the emp table with the
deptno column of all rows in the dept table, and select the pairs of rows where these
values match. This would be accomplished by the following query:
There is no result row for department 40. This is because there is no matching
entry in the emp table for department 40, so the join ignores the unmatched rows
in the dept table. Shortly we will see how this can be fixed.
It is more desirable to list the output columns qualified by table name rather than
using * or leaving out the qualification as follows:
SELECT ename, sal, dept.deptno, dname, loc FROM emp, dept WHERE emp.deptno =
dept.deptno;
Since all the columns had different names (except for deptno which therefore must be
qualified), the parser automatically found out which table they belong to, but it is good
style to fully qualify column names in join queries:
Join queries of the kind seen thus far can also be written in this alternative form:
This syntax is not as commonly used as the one above, but we show it here to help you
understand the following topics.
You will notice that in all the above results for joins no employees were returned that
belonged to department 40 and as a consequence, the record for department 40 never
appears. Now we will figure out how we can get the department 40 record in the results
despite the fact that there are no matching employees. What we want the query to do is to
scan the dept table and for each row to find the matching emp row. If no matching row
is found we want some empty values to be substituted for the emp tables columns.
This kind of query is called an outer join. (The joins we have seen so far are inner joins.)
The command looks like this:
This query is called a left outer join because the table mentioned on the left of the join
operator will have each of its rows in the output at least once, whereas the table on the
right will only have those rows output that match some row of the left table. When a left-
table row is selected for which there is no right-table match, empty (NULL) values are
substituted for the right-table columns.
An alternative syntax for an outer join is to use the outer join operator, (+), in the join
condition within the WHERE clause. The outer join operator is placed after the column
name of the table for which null values should be substituted for unmatched rows. So for
all the rows in the dept table that have no matching rows in the emp table, Advanced
Server returns null for any select list expressions containing columns of emp. Hence the
above example could be rewritten as:
We can also join a table against itself. This is called a self join. As an example, suppose
we wish to find the name of each employee along with the name of that employees
manager. So we need to compare the mgr column of each emp row to the empno column
of all other emp rows.
SELECT e1.ename || ' works for ' || e2.ename AS "Employees and their
Managers" FROM emp e1, emp e2 WHERE e1.mgr = e2.empno;
Here, the emp table has been re-labeled as e1 to represent the employee row in the select
list and in the join condition, and also as e2 to represent the matching employee row
acting as manager in the select list and in the join condition. These kinds of aliases can be
used in other queries to save some typing, for example:
SELECT e.ename, e.mgr, d.deptno, d.dname, d.loc FROM emp e, dept d WHERE
e.deptno = d.deptno;
Like most other relational database products, Advanced Server supports aggregate
functions. An aggregate function computes a single result from multiple input rows. For
example, there are aggregates to compute the COUNT, SUM, AVG (average), MAX
(maximum), and MIN (minimum) over a set of rows.
As an example, the highest and lowest salaries can be found with the following query:
highest_salary | lowest_salary
----------------+---------------
5000.00 | 800.00
(1 row)
If we wanted to find the employee with the largest salary, we may be tempted to try:
This does not work because the aggregate function, MAX, cannot be used in the WHERE
clause. This restriction exists because the WHERE clause determines the rows that will go
into the aggregation stage so it has to be evaluated before aggregate functions are
computed. However, the query can be restated to accomplish the intended result by using
a subquery:
SELECT ename FROM emp WHERE sal = (SELECT MAX(sal) FROM emp);
ename
-------
KING
(1 row)
The subquery is an independent computation that obtains its own result separately from
the outer query.
Aggregates are also very useful in combination with the GROUP BY clause. For example,
the following query gets the highest salary in each department.
deptno | max
--------+---------
10 | 5000.00
20 | 3000.00
30 | 2850.00
(3 rows)
This query produces one output row per department. Each aggregate result is computed
over the rows matching that department. These grouped rows can be filtered using the
HAVING clause.
SELECT deptno, MAX(sal) FROM emp GROUP BY deptno HAVING AVG(sal) > 2000;
deptno | max
--------+---------
10 | 5000.00
20 | 3000.00
(2 rows)
This query gives the same results for only those departments that have an average salary
greater than 2000.
Finally, the following query takes into account only the highest paid employees who are
analysts in each department.
SELECT deptno, MAX(sal) FROM emp WHERE job = 'ANALYST' GROUP BY deptno HAVING
AVG(sal) > 2000;
deptno | max
--------+---------
20 | 3000.00
(1 row)
There is a subtle distinction between the WHERE and HAVING clauses. The WHERE clause
filters out rows before grouping occurs and aggregate functions are applied. The HAVING
clause applies filters on the results after rows have been grouped and aggregate functions
have been computed for each group.
So in the previous example, only employees who are analysts are considered. From this
subset, the employees are grouped by department and only those groups where the
average salary of analysts in the group is greater than 2000 are in the final result. This is
true of only the group for department 20 and the maximum analyst salary in department
20 is 3000.00.
2.1.7 Updates
The column values of existing rows can be changed using the UPDATE command. For
example, the following sequence of commands shows the before and after results of
giving everyone who is a manager a 10% raise:
ename | sal
-------+---------
JONES | 2975.00
BLAKE | 2850.00
CLARK | 2450.00
(3 rows)
ename | sal
-------+---------
JONES | 3272.50
BLAKE | 3135.00
CLARK | 2695.00
(3 rows)
2.1.8 Deletions
Rows can be removed from a table using the DELETE command. For example, the
following sequence of commands shows the before and after results of deleting all
employees in department 20.
ename | deptno
--------+--------
SMITH | 20
ALLEN | 30
WARD | 30
JONES | 20
MARTIN | 30
BLAKE | 30
CLARK | 10
SCOTT | 20
KING | 10
TURNER | 30
ADAMS | 20
JAMES | 30
FORD | 20
MILLER | 10
(14 rows)
Be extremely careful of giving a DELETE command without a WHERE clause such as the
following:
This statement will remove all rows from the given table, leaving it completely empty.
The system will not request confirmation before doing this.
Advanced Server supports SQL language that is compatible with Oracle syntax as well as
syntax and commands for extended functionality (functionality that does not provide
database compatibility for Oracle or support Oracle-styled applications).
The Reference Guide that supports the Database Compatibility for Oracle Developer's
Guide provides detailed information about:
To review a copy of the Reference Guide, visit the Advanced Server website at:
http://www.enterprisedb.com/products-services-training/products/documentation
2.2.1 Views
If this is a query that is used repeatedly, a shorthand method of reusing this query without
re-typing the entire SELECT command each time is to create a view as shown below.
The view name, employee_pay, can now be used like an ordinary table name to
perform the query.
Making liberal use of views is a key aspect of good SQL database design. Views provide
a consistent interface that encapsulate details of the structure of your tables which may
change as your application evolves.
Views can be used in almost any place a real table can be used. Building views upon
other views is not uncommon.
Suppose you want to make sure all employees belong to a valid department. This is called
maintaining the referential integrity of your data. In simplistic database systems this
would be implemented (if at all) by first looking at the dept table to check if a matching
record exists, and then inserting or rejecting the new employee record. This approach has
a number of problems and is very inconvenient. Advanced Server can make it easier for
you.
A modified version of the emp table presented in Section 2.1.2 is shown in this section
with the addition of a foreign key constraint. The modified emp table looks like the
following:
If an attempt is made to issue the following INSERT command in the sample emp table,
the foreign key constraint, emp_ref_dept_fk, ensures that department 50 exists in the
dept table. Since it does not, the command is rejected.
The behavior of foreign keys can be finely tuned to your application. Making correct use
of foreign keys will definitely improve the quality of your database applications, so you
are strongly encouraged to learn more about them.
This feature can be used to limit the number of rows retrieved by a query. This is
demonstrated in the following example:
The ROWNUM value is assigned to each row before any sorting of the result set takes place.
Thus, the result set is returned in the order given by the ORDER BY clause, but the
ROWNUM values may not necessarily be in ascending order as shown in the following
example:
SELECT ROWNUM, empno, ename, job FROM emp WHERE ROWNUM < 5 ORDER BY ename;
The following example shows how a sequence number can be added to every row in the
jobhist table. First a new column named, seqno, is added to the table and then seqno
is set to ROWNUM in the UPDATE command.
2.2.4 Synonyms
A synonym is an identifier that can be used to reference another database object in a SQL
statement. A synonym is useful in cases where a database object would normally require
full qualification by schema name to be properly referenced in a SQL statement. A
synonym defined for that object simplifies the reference to a single, unqualified name.
tables
views
materialized views
sequences
packages
procedures
functions
types
objects that are accessible through a database link
other synonyms
Neither the referenced schema or referenced object must exist at the time that you create
the synonym; a synonym may refer to a non-existent object or schema. A synonym will
become invalid if you drop the referenced object or schema. You must explicitly drop a
synonym to remove it.
As with any other schema object, Advanced Server uses the search path to resolve
unqualified synonym names. If you have two synonyms with the same name, an
unqualified reference to a synonym will resolve to the first synonym with the given name
in the search path. If public is in your search path, you can refer to a synonym in that
schema without qualifying that name.
When Advanced Server executes an SQL command, the privileges of the current user are
checked against the synonyms underlying database object; if the user does not have the
proper permissions for that object, the SQL command will fail.
Creating a Synonym
Use the CREATE SYNONYM command to create a synonym. The syntax is:
Parameters:
syn_name
syn_name is the name of the synonym. A synonym name must be unique within
a schema.
schema
schema specifies the name of the schema that the synonym resides in. If you do
not specify a schema name, the synonym is created in the first existing schema in
your search path.
object_name
object_schema
object_schema specifies the name of the schema that the object resides in.
dblink_name
dblink_name specifies the name of the database link through which a target
object may be accessed.
Include the REPLACE clause to replace an existing synonym definition with a new
synonym definition.
Include the PUBLIC clause to create the synonym in the public schema. Compatible
with Oracle databases, the CREATE PUBLIC SYNONYM command creates a synonym that
resides in the public schema:
The following example creates a synonym named personnel that refers to the
enterprisedb.emp table.
Unless the synonym is schema qualified in the CREATE SYNONYM command, it will be
created in the first existing schema in your search path. You can view your search path
by executing the following command:
SHOW SEARCH_PATH;
search_path
-----------------------
development,accounting
(1 row)
In our example, if a schema named development does not exist, the synonym will be
created in the schema named accounting.
Now, the emp table in the enterprisedb schema can be referenced in any SQL
statement (DDL or DML), by using the synonym, personnel:
Deleting a Synonym
To delete a synonym, use the command, DROP SYNONYM. The syntax is:
Parameters:
syn_name
syn_name is the name of the synonym. A synonym name must be unique within
a schema.
schema specifies the name of the schema in which the synonym resides.
Like any other object that can be schema-qualified, you may have two synonyms with the
same name in your search path. To disambiguate the name of the synonym that you are
dropping, include a schema name. Unless a synonym is schema qualified in the DROP
SYNONYM command, Advanced Server deletes the first instance of the synonym it finds in
your search path.
You can optionally include the PUBLIC clause to drop a synonym that resides in the
public schema. Compatible with Oracle databases, the DROP PUBLIC SYNONYM
command drops a synonym that resides in the public schema:
A hierarchical query is a type of query that returns the rows of the result set in a
hierarchical order based upon data forming a parent-child relationship. A hierarchy is
typically represented by an inverted tree structure. The tree is comprised of
interconnected nodes. Each node may be connected to none, one, or multiple child nodes.
Each node is connected to one parent node except for the top node which has no parent.
This node is the root node. Each tree has exactly one root node. Nodes that dont have
any children are called leaf nodes. A tree always has at least one leaf node - e.g., the
trivial case where the tree is comprised of a single node. In this case it is both the root and
the leaf.
In a hierarchical query the rows of the result set represent the nodes of one or more trees.
Note: It is possible that a single, given row may appear in more than one tree and thus
appear more than once in the result set.
select_list is one or more expressions that comprise the fields of the result set.
table_expression is one or more tables or views from which the rows of the result set
originate. other is any additional legal SELECT command clauses. The clauses pertinent
to hierarchical queries, START WITH, CONNECT BY, and ORDER SIBLINGS BY are
described in the following sections.
Note: At this time, Advanced Server does not support the use of AND (or other operators)
in the CONNECT BY clause.
For any given row, its parent and its children are determined by the CONNECT BY clause.
The CONNECT BY clause must consist of two expressions compared with the equals (=)
operator. In addition, one of these two expressions must be preceded by the keyword,
PRIOR.
Note: The evaluation process to determine if a row is a child node occurs on every row
returned by table_expression before the WHERE clause is applied to
table_expression.
By iteratively repeating this process treating each child node found in the prior steps as a
parent, an inverted tree of nodes is constructed. The process is complete when the final
set of child nodes has no children of their own - these are the leaf nodes.
A SELECT command that includes a CONNECT BY clause typically includes the START
WITH clause. The START WITH clause determines the rows that are to be the root nodes -
i.e., the rows that are the initial parent nodes upon which the algorithm described
previously is to be applied. This is further explained in the following section.
Consider the emp table of the sample application. The rows of the emp table form a
hierarchy based upon the mgr column which contains the employee number of the
employees manager. Each employee has at most, one manager. KING is the president of
the company so he has no manager, therefore KINGs mgr column is null. Also, it is
possible for an employee to act as a manager for more than one employee. This
relationship forms a typical, tree-structured, hierarchical organization chart as illustrated
below.
To form a hierarchical query based upon this relationship, the SELECT command includes
the clause, CONNECT BY PRIOR empno = mgr. For example, given the company
president, KING, with employee number 7839, any employee whose mgr column is 7839
reports directly to KING which is true for JONES, BLAKE, and CLARK (these are the child
nodes of KING). Similarly, for employee, JONES, any other employee with mgr column
equal to 7566 is a child node of JONES - these are SCOTT and FORD in this example.
The top of the organization chart is KING so there is one root node in this tree. The
START WITH mgr IS NULL clause selects only KING as the initial root node.
The rows in the query output traverse each branch from the root to leaf moving in a top-
to-bottom, left-to-right order. Below is the output from this query.
LEVEL is a pseudo-column that can be used wherever a column can appear in the SELECT
command. For each row in the result set, LEVEL returns a non-zero integer value
designating the depth in the hierarchy of the node represented by this row. The LEVEL for
root nodes is 1. The LEVEL for direct children of root nodes is 2, and so on.
The following query is a modification of the previous query with the addition of the
LEVEL pseudo-column. In addition, using the LEVEL value, the employee names are
indented to further emphasize the depth in the hierarchy of each row.
SELECT LEVEL, LPAD (' ', 2 * (LEVEL - 1)) || ename "employee", empno, mgr
FROM emp START WITH mgr IS NULL
CONNECT BY PRIOR empno = mgr;
Nodes that share a common parent and are at the same level are called siblings. For
example in the above output, employees ALLEN, WARD, MARTIN, TURNER, and JAMES are
siblings since they are all at level three with parent, BLAKE. JONES, BLAKE, and CLARK
are siblings since they are at level two and KING is their common parent.
The previous query is further modified with the addition of ORDER SIBLINGS BY
ename ASC.
SELECT LEVEL, LPAD (' ', 2 * (LEVEL - 1)) || ename "employee", empno, mgr
FROM emp START WITH mgr IS NULL
CONNECT BY PRIOR empno = mgr
ORDER SIBLINGS BY ename ASC;
The output from the prior query is now modified so the siblings appear in ascending
order by name. Siblings BLAKE, CLARK, and JONES are now alphabetically arranged
under KING. Siblings ALLEN, JAMES, MARTIN, TURNER, and WARD are alphabetically
arranged under BLAKE, and so on.
This final example adds the WHERE clause and starts with three root nodes. After the node
tree is constructed, the WHERE clause filters out rows in the tree to form the result set.
SELECT LEVEL, LPAD (' ', 2 * (LEVEL - 1)) || ename "employee", empno, mgr
FROM emp WHERE mgr IN (7839, 7782, 7902, 7788)
START WITH ename IN ('BLAKE','CLARK','JONES')
CONNECT BY PRIOR empno = mgr
ORDER SIBLINGS BY ename ASC;
The output from the query shows three root nodes (level one) - BLAKE, CLARK, and
JONES. In addition, rows that do not satisfy the WHERE clause have been eliminated from
the output.
In the context of the SELECT list, the CONNECT_BY_ROOT operator is shown by the
following.
The following are some points to note about the CONNECT_BY_ROOT operator.
The CONNECT_BY_ROOT operator can be used in the SELECT list, the WHERE
clause, the GROUP BY clause, the HAVING clause, the ORDER BY clause, and the
ORDER SIBLINGS BY clause as long as the SELECT command is for a
hierarchical query.
The CONNECT_BY_ROOT operator cannot be used in the CONNECT BY clause or
the START WITH clause of the hierarchical query.
It is possible to apply CONNECT_BY_ROOT to an expression involving a column,
but to do so, the expression must be enclosed within parentheses.
The following query shows the use of the CONNECT_BY_ROOT operator to return the
employee number and employee name of the root node for each employee listed in the
result set based on trees starting with employees BLAKE, CLARK, and JONES.
SELECT LEVEL, LPAD (' ', 2 * (LEVEL - 1)) || ename "employee", empno, mgr,
CONNECT_BY_ROOT empno "mgr empno",
CONNECT_BY_ROOT ename "mgr ename"
FROM emp
START WITH ename IN ('BLAKE','CLARK','JONES')
CONNECT BY PRIOR empno = mgr
ORDER SIBLINGS BY ename ASC;
Note that the output from the query shows that all of the root nodes in columns mgr
empno and mgr ename are one of the employees, BLAKE, CLARK, or JONES, listed in the
START WITH clause.
The following is a similar query, but producing only one tree starting with the single, top-
level, employee where the mgr column is null.
SELECT LEVEL, LPAD (' ', 2 * (LEVEL - 1)) || ename "employee", empno, mgr,
CONNECT_BY_ROOT empno "mgr empno",
CONNECT_BY_ROOT ename "mgr ename"
FROM emp START WITH mgr IS NULL
CONNECT BY PRIOR empno = mgr
ORDER SIBLINGS BY ename ASC;
In the following output, all of the root nodes in columns mgr empno and mgr ename
indicate KING as the root for this particular query.
By contrast, the following example omits the START WITH clause thereby resulting in
fourteen trees.
SELECT LEVEL, LPAD (' ', 2 * (LEVEL - 1)) || ename "employee", empno, mgr,
CONNECT_BY_ROOT empno "mgr empno",
The following is the output from the query. Each node appears at least once as a root
node under the mgr empno and mgr ename columns since even the leaf nodes form the
top of their own trees.
value of the currently processed row while the first occurrence of ename results in the
value from the root node.
SELECT LEVEL, LPAD (' ', 2 * (LEVEL - 1)) || ename "employee", empno, mgr,
CONNECT_BY_ROOT ename || ' manages ' || ename "top mgr/employee"
FROM emp
START WITH ename IN ('BLAKE','CLARK','JONES')
CONNECT BY PRIOR empno = mgr
ORDER SIBLINGS BY ename ASC;
The following is the output from the query. Note the values produced under the top
mgr/employee column.
SELECT LEVEL, LPAD (' ', 2 * (LEVEL - 1)) || ename "employee", empno, mgr,
CONNECT_BY_ROOT ('Manager ' || ename || ' is emp # ' || empno)
"top mgr/empno"
FROM emp
START WITH ename IN ('BLAKE','CLARK','JONES')
CONNECT BY PRIOR empno = mgr
ORDER SIBLINGS BY ename ASC;
The following is the output of the query. Note that the values of both ename and empno
are affected by the CONNECT_BY_ROOT operator and as a result, return the values from
the root node as shown under the top mgr/empno column.
column is the name of a column that resides within a table specified in the
hierarchical query that is calling the function.
delimiter is the varchar value that separates each entry in the specified
column.
The following example returns a list of employee names, and their managers; if the
manager has a manager, that name is appended to the result:
The level column displays the number of levels that the query returned.
The ename column displays the employee name.
The managers column contains the hierarchical list of managers.
The GROUP BY clause of the SQL SELECT command supports the following extensions
that simplify the process of producing aggregate results.
ROLLUP extension
CUBE extension
GROUPING SETS extension
In addition, the GROUPING function and the GROUPING_ID function can be used in the
SELECT list or the HAVING clause to aid with the interpretation of the results when these
extensions are used.
Note: The sample dept and emp tables are used extensively in this discussion to provide
usage examples. The following changes were applied to these tables to provide more
informative results.
The following rows from a join of the emp and dept tables are used:
The loc, dname, and job columns are used for the dimensions of the SQL aggregations
used in the examples. The resulting facts of the aggregations are the number of
employees obtained by using the COUNT(*) function.
A basic query grouping the loc, dname, and job columns is given by the following.
The rows of this result set using the basic GROUP BY clause without extensions are
referred to as the base aggregate rows.
The ROLLUP and CUBE extensions add to the base aggregate rows by providing additional
levels of subtotals to the result set.
The GROUPING SETS extension provides the ability to combine different types of
groupings into a single result set.
The GROUPING and GROUPING_ID functions aid in the interpretation of the result set.
The additions provided by these extensions are discussed in more detail in the subsequent
sections.
The ROLLUP extension produces a hierarchical set of groups with subtotals for each
hierarchical group as well as a grand total. The order of the hierarchy is determined by
the order of the expressions given in the ROLLUP expression list. The top of the hierarchy
is the leftmost item in the list. Each successive item proceeding to the right moves down
the hierarchy with the rightmost item being the lowest level.
Each expr is an expression that determines the grouping of the result set. If enclosed
within parenthesis as ( expr_1a, expr_1b, ...) then the combination of values
returned by expr_1a and expr_1b defines a single grouping level of the hierarchy.
The base level of aggregates returned in the result set is for each unique combination of
values returned by the expression list.
In addition, a subtotal is returned for the first item in the list (expr_1 or the combination
of ( expr_1a, expr_1b, ...), whichever is specified) for each unique value. A
subtotal is returned for the second item in the list (expr_2 or the combination of (
expr_2a, expr_2b, ...), whichever is specified) for each unique value, within each
grouping of the first item and so on. Finally a grand total is returned for the entire result
set.
For the subtotal rows, null is returned for the items across which the subtotal is taken.
The ROLLUP extension specified within the context of the GROUP BY clause is shown by
the following:
The GROUP BY clause may specify multiple ROLLUP extensions as well as multiple
occurrences of other GROUP BY extensions and individual expressions.
The ORDER BY clause should be used if you want the output to display in a hierarchical
or other meaningful structure. There is no guarantee on the order of the result set if no
ORDER BY clause is specified.
The number of grouping levels or totals is n + 1 where n represents the number of items
in the ROLLUP expression list. A parenthesized list counts as one item.
The following query produces a rollup based on a hierarchy of columns loc, dname,
then job.
The following is the result of the query. There is a count of the number of employees for
each unique combination of loc, dname, and job, as well as subtotals for each unique
combination of loc and dname, for each unique value of loc, and a grand total
displayed on the last line.
The following query shows the effect of combining items in the ROLLUP list within
parenthesis.
In the output, note that there are no subtotals for loc and dname combinations as in the
prior example.
If the first two columns in the ROLLUP list are enclosed in parenthesis, the subtotal levels
differ as well.
Now there is a subtotal for each unique loc and dname combination, but none for unique
values of loc.
The CUBE extension is similar to the ROLLUP extension. However, unlike ROLLUP, which
produces groupings and results in a hierarchy based on a left to right listing of items in
the ROLLUP expression list, a CUBE produces groupings and subtotals based on every
permutation of all items in the CUBE expression list. Thus, the result set contains more
rows than a ROLLUP performed on the same expression list.
Each expr is an expression that determines the grouping of the result set. If enclosed
within parenthesis as ( expr_1a, expr_1b, ...) then the combination of values
returned by expr_1a and expr_1b defines a single group.
The base level of aggregates returned in the result set is for each unique combination of
values returned by the expression list.
In addition, a subtotal is returned for the first item in the list (expr_1 or the combination
of ( expr_1a, expr_1b, ...), whichever is specified) for each unique value. A
subtotal is returned for the second item in the list (expr_2 or the combination of (
expr_2a, expr_2b, ...), whichever is specified) for each unique value. A subtotal
is also returned for each unique combination of the first item and the second item.
Similarly, if there is a third item, a subtotal is returned for each unique value of the third
item, each unique value of the third item and first item combination, each unique value of
the third item and second item combination, and each unique value of the third item,
second item, and first item combination. Finally a grand total is returned for the entire
result set.
For the subtotal rows, null is returned for the items across which the subtotal is taken.
The CUBE extension specified within the context of the GROUP BY clause is shown by the
following:
The items specified in select_list must also appear in the CUBE expression_list;
or they must be aggregate functions such as COUNT, SUM, AVG, MIN, or MAX; or they must
be constants or functions whose return values are independent of the individual rows in
the group (for example, the SYSDATE function).
The GROUP BY clause may specify multiple CUBE extensions as well as multiple
occurrences of other GROUP BY extensions and individual expressions.
The ORDER BY clause should be used if you want the output to display in a meaningful
structure. There is no guarantee on the order of the result set if no ORDER BY clause is
specified.
The number of grouping levels or totals is 2 raised to the power of n where n represents
the number of items in the CUBE expression list. A parenthesized list counts as one item.
The following query produces a cube based on permutations of columns loc, dname, and
job.
The following is the result of the query. There is a count of the number of employees for
each combination of loc, dname, and job, as well as subtotals for each combination of
loc and dname, for each combination of loc and job, for each combination of dname
and job, for each unique value of loc, for each unique value of dname, for each unique
value of job, and a grand total displayed on the last line.
The following query shows the effect of combining items in the CUBE list within
parenthesis.
In the output note that there are no subtotals for permutations involving loc and dname
combinations, loc and job combinations, or for dname by itself, or for job by itself.
The following query shows another variation whereby the first expression is specified
outside of the CUBE extension.
In this output, the permutations are performed for dname and job within each grouping
of loc.
The use of the GROUPING SETS extension within the GROUP BY clause provides a
means to produce one result set that is actually the concatenation of multiple results sets
based upon different groupings. In other words, a UNION ALL operation is performed
combining the result sets of multiple groupings into one result set.
Note that a UNION ALL operation, and therefore the GROUPING SETS extension, do not
eliminate duplicate rows from the result sets that are being combined together.
GROUPING SETS (
{ expr_1 | ( expr_1a [, expr_1b ] ...) |
ROLLUP ( expr_list ) | CUBE ( expr_list )
} [, ...] )
A GROUPING SETS extension can contain any combination of one or more comma-
separated expressions, lists of expressions enclosed within parenthesis, ROLLUP
extensions, and CUBE extensions.
The GROUPING SETS extension is specified within the context of the GROUP BY clause
as shown by the following:
The items specified in select_list must also appear in the GROUPING SETS
expression_list; or they must be aggregate functions such as COUNT, SUM, AVG, MIN,
or MAX; or they must be constants or functions whose return values are independent of the
individual rows in the group (for example, the SYSDATE function).
The GROUP BY clause may specify multiple GROUPING SETS extensions as well as
multiple occurrences of other GROUP BY extensions and individual expressions.
The ORDER BY clause should be used if you want the output to display in a meaningful
structure. There is no guarantee on the order of the result set if no ORDER BY clause is
specified.
The following query produces a union of groups given by columns loc, dname, and job.
This is equivalent to the following query, which employs the use of the UNION ALL
operator.
The output from the UNION ALL query is the same as the GROUPING SETS output.
The following example shows how various types of GROUP BY extensions can be used
together within a GROUPING SETS expression list.
The output is basically a concatenation of the result sets that would be produced
individually from GROUP BY loc, GROUP BY ROLLUP (dname, job), and GROUP
BY CUBE (job, loc). These individual queries are shown by the following.
The following is the result set from the GROUP BY loc clause.
The following query uses the GROUP BY ROLLUP (dname, job) clause.
SELECT NULL AS "loc", dname, job, COUNT(*) AS "employees" FROM emp e, dept d
WHERE e.deptno = d.deptno
GROUP BY ROLLUP (dname, job)
ORDER BY 2, 3;
The following is the result set from the GROUP BY ROLLUP (dname, job) clause.
The following query uses the GROUP BY CUBE (job, loc) clause.
SELECT loc, NULL AS "dname", job, COUNT(*) AS "employees" FROM emp e, dept d
WHERE e.deptno = d.deptno
GROUP BY CUBE (job, loc)
ORDER BY 1, 3;
The following is the result set from the GROUP BY CUBE (job, loc) clause.
If the previous three queries are combined with the UNION ALL operator, a concatenation
of the three results sets is produced.
The following is the output, which is the same as when the GROUP BY GROUPING SETS
(loc, ROLLUP (dname, job), CUBE (job, loc)) clause is used.
When using the ROLLUP, CUBE, or GROUPING SETS extensions to the GROUP BY clause,
it may sometimes be difficult to differentiate between the various levels of subtotals
generated by the extensions as well as the base aggregate rows in the result set. The
GROUPING function provides a means of making this distinction.
The general syntax for use of the GROUPING function is shown by the following.
The return value of the GROUPING function is either a 0 or 1. In the result set of a query,
if the column expression specified in the GROUPING function is null because the row
represents a subtotal over multiple values of that column then the GROUPING function
returns a value of 1. If the row returns results based on a particular value of the column
specified in the GROUPING function, then the GROUPING function returns a value of 0. In
the latter case, the column can be null as well as non-null, but in any case, it is for a
particular value of that column, not a subtotal across multiple values.
The following query shows how the return values of the GROUPING function correspond
to the subtotal lines.
In the three right-most columns displaying the output of the GROUPING functions, a value
of 1 appears on a subtotal line wherever a subtotal is taken across values of the
corresponding columns.
These indicators can be used as screening criteria for particular subtotals. For example,
using the previous query, you can display only those subtotals for loc and dname
combinations by using the GROUPING function in a HAVING clause.
The GROUPING function can be used to distinguish a subtotal row from a base aggregate
row or from certain subtotal rows where one of the items in the expression list returns
null as a result of the column on which the expression is based being null for one or more
rows in the table, as opposed to representing a subtotal over the column.
To illustrate this point, the following row is added to the emp table. This provides a row
with a null value for the job column.
The following query is issued using a reduced number of rows for clarity.
Note that the output contains two rows containing BOSTON in the loc column and spaces
in the job column (fourth and fifth entries in the table).
The fifth row where the GROUPING function on the job column (gf_job) returns 1
indicates this is a subtotal over all jobs. Note that the row contains a subtotal value of 9 in
the employees column.
The fourth row where the GROUPING function on the job column as well as on the loc
column returns 0 indicates this is a base aggregate of all rows where loc is BOSTON and
job is null, which is the row inserted for this example. The employees column contains
1, which is the count of the single such row inserted.
Also note that in the ninth row (next to last) the GROUPING function on the job column
returns 0 while the GROUPING function on the loc column returns 1 indicating this is a
subtotal over all locations where the job column is null, which again, is a count of the
single row inserted for this example.
The GROUPING function takes only one column expression and returns an indication of
whether or not a row is a subtotal over all values of the given column. Thus, multiple
GROUPING functions may be required to interpret the level of subtotals for queries with
multiple grouping columns.
The GROUPING_ID function accepts one or more column expressions that have been used
in the ROLLBACK, CUBE, or GROUPING SETS extensions and returns a single integer that
can be used to determine over which of these columns a subtotal has been aggregated.
The general syntax for use of the GROUPING_ID function is shown by the following.
The GROUPING_ID function takes one or more parameters that must be expressions of
dimension columns specified in the expression list of a ROLLUP, CUBE, or GROUPING
SETS extension of the GROUP BY clause.
The GROUPING_ID function returns an integer value. This value corresponds to the base-
10 interpretation of a bit vector consisting of the concatenated 1s and 0s that would be
returned by a series of GROUPING functions specified in the same left-to-right order as
the ordering of the parameters specified in the GROUPING_ID function.
The following query shows how the returned values of the GROUPING_ID function
represented in column gid correspond to the values returned by two GROUPING functions
on columns loc and dname.
In the following output, note the relationship between a bit vector consisting of the
gf_loc value and gf_dname value compared to the integer given in gid.
The following table summarizes how the GROUPING_ID function return values
correspond to the grouping columns over which aggregation occurs.
So to display only those subtotals by dname, the following simplified query can be used
with a HAVING clause based on the GROUPING_ID function.
A profile is a named set of password attributes that allow you to easily manage a group of
roles that share comparable authentication requirements. If the password requirements
change, you can modify the profile to have the new requirements applied to each user that
is associated with that profile.
After creating the profile, you can associate the profile with one or more users. When a
user connects to the server, the server enforces the profile that is associated with their
login role. Profiles are shared by all databases within a cluster, but each cluster may have
multiple profiles. A single user with access to multiple databases will use the same
profile when connecting to each database within the cluster.
Advanced Server creates a profile named default that is associated with a new role
when the role is created unless an alternate profile is specified. If you upgrade to
Advanced Server from a previous server version, existing roles will automatically be
assigned to the default profile. You cannot delete the default profile.
FAILED_LOGIN_ATTEMPTS UNLIMITED
PASSWORD_LOCK_TIME UNLIMITED
PASSWORD_LIFE_TIME UNLIMITED
PASSWORD_GRACE_TIME UNLIMITED
PASSWORD_REUSE_TIME UNLIMITED
PASSWORD_REUSE_MAX UNLIMITED
PASSWORD_VERIFY_FUNCTION NULL
A database superuser can use the ALTER PROFILE command to modify the values
specified by the default profile. For more information about modifying a profile, see
Section 2.3.2.
Use the CREATE PROFILE command to create a new profile. The syntax is:
Include the LIMIT clause and one or more space-delimited parameter/value pairs to
specify the rules enforced by Advanced Server.
Parameters:
Advanced Server supports the value shown below for each parameter:
PASSWORD_LOCK_TIME specifies the length of time that must pass before the server
unlocks an account that has been locked because of FAILED_LOGIN_ATTEMPTS.
Supported values are:
PASSWORD_LIFE_TIME specifies the number of days that the current password may
be used before the user is prompted to provide a new password. Include the
PASSWORD_GRACE_TIME clause when using the PASSWORD_LIFE_TIME clause to
specify the number of days that will pass after the password expires before
connections by the role are rejected. If PASSWORD_GRACE_TIME is not specified, the
password will expire on the day specified by the default value of
PASSWORD_GRACE_TIME, and the user will not be allowed to execute any command
until a new password is provided. Supported values are:
PASSWORD_REUSE_TIME specifies the number of days a user must wait before re-
using a password. The PASSWORD_REUSE_TIME and PASSWORD_REUSE_MAX
parameters are intended to be used together. If you specify a finite value for one of
these parameters while the other is UNLIMITED, old passwords can never be reused.
If both parameters are set to UNLIMITED there are no restrictions on password reuse.
Supported values are:
Notes
Examples
The following command creates a profile named acctg. The profile specifies that if a
user has not authenticated with the correct password in five attempts, the account will be
locked for one day:
The following command creates a profile named sales. The profile specifies that a user
must change their password every 90 days:
If the user has not changed their password before the 90 days specified in the profile has
passed, they will be issued a warning at login. After a grace period of 3 days, their
account will not be allowed to invoke any commands until they change their password.
The following command creates a profile named accts. The profile specifies that a user
cannot re-use a password within 180 days of the last use of the password, and must
change their password at least 5 times before re-using the password:
The following command creates a profile named resources; the profile calls a user-
defined function named password_rules that will verify that the password provided
meets their standards for complexity:
Where:
When a user with the CREATEROLE attribute changes their password, the
parameter will pass the previous password if the statement includes the
REPLACE clause. Note that the REPLACE clause is optional syntax for a
user with the CREATEROLE privilege.
When a user that is not a database superuser and does not have the
CREATEROLE attribute changes their password, the third parameter will
contain the previous password for the role.
The function returns a Boolean value. If the function returns true and does not raise an
exception, the password is accepted; if the function returns false or raises an exception,
the password is rejected. If the function raises an exception, the specified error message
is displayed to the user. If the function does not raise an exception, but returns false, the
following error message is displayed:
The function must be owned by a database superuser, and reside in the sys schema.
Example:
The following example creates a profile and a custom function; then, the function is
associated with the profile. The following CREATE PROFILE command creates a profile
named acctg_pwd_profile:
RETURN true;
END;
The function first ensures that the password is at least 5 characters long, and then
compares the new password to the old password. If the new password contains fewer
than 5 characters, or contains the old password, the function raises an error.
The following statement sets the ownership of the verify_password function to the
enterprisedb database superuser:
The following statements confirm that the function is working by first creating a test user
(alice), and then attempting to associate invalid and valid passwords with her role:
Then, when alice connects to the database and attempts to change her password, she
must adhere to the rules established by the profile function. A non-superuser without
CREATEROLE must include the REPLACE clause when changing a password:
If alice decides to change her password, the new password must not contain the old
password:
Use the ALTER PROFILE command to modify a user-defined profile; Advanced Server
supports two forms of the command:
Include the LIMIT clause and one or more space-delimited parameter/value pairs to
specify the rules enforced by Advanced Server, or use ALTER PROFILERENAME TO to
change the name of a profile.
Parameters:
See the table in Section 2.3.1 for a complete list of accepted parameter/value pairs.
Examples
acctg_profile will count failed connection attempts when a login role attempts to
connect to the server. The profile specifies that if a user has not authenticated with the
correct password in three attempts, the account will be locked for one day.
Use the DROP PROFILE command to drop a profile. The syntax is:
Include the IF EXISTS clause to instruct the server to not throw an error if the specified
profile does not exist. The server will issue a notice if the profile does not exist.
Include the optional CASCADE clause to reassign any users that are currently associated
with the profile to the default profile, and then drop the profile. Include the optional
RESTRICT clause to instruct the server to not drop any profile that is associated with a
role. This is the default behavior.
Parameters
profile_name
Examples
The command first re-associates any roles associated with the acctg_profile profile
with the default profile, and then drops the acctg_profile profile.
The RESTRICT clause in the command instructs the server to not drop acctg_profile
if there are any roles associated with the profile.
After creating a profile, you can use the ALTER USER PROFILE or ALTER ROLE
PROFILE command to associate the profile with a role. The command syntax related to
profile management functionality is:
PROFILE profile_name
| ACCOUNT {LOCK|UNLOCK}
| PASSWORD EXPIRE [AT 'timestamp']
For information about the administrative clauses of the ALTER USER or ALTER ROLE
command that are supported by Advanced Server, please see the PostgreSQL core
documentation available at:
https://www.postgresql.org/docs/10/static/sql-commands.html
Only a database superuser can use the ALTER USER|ROLE clauses that enforce profile
management. The clauses enforce the following behaviors:
Include the ACCOUNT clause and the LOCK or UNLOCK keyword to specify that the
user account should be placed in a locked or unlocked state.
Include the LOCK TIME 'timestamp' clause and a date/time value to lock the
role at the specified time, and unlock the role at the time indicated by the
PASSWORD_LOCK_TIME parameter of the profile assigned to this role. If LOCK
TIME is used with the ACCOUNT LOCK clause, the role can only be unlocked by a
database superuser with the ACCOUNT UNLOCK clause.
Each login role may only have one profile. To discover the profile that is currently
associated with a login role, query the profile column of the DBA_USERS view.
Parameters
name
The name of the role with which the specified profile will be associated.
password
profile_name
The name of the profile that will be associated with the role.
timestamp
The date and time at which the clause will be enforced. When specifying a value
for timestamp, enclose the value in single-quotes.
Examples
The following command uses the ALTER USER PROFILE command to associate a
profile named acctg with a user named john:
The following command uses the ALTER ROLE PROFILE command to associate a
profile named acctg with a user named john:
A database superuser can use clauses of the ALTER USER|ROLE command to lock or
unlock a role. The syntax is:
Include the ACCOUNT LOCK clause to lock a role immediately; when locked, a roles
LOGIN functionality is disabled. When you specify the ACCOUNT LOCK clause without
the LOCK TIME clause, the state of the role will not change until a superuser uses the
ACCOUNT UNLOCK clause to unlock the role.
Use the LOCK TIME 'timestamp' clause to instruct the server to lock the account at the
time specified by the given timestamp for the length of time specified by the
PASSWORD_LOCK_TIME parameter of the profile associated with this role.
Combine the LOCK TIME 'timestamp' clause and the ACCOUNT LOCK clause to lock
an account at a specified time until the account is unlocked by a superuser invoking the
ACCOUNT UNLOCK clause.
Parameters
name
timestamp
The date and time at which the role will be locked. When specifying a value for
timestamp, enclose the value in single-quotes.
Note
Examples
The following example uses the ACCOUNT LOCK clause to lock the role named john.
The account will remain locked until the account is unlocked with the ACCOUNT UNLOCK
clause:
The following example uses the ACCOUNT UNLOCK clause to unlock the role named
john:
The following example uses the LOCK TIME 'timestamp' clause to lock the role
named john on September 4, 2015:
The role will remain locked for the length of time specified by the
PASSWORD_LOCK_TIME parameter.
The following example combines the LOCK TIME 'timestamp' clause and the
ACCOUNT LOCK clause to lock the role named john on September 4, 2015:
ALTER ROLE john LOCK TIME September 4 12:00:00 2015 ACCOUNT LOCK;
The role will remain locked until a database superuser uses the ACCOUNT UNLOCK
command to unlock the role.
A database superuser can use clauses of the CREATE USER|ROLE command to assign a
named profile to a role when creating the role, or to specify profile management details
for a role. The command syntax related to profile management functionality is:
PROFILE profile_name
| ACCOUNT {LOCK|UNLOCK}
| PASSWORD EXPIRE [AT 'timestamp']
For information about the administrative clauses of the CREATE USER or CREATE ROLE
command that are supported by Advanced Server, please see the PostgreSQL core
documentation available at:
https://www.postgresql.org/docs/10/static/sql-commands.html
Roles created with the CREATE USER command are (by default) login roles. Roles
created with the CREATE ROLE command are (by default) not login roles. To create a
login account with the CREATE ROLE command, you must include the LOGIN keyword.
Only a database superuser can use the CREATE USER|ROLE clauses that enforce profile
management; these clauses enforce the following behaviors:
Include the ACCOUNT clause and the LOCK or UNLOCK keyword to specify that the
user account should be placed in a locked or unlocked state.
Include the LOCK TIME 'timestamp' clause and a date/time value to lock the
role at the specified time, and unlock the role at the time indicated by the
PASSWORD_LOCK_TIME parameter of the profile assigned to this role. If LOCK
TIME is used with the ACCOUNT LOCK clause, the role can only be unlocked by a
database superuser with the ACCOUNT UNLOCK clause.
Each login role may only have one profile. To discover the profile that is currently
associated with a login role, query the profile column of the DBA_USERS view.
Parameters
name
profile_name
timestamp
The date and time at which the clause will be enforced. When specifying a value
for timestamp, enclose the value in single-quotes.
Examples
The following example uses CREATE USER to create a login role named john who is
associated with the acctg_profile profile:
The following example uses CREATE ROLE to create a login role named john who is
associated with the acctg_profile profile:
Invoking pg_dumpall with the g or r option will create a script that recreates the
definition of any existing profiles, but that does not recreate the user-defined functions
that are referred to by the PASSWORD_VERIFY_FUNCTION clause. You should use the
pg_dump utility to explicitly dump (and later restore) the database in which those
functions reside.
The script created by pg_dump will contain a command that includes the clause and
function name:
to associate the restored function with the profile with which it was previously associated.
When you invoke a DELETE, INSERT, SELECT or UPDATE command, the server
generates a set of execution plans; after analyzing those execution plans, the server
selects a plan that will (generally) return the result set in the least amount of time. The
server's choice of plan is dependent upon several factors:
As a rule, the query planner will select the least expensive plan. You can use an
optimizer hint to influence the server as it selects a query plan. An optimizer hint is a
directive (or multiple directives) embedded in a comment-like syntax that immediately
follows a DELETE, INSERT, SELECT or UPDATE command. Keywords in the comment
instruct the server to employ or avoid a specific plan when producing the result set.
Synopsis
Optimizer hints may be included in either of the forms shown above. Note that in both
forms, a plus sign (+) must immediately follow the /* or -- opening comment symbols,
with no intervening space, or the server will not interpret the following tokens as hints.
If you are using the first form, the hint and optional comment may span multiple lines.
The second form requires all hints and comments to occupy a single line; the remainder
of the statement must start on a new line.
Description
Please Note:
The database server will always try to use the specified hints if at all possible.
If a planner method parameter is set so as to disable a certain plan type, then this
plan will not be used even if it is specified in a hint, unless there are no other
possible options for the planner. Examples of planner method parameters are
Use the EXPLAIN command to ensure that the hint is correctly formed and the planner is
using the hint. See the Advanced Server documentation set for information on the
EXPLAIN command.
In general, optimizer hints should not be used in production applications (where table
data changes throughout the life of the application). By ensuring that dynamic columns
are ANALYZEd frequently, the column statistics will be updated to reflect value changes,
and the planner will use such information to produce the least cost plan for any given
command execution. Use of optimizer hints defeats the purpose of this process and will
result in the same plan regardless of how the table data changes.
Parameters
hint
comment
A string with additional information. Note that there are restrictions as to what
characters may be included in the comment. Generally, comment may only
consist of alphabetic, numeric, the underscore, dollar sign, number sign and space
characters. These must also conform to the syntax of an identifier. Any
subsequent hint will be ignored if the comment is not in this form.
statement_body
The following sections describe the optimizer hint directives in more detail.
There are a number of optimization modes that can be chosen as the default setting for an
Advanced Server database cluster. This setting can also be changed on a per session basis
by using the ALTER SESSION command as well as in individual DELETE, SELECT, and
UPDATE commands within an optimizer hint. The configuration parameter that controls
these default modes is named OPTIMIZER_MODE. The following table shows the possible
values.
These optimization modes are based upon the assumption that the client submitting the
SQL command is interested in viewing only the first n rows of the result set and will
then abandon the remainder of the result set. Resources allocated to the query are
adjusted as such.
Examples
Alter the current session to optimize for retrieval of the first 10 rows of the result set.
The current value of the OPTIMIZER_MODE parameter can be shown by using the SHOW
command. Note that this command is a utility dependent command. In PSQL, the SHOW
command is used as follows:
SHOW OPTIMIZER_MODE;
optimizer_mode
----------------
first_rows_10
(1 row)
The SHOW command, compatible with Oracle databases, has the following syntax:
NAME
--------------------------------------------------
VALUE
--------------------------------------------------
optimizer_mode
first_rows_10
The following hints influence how the optimizer accesses relations to create the result set.
In addition, the ALL_ROWS, FIRST_ROWS, and FIRST_ROWS(n) hints of Table 2-1 can
be used.
Examples
The sample application does not have sufficient data to illustrate the effects of optimizer
hints so the remainder of the examples in this section will use a banking database created
by the pgbench application located in the Advanced Server bin subdirectory.
The following steps create a database named, bank, populated by the tables,
pgbench_accounts, pgbench_branches, pgbench_tellers, and
pgbench_history. The s 20 option specifies a scaling factor of twenty, which
results in the creation of twenty branches, each with 100,000 accounts, resulting in a total
of 2,000,000 rows in the pgbench_accounts table and twenty rows in the
pgbench_branches table. Ten tellers are assigned to each branch resulting in a total of
200 rows in the pgbench_tellers table.
A total of 500,00 transactions are then processed. This will populate the
pgbench_history table with 500,000 rows.
starting vacuum...end.
transaction type: <builtin: TPC-B (sort of)>
scaling factor: 20
query mode: simple
number of clients: 1
number of threads: 1
number of transactions per client: 500000
number of transactions actually processed: 500000/500000
latency average: 0.000 ms
tps = 1464.338375 (including connections establishing)
tps = 1464.350357 (excluding connections establishing)
\d pgbench_accounts
Table "public.pgbench_accounts"
Column | Type | Modifiers
----------+---------------+-----------
aid | integer | not null
bid | integer |
abalance | integer |
filler | character(84) |
Indexes:
"pgbench_accounts_pkey" PRIMARY KEY, btree (aid)
\d pgbench_branches
Table "public.pgbench_branches"
Column | Type | Modifiers
----------+---------------+-----------
bid | integer | not null
bbalance | integer |
filler | character(88) |
Indexes:
"pgbench_branches_pkey" PRIMARY KEY, btree (bid)
\d pgbench_tellers
Table "public.pgbench_tellers"
Column | Type | Modifiers
\d pgbench_history
Table "public.pgbench_history"
Column | Type | Modifiers
--------+-----------------------------+-----------
tid | integer |
bid | integer |
aid | integer |
delta | integer |
mtime | timestamp without time zone |
filler | character(22) |
The EXPLAIN command shows the plan selected by the query planner. In the following
example, aid is the primary key column, so an indexed search is used on index,
pgbench_accounts_pkey.
QUERY PLAN
-----------------------------------------------------------------------------
------------------
Index Scan using pgbench_accounts_pkey on pgbench_accounts (cost=0.43..8.45
rows=1 width=97)
Index Cond: (aid = 100)
(2 rows)
The FULL hint is used to force a full sequential scan instead of using the index as shown
below:
QUERY PLAN
---------------------------------------------------------------------
Seq Scan on pgbench_accounts (cost=0.00..58781.69 rows=1 width=97)
Filter: (aid = 100)
(2 rows)
The NO_INDEX hint forces a parallel sequential scan instead of use of the index as shown
below:
QUERY PLAN
-----------------------------------------------------------------------------
-------
Gather (cost=1000.00..45094.80 rows=1 width=97)
Workers Planned: 2
In addition to using the EXPLAIN command as shown in the prior examples, more
detailed information regarding whether or not a hint was used by the planner can be
obtained by setting the trace_hints configuration parameter as follows:
The SELECT command with the NO_INDEX hint is repeated below to illustrate the
additional information produced when the trace_hints configuration parameters is set.
Note that if a hint is ignored, the INFO: [HINTS] line will not appear. This may be an
indication that there was a syntax error or some other misspelling in the hint as shown in
the following example where the index name is misspelled.
QUERY PLAN
-----------------------------------------------------------------------------
------------------
Index Scan using pgbench_accounts_pkey on pgbench_accounts (cost=0.43..8.45
rows=1 width=97)
Index Cond: (aid = 100)
(2 rows)
Include the ORDERED directive to instruct the query optimizer to join tables in the order in
which they are listed in the FROM clause. If you do not include the ORDERED keyword,
the query optimizer will choose the order in which to join the tables.
For example, the following command allows the optimizer to choose the order in which
to join the tables listed in the FROM clause:
The following command instructs the optimizer to join the tables in the ordered specified:
In the ORDERED version of the command, Advanced Server will first join emp e with
dept d before joining the results with jobhist h. Without the ORDERED directive, the
join order is selected by the query optimizer.
Please note: the ORDERED directive does not work for Oracle-style outer joins (those joins
that contain a '+' sign).
When two tables are to be joined, there are three possible plans that may be used to
perform the join.
Nested Loop Join A table is scanned once for every row in the other joined
table.
Merge Sort Join Each table is sorted on the join attributes before the join starts.
The two tables are then scanned in parallel and the matching rows are combined
to form the join rows.
Hash Join A table is scanned and its join attributes are loaded into a hash table
using its join attributes as hash keys. The other joined table is then scanned and its
join attributes are used as hash keys to locate the matching rows from the first
table.
The following table lists the optimizer hints that can be used to influence the planner to
use one type of join plan over another.
Examples
In the following example, the USE_HASH hint is used for a join on the
pgbench_branches and pgbench_accounts tables. The query plan shows that a hash
join is used by creating a hash table from the join attribute of the pgbench_branches
table.
QUERY PLAN
-----------------------------------------------------------------------------
------
Hash Join (cost=21.45..81463.06 rows=2014215 width=12)
Hash Cond: (a.bid = b.bid)
-> Seq Scan on pgbench_accounts a (cost=0.00..53746.15 rows=2014215
width=12)
-> Hash (cost=21.20..21.20 rows=20 width=4)
-> Seq Scan on pgbench_branches b (cost=0.00..21.20 rows=20
width=4)
Next, the NO_USE_HASH(a b) hint forces the planner to use an approach other than
hash tables. The result is a merge join.
QUERY PLAN
-----------------------------------------------------------------------------
------------------
Merge Join (cost=333526.08..368774.94 rows=2014215 width=12)
Merge Cond: (b.bid = a.bid)
-> Sort (cost=21.63..21.68 rows=20 width=4)
Sort Key: b.bid
-> Seq Scan on pgbench_branches b (cost=0.00..21.20 rows=20
width=4)
-> Materialize (cost=333504.45..343575.53 rows=2014215 width=12)
-> Sort (cost=333504.45..338539.99 rows=2014215 width=12)
Sort Key: a.bid
-> Seq Scan on pgbench_accounts a (cost=0.00..53746.15
rows=2014215 width=12)
(9 rows)
Finally, the USE_MERGE hint forces the planner to use a merge join.
QUERY PLAN
-----------------------------------------------------------------------------
------------------
Merge Join (cost=333526.08..368774.94 rows=2014215 width=12)
Merge Cond: (b.bid = a.bid)
-> Sort (cost=21.63..21.68 rows=20 width=4)
Sort Key: b.bid
-> Seq Scan on pgbench_branches b (cost=0.00..21.20 rows=20
width=4)
-> Materialize (cost=333504.45..343575.53 rows=2014215 width=12)
-> Sort (cost=333504.45..338539.99 rows=2014215 width=12)
Sort Key: a.bid
-> Seq Scan on pgbench_accounts a (cost=0.00..53746.15
rows=2014215 width=12)
(9 rows)
In this three-table join example, the planner first performs a hash join on the
pgbench_branches and pgbench_history tables, then finally performs a hash join
of the result with the pgbench_accounts table.
EXPLAIN SELECT h.mtime, h.delta, b.bid, a.aid FROM pgbench_history h, pgbench_branches
b, pgbench_accounts a WHERE h.bid = b.bid AND h.aid = a.aid;
QUERY PLAN
---------------------------------------------------------------------------------------
-
Hash Join (cost=86814.29..123103.29 rows=500000 width=20)
Hash Cond: (h.aid = a.aid)
-> Hash Join (cost=21.45..15081.45 rows=500000 width=20)
Hash Cond: (h.bid = b.bid)
-> Seq Scan on pgbench_history h (cost=0.00..8185.00 rows=500000 width=20)
This plan is altered by using hints to force a combination of a merge sort join and a hash
join.
EXPLAIN SELECT /*+ USE_MERGE(h b) USE_HASH(a) */ h.mtime, h.delta, b.bid, a.aid FROM
pgbench_history h, pgbench_branches b, pgbench_accounts a WHERE h.bid = b.bid AND h.aid
= a.aid;
QUERY PLAN
---------------------------------------------------------------------------------------
-----------
Hash Join (cost=152583.39..182562.49 rows=500000 width=20)
Hash Cond: (h.aid = a.aid)
-> Merge Join (cost=65790.55..74540.65 rows=500000 width=20)
Merge Cond: (b.bid = h.bid)
-> Sort (cost=21.63..21.68 rows=20 width=4)
Sort Key: b.bid
-> Seq Scan on pgbench_branches b (cost=0.00..21.20 rows=20 width=4)
-> Materialize (cost=65768.92..68268.92 rows=500000 width=20)
-> Sort (cost=65768.92..67018.92 rows=500000 width=20)
Sort Key: h.bid
-> Seq Scan on pgbench_history h (cost=0.00..8185.00 rows=500000
width=20)
-> Hash (cost=53746.15..53746.15 rows=2014215 width=4)
-> Seq Scan on pgbench_accounts a (cost=0.00..53746.15 rows=2014215 width=4)
(13 rows)
Thus far, hints have been applied directly to tables that are referenced in the SQL
command. It is also possible to apply hints to tables that appear in a view when the view
is referenced in the SQL command. The hint does not appear in the view, itself, but rather
in the SQL command that references the view.
When specifying a hint that is to apply to a table within a view, the view and table names
are given in dot notation within the hint argument list.
Synopsis
hint(view.table)
Parameters
hint
view
table
Examples
The query plan produced by selecting from this view is show below:
EXPLAIN SELECT * FROM tx;
QUERY PLAN
---------------------------------------------------------------------------------------
-
Hash Join (cost=86814.29..123103.29 rows=500000 width=20)
Hash Cond: (h.aid = a.aid)
-> Hash Join (cost=21.45..15081.45 rows=500000 width=20)
The same hints that were applied to this join at the end of Section 2.4.4 can be applied to
the view as follows:
EXPLAIN SELECT /*+ USE_MERGE(tx.h tx.b) USE_HASH(tx.a) */ * FROM tx;
QUERY PLAN
---------------------------------------------------------------------------------------
-----------
Hash Join (cost=152583.39..182562.49 rows=500000 width=20)
Hash Cond: (h.aid = a.aid)
-> Merge Join (cost=65790.55..74540.65 rows=500000 width=20)
Merge Cond: (b.bid = h.bid)
-> Sort (cost=21.63..21.68 rows=20 width=4)
Sort Key: b.bid
-> Seq Scan on pgbench_branches b (cost=0.00..21.20 rows=20 width=4)
-> Materialize (cost=65768.92..68268.92 rows=500000 width=20)
-> Sort (cost=65768.92..67018.92 rows=500000 width=20)
Sort Key: h.bid
-> Seq Scan on pgbench_history h (cost=0.00..8185.00 rows=500000
width=20)
-> Hash (cost=53746.15..53746.15 rows=2014215 width=4)
-> Seq Scan on pgbench_accounts a (cost=0.00..53746.15 rows=2014215 width=4)
(13 rows)
In addition to applying hints to tables within stored views, hints can be applied to tables
within subqueries as illustrated by the following example. In this query on the sample
application emp table, employees and their managers are listed by joining the emp table
with a subquery of the emp table identified by the alias, b.
SELECT a.empno, a.ename, b.empno "mgr empno", b.ename "mgr ename" FROM emp a,
(SELECT * FROM emp) b WHERE a.mgr = b.empno;
EXPLAIN SELECT a.empno, a.ename, b.empno "mgr empno", b.ename "mgr ename"
FROM emp a, (SELECT * FROM emp) b WHERE a.mgr = b.empno;
QUERY PLAN
-----------------------------------------------------------------
Hash Join (cost=1.32..2.64 rows=13 width=22)
Hash Cond: (a.mgr = emp.empno)
-> Seq Scan on emp a (cost=0.00..1.14 rows=14 width=16)
-> Hash (cost=1.14..1.14 rows=14 width=11)
-> Seq Scan on emp (cost=0.00..1.14 rows=14 width=11)
(5 rows)
A hint can be applied to the emp table within the subquery to perform an index scan on
index, emp_pk, instead of a table scan. Note the difference in the query plans.
QUERY PLAN
---------------------------------------------------------------------------
Merge Join (cost=4.17..13.11 rows=13 width=22)
Merge Cond: (a.mgr = emp.empno)
-> Sort (cost=1.41..1.44 rows=14 width=16)
Sort Key: a.mgr
-> Seq Scan on emp a (cost=0.00..1.14 rows=14 width=16)
-> Index Scan using emp_pk on emp (cost=0.14..12.35 rows=14 width=11)
(6 rows)
By default, Advanced Server will add new data into the first available free-space in a
table (vacated by vacuumed records). Include the APPEND directive after an INSERT or
SELECT command to instruct the server to bypass mid-table free space, and affix new
rows to the end of the table. This optimizer hint can be particularly useful when bulk
loading data.
/*+APPEND*/
For example, the following command, compatible with Oracle databases, instructs the
server to append the data in the INSERT statement to the end of the sales table:
Note that Advanced Server supports the APPEND hint when adding multiple rows in a
single INSERT statement:
The APPEND hint can also be included in the SELECT clause of an INSERT INTO
statement:
Synopsis
NO_PARALLEL (table)
Description
Parameters
table
parallel_degree | DEFAULT
https://www.postgresql.org/docs/10/static/runtime-config-resource.html
If both parallel_degree and DEFAULT are omitted, then the query optimizer
determines the parallel degree. In this case, if table has been set with the
parallel_workers storage parameter, then this value is used as the parallel
degree, otherwise the optimizer uses the maximum possible parallel degree as if
DEFAULT was specified. For information on the parallel_workers storage
https://www.postgresql.org/docs/10/static/sql-createtable.html
Regardless of the circumstance, the parallel degree never exceeds the setting of
configuration parameter max_parallel_workers_per_gather.
Examples
SHOW max_worker_processes;
max_worker_processes
----------------------
8
(1 row)
SHOW max_parallel_workers_per_gather;
max_parallel_workers_per_gather
---------------------------------
2
(1 row)
The following example shows the default scan on table pgbench_accounts. Note that a
sequential scan is shown in the query plan.
QUERY PLAN
---------------------------------------------------------------------------
Seq Scan on pgbench_accounts (cost=0.00..53746.15 rows=2014215 width=97)
(1 row)
The following example uses the PARALLEL hint. In the query plan, the Gather node,
which launches the background workers, indicates that two workers are planned to be
used.
Note: If trace_hints is set to on, the INFO: [HINTS] lines appear stating that
PARALLEL has been accepted for pgbench_accounts as well as other hint information.
For the remaining examples, these lines will not be displayed as they generally show the
same output (that is, trace_hints has been reset to off).
SET max_parallel_workers_per_gather TO 6;
SHOW max_parallel_workers_per_gather;
max_parallel_workers_per_gather
---------------------------------
6
(1 row)
QUERY PLAN
-----------------------------------------------------------------------------
------------
Gather (cost=1000.00..241061.04 rows=2014215 width=97)
Workers Planned: 4
-> Parallel Seq Scan on pgbench_accounts (cost=0.00..38639.54
rows=503554 width=97)
(3 rows)
Now, a value of 6 is specified for the parallel degree parameter of the PARALLEL hint.
The planned number of workers is now returned as this specified value:
QUERY PLAN
-----------------------------------------------------------------------------
------------
Gather (cost=1000.00..239382.52 rows=2014215 width=97)
Workers Planned: 6
-> Parallel Seq Scan on pgbench_accounts (cost=0.00..36961.03
rows=335702 width=97)
(3 rows)
The same query is now issued with the DEFAULT setting for the parallel degree. The
results indicate that the maximum allowable number of workers is planned.
QUERY PLAN
Note: This format of the ALTER TABLE command to set the parallel_workers
parameter is not compatible with Oracle databases.
\d+ pgbench_accounts
Table "public.pgbench_accounts"
Column | Type | Modifiers | Storage | Stats target | Description
----------+---------------+-----------+----------+--------------+------------
-
aid | integer | not null | plain | |
bid | integer | | plain | |
abalance | integer | | plain | |
filler | character(84) | | extended | |
Indexes:
"pgbench_accounts_pkey" PRIMARY KEY, btree (aid)
Options: fillfactor=100, parallel_workers=3
Now, when the PARALLEL hint is given with no parallel degree, the resulting number of
planned workers is the value from the parallel_workers parameter:
QUERY PLAN
-----------------------------------------------------------------------------
------------
Gather (cost=1000.00..242522.97 rows=2014215 width=97)
Workers Planned: 3
-> Parallel Seq Scan on pgbench_accounts (cost=0.00..40101.47
rows=649747 width=97)
(3 rows)
Specifying a parallel degree value or DEFAULT in the PARALLEL hint overrides the
parallel_workers setting.
The following example shows the NO_PARALLEL hint. Note that with trace_hints set
to on, the INFO: [HINTS] message states that the parallel scan was rejected due to the
NO_PARALLEL hint.
If a command includes two or more conflicting hints, the server will ignore the
contradictory hints. The following table lists hints that are contradictory to each other.
This chapter describes the basic elements of an SPL program, before providing an
overview of the organization of an SPL program and how it is used to create a procedure
or a function. Triggers, while still utilizing SPL, are sufficiently different to warrant a
separate discussion (see Section 4 for information about triggers). Packages are discussed
in the Database Compatibility for Oracle Developers Built-in Package Guide available
at:
http://www.enterprisedb.com/products-services-training/products/documentation
The remaining sections of this chapter delve into the details of the SPL language and
provide examples of its application.
Identifiers, expressions, statements, control structures, etc. that comprise the SPL
language are written using these characters.
Note: The data that can be manipulated by an SPL program is determined by the
character set supported by the database encoding.
Keywords and user-defined identifiers that are used in an SPL program are case
insensitive. So for example, the statement DBMS_OUTPUT.PUT_LINE('Hello
World'); is interpreted to mean the same thing as dbms_output.put_line('Hello
World'); or Dbms_Output.Put_Line('Hello World'); or
DBMS_output.Put_line('Hello World');.
Character and string constants, however, are case sensitive as well as any data retrieved
from the Advanced Server database or data obtained from other external sources. The
statement DBMS_OUTPUT.PUT_LINE('Hello World!'); produces the following
output:
Hello World!
HELLO WORLD!
3.1.3 Identifiers
Identifiers are user-defined names that are used to identify various elements of an SPL
program including variables, cursors, labels, programs, and parameters. The syntax rules
for valid identifiers are the same as for identifiers in the SQL language.
An identifier must not be the same as an SPL keyword or a keyword of the SQL
language. The following are some examples of valid identifiers:
x
last___name
a_$_Sign
Many$$$$$$$$signs_____
THIS_IS_AN_EXTREMELY_LONG_NAME
A1
3.1.4 Qualifiers
A qualifier is a name that specifies the owner or context of an entity that is the object of
the qualification. A qualified object is specified as the qualifier name followed by a dot
with no intervening white space, followed by the name of the object being qualified with
no intervening white space. This syntax is called dot notation.
qualifier is the name of the owner of the object. object is the name of the entity
belonging to qualifier. It is possible to have a chain of qualifications where the
preceding qualifier owns the entity identified by the subsequent qualifier(s) and object.
Almost any identifier can be qualified. What an identifier is qualified by depends upon
what the identifier represents and the context of its usage.
Procedure and function names qualified by the schema to which they belong -
e.g., schema_name.procedure_name(...)
Trigger names qualified by the schema to which they belong - e.g.,
schema_name.trigger_name
Column names qualified by the table to which they belong - e.g., emp.empno
Table names qualified by the schema to which they belong - e.g., public.emp
Column names qualified by table and schema - e.g., public.emp.empno
As a general rule, wherever a name appears in the syntax of an SPL statement, its
qualified name can be used as well. Typically a qualified name would only be used if
there is some ambiguity associated with the name. For example, if two procedures with
the same name belonging to two different schemas are invoked from within a program or
if the same name is used for a table column and SPL variable within the same program.
You should avoid using qualified names if at all possible. In this chapter, the following
conventions are adopted to avoid naming conflicts:
All variables declared in the declaration section of an SPL program are prefixed
by v_. E.g., v_empno
All formal parameters declared in a procedure or function definition are prefixed
by p_. E.g., p_empno
Column names and table names do not have any special prefix conventions. E.g.,
column empno in table emp
3.1.5 Constants
Constants or literals are fixed values that can be used in SPL programs to represent
values of various types - e.g., numbers, strings, dates, etc. Constants come in the
following types:
Where:
subtype_name
type_name
type_name specifies the name of the original type on which the subtype is based.
type_name may be:
Include the constraint clause to define restrictions for types that support precision or
scale.
precision
length
Include the NOT NULL clause to specify that NULL values may not be stored in a column
of the specified subtype.
Note that a subtype that is based on a column will inherit the column size constraints, but
the subtype will not inherit NOT NULL or CHECK constraints.
Unconstrained Subtypes
To create an unconstrained subtype, use the SUBTYPE command to specify the new
subtype name and the name of the type on which the subtype is based. For example, the
following command creates a subtype named address that has all of the attributes of the
type, CHAR:
You can also create a subtype (constrained or unconstrained) that is a subtype of another
subtype:
This command creates a subtype named cust_address that shares all of the attributes
of the address subtype. Include the NOT NULL clause to specify that a value of the
cust_address may not be NULL.
Constrained Subtypes
Include a length value when creating a subtype that is based on a character type to
define the maximum length of the subtype. For example:
This example creates a subtype named acct_name that is based on a VARCHAR data type,
but is limited to 15 characters in length.
Include values for precision (to specify the maximum number of digits in a value of
the subtype) and optionally, scale (to specify the number of digits to the right of the
decimal point) when constraining a numeric base type. For example:
This example creates a subtype named acct_balance that shares all of the attributes of
a NUMBER type, but that may not exceed 3 digits to the left of the decimal point and 2
digits to the right of the decimal.
Advanced Server does not enforce subtype constraints when assigning an actual
argument to a formal argument when invoking a function.
You can use %TYPE notation to declare a subtype anchored to a column. For example:
This command creates a subtype named emp_type whose base type matches the type of
the empno column in the emp table. A subtype that is based on a column will share the
column size constraints; NOT NULL and CHECK constraints are not inherited.
Subtype Conversion
Unconstrained subtypes are aliases for the type on which they are based. Any variable of
type subtype (unconstrained) is interchangeable with a variable of the base type without
conversion, and vice versa.
A variable of a constrained subtype may be interchanged with a variable of the base type
without conversion, but a variable of the base type may only be interchanged with a
constrained subtype if it complies with the constraints of the subtype. A variable of a
constrained subtype may be implicitly converted to another subtype if it is based on the
same subtype, and the constraint values are within the values of the subtype to which it is
being converted.
The optional declaration section is used to declare variables, cursors, types, and
subprograms that are used by the statements within the executable and exception
sections. Declarations appear just prior to the BEGIN keyword of the executable section.
Depending upon the context of where the block is used, the declaration section may begin
with the keyword DECLARE.
You can include an exception section within the BEGIN - END block. The exception
section begins with the keyword, EXCEPTION, and continues until the end of the block in
which it appears. If an exception is thrown by a statement within the block, program
control goes to the exception section where the thrown exception may or may not be
handled depending upon the exception and the contents of the exception section.
[ [ DECLARE ]
declarations ]
BEGIN
statements
[ EXCEPTION
WHEN exception_condition THEN
statements [, ...] ]
END;
declarations are one or more variable, cursor, type, or subprogram declarations that
are local to the block. If subprogram declarations are included, they must be declared
after all other variable, cursor, and type declarations. Each declaration must be terminated
by a semicolon. The use of the keyword DECLARE depends upon the context in which the
block appears.
statements are one or more SPL statements. Each statement must be terminated by a
semicolon. The end of the block denoted by the keyword END must also be terminated by
a semicolon.
If present, the keyword EXCEPTION marks the beginning of the exception section.
exception_condition is a conditional expression testing for one or more types of
exceptions. If a thrown exception matches one of the exceptions in
exception_condition, the statements following the WHEN
The following is the simplest possible block consisting of the NULL statement within the
executable section. The NULL statement is an executable statement that does nothing.
BEGIN
NULL;
END;
The following block contains a declaration section as well as the executable section.
DECLARE
v_numerator NUMBER(2);
v_denominator NUMBER(2);
v_result NUMBER(5,2);
BEGIN
v_numerator := 75;
v_denominator := 14;
v_result := v_numerator / v_denominator;
DBMS_OUTPUT.PUT_LINE(v_numerator || ' divided by ' || v_denominator ||
' is ' || v_result);
END;
In this example, three numeric variables are declared of data type NUMBER. Values are
assigned to two of the variables, and one number is divided by the other, storing the
results in a third variable which is then displayed. If executed, the output would be:
75 divided by 14 is 5.36
DECLARE
v_numerator NUMBER(2);
v_denominator NUMBER(2);
v_result NUMBER(5,2);
BEGIN
v_numerator := 75;
v_denominator := 0;
v_result := v_numerator / v_denominator;
DBMS_OUTPUT.PUT_LINE(v_numerator || ' divided by ' || v_denominator ||
' is ' || v_result);
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('An exception occurred');
END;
The following output shows that the statement within the exception section is executed as
a result of the division by zero.
An exception occurred
A block of this type is called an anonymous block. An anonymous block is unnamed and
is not stored in the database. Once the block has been executed and erased from the
application buffer, it cannot be re-executed unless the block code is re-entered into the
application.
Typically, the same block of code will be re-executed many times. In order to run a block
of code repeatedly without the necessity of re-entering the code each time, with some
simple modifications, an anonymous block can be turned into a procedure or function.
The following sections discuss how to create a procedure or function that can be stored in
the database and invoked repeatedly by another procedure, function, or application
program.
Procedures are standalone SPL programs that are invoked or called as an individual SPL
program statement. When called, procedures may optionally receive values from the
caller in the form of input parameters and optionally return values to the caller in the
form of output parameters.
The CREATE PROCEDURE command defines and names a standalone procedure that will
be stored in the database.
Where:
name
name is the identifier of the procedure. If you specify the [OR REPLACE] clause
and a procedure with the same name already exists in the schema, the new
procedure will replace the existing one. If you do not specify [OR REPLACE],
the new procedure will not replace the existing procedure with the same name in
the same schema.
declarations
statements
statements are SPL program statements (the BEGIN - END block may contain
an EXCEPTION section).
IMMUTABLE
STABLE
VOLATILE
These attributes inform the query optimizer about the behavior of the procedure;
you can specify only one choice. VOLATILE is the default behavior.
IMMUTABLE indicates that the procedure cannot modify the database and always
reaches the same result when given the same argument values; it does not do
database lookups or otherwise use information not directly present in its argument
list. If you include this clause, any call of the procedure with all-constant
arguments can be immediately replaced with the procedure value.
STABLE indicates that the procedure cannot modify the database, and that within a
single table scan, it will consistently return the same result for the same argument
values, but that its result could change across SQL statements. This is the
appropriate selection for procedures that depend on database lookups, parameter
variables (such as the current time zone), etc.
VOLATILE indicates that the procedure value can change even within a single
table scan, so no optimizations can be made. Please note that any function that
has side-effects must be classified volatile, even if its result is quite predictable, to
prevent calls from being optimized away.
DETERMINISTIC
[ NOT ] LEAKPROOF
A LEAKPROOK procedure has no side effects, and reveals no information about the
values used to call the procedure.
CALLED ON NULL INPUT (the default) indicates that the procedure will be called
normally when some of its arguments are NULL. It is the author's responsibility to
check for NULL values if necessary and respond appropriately.
RETURNS NULL ON NULL INPUT or STRICT indicates that the procedure always
returns NULL whenever any of its arguments are NULL. If these clauses are
specified, the procedure is not executed when there are NULL arguments; instead a
NULL result is assumed automatically.
SECURITY DEFINER specifies that the procedure will execute with the privileges
of the user that created it; this is the default. The key word EXTERNAL is allowed
for SQL conformance, but is optional.
The SECURITY INVOKER clause indicates that the procedure will execute with the
privileges of the user that calls it. The key word EXTERNAL is allowed for SQL
conformance, but is optional.
AUTHID DEFINER
AUTHID CURRENT_USER
The PARALLEL clause enables the use of parallel sequential scans (parallel mode).
A parallel sequential scan uses multiple workers to scan a relation in parallel
during a query in contrast to a serial sequential scan.
When set to UNSAFE, the procedure cannot be executed in parallel mode. The
presence of such a procedure forces a serial execution plan. This is the default
setting if the PARALLEL clause is omitted.
When set to RESTRICTED, the procedure can be executed in parallel mode, but
the execution is restricted to the parallel group leader. If the qualification for any
particular relation has anything that is parallel restricted, that relation won't be
chosen for parallelism.
When set to SAFE, the procedure can be executed in parallel mode with no
restriction.
COST execution_cost
ROWS result_rows
result_rows is a positive number giving the estimated number of rows that the
planner should expect the procedure to return. This is only allowed when the
procedure is declared to return a set. The default assumption is 1000 rows.
The SET clause causes the specified configuration parameter to be set to the
specified value when the procedure is entered, and then restored to its prior value
when the procedure exits. SET FROM CURRENT saves the session's current value
of the parameter as the value to be applied when the procedure is entered.
Please Note: The STRICT, LEAKPROOF, PARALLEL, COST, ROWS and SET keywords
provide extended functionality for Advanced Server and are not supported by Oracle.
Example
The procedure is stored in the database by entering the procedure code in Advanced
Server.
The following example demonstrates using the AUTHID DEFINER and SET clauses in a
procedure declaration. The update_salary procedure conveys the privileges of the
role that defined the procedure to the role that is calling the procedure (while the
procedure executes):
Include the SET clause to set the procedure's search path to public and the work
memory to 1MB. Other procedures, functions and objects will not be affected by these
settings.
In this example, the AUTHID DEFINER clause temporarily grants privileges to a role that
might otherwise not be allowed to execute the statements within the procedure. To
instruct the server to use the privileges associated with the role invoking the procedure,
replace the AUTHID DEFINER clause with the AUTHID CURRENT_USER clause.
name [ ([ parameters ]) ];
Where:
Note: If there are no actual parameters to be passed, the procedure may be called with an
empty parameter list, or the opening and closing parenthesis may be omitted entirely.
Note: The syntax for calling a procedure is the same as in the preceding syntax diagram
when executing it with the EXEC command in PSQL or EDB*Plus. See the Database
Compatibility for Oracle Developers Tools and Utilities Guide for information about the
EXEC command.
BEGIN
simple_procedure;
END;
Note: Each application has its own unique way to call a procedure. For example, in a
Java application, the application programming interface, JDBC, is used.
A procedure can be deleted from the database using the DROP PROCEDURE command.
Functions are standalone SPL programs that are invoked as expressions. When evaluated,
a function returns a value that is substituted in the expression in which the function is
embedded. Functions may optionally take values from the calling program in the form of
input parameters. In addition to the fact that the function, itself, returns a value, a
function may optionally return additional values to the caller in the form of output
parameters. The use of output parameters in functions, however, is not an encouraged
programming practice.
The CREATE FUNCTION command defines and names a standalone function that will be
stored in the database.
Where:
name
name is the identifier of the function. If you specify the [OR REPLACE] clause
and a function with the same name already exists in the schema, the new function
will replace the existing one. If you do not specify [OR REPLACE], the new
function will not replace the existing function with the same name in the same
schema.
parameters
data_type
data_type is the data type of the value returned by the functions RETURN
statement.
declarations
statements
statements are SPL program statements (the BEGIN - END block may contain
an EXCEPTION section).
IMMUTABLE
STABLE
VOLATILE
These attributes inform the query optimizer about the behavior of the function;
you can specify only one choice. VOLATILE is the default behavior.
IMMUTABLE indicates that the function cannot modify the database and always
reaches the same result when given the same argument values; it does not do
database lookups or otherwise use information not directly present in its argument
list. If you include this clause, any call of the function with all-constant
arguments can be immediately replaced with the function value.
STABLE indicates that the function cannot modify the database, and that within a
single table scan, it will consistently return the same result for the same argument
values, but that its result could change across SQL statements. This is the
appropriate selection for function that depend on database lookups, parameter
variables (such as the current time zone), etc.
VOLATILE indicates that the function value can change even within a single table
scan, so no optimizations can be made. Please note that any function that has
side-effects must be classified volatile, even if its result is quite predictable, to
prevent calls from being optimized away.
DETERMINISTIC
[ NOT ] LEAKPROOF
A LEAKPROOK function has no side effects, and reveals no information about the
values used to call the function.
CALLED ON NULL INPUT (the default) indicates that the procedure will be called
normally when some of its arguments are NULL. It is the author's responsibility to
check for NULL values if necessary and respond appropriately.
RETURNS NULL ON NULL INPUT or STRICT indicates that the procedure always
returns NULL whenever any of its arguments are NULL. If these clauses are
specified, the procedure is not executed when there are NULL arguments; instead a
NULL result is assumed automatically.
SECURITY DEFINER specifies that the function will execute with the privileges of
the user that created it; this is the default. The key word EXTERNAL is allowed for
SQL conformance, but is optional.
The SECURITY INVOKER clause indicates that the function will execute with the
privileges of the user that calls it. The key word EXTERNAL is allowed for SQL
conformance, but is optional.
AUTHID DEFINER
AUTHID CURRENT_USER
The PARALLEL clause enables the use of parallel sequential scans (parallel mode).
A parallel sequential scan uses multiple workers to scan a relation in parallel
during a query in contrast to a serial sequential scan.
When set to UNSAFE, the function cannot be executed in parallel mode. The
presence of such a function in a SQL statement forces a serial execution plan.
This is the default setting if the PARALLEL clause is omitted.
When set to RESTRICTED, the function can be executed in parallel mode, but the
execution is restricted to the parallel group leader. If the qualification for any
particular relation has anything that is parallel restricted, that relation won't be
chosen for parallelism.
When set to SAFE, the function can be executed in parallel mode with no
restriction.
COST execution_cost
ROWS result_rows
result_rows is a positive number giving the estimated number of rows that the
planner should expect the function to return. This is only allowed when the
function is declared to return a set. The default assumption is 1000 rows.
The SET clause causes the specified configuration parameter to be set to the
specified value when the function is entered, and then restored to its prior value
when the function exits. SET FROM CURRENT saves the session's current value of
the parameter as the value to be applied when the function is entered.
Please Note: The STRICT, LEAKPROOF, PARALLEL, COST, ROWS and SET keywords
provide extended functionality for Advanced Server and are not supported by Oracle.
Examples
The following function takes two input parameters. Parameters are discussed in more
detail in subsequent sections.
The following example demonstrates using the AUTHID CURRENT_USER clause and
STRICT keyword in a function declaration:
Include the STRICT keyword to instruct the server to return NULL if any input parameter
passed is NULL; if a NULL value is passed, the function will not execute.
The dept_salaries function executes with the privileges of the role that is calling the
function. If the current user does not have sufficient privileges to perform the SELECT
statement querying the emp table (to display employee salaries), the function will report
an error. To instruct the server to use the privileges associated with the role that defined
the function, replace the AUTHID CURRENT_USER clause with the AUTHID DEFINER
clause.
name [ ([ parameters ]) ]
Note: If there are no actual parameters to be passed, the function may be called with an
empty parameter list, or the opening and closing parenthesis may be omitted entirely.
The following shows how the function can be called from another SPL program.
BEGIN
DBMS_OUTPUT.PUT_LINE(simple_function);
END;
A function can be deleted from the database using the DROP FUNCTION command.
Note: The specification of the parameter list is required in Advanced Server under certain
circumstances. Oracle requires that the parameter list always be omitted.
An important aspect of using procedures and functions is the capability to pass data from
the calling program to the procedure or function and to receive data back from the
procedure or function. This is accomplished by using parameters.
Note: In the previous example, no maximum length was specified on the VARCHAR2
parameters and no precision and scale were specified on the NUMBER parameters. It is
illegal to specify a length, precision, scale or other constraints on parameter declarations.
These constraints are automatically inherited from the actual parameters that are used
when the procedure or function is called.
The emp_query procedure can be called by another program, passing it the actual
parameters. The following is an example of another SPL program that calls emp_query.
DECLARE
v_deptno NUMBER(2);
v_empno NUMBER(4);
v_ename VARCHAR2(10);
v_job VARCHAR2(9);
v_hiredate DATE;
v_sal NUMBER;
BEGIN
v_deptno := 30;
v_empno := 7900;
v_ename := '';
emp_query(v_deptno, v_empno, v_ename, v_job, v_hiredate, v_sal);
DBMS_OUTPUT.PUT_LINE('Department : ' || v_deptno);
DBMS_OUTPUT.PUT_LINE('Employee No: ' || v_empno);
DBMS_OUTPUT.PUT_LINE('Name : ' || v_ename);
DBMS_OUTPUT.PUT_LINE('Job : ' || v_job);
DBMS_OUTPUT.PUT_LINE('Hire Date : ' || v_hiredate);
DBMS_OUTPUT.PUT_LINE('Salary : ' || v_sal);
END;
In this example, v_deptno, v_empno, v_ename, v_job, v_hiredate, and v_sal are
the actual parameters.
Department : 30
Employee No: 7900
Name : JAMES
Job : CLERK
Hire Date : 03-DEC-81
Salary : 950
To specify parameters using named notation, list the name of each parameter followed by
an arrow (=>) and the parameter value. Named notation is more verbose, but makes your
code easier to read and maintain.
A simple example that demonstrates using positional and named parameter notation
follows:
Using named notation can alleviate the need to re-arrange a procedures parameter list if
the parameter list changes, if the parameters are reordered or if a new optional parameter
is added.
In a case where you have a default value for an argument and the argument is not a
trailing argument, you must use named notation to call the procedure or function. The
following case demonstrates a procedure with two, leading, default arguments.
You can only omit non-trailing argument values (when you call this procedure) by using
named notation; when using positional notation, only trailing arguments are allowed to
default. You can call this procedure with the following arguments:
You can use a combination of positional and named notation (mixed notation) to specify
parameters. A simple example that demonstrates using mixed parameter notation
follows:
If you do use mixed notation, remember that named arguments cannot precede positional
arguments.
As previously discussed, a parameter has one of three possible modes - IN, OUT, or IN
OUT. The following characteristics of a formal parameter are dependent upon its mode:
How the actual parameter value is passed from the calling program to the called
program.
What happens to the formal parameter value when an unhandled exception occurs
in the called program.
The following table summarizes the behavior of parameters according to their mode.
As shown by the table, an IN formal parameter is initialized to the actual parameter with
which it is called unless it was explicitly initialized with a default value. The IN
parameter may be referenced within the called program, however, the called program
may not assign a new value to the IN parameter. After control returns to the calling
program, the actual parameter always contains the same value as it was set to prior to the
call.
The OUT formal parameter is initialized to the actual parameter with which it is called.
The called program may reference and assign new values to the formal parameter. If the
called program terminates without an exception, the actual parameter takes on the value
last set in the formal parameter. If a handled exception occurs, the value of the actual
parameter takes on the last value assigned to the formal parameter. If an unhandled
exception occurs, the value of the actual parameter remains as it was prior to the call.
You can set a default value of a formal parameter by including the DEFAULT clause or
using the assignment operator (:=) in the CREATE PROCEDURE or CREATE FUNCTION
statement.
expr is the default value assigned to the parameter. If you do not include a DEFAULT
clause, the caller must provide a value for the parameter.
The default value is evaluated every time the function or procedure is invoked. For
example, assigning SYSDATE to a parameter of type DATE causes the parameter to have
the time of the current invocation, not the time when the procedure or function was
created.
The following simple procedure demonstrates using the assignment operator to set a
default value of SYSDATE into the parameter, hiredate:
DBMS_OUTPUT.PUT_LINE('Hired!');
END hire_emp;
If the parameter declaration includes a default value, you can omit the parameter from the
actual parameter list when you call the procedure. Calls to the sample procedure
(hire_emp) must include two arguments: the employee number (p_empno) and
employee name (p_empno). The third parameter (p_hiredate) defaults to the value of
SYSDATE:
If you do include a value for the actual parameter when you call the procedure, that value
takes precedence over the default value:
Adds a new employee with a hiredate of February 15, 2010, regardless of the current
value of SYSDATE.
You can write the same procedure by substituting the DEFAULT keyword for the
assignment operator:
DBMS_OUTPUT.PUT_LINE('Hired!');
END hire_emp;
The capability and functionality of SPL procedure and function programs can be used in
an advantageous manner to build well-structured and maintainable programs by
organizing the SPL code into subprocedures and subfunctions.
The same SPL code can be invoked multiple times from different locations within a
relatively large SPL program by declaring subprocedures and subfunctions within the
SPL program.
can exist in the standalone program. Within the hierarchy, a subprogram can
access the identifiers of upper level parent subprograms and also invoke upper
level parent subprograms. However, the same access to identifiers and invocation
cannot be done for lower level child subprograms in the hierarchy.
Subprocedures and subfunctions can be declared and invoked from within any of the
following types of SPL programs:
The rules regarding subprocedure and subfunction structure and access are discussed in
more detail in the next sections.
The PROCEDURE clause specified in the declaration section defines and names a
subprocedure local to that block.
The term block refers to the SPL block structure consisting of an optional declaration
section, a mandatory executable section, and an optional exception section. Blocks are
the structures for standalone procedures and functions, anonymous blocks, subprograms,
triggers, packages, and object type methods.
The phrase the identifier is local to the block means that the identifier (that is, a variable,
cursor, type, or subprogram) is declared within the declaration section of that block and is
therefore accessible by the SPL code within the executable section and optional exception
section of that block.
Subprocedures can only be declared after all other variable, cursor, and type declarations
included in the declaration section. (That is, subprograms must be the last set of
declarations.)
Where:
parameters
declarations
statements
statements are SPL program statements (the BEGIN - END block may contain
an EXCEPTION section).
Examples
DECLARE
PROCEDURE list_emp
IS
v_empno NUMBER(4);
v_ename VARCHAR2(10);
CURSOR emp_cur IS
SELECT empno, ename FROM emp ORDER BY empno;
BEGIN
OPEN emp_cur;
DBMS_OUTPUT.PUT_LINE('Subprocedure list_emp:');
DBMS_OUTPUT.PUT_LINE('EMPNO ENAME');
DBMS_OUTPUT.PUT_LINE('----- -------');
LOOP
FETCH emp_cur INTO v_empno, v_ename;
EXIT WHEN emp_cur%NOTFOUND;
DBMS_OUTPUT.PUT_LINE(v_empno || ' ' || v_ename);
END LOOP;
CLOSE emp_cur;
END;
BEGIN
list_emp;
END;
Subprocedure list_emp:
EMPNO ENAME
----- -------
7369 SMITH
7499 ALLEN
7521 WARD
The FUNCTION clause specified in the declaration section defines and names a
subfunction local to that block.
The term block refers to the SPL block structure consisting of an optional declaration
section, a mandatory executable section, and an optional exception section. Blocks are
the structures for standalone procedures and functions, anonymous blocks, subprograms,
triggers, packages, and object type methods.
The phrase the identifier is local to the block means that the identifier (that is, a variable,
cursor, type, or subprogram) is declared within the declaration section of that block and is
therefore accessible by the SPL code within the executable section and optional exception
section of that block.
Where:
name
parameters
data_type
data_type is the data type of the value returned by the functions RETURN
statement.
declarations
statements
statements are SPL program statements (the BEGIN - END block may contain
an EXCEPTION section).
Examples
DECLARE
FUNCTION factorial (
n BINARY_INTEGER
) RETURN BINARY_INTEGER
IS
BEGIN
1! = 1
2! = 2
3! = 6
4! = 24
5! = 120
The sibling blocks are the set of blocks that have the same parent block (that is,
they are all locally declared in the same block). Sibling blocks are also always at
the same level relative to each other.
The two vertical lines on the left-hand side of the blocks indicate there are two pairs of
sibling blocks. block_1a and block_1b is one pair, and block_2a and block_2b is
the second pair.
The relationship of each block with its ancestors is shown on the right-hand side of the
blocks. There are three hierarchical paths formed when progressing up the hierarchy from
the lowest level child blocks. The first consists of block_0, block_1a, block_2a, and
block_3. The second is block_0, block_1a, and block_2b. The third is block_0,
block_1b, and block_2b.
The rules for invoking subprograms based upon block location is described starting with
Section 3.2.6.4. The rules for accessing variables based upon block location is described
in Section 3.2.6.7.
The subprogram may be invoked with none, one, or more qualifiers, which are the names
of the parent subprograms or labeled anonymous blocks forming the ancestor hierarchy
from where the subprogram has been declared.
[[qualifier_1.][...]qualifier_n.]subprog [(arguments)]
If specified, qualifier_n is the subprogram in which subprog has been declared in its
declaration section. The preceding list of qualifiers must reside in a continuous path up
the hierarchy from qualifier_n to qualifier_1. qualifier_1 may be any
ancestor subprogram in the path as well as any of the following:
Note: qualifier_1 may not be a schema name, otherwise an error is thrown upon
invocation of the subprogram. This Advanced Server restriction is not compatible with
Oracle databases, which allow use of the schema name as a qualifier.
The invoked subprogram name of its type (that is, subprocedure or subfunction)
along with any qualifiers in the specified order, (referred to as the invocation list)
is used to find a matching set of blocks residing in the same hierarchical order.
The search begins in the block hierarchy where the lowest level is the block from
where the subprogram is invoked. The declaration of the subprogram must be in
the SPL code prior to the code line where it is invoked when the code is observed
from top to bottom. (An exception to this requirement can be accomplished using
a forward declaration. See Section 3.2.6.5 for information on forward
declarations.)
If the invocation list does not match the hierarchy of blocks starting from the
block where the subprogram is invoked, a comparison is made by matching the
invocation list starting with the parent of the previous starting block. In other
words, the comparison progresses up the hierarchy.
If there are sibling blocks of the ancestors, the invocation list comparison also
includes the hierarchy of the sibling blocks, but always comparing in an upward
level, never comparing the descendants of the sibling blocks.
This comparison process continues up the hierarchies until the first complete
match is found in which case the located subprogram is invoked. Note that the
formal parameter list of the matched subprogram must comply with the actual
parameter list specified for the invoked subprogram, otherwise an error occurs
upon invocation of the subprogram.
If no match is found after searching up to the standalone program, then an error is
thrown upon invocation of the subprogram.
Note: The Advanced Server search algorithm for subprogram invocation is not quite
compatible with Oracle databases. For Oracle, the search looks for the first match of the
first qualifier (that is qualifier_1). When such a match is found, all remaining
qualifiers, the subprogram name, subprogram type, and arguments of the invocation must
match the hierarchy content where the matching first qualifier is found, otherwise an
error is thrown. For Advanced Server, a match is not found unless all qualifiers, the
subprogram name, and the subprogram type of the invocation match the hierarchy
content. If such an exact match is not initially found, Advanced Server continues the
search progressing up the hierarchy.
The location of subprograms relative to the block from where the invocation is made can
be accessed as follows:
Subprograms declared in the local block can be invoked from the executable
section or the exception section of the same block.
Subprograms declared in the parent or other ancestor blocks can be invoked from
the child block of the parent or other ancestors.
Subprograms declared in sibling blocks can be called from a sibling block or from
any descendent block of the sibling.
However, the following location of subprograms cannot be accessed relative to the block
from where the invocation is made:
Subprograms declared in blocks that are descendants of the block from where the
invocation is attempted.
Subprograms declared in blocks that are descendants of a sibling block from
where the invocation is attempted.
The following example contains a single hierarchy of blocks contained within standalone
procedure level_0. Within the executable section of procedure level_1a, the means
of invoking the local procedure level_2a are shown, both with and without qualifiers.
Also note that access to the descendant of local procedure level_2a, which is procedure
level_3a, is not permitted, with or without qualifiers. These calls are commented out in
the example.
CREATE OR REPLACE PROCEDURE level_0
When the standalone procedure is invoked, the output is the following, which indicates
that procedure level_2a is successfully invoked from the calls in the executable section
of procedure level_1a.
BEGIN
level_0;
END;
BLOCK level_0
.. BLOCK level_1a
...... BLOCK level_2a
...... END BLOCK level_2a
...... BLOCK level_2a
...... END BLOCK level_2a
...... BLOCK level_2a
...... END BLOCK level_2a
.. END BLOCK level_1a
END BLOCK level_0
If you were to attempt to run procedure level_0 with any of the calls to the descendent
block uncommented, then an error occurs.
The following example shows how subprograms can be invoked that are declared in
parent and other ancestor blocks relative to the block where the invocation is made.
BLOCK level_0
.. BLOCK level_1a
...... BLOCK level_2a
........ BLOCK level_3a
...... BLOCK level_2a
........ BLOCK level_3a
........ END BLOCK level_3a
...... END BLOCK level_2a
........ END BLOCK level_3a
...... END BLOCK level_2a
.. END BLOCK level_1a
END BLOCK level_0
BLOCK level_0
.. BLOCK level_1a
...... BLOCK level_2a
........ BLOCK level_3a
.. BLOCK level_1a
...... BLOCK level_2a
........ BLOCK level_3a
........ END BLOCK level_3a
...... END BLOCK level_2a
.. END BLOCK level_1a
........ END BLOCK level_3a
...... END BLOCK level_2a
.. END BLOCK level_1a
END BLOCK level_0
The following examples show how subprograms can be invoked that are declared in a
sibling block relative to the local, parent, or other ancestor blocks from where the
invocation of the subprogram is made.
BLOCK level_0
.. BLOCK level_1b
.. BLOCK level_1a
.. END BLOCK level_1a
.. END BLOCK level_1b
END BLOCK level_0
BLOCK level_0
.. BLOCK level_1b
...... BLOCK level_2b
........ BLOCK level_3b
.. BLOCK level_1a
.. END BLOCK level_1a
.. BLOCK level_1a
.. END BLOCK level_1a
........ END BLOCK level_3b
...... END BLOCK level_2b
.. END BLOCK level_1b
END BLOCK level_0
However, there is a method of constructing the SPL code so that the full declaration of
the subprogram (that is, its optional declaration section, its mandatory executable section,
and optional exception section) appears in the SPL code after the point in the code where
it is invoked.
This is accomplished by inserting a forward declaration in the SPL code prior to its
invocation. The forward declaration is the specification of a subprocedure or subfunction
name, formal parameters, and return type if it is a subfunction.
The full subprogram specification consisting of the optional declaration section, the
executable section, and the optional exception section must be specified in the same
declaration section as the forward declaration, but may appear following other
subprogram declarations that invoke this subprogram with the forward declaration.
Typical usage of a forward declaration is when two subprograms invoke each other as
shown by the following:
DECLARE
FUNCTION add_one (
p_add IN NUMBER
) RETURN NUMBER;
FUNCTION test_max (
Increase by 1
Increase by 1
Final value is 5
Each subprogram can be individually invoked depending upon the use of qualifiers and
the location where the subprogram invocation is made as discussed in the previous
sections.
It is however possible to declare subprograms, even as siblings, that are of the same
subprogram type and name as long as certain aspects of the formal parameters differ.
These characteristics (subprogram type, name, and formal parameter specification) is
generally known as a programs signature.
The declaration of multiple subprograms where the signatures are identical except for
certain aspects of the formal parameter specification is referred to as subprogram
overloading.
Note that the following differences alone do not permit overloaded subprograms:
However, certain data types have alternative names referred to as aliases, which can be
used for the table definition.
For example, there are fixed length character data types that can be specified as CHAR or
CHARACTER. There are variable length character data types that can be specified as CHAR
VARYING, CHARACTER VARYING, VARCHAR, or VARCHAR2. For integers, there are
BINARY_INTEGER, PLS_INTEGER, and INTEGER data types. For numbers, there are
NUMBER, NUMERIC, DEC, and DECIMAL data types.
For detailed information about the data types supported by Advanced Server, please see
the Database Compatibility for Oracle Developers Reference Guide, available from
EnterpriseDB at:
http://www.enterprisedb.com/products-services-training/products/documentation
Thus, when attempting to create overloaded subprograms, the formal parameter data
types are not considered different if the specified data types are aliases of each other.
It can be determined if certain data types are aliases of other types by displaying the table
definition containing the data types in question.
For example, the following table definition contains some data types and their aliases.
Using the PSQL \d command to display the table definition, the Type column displays
the data type internally assigned to each column based upon its data type in the table
definition:
\d data_type_aliases
Column | Type | Modifiers
---------------------+----------------------+-----------
dt_blob | bytea |
dt_long_raw | bytea |
dt_raw | bytea(4) |
dt_bytea | bytea |
dt_integer | integer |
dt_binary_integer | integer |
dt_pls_integer | integer |
dt_real | real |
dt_double_precision | double precision |
dt_float | double precision |
dt_number | numeric |
dt_decimal | numeric |
dt_numeric | numeric |
dt_char | character(1) |
dt_character | character(1) |
dt_varchar2 | character varying(4) |
dt_char_varying | character varying(4) |
dt_varchar | character varying(4) |
In the example, the base set of data types are bytea, integer, real, double
precision, numeric, character, and character varying.
Note: The overloading rules based upon formal parameter data types are not compatible
with Oracle databases. Generally, the Advanced Server rules are more flexible, and
certain combinations are allowed in Advanced Server that would result in an error when
attempting to create the procedure or function in Oracle databases.
For certain pairs of data types used for overloading, casting of the arguments specified by
the subprogram invocation may be required to avoid an error encountered during runtime
of the subprogram. Invocation of a subprogram must include the actual parameter list that
can specifically identify the data types. Certain pairs of overloaded data types may
require the CAST function to explicitly identify data types. For example, pairs of
overloaded data types that may require casting during the invocation are CHAR and
VARCHAR2, or NUMBER and REAL.
The following example shows a group of overloaded subfunctions invoked from within
an anonymous block. The executable section of the anonymous block contains the use of
the CAST function to invoke overloaded functions with certain data types.
DECLARE
FUNCTION add_it (
p_add_1 IN BINARY_INTEGER,
p_add_2 IN BINARY_INTEGER
) RETURN VARCHAR2
IS
BEGIN
RETURN 'add_it BINARY_INTEGER: ' || TO_CHAR(p_add_1 + p_add_2,9999.9999);
END add_it;
FUNCTION add_it (
p_add_1 IN NUMBER,
p_add_2 IN NUMBER
) RETURN VARCHAR2
IS
BEGIN
RETURN 'add_it NUMBER: ' || TO_CHAR(p_add_1 + p_add_2,999.9999);
END add_it;
FUNCTION add_it (
p_add_1 IN REAL,
p_add_2 IN REAL
) RETURN VARCHAR2
IS
BEGIN
RETURN 'add_it REAL: ' || TO_CHAR(p_add_1 + p_add_2,9999.9999);
END add_it;
FUNCTION add_it (
p_add_1 IN DOUBLE PRECISION,
p_add_2 IN DOUBLE PRECISION
) RETURN VARCHAR2
IS
BEGIN
RETURN 'add_it DOUBLE PRECISION: ' || TO_CHAR(p_add_1 + p_add_2,9999.9999);
END add_it;
BEGIN
DBMS_OUTPUT.PUT_LINE(add_it (25, 50));
DBMS_OUTPUT.PUT_LINE(add_it (25.3333, 50.3333));
DBMS_OUTPUT.PUT_LINE(add_it (TO_NUMBER(25.3333), TO_NUMBER(50.3333)));
DBMS_OUTPUT.PUT_LINE(add_it (CAST('25.3333' AS REAL), CAST('50.3333' AS REAL)));
DBMS_OUTPUT.PUT_LINE(add_it (CAST('25.3333' AS DOUBLE PRECISION),
CAST('50.3333' AS DOUBLE PRECISION)));
END;
Accessing a variable means being able to reference it within a SQL statement or an SPL
statement as is done with any local variable.
Note: If the subprogram signature contains formal parameters, these may be accessed in
the same manner as local variables of the subprogram. In this section, all discussion
related to variables of a subprogram also applies to formal parameters of the subprogram.
Access of variables not only includes those defined as a data type, but also includes
others such as record types, collection types, and cursors.
The variable may be accessed by at most one qualifier, which is the name of the
subprogram or labeled anonymous block in which the variable has been locally declared.
[qualifier.]variable
Note: In Advanced Server, there is only one circumstance where two qualifiers are
permitted. This scenario is for accessing public variables of packages where the reference
can be specified in the following format:
schema_name.package_name.public_variable_name
For more information about supported package syntax, please see the Database
Compatibility for Oracle Developers Built-In Packages Guide.
Variables can be accessed as long as the block in which the variable has been
locally declared is within the ancestor hierarchical path starting from the block
containing the reference to the variable. Such variables declared in ancestor
blocks are referred to as global variables.
If a reference to an unqualified variable is made, the first attempt is to locate a
local variable of that name. If such a local variable does not exist, then the search
for the variable is made in the parent of the current block, and so forth,
proceeding up the ancestor hierarchy. If such a variable is not found, then an error
occurs upon invocation of the subprogram.
If a reference to a qualified variable is made, the same search process is
performed as described in the previous bullet point, but searching for the first
match of the subprogram or labeled anonymous block that contains the local
variable. The search proceeds up the ancestor hierarchy until a match is found. If
such a match is not found, then an error occurs upon invocation of the
subprogram.
The following location of variables cannot be accessed relative to the block from where
the reference to the variable is made:
Note: The Advanced Server process for accessing variables is not compatible with Oracle
databases. For Oracle, any number of qualifiers can be specified and the search is based
upon the first match of the first qualifier in a similar manner to the Oracle matching
algorithm for invoking subprograms.
The following example displays how variables in various blocks are accessed, with and
without qualifiers. The lines that are commented out illustrate attempts to access
variables that would result in an error.
CREATE OR REPLACE PROCEDURE level_0
IS
v_level_0 VARCHAR2(20) := 'Value from level_0';
PROCEDURE level_1a
IS
v_level_1a VARCHAR2(20) := 'Value from level_1a';
PROCEDURE level_2a
IS
v_level_2a VARCHAR2(20) := 'Value from level_2a';
BEGIN
DBMS_OUTPUT.PUT_LINE('...... BLOCK level_2a');
DBMS_OUTPUT.PUT_LINE('........ v_level_2a: ' || v_level_2a);
DBMS_OUTPUT.PUT_LINE('........ v_level_1a: ' || v_level_1a);
DBMS_OUTPUT.PUT_LINE('........ level_1a.v_level_1a: ' ||
level_1a.v_level_1a);
DBMS_OUTPUT.PUT_LINE('........ v_level_0: ' || v_level_0);
DBMS_OUTPUT.PUT_LINE('........ level_0.v_level_0: ' || level_0.v_level_0);
DBMS_OUTPUT.PUT_LINE('...... END BLOCK level_2a');
END level_2a;
BEGIN
DBMS_OUTPUT.PUT_LINE('.. BLOCK level_1a');
level_2a;
-- DBMS_OUTPUT.PUT_LINE('.... v_level_2a: ' || v_level_2a);
-- Error - Descendent block ----^
-- DBMS_OUTPUT.PUT_LINE('.... level_2a.v_level_2a: ' || level_2a.v_level_2a);
-- Error - Descendent block ---------------^
DBMS_OUTPUT.PUT_LINE('.. END BLOCK level_1a');
END level_1a;
PROCEDURE level_1b
IS
v_level_1b VARCHAR2(20) := 'Value from level_1b';
The following is the output showing the content of each variable when the procedure is
invoked:
BEGIN
level_0;
END;
BLOCK level_0
.. v_level_0: Value from level_0
.. BLOCK level_1a
...... BLOCK level_2a
........ v_level_2a: Value from level_2a
........ v_level_1a: Value from level_1a
........ level_1a.v_level_1a: Value from level_1a
........ v_level_0: Value from level_0
........ level_0.v_level_0: Value from level_0
...... END BLOCK level_2a
.. END BLOCK level_1a
.. BLOCK level_1b
.... v_level_1b: Value from level_1b
.... v_level_0 : Value from level_0
.. END BLOCK level_1b
END BLOCK level_0
The following example shows similar access attempts when all variables in all blocks
have the same name:
CREATE OR REPLACE PROCEDURE level_0
IS
v_common VARCHAR2(20) := 'Value from level_0';
PROCEDURE level_1a
IS
v_common VARCHAR2(20) := 'Value from level_1a';
PROCEDURE level_2a
IS
v_common VARCHAR2(20) := 'Value from level_2a';
BEGIN
DBMS_OUTPUT.PUT_LINE('...... BLOCK level_2a');
DBMS_OUTPUT.PUT_LINE('........ v_common: ' || v_common);
DBMS_OUTPUT.PUT_LINE('........ level_2a.v_common: ' || level_2a.v_common);
DBMS_OUTPUT.PUT_LINE('........ level_1a.v_common: ' || level_1a.v_common);
DBMS_OUTPUT.PUT_LINE('........ level_0.v_common: ' || level_0.v_common);
DBMS_OUTPUT.PUT_LINE('...... END BLOCK level_2a');
END level_2a;
BEGIN
DBMS_OUTPUT.PUT_LINE('.. BLOCK level_1a');
DBMS_OUTPUT.PUT_LINE('.... v_common: ' || v_common);
DBMS_OUTPUT.PUT_LINE('.... level_0.v_common: ' || level_0.v_common);
level_2a;
The following is the output showing the content of each variable when the procedure is
invoked:
BEGIN
level_0;
END;
BLOCK level_0
.. v_common: Value from level_0
.. BLOCK level_1a
.... v_common: Value from level_1a
.... level_0.v_common: Value from level_0
...... BLOCK level_2a
........ v_common: Value from level_2a
........ level_2a.v_common: Value from level_2a
........ level_1a.v_common: Value from level_1a
........ level_0.v_common: Value from level_0
...... END BLOCK level_2a
.. END BLOCK level_1a
.. BLOCK level_1b
.... v_common: Value from level_1b
.... level_0.v_common : Value from level_0
.. END BLOCK level_1b
END BLOCK level_0
As previously discussed, the labels on anonymous blocks can also be used to qualify
access to variables. The following example shows variable access within a set of nested
anonymous blocks:
DECLARE
v_common VARCHAR2(20) := 'Value from level_0';
BEGIN
DBMS_OUTPUT.PUT_LINE('BLOCK level_0');
DBMS_OUTPUT.PUT_LINE('.. v_common: ' || v_common);
<<level_1a>>
DECLARE
v_common VARCHAR2(20) := 'Value from level_1a';
BEGIN
DBMS_OUTPUT.PUT_LINE('.. BLOCK level_1a');
DBMS_OUTPUT.PUT_LINE('.... v_common: ' || v_common);
<<level_2a>>
DECLARE
v_common VARCHAR2(20) := 'Value from level_2a';
BEGIN
DBMS_OUTPUT.PUT_LINE('...... BLOCK level_2a');
DBMS_OUTPUT.PUT_LINE('........ v_common: ' || v_common);
DBMS_OUTPUT.PUT_LINE('........ level_1a.v_common: ' || level_1a.v_common);
The following is the output showing the content of each variable when the anonymous
block is invoked:
BLOCK level_0
.. v_common: Value from level_0
.. BLOCK level_1a
.... v_common: Value from level_1a
...... BLOCK level_2a
........ v_common: Value from level_2a
........ level_1a.v_common: Value from level_1a
...... END BLOCK level_2a
.. END BLOCK level_1a
.. BLOCK level_1b
.... v_common: Value from level_1b
.... level_1b.v_common: Value from level_1b
.. END BLOCK level_1b
END BLOCK level_0
The following example is an object type whose object type method, display_emp,
contains record type emp_typ and subprocedure emp_sal_query. Record variable
r_emp declared locally to emp_sal_query is able to access the record type emp_typ
declared in the parent block display_emp.
CREATE OR REPLACE TYPE emp_pay_obj_typ AS OBJECT
(
empno NUMBER(4),
MEMBER PROCEDURE display_emp(SELF IN OUT emp_pay_obj_typ)
);
The following is the output displayed when an instance of the object type is created and
procedure display_emp is invoked:
DECLARE
v_emp EMP_PAY_OBJ_TYP;
BEGIN
v_emp := emp_pay_obj_typ(7900);
v_emp.display_emp;
END;
Employee # : 7900
Name : JAMES
Job : CLERK
Hire Date : 03-DEC-81 00:00:00
Salary : 950.00
Dept # : 30
Employee's salary does not exceed the department average of 1566.67
The following example is a package with three levels of subprocedures. A record type,
collection type, and cursor type declared in the upper level procedure can be accessed by
the descendent subprocedure.
CREATE OR REPLACE PACKAGE emp_dept_pkg
IS
PROCEDURE display_emp (
p_deptno NUMBER
);
END;
The following is the output displayed when the top level package procedure is invoked:
BEGIN
emp_dept_pkg.display_emp(20);
END;
EMPNO ENAME
----- -------
7369 SMITH
7566 JONES
7788 SCOTT
7876 ADAMS
7902 FORD
When the Advanced Server parsers compile a procedure or function, they confirm that
both the CREATE statement and the program body (that portion of the program that
follows the AS keyword) conforms to the grammar rules for SPL and SQL constructs. By
default, the server will terminate the compilation process if a parser detects an error.
Note that the parsers detect syntax errors in expressions, but not semantic errors (i.e. an
expression referencing a non-existent column, table, or function, or a value of incorrect
type).
You can use the SET command to specify a value for spl.max_error_count for your
current session. The syntax is:
Where number_of_errors specifies the number of SPL errors that may occur before
the server halts the compilation process. For example:
SET spl.max_error_count = 6
The example instructs the server to continue past the first five SPL errors it encounters.
When the server encounters the sixth error it will stop validating, and print six detailed
error messages, and one error summary.
To save time when developing new code, or when importing existing code from another
source, you may want to set the spl.max_error_count configuration parameter to a
relatively high number of errors.
Please note that if you instruct the server to continue parsing in spite of errors in the SPL
code in a program body, and the parser encounters an error in a segment of SQL code,
there may still be errors in any SPL or SQL code that follows the erroneous SQL code.
For example, the following function results in two errors:
RETURN bonus;
END;
The following example adds a SELECT statement to the previous example. The error in
the SELECT statement masks the other errors that follow:
RETURN bonus;
END;
Security over what user may execute an SPL program and what database objects an SPL
program may access for any given user executing the program is controlled by the
following:
This default privilege can be removed by using the REVOKE EXECUTE command. The
following is an example:
Explicit EXECUTE privilege on the program can then be granted to individual users or
groups.
Now, user, john, can execute the list_emp program; other users who do not meet any
of the conditions listed at the beginning of this section cannot.
Once a program begins execution, the next aspect of security is what privilege checks
occur if the program attempts to perform an action on any database object including:
Each such action can be protected by privileges on the database object either allowed or
disallowed for the user.
Note that it is possible for a database to have more than one object of the same type with
the same name, but each such object belonging to a different schema in the database. If
this is the case, which object is being referenced by an SPL program? This is the topic of
the next section.
Locating an object with an unqualified name, however, requires the use of the current
users search path. When a user becomes the current user of a session, a default search
path is always associated with that user. The search path consists of a list of schemas
which are searched in left-to-right order for locating an unqualified database object
reference. The object is considered non-existent if it cant be found in any of the schemas
in the search path. The default search path can be displayed in PSQL using the SHOW
search_path command.
SHOW search_path;
search_path
----------------------
$user,public,sys,dbo
(1 row)
$user in the above search path is a generic placeholder that refers to the current user so
if the current user of the above session is enterprisedb, an unqualified database object
would be searched for in the following schemas in this order first, enterprisedb,
then public, then sys, and finally, dbo.
Once an unqualified name has been resolved in the search path, it can be determined if
the current user has the appropriate privilege to perform the desired action on that
specific object.
Note: The concept of the search path is not compatible with Oracle databases. For an
unqualified reference, Oracle simply looks in the schema of the current user for the
named database object. It also important to note that in Oracle, a user and his or her
schema is the same entity while in Advanced Server, a user and a schema are two distinct
objects.
The selection of the current user is influenced by whether the SPL program was created
with definers right or invokers rights. The AUTHID clause determines that selection.
Appearance of the clause AUTHID DEFINER gives the program definers rights. This is
also the default if the AUTHID clause is omitted. Use of the clause AUTHID
CURRENT_USER gives the program invokers rights. The difference between the two is
summarized as follows:
If a program has definers rights, then the owner of the program becomes the
current user when program execution begins. The program owners database
object privileges are used to determine if access to a referenced object is
permitted. In a definers rights program, it is irrelevant as to which user actually
invoked the program.
If a program has invokers rights, then the current user at the time the program is
called remains the current user while the program is executing (but not necessarily
within called subprograms see the following bullet points). When an invokers
rights program is invoked, the current user is typically the user that started the
session (i.e., made the database connection) although it is possible to change the
current user after the session has started using the SET ROLE command. In an
invokers rights program, it is irrelevant as to which user actually owns the
program.
If a definers rights program calls a definers rights program, the current user
changes from the owner of the calling program to the owner of the called program
during execution of the called program.
If a definers rights program calls an invokers rights program, the owner of the
calling program remains the current user during execution of both the calling and
called programs.
If an invokers rights program calls an invokers rights program, the current user
of the calling program remains the current user during execution of the called
program.
If an invokers rights program calls a definers rights program, the current user
switches to the owner of the definers rights program during execution of the
called program.
The same principles apply if the called program in turn calls another program in the cases
cited above.
This section on security concludes with an example using the sample application.
In the following example, a new database will be created along with two users hr_mgr
who will own a copy of the entire sample application in schema, hr_mgr; and
sales_mgr who will own a schema named, sales_mgr, that will have a copy of only
the emp table containing only the employees who work in sales.
The procedure list_emp, function hire_clerk, and package emp_admin will be used
in this example. All of the default privileges that are granted upon installation of the
sample application will be removed and then be explicitly re-granted so as to present a
more secure environment in this example.
Programs list_emp and hire_clerk will be changed from the default of definers
rights to invokers rights. It will be then illustrated that when sales_mgr runs these
programs, they act upon the emp table in sales_mgrs schema since sales_mgrs
search path and privileges will be used for name resolution and authorization checking.
definers rights. Since the default search path is in effect with the $user placeholder, the
schema matching the user (in this case, hr_mgr) is used to find the tables.
\c hr enterprisedb
CREATE USER hr_mgr IDENTIFIED BY password;
CREATE USER sales_mgr IDENTIFIED BY password;
\c - hr_mgr
\i /opt/edb/as10/installer/server/edb-sample.sql
BEGIN
CREATE TABLE
CREATE TABLE
CREATE TABLE
CREATE VIEW
CREATE SEQUENCE
.
.
.
CREATE PACKAGE
CREATE PACKAGE BODY
COMMIT
\c hr_mgr
GRANT USAGE ON SCHEMA hr_mgr TO sales_mgr;
\c sales_mgr
CREATE TABLE emp AS SELECT * FROM hr_mgr.emp WHERE job = 'SALESMAN';
In the above example, the GRANT USAGE ON SCHEMA command is given to allow
sales_mgr access into hr_mgrs schema to make a copy of hr_mgrs emp table. This
step is required in Advanced Server and is not compatible with Oracle databases since
Oracle does not have the concept of a schema that is distinct from its user.
Remove all privileges to later illustrate the minimum required privileges needed.
While connected as user, hr_mgr, add the AUTHID CURRENT_USER clause to the
list_emp program and resave it in Advanced Server. When performing this step, be
sure you are logged on as hr_mgr, otherwise the modified program may wind up in the
public schema instead of in hr_mgrs schema.
While connected as user, hr_mgr, add the AUTHID CURRENT_USER clause to the
hire_clerk program.
Also, after the BEGIN statement, fully qualify the reference, new_empno, to
hr_mgr.new_empno in order to ensure the hire_clerk function call to the
new_empno function resolves to the hr_mgr schema.
When resaving the program, be sure you are logged on as hr_mgr, otherwise the
modified program may wind up in the public schema instead of in hr_mgrs schema.
While connected as user, hr_mgr, grant the privileges needed so sales_mgr can
execute the list_emp procedure, hire_clerk function, and emp_admin package.
Note that the only data object sales_mgr has access to is the emp table in the
sales_mgr schema. sales_mgr has no privileges on any table in the hr_mgr schema.
\c sales_mgr
DECLARE
v_empno NUMBER(4);
BEGIN
hr_mgr.list_emp;
DBMS_OUTPUT.PUT_LINE('*** Adding new employee ***');
EMPNO ENAME
----- -------
7499 ALLEN
7521 WARD
7654 MARTIN
7844 TURNER
*** Adding new employee ***
Department : 40
Employee No: 8000
Name : JONES
Job : CLERK
Manager : 7782
Hire Date : 08-NOV-07 00:00:00
Salary : 950.00
*** After new employee added ***
EMPNO ENAME
----- -------
7499 ALLEN
7521 WARD
7654 MARTIN
7844 TURNER
8000 JONES
The table and sequence accessed by the programs of the anonymous block are illustrated
in the following diagram. The gray ovals represent the schemas of sales_mgr and
hr_mgr. The current user during each program execution is shown within parenthesis in
bold red font.
(sales_mgr)
BEGIN
hr_mgr.list_emp;
hr_mgr.hire_clerk
...
END;
hr_mgr
sales_mgr
list_emp hire_clerk
(sales_mgr) (sales_mgr)
new_empno
(hr_mgr)
Selecting from sales_mgrs emp table shows that the update was made in this table.
The following diagram shows that the SELECT command references the emp table in the
sales_mgr schema, but the dept table referenced by the get_dept_name function in
the emp_admin package is from the hr_mgr schema since the emp_admin package has
definers rights and is owned by hr_mgr. The default search path setting with the $user
placeholder resolves the access by user hr_mgr to the dept table in the hr_mgr schema.
(sales_mgr)
SELECT empno, ename...
hr_mgr.emp_admin.get_dept_name...
FROM sales_mgr.emp
hr_mgr
sales_mgr
emp_admin
(hr_mgr)
hire_emp
get_dept_name
While connected as user, sales_mgr, run the hire_emp procedure in the emp_admin
package.
EXEC hr_mgr.emp_admin.hire_emp(9001,
'ALICE','SALESMAN',8000,TRUNC(SYSDATE),1000,7369,40);
This diagram illustrates that the hire_emp procedure in the emp_admin definers rights
package updates the emp table belonging to hr_mgr since the object privileges of
hr_mgr are used, and the default search path setting with the $user placeholder resolves
to the schema of hr_mgr.
(sales_mgr)
EXEC hr_mgr.emp_admin.hire_emp...
hr_mgr
sales_mgr
emp_admin
(hr_mgr)
hire_emp
get_dept_name
Now connect as user, hr_mgr. The following SELECT command verifies that the new
employee was added to hr_mgrs emp table since the emp_admin package has definers
rights and hr_mgr is emp_admins owner.
\c hr_mgr
SELECT empno, ename, hiredate, sal, deptno,
hr_mgr.emp_admin.get_dept_name(deptno) FROM hr_mgr.emp;
Generally, all variables used in a block must be declared in the declaration section of the
block. A variable declaration consists of a name that is assigned to the variable and its
data type. Optionally, the variable can be initialized to a default value in the variable
declaration.
[ := expression ], if given, specifies the initial value assigned to the variable when the
block is entered. If the clause is not given then the variable is initialized to the SQL NULL
value.
The default value is evaluated every time the block is entered. So, for example, assigning
SYSDATE to a variable of type DATE causes the variable to have the time of the current
invocation, not the time when the procedure or function was precompiled.
The following procedure illustrates some variable declarations that utilize defaults
consisting of string and numeric expressions.
The following output of the above procedure shows that default values in the variable
declarations are indeed assigned to the variables.
EXEC dept_salary_rpt(20);
Often, variables will be declared in SPL programs that will be used to hold values from
tables in the database. In order to ensure compatibility between the table columns and the
SPL variables, the data types of the two should be the same.
However, as quite often happens, a change might be made to the table definition. If the
data type of the column is changed, the corresponding change may be required to the
variable in the SPL program.
Instead of coding the specific column data type into the variable declaration the column
attribute, %TYPE, can be used instead. A qualified column name in dot notation or the
name of a previously declared variable must be specified as a prefix to %TYPE. The data
type of the column or variable prefixed to %TYPE is assigned to the variable being
declared. If the data type of the given column or variable changes, the new data type will
be associated with the variable without the need to modify the declaration code.
Note: The %TYPE attribute can be used with formal parameter declarations as well.
name is the identifier assigned to the variable or formal parameter that is being declared.
column is the name of a column in table or view. variable is the name of a variable
that was declared prior to the variable identified by name.
Note: The variable does not inherit any of the columns other attributes such as might be
specified on the column with the NOT NULL clause or the DEFAULT clause.
In the following example a procedure queries the emp table using an employee number,
displays the employees data, finds the average salary of all employees in the department
to which the employee belongs, and then compares the chosen employees salary with the
department average.
Instead of the above, the procedure could be written as follows without explicitly coding
the emp table data types into the declaration section of the procedure.
v_avgsal illustrates the usage of %TYPE referring to another variable instead of a table
column.
EXEC emp_sal_query(7698);
Employee # : 7698
Name : BLAKE
Job : MANAGER
Hire Date : 01-MAY-81 00:00:00
Salary : 2850.00
Dept # : 30
Employee's salary is more than the department average of 1566.67
The %TYPE attribute provides an easy way to create a variable dependent upon a
columns data type. Using the %ROWTYPE attribute, you can define a record that contains
fields that correspond to all columns of a given table. Each field takes on the data type of
its corresponding column. The fields in the record do not inherit any of the columns
other attributes such as might be specified with the NOT NULL clause or the DEFAULT
clause.
You can use the %ROWTYPE attribute to declare a record. The %ROWTYPE attribute is
prefixed by a table name. Each column in the named table defines an identically named
field in the record with the same data type as the column.
record table%ROWTYPE;
record is an identifier assigned to the record. table is the name of a table (or view)
whose columns are to define the fields in the record. The following example shows how
the emp_sal_query procedure from the prior section can be modified to use
emp%ROWTYPE to create a record named r_emp instead of declaring individual variables
for the columns in emp.
Records can be declared based upon a table definition using the %ROWTYPE attribute as
shown in Section 3.3.3. This section describes how a new record structure can be defined
that is not tied to any particular table definition.
The TYPE IS RECORD statement is used to create the definition of a record type. A
record type is a definition of a record comprised of one or more identifiers and their
corresponding data types. A record type cannot, by itself, be used to manipulate data.
Where:
rec_type
field_name
data_type
DEFAULT default_value
The DEFAULT clause assigns a default data value for the corresponding field. The
data type of the default expression must match the data type of the column. If no
default is specified, then the default is NULL.
record rectype
record.field
record is a previously declared record variable and field is the identifier of a field
belonging to the record type from which record is defined.
The emp_sal_query is again modified this time using a user-defined record type and
record variable.
Note that instead of specifying data type names, the %TYPE attribute can be used for the
field data types in the record type definition.
EXEC emp_sal_query(7698);
Employee # : 7698
Name : BLAKE
Job : MANAGER
Hire Date : 01-MAY-81 00:00:00
Salary : 2850.00
Dept # : 30
Employee's salary is more than the department average of 1566.67
3.4.1 NULL
The simplest statement is the NULL statement. This statement is an executable statement
that does nothing.
NULL;
BEGIN
NULL;
END;
The NULL statement can act as a placeholder where an executable statement is required
such as in a branch of an IF-THEN-ELSE statement.
For example:
3.4.2 Assignment
The assignment statement sets a variable or a formal parameter of mode OUT or IN OUT
specified on the left side of the assignment, :=, to the evaluated expression specified on
the right side of the assignment.
variable := expression;
expression is an expression that produces a single value. The value produced by the
expression must have a compatible data type with that of variable.
The following example shows the typical use of assignment statements in the executable
section of the procedure.
DBMS_OUTPUT.PUT_LINE(rpt_title);
DBMS_OUTPUT.PUT_LINE('Base Annual Salary: ' || base_annual);
END;
The SELECT INTO statement is an SPL variation of the SQL SELECT command, the
differences being:
That SELECT INTO is designed to assign the results to variables or records where
they can then be used in SPL program statements.
The accessible result set of SELECT INTO is at most one row.
Other than the above, all of the clauses of the SELECT command such as WHERE, ORDER
BY, GROUP BY, HAVING, etc. are valid for SELECT INTO. The following are the two
variations of SELECT INTO.
If the query returns zero rows, null values are assigned to the target(s). If the query
returns multiple rows, the first row is assigned to the target(s) and the rest are discarded.
(Note that "the first row" is not well-defined unless youve used ORDER BY.)
Note: In either cases, where no row is returned or more than one row is returned, SPL
throws an exception.
Note: There is a variation of SELECT INTO using the BULK COLLECT clause that allows
a result set of more than one row that is returned into a collection. See Section 3.12.4.1
for more information on using the BULK COLLECT clause with the SELECT INTO
statement.
You can use the WHEN NO_DATA_FOUND clause in an EXCEPTION block to determine
whether the assignment was successful (that is, at least one row was returned by the
query).
This version of the emp_sal_query procedure uses the variation of SELECT INTO that
returns the result set into a record. Also note the addition of the EXCEPTION block
containing the WHEN NO_DATA_FOUND conditional expression.
If the query is executed with a non-existent employee number the results appear as
follows.
Another conditional clause of use in the EXCEPTION section with SELECT INTO is the
TOO_MANY_ROWS exception. If more than one row is selected by the SELECT INTO
statement an exception is thrown by SPL.
When the following block is executed, the TOO_MANY_ROWS exception is thrown since
there are many employees in the specified department.
DECLARE
v_ename emp.ename%TYPE;
BEGIN
SELECT ename INTO v_ename FROM emp WHERE deptno = 20 ORDER BY ename;
EXCEPTION
WHEN TOO_MANY_ROWS THEN
DBMS_OUTPUT.PUT_LINE('More than one employee found');
DBMS_OUTPUT.PUT_LINE('First employee returned is ' || v_ename);
END;
3.4.4 INSERT
The INSERT command available in the SQL language can also be used in SPL programs.
An expression in the SPL language can be used wherever an expression is allowed in the
SQL INSERT command. Thus, SPL variables and parameters can be used to supply
values to the insert operation.
DBMS_OUTPUT.PUT_LINE('Added employee...');
DBMS_OUTPUT.PUT_LINE('Employee # : ' || p_empno);
DBMS_OUTPUT.PUT_LINE('Name : ' || p_ename);
DBMS_OUTPUT.PUT_LINE('Job : ' || p_job);
DBMS_OUTPUT.PUT_LINE('Manager : ' || p_mgr);
DBMS_OUTPUT.PUT_LINE('Hire Date : ' || p_hiredate);
DBMS_OUTPUT.PUT_LINE('Salary : ' || p_sal);
DBMS_OUTPUT.PUT_LINE('Commission : ' || p_comm);
DBMS_OUTPUT.PUT_LINE('Dept # : ' || p_deptno);
DBMS_OUTPUT.PUT_LINE('----------------------');
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('OTHERS exception on INSERT of employee # '
|| p_empno);
DBMS_OUTPUT.PUT_LINE('SQLCODE : ' || SQLCODE);
DBMS_OUTPUT.PUT_LINE('SQLERRM : ' || SQLERRM);
END;
If an exception occurs all database changes made in the procedure are automatically
rolled back. In this example the EXCEPTION section with the WHEN OTHERS clause
catches all exceptions. Two variables are displayed. SQLCODE is a number that identifies
the specific exception that occurred. SQLERRM is a text message explaining the error. See
Section 3.5.7 for more information on exception handling.
Added employee...
Employee # : 9503
Name : PETERSON
Job : ANALYST
Manager : 7902
Hire Date : 31-MAR-05 00:00:00
Salary : 5000
Dept # : 40
----------------------
3.4.5 UPDATE
The UPDATE command available in the SQL language can also be used in SPL programs.
An expression in the SPL language can be used wherever an expression is allowed in the
SQL UPDATE command. Thus, SPL variables and parameters can be used to supply
values to the update operation.
IF SQL%FOUND THEN
DBMS_OUTPUT.PUT_LINE('Updated Employee # : ' || p_empno);
DBMS_OUTPUT.PUT_LINE('New Salary : ' || p_sal);
DBMS_OUTPUT.PUT_LINE('New Commission : ' || p_comm);
ELSE
DBMS_OUTPUT.PUT_LINE('Employee # ' || p_empno || ' not found');
END IF;
END;
The following shows the update on the employee using this procedure.
EXEC emp_comp_update(9503, 6540, 1200);
3.4.6 DELETE
The DELETE command (available in the SQL language) can also be used in SPL
programs.
An expression in the SPL language can be used wherever an expression is allowed in the
SQL DELETE command. Thus, SPL variables and parameters can be used to supply
values to the delete operation.
IF SQL%FOUND THEN
DBMS_OUTPUT.PUT_LINE('Deleted Employee # : ' || p_empno);
ELSE
DBMS_OUTPUT.PUT_LINE('Employee # ' || p_empno || ' not found');
END IF;
END;
EXEC emp_delete(9503);
The INSERT, UPDATE, and DELETE commands may be appended by the optional
RETURNING INTO clause. This clause allows the SPL program to capture the newly
added, modified, or deleted values from the results of an INSERT, UPDATE, or DELETE
command, respectively.
If the INSERT, UPDATE, or DELETE command returns a result set with more than one
row, then an exception is thrown with SQLCODE 01422, query returned more than
one row. If no rows are in the result set, then the variables following the INTO keyword
are set to null.
Note: There is a variation of RETURNING INTO using the BULK COLLECT clause that
allows a result set of more than one row that is returned into a collection. See Section
3.12.4 for more information on the BULK COLLECT clause.
The following is the output from this procedure (assuming employee 9503 created by the
emp_insert procedure still exists within the table).
IF SQL%FOUND THEN
DBMS_OUTPUT.PUT_LINE('Deleted Employee # : ' || r_emp.empno);
DBMS_OUTPUT.PUT_LINE('Name : ' || r_emp.ename);
DBMS_OUTPUT.PUT_LINE('Job : ' || r_emp.job);
DBMS_OUTPUT.PUT_LINE('Manager : ' || r_emp.mgr);
DBMS_OUTPUT.PUT_LINE('Hire Date : ' || r_emp.hiredate);
DBMS_OUTPUT.PUT_LINE('Salary : ' || r_emp.sal);
DBMS_OUTPUT.PUT_LINE('Commission : ' || r_emp.comm);
DBMS_OUTPUT.PUT_LINE('Department : ' || r_emp.deptno);
ELSE
DBMS_OUTPUT.PUT_LINE('Employee # ' || p_empno || ' not found');
END IF;
END;
EXEC emp_delete(9503);
There are several attributes that can be used to determine the effect of a command.
SQL%FOUND is a Boolean that returns TRUE if at least one row was affected by an
INSERT, UPDATE or DELETE command or a SELECT INTO command retrieved one or
more rows.
The following anonymous block inserts a row and then displays the fact that the row has
been inserted.
BEGIN
INSERT INTO emp (empno,ename,job,sal,deptno) VALUES (
9001, 'JONES', 'CLERK', 850.00, 40);
IF SQL%FOUND THEN
DBMS_OUTPUT.PUT_LINE('Row has been inserted');
END IF;
END;
BEGIN
UPDATE emp SET hiredate = '03-JUN-07' WHERE empno = 9001;
DBMS_OUTPUT.PUT_LINE('# rows updated: ' || SQL%ROWCOUNT);
END;
# rows updated: 1
BEGIN
UPDATE emp SET hiredate = '03-JUN-07' WHERE empno = 9000;
IF SQL%NOTFOUND THEN
DBMS_OUTPUT.PUT_LINE('No rows were updated');
END IF;
END;
3.5.1 IF Statement
IF statements let you execute commands based on certain conditions. SPL has four forms
of IF:
IF ... THEN
IF ... THEN ... ELSE
IF ... THEN ... ELSE IF
IF ... THEN ... ELSIF ... THEN ... ELSE
3.5.1.1 IF-THEN
IF boolean-expression THEN
statements
END IF;
IF-THEN statements are the simplest form of IF. The statements between THEN and END
IF will be executed if the condition is TRUE. Otherwise, they are skipped.
In the following example an IF-THEN statement is used to test and display employees
who have a commission.
DECLARE
v_empno emp.empno%TYPE;
v_comm emp.comm%TYPE;
CURSOR emp_cursor IS SELECT empno, comm FROM emp;
BEGIN
OPEN emp_cursor;
DBMS_OUTPUT.PUT_LINE('EMPNO COMM');
DBMS_OUTPUT.PUT_LINE('----- -------');
LOOP
FETCH emp_cursor INTO v_empno, v_comm;
EXIT WHEN emp_cursor%NOTFOUND;
--
-- Test whether or not the employee gets a commission
--
IF v_comm IS NOT NULL AND v_comm > 0 THEN
DBMS_OUTPUT.PUT_LINE(v_empno || ' ' ||
TO_CHAR(v_comm,'$99999.99'));
END IF;
END LOOP;
CLOSE emp_cursor;
END;
EMPNO COMM
----- -------
7499 $300.00
7521 $500.00
7654 $1400.00
3.5.1.2 IF-THEN-ELSE
IF boolean-expression THEN
statements
ELSE
statements
END IF;
DECLARE
v_empno emp.empno%TYPE;
v_comm emp.comm%TYPE;
CURSOR emp_cursor IS SELECT empno, comm FROM emp;
BEGIN
OPEN emp_cursor;
DBMS_OUTPUT.PUT_LINE('EMPNO COMM');
DBMS_OUTPUT.PUT_LINE('----- -------');
LOOP
FETCH emp_cursor INTO v_empno, v_comm;
EXIT WHEN emp_cursor%NOTFOUND;
--
-- Test whether or not the employee gets a commission
--
IF v_comm IS NOT NULL AND v_comm > 0 THEN
DBMS_OUTPUT.PUT_LINE(v_empno || ' ' ||
TO_CHAR(v_comm,'$99999.99'));
ELSE
DBMS_OUTPUT.PUT_LINE(v_empno || ' ' || 'Non-commission');
END IF;
END LOOP;
CLOSE emp_cursor;
END;
EMPNO COMM
----- -------
7369 Non-commission
7499 $ 300.00
7521 $ 500.00
7566 Non-commission
7654 $ 1400.00
3.5.1.3 IF-THEN-ELSE IF
In the following example the outer IF-THEN-ELSE statement tests whether or not an
employee has a commission. The inner IF-THEN-ELSE statements then test whether the
employees total compensation exceeds or is less than the company average.
DECLARE
v_empno emp.empno%TYPE;
v_sal emp.sal%TYPE;
v_comm emp.comm%TYPE;
v_avg NUMBER(7,2);
CURSOR emp_cursor IS SELECT empno, sal, comm FROM emp;
BEGIN
--
-- Calculate the average yearly compensation in the company
--
SELECT AVG((sal + NVL(comm,0)) * 24) INTO v_avg FROM emp;
DBMS_OUTPUT.PUT_LINE('Average Yearly Compensation: ' ||
TO_CHAR(v_avg,'$999,999.99'));
OPEN emp_cursor;
DBMS_OUTPUT.PUT_LINE('EMPNO YEARLY COMP');
DBMS_OUTPUT.PUT_LINE('----- -----------');
LOOP
FETCH emp_cursor INTO v_empno, v_sal, v_comm;
EXIT WHEN emp_cursor%NOTFOUND;
--
-- Test whether or not the employee gets a commission
--
IF v_comm IS NOT NULL AND v_comm > 0 THEN
--
-- Test if the employee's compensation with commission exceeds the average
--
IF (v_sal + v_comm) * 24 > v_avg THEN
DBMS_OUTPUT.PUT_LINE(v_empno || ' ' ||
TO_CHAR((v_sal + v_comm) * 24,'$999,999.99') ||
' Exceeds Average');
ELSE
DBMS_OUTPUT.PUT_LINE(v_empno || ' ' ||
TO_CHAR((v_sal + v_comm) * 24,'$999,999.99') ||
' Below Average');
END IF;
ELSE
--
-- Test if the employee's compensation without commission exceeds the
average
--
Note: The logic in this program can be simplified considerably by calculating the
employees yearly compensation using the NVL function within the SELECT command of
the cursor declaration, however, the purpose of this example is to demonstrate how IF
statements can be used.
When you use this form, you are actually nesting an IF statement inside the ELSE part of
an outer IF statement. Thus you need one END IF statement for each nested IF and one
for the parent IF-ELSE.
3.5.1.4 IF-THEN-ELSIF-ELSE
IF boolean-expression THEN
statements
[ ELSIF boolean-expression THEN
statements
[ ELSIF boolean-expression THEN
statements ] ...]
[ ELSE
statements ]
END IF;
DECLARE
v_empno emp.empno%TYPE;
v_comp NUMBER(8,2);
v_lt_25K SMALLINT := 0;
v_25K_50K SMALLINT := 0;
v_50K_75K SMALLINT := 0;
v_75K_100K SMALLINT := 0;
v_ge_100K SMALLINT := 0;
CURSOR emp_cursor IS SELECT empno, (sal + NVL(comm,0)) * 24 FROM emp;
BEGIN
OPEN emp_cursor;
LOOP
FETCH emp_cursor INTO v_empno, v_comp;
EXIT WHEN emp_cursor%NOTFOUND;
IF v_comp < 25000 THEN
v_lt_25K := v_lt_25K + 1;
ELSIF v_comp < 50000 THEN
v_25K_50K := v_25K_50K + 1;
ELSIF v_comp < 75000 THEN
v_50K_75K := v_50K_75K + 1;
ELSIF v_comp < 100000 THEN
v_75K_100K := v_75K_100K + 1;
ELSE
v_ge_100K := v_ge_100K + 1;
END IF;
END LOOP;
CLOSE emp_cursor;
DBMS_OUTPUT.PUT_LINE('Number of employees by yearly compensation');
DBMS_OUTPUT.PUT_LINE('Less than 25,000 : ' || v_lt_25K);
DBMS_OUTPUT.PUT_LINE('25,000 - 49,9999 : ' || v_25K_50K);
DBMS_OUTPUT.PUT_LINE('50,000 - 74,9999 : ' || v_50K_75K);
DBMS_OUTPUT.PUT_LINE('75,000 - 99,9999 : ' || v_75K_100K);
DBMS_OUTPUT.PUT_LINE('100,000 and over : ' || v_ge_100K);
END;
The RETURN statement terminates the current function, procedure or anonymous block
and returns control to the caller.
There are two forms of the RETURN Statement. The first form of the RETURN statement is
used to terminate a procedure or function that returns void. The syntax of the first form
is:
RETURN;
The second form of RETURN returns a value to the caller. The syntax of the second form
of the RETURN statement is:
RETURN expression;
expression must evaluate to the same data type as the return type of the function.
The following example uses the RETURN statement returns a value to the caller:
The GOTO statement causes the point of execution to jump to the statement with the
specified label. The syntax of a GOTO statement is:
GOTO label
label is a name assigned to an executable statement. label must be unique within the
scope of the function, procedure or anonymous block.
<<label>> statement
You can label assignment statements, any SQL statement (like INSERT, UPDATE,
CREATE, etc.) and selected procedural language statements. The procedural language
statements that can be labeled are:
IF
EXIT
RETURN
RAISE
EXECUTE
PERFORM
GET DIAGNOSTICS
OPEN
FETCH
MOVE
CLOSE
NULL
COMMIT
ROLLBACK
GOTO
CASE
LOOP
WHILE
FOR
Please note that exit is considered a keyword, and cannot be used as the name of a label.
GOTO statements cannot transfer control into a conditional block or sub-block, but can
transfer control from a conditional block or sub-block.
The following example verifies that an employee record contains a name, job description,
and employee hire date; if any piece of information is missing, a GOTO statement transfers
the point of execution to a statement that prints a message that the employee is not valid.
The CASE expression returns a value that is substituted where the CASE expression is
located within an expression.
There are two formats of the CASE expression - one that is called a searched CASE and
the other that uses a selector.
The selector CASE expression attempts to match an expression called the selector to the
expression specified in one or more WHEN clauses. result is an expression that is type-
compatible in the context where the CASE expression is used. If a match is found, the
value given in the corresponding THEN clause is returned by the CASE expression. If there
are no matches, the value following ELSE is returned. If ELSE is omitted, the CASE
expression returns null.
CASE selector-expression
WHEN match-expression THEN
result
[ WHEN match-expression THEN
result
[ WHEN match-expression THEN
result ] ...]
[ ELSE
result ]
END;
The following example uses a selector CASE expression to assign the department name to
a variable based upon the department number.
DECLARE
v_empno emp.empno%TYPE;
v_ename emp.ename%TYPE;
v_deptno emp.deptno%TYPE;
v_dname dept.dname%TYPE;
CURSOR emp_cursor IS SELECT empno, ename, deptno FROM emp;
BEGIN
OPEN emp_cursor;
A searched CASE expression uses one or more Boolean expressions to determine the
resulting value to return.
evaluates to TRUE, result in the corresponding THEN clause is returned as the value of
the CASE expression. If none of boolean-expression evaluates to true then result
following ELSE is returned. If no ELSE is specified, the CASE expression returns null.
The following example uses a searched CASE expression to assign the department name
to a variable based upon the department number.
DECLARE
v_empno emp.empno%TYPE;
v_ename emp.ename%TYPE;
v_deptno emp.deptno%TYPE;
v_dname dept.dname%TYPE;
CURSOR emp_cursor IS SELECT empno, ename, deptno FROM emp;
BEGIN
OPEN emp_cursor;
DBMS_OUTPUT.PUT_LINE('EMPNO ENAME DEPTNO DNAME');
DBMS_OUTPUT.PUT_LINE('----- ------- ------ ----------');
LOOP
FETCH emp_cursor INTO v_empno, v_ename, v_deptno;
EXIT WHEN emp_cursor%NOTFOUND;
v_dname :=
CASE
WHEN v_deptno = 10 THEN 'Accounting'
WHEN v_deptno = 20 THEN 'Research'
WHEN v_deptno = 30 THEN 'Sales'
WHEN v_deptno = 40 THEN 'Operations'
ELSE 'unknown'
END;
DBMS_OUTPUT.PUT_LINE(v_empno || ' ' || RPAD(v_ename, 10) ||
' ' || v_deptno || ' ' || v_dname);
END LOOP;
CLOSE emp_cursor;
END;
The CASE statement executes a set of one or more statements when a specified search
condition is TRUE. The CASE statement is a stand-alone statement in itself while the
previously discussed CASE expression must appear as part of an expression.
There are two formats of the CASE statement - one that is called a searched CASE and the
other that uses a selector.
The selector CASE statement attempts to match an expression called the selector to the
expression specified in one or more WHEN clauses. When a match is found one or more
corresponding statements are executed.
CASE selector-expression
WHEN match-expression THEN
statements
[ WHEN match-expression THEN
statements
[ WHEN match-expression THEN
statements ] ...]
[ ELSE
statements ]
END CASE;
The following example uses a selector CASE statement to assign a department name and
location to a variable based upon the department number.
DECLARE
v_empno emp.empno%TYPE;
v_ename emp.ename%TYPE;
v_deptno emp.deptno%TYPE;
v_dname dept.dname%TYPE;
v_loc dept.loc%TYPE;
CURSOR emp_cursor IS SELECT empno, ename, deptno FROM emp;
BEGIN
A searched CASE statement uses one or more Boolean expressions to determine the
resulting set of statements to execute.
The following example uses a searched CASE statement to assign a department name and
location to a variable based upon the department number.
DECLARE
v_empno emp.empno%TYPE;
v_ename emp.ename%TYPE;
v_deptno emp.deptno%TYPE;
v_dname dept.dname%TYPE;
v_loc dept.loc%TYPE;
CURSOR emp_cursor IS SELECT empno, ename, deptno FROM emp;
BEGIN
OPEN emp_cursor;
DBMS_OUTPUT.PUT_LINE('EMPNO ENAME DEPTNO DNAME '
|| ' LOC');
DBMS_OUTPUT.PUT_LINE('----- ------- ------ ----------'
|| ' ---------');
LOOP
FETCH emp_cursor INTO v_empno, v_ename, v_deptno;
EXIT WHEN emp_cursor%NOTFOUND;
CASE
WHEN v_deptno = 10 THEN v_dname := 'Accounting';
v_loc := 'New York';
WHEN v_deptno = 20 THEN v_dname := 'Research';
v_loc := 'Dallas';
WHEN v_deptno = 30 THEN v_dname := 'Sales';
v_loc := 'Chicago';
WHEN v_deptno = 40 THEN v_dname := 'Operations';
v_loc := 'Boston';
ELSE v_dname := 'unknown';
v_loc := '';
END CASE;
DBMS_OUTPUT.PUT_LINE(v_empno || ' ' || RPAD(v_ename, 10) ||
' ' || v_deptno || ' ' || RPAD(v_dname, 14) || ' ' ||
v_loc);
END LOOP;
CLOSE emp_cursor;
END;
3.5.6 Loops
With the LOOP, EXIT, CONTINUE, WHILE, and FOR statements, you can arrange for your
SPL program to repeat a series of commands.
3.5.6.1 LOOP
LOOP
statements
END LOOP;
3.5.6.2 EXIT
EXIT [ WHEN expression ];
The innermost loop is terminated and the statement following END LOOP is executed
next.
If WHEN is present, loop exit occurs only if the specified condition is TRUE, otherwise
control passes to the statement after EXIT.
EXIT can be used to cause early exit from all types of loops; it is not limited to use with
unconditional loops.
The following is a simple example of a loop that iterates ten times and then uses the EXIT
statement to terminate.
DECLARE
v_counter NUMBER(2);
BEGIN
v_counter := 1;
LOOP
EXIT WHEN v_counter > 10;
DBMS_OUTPUT.PUT_LINE('Iteration # ' || v_counter);
v_counter := v_counter + 1;
END LOOP;
END;
Iteration # 1
Iteration # 2
Iteration # 3
Iteration # 4
Iteration # 5
Iteration # 6
Iteration # 7
3.5.6.3 CONTINUE
The CONTINUE statement provides a way to proceed with the next iteration of a loop
while skipping intervening statements.
When the CONTINUE statement is encountered, the next iteration of the innermost loop is
begun, skipping all statements following the CONTINUE statement until the end of the
loop. That is, control is passed back to the loop control expression, if any, and the body
of the loop is re-evaluated.
If the WHEN clause is used, then the next iteration of the loop is begun only if the specified
expression in the WHEN clause evaluates to TRUE. Otherwise, control is passed to the next
statement following the CONTINUE statement.
The following is a variation of the previous example that uses the CONTINUE statement to
skip the display of the odd numbers.
DECLARE
v_counter NUMBER(2);
BEGIN
v_counter := 0;
LOOP
v_counter := v_counter + 1;
EXIT WHEN v_counter > 10;
CONTINUE WHEN MOD(v_counter,2) = 1;
DBMS_OUTPUT.PUT_LINE('Iteration # ' || v_counter);
END LOOP;
END;
Iteration # 2
Iteration # 4
Iteration # 6
Iteration # 8
Iteration # 10
3.5.6.4 WHILE
WHILE expression LOOP
statements
END LOOP;
The following example contains the same logic as in the previous example except the
WHILE statement is used to take the place of the EXIT statement to determine when to
exit the loop.
Note: The conditional expression used to determine when to exit the loop must be
altered. The EXIT statement terminates the loop when its conditional expression is true.
The WHILE statement terminates (or never begins the loop) when its conditional
expression is false.
DECLARE
v_counter NUMBER(2);
BEGIN
v_counter := 1;
WHILE v_counter <= 10 LOOP
DBMS_OUTPUT.PUT_LINE('Iteration # ' || v_counter);
v_counter := v_counter + 1;
END LOOP;
END;
Iteration # 1
Iteration # 2
Iteration # 3
Iteration # 4
Iteration # 5
Iteration # 6
Iteration # 7
Iteration # 8
Iteration # 9
Iteration # 10
This form of FOR creates a loop that iterates over a range of integer values. The variable
name is automatically defined as type INTEGER and exists only inside the loop. The two
expressions giving the loop range are evaluated once when entering the loop. The
iteration step is +1 and name begins with the value of expression to the left of .. and
terminates once name exceeds the value of expression to the right of ... Thus the two
expressions take on the following roles: start-value .. end-value.
The optional REVERSE clause specifies that the loop should iterate in reverse order. The
first time through the loop, name is set to the value of the right-most expression; the
loop terminates when the name is less than the left-most expression.
The following example simplifies the WHILE loop example even further by using a FOR
loop that iterates from 1 to 10.
Iteration # 1
Iteration # 2
Iteration # 3
Iteration # 4
Iteration # 5
Iteration # 6
Iteration # 7
Iteration # 8
Iteration # 9
Iteration # 10
If the start value is greater than the end value the loop body is not executed at all. No
error is raised as shown by the following example.
BEGIN
FOR i IN 10 .. 1 LOOP
DBMS_OUTPUT.PUT_LINE('Iteration # ' || i);
END LOOP;
END;
There is no output from this example as the loop body is never executed.
Note: SPL also supports CURSOR FOR loops (see Section 3.8.7).
By default, any error occurring in an SPL program aborts execution of the program. You
can trap errors and recover from them by using a BEGIN block with an EXCEPTION
section. The syntax is an extension of the normal syntax for a BEGIN block:
[ DECLARE
declarations ]
BEGIN
statements
EXCEPTION
WHEN condition [ OR condition ]... THEN
handler_statements
[ WHEN condition [ OR condition ]... THEN
handler_statements ]...
END;
If no error occurs, this form of block simply executes all the statements, and then
control passes to the next statement after END. If an error occurs within the
statements, further processing of the statements is abandoned, and control passes to
the EXCEPTION list. The list is searched for the first condition matching the error that
occurred. If a match is found, the corresponding handler_statements are executed,
and then control passes to the next statement after END. If no match is found, the error
propagates out as though the EXCEPTION clause were not there at all. The error can be
caught by an enclosing block with EXCEPTION; if there is no enclosing block, it aborts
processing of the subprogram.
The special condition name OTHERS matches every error type. Condition names are not
case-sensitive.
The following table lists the condition names that may be used:
Note: Condition names INVALID_NUMBER and VALUE_ERROR are not compatible with
Oracle databases for which these condition names are for exceptions resulting only from
a failed conversion of a string to a numeric literal. In addition, for Oracle databases, an
INVALID_NUMBER exception is applicable only to SQL statements while a
VALUE_ERROR exception is applicable only to procedural statements.
Any number of errors (referred to in PL/SQL as exceptions) can occur during program
execution. When an exception is thrown, normal execution of the program stops, and
control of the program transfers to the error-handling portion of the program. An
exception may be a pre-defined error that is generated by the server, or may be a logical
error that raises a user-defined exception.
User-defined exceptions are never raised by the server; they are raised explicitly by a
RAISE statement. A user-defined exception is raised when a developer-defined logical
rule is broken; a common example of a logical rule being broken occurs when a check is
presented against an account with insufficient funds. An attempt to cash a check against
an account with insufficient funds will provoke a user-defined exception.
Before implementing a user-defined exception, you must declare the exception in the
declaration section of a function, procedure, package or anonymous block. You can then
raise the exception using the RAISE statement:
DECLARE
exception_name EXCEPTION;
BEGIN
...
RAISE exception_name;
...
END;
Unhandled exceptions propagate back through the call stack. If the exception remains
unhandled, the exception is eventually reported to the client application.
User-defined exceptions declared in a block are considered to be local to that block, and
global to any nested blocks within the block. To reference an exception that resides in an
outer block, you must assign a label to the outer block; then, preface the name of the
exception with the block name:
block_name.exception_name
inventory_control.out_of_stock
EXCEPTION
WHEN ar.overdrawn THEN
raise_credit_limit(customerid, amount*1.5);
The exception handler raises the customers credit limit and ends. When the exception
handler ends, execution resumes with the statement that follows ar.check_balance.
PRAGMA EXCEPTION_INIT(exception_name,
{exception_number | exception_code})
Where:
https://www.postgresql.org/docs/10/static/errcodes-appendix.html
EXCEPTION
WHEN ar.overdrawn THEN
DBMS_OUTPUT.PUT_LINE ('This account is overdrawn.');
DBMS_OUTPUT.PUT_LINE ('SQLCode :'||SQLCODE||' '||SQLERRM );
The following example demonstrates using a pre-defined exception. The code creates a
more meaningful name for the no_data_found exception; if the given customer
does not exist, the code catches the exception, calls DBMS_OUTPUT.PUT_LINE to report
the error, and then re-raises the original exception:
3.5.10 RAISE_APPLICATION_ERROR
RAISE_APPLICATION_ERROR(error_number, message);
Where:
For additional information on the SQLCODE and SQLERRM variables, see Section 3.13,
Errors and Messages.
The following shows the output in a case where the manager number is missing from an
employee record.
EXEC verify_emp(7839);
SQLCODE: -20030
SQLERRM: EDB-20030: No manager for 7839
A common example in banking is a funds transfer between two accounts. The two parts
of the transaction are the withdrawal of funds from one account, and the deposit of the
funds in another account. Both parts of this transaction must occur otherwise the banks
books will be out of balance. The deposit and withdrawal are one transaction.
An SPL application can be created that uses a style of transaction control compatible with
Oracle databases if the following conditions are met:
A transaction begins when the first SQL command is encountered in the SPL program.
All subsequent SQL commands are included as part of that transaction. The transaction
ends when one of the following occurs:
An unhandled exception occurs in which case the effects of all database updates
made during the transaction are rolled back and the transaction is aborted.
A COMMIT command is encountered in which case the effect of all database
updates made during the transaction become permanent.
A ROLLBACK command is encountered in which case the effects of all database
updates made during the transaction are rolled back and the transaction is aborted.
If a new SQL command is encountered, a new transaction begins.
Control returns to the calling application (such as Java, PSQL, etc.) in which case
the action of the application determines whether the transaction is committed or
rolled back.
Note: Unlike Oracle, DDL commands such as CREATE TABLE do not implicitly occur
within their own transaction. Therefore, DDL commands do not automatically cause an
immediate database commit as in Oracle, and DDL commands may be rolled back just
like DML commands.
A transaction may span one or more BEGIN/END blocks, or a single BEGIN/END block
may contain one or more transactions.
The following sections discuss the COMMIT and ROLLBACK commands in more detail.
3.6.1 COMMIT
The COMMIT command makes all database updates made during the current transaction
permanent, and ends the current transaction.
COMMIT [ WORK ];
The COMMIT command may be used within anonymous blocks, stored procedures, or
functions. Within an SPL program, it may appear in the executable section and/or the
exception section.
In the following example, the third INSERT command in the anonymous block results in
an error. The effect of the first two INSERT commands are retained as shown by the first
SELECT command. Even after issuing a ROLLBACK command, the two rows remain in the
table as shown by the second SELECT command verifying that they were indeed
committed.
BEGIN
INSERT INTO dept VALUES (50, 'FINANCE', 'DALLAS');
INSERT INTO dept VALUES (60, 'MARKETING', 'CHICAGO');
COMMIT;
INSERT INTO dept VALUES (70, 'HUMAN RESOURCES', 'CHICAGO');
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('SQLERRM: ' || SQLERRM);
DBMS_OUTPUT.PUT_LINE('SQLCODE: ' || SQLCODE);
END;
ROLLBACK;
3.6.2 ROLLBACK
The ROLLBACK command undoes all database updates made during the current
transaction, and ends the current transaction.
ROLLBACK [ WORK ];
The ROLLBACK command may be used within anonymous blocks, stored procedures, or
functions. Within an SPL program, it may appear in the executable section and/or the
exception section.
In the following example, the exception section contains a ROLLBACK command. Even
though the first two INSERT commands are executed successfully, the third results in an
exception that results in the rollback of all the INSERT commands in the anonymous
block.
BEGIN
INSERT INTO dept VALUES (50, 'FINANCE', 'DALLAS');
INSERT INTO dept VALUES (60, 'MARKETING', 'CHICAGO');
INSERT INTO dept VALUES (70, 'HUMAN RESOURCES', 'CHICAGO');
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
DBMS_OUTPUT.PUT_LINE('SQLERRM: ' || SQLERRM);
DBMS_OUTPUT.PUT_LINE('SQLCODE: ' || SQLCODE);
END;
The following is a more complex example using both COMMIT and ROLLBACK. First, the
following stored procedure is created which inserts a new employee.
DBMS_OUTPUT.PUT_LINE('Added employee...');
DBMS_OUTPUT.PUT_LINE('Employee # : ' || p_empno);
DBMS_OUTPUT.PUT_LINE('Name : ' || p_ename);
DBMS_OUTPUT.PUT_LINE('Job : ' || p_job);
DBMS_OUTPUT.PUT_LINE('Manager : ' || p_mgr);
DBMS_OUTPUT.PUT_LINE('Hire Date : ' || p_hiredate);
DBMS_OUTPUT.PUT_LINE('Salary : ' || p_sal);
DBMS_OUTPUT.PUT_LINE('Commission : ' || p_comm);
DBMS_OUTPUT.PUT_LINE('Dept # : ' || p_deptno);
DBMS_OUTPUT.PUT_LINE('----------------------');
END;
Note that this procedure has no exception section so any error that may occur is
propagated up to the calling program.
The following anonymous block is run. Note the use of the COMMIT command after all
calls to the emp_insert procedure and the ROLLBACK command in the exception
section.
BEGIN
emp_insert(9601,'FARRELL','ANALYST',7902,'03-MAR-08',5000,NULL,40);
emp_insert(9602,'TYLER','ANALYST',7900,'25-JAN-08',4800,NULL,40);
COMMIT;
EXCEPTION
Added employee...
Employee # : 9601
Name : FARRELL
Job : ANALYST
Manager : 7902
Hire Date : 03-MAR-08 00:00:00
Salary : 5000
Commission :
Dept # : 40
----------------------
Added employee...
Employee # : 9602
Name : TYLER
Job : ANALYST
Manager : 7900
Hire Date : 25-JAN-08 00:00:00
Salary : 4800
Commission :
Dept # : 40
----------------------
The following SELECT command shows that employees Farrell and Tyler were
successfully added.
SELECT * FROM emp WHERE empno > 9600;
BEGIN
emp_insert(9603,'HARRISON','SALESMAN',7902,'13-DEC-07',5000,3000,20);
emp_insert(9604,'JARVIS','SALESMAN',7902,'05-MAY-08',4800,4100,11);
COMMIT;
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('SQLERRM: ' || SQLERRM);
DBMS_OUTPUT.PUT_LINE('An error occurred - roll back inserts');
ROLLBACK;
END;
Added employee...
Employee # : 9603
Name : HARRISON
Job : SALESMAN
Manager : 7902
Hire Date : 13-DEC-07 00:00:00
Salary : 5000
Commission : 3000
Dept # : 20
----------------------
The ROLLBACK command in the exception section successfully undoes the insert of
employee Harrison. Also note that employees Farrell and Tyler are still in the table as
their inserts were made permanent by the COMMIT command in the first anonymous
block.
In addition, dynamic SQL is the only method by which data definition commands, such
as CREATE TABLE, can be executed from within an SPL program.
Note, however, that the runtime performance of dynamic SQL will be slower than static
SQL.
The following example shows basic dynamic SQL commands as string literals.
DECLARE
v_sql VARCHAR2(50);
BEGIN
EXECUTE IMMEDIATE 'CREATE TABLE job (jobno NUMBER(3),' ||
The following example illustrates the USING clause to pass values to placeholders in the
SQL string.
DECLARE
v_sql VARCHAR2(50) := 'INSERT INTO job VALUES ' ||
'(:p_jobno, :p_jname)';
v_jobno job.jobno%TYPE;
v_jname job.jname%TYPE;
BEGIN
v_jobno := 300;
v_jname := 'MANAGER';
EXECUTE IMMEDIATE v_sql USING v_jobno, v_jname;
v_jobno := 400;
v_jname := 'SALESMAN';
EXECUTE IMMEDIATE v_sql USING v_jobno, v_jname;
v_jobno := 500;
v_jname := 'PRESIDENT';
EXECUTE IMMEDIATE v_sql USING v_jobno, v_jname;
END;
The following example shows both the INTO and USING clauses. Note the last execution
of the SELECT command returns the results into a record instead of individual variables.
DECLARE
v_sql VARCHAR2(60);
v_jobno job.jobno%TYPE;
v_jname job.jname%TYPE;
r_job job%ROWTYPE;
BEGIN
DBMS_OUTPUT.PUT_LINE('JOBNO JNAME');
DBMS_OUTPUT.PUT_LINE('----- -------');
v_sql := 'SELECT jobno, jname FROM job WHERE jobno = :p_jobno';
EXECUTE IMMEDIATE v_sql INTO v_jobno, v_jname USING 100;
DBMS_OUTPUT.PUT_LINE(v_jobno || ' ' || v_jname);
EXECUTE IMMEDIATE v_sql INTO v_jobno, v_jname USING 200;
DBMS_OUTPUT.PUT_LINE(v_jobno || ' ' || v_jname);
EXECUTE IMMEDIATE v_sql INTO v_jobno, v_jname USING 300;
DBMS_OUTPUT.PUT_LINE(v_jobno || ' ' || v_jname);
EXECUTE IMMEDIATE v_sql INTO v_jobno, v_jname USING 400;
DBMS_OUTPUT.PUT_LINE(v_jobno || ' ' || v_jname);
EXECUTE IMMEDIATE v_sql INTO r_job USING 500;
DBMS_OUTPUT.PUT_LINE(r_job.jobno || ' ' || r_job.jname);
END;
JOBNO JNAME
----- -------
100 ANALYST
200 CLERK
300 MANAGER
400 SALESMAN
You can use the BULK COLLECT clause to assemble the result set from an EXECUTE
IMMEDIATE statement into a named collection. See Section 3.12.4, EXECUTE
IMMEDIATE BULK COLLECT for information about using the BULK COLLECT clause.
Cursors are most often used in the context of a FOR or WHILE loop. A conditional test
should be included in the SPL logic that detects when the end of the result set has been
reached so the program can exit the loop.
In order to use a cursor, it must first be declared in the declaration section of the SPL
program. A cursor declaration appears as follows:
name is an identifier that will be used to reference the cursor and its result set later in the
program. query is a SQL SELECT command that determines the result set retrievable by
the cursor.
Note: An extension of this syntax allows the use of parameters. This is discussed in more
detail in Section 3.8.8.
Before a cursor can be used to retrieve rows, it must first be opened. This is accomplished
with the OPEN statement.
OPEN name;
name is the identifier of a cursor that has been previously declared in the declaration
section of the SPL program. The OPEN statement must not be executed on a cursor that
has already been, and still is open.
The following shows an OPEN statement with its corresponding cursor declaration.
Once a cursor has been opened, rows can be retrieved from the cursors result set by
using the FETCH statement.
Note: There is a variation of FETCH INTO using the BULK COLLECT clause that can
return multiple rows at a time into a collection. See Section 3.12.4 for more information
on using the BULK COLLECT clause with the FETCH INTO statement.
Instead of explicitly declaring the data type of a target variable, %TYPE can be used
instead. In this way, if the data type of the database column is changed, the target variable
declaration in the SPL program does not have to be changed. %TYPE will automatically
pick up the new data type of the specified column.
If all the columns in a table are retrieved in the order defined in the table, %ROWTYPE can
be used to define a record into which the FETCH statement will place the retrieved data.
Each field within the record can then be accessed using dot notation.
Once all the desired rows have been retrieved from the cursor result set, the cursor must
be closed. Once closed, the result set is no longer accessible. The CLOSE statement
appears as follows:
CLOSE name;
name is the identifier of a cursor that is currently open. Once a cursor is closed, it must
not be closed again. However, once the cursor is closed, the OPEN statement can be
issued again on the closed cursor and the query result set will be rebuilt after which the
FETCH statement can then be used to retrieve the rows of the new result set.
This procedure produces the following output when invoked. Employee number 7369,
SMITH is the first row of the result set.
EXEC cursor_example;
Using the %ROWTYPE attribute, a record can be defined that contains fields corresponding
to all columns fetched from a cursor or cursor variable. Each field takes on the data type
of its corresponding column. The %ROWTYPE attribute is prefixed by a cursor name or
cursor variable name.
record cursor%ROWTYPE;
The following example shows how you can use a cursor with %ROWTYPE to get
information about which employee works in which department.
EXEC emp_info;
Each cursor has a set of attributes associated with it that allows the program to test the
state of the cursor. These attributes are %ISOPEN, %FOUND, %NOTFOUND, and
%ROWCOUNT. These attributes are described in the following sections.
3.8.6.1 %ISOPEN
cursor_name%ISOPEN
cursor_name is the name of the cursor for which a BOOLEAN data type of TRUE will be
returned if the cursor is open, FALSE otherwise.
3.8.6.2 %FOUND
The %FOUND attribute is used to test whether or not a row is retrieved from the result set
of the specified cursor after a FETCH on the cursor.
cursor_name%FOUND
cursor_name is the name of the cursor for which a BOOLEAN data type of TRUE will be
returned if a row is retrieved from the result set of the cursor after a FETCH.
After the last row of the result set has been FETCHed the next FETCH results in %FOUND
returning FALSE. FALSE is also returned after the first FETCH if there are no rows in the
result set to begin with.
%FOUND returns null if it is referenced when the cursor is open, but before the first
FETCH.
EXEC cursor_example;
EMPNO ENAME
----- ------
7369 SMITH
7499 ALLEN
7521 WARD
7566 JONES
7654 MARTIN
7698 BLAKE
7782 CLARK
7788 SCOTT
7839 KING
7844 TURNER
7876 ADAMS
7900 JAMES
7902 FORD
7934 MILLER
3.8.6.3 %NOTFOUND
cursor_name%NOTFOUND
cursor_name is the name of the cursor for which a BOOLEAN data type of FALSE will
be returned if a row is retrieved from the result set of the cursor after a FETCH.
After the last row of the result set has been FETCHed the next FETCH results in
%NOTFOUND returning TRUE. TRUE is also returned after the first FETCH if there are no
rows in the result set to begin with.
%NOTFOUND returns null if it is referenced when the cursor is open, but before the first
FETCH.
Similar to the prior example, this procedure produces the same output when invoked.
EXEC cursor_example;
EMPNO ENAME
----- ------
7369 SMITH
7499 ALLEN
7521 WARD
7566 JONES
7654 MARTIN
7698 BLAKE
7782 CLARK
7788 SCOTT
7839 KING
7844 TURNER
7876 ADAMS
7900 JAMES
7902 FORD
7934 MILLER
3.8.6.4 %ROWCOUNT
The %ROWCOUNT attribute returns an integer showing the number of rows FETCHed so far
from the specified cursor.
cursor_name%ROWCOUNT
cursor_name is the name of the cursor for which %ROWCOUNT returns the number of
rows retrieved thus far. After the last row has been retrieved, %ROWCOUNT remains set to
the total number of rows returned until the cursor is closed at which point %ROWCOUNT
will throw an INVALID_CURSOR exception if referenced.
%ROWCOUNT returns 0 if it is referenced when the cursor is open, but before the first
FETCH. %ROWCOUNT also returns 0 after the first FETCH when there are no rows in the
result set to begin with.
This procedure prints the total number of rows retrieved at the end of the employee list as
follows:
EXEC cursor_example;
EMPNO ENAME
----- -------
7369 SMITH
7499 ALLEN
7521 WARD
7566 JONES
7654 MARTIN
7698 BLAKE
7782 CLARK
In the cursor examples presented so far, the programming logic required to process the
result set of a cursor included a statement to open the cursor, a loop construct to retrieve
each row of the result set, a test for the end of the result set, and finally a statement to
close the cursor. The cursor FOR loop is a loop construct that eliminates the need to
individually code the statements just listed.
The cursor FOR loop opens a previously declared cursor, fetches all rows in the cursor
result set, and then closes the cursor.
The following example shows the example from Section 3.8.6.3, modified to use a cursor
FOR loop.
EXEC cursor_example;
EMPNO ENAME
----- -------
7369 SMITH
7499 ALLEN
7521 WARD
7566 JONES
7654 MARTIN
7698 BLAKE
7782 CLARK
7788 SCOTT
7839 KING
7844 TURNER
7876 ADAMS
7900 JAMES
7902 FORD
7934 MILLER
A user can also declare a static cursor that accepts parameters, and can pass values for
those parameters when opening that cursor. In the following example we have created a
parameterized cursor which will display the name and salary of all employees from the
emp table that have a salary less than a specified value which is passed as a parameter.
DECLARE
my_record emp%ROWTYPE;
CURSOR c1 (max_wage NUMBER) IS
SELECT * FROM emp WHERE sal < max_wage;
BEGIN
OPEN c1(2000);
LOOP
FETCH c1 INTO my_record;
EXIT WHEN c1%NOTFOUND;
So for example if we pass the value 2000 as max_wage, then we will only be shown the
name and salary of all employees that have a salary less than 2000. The result of the
above query is the following:
A cursor variable is a cursor that actually contains a pointer to a query result set. The
result set is determined by the execution of the OPEN FOR statement using the cursor
variable.
A cursor variable is not tied to a single particular query like a static cursor. The same
cursor variable may be opened a number of times with OPEN FOR statements containing
different queries. Each time, a new result set is created from that query and made
available via the cursor variable.
REF CURSOR types may be passed as parameters to or from stored procedures and
functions. The return type of a function may also be a REF CURSOR type. This provides
the capability to modularize the operations on a cursor into separate programs by passing
a cursor variable between programs.
SPL supports the declaration of a cursor variable using both the SYS_REFCURSOR built-
in data type as well as creating a type of REF CURSOR and then declaring a variable of
that type. SYS_REFCURSOR is a REF CURSOR type that allows any result set to be
associated with it. This is known as a weakly-typed REF CURSOR.
Only the declaration of SYS_REFCURSOR and user-defined REF CURSOR variables are
different. The remaining usage like opening the cursor, selecting into the cursor and
closing the cursor is the same across both the cursor types. For the rest of this chapter our
examples will primarily be making use of the SYS_REFCURSOR cursors. All you need to
change in the examples to make them work for user defined REF CURSORs is the
declaration section.
Note: Strongly-typed REF CURSORs require the result set to conform to a declared
number and order of fields with compatible data types and can also optionally return a
result set.
name SYS_REFCURSOR;
DECLARE
emp_refcur SYS_REFCURSOR;
...
You must perform two distinct declaration steps in order to use a user defined REF
CURSOR variable:
The syntax for creating a user defined REF CURSOR type is as follows:
DECLARE
TYPE emp_cur_type IS REF CURSOR RETURN emp%ROWTYPE;
my_rec emp_cur_type;
...
In the following example, the result set is a list of employee numbers and names from a
selected department. Note that a variable or parameter can be used in the SELECT
command anywhere an expression can normally appear. In this case a parameter is used
in the equality test for department number.
After a cursor variable is opened, rows may be retrieved from the result set using the
FETCH statement. See Section 3.8.3 for details on using the FETCH statement to retrieve
rows from a result set.
In the example below, a FETCH statement has been added to the previous example so now
the result set is returned into two variables and then displayed. Note that the cursor
attributes used to determine cursor state of static cursors can also be used with cursor
variables. See Section 3.8.6 for details on cursor attributes.
Use the CLOSE statement described in Section 3.8.4 to release the result set.
Note: Unlike static cursors, a cursor variable does not have to be closed before it can be
re-opened again. The result set from the previous open will be lost.
EXEC emp_by_dept(20)
EMPNO ENAME
----- -------
7369 SMITH
7566 JONES
7788 SCOTT
7876 ADAMS
7902 FORD
In addition the following table shows the permitted parameter modes for a cursor variable
used as a procedure or function parameter depending upon the operations on the cursor
variable within the procedure or function.
So for example, if a procedure performs all three operations, OPEN FOR, FETCH, and
CLOSE on a cursor variable declared as the procedures formal parameter, then that
parameter must be declared with IN OUT mode.
3.9.7 Examples
This function is invoked in the following anonymous block by assigning the functions
return value to a cursor variable declared in the anonymous blocks declaration section.
The result set is fetched using this cursor variable and then it is closed.
DECLARE
v_empno emp.empno%TYPE;
v_ename emp.ename%TYPE;
v_job emp.job%TYPE := 'SALESMAN';
v_emp_refcur SYS_REFCURSOR;
BEGIN
DBMS_OUTPUT.PUT_LINE('EMPLOYEES WITH JOB ' || v_job);
DBMS_OUTPUT.PUT_LINE('EMPNO ENAME');
DBMS_OUTPUT.PUT_LINE('----- -------');
v_emp_refcur := emp_by_job(v_job);
LOOP
FETCH v_emp_refcur INTO v_empno, v_ename;
EXIT WHEN v_emp_refcur%NOTFOUND;
DBMS_OUTPUT.PUT_LINE(v_empno || ' ' || v_ename);
END LOOP;
CLOSE v_emp_refcur;
END;
The following procedure opens the given cursor variable with a SELECT command that
retrieves all rows.
This variation opens the given cursor variable with a SELECT command that retrieves all
rows, but of a given department.
This third variation opens the given cursor variable with a SELECT command that
retrieves all rows, but from a different table. Also note that the functions return value is
the opened cursor variable.
This procedure fetches and displays a cursor variable result set consisting of employee
number and name.
This procedure fetches and displays a cursor variable result set consisting of department
number and name.
The following anonymous block executes all the previously described programs.
DECLARE
gen_refcur SYS_REFCURSOR;
BEGIN
DBMS_OUTPUT.PUT_LINE('ALL EMPLOYEES');
open_all_emp(gen_refcur);
fetch_emp(gen_refcur);
DBMS_OUTPUT.PUT_LINE('****************');
DBMS_OUTPUT.PUT_LINE('DEPARTMENTS');
fetch_dept(open_dept(gen_refcur));
DBMS_OUTPUT.PUT_LINE('*****************');
close_refcur(gen_refcur);
END;
ALL EMPLOYEES
EMPNO ENAME
----- -------
7369 SMITH
7499 ALLEN
7521 WARD
7566 JONES
7654 MARTIN
7698 BLAKE
7782 CLARK
7788 SCOTT
7839 KING
7844 TURNER
7876 ADAMS
7900 JAMES
7902 FORD
7934 MILLER
****************
EMPLOYEES IN DEPT #10
EMPNO ENAME
----- -------
7782 CLARK
7839 KING
7934 MILLER
****************
DEPARTMENTS
DEPT DNAME
---- ---------
10 ACCOUNTING
20 RESEARCH
30 SALES
40 OPERATIONS
*****************
Advanced Server also supports dynamic queries via the OPEN FOR USING statement. A
string literal or string variable is supplied in the OPEN FOR USING statement to the
SELECT command.
EXEC dept_query;
EMPNO ENAME
----- -------
7499 ALLEN
7698 BLAKE
7844 TURNER
In the next example, the previous query is modified to use bind arguments to pass the
query parameters.
EMPNO ENAME
----- -------
7499 ALLEN
7698 BLAKE
7844 TURNER
Finally, a string variable is used to pass the SELECT providing the most flexibility.
EMPNO ENAME
----- -------
7566 JONES
7788 SCOTT
7902 FORD
3.10 Collections
A collection is a set of ordered data items with the same data type. Generally, the data
item is a scalar field, but may also be a user-defined type such as a record type or an
object type as long as the structure and the data types that comprise each field of the user-
defined type are the same for each element in the set. Each particular data item in the set
is referenced by using subscript notation within a pair of parentheses.
Note: Multilevel collections (that is, where the data item of a collection is another
collection) are not supported.
The most commonly known type of collection is an array. In Advanced Server, the
supported collection types are associative arrays (formerly called index-by-tables in
Oracle), nested tables, and varrays.
A collection of the desired type must be defined. This can be done in the
declaration section of an SPL program, which results in a local type that is
accessible only within that program. For nested table and varray types this can
also be done using the CREATE TYPE command, which creates a persistent,
standalone type that can be referenced by any SPL program in the database.
Variables of the collection type are declared. The collection associated with the
declared variable is said to be uninitialized at this point if there is no value
assignment made as part of the variable declaration.
Uninitialized collections of nested tables and varrays are null. A null collection
does not yet exist. Generally, a COLLECTION_IS_NULL exception is thrown if a
collection method is invoked on a null collection.
Uninitialized collections of associative arrays exist, but have no elements. An
existing collection with no elements is called an empty collection.
To initialize a null collection, you must either make it an empty collection or
assign a non-null value to it. Generally, a null collection is initialized by using its
constructor.
To add elements to an empty associative array, you can simply assign values to its
keys. For nested tables and varrays, generally its constructor is used to assign
initial values to the nested table or varray. For nested tables and varrays, the
EXTEND method is then used to grow the collection beyond its initial size
established by the constructor.
The specific process for each collection type is described in the following sections.
An associative array is a type of collection that associates a unique key with a value. The
key does not have to be numeric, but can be character data as well.
An associative array type must be defined after which array variables can be
declared of that array type. Data manipulation occurs using the array variable.
When an array variable is declared, the associative array is created, but it is empty
- just start assigning values to key values.
The key can be any negative integer, positive integer, or zero if INDEX BY
BINARY_INTEGER or PLS_INTEGER is specified.
The key can be character data if INDEX BY VARCHAR2 is specified.
There is no pre-defined limit on the number of elements in the array - it grows
dynamically as elements are added.
The array can be sparse - there may be gaps in the assignment of values to keys.
An attempt to reference an array element that has not been assigned a value will
result in an exception.
assoctype is an identifier assigned to the array type. datatype is a scalar data type
such as VARCHAR2 or NUMBER. rectype is a previously defined record type. objtype is
a previously defined object type. n is the maximum length of a character key.
In order to make use of the array, a variable must be declared with that array type. The
following is the syntax for declaring an array variable.
array assoctype
array(n)[.field ]
array is the identifier of a previously declared array. n is the key value, type-compatible
with the data type given in the INDEX BY clause. If the array type of array is defined
from a record type or object type, then [.field ] must reference an individual field
within the record type or attribute within the object type from which the array type is
defined. Alternatively, the entire record can be referenced by omitting [.field ].
The following example reads the first ten employee names from the emp table, stores
them in an array, then displays the results from the array.
SMITH
ALLEN
WARD
JONES
MARTIN
BLAKE
CLARK
SCOTT
KING
TURNER
The previous example is now modified to use a record type in the array definition.
DECLARE
TYPE emp_rec_typ IS RECORD (
empno NUMBER(4),
ename VARCHAR2(10)
);
TYPE emp_arr_typ IS TABLE OF emp_rec_typ INDEX BY BINARY_INTEGER;
emp_arr emp_arr_typ;
CURSOR emp_cur IS SELECT empno, ename FROM emp WHERE ROWNUM <= 10;
i INTEGER := 0;
BEGIN
DBMS_OUTPUT.PUT_LINE('EMPNO ENAME');
DBMS_OUTPUT.PUT_LINE('----- -------');
FOR r_emp IN emp_cur LOOP
i := i + 1;
emp_arr(i).empno := r_emp.empno;
emp_arr(i).ename := r_emp.ename;
END LOOP;
FOR j IN 1..10 LOOP
DBMS_OUTPUT.PUT_LINE(emp_arr(j).empno || ' ' ||
emp_arr(j).ename);
END LOOP;
END;
EMPNO ENAME
----- -------
7369 SMITH
7499 ALLEN
7521 WARD
The emp%ROWTYPE attribute could be used to define emp_arr_typ instead of using the
emp_rec_typ record type as shown in the following.
DECLARE
TYPE emp_arr_typ IS TABLE OF emp%ROWTYPE INDEX BY BINARY_INTEGER;
emp_arr emp_arr_typ;
CURSOR emp_cur IS SELECT empno, ename FROM emp WHERE ROWNUM <= 10;
i INTEGER := 0;
BEGIN
DBMS_OUTPUT.PUT_LINE('EMPNO ENAME');
DBMS_OUTPUT.PUT_LINE('----- -------');
FOR r_emp IN emp_cur LOOP
i := i + 1;
emp_arr(i).empno := r_emp.empno;
emp_arr(i).ename := r_emp.ename;
END LOOP;
FOR j IN 1..10 LOOP
DBMS_OUTPUT.PUT_LINE(emp_arr(j).empno || ' ' ||
emp_arr(j).ename);
END LOOP;
END;
Instead of assigning each field of the record individually, a record level assignment can
be made from r_emp to emp_arr.
DECLARE
TYPE emp_rec_typ IS RECORD (
empno NUMBER(4),
ename VARCHAR2(10)
);
TYPE emp_arr_typ IS TABLE OF emp_rec_typ INDEX BY BINARY_INTEGER;
emp_arr emp_arr_typ;
CURSOR emp_cur IS SELECT empno, ename FROM emp WHERE ROWNUM <= 10;
i INTEGER := 0;
BEGIN
DBMS_OUTPUT.PUT_LINE('EMPNO ENAME');
DBMS_OUTPUT.PUT_LINE('----- -------');
FOR r_emp IN emp_cur LOOP
i := i + 1;
emp_arr(i) := r_emp;
END LOOP;
FOR j IN 1..10 LOOP
DBMS_OUTPUT.PUT_LINE(emp_arr(j).empno || ' ' ||
emp_arr(j).ename);
END LOOP;
END;
The key of an associative array can be character data as shown in the following example.
DECLARE
TYPE job_arr_typ IS TABLE OF NUMBER INDEX BY VARCHAR2(9);
job_arr job_arr_typ;
BEGIN
job_arr('ANALYST') := 100;
job_arr('CLERK') := 200;
job_arr('MANAGER') := 300;
job_arr('SALESMAN') := 400;
job_arr('PRESIDENT') := 500;
DBMS_OUTPUT.PUT_LINE('ANALYST : ' || job_arr('ANALYST'));
DBMS_OUTPUT.PUT_LINE('CLERK : ' || job_arr('CLERK'));
DBMS_OUTPUT.PUT_LINE('MANAGER : ' || job_arr('MANAGER'));
DBMS_OUTPUT.PUT_LINE('SALESMAN : ' || job_arr('SALESMAN'));
DBMS_OUTPUT.PUT_LINE('PRESIDENT: ' || job_arr('PRESIDENT'));
END;
ANALYST : 100
CLERK : 200
MANAGER : 300
SALESMAN : 400
PRESIDENT: 500
A nested table is a type of collection that associates a positive integer with a value. A
nested table has the following characteristics:
A nested table type must be defined after which nested table variables can be
declared of that nested table type. Data manipulation occurs using the nested table
variable, or simply, table for short.
When a nested table variable is declared, the nested table initially does not exist
(it is a null collection). The null table must be initialized with a constructor. You
can also initialize the table by using an assignment statement where the right-hand
side of the assignment is an initialized table of the same type. Note: Initialization
of a nested table is mandatory in Oracle, but optional in SPL.
The key is a positive integer.
The constructor establishes the number of elements in the table. The EXTEND
method adds additional elements to the table. See Section 3.11 for information on
collection methods. Note: Usage of the constructor to establish the number of
elements in the table and usage of the EXTEND method to add additional elements
to the table are mandatory in Oracle, but optional in SPL.
The table can be sparse - there may be gaps in the assignment of values to keys.
An attempt to reference a table element beyond its initialized or extended size will
result in a SUBSCRIPT_BEYOND_COUNT exception.
The TYPE IS TABLE statement is used to define a nested table type within the
declaration section of an SPL program.
tbltype is an identifier assigned to the nested table type. datatype is a scalar data
type such as VARCHAR2 or NUMBER. rectype is a previously defined record type.
objtype is a previously defined object type.
Note: You can use the CREATE TYPE command to define a nested table type that is
available to all SPL programs in the database. See the Database Compatibility for Oracle
Developers Reference Guide for more information about the CREATE TYPE command.
In order to make use of the table, a variable must be declared of that nested table type.
The following is the syntax for declaring a table variable.
table tbltype
tbltype is the identifier of the nested table types constructor, which has the same name
as the nested table type. expr1, expr2, are expressions that are type-compatible with
the element type of the table. If NULL is specified, the corresponding element is set to
null. If the parameter list is empty, then an empty nested table is returned, which means
there are no elements in the table. If the table is defined from an object type, then exprn
must return an object of that object type. The object can be the return value of a function
or the object types constructor, or the object can be an element of another nested table of
the same type.
DECLARE
TYPE nested_typ IS TABLE OF CHAR(1);
v_nested nested_typ := nested_typ('A','B');
table(n)[.element ]
table is the identifier of a previously declared table. n is a positive integer. If the table
type of table is defined from a record type or object type, then [.element ] must
reference an individual field within the record type or attribute within the object type
from which the nested table type is defined. Alternatively, the entire record or object can
be referenced by omitting [.element ].
The following is an example of a nested table where it is known that there will be four
elements.
DECLARE
TYPE dname_tbl_typ IS TABLE OF VARCHAR2(14);
dname_tbl dname_tbl_typ;
CURSOR dept_cur IS SELECT dname FROM dept ORDER BY dname;
i INTEGER := 0;
BEGIN
dname_tbl := dname_tbl_typ(NULL, NULL, NULL, NULL);
FOR r_dept IN dept_cur LOOP
i := i + 1;
dname_tbl(i) := r_dept.dname;
END LOOP;
DBMS_OUTPUT.PUT_LINE('DNAME');
DBMS_OUTPUT.PUT_LINE('----------');
FOR j IN 1..i LOOP
DBMS_OUTPUT.PUT_LINE(dname_tbl(j));
END LOOP;
END;
DNAME
----------
ACCOUNTING
OPERATIONS
RESEARCH
SALES
The following example reads the first ten employee names from the emp table, stores
them in a nested table, then displays the results from the table. The SPL code is written to
assume that the number of employees to be returned is not known beforehand.
DECLARE
TYPE emp_rec_typ IS RECORD (
empno NUMBER(4),
ename VARCHAR2(10)
);
TYPE emp_tbl_typ IS TABLE OF emp_rec_typ;
emp_tbl emp_tbl_typ;
CURSOR emp_cur IS SELECT empno, ename FROM emp WHERE ROWNUM <= 10;
i INTEGER := 0;
BEGIN
emp_tbl := emp_tbl_typ();
DBMS_OUTPUT.PUT_LINE('EMPNO ENAME');
DBMS_OUTPUT.PUT_LINE('----- -------');
FOR r_emp IN emp_cur LOOP
i := i + 1;
emp_tbl.EXTEND;
emp_tbl(i) := r_emp;
END LOOP;
FOR j IN 1..10 LOOP
Note the creation of an empty table with the constructor emp_tbl_typ() as the first
statement in the executable section of the anonymous block. The EXTEND collection
method is then used to add an element to the table for each employee returned from the
result set. See Section 3.11.4 for information on EXTEND.
EMPNO ENAME
----- -------
7369 SMITH
7499 ALLEN
7521 WARD
7566 JONES
7654 MARTIN
7698 BLAKE
7782 CLARK
7788 SCOTT
7839 KING
7844 TURNER
The following example shows how a nested table of an object type can be used. First, an
object type is created with attributes for the department name and location.
The following anonymous block defines a nested table type whose element consists of
the dept_obj_typ object type. A nested table variable is declared, initialized, and then
populated from the dept table. Finally, the elements from the nested table are displayed.
DECLARE
TYPE dept_tbl_typ IS TABLE OF dept_obj_typ;
dept_tbl dept_tbl_typ;
CURSOR dept_cur IS SELECT dname, loc FROM dept ORDER BY dname;
i INTEGER := 0;
BEGIN
dept_tbl := dept_tbl_typ(
dept_obj_typ(NULL,NULL),
dept_obj_typ(NULL,NULL),
dept_obj_typ(NULL,NULL),
dept_obj_typ(NULL,NULL)
);
FOR r_dept IN dept_cur LOOP
i := i + 1;
dept_tbl(i).dname := r_dept.dname;
dept_tbl(i).loc := r_dept.loc;
END LOOP;
DBMS_OUTPUT.PUT_LINE('DNAME LOC');
DBMS_OUTPUT.PUT_LINE('---------- ----------');
FOR j IN 1..i LOOP
Note: The parameters comprising the nested tables constructor, dept_tbl_typ, are
calls to the object types constructor dept_obj_typ.
DNAME LOC
---------- ----------
ACCOUNTING NEW YORK
OPERATIONS BOSTON
RESEARCH DALLAS
SALES CHICAGO
3.10.3 Varrays
A varray type must be defined along with a maximum size limit. After the varray
type is defined, varray variables can be declared of that varray type. Data
manipulation occurs using the varray variable, or simply, varray for short. The
number of elements in the varray cannot exceed the maximum size limit
established in the varray type definition.
When a varray variable is declared, the varray initially does not exist (it is a null
collection). The null varray must be initialized with a constructor. You can also
initialize the varray by using an assignment statement where the right-hand side of
the assignment is an initialized varray of the same type.
The key is a positive integer.
The constructor establishes the number of elements in the varray, which must not
exceed the maximum size limit. The EXTEND method can add additional elements
to the varray up to the maximum size limit. See Section 3.11 for information on
collection methods.
Unlike a nested table, a varray cannot be sparse - there are no gaps in the
assignment of values to keys.
An attempt to reference a varray element beyond its initialized or extended size,
but within the maximum size limit will result in a SUBSCRIPT_BEYOND_COUNT
exception.
An attempt to reference a varray element beyond the maximum size limit or
extend a varray beyond the maximum size limit will result in a
SUBSCRIPT_OUTSIDE_LIMIT exception.
The TYPE IS VARRAY statement is used to define a varray type within the declaration
section of an SPL program.
varraytype is an identifier assigned to the varray type. datatype is a scalar data type
such as VARCHAR2 or NUMBER. maxsize is the maximum number of elements permitted
in varrays of that type. objtype is a previously defined object type.
Note: The CREATE TYPE command can be used to define a varray type that is available
to all SPL programs in the database. In order to make use of the varray, a variable must
be declared of that varray type. The following is the syntax for declaring a varray
variable.
varray varraytype
varraytype is the identifier of the varray types constructor, which has the same name
as the varray type. expr1, expr2, are expressions that are type-compatible with the
element type of the varray. If NULL is specified, the corresponding element is set to null.
If the parameter list is empty, then an empty varray is returned, which means there are no
elements in the varray. If the varray is defined from an object type, then exprn must
return an object of that object type. The object can be the return value of a function or the
return value of the object types constructor. The object can also be an element of another
varray of the same varray type.
DECLARE
TYPE varray_typ IS VARRAY(2) OF CHAR(1);
v_varray varray_typ := varray_typ('A','B');
The following is an example of a varray where it is known that there will be four
elements.
DECLARE
TYPE dname_varray_typ IS VARRAY(4) OF VARCHAR2(14);
dname_varray dname_varray_typ;
CURSOR dept_cur IS SELECT dname FROM dept ORDER BY dname;
i INTEGER := 0;
BEGIN
dname_varray := dname_varray_typ(NULL, NULL, NULL, NULL);
FOR r_dept IN dept_cur LOOP
i := i + 1;
dname_varray(i) := r_dept.dname;
END LOOP;
DBMS_OUTPUT.PUT_LINE('DNAME');
DBMS_OUTPUT.PUT_LINE('----------');
FOR j IN 1..i LOOP
DBMS_OUTPUT.PUT_LINE(dname_varray(j));
END LOOP;
END;
DNAME
----------
ACCOUNTING
OPERATIONS
RESEARCH
SALES
3.11.1 COUNT
COUNT is a method that returns the number of elements in a collection. The syntax for
using COUNT is as follows:
collection.COUNT
The following example shows that an associative array can be sparsely populated (i.e.,
there are gaps in the sequence of assigned elements). COUNT includes only the
elements that have been assigned a value.
DECLARE
TYPE sparse_arr_typ IS TABLE OF NUMBER INDEX BY BINARY_INTEGER;
sparse_arr sparse_arr_typ;
BEGIN
sparse_arr(-100) := -100;
sparse_arr(-10) := -10;
sparse_arr(0) := 0;
sparse_arr(10) := 10;
sparse_arr(100) := 100;
DBMS_OUTPUT.PUT_LINE('COUNT: ' || sparse_arr.COUNT);
END;
The following output shows that there are five populated elements included in COUNT.
COUNT: 5
3.11.2 DELETE
The DELETE method deletes entries from a collection. You can call the DELETE method
in three different ways.
Use the first form of the DELETE method to remove all entries from a collection:
collection.DELETE
Use the second form of the DELETE method to remove the specified entry from a
collection:
collection.DELETE(subscript)
Use the third form of the DELETE method to remove the entries that are within the range
specified by first_subscript and last_subscript (including the entries for the
first_subscript and the last_subscript) from a collection.
collection.DELETE(first_subscript, last_subscript)
Note that when you delete an entry, the subscript remains in the collection; you can re-
use the subscript with an alternate entry. If you specify a subscript that does not exist in
the call to the DELETE method, DELETE does not raise an exception.
The following example demonstrates using the DELETE method to remove the element
with subscript 0 from the collection:
DECLARE
TYPE sparse_arr_typ IS TABLE OF NUMBER INDEX BY BINARY_INTEGER;
sparse_arr sparse_arr_typ;
v_results VARCHAR2(50);
v_sub NUMBER;
BEGIN
sparse_arr(-100) := -100;
sparse_arr(-10) := -10;
sparse_arr(0) := 0;
sparse_arr(10) := 10;
sparse_arr(100) := 100;
DBMS_OUTPUT.PUT_LINE('COUNT: ' || sparse_arr.COUNT);
sparse_arr.DELETE(0);
DBMS_OUTPUT.PUT_LINE('COUNT: ' || sparse_arr.COUNT);
v_sub := sparse_arr.FIRST;
WHILE v_sub IS NOT NULL LOOP
IF sparse_arr(v_sub) IS NULL THEN
v_results := v_results || 'NULL ';
ELSE
v_results := v_results || sparse_arr(v_sub) || ' ';
END IF;
v_sub := sparse_arr.NEXT(v_sub);
END LOOP;
DBMS_OUTPUT.PUT_LINE('Results: ' || v_results);
END;
COUNT: 5
COUNT: 4
Results: -100 -10 10 100
COUNT indicates that before the DELETE method, there were 5 elements in the collection;
after the DELETE method was invoked, the collection contains 4 elements.
3.11.3 EXISTS
The EXISTS method verifies that a subscript exists within a collection. EXISTS returns
TRUE if the subscript exists; if the subscript does not exist, EXISTS returns FALSE. The
method takes a single argument; the subscript that you are testing for. The syntax is:
collection.EXISTS(subscript)
subscript is the value that you are testing for. If you specify a value of NULL, EXISTS
returns false.
The following example verifies that subscript number 10 exists within the associative
array:
DECLARE
TYPE sparse_arr_typ IS TABLE OF NUMBER INDEX BY BINARY_INTEGER;
sparse_arr sparse_arr_typ;
BEGIN
sparse_arr(-100) := -100;
sparse_arr(-10) := -10;
sparse_arr(0) := 0;
sparse_arr(10) := 10;
sparse_arr(100) := 100;
DBMS_OUTPUT.PUT_LINE('The index exists: ' ||
CASE WHEN sparse_arr.exists(10) = TRUE THEN 'true' ELSE 'false' END);
END;
Some collection methods raise an exception if you call them with a subscript that does
not exist within the specified collection. Rather than raising an error, the EXISTS
method returns a value of FALSE.
3.11.4 EXTEND
The EXTEND method increases the size of a collection. There are three variations of the
EXTEND method. The first variation appends a single NULL element to a collection; the
syntax for the first variation is:
collection.EXTEND
The following example demonstrates using the EXTEND method to append a single, null
element to a collection:
DECLARE
TYPE sparse_arr_typ IS TABLE OF NUMBER;
sparse_arr sparse_arr_typ := sparse_arr_typ(-100,-10,0,10,100);
v_results VARCHAR2(50);
BEGIN
DBMS_OUTPUT.PUT_LINE('COUNT: ' || sparse_arr.COUNT);
sparse_arr.EXTEND;
DBMS_OUTPUT.PUT_LINE('COUNT: ' || sparse_arr.COUNT);
FOR i IN sparse_arr.FIRST .. sparse_arr.LAST LOOP
IF sparse_arr(i) IS NULL THEN
v_results := v_results || 'NULL ';
ELSE
v_results := v_results || sparse_arr(i) || ' ';
END IF;
END LOOP;
DBMS_OUTPUT.PUT_LINE('Results: ' || v_results);
END;
COUNT: 5
COUNT: 6
Results: -100 -10 0 10 100 NULL
COUNT indicates that before the EXTEND method, there were 5 elements in the collection;
after the EXTEND method was invoked, the collection contains 6 elements.
The second variation of the EXTEND method appends a specified number of elements to
the end of a collection.
collection.EXTEND(count)
count is the number of null elements added to the end of the collection.
The following example demonstrates using the EXTEND method to append multiple null
elements to a collection:
DECLARE
TYPE sparse_arr_typ IS TABLE OF NUMBER;
sparse_arr sparse_arr_typ := sparse_arr_typ(-100,-10,0,10,100);
v_results VARCHAR2(50);
BEGIN
DBMS_OUTPUT.PUT_LINE('COUNT: ' || sparse_arr.COUNT);
sparse_arr.EXTEND(3);
DBMS_OUTPUT.PUT_LINE('COUNT: ' || sparse_arr.COUNT);
FOR i IN sparse_arr.FIRST .. sparse_arr.LAST LOOP
IF sparse_arr(i) IS NULL THEN
v_results := v_results || 'NULL ';
ELSE
v_results := v_results || sparse_arr(i) || ' ';
END IF;
END LOOP;
DBMS_OUTPUT.PUT_LINE('Results: ' || v_results);
COUNT: 5
COUNT: 8
Results: -100 -10 0 10 100 NULL NULL NULL
COUNT indicates that before the EXTEND method, there were 5 elements in the collection;
after the EXTEND method was invoked, the collection contains 8 elements.
The third variation of the EXTEND method appends a specified number of copies of a
particular element to the end of a collection.
collection.EXTEND(count, index_number)
index_number is the subscript of the element that is being copied to the collection.
The following example demonstrates using the EXTEND method to append multiple
copies of the second element to the collection:
DECLARE
TYPE sparse_arr_typ IS TABLE OF NUMBER;
sparse_arr sparse_arr_typ := sparse_arr_typ(-100,-10,0,10,100);
v_results VARCHAR2(50);
BEGIN
DBMS_OUTPUT.PUT_LINE('COUNT: ' || sparse_arr.COUNT);
sparse_arr.EXTEND(3, 2);
DBMS_OUTPUT.PUT_LINE('COUNT: ' || sparse_arr.COUNT);
FOR i IN sparse_arr.FIRST .. sparse_arr.LAST LOOP
IF sparse_arr(i) IS NULL THEN
v_results := v_results || 'NULL ';
ELSE
v_results := v_results || sparse_arr(i) || ' ';
END IF;
END LOOP;
DBMS_OUTPUT.PUT_LINE('Results: ' || v_results);
END;
COUNT: 5
COUNT: 8
Results: -100 -10 0 10 100 -10 -10 -10
COUNT indicates that before the EXTEND method, there were 5 elements in the collection;
after the EXTEND method was invoked, the collection contains 8 elements.
3.11.5 FIRST
FIRST is a method that returns the subscript of the first element in a collection. The
syntax for using FIRST is as follows:
collection.FIRST
The following example displays the first element of the associative array.
DECLARE
TYPE sparse_arr_typ IS TABLE OF NUMBER INDEX BY BINARY_INTEGER;
sparse_arr sparse_arr_typ;
BEGIN
sparse_arr(-100) := -100;
sparse_arr(-10) := -10;
sparse_arr(0) := 0;
sparse_arr(10) := 10;
sparse_arr(100) := 100;
DBMS_OUTPUT.PUT_LINE('FIRST element: ' || sparse_arr(sparse_arr.FIRST));
END;
3.11.6 LAST
LAST is a method that returns the subscript of the last element in a collection. The syntax
for using LAST is as follows:
collection.LAST
The following example displays the last element of the associative array.
DECLARE
TYPE sparse_arr_typ IS TABLE OF NUMBER INDEX BY BINARY_INTEGER;
sparse_arr sparse_arr_typ;
BEGIN
sparse_arr(-100) := -100;
sparse_arr(-10) := -10;
sparse_arr(0) := 0;
sparse_arr(10) := 10;
sparse_arr(100) := 100;
DBMS_OUTPUT.PUT_LINE('LAST element: ' || sparse_arr(sparse_arr.LAST));
END;
3.11.7 LIMIT
collection.LIMIT
For an initialized varray, LIMIT returns the maximum size limit determined by the varray
type definition. If the varray is uninitialized (that is, it is a null varray), an exception is
thrown.
For an associative array or an initialized nested table, LIMIT returns NULL. If the nested
table is uninitialized (that is, it is a null nested table), an exception is thrown.
3.11.8 NEXT
NEXT is a method that returns the subscript that follows a specified subscript. The
method takes a single argument; the subscript that you are testing for.
collection.NEXT(subscript)
If the specified subscript is less than the first subscript in the collection, the function
returns the first subscript. If the subscript does not have a successor, NEXT returns NULL.
If you specify a NULL subscript, PRIOR does not return a value.
The following example demonstrates using NEXT to return the subscript that follows
subscript 10 in the associative array, sparse_arr:
DECLARE
TYPE sparse_arr_typ IS TABLE OF NUMBER INDEX BY BINARY_INTEGER;
sparse_arr sparse_arr_typ;
BEGIN
sparse_arr(-100) := -100;
sparse_arr(-10) := -10;
sparse_arr(0) := 0;
sparse_arr(10) := 10;
sparse_arr(100) := 100;
DBMS_OUTPUT.PUT_LINE('NEXT element: ' || sparse_arr.next(10));
END;
3.11.9 PRIOR
The PRIOR method returns the subscript that precedes a specified subscript in a
collection. The method takes a single argument; the subscript that you are testing for.
The syntax is:
collection.PRIOR(subscript)
If the subscript specified does not have a predecessor, PRIOR returns NULL. If the
specified subscript is greater than the last subscript in the collection, the method returns
the last subscript. If you specify a NULL subscript, PRIOR does not return a value.
The following example returns the subscript that precedes subscript 100 in the
associative array, sparse_arr:
DECLARE
TYPE sparse_arr_typ IS TABLE OF NUMBER INDEX BY BINARY_INTEGER;
sparse_arr sparse_arr_typ;
BEGIN
sparse_arr(-100) := -100;
sparse_arr(-10) := -10;
sparse_arr(0) := 0;
sparse_arr(10) := 10;
sparse_arr(100) := 100;
DBMS_OUTPUT.PUT_LINE('PRIOR element: ' || sparse_arr.prior(100));
END;
PRIOR element: 10
3.11.10 TRIM
The TRIM method removes an element or elements from the end of a collection. The
syntax for the TRIM method is:
collection.TRIM[(count)]
count is the number of elements removed from the end of the collection. Advanced
Server will return an error if count is less than 0 or greater than the number of elements
in the collection.
The following example demonstrates using the TRIM method to remove an element from
the end of a collection:
COUNT: 5
COUNT: 4
COUNT indicates that before the TRIM method, there were 5 elements in the collection;
after the TRIM method was invoked, the collection contains 4 elements.
You can also specify the number of elements to remove from the end of the collection
with the TRIM method:
DECLARE
TYPE sparse_arr_typ IS TABLE OF NUMBER;
sparse_arr sparse_arr_typ := sparse_arr_typ(-100,-10,0,10,100);
v_results VARCHAR2(50);
BEGIN
DBMS_OUTPUT.PUT_LINE('COUNT: ' || sparse_arr.COUNT);
sparse_arr.TRIM(2);
DBMS_OUTPUT.PUT_LINE('COUNT: ' || sparse_arr.COUNT);
FOR i IN sparse_arr.FIRST .. sparse_arr.LAST LOOP
IF sparse_arr(i) IS NULL THEN
v_results := v_results || 'NULL ';
ELSE
v_results := v_results || sparse_arr(i) || ' ';
END IF;
END LOOP;
DBMS_OUTPUT.PUT_LINE('Results: ' || v_results);
END;
COUNT: 5
COUNT: 3
Results: -100 -10 0
COUNT indicates that before the TRIM method, there were 5 elements in the collection;
after the TRIM method was invoked, the collection contains 3 elements.
3.12.1 TABLE()
Use the TABLE() function to transform the members of an array into a set of rows. The
signature is:
TABLE(collection_value)
Where:
collection_value
The TABLE() function expands the nested contents of a collection into a table format.
You can use the TABLE() function anywhere you use a regular table expression.
The TABLE() function returns a SETOF ANYELEMENT (a set of values of any type). For
example, if the argument passed to this function is an array of dates, TABLE() will
return a SETOF dates. If the argument passed to this function is an array of paths,
TABLE() will return a SETOF paths.
You can use the TABLE() function to expand the contents of a collection into table form:
monthly_balance
----------------
445.00
980.20
552.00
(3 rows)
The MULTISET UNION operator combines two collections to form a third collection. The
signature is:
Include the ALL keyword to specify that duplicate elements (elements that are present in
both coll_1 and coll_2) should be represented in the result, once for each time they
are present in the original collections. This is the default behavior of MULTISET UNION.
Include the DISTINCT keyword to specify that duplicate elements should be included in
the result only once.
The following example demonstrates using the MULTISET UNION operator to combine
two collections (collection_1 and collection_2) into a third collection
(collection_3):
DECLARE
TYPE int_arr_typ IS TABLE OF NUMBER(2);
collection_1 int_arr_typ;
collection_2 int_arr_typ;
collection_3 int_arr_typ;
v_results VARCHAR2(50);
BEGIN
collection_1 := int_arr_typ(10,20,30);
collection_2 := int_arr_typ(30,40);
collection_3 := collection_1 MULTISET UNION ALL collection_2;
DBMS_OUTPUT.PUT_LINE('COUNT: ' || collection_3.COUNT);
FOR i IN collection_3.FIRST .. collection_3.LAST LOOP
IF collection_3(i) IS NULL THEN
v_results := v_results || 'NULL ';
ELSE
v_results := v_results || collection_3(i) || ' ';
END IF;
END LOOP;
DBMS_OUTPUT.PUT_LINE('Results: ' || v_results);
END;
COUNT: 5
Results: 10 20 30 30 40
The resulting collection includes one entry for each element in collection_1 and
collection_2. If the DISTINCT keyword is used, the results are the following:
DECLARE
TYPE int_arr_typ IS TABLE OF NUMBER(2);
collection_1 int_arr_typ;
collection_2 int_arr_typ;
collection_3 int_arr_typ;
v_results VARCHAR2(50);
BEGIN
collection_1 := int_arr_typ(10,20,30);
collection_2 := int_arr_typ(30,40);
collection_3 := collection_1 MULTISET UNION DISTINCT collection_2;
DBMS_OUTPUT.PUT_LINE('COUNT: ' || collection_3.COUNT);
FOR i IN collection_3.FIRST .. collection_3.LAST LOOP
IF collection_3(i) IS NULL THEN
v_results := v_results || 'NULL ';
ELSE
v_results := v_results || collection_3(i) || ' ';
END IF;
END LOOP;
DBMS_OUTPUT.PUT_LINE('Results: ' || v_results);
COUNT: 4
Results: 10 20 30 40
The resulting collection includes only those members with distinct values. Note in the
following example that the MULTISET UNION DISTINCT operator also removes
duplicate entries that are stored within the same collection:
DECLARE
TYPE int_arr_typ IS TABLE OF NUMBER(2);
collection_1 int_arr_typ;
collection_2 int_arr_typ;
collection_3 int_arr_typ;
v_results VARCHAR2(50);
BEGIN
collection_1 := int_arr_typ(10,20,30,30);
collection_2 := int_arr_typ(40,50);
collection_3 := collection_1 MULTISET UNION DISTINCT collection_2;
DBMS_OUTPUT.PUT_LINE('COUNT: ' || collection_3.COUNT);
FOR i IN collection_3.FIRST .. collection_3.LAST LOOP
IF collection_3(i) IS NULL THEN
v_results := v_results || 'NULL ';
ELSE
v_results := v_results || collection_3(i) || ' ';
END IF;
END LOOP;
DBMS_OUTPUT.PUT_LINE('Results: ' || v_results);
END;
COUNT: 5
Results: 10 20 30 40 50
Collections can be used to more efficiently process DML commands by passing all the
values to be used for repetitive execution of a DELETE, INSERT, or UPDATE command in
one pass to the database server rather than re-iteratively invoking the DML command
with new values. The DML command to be processed in such a manner is specified with
the FORALL statement. In addition, one or more collections are given in the DML
command where different values are to be substituted each time the command is
executed.
Note: If an exception occurs during any iteration of the FORALL statement, all updates
that occurred since the start of the execution of the FORALL statement are automatically
rolled back. This behavior is not compatible with Oracle databases. Oracle allows explicit
use of the COMMIT or ROLLBACK commands to control whether or not to commit or roll
back updates that occurred prior to the exception.
The FORALL statement creates a loop each iteration of the loop increments the index
variable (you typically use the index within the loop to select a member of a collection).
The number of iterations is controlled by the lower_bound .. upper_bound clause.
The loop is executes once for each integer between the lower_bound and
upper_bound (inclusive) and the index is incremented by one for each iteration. For
example:
FORALL i IN 2 .. 5
Creates a loop that executes four times in the first iteration, the index (i) is set to the
value 2; in the second iteration, the index is set to the value 3, and so on. The loop
executes for the value 5 and then terminates.
The following example creates a table (emp_copy) that is an empty copy of the emp
table. The example declares a type (emp_tbl) that is an array where each element in the
array is of composite type, composed of the column definitions used to create the table,
emp. The example also creates an index on the emp_tbl type.
t_emp is an associative array, of type emp_tbl. The SELECT statement uses the BULK
COLLECT INTO command to populate the t_emp array. After the t_emp array is
populated, the FORALL statement iterates through the values (i) in the t_emp array index
and inserts a row for each record into emp_copy.
DECLARE
t_emp emp_tbl;
BEGIN
SELECT * FROM emp BULK COLLECT INTO t_emp;
END;
The following example uses a FORALL statement to update the salary of three employees:
DECLARE
TYPE empno_tbl IS TABLE OF emp.empno%TYPE INDEX BY BINARY_INTEGER;
TYPE sal_tbl IS TABLE OF emp.ename%TYPE INDEX BY BINARY_INTEGER;
t_empno EMPNO_TBL;
t_sal SAL_TBL;
BEGIN
DECLARE
TYPE empno_tbl IS TABLE OF emp.empno%TYPE INDEX BY BINARY_INTEGER;
t_empno EMPNO_TBL;
BEGIN
t_empno(1) := 9001;
t_empno(2) := 9002;
t_empno(3) := 9003;
FORALL i IN t_empno.FIRST..t_empno.LAST
DELETE FROM emp WHERE empno = t_empno(i);
END;
SQL commands that return a result set consisting of a large number of rows may not be
operating as efficiently as possible due to the constant context switching that must occur
between the database server and the client in order to transfer the entire result set. This
inefficiency can be mitigated by using a collection to gather the entire result set in
memory which the client can then access. The BULK COLLECT clause is used to specify
the aggregation of the result set into a collection.
The BULK COLLECT clause can be used with the SELECT INTO, FETCH INTO and
EXECUTE IMMEDIATE commands, and with the RETURNING INTO clause of the
DELETE, INSERT, and UPDATE commands. Each of these is illustrated in the following
sections.
The BULK COLLECT clause can be used with the SELECT INTO statement as follows.
(Refer to Section 3.4.3 for additional information on the SELECT INTO statement.)
The following example shows the use of the BULK COLLECT clause where the target
collections are associative arrays consisting of a single field.
DECLARE
TYPE empno_tbl IS TABLE OF emp.empno%TYPE INDEX BY BINARY_INTEGER;
TYPE ename_tbl IS TABLE OF emp.ename%TYPE INDEX BY BINARY_INTEGER;
TYPE job_tbl IS TABLE OF emp.job%TYPE INDEX BY BINARY_INTEGER;
TYPE hiredate_tbl IS TABLE OF emp.hiredate%TYPE INDEX BY BINARY_INTEGER;
TYPE sal_tbl IS TABLE OF emp.sal%TYPE INDEX BY BINARY_INTEGER;
TYPE comm_tbl IS TABLE OF emp.comm%TYPE INDEX BY BINARY_INTEGER;
TYPE deptno_tbl IS TABLE OF emp.deptno%TYPE INDEX BY BINARY_INTEGER;
t_empno EMPNO_TBL;
t_ename ENAME_TBL;
t_job JOB_TBL;
t_hiredate HIREDATE_TBL;
t_sal SAL_TBL;
t_comm COMM_TBL;
t_deptno DEPTNO_TBL;
BEGIN
SELECT empno, ename, job, hiredate, sal, comm, deptno BULK COLLECT
INTO t_empno, t_ename, t_job, t_hiredate, t_sal, t_comm, t_deptno
FROM emp;
DBMS_OUTPUT.PUT_LINE('EMPNO ENAME JOB HIREDATE ' ||
'SAL ' || 'COMM DEPTNO');
DBMS_OUTPUT.PUT_LINE('----- ------- --------- --------- ' ||
'-------- ' || '-------- ------');
FOR i IN 1..t_empno.COUNT LOOP
DBMS_OUTPUT.PUT_LINE(t_empno(i) || ' ' ||
RPAD(t_ename(i),8) || ' ' ||
RPAD(t_job(i),10) || ' ' ||
TO_CHAR(t_hiredate(i),'DD-MON-YY') || ' ' ||
TO_CHAR(t_sal(i),'99,999.99') || ' ' ||
TO_CHAR(NVL(t_comm(i),0),'99,999.99') || ' ' ||
t_deptno(i));
END LOOP;
END;
The following example produces the same result, but uses an associative array on a
record type defined with the %ROWTYPE attribute.
DECLARE
TYPE emp_tbl IS TABLE OF emp%ROWTYPE INDEX BY BINARY_INTEGER;
t_emp EMP_TBL;
BEGIN
SELECT * BULK COLLECT INTO t_emp FROM emp;
DBMS_OUTPUT.PUT_LINE('EMPNO ENAME JOB HIREDATE ' ||
'SAL ' || 'COMM DEPTNO');
DBMS_OUTPUT.PUT_LINE('----- ------- --------- --------- ' ||
'-------- ' || '-------- ------');
FOR i IN 1..t_emp.COUNT LOOP
DBMS_OUTPUT.PUT_LINE(t_emp(i).empno || ' ' ||
RPAD(t_emp(i).ename,8) || ' ' ||
RPAD(t_emp(i).job,10) || ' ' ||
TO_CHAR(t_emp(i).hiredate,'DD-MON-YY') || ' ' ||
TO_CHAR(t_emp(i).sal,'99,999.99') || ' ' ||
TO_CHAR(NVL(t_emp(i).comm,0),'99,999.99') || ' ' ||
t_emp(i).deptno);
END LOOP;
END;
The BULK COLLECT clause can be used with a FETCH statement. (See Section 3.8.3 for
information on the FETCH statement.) Instead of returning a single row at a time from the
result set, the FETCH BULK COLLECT will return all rows at once from the result set into
the specified collection unless restricted by the LIMIT clause.
The following example uses the FETCH BULK COLLECT statement to retrieve rows into
an associative array.
DECLARE
TYPE emp_tbl IS TABLE OF emp%ROWTYPE INDEX BY BINARY_INTEGER;
t_emp EMP_TBL;
CURSOR emp_cur IS SELECT * FROM emp;
BEGIN
OPEN emp_cur;
FETCH emp_cur BULK COLLECT INTO t_emp;
CLOSE emp_cur;
DBMS_OUTPUT.PUT_LINE('EMPNO ENAME JOB HIREDATE ' ||
'SAL ' || 'COMM DEPTNO');
DBMS_OUTPUT.PUT_LINE('----- ------- --------- --------- ' ||
'-------- ' || '-------- ------');
FOR i IN 1..t_emp.COUNT LOOP
DBMS_OUTPUT.PUT_LINE(t_emp(i).empno || ' ' ||
RPAD(t_emp(i).ename,8) || ' ' ||
RPAD(t_emp(i).job,10) || ' ' ||
TO_CHAR(t_emp(i).hiredate,'DD-MON-YY') || ' ' ||
TO_CHAR(t_emp(i).sal,'99,999.99') || ' ' ||
TO_CHAR(NVL(t_emp(i).comm,0),'99,999.99') || ' ' ||
t_emp(i).deptno);
END LOOP;
END;
The BULK COLLECT clause can be used with a EXECUTE IMMEDIATE statement to
specify a collection to receive the returned rows.
bind_argument specifies a parameter that contains a value that is either passed to the
sql_expression (specified with a bind_type of IN), or that receives a value from the
sql_expression (specified with a bind_type of OUT), or both (specified with a
bind_type of IN OUT).
The BULK COLLECT clause can be added to the RETURNING INTO clause of a DELETE,
INSERT, or UPDATE command. (See Section 3.4.7 for information on the RETURNING
INTO clause.)
insert, update, and delete are the INSERT, UPDATE, and DELETE commands as
described in Sections 3.4.4, 3.4.5, and 3.4.6, respectively. If a single collection is
specified, then collection may be a collection of a single field, or it may be a
collection of a record type. If more than one collection is specified, then each
collection must consist of a single field. The expressions following the RETURNING
keyword must match in number, order, and type-compatibility all fields in the target
collections. If * is specified, then all columns in the affected table are returned. (Note that
the use of * is an Advanced Server extension and is not compatible with Oracle
databases.)
The clerkemp table created by copying the emp table is used in the remaining examples
in this section as shown below.
The following example increases everyones salary by 1.5, stores the employees
numbers, names, and new salaries in three associative arrays, and finally, displays the
contents of these arrays.
DECLARE
TYPE empno_tbl IS TABLE OF emp.empno%TYPE INDEX BY BINARY_INTEGER;
TYPE ename_tbl IS TABLE OF emp.ename%TYPE INDEX BY BINARY_INTEGER;
TYPE sal_tbl IS TABLE OF emp.sal%TYPE INDEX BY BINARY_INTEGER;
t_empno EMPNO_TBL;
t_ename ENAME_TBL;
t_sal SAL_TBL;
BEGIN
UPDATE clerkemp SET sal = sal * 1.5 RETURNING empno, ename, sal
BULK COLLECT INTO t_empno, t_ename, t_sal;
DBMS_OUTPUT.PUT_LINE('EMPNO ENAME SAL ');
DBMS_OUTPUT.PUT_LINE('----- ------- -------- ');
FOR i IN 1..t_empno.COUNT LOOP
DBMS_OUTPUT.PUT_LINE(t_empno(i) || ' ' || RPAD(t_ename(i),8) ||
' ' || TO_CHAR(t_sal(i),'99,999.99'));
END LOOP;
END;
The following example performs the same functionality as the previous example, but uses
a single collection defined with a record type to store the employees numbers, names,
and new salaries.
DECLARE
The following example deletes all rows from the clerkemp table, and returns
information on the deleted rows into an associative array, which is then displayed.
DECLARE
TYPE emp_rec IS RECORD (
empno emp.empno%TYPE,
ename emp.ename%TYPE,
job emp.job%TYPE,
hiredate emp.hiredate%TYPE,
sal emp.sal%TYPE,
comm emp.comm%TYPE,
deptno emp.deptno%TYPE
);
TYPE emp_tbl IS TABLE OF emp_rec INDEX BY BINARY_INTEGER;
r_emp EMP_TBL;
BEGIN
DELETE FROM clerkemp RETURNING empno, ename, job, hiredate, sal,
comm, deptno BULK COLLECT INTO r_emp;
DBMS_OUTPUT.PUT_LINE('EMPNO ENAME JOB HIREDATE ' ||
'SAL ' || 'COMM DEPTNO');
DBMS_OUTPUT.PUT_LINE('----- ------- --------- --------- ' ||
'-------- ' || '-------- ------');
FOR i IN 1..r_emp.COUNT LOOP
DBMS_OUTPUT.PUT_LINE(r_emp(i).empno || ' ' ||
RPAD(r_emp(i).ename,8) || ' ' ||
RPAD(r_emp(i).job,10) || ' ' ||
TO_CHAR(r_emp(i).hiredate,'DD-MON-YY') || ' ' ||
TO_CHAR(r_emp(i).sal,'99,999.99') || ' ' ||
TO_CHAR(NVL(r_emp(i).comm,0),'99,999.99') || ' ' ||
r_emp(i).deptno);
END LOOP;
END;
DBMS_OUTPUT.PUT_LINE ( message );
The special variables SQLCODE and SQLERRM contain a numeric code and a text message,
respectively, that describe the outcome of the last SQL command issued. If any other
error occurs in the program such as division by zero, these variables contain information
pertaining to the error.
4 Triggers
This chapter describes triggers in Advanced Server. As with procedures and functions,
triggers are written in the SPL language.
4.1 Overview
A trigger is a named SPL code block that is associated with a table and stored in the
database. When a specified event occurs on the associated table, the SPL code block is
executed. The trigger is said to be fired when the code block is executed.
The event that causes a trigger to fire can be any combination of an insert, update, or
deletion carried out on the table, either directly or indirectly. If the table is the object of a
SQL INSERT, UPDATE, or DELETE command the trigger is directly fired assuming that
the corresponding insert, update, or deletion event is defined as a triggering event. The
events that fire the trigger are defined in the CREATE TRIGGER command.
A trigger can be fired indirectly if a triggering event occurs on the table as a result of an
event initiated on another table. For example, if a trigger is defined on a table containing
a foreign key defined with the ON DELETE CASCADE clause and a row in the parent
table is deleted, all children of the parent would be deleted as well. If deletion is a
triggering event on the child table, deletion of the children will cause the trigger to fire.
In contrast, a statement-level trigger fires once per triggering statement regardless of the
number of rows affected by the triggering event. In the prior example of a single DELETE
command deleting five rows, a statement-level trigger would fire only once.
The sequence of actions can be defined regarding whether the trigger code block is
executed before or after the triggering statement, itself, in the case of statement-level
triggers; or before or after each row is affected by the triggering statement in the case of
row-level triggers.
In a before row-level trigger, the trigger code block is executed before the triggering
action is carried out on each affected row. In a before statement-level trigger, the trigger
code block is executed before the action of the triggering statement is carried out.
In an after row-level trigger, the trigger code block is executed after the triggering action
is carried out on each affected row. In an after statement-level trigger, the trigger code
block is executed after the action of the triggering statement is carried out.
The CREATE TRIGGER command defines and names a trigger that will be stored in the
database.
Name
Synopsis
Description
CREATE TRIGGER defines a new trigger. CREATE OR REPLACE TRIGGER will either
create a new trigger, or replace an existing definition.
If you are using the CREATE TRIGGER keywords to create a new trigger, the name of the
new trigger must not match any existing trigger defined on the same table. New triggers
will be created in the same schema as the table on which the triggering event is defined.
If you are updating the definition of an existing trigger, use the CREATE OR REPLACE
TRIGGER keywords.
When you use syntax compatible with Oracle databases to create a trigger, the trigger
runs as a SECURITY DEFINER function.
Parameters
name
BEFORE | AFTER
Determines whether the trigger is fired before or after the triggering event.
table
condition
If the trigger definition includes the FOR EACH ROW keywords, the WHEN clause
can refer to columns of the old and/or new row values by writing
OLD.column_name or NEW.column_name respectively. INSERT triggers cannot
refer to OLD and DELETE triggers cannot refer to NEW.
If the trigger includes the INSTEAD OF keywords, it may not include a WHEN
clause.
REFERENCING clause to reference old rows and new rows, but restricted in that
old may only be replaced by an identifier named old or any equivalent that is
saved in all lowercase (for example, REFERENCING OLD AS old,
REFERENCING OLD AS OLD, or REFERENCING OLD AS "old"). Also, new
may only be replaced by an identifier named new or any equivalent that is saved
in all lowercase (for example, REFERENCING NEW AS new, REFERENCING
NEW AS NEW, or REFERENCING NEW AS "new").
Either one, or both phrases OLD AS old and NEW AS new may be specified in
the REFERENCING clause (for example, REFERENCING NEW AS New OLD AS
Old).
See Section 3.4 for information on how these identifiers are used as pseudo-
record names to reference old rows and new rows.
This clause is not compatible with Oracle databases in that identifiers other than
old or new may not be used.
Determines whether the trigger should be fired once for every row affected by the
triggering event, or just once per SQL statement. If specified, the trigger is fired
once for every affected row (row-level trigger), otherwise the trigger is a
statement-level trigger.
declaration
statement
exception
NEW
NEW is a pseudo-record name that refers to the new table row for insert and update
operations in row-level triggers. This variable is not applicable in statement-level
triggers and in delete operations of row-level triggers.
Its usage is: :NEW.column where column is the name of a column in the table on
which the trigger is defined.
The initial content of :NEW.column is the value in the named column of the new
row to be inserted or of the new row that is to replace the old one when used in a
before row-level trigger. When used in an after row-level trigger, this value has
already been stored in the table since the action has already occurred on the
affected row.
In the trigger code block, :NEW.column can be used like any other variable. If a
value is assigned to :NEW.column, in the code block of a before row-level trigger,
the assigned value will be used in the new inserted or updated row.
OLD
OLD is a pseudo-record name that refers to the old table row for update and delete
operations in row-level triggers. This variable is not applicable in statement-level
triggers and in insert operations of row-level triggers.
Its usage is: :OLD.column where column is the name of a column in the table on
which the trigger is defined.
The initial content of :OLD.column is the value in the named column of the row
to be deleted or of the old row that is to be replaced by the new one when used in
a before row-level trigger. When used in an after row-level trigger, this value is
no longer stored in the table since the action has already occurred on the affected
row.
In the trigger code block, :OLD.column can be used like any other variable.
Assigning a value to :OLD.column, has no effect on the action of the trigger.
INSERTING
DELETING
If an exception does occur within the trigger code block, but it is caught and handled in
an exception section, the effects of any DML commands within the trigger are still rolled
back nonetheless. The triggering statement itself, however, is not rolled back unless the
application forces a roll back of the encapsulating transaction.
If an unhandled exception occurs within the trigger code block, the transaction that
encapsulates the trigger is aborted and rolled back. Therefore the effects of any DML
commands within the trigger and the triggering statement, itself are all rolled back.
The following INSERT is constructed so that several new rows are inserted upon a single
execution of the command. For each row that has an employee id between 7900 and
7999, a new row is inserted with an employee id incremented by 1000. The following are
the results of executing the command when three new rows are inserted.
INSERT INTO emp (empno, ename, deptno) SELECT empno + 1000, ename, 40
FROM emp WHERE empno BETWEEN 7900 AND 7999;
New employees are about to be added
SELECT empno, ename, deptno FROM emp WHERE empno BETWEEN 8900 AND 8999;
The message, New employees are about to be added, is displayed once by the
firing of the trigger even though the result is the addition of three new rows.
In the following sequence of commands, two rows are inserted into the emp table using
two INSERT commands. The sal and comm columns of both rows are updated with one
UPDATE command. Finally, both rows are deleted with one DELETE command.
UPDATE emp SET sal = 4000.00, comm = 1200.00 WHERE empno IN (9001, 9002);
The contents of the empauditlog table show how many times the trigger was fired -
once each for the two inserts, once for the update (even though two rows were changed)
and once for the deletion (even though two rows were deleted).
The following example is a before row-level trigger that calculates the commission of
every new employee belonging to department 30 that is inserted into the emp table.
The listing following the addition of the two employees shows that the trigger computed
their commissions and inserted it as part of the new employee rows.
INSERT INTO emp VALUES (9005,'ROBERS','SALESMAN',7782,SYSDATE,3000.00,NULL,30);
The following example is an after row-level trigger. When a new employee row is
inserted, the trigger adds a new row to the jobhist table for that employee. When an
existing employee is updated, the trigger sets the enddate column of the latest jobhist
row (assumed to be the one with a null enddate) to the current date and inserts a new
jobhist row with the employees new information.
Finally, trigger adds a row to the empchglog table with a description of the action.
In the first sequence of commands shown below, two employees are added using two
separate INSERT commands and then both are updated using a single UPDATE command.
The contents of the jobhist table shows the action of the trigger for each affected row -
two new hire entries for the two new employees and two changed commission records for
the updated commissions on the two employees. The empchglog table also shows the
trigger was fired a total of four times, once for each action on the two rows.
INSERT INTO emp VALUES (9003,'PETERS','ANALYST',7782,SYSDATE,5000.00,NULL,40);
UPDATE emp SET comm = sal * 1.1 WHERE empno IN (9003, 9004);
CHG_DATE CHG_DESC
--------- ------------------------------
31-MAR-05 Added employee # 9003
31-MAR-05 Added employee # 9004
31-MAR-05 Updated employee # 9003
31-MAR-05 Updated employee # 9004
Finally, both employees are deleted with a single DELETE command. The empchglog
table now shows the trigger was fired twice, once for each deleted employee.
CHG_DATE CHG_DESC
--------- ------------------------------
31-MAR-05 Added employee # 9003
31-MAR-05 Added employee # 9004
31-MAR-05 Updated employee # 9003
31-MAR-05 Updated employee # 9004
31-MAR-05 Deleted employee # 9003
31-MAR-05 Deleted employee # 9004
5 Packages
Advanced Server provides a collection of packages that provide compatibility with
Oracle packages.
For more information about the package support provided by Advanced Server, please
see the Database Compatibility for Oracle Developers Built-in Package Guide, available
at:
https://www.enterprisedb.com/docs/en/9.6/DB_Compat_Oracle_Built_in_Package/Datab
ase_Compatibility_for_Oracle_Developers_Built-in_Package_Guide.1.01.html
For a list of built-in packages, see the Table of Contents, beginning with Chapter 3
"Built-In Packages" of the Database Compatibility for Oracle Developers Built-in
Package Guide, available at:
https://www.enterprisedb.com/docs/en/9.6/DB_Compat_Oracle_Built_in_Package/toc.ht
ml
Note: The terms database objects and objects that have been used in this document
up to this point should not be confused with an object type and object as used in this
chapter. The previous usage of these terms relates to the entities that can be created in a
database such as tables, views, indexes, users, etc. Within the context of this chapter,
object type and object refer to specific data structures supported by the SPL programming
language to implement object-oriented concepts.
Note: In Oracle, the term abstract data type (ADT) is used to describe object types in
PL/SQL. The SPL implementation of object types is intended to be compatible with
Oracle abstract data types.
Note: Advanced Server has not yet implemented support for some features of object-
oriented programming languages. This chapter documents only those features that have
been implemented.
6.1.1 Attributes
Every object type must contain at least one attribute. The data type of an attribute can be
any of the following:
An attribute gets its initial value (which may be null) when an object instance is initially
created. Each object instance has its own set of attribute values.
6.1.2 Methods
Methods are SPL procedures or functions defined within an object type. Methods can be
categorized into three general types:
In an object type it is permissible to define two or more identically named methods of the
same type (this is, either a procedure or function), but with different signatures. Such
methods are referred to as overloaded methods.
A methods signature consists of the number of formal parameters, the data types of its
formal parameters, and their order.
The object type specification - This is the public interface specifying the attributes
and method signatures of the object type.
The object type body - This contains the implementation of the methods specified
in the object type specification.
The following sections describe the commands used to create the object type
specification and the object type body.
{ MEMBER | STATIC }
{ PROCEDURE proc_name
[ ( [ SELF [ IN | IN OUT ] name ]
[, parm1 [ IN | IN OUT | OUT ] datatype1
[ DEFAULT value1 ] ]
[, parm2 [ IN | IN OUT | OUT ] datatype2
[ DEFAULT value2 ]
] ...)
]
|
FUNCTION func_name
[ ( [ SELF [ IN | IN OUT ] name ]
[, parm1 [ IN | IN OUT | OUT ] datatype1
[ DEFAULT value1 ] ]
CONSTRUCTOR func_name
[ ( [ SELF [ IN | IN OUT ] name ]
[, parm1 [ IN | IN OUT | OUT ] datatype1
[ DEFAULT value1 ] ]
[, parm2 [ IN | IN OUT | OUT ] datatype2
[ DEFAULT value2 ]
] ...)
]
RETURN self AS RESULT
Note: The OR REPLACE option cannot be currently used to add, delete, or modify the
attributes of an existing object type. Use the DROP TYPE command to first delete the
existing object type. The OR REPLACE option can be used to add, delete, or modify the
methods in an existing object type.
Note: The PostgreSQL form of the ALTER TYPE ALTER ATTRIBUTE command can be
used to change the data type of an attribute in an existing object type. However, the
ALTER TYPE command cannot add or delete attributes in the object type.
If the AUTHID clause is omitted or DEFINER is specified, the rights of the object type
owner are used to determine access privileges to database objects. If CURRENT_USER is
specified, the rights of the current user executing a method in the object are used to
determine access privileges.
Following the closing parenthesis of the CREATE TYPE definition, [ NOT ] FINAL
specifies whether or not a subtype can be derived from this object type. FINAL, which is
the default, means that no subtypes can be derived from this object type. Specify NOT
FINAL if you want to allow subtypes to be defined under this object type.
Note: Even though the specification of NOT FINAL is accepted in the CREATE TYPE
command, SPL does not currently support the creation of subtypes.
Note: Even though the specification of NOT INSTANTIABLE is accepted in the CREATE
TYPE command, SPL does not currently support the creation of subtypes.
Prior to the definition of a method, [ NOT ] FINAL specifies whether or not the method
can be overridden in a subtype. NOT FINAL is the default meaning the method can be
overridden in a subtype.
There may be none, one, or more methods defined in the object type.
For each member method there is an implicit, built-in parameter named SELF,
whose data type is that of the object type being defined.
SELF refers to the object instance that is currently invoking the method. SELF
can be explicitly declared as an IN or IN OUT parameter in the parameter list (for
example as MEMBER FUNCTION (SELF IN OUT object_type ...)).
If SELF is explicitly declared, SELF must be the first parameter in the parameter
list. If SELF is not explicitly declared, its parameter mode defaults to IN OUT for
member procedures and IN for member functions.
subprogram_spec
{ MEMBER | STATIC }
{ PROCEDURE proc_name
[ ( [ SELF [ IN | IN OUT ] name ]
[, parm1 [ IN | IN OUT | OUT ] datatype1
[ DEFAULT value1 ] ]
[, parm2 [ IN | IN OUT | OUT ] datatype2
[ DEFAULT value2 ]
] ...)
]
{ IS | AS }
[ declarations ]
BEGIN
statement; ...
[ EXCEPTION
WHEN ... THEN
statement; ...]
END;
|
FUNCTION func_name
[ ( [ SELF [ IN | IN OUT ] name ]
[, parm1 [ IN | IN OUT | OUT ] datatype1
[ DEFAULT value1 ] ]
[, parm2 [ IN | IN OUT | OUT ] datatype2
[ DEFAULT value2 ]
] ...)
]
RETURN return_type
{ IS | AS }
[ declarations ]
BEGIN
statement; ...
[ EXCEPTION
WHEN ... THEN
statement; ...]
CONSTRUCTOR func_name
[ ( [ SELF [ IN | IN OUT ] name ]
[, parm1 [ IN | IN OUT | OUT ] datatype1
[ DEFAULT value1 ] ]
[, parm2 [ IN | IN OUT | OUT ] datatype2
[ DEFAULT value2 ]
] ...)
]
RETURN self AS RESULT
{ IS | AS }
[ declarations ]
BEGIN
statement; ...
[ EXCEPTION
WHEN ... THEN
statement; ...]
END;
You can use the CREATE TYPE command to create an object type specification, and the
CREATE TYPE BODY command to create an object type body. This section provides
some examples using the CREATE TYPE and CREATE TYPE BODY commands.
The first example creates the addr_object_type object type that contains only
attributes and no methods:
Since there are no methods in this object type, an object type body is not required. This
example creates a composite type, which allows you to treat related objects as a single
attribute.
A member method is a function or procedure that is defined within an object type and can
only be invoked through an instance of that type. Member methods have access to, and
can change the attributes of, the object instance on which they are operating.
The following object type specification creates the emp_obj_typ object type:
A SELF parameter is a parameter whose data type is that of the object type being defined.
SELF always refers to the instance that is invoking the method. A SELF parameter is the
first parameter in a member procedure or function regardless of whether it is explicitly
declared in the parameter list.
The following code snippet defines an object type body for emp_obj_typ:
You can also use the SELF parameter in an object type body. To illustrate how the SELF
parameter would be used in the CREATE TYPE BODY command, the preceding object type
body could be written as follows:
Like a member method, a static method belongs to a type. A static method, however, is
invoked not by an instance of the type, but by using the name of the type. For example,
to invoke a static function named get_count, defined within the emp_obj_type type,
you would write:
emp_obj_type.get_count();
A static method does not have access to, and cannot change the attributes of an object
instance, and does not typically work with an instance of the type.
The following object type specification includes a static function get_dname and a
member procedure display_dept:
The object type body for dept_obj_typ defines a static function named get_dname
and a member procedure named display_dept:
Within the static function get_dname, there can be no references to SELF. Since a static
function is invoked independently of any object instance, it has no implicit access to any
object attribute.
Member procedure display_dept can access the deptno attribute of the object
instance passed in the SELF parameter. It is not necessary to explicitly declare the SELF
parameter in the display_dept parameter list.
For example, if you define a type named address, each constructor is named address.
You may overload a constructor by creating one or more different constructor functions
with the same name, but with different argument types.
The SPL compiler will provide a default constructor for each object type. The default
constructor is a member function whose name matches the name of the type and whose
argument list matches the type members (in order). For example, given an object type
such as:
The SPL compiler will provide a default constructor with the following signature:
The body of the default constructor simply sets each member to NULL.
To create a custom constructor, declare the constructor function (using the keyword
constructor) in the CREATE TYPE command and define the construction function in the
CREATE TYPE BODY command. For example, you may wish to create a custom
constructor for the address type which computes the city and state given a
street_address and postal_code:
To create an instance of an object type, you invoke one of the constructor methods for
that type. For example:
DECLARE
cust_addr address := address('100 Main Street', 02203');
BEGIN
DBMS_OUTPUT.PUT_LINE(cust_addr.city); -- displays Boston
DBMS_OUTPUT.PUT_LINE(cust_addr.state); -- displays MA
END;
Custom constructor functions are typically used to compute member values when given
incomplete information. The preceding example computes the values for city and
state when given a postal code.
Custom constructor functions are also used to enforce business rules that restrict the state
of an object. For example, if you define an object type to represent a payment, you can
use a custom constructor to ensure that no object of type payment can be created with an
amount that is NULL, negative, or zero. The default constructor would set
payment.amount to NULL so you must create a custom constructor (whose signature
matches the default constructor) to prohibit NULL amounts.
object obj_type
After declaring the object variable, you must invoke a constructor method to initialize the
object with values. Use the following syntax to invoke the constructor method:
obj_type is the identifier of the object types constructor method; the constructor
method has the same name as the previously declared object type.
expr1, expr2, are expressions that are type-compatible with the first attribute of the
object type, the second attribute of the object type, etc. If an attribute is of an object type,
then the corresponding expression can be NULL, an object initialization expression, or any
expression that returns that object type.
DECLARE
v_emp EMP_OBJ_TYP;
BEGIN
v_emp := emp_obj_typ (9001,'JONES',
addr_obj_typ('123 MAIN STREET','EDISON','NJ',08817));
END;
The variable (v_emp) is declared with a previously defined object type named
EMP_OBJ_TYPE. The body of the block initializes the variable using the emp_obj_typ
and addr_obj_type constructors.
You can include the NEW keyword when creating a new instance of an object in the body
of a block. The NEW keyword invokes the object constructor whose signature matches the
arguments provided.
The following example declares two variables, named mgr and emp. The variables are
both of EMP_OBJ_TYPE. The mgr object is initialized in the declaration, while the emp
object is initialized to NULL in the declaration, and assigned a value in the body.
DECLARE
mgr EMP_OBJ_TYPE := (9002,'SMITH');
Note: In Advanced Server, the following alternate syntax can be used in place of the
constructor method.
ROW is an optional keyword if two or more terms are specified within the parenthesis-
enclosed, comma-delimited list. If only one term is specified, then specification of the
ROW keyword is mandatory.
object.attribute
object is the identifier assigned to the object variable. attribute is the identifier of an
object type attribute.
If attribute, itself, is of an object type, then the reference must take the form:
object.attribute.attribute_inner
The following example expands upon the previous anonymous block to display the
values assigned to the emp_obj_typ object.
DECLARE
v_emp EMP_OBJ_TYP;
BEGIN
v_emp := emp_obj_typ(9001,'JONES',
addr_obj_typ('123 MAIN STREET','EDISON','NJ',08817));
DBMS_OUTPUT.PUT_LINE('Employee No : ' || v_emp.empno);
DBMS_OUTPUT.PUT_LINE('Name : ' || v_emp.ename);
DBMS_OUTPUT.PUT_LINE('Street : ' || v_emp.addr.street);
DBMS_OUTPUT.PUT_LINE('City/State/Zip: ' || v_emp.addr.city || ', ' ||
v_emp.addr.state || ' ' || LPAD(v_emp.addr.zip,5,'0'));
END;
Employee No : 9001
Name : JONES
Street : 123 MAIN STREET
City/State/Zip: EDISON, NJ 08817
Once an object variable is created and initialized, member procedures or functions are
called using dot notation of the form:
object.prog_name
object is the identifier assigned to the object variable. prog_name is the identifier of
the procedure or function.
Static procedures or functions are not called utilizing an object variable. Instead the
procedure or function is called utilizing the object type name:
object_type.prog_name
object_type is the identifier assigned to the object type. prog_name is the identifier
of the procedure or function.
The results of the previous anonymous block can be duplicated by calling the member
procedure display_emp:
DECLARE
v_emp EMP_OBJ_TYP;
BEGIN
v_emp := emp_obj_typ(9001,'JONES',
addr_obj_typ('123 MAIN STREET','EDISON','NJ',08817));
v_emp.display_emp;
END;
Employee No : 9001
Name : JONES
Street : 123 MAIN STREET
City/State/Zip: EDISON, NJ 08817
The following anonymous block creates an instance of dept_obj_typ and calls the
member procedure display_dept:
DECLARE
v_dept DEPT_OBJ_TYP := dept_obj_typ (20);
BEGIN
v_dept.display_dept;
END;
Dept No : 20
Dept Name : RESEARCH
BEGIN
DBMS_OUTPUT.PUT_LINE(dept_obj_typ.get_dname(20));
END;
RESEARCH
objtype is the identifier of the object type to be dropped. If the definition of objtype
contains attributes that are themselves object types or collection types, these nested object
types or collection types must be dropped last.
If an object type body is defined for the object type, the DROP TYPE command deletes
the object type body as well as the object type specification. In order to recreate the
complete object type, both the CREATE TYPE and CREATE TYPE BODY commands must
be reissued.
The following example drops the emp_obj_typ and the addr_obj_typ object types
created earlier in this chapter. emp_obj_typ must be dropped first since it contains
addr_obj_typ within its definition as an attribute.
The syntax for deleting an object type body, but not the object type specification is as
follows.
The object type body can be recreated by issuing the CREATE TYPE BODY command.
The following example drops only the object type body of the dept_obj_typ.
The following diagram compares the Open Client Library and Oracle Call Interface
application stacks.
For detailed usage information about the Open Client Library and the supported
functions, please see the EDB Postgres Advanced Server OCI Connector Guide, available
at:
http://www.enterprisedb.com/products-services-training/products/documentation
Please note: EnterpriseDB does not support use of the Open Client Library with Oracle
Real Application Clusters (RAC) and Oracle Exadata; the aforementioned Oracle
products have not been evaluated nor certified with this EnterpriseDB product.
http://www.enterprisedb.com/products-services-training/products/documentation
EDB*Plus
EDB*Loader
EDB*Wrap
For detailed information about the functionality supported by Advanced Server, please
consult the Database Compatibility for Oracle Developers Tools and Utilities Guide,
available at:
http://www.enterprisedb.com/products-services-training/products/documentation
10 Table Partitioning
In a partitioned table, one logically large table is broken into smaller physical pieces.
Partitioning can provide several benefits:
Table partitioning is worthwhile only when a table would otherwise be very large. The
exact point at which a table will benefit from partitioning depends on the application; a
good rule of thumb is that the size of the table should exceed the physical memory of the
database server.
This document discusses the aspects of table partitioning compatible with Oracle
databases that are supported by Advanced Server.
When you create a partitioned table, you specify LIST, RANGE, or HASH partitioning
rules. The partitioning rules provide a set of constraints that define the data that is stored
in each partition. As new rows are added to the partitioned table, the server uses the
partitioning rules to decide which partition should contain each row.
Advanced Server can also use the partitioning rules to enforce partition pruning,
improving performance when responding to user queries. When selecting a partitioning
type and partitioning keys for a table, you should take into consideration how the data
that is stored within a table will be queried, and include often-queried columns in the
partitioning rules.
List Partitioning
When you create a list-partitioned table, you specify a single partitioning key column.
When adding a row to the table, the server compares the key values specified in the
partitioning rule to the corresponding column within the row. If the column value
matches a value in the partitioning rule, the row is stored in the partition named in the
rule.
Range Partitioning
When you create a range-partitioned table, you specify one or more partitioning key
columns. When you add a new row to the table, the server compares the value of the
partitioning key (or keys) to the corresponding column (or columns) in a table entry. If
the column values satisfy the conditions specified in the partitioning rule, the row is
stored in the partition named in the rule.
Hash Partitioning
When you create a hash-partitioned table, you specify one or more partitioning key
columns. Data is divided into (approx.) equal-sized partitions amongst the specified
partitions. When you add a row to a hash-partitioned table, the server computes a hash
value for the data in the specified column (or columns), and stores the row in a partition
according to the hash value.
Subpartitioning
Subpartitioning breaks a partitioned table into smaller subsets. All subsets must be stored
in the same database server cluster. A table is typically subpartitioned by a different set
of columns, and may be a different subpartitioning type than the parent partition. If one
partition is subpartitioned, then each partition will have at least one subpartition.
If a table is subpartitioned, no data will be stored in any of the partition tables; the data
will be stored instead in the corresponding subpartitions.
Fast Pruning
Constraint exclusion
Partition pruning techniques limit the search for data to only those partitions in which the
values for which you are searching might reside. Both pruning techniques remove
partitions from a query's execution plan, increasing performance.
The difference between the fast pruning and constraint exclusion is that fast pruning
understands the relationship between the partitions in an Oracle-partitioned table, while
constraint exclusion does not. For example, when a query searches for a specific value
within a list-partitioned table, fast pruning can reason that only a specific partition may
hold that value, while constraint exclusion must examine the constraints defined for each
partition. Fast pruning occurs early in the planning process to reduce the number of
partitions that the planner must consider, while constraint exclusion occurs late in the
planning process.
https://www.postgresql.org/docs/10/static/ddl-partitioning.html
When constraint exclusion is enabled, the server examines the constraints defined for
each partition to determine if that partition can satisfy a query.
When you execute a SELECT statement that does not contain a WHERE clause, the query
planner must recommend an execution plan that searches the entire table. When you
execute a SELECT statement that does contain a WHERE clause, the query planner
determines in which partition that row would be stored, and sends query fragments to that
partition, pruning the partitions that could not contain that row from the execution plan.
If you are not using partitioned tables, disabling constraint exclusion may improve
performance.
Fast Pruning
Like constraint exclusion, fast pruning can only optimize queries that include a WHERE
(or join) clause, and only when the qualifiers in the WHERE clause match a certain form.
In both cases, the query planner will avoid searching for data within partitions that cannot
possibly hold the data required by the query.
Please note: Fast pruning cannot optimize queries against subpartitioned tables or
optimize queries against range-partitioned tables that are partitioned on more than one
column.
For LIST-partitioned tables, Advanced Server can fast prune queries that contain a
WHERE clause that constrains a partitioning column to a literal value. For example, given
a LIST-partitioned table such as:
(
PARTITION americas VALUES('US', 'CA', 'MX'),
PARTITION europe VALUES('BE', 'NL', 'FR'),
PARTITION asia VALUES('JP', 'PK', 'CN'),
PARTITION others VALUES(DEFAULT)
)
Given the first WHERE clause, fast pruning would eliminate partitions europe, asia, and
others because those partitions cannot hold rows that satisfy the qualifier: WHERE
country = 'US'.
Given the second WHERE clause, fast pruning would eliminate partitions americas,
europe, and asia because those partitions cannot hold rows where country IS NULL.
The operator specified in the WHERE clause must be an equal sign (=) or the equality
operator appropriate for the data type of the partitioning column.
For range-partitioned tables, Advanced Server can fast prune queries that contain a
WHERE clause that constrains a partitioning column to a literal value, but the operator may
be any of the following:
>
>=
=
<=
<
Fast pruning will also reason about more complex expressions involving AND and
BETWEEN operators, such as:
WHERE size > 100 AND size < 199 -- scan partition 'medium'
WHERE color = 'red' AND (size > 100 AND size < 199) -- scan 'medium'
In each case, fast pruning requires that the qualifier must refer to a partitioning column
and literal value (or IS NULL/IS NOT NULL).
Note that fast pruning can also optimize DELETE and UPDATE statements containing
WHERE clauses of the forms described above.
The EXPLAIN statement displays the execution plan of a statement. You can use the
EXPLAIN statement to confirm that Advanced Server is pruning partitions from the
execution plan of a query.
The resulting query plan shows that the server will scan only the sales_asia table - the
table in which a row with a country value of INDIA would be stored:
edb=# EXPLAIN (COSTS OFF) SELECT * FROM sales WHERE country = 'INDIA';
QUERY PLAN
---------------------------------------------------
Append
-> Seq Scan on sales
Filter: ((country)::text = 'INDIA'::text)
-> Seq Scan on sales_asia
Filter: ((country)::text = 'INDIA'::text)
(5 rows)
If you perform a query that searches for a row that matches a value not included in the
partitioning key:
The resulting query plan shows that the server must look in all of the partitions to locate
the rows that satisfy the query:
When you query the table, the query planner prunes any partitions or subpartitions from
the search path that cannot possibly contain the desired result set:
edb=# EXPLAIN (COSTS OFF) SELECT * FROM sales WHERE country = 'US' AND date =
'Dec 12, 2012';
QUERY PLAN
-----------------------------------------------------------------------------
Append
Use the PARTITION BY clause of the CREATE TABLE command to create a partitioned
table with data distributed amongst one or more partitions (and subpartitions). The
command syntax comes in the following forms:
PARTITION [partition_name]
VALUES (value[, value]...)
[TABLESPACE tablespace_name]
[(subpartition, ...)]
PARTITION [partition_name]
VALUES LESS THAN (value[, value]...)
[TABLESPACE tablespace_name]
[(subpartition, ...)]
[PARTITION partition_name]
[TABLESPACE tablespace_name]
[(subpartition, ...)]
Subpartitioning Syntax
SUBPARTITION [subpartition_name]
VALUES (value[, value]...)
[TABLESPACE tablespace_name]
SUBPARTITION [subpartition_name]
VALUES LESS THAN (value[, value]...)
[TABLESPACE tablespace_name]
[SUBPARTITION subpartition_name]
[TABLESPACE tablespace_name]
Description
The CREATE TABLE PARTITION BY command creates a table with one or more
partitions; each partition may have one or more subpartitions. There is no upper limit to
the number of defined partitions, but if you include the PARTITION BY clause, you must
specify at least one partitioning rule. The resulting table will be owned by the user that
creates it.
Use the PARTITION BY LIST clause to divide a table into partitions based on the values
entered in a specified column. Each partitioning rule must specify at least one literal
value, but there is no upper limit placed on the number of values you may specify.
Include a rule that specifies a matching value of DEFAULT to direct any un-qualified rows
to the given partition; for more information about using the DEFAULT keyword, see
Section 10.4.
Use the PARTITION BY RANGE clause to specify boundary rules by which to create
partitions. Each partitioning rule must contain at least one column of a data type that has
two operators (i.e., a greater-than or equal to operator, and a less-than operator). Range
boundaries are evaluated against a LESS THAN clause and are non-inclusive; a date
boundary of January 1, 2013 will include only those date values that fall on or before
December 31, 2012.
Range partition rules must be specified in ascending order. INSERT commands that store
rows with values that exceed the top boundary of a range-partitioned table will fail unless
the partitioning rules include a boundary rule that specifies a value of MAXVALUE. If you
do not include a MAXVALUE partitioning rule, any row that exceeds the maximum limit
specified by the boundary rules will result in an error.
For more information about using the MAXVALUE keyword, see Section 10.4.
Use the TABLESPACE keyword to specify the name of a tablespace on which a partition
or subpartition will reside; if you do not specify a tablespace, the partition or subpartition
will reside in the default tablespace.
If a table definition includes the SUBPARTITION BY clause, each partition within that
table will have at least one subpartition. Each subpartition may be explicitly defined or
system-defined.
The server will generate a subpartition name that is a combination of the partition table
name and a unique identifier. You can query the ALL_TAB_SUBPARTITIONS table to
review a complete list of subpartition names.
Parameters
table_name
table_definition
The column names, data types, and constraint information as described in the
PostgreSQL core documentation for the CREATE TABLE statement, available at:
https://www.postgresql.org/docs/10/static/sql-createtable.html
partition_name
The name of the partition to be created. Partition names must be unique amongst
all partitions and subpartitions, and must follow the naming conventions for
object identifiers.
subpartition_name
column
The name of a column on which the partitioning rules are based. Each row will
be stored in a partition that corresponds to the value of the specified column(s).
(value[, value]...)
Use value to specify a quoted literal value (or comma-delimited list of literal
values) by which table entries will be grouped into partitions. Each partitioning
rule must specify at least one value, but there is no limit placed on the number of
values specified within a rule. value may be NULL, DEFAULT (if specifying a
LIST partition), or MAXVALUE (if specifying a RANGE partition).
When specifying rules for a list-partitioned table, include the DEFAULT keyword in the
last partition rule to direct any un-matched rows to the given partition. If you do not
include a rule that includes a value of DEFAULT, any INSERT statement that attempts to
add a row that does not match the specified rules of at least one partition will fail, and
return an error.
When specifying rules for a list-partitioned table, include the MAXVALUE keyword in the
last partition rule to direct any un-categorized rows to the given partition. If you do not
include a MAXVALUE partition, any INSERT statement that attempts to add a row where
the partitioning key is greater than the highest value specified will fail, and return an
error.
tablespace_name
The following example creates a partitioned table (sales) using the PARTITION BY
LIST clause. The sales table stores information in three partitions (europe, asia, and
americas):
The resulting table is partitioned by the value specified in the country column:
Rows with a value of US or CANADA in the country column are stored in the
americas partition.
Rows with a value of INDIA or PAKISTAN in the country column are stored in
the asia partition.
Rows with a value of FRANCE or ITALY in the country column are stored in the
europe partition.
The server would evaluate the following statement against the partitioning rules, and
store the row in the europe partition:
The following example creates a partitioned table (sales) using the PARTITION BY
RANGE clause. The sales table stores information in four partitions (q1_2012,
q2_2012, q3_2012 and q4_2012) :
The resulting table is partitioned by the value specified in the date column:
Any row with a value in the date column before April 1, 2012 is stored in a
partition named q1_2012.
Any row with a value in the date column before July 1, 2012 is stored in a
partition named q2_2012.
Any row with a value in the date column before October 1, 2012 is stored in a
partition named q3_2012.
Any row with a value in the date column before January 1, 2013 is stored in a
partition named q4_2012.
The server would evaluate the following statement against the partitioning rules and store
the row in the q3_2012 partition:
The following example creates a partitioned table (sales) using the PARTITION BY
HASH clause. The sales table stores information in three partitions (p1, p2, and p3:
The table is partitioned by the hash value of the value specified in the part_no column:
The server will evaluate the hash value of the part_no column, and distribute the rows
into approximately equal partitions.
The following example creates a partitioned table (sales) that is first partitioned by the
transaction date; the range partitions (q1_2012, q2_2012, q3_2012 and q4_2012) are
then list-subpartitioned using the value of the country column.
This statement creates a table with four partitions; each partition has three subpartitions:
When a row is added to this table, the value in the date column is compared to the
values specified in the range partitioning rules, and the server selects the partition in
which the row should reside. The value in the country column is then compared to the
values specified in the list subpartitioning rules; when the server locates a match for the
value, the row is stored in the corresponding subpartition.
Any row added to the table will be stored in a subpartition, so the partitions will contain
no data.
The server would evaluate the following statement against the partitioning and
subpartitioning rules and store the row in the q3_europe partition:
Use the ALTER TABLE ADD PARTITION command to add a partition to an existing
partitioned table. The syntax is:
{list_partition | range_partition }
PARTITION [partition_name]
VALUES (value[, value]...)
[TABLESPACE tablespace_name]
[(subpartition, ...)]
PARTITION [partition_name]
VALUES LESS THAN (value[, value]...)
[TABLESPACE tablespace_name]
[(subpartition, ...)]
SUBPARTITION [subpartition_name]
VALUES (value[, value]...)
[TABLESPACE tablespace_name]
SUBPARTITION [subpartition_name ]
VALUES LESS THAN (value[, value]...)
[TABLESPACE tablespace_name]
Description
New partitions must be of the same type (LIST, RANGE or HASH) as existing partitions.
The new partition rules must reference the same column specified in the partitioning rules
that define the existing partition(s).
You cannot use the ALTER TABLE ADD PARTITION statement to add a partition to a
table with a MAXVALUE or DEFAULT rule. Note that you can alternatively use the ALTER
TABLE SPLIT PARTITION statement to split an existing partition, effectively
increasing the number of partitions in a table.
RANGE partitions must be specified in ascending order. You cannot add a new partition
that precedes existing partitions in a RANGE partitioned table.
Include the TABLESPACE clause to specify the tablespace in which the new partition will
reside. If you do not specify a tablespace, the partition will reside in the default
tablespace.
If the table is indexed, the index will be created on the new partition.
To use the ALTER TABLE... ADD PARTITION command you must be the table owner,
or have superuser (or administrative) privileges.
Parameters
table_name
partition_name
The name of the partition to be created. Partition names must be unique amongst
all partitions and subpartitions, and must follow the naming conventions for
object identifiers.
subpartition_name
(value[, value]...)
Use value to specify a quoted literal value (or comma-delimited list of literal
values) by which rows will be distributed into partitions. Each partitioning rule
must specify at least one value, but there is no limit placed on the number of
values specified within a rule. value may also be NULL, DEFAULT (if specifying
a LIST partition), or MAXVALUE (if specifying a RANGE partition).
tablespace_name
The example that follows adds a partition to the list-partitioned sales table. The table
was created using the command:
The following command adds a partition named east_asia to the sales table:
After invoking the command, the table includes the east_asia partition:
The example that follows adds a partition to a range-partitioned table named sales:
The table contains four partitions (q1_2012, q2_2012, q3_2012, and q4_2012):
The following command adds a partition named q1_2013 to the sales table:
After invoking the command, the table includes the q1_2013 partition:
{list_subpartition | range_subpartition}
SUBPARTITION [subpartition_name]
VALUES (value[, value]...)
[TABLESPACE tablespace_name]
SUBPARTITION [subpartition_name]
VALUES LESS THAN (value[, value]...)
[TABLESPACE tablespace_name]
Description
New subpartitions must be of the same type (LIST, RANGE or HASH) as existing
subpartitions. The new subpartition rules must reference the same column specified in
the subpartitioning rules that define the existing subpartition(s).
You cannot use the ALTER TABLE ADD SUBPARTITION statement to add a subpartition
to a table with a MAXVALUE or DEFAULT rule , but you can split an existing subpartition
with the ALTER TABLE SPLIT SUBPARTITION statement, effectively adding a
subpartition to a table.
You cannot add a new subpartition that precedes existing subpartitions in a range
subpartitioned table; range subpartitions must be specified in ascending order.
Include the TABLESPACE clause to specify the tablespace in which the subpartition will
reside. If you do not specify a tablespace, the subpartition will be created in the default
tablespace.
If the table is indexed, the index will be created on the new subpartition.
To use the ALTER TABLE... ADD SUBPARTITION command you must be the table
owner, or have superuser (or administrative) privileges.
Parameters
table_name
partition_name
The name of the partition in which the new subpartition will reside.
subpartition_name
(value[, value]...)
Use value to specify a quoted literal value (or comma-delimited list of literal
values) by which table entries will be grouped into partitions. Each partitioning
rule must specify at least one value, but there is no limit placed on the number of
values specified within a rule. value may also be NULL, DEFAULT (if specifying
a LIST partition), or MAXVALUE (if specifying a RANGE partition).
tablespace_name
The following example adds a RANGE subpartition to the list-partitioned sales table.
The sales table was created with the command:
The sales table has three partitions, named europe, asia, and americas. Each
partition has two range-defined subpartitions:
After invoking the command, the table includes a subpartition named europe_2013:
Note that when adding a new range subpartition, the subpartitioning rules must specify a
range that falls after any existing subpartitions.
The following example adds a LIST subpartition to the RANGE partitioned sales table.
The sales table was created with the command:
After executing the above command, the sales table will have two partitions, named
first_half_2012 and second_half_2012. The first_half_2012 partition has
two subpartitions, named europe and americas, and the second_half_2012 partition
has one partition, named asia:
After invoking the command, the table includes a subpartition named east_asia:
Use the ALTER TABLE SPLIT PARTITION command to divide a single partition into
two partitions, and redistribute the partition's contents between the new partitions. The
command syntax comes in two forms.
Description
Include the TABLESPACE clause to specify the tablespace in which a partition will reside.
If you do not specify a tablespace, the partition will reside in the default tablespace.
If the table is indexed, the index will be created on the new partition.
To use the ALTER TABLE... SPLIT PARTITION command you must be the table
owner, or have superuser (or administrative) privileges.
Parameters
table_name
partition_name
new_part1
The name of the first new partition to be created. Partition names must be unique
amongst all partitions and subpartitions, and must follow the naming conventions
for object identifiers.
new_part1 will receive the rows that meet the subpartitioning constraints
specified in the ALTER TABLE SPLIT SUBPARTITION command.
new_part2
The name of the second new partition to be created. Partition names must be
unique amongst all partitions and subpartitions, and must follow the naming
conventions for object identifiers.
new_part2 will receive the rows are not directed to new_part1 by the
partitioning constraints specified in the ALTER TABLE SPLIT PARTITION
command.
range_part_value
(value[, value]...)
Use value to specify a quoted literal value (or comma-delimited list of literal
values) by which rows will be distributed into partitions. Each partitioning rule
must specify at least one value, but there is no limit placed on the number of
values specified within a rule.
tablespace_name
Our example will divide one of the partitions in the list-partitioned sales table into two
new partitions, and redistribute the contents of the partition between them. The sales
table is created with the statement:
The table definition creates three partitions (europe, asia, and americas). The
following command adds rows to each partition:
The following command splits the americas partition into two partitions named us and
canada:
A SELECT statement confirms that the rows have been redistributed across the correct
partitions:
This example divides the q4_2012 partition (of the range-partitioned sales table) into
two partitions, and redistribute the partition's contents. Use the following command to
create the sales table:
The table definition creates four partitions (q1_2012, q2_2012, q3_2012, and
q4_2012). The following command adds rows to each partition:
A SELECT statement confirms that the rows are distributed amongst the partitions as
expected:
The following command splits the q4_2012 partition into two partitions named
q4_2012_p1 and q4_2012_p2:
A SELECT statement confirms that the rows have been redistributed across the new
partitions:
Description
The new subpartition rules must reference the column specified in the rules that define
the existing subpartition(s).
Include the TABLESPACE clause to specify a tablespace in which a new subpartition will
reside. If you do not specify a tablespace, the subpartition will be created in the default
tablespace.
If the table is indexed, the index will be created on the new subpartition.
To use the ALTER TABLE... SPLIT SUBPARTITION command you must be the table
owner, or have superuser (or administrative) privileges.
Parameters
table_name
subpartition_name
new_subpart1
The name of the first new subpartition to be created. Subpartition names must be
unique amongst all partitions and subpartitions, and must follow the naming
conventions for object identifiers.
new_subpart1 will receive the rows that meet the subpartitioning constraints
specified in the ALTER TABLE SPLIT SUBPARTITION command.
new_subpart2
The name of the second new subpartition to be created. Subpartition names must
be unique amongst all partitions and subpartitions, and must follow the naming
conventions for object identifiers.
new_subpart2 will receive the rows are not directed to new_subpart1 by the
subpartitioning constraints specified in the ALTER TABLE SPLIT
SUBPARTITION command.
(value[, value]...)
Use value to specify a quoted literal value (or comma-delimited list of literal
values) by which table entries will be grouped into partitions. Each partitioning
rule must specify at least one value, but there is no limit placed on the number of
values specified within a rule. value may also be NULL, DEFAULT (if specifying
a LIST subpartition), or MAXVALUE (if specifying a RANGE subpartition).
tablespace_name
A SELECT statement confirms that the rows are correctly distributed amongst the
subpartitions:
The following command splits the p2_americas subpartition into two new
subpartitions, and redistributes the contents:
After invoking the command, the p2_americas subpartition has been deleted; in its
place, the server has created two new subpartitions (p2_us and p2_canada):
Querying the sales table demonstrates that the content of the p2_americas
subpartition has been redistributed:
The sales table has three partitions (europe, asia, and americas). Each partition
has two range-defined subpartitions that sort the partitions contents into subpartitions by
the value of the date column:
A SELECT statement confirms that the rows are distributed amongst the subpartitions:
The following command splits the americas_2012 subpartition into two new
subpartitions, and redistributes the contents:
After invoking the command, the americas_2012 subpartition has been deleted; in its
place, the server has created two new subpartitions (americas_p1_2012 and
americas_p2_2012):
Querying the sales table demonstrates that the subpartition's contents are redistributed:
Description
When the ALTER TABLE EXCHANGE PARTITION command completes, the data
originally located in the target_partition will be located in the source_table,
and the data originally located in the source_table will be located in the
target_partition.
The previously used matching index term refers to indexes that have the same attributes
such as the collation order, ascending or descending direction, ordering of nulls first or
nulls last, and so forth as determined by the CREATE INDEX command.
If both INCLUDING INDEXES and EXCLUDING INDEXES are omitted, then the default
action is the EXCLUDING INDEXES behavior.
Advanced Server accepts the WITHOUT VALIDATION clause, but ignores it; the new table
is always validated.
The same behavior as previously described applies for the target_subpartition used
with the EXCHANGE SUBPARTITION clause.
You must own a table to invoke ALTER TABLE EXCHANGE PARTITION or ALTER
TABLE EXCHANGE SUBPARTITION against that table.
Parameters:
target_table
target_partition
target_subpartition
source_table
The example that follows demonstrates swapping a table for a partition (americas) of
the sales table. You can create the sales table with the following command:
Use the following command to add sample data to the sales table:
Querying the sales table shows that only one row resides in the americas partition:
The following command creates a table (n_america) that matches the definition of the
sales table:
The following command adds data to the n_america table. The data conforms to the
partitioning rules of the americas partition:
The following command swaps the table into the partitioned table:
Querying the sales table shows that the contents of the n_america table has been
exchanged for the content of the americas partition:
Querying the n_america table shows that the row that was previously stored in the
americas partition has been moved to the n_america table:
Use the ALTER TABLE MOVE PARTITION command to move a partition to a different
tablespace. The command takes two forms.
Description
The ALTER TABLEMOVE PARTITION command moves a partition from its current
tablespace to a different tablespace. The ALTER TABLE MOVE PARTITION command
can move partitions of a LIST, RANGE or HASH partitioned table.
The same behavior as previously described applies for the subpartition_name used
with the MOVE SUBPARTITION clause.
You must own the table to invoke ALTER TABLE MOVE PARTITION or ALTER
TABLE MOVE SUBPARTITION.
Parameters
table_name
partition_name
subpartition_name
The name of the tablespace to which the partition or subpartition will be moved.
The following example moves a partition of the sales table from one tablespace to
another. First, create the sales table with the command:
Querying the ALL_TAB_PARTITIONS view confirms that the partitions reside on the
expected servers and tablespaces:
After preparing the target tablespace, invoke the ALTER TABLE MOVE PARTITION
command to move the q1_2013 partition from a tablespace named ts_2 to a tablespace
named ts_3:
Querying the ALL_TAB_PARTITIONS view shows that the move was successful:
Use the ALTER TABLE RENAME PARTITION command to rename a table partition. The
syntax takes two forms.
Description
The same behavior as previously described applies for the subpartition_name used
with the RENAME SUBPARTITION clause.
You must own the specified table to invoke ALTER TABLE RENAME PARTITION or
ALTER TABLE RENAME SUBPARTITION.
Parameters
table_name
partition_name
subpartition_name
new_name
Querying the ALL_TAB_PARTITIONS view demonstrates that the partition has been
successfully renamed:
Use the PostgreSQL DROP TABLE command to remove a partitioned table definition, it's
partitions and subpartitions, and delete the table contents. The syntax is:
Parameters
table_name
Description
The DROP TABLE command removes an entire table, and the data that resides in that
table. When you delete a table, any partitions or subpartitions (of that table) are deleted
as well.
To use the DROP TABLE command, you must be the owner of the partitioning root, a
member of a group that owns the table, the schema owner, or a database superuser.
Example
To delete a table, connect to the controller node (the host of the partitioning root), and
invoke the DROP TABLE command. For example, to delete the sales table, invoke the
following command:
The server will confirm that the table has been dropped:
For more information about the DROP TABLE command, please see the PostgreSQL core
documentation at:
https://www.postgresql.org/docs/10/static/sql-droptable.html
Use the ALTER TABLE DROP PARTITION command to delete a partition definition, and
the data stored in that partition. The syntax is:
Parameters
table_name
partition_name
Description
The ALTER TABLE DROP PARTITION command deletes a partition and any data stored
on that partition. The ALTER TABLE DROP PARTITION command can drop partitions
of a LIST or RANGE partitioned table; please note that this command does not work on a
HASH partitioned table. When you delete a partition, any subpartitions (of that partition)
are deleted as well.
To use the DROP PARTITION clause, you must be the owner of the partitioning root, a
member of a group that owns the table, or have database superuser or administrative
privileges.
The example that follows deletes a partition of the sales table. Use the following
command to create the sales table:
To delete the americas partition from the sales table, invoke the following command:
Querying the ALL_TAB_PARTITIONS view demonstrates that the partition has been
successfully deleted:
Parameters
table_name
subpartition_name
Description
The ALTER TABLE DROP SUBPARTITION command deletes a subpartition, and the data
stored in that subpartition. To use the DROP SUBPARTITION clause, you must be the
owner of the partitioning root, a member of a group that owns the table, or have superuser
or administrative privileges.
The example that follows deletes a subpartition of the sales table. Use the following
command to create the sales table:
To delete the americas subpartition from the sales table, invoke the following
command:
Use the TRUNCATE TABLE command to remove the contents of a table, while preserving
the table definition. When you truncate a table, any partitions or subpartitions of that
table are also truncated. The syntax is:
Description
The TRUNCATE TABLE command removes an entire table, and the data that resides in
that table. When you delete a table, any partitions or subpartitions (of that table) are
deleted as well.
To use the TRUNCATE TABLE command, you must be the owner of the partitioning root, a
member of a group that owns the table, the schema owner, or a database superuser.
Parameters
table_name
The example that follows removes the data from the sales table. Use the following
command to create the sales table:
Querying the sales table shows that the partitions are populated with data:
To delete the contents of the sales table, invoke the following command:
Now, querying the sales table shows that the data has been removed but the structure is
intact:
For more information about the TRUNCATE TABLE command, please see the PostgreSQL
documentation at:
https://www.postgresql.org/docs/10/static/sql-truncate.html
Use the ALTER TABLE TRUNCATE PARTITION command to remove the data from the
specified partition, leaving the partition structure intact. The syntax is:
Description
Use the ALTER TABLE TRUNCATE PARTITION command to remove the data from the
specified partition, leaving the partition structure intact. When you truncate a partition,
any subpartitions of that partition are also truncated.
ALTER TABLE TRUNCATE PARTITION will not cause ON DELETE triggers that might
exist for the table to fire, but it will fire ON TRUNCATE triggers. If an ON TRUNCATE
trigger is defined for the partition, all BEFORE TRUNCATE triggers are fired before any
truncation happens, and all AFTER TRUNCATE triggers are fired after the last truncation
occurs.
You must have the TRUNCATE privilege on a table to invoke ALTER TABLE
TRUNCATE PARTITION.
Parameters
table_name
partition_name
DROP STORAGE and REUSE STORAGE are included for compatibility only; the clauses are
parsed and ignored.
The example that follows removes the data from a partition of the sales table. Use the
following command to create the sales table:
Querying the sales table shows that the partitions are populated with data:
To delete the contents of the americas partition, invoke the following command:
Now, querying the sales table shows that the content of the americas partition has
been removed:
While the rows have been removed, the structure of the americas partition is still intact:
Use the ALTER TABLE TRUNCATE SUBPARTITION command to remove all of the data
from the specified subpartition, leaving the subpartition structure intact. The syntax is:
Description
The ALTER TABLE TRUNCATE SUBPARTITION command removes all data from a
specified subpartition, leaving the subpartition structure intact.
ALTER TABLE TRUNCATE SUBPARTITION will not cause ON DELETE triggers that
might exist for the table to fire, but it will fire ON TRUNCATE triggers. If an ON
TRUNCATE trigger is defined for the subpartition, all BEFORE TRUNCATE triggers are
fired before any truncation happens, and all AFTER TRUNCATE triggers are fired after the
last truncation occurs.
You must have the TRUNCATE privilege on a table to invoke ALTER TABLE
TRUNCATE SUBPARTITION.
Parameters
table_name
subpartition_name
The DROP STORAGE and REUSE STORAGE clauses are included for compatibility only; the
clauses are parsed and ignored.
The example that follows removes the data from a subpartition of the sales table. Use
the following command to create the sales table:
Querying the sales table shows that the rows have been distributed amongst the
subpartitions:
To delete the contents of the 2012_americas partition, invoke the following command:
Now, querying the sales table shows that the content of the americas_2012 partition
has been removed:
While the rows have been removed, the structure of the 2012_americas partition is still
intact:
A DEFAULT or MAXVALUE partition or subpartition will capture any rows that do not meet
the other partitioning rules defined for a table.
A DEFAULT partition will capture any rows that do not fit into any other partition in a
LIST partitioned (or subpartitioned) table. If you do not include a DEFAULT rule, any
row that does not match one of the values in the partitioning constraints will result in an
error. Each LIST partition or subpartition may have its own DEFAULT rule.
Where partition_name specifies the name of the partition or subpartition that will
store any rows that do not match the rules specified for other partitions.
The last example created a list partitioned table in which the server decided which
partition to store the data based upon the value of the country column. If you attempt
to add a row in which the value of the country column contains a value not listed in the
rules, Advanced Server reports an error:
The following example creates the same table, but adds a DEFAULT partition. The server
will store any rows that do not match a value specified in the partitioning rules for
europe, asia, or americas partitions in the others partition:
To test the DEFAULT partition, add row with a value in the country column that does
not match one of the countries specified in the partitioning constraints:
Querying the contents of the sales table confirms that the previously rejected row is
now stored in the sales_others partition:
Please note that Advanced Server does not have a way to re-assign the contents of a
DEFAULT partition or subpartition:
You cannot use the ALTER TABLE ADD PARTITION command to add a
partition to a table with a DEFAULT rule, but you can use the ALTER TABLE
SPLIT PARTITION command to split an existing partition.
You cannot use the ALTER TABLE ADD SUBPARTITION command to add a
subpartition to a table with a DEFAULT rule, but you can use the ALTER TABLE
SPLIT SUBPARTITION command to split an existing subpartition.
A MAXVALUE partition (or subpartition) will capture any rows that do not fit into any
other partition in a range-partitioned (or subpartitioned) table. If you do not include a
MAXVALUE rule, any row that exceeds the maximum limit specified by the partitioning
rules will result in an error. Each partition or subpartition may have its own MAXVALUE
partition.
Where partition_name specifies the name of the partition that will store any rows that
do not match the rules specified for other partitions.
The last example created a range-partitioned table in which the data was partitioned
based upon the value of the date column. If you attempt to add a row with a date that
exceeds a date listed in the partitioning constraints, Advanced Server reports an error:
The following CREATE TABLE command creates the same table, but with a MAXVALUE
partition. Instead of throwing an error, the server will store any rows that do not match
the previous partitioning constraints in the others partition:
To test the MAXVALUE partition, add a row with a value in the date column that exceeds
the last date value listed in a partitioning rule. The server will store the row in the
others partition:
Querying the contents of the sales table confirms that the previously rejected row is
now stored in the sales_others partition :
Please note that Advanced Server does not have a way to re-assign the contents of a
MAXVALUE partition or subpartition:
You cannot use the ALTER TABLE ADD PARTITION statement to add a partition
to a table with a MAXVALUE rule, but you can use the ALTER TABLE SPLIT
PARTITION statement to split an existing partition.
You cannot use the ALTER TABLE ADD SUBPARTITION statement to add a
subpartition to a table with a MAXVALUE rule , but you can split an existing
subpartition with the ALTER TABLE SPLIT SUBPARTITION statement.
You can often improve performance by specifying multiple key columns for a RANGE
partitioned table. If you often select rows using comparison operators (based on a
greater-than or less-than value) on a small set of columns, consider using those columns
in RANGE partitioning rules.
Range-partitioned table definitions may include multiple columns in the partitioning key.
To specify multiple partitioning keys for a range-partitioned table, include the column
names in a comma-separated list after the PARTITION BY RANGE clause:
If a table is created with multiple partitioning keys, you must specify multiple key values
when querying the table to take full advantage of partition pruning:
acctg=# EXPLAIN SELECT * FROM sales WHERE sale_year = 2012 AND sale_month =
8;
QUERY PLAN
-----------------------------------------------------------------------------
Result (cost=0.00..14.35 rows=2 width=250)
-> Append (cost=0.00..14.35 rows=2 width=250)
-> Seq Scan on sales (cost=0.00..0.00 rows=1 width=250)
Filter: ((sale_year = 2012::numeric) AND (sale_month =
8::numeric))
-> Seq Scan on sales_q3_2012 sales (cost=0.00..14.35 rows=1
width=250)
Since all rows with a value of 8 in the sale_month column and a value of 2012 in the
sale_year column will be stored in the q3_2012 partition, Advanced Server searches
only that partition.
You can query the following views to retrieve information about partitioned and
subpartitioned tables:
ALL_PART_TABLES
ALL_TAB_PARTITIONS
ALL_TAB_SUBPARTITIONS
ALL_PART_KEY_COLUMNS
ALL_SUBPART_KEY_COLUMNS
The structure of each view is explained in Section 10.6.1, Table Partitioning Views. If
you are using the EDB-PSQL client, you can also discover the structure of a view by
entering:
\d view_name
Query the following catalog views, compatible with Oracle databases, to review detailed
information about your partitioned tables.
10.6.1.1 ALL_PART_TABLES
The following table lists the information available in the ALL_PART_TABLES view:
10.6.1.2 ALL_TAB_PARTITIONS
The following table lists the information available in the ALL_TAB_PARTITIONS view:
10.6.1.3 ALL_TAB_SUBPARTITIONS
10.6.1.4 ALL_PART_KEY_COLUMNS
10.6.1.5 ALL_SUBPART_KEY_COLUMNS
The following table lists the information available in the ALL_SUBPART_KEY_COLUMNS view:
Column Type Description
owner name The name of the table owner.
name name The name of the table.
schema name The name of the schema on which the table
resides.
object_type character(5) This column will always be TABLE.
column_name name The name of the partitioning key column.
column_position integer The position of this column within the
subpartitioning key (the first column has a
column position of 1, the second column has a
column position of 2...)
11 ECPGPlus
EnterpriseDB has enhanced ECPG (the PostgreSQL pre-compiler) to create ECPGPlus.
ECPGPlus allows you to include embedded SQL commands in C applications; when you
use ECPGPlus to compile an application that contains embedded SQL commands, the
SQL code is syntax-checked and translated into C.
As part of ECPGPlus' Pro*C compatibility, you do not need to include the BEGIN
DECLARE SECTION and END DECLARE SECTION directives.
For more information about using ECPGPlus, please see the EDB Postgres Advanced
Server ECPG Connector Guide available from the EnterpriseDB website at:
http://www.enterprisedb.com/products-services-training/products/documentation
12 dblink_ora
dblink_ora provides an OCI-based database link that allows you to SELECT, INSERT,
UPDATE or DELETE data stored on an Oracle system from within Advanced Server.
To enable Oracle connectivity, download Oracle's freely available OCI drivers from their
website, presently at:
http://www.oracle.com/technetwork/database/features/instant-
client/index-100365.html
For Linux, if the Oracle instant client that you've downloaded does not include the
libclntsh.so library, you must create a symbolic link named libclntsh.so that
points to the downloaded version. Navigate to the instant client directory and execute the
following command:
ln -s libclntsh.so.version libclntsh.so
Where version is the version number of the libclntsh.so library. For example:
ln -s libclntsh.so.12.1 libclntsh.so
Before creating a link to an Oracle server, you must tell Advanced Server where to find
the OCI driver.
Set the LD_LIBRARY_PATH environment variable on Linux (or PATH on Windows) to the
lib directory of the Oracle client installation directory.
For Windows only, you can instead set the value of the oracle_home configuration
parameter in the postgresql.conf file. The value specified in the oracle_home
configuration parameter will override the Windows PATH environment variable.
When using a Linux service script to start Advanced Server, be sure LD_LIBRARY_PATH
has been set within the service script so it is in effect when the script invokes the pg_ctl
utility to start Advanced Server.
Substitute the name of the Windows directory that contains oci.dll for
lib_directory.
After setting the oracle_home configuration parameter, you must restart the server for
the changes to take effect. Restart the server from the Windows Services console.
12.1.1 dblink_ora_connect()
Where:
asDBA is True if you wish to request SYSDBA privileges on the Oracle server.
This parameter is optional; if omitted, the default value is FALSE.
dblink_ora_connect(foreign_server_name, asDBA)
Where:
asDBA is True if you wish to request SYSDBA privileges on the Oracle server.
This parameter is optional; if omitted, the default value is FALSE.
The second form of the dblink_ora_connect() function allows you to use the
connection properties of a pre-defined foreign server when establishing a connection to
the server.
Before invoking the second form of the dblink_ora_connect() function, use the
CREATE SERVER command to store the connection properties for the link to a system
table. When you call the dblink_ora_connect() function, substitute the server name
specified in the CREATE SERVER command for the name of the link.
12.1.2 dblink_ora_status()
dblink_ora_status(conn_name)
Where:
If the specified connection is active, the function returns a TEXT value of OK.
12.1.3 dblink_ora_disconnect()
dblink_ora_disconnect(conn_name)
Where:
12.1.4 dblink_ora_record()
dblink_ora_record(conn_name, query_text)
Where:
query_text specifies the text of the SQL SELECT statement that will be
invoked on the Oracle server.
12.1.5 dblink_ora_call()
Where:
command specifies the text of the SQL statement that will be invoked on the
Oracle server.
12.1.6 dblink_ora_exec()
Where:
command specifies the text of the INSERT, UPDATE, or DELETE SQL statement
that will be invoked on the Oracle server.
12.1.7 dblink_ora_copy()
Where:
command specifies the text of the SQL SELECT statement that will be invoked on
the Oracle server.
truncate specifies if the server should TRUNCATE the table prior to copying;
specify TRUE to indicate that the server should TRUNCATE the table. truncate is
optional; if omitted, the value is FALSE.
count instructs the server to report status information every n record, where n is
the number specified. During the execution of the function, Advanced Server
raises a notice of severity INFO with each iteration of the count. For example, if
FeedbackCount is 10, dblink_ora_copy() raises a notice every 10 records.
count is optional; if omitted, the value is 0.
The example connects to a service named xe running on port 1521 (on the localhost)
with a user name of hr and a password of pwd. You can use the connection name acctg
to refer to this connection when calling other dblink_ora functions.
INFO: Row: 0
INFO: Row: 3
INFO: Row: 6
INFO: Row: 9
INFO: Row: 12
dblink_ora_copy
-----------------
12
(1 row)
The following SELECT statement uses dblink_ora_record() function and the acctg
connection to retrieve information from the Oracle server:
The command retrieves a list that includes all of the entries in the first_name column
of the employees table.
For detailed information about the system catalog tables, please see the Database
Compatibility for Oracle Developers Reference Guide, available at:
http://www.enterprisedb.com/products-services-training/products/documentation
14 Acknowledgements
The PostgreSQL 8.3, 8.4, 9.0, 9.1, 9.2, 9.3, 9.4, 9.5, 9.6, and 10 Documentation provided
the baseline for the portions of this guide that are common to PostgreSQL, and is hereby
acknowledged:
Portions of this EnterpriseDB Software and Documentation may utilize the following
copyrighted material, the use of which is hereby acknowledged.
Permission to use, copy, modify, and distribute this software and its documentation for
any purpose, without fee, and without a written agreement is hereby granted, provided
that the above copyright notice and this paragraph and the following two paragraphs
appear in all copies.