Example 1-1 Declaring Variables in PL/SQL: Bonus NUMBER (8,2)
Example 1-1 Declaring Variables in PL/SQL: Bonus NUMBER (8,2)
DECLARE
part_no NUMBER(6);
part_name VARCHAR2(20);
in_stock BOOLEAN;
part_price NUMBER(6,2);
part_desc VARCHAR2(50);
Bind Variables
When you embed an INSERT, UPDATE, DELETE, or SELECT SQL statement directly in your PL/SQL code,
PL/SQL turns the variables in the WHERE and VALUES clauses into bind variables automatically. Oracle can reuse
these SQL statement each time the same code is executed. To run similar statements with different variable values,
you can save parsing overhead by calling a stored procedure that accepts parameters, then issues the statements with
the parameters substituted in the appropriate places. You do need to specify bind variables with dynamic SQL, in
clauses like WHERE and VALUES where you normally use variables. Instead of concatenating literals and variable
values into a single string, replace the variables with the names of bind variables (prefixed by a colon) and specify the
corresponding PL/SQL variables with the USING clause. Using the USING clause, instead of concatenating the
variables into the string, reduces parsing overhead and lets Oracle reuse the SQL statements. For example:
Declaring Constants
Declaring a constant is like declaring a variable except that you must add the keyword
CONSTANT and immediately assign a value to the constant. No further assignments to
the constant are allowed. The following example declares a constant:
credit_limit CONSTANT NUMBER := 5000.00;
%TYPE
v_last_name employees.last_name%TYPE;
%ROWTYPE
DECLARE
dept_rec departments%ROWTYPE; -- declare record variable
You use dot notation to reference fields, as the following example shows:
v_deptid := dept_rec.department_id;
Conditional Control
Example 1–7 Using the IF-THEN_ELSE and CASE Statement for Conditional Control
DECLARE
jobid employees.job_id%TYPE;
empid employees.employee_id%TYPE := 115;
sal employees.salary%TYPE;
sal_raise NUMBER(3,2);
BEGIN
SELECT job_id, salary INTO jobid, sal from employees WHERE employee_id = empid;
CASE
WHEN jobid = 'PU_CLERK' THEN
IF sal < 3000 THEN sal_raise := .12;
ELSE sal_raise := .09;
END IF;
WHEN jobid = 'SH_CLERK' THEN
IF sal < 4000 THEN sal_raise := .11;
ELSE sal_raise := .08;
END IF;
WHEN jobid = 'ST_CLERK' THEN
IF sal < 3500 THEN sal_raise := .10;
ELSE sal_raise := .07;
END IF;
ELSE
BEGIN
DBMS_OUTPUT.PUT_LINE('No raise for this job: ' || jobid);
END;
END CASE;
UPDATE employees SET salary = salary + salary * sal_raise
WHERE employee_id = empid;
COMMIT;
END;
Iterative Control
Example 1–8 Using the FOR-LOOP
CREATE TABLE sqr_root_sum (num NUMBER, sq_root NUMBER(6,2),
sqr NUMBER, sum_sqrs NUMBER);
DECLARE
s PLS_INTEGER;
BEGIN
FOR i in 1..100 LOOP
s := (i * (i + 1) * (2*i +1)) / 6; -- sum of squares
INSERT INTO sqr_root_sum VALUES (i, SQRT(i), i*i, s );
END LOOP;
END;
Most PL/SQL input and output is through SQL statements, to store data in database
tables or query those tables. All other PL/SQL I/O is done through APIs that interact
with other programs. For example, the DBMS_OUTPUT package has procedures such as
PUT_LINE. To see the result outside of PL/SQL requires another program, such as
SQL*Plus, to read and display the data passed to DBMS_OUTPUT.
SQL*Plus does not display DBMS_OUTPUT data unless you first issue the SQL*Plus
command SET SERVEROUTPUT ON as follows:
SET SERVEROUTPUT ON
For information on the SEVEROUTPUT setting, see the "SQL*Plus Command Reference"
chapter in SQL*Plus User's Guide and Reference.
Other PL/SQL APIs for processing I/O are:
■ HTF and HTP for displaying output on a web page
■ DBMS_PIPE for passing information back and forth between PL/SQL and
operating-system commands
■ UTL_FILE for reading and writing operating-system files
■ UTL_HTTP for communicating with web servers
■ UTL_SMTP for communicating with mail servers
Records
Records are composite data structures whose fields can have different datatypes. You
can use records to hold related items and pass them to subprograms with a single
Understanding the Main Features of PL/SQL
Overview of PL/SQL 1-17
parameter. When declaring records, you use a TYPE definition
Example 1–16 Declaring a Record Type
DECLARE
TYPE timerec IS RECORD (hours SMALLINT, minutes SMALLINT);
TYPE meetin_typ IS RECORD (
date_held DATE,
duration timerec, -- nested record
location VARCHAR2(20),
purpose VARCHAR2(50));
BEGIN
-- NULL does nothing but allows unit to be compiled and tested
NULL;
END;
Object Types
PL/SQL supports object-oriented programming through object types. An object type
encapsulates a data structure along with the functions and procedures needed to
manipulate the data. The variables that form the data structure are known as
attributes. The functions and procedures that manipulate the attributes are known as
methods.
Object types reduce complexity by breaking down a large system into logical entities.
This lets you create software components that are modular, maintainable, and
reusable. Object-type definitions, and the code for the methods, are stored in the
database. Instances of these object types can be stored in tables or used as variables
inside PL/SQL code. Example 1–17 shows an object type definition for a bank account.
Example 1–17 Defining an Object Type
CREATE TYPE bank_account AS OBJECT (
acct_number NUMBER(5),
balance NUMBER,
status VARCHAR2(10),
MEMBER PROCEDURE open (SELF IN OUT NOCOPY bank_account, amount IN NUMBER),
MEMBER PROCEDURE close (SELF IN OUT NOCOPY bank_account, num IN NUMBER,
amount OUT NUMBER),
MEMBER PROCEDURE deposit (SELF IN OUT NOCOPY bank_account, num IN NUMBER,
amount IN NUMBER),
MEMBER PROCEDURE withdraw (SELF IN OUT NOCOPY bank_account, num IN NUMBER,
amount IN NUMBER),
MEMBER FUNCTION curr_bal (num IN NUMBER) RETURN NUMBER );
Database Triggers
A database trigger is a stored subprogram associated with a database table, view, or
event. The trigger can be called once, when some event occurs, or many times, once for
each row affected by an INSERT, UPDATE, or DELETE statement. The trigger can be
called after the event, to record it or take some followup action. Or, the trigger can be
called before the event to prevent erroneous operations or fix new data so that it
conforms to business rules. In Example 1–19 the table-level trigger fires whenever
salaries in the employees table are updated, such as the processing in Example 1–7 on
page 1-10. For each update, the trigger writes a record to the emp_audit table.
Example 1–19 Creating a Database Trigger
CREATE TABLE emp_audit ( emp_audit_id NUMBER(6), up_date DATE,
new_sal NUMBER(8,2), old_sal NUMBER(8,2) );
CREATE OR REPLACE TRIGGER audit_sal
AFTER UPDATE OF salary ON employees FOR EACH ROW
BEGIN
-- bind variables are used here for values
INSERT INTO emp_audit VALUES( :old.employee_id, SYSDATE,
:new.salary, :old.salary );
END;
/
Delimiters
A delimiter is a simple or compound symbol that has a special meaning to PL/SQL.
For example, you use delimiters to represent arithmetic operations such as addition
and subtraction. Table 2–1 contains a list of PL/SQL delimiters.
Table 2–1 PL/SQL Delimiters
Symbol Meaning
+ addition operator
% attribute indicator
' character string delimiter
. component selector
/ division operator
( expression or list delimiter
) expression or list delimiter
: host variable indicator
, item separator
* multiplication operator
" quoted identifier delimiter
= relational operator
< relational operator
> relational operator
@ remote access indicator
; statement terminator
- subtraction/negation operator
:= assignment operator
=> association operator
|| concatenation operator
** exponentiation operator
<< label delimiter (begin)
>> label delimiter (end)
/* multi-line comment delimiter (begin)
*/ multi-line comment delimiter (end)
.. range operator
<> relational operator
!= relational operator
~= relational operator
^= relational operator
<= relational operator
>= relational operator
-- single-line comment indicator
Quoted Identifiers
For flexibility, PL/SQL lets you enclose identifiers within double quotes. Quoted
identifiers are seldom needed, but occasionally they can be useful. They can contain
any sequence of printable characters including spaces but excluding double quotes.
Thus, the following identifiers are valid:
"X+Y"
"last name"
"on/off switch"
"employee(s)"
"*** header info ***"
The maximum size of a quoted identifier is 30 characters not counting the double
quotes. Though allowed, using PL/SQL reserved words as quoted identifiers is a poor
programming practice.
Datetime Literals
Datetime literals have various formats depending on the datatype. For example:
Example 2–3 Using DateTime Literals
DECLARE
d1 DATE := DATE '1998-12-25';
t1 TIMESTAMP := TIMESTAMP '1997-10-22 13:01:01';
t2 TIMESTAMP WITH TIME ZONE := TIMESTAMP '1997-01-31 09:26:56.66 +02:00';
-- Three years and two months
-- For greater precision, we would use the day-to-second interval
i1 INTERVAL YEAR TO MONTH := INTERVAL '3-2' YEAR TO MONTH;
-- Five days, four hours, three minutes, two and 1/100 seconds
i2 INTERVAL DAY TO SECOND := INTERVAL '5 04:03:02.01' DAY TO SECOND;
Single-Line Comments
Single-line comments begin with a double hyphen (--) anywhere on a line and extend
to the end of the line. A few examples follow:
Example 2–4 Using Single-Line Comments
DECLARE
howmany NUMBER;
num_tables NUMBER;
BEGIN
-- begin processing
SELECT COUNT(*) INTO howmany FROM USER_OBJECTS
WHERE OBJECT_TYPE = 'TABLE'; -- Check number of tables
num_tables := howmany; -- Compute some other value
END;
Example 2–5 Using Multi-Line Comments
DECLARE
some_condition BOOLEAN;
pi NUMBER := 3.1415926;
radius NUMBER := 15;
area NUMBER;
BEGIN
/* Perform some simple tests and assignments */
IF 2 + 2 = 4 THEN
some_condition := TRUE; /* We expect this THEN to always be performed */
END IF;
/* The following line computes the area of a circle using pi, which is the
ratio between the circumference and diameter. After the area is computed,
the result is displayed. */
area := pi * radius**2;
DBMS_OUTPUT.PUT_LINE('The area is: ' || TO_CHAR(area));
END;
Using DEFAULT
You can use the keyword DEFAULT instead of the assignment operator to initialize
variables. For example, the declaration
blood_type CHAR := 'O';
can be rewritten as follows:
blood_type CHAR DEFAULT 'O';
Use DEFAULT for variables that have a typical value. Use the assignment operator for
variables (such as counters and accumulators) that have no typical value. For example:
hours_worked INTEGER DEFAULT 40;
employee_count INTEGER := 0;
You can assign a list of column values to a record by using the SELECT or FETCH
statement, as the following example shows. The column names must appear in the
order in which they were defined by the CREATE TABLE or CREATE VIEW statement.
DECLARE
dept_rec departments%ROWTYPE;
BEGIN
SELECT * INTO dept_rec FROM departments
WHERE department_id = 30 and ROWNUM < 2;
END;
Case Sensitivity
Like all identifiers, the names of constants, variables, and parameters are not case
sensitive. For instance, PL/SQL considers the following names to be the same:
Example 2–13 Case Sensitivity of Identifiers
DECLARE
zip_code INTEGER;
Zip_Code INTEGER; -- duplicate identifier, despite Z/z case difference
BEGIN
zip_code := 90120; -- raises error here because of duplicate identifiers
END;
IS NULL Operator
The IS NULL operator returns the BOOLEAN value TRUE if its operand is null or FALSE
if it is not null. Comparisons involving nulls always yield NULL. Test whether a value
is null as follows:
IF variable IS NULL THEN ...
LIKE Operator
You use the LIKE operator to compare a character, string, or CLOB value to a pattern.
Case is significant. LIKE returns the BOOLEAN value TRUE if the patterns match or
FALSE if they do not match.
The patterns matched by LIKE can include two special-purpose characters called
wildcards. An underscore (_) matches exactly one character; a percent sign (%) matches
zero or more characters. For example, if the value of last_name is 'JOHNSON', the
following expression is true:
last_name LIKE 'J%S_N'
To search for the percent sign and underscore characters, you define an escape
character and put that character before the percent sign or underscore. The following
example uses the backslash as the escape character, so that the percent sign in the
string does not act as a wildcard:
IF sale_sign LIKE '50\% off!' ESCAPE '\' THEN...
BETWEEN Operator
The BETWEEN operator tests whether a value lies in a specified range. It means "greater
than or equal to low value and less than or equal to high value." For example, the
following expression is false:
45 BETWEEN 38 AND 44
IN Operator
The IN operator tests set membership. It means "equal to any member of." The set can
contain nulls, but they are ignored. For example, the following expression tests
whether a value is part of a set of values:
letter IN ('a','b','c')
Be careful when inverting this condition. Expressions of the form:
value NOT IN set
yield FALSE if the set contains a null.
Concatenation Operator
Double vertical bars (||) serve as the concatenation operator, which appends one
string (CHAR, VARCHAR2, CLOB, or the equivalent Unicode-enabled type) to another.
For example, the expression
'suit' || 'case'
returns the following value:
>= greater than or equal to
Operator Meaning
PL/SQL Expressions and Comparisons
2-24 Oracle Database PL/SQL User’s Guide and Reference
'suitcase'
If both operands have datatype CHAR, the concatenation operator returns a CHAR
value. If either operand is a CLOB value, the operator returns a temporary CLOB.
Otherwise, it returns a VARCHAR2 valu
DECLARE
a NUMBER := NULL;
b NUMBER := NULL;
BEGIN
IF a = b THEN -- yields NULL, not TRUE
DBMS_OUTPUT.PUT_LINE('a = b'); -- not executed
ELSIF a != b THEN -- yields NULL, not TRUE
DBMS_OUTPUT.PUT_LINE('a != b'); -- not executed
ELSE
DBMS_OUTPUT.PUT_LINE('Can''t tell if two NULLs are equal');
END IF;
END;
DECLARE
string_type VARCHAR2(60);
dashed string_type%TYPE := 'Gold-i-locks';
-- When the substitution text for REPLACE is NULL,
-- the text being replaced is deleted.
name string_type%TYPE := REPLACE(dashed, '-', NULL);
BEGIN
DBMS_OUTPUT.PUT_LINE('Dashed name = ' || dashed);
DBMS_OUTPUT.PUT_LINE('Dashes removed = ' || name);
END;
SQL> SELECT id, first_name, last_name, NVL(first_name, 'Unknown First Name') FROM employee;
8 rows updated.
SQL>
SQL> PRINT average_salary;
AVERAGE_SALARY
--------------
3053.81875
SQL> SET SERVEROUTPUT ON ESCAPE OFF
SQL>
SQL> DECLARE
2 v_salary employee.salary%TYPE;
3 BEGIN
4
5 SELECT salary
6 INTO v_salary
7 FROM employee
8 WHERE id = '07';
9
10 DBMS_OUTPUT.PUT_LINE('The original salary for id 7 was: '||v_salary);
11
12 v_salary := v_salary * .9;
13
14 UPDATE employee
15 SET salary = v_salary
16 WHERE id = '07';
17
18 DBMS_OUTPUT.PUT_LINE(CHR(0));
19 -- DBMS_OUTPUT.PUT_LINE('The new salary for id 7 is: '||v_salary);
20
21 EXCEPTION
22 WHEN OTHERS
23 THEN DBMS_OUTPUT.PUT_LINE (SQLERRM);
24 END;
25 /
The original salary for id 7 was: 7897.78
PL/SQL procedure successfully completed.
Operator Description
LIKE Matches patterns in strings
IN Matches lists of values
BETWEEN Matches a range of values
IS NULL Matches null values
IS NAN New for Oracle10g. Matches the NaN special value, which means "not a number"
IS INFINITE New for Oracle10g. Matches infinite BINARY_FLOAT and BINARY_DOUBLE values
You can also use the NOT operator to reverse the meaning of LIKE, IN, BETWEEN, and IS NULL:
1. NOT LIKE
2. NOT IN
3. NOT BETWEEN
4. IS NOT NULL
5. IS NOT NAN
6. IS NOT INFINITE
SQL>
SQL> SELECT COUNT(*) num_owned, a.owner
2 FROM dba_objects a
3 WHERE 10<(SELECT COUNT(*) FROM dba_objects b
4 WHERE a.owner=b.owner)
5 GROUP BY a.owner;
NUM_OWNED OWNER
---------- ------------------------------
473 MDSYS
1143 FLOWS_020100
2769 PUBLIC
575 JAVA2S
339 CTXSYS
34 HR
12 FLOWS_FILES
449 SYSTEM
46 DBSNMP
668 XDB
6631 SYS
11 rows selected.
SQL>
SQL>
SQL> SELECT COUNT(*) num_owned, a.owner
2 FROM dba_objects a
3 WHERE 100<(SELECT COUNT(*) FROM dba_objects b
4 WHERE a.owner=b.owner)
5 GROUP BY a.owner;
NUM_OWNED OWNER
---------- ------------------------------
473 MDSYS
1143 FLOWS_020100
2769 PUBLIC
575 JAVA2S
339 CTXSYS
449 SYSTEM
668 XDB
6631 SYS
8 rows selected.
SQL> SELECT COUNT(*) num_owned, a.owner
2 FROM dba_objects a
3 WHERE 10<(SELECT COUNT(*) FROM dba_objects b
4 WHERE a.owner=b.owner)
5 GROUP BY a.owner;
select *
2 from (select empno, msal
3 from history
4 order by msal desc)
5 where rownum <= 3;
SQL> select job, ename
2 , case
3 when msal <= 2500
4 then 'cheap'
5 else 'expensive'
6 end as class
7 from employees
8 where bdate < date '1964-01-01'
9 order by case job
10 when 'DIRECTOR' then 1
11 when 'MANAGER' then 2
12 else 3
13 end;