You are on page 1of 33

Effective SQL, PL/SQL Using

• Multi-Table Insert
• Data Aggregation
• Merge Statement
• Analytical Functions
• BulkCollect and ForAll

May 2007
Multi-Table Insert

• INSERT … SELECT

In a multi table insert, you insert computed rows derived from the rows
returned from the evaluation of a sub query into one or more tables.

Types:
• Unconditional Insert
• Conditional Insert
• Conditional First Insert
• Pivoting Insert
Current Scenario :

INSERT INTO revenue (person,month,revenue)


SELECT person, 'Jan', rev_jan FROM revert UNION ALL
SELECT person, 'Feb', rev_feb FROM revert UNION ALL
SELECT person, ‘Apr‘ ,rev_mar FROM revert ;

With Multi Table Insert:


Insert ALL
into revenue values (person ,'JAN', rev_jan)
into revenue values (person, 'FEB', rev_feb)
into revenue values (person,‘ MAR', rev_mar)
Select person, rev_jan, rev_feb, rev_mar, rev_apr, rev_may,
rev_jun
from revert;
Unconditional INSERT

Insert all The rows fetched by the select

INSERT ALL
INTO hbg2 VALUES (emp_no,name )
INTO hbg1 VALUES (emp_no,name)
SELECT emp_no, name
FROM hbg
WHERE emp_no > 2;
Conditional INSERT ALL

When the Row Meets this Predicate – Insert

INSERT ALL
WHEN emp_no > 4 THEN
INTO hbg2 VALUES (emp_no,name )
WHEN emp_no <= 4 THEN
INTO hbg1 VALUES (emp_no,name)
SELECT emp_no,name
FROM hbg
WHERE emp_no > 2;
Conditional INSERT FIRST

Check for the First Satisfying Condition

INSERT FIRST
WHEN name ='Harsh' THEN
INTO hbg1 VALUES (emp_no,name)
WHEN emp_no = 2 THEN
INTO hbg2 VALUES (emp_no,name)
WHEN emp_no >2 and emp_no < 4 THEN
INTO hbg3 VALUES (emp_no,name)
ELSE
INTO hbg4 VALUES (emp_no,name)
SELECT emp_no, name FROM hbg;
Pivoting INSERT

All the Inserts are in same table

INSERT ALL
INTO sales VALUES (emp_id,name,region1)
INTO sales VALUES (emp_id,name,region2)
INTO sales VALUES (emp_id,name,region3)
SELECT emp_id, name, region1,region2, region3
FROM sales_main;
Tips and Fine Points

• Specifying FIRST tells oracle to skip subsequent WHEN clauses after it


finds one true for the row to be evaluated.

• We can also use an ELSE clause to tell oracle what to do if none of the
WHEN clauses evaluates to true

• Aliases name of the column given in the select can be used in


conditions as well as insert values.

• Watch For Sequences.

• Instead of using Sqlldr to load data into multiple table , multi table inserts
coupled with an external table can be used.
MERGE Statement

• This specialized statement combines insert and update into a single


operation. It is intended for applications that perform particular patterns of
inserts and updates.

• Previous approach for achieving the same was:


- Insert, if failed on primary key then update
- Update, if SQL%NOTFOUND then insert.

• Avoid Multiple update statements.


• Now, a single SQL statement does the job.
Syntax

We can conditionally insert rows into table B from table A with help of
single SQL statement.

MERGE INTO high_score_copy B  Target table


USING high_score A - source (can be a table or view or
subquerry)
ON ( A. name=B.name) condition to either update or
insert
WHEN MATCHED THEN
UPDATE SET B.score=A. score
WHEN NOT MATCHED THEN
INSERT VALUES (A.name,A.score);
Classic Example

Merge into accountattributes a using dual


On (a.Account_num = ‘acc00010102')
When matched
Then update
Set a.Marital_status =‘single'
When not matched
Then insert (a.Account_num, a.Marital_status)
Values (‘acc00010102',‘single')
Data Aggregation

• ROLLUP
Used to calculate sub-totals
• CUBE
Used to get cross-tabulation results
• Grouping Sets
For Multiple Groupings
• With Clause
Materialize the Repeated Inline Views
With Clause

• The WITH clause (aka subquery_factoring_clause) enables you to


reuse the same query block in a SELECT statement when it occurs
more than once within a complex query.
• This is particularly useful when a query has multiple references to the
same query block and there are joins and aggregations.
• Using the WITH clause, Oracle retrieves the results of a query block and
stores them in the user's temporary table space.

With data as
(Select rownum r from all_objects
Where rownum< 50)
Select sysdate + r from data
Tips and Fine Points

• All the group by enhancements make only one pass over


the base table
• Code readability is improved
• Gets you different variations in aggregating data
Analytics

Ordered Array Semantics in SQL queries

Select deptno,ename,sal
Row_number() over (partition by deptno
Order by sal desc )
from emp
Deptno Ename Sal SCOTT
King 3000 11
5000
10 FORD
Clark 3000 22
2450
JONES
Miller 2975 33
1300
20 ADAMS 1100 4
30 SMITH 800 5
Analytics
• And the list is infinitely long
– "Analytics are the coolest thing to happen to SQL since the keyword
Select"
– Lets look at a complex example

• RANK

• DENSE_RANK

• ROW_NUMBER

• FIRST_VALUE / LAST_VALUE

• LEAD

• LAG
Classic Problem
I have records like this:
Time Amount
11/22/2003 12:22:01 100
11/22/2003 12:22:03 200
11/22/2003 12:22:04 300
11/22/2003 12:22:45 100
11/22/2003 12:22:46 200
11/22/2003 12:23:12 100
11/22/2003 12:23:12 200

What I need to do is sum the amounts where the time of the records is within 3
seconds of each other. In the case where the data is like this:
11/22/2003 12:22:03 200
11/22/2003 12:22:04 200
11/22/2003 12:22:05 200
11/22/2003 12:22:06 200
11/22/2003 12:22:07 200
11/22/2003 12:22:08 200
11/22/2003 12:22:09 200

There would only be one row with the total for all the rows. (Basically, we are looking
for "instances" where we define an instance such that all the records within the
instance are no more than three seconds apart. So there can be 1 or many records all
of the same instance and the resulting summation would have one summary record
per instance.) Would you please point me in the right direction?
Analytics on Analytics

• Start with first row (thinking iteratively here)


– If prior row is within 3 seconds -- same group, continue

• Abs(lag(x) over (order by x)-x) <= 3 seconds


– Else new group, break and get a "new" group id
– Need to use analytics on top of analytics

• Inline views -- very powerful here


Analytics at two levels
Select min(x), max(x), sum(y)
from (
select x, y, max(rn) over (order by x) max_rn
from ( select x, y, lag(x) over (order by x),
case when abs(lag(x) over (order by x) - x) > 3/24/60/60
then row_number() over (order by x)
end rn
from t))
group by max_rn
order by 1
BULK COLLECT and FORALL
• Improve the performance of multi-row SQL operations by an order of
magnitude or more with bulk/array processing in PL/SQL!
CREATE PROCEDURE upd_for_dept (
dept_in IN employee.department_id%TYPE,
newsal_in IN employee.salary%TYPE)
IS
CURSOR emp_cur IS
SELECT employee_id,salary,hire_date
FROM employee WHERE department_id = dept_in;
BEGIN
FOR rec IN emp_cur LOOP
UPDATE employee SET salary = newsal_in
WHERE employee_id = rec.employee_id;
END LOOP;
END upd_for_dept;

“Conventional
binds”
Conventional Bind

Oracle server

PL/SQL Runtime Engine SQL Engine


PL/SQL block
Procedur
al SQL
FOR rec IN emp_cur LOOP
statement statement
UPDATE employee
SET salary = ...
executor executor
WHERE employee_id =
rec.employee_id;
END LOOP;

Performance penalty
for many “context
switches”
Enter the “Bulk Bind”

Oracle server0

PL/SQL Runtime Engine SQL Engine

PL/SQL block
Procedural
statement SQL
FORALL indx IN executor statement
deptlist.FIRST.. executor
deptlist.LAST
UPDATE employee
SET salary = ...
WHERE employee_id =
deptlist(indx);
Much less overhead for
context switching
Use the FORALL Bulk Bind Statement
• Instead of executing repetitive, individual DML statements,
you can write your code like this:
PROCEDURE remove_emps_by_dept (deptlist dlist_t)
IS
BEGIN
FORALL aDept IN deptlist.FIRST..deptlist.LAST
DELETE FROM emp WHERE deptno = deptlist(aDept);
END;

• Things to be aware of:


– You MUST know how to use collections to use this feature!
– Only a single DML statement is allowed per FORALL.
– SQL%BULK_ROWCOUNT returns the number of rows affected by
each row in the binding array.
Use BULK COLLECT INTO for Queries

DECLARE
TYPE employees_aat IS TABLE OF employees%ROWTYPE
Declare a collection of INDEX BY BINARY_INTEGER;
records to hold the
queried data. l_employees employees_aat;
BEGIN
SELECT *
BULK COLLECT INTO l_employees
Use BULK COLLECT FROM employees;
to retrieve all rows.
FOR indx IN 1 .. l_employees.COUNT
LOOP
process_employee (l_employees(indx));
END LOOP;
Iterate through the END;
collection contents with
a loop.
Limit the number of rows returned by BULK COLLECT

CREATE OR REPLACE PROCEDURE bulk_with_limit


(deptno_in IN dept.deptno%TYPE)
IS
CURSOR emps_in_dept_cur IS Use the LIMIT clause with the INTO
SELECT * to manage the amount of memory
FROM emp used with the BULK COLLECT
WHERE deptno = deptno_in; operation.

TYPE emp_tt IS TABLE OF emp%ROWTYPE;


emps emp_tt;
BEGIN
OPEN three_cols_cur;
LOOP WARNING!
FETCH emps_in_dept_cur
BULK COLLECT INTO emps BULK COLLECT will not raise
LIMIT 100; NO_DATA_FOUND if no rows
are found.
EXIT WHEN emps.COUNT = 0;
Best to check contents of
process_emps (emps); collection to confirm that
END LOOP; something was retrieved.
END bulk_with_limit;
Dynamic FORALL Example
• This example shows the use of bulk binding and collecting, plus
application of the RETURNING clause.

CREATE TYPE NumList IS TABLE OF NUMBER;


CREATE TYPE NameList IS TABLE OF VARCHAR2(15);

PROCEDURE update_emps (
col_in IN VARCHAR2, empnos_in IN numList) IS
enames NameList;
BEGIN
FORALL indx IN empnos_in.FIRST .. empnos_in.LAST
EXECUTE IMMEDIATE
'UPDATE emp SET ' || col_in || ' = ' || col_in
|| ' * 1.1 WHERE empno = :1
RETURNING ename INTO :2'
USING empnos_in (indx ) Notice that empnos_in is
RETURNING BULK COLLECT INTO enames; indexed, but enames is
... not.
END;
Excellent Exception Handling for Bulk Operations

• Allows you to continue past errors and obtain error information for
each individual operation (for dynamic and static SQL).

CREATE OR REPLACE PROCEDURE load_books (books_in IN book_obj_list_t)


IS
bulk_errors EXCEPTION;
PRAGMA EXCEPTION_INIT ( bulk_errors, -24381 );
BEGIN
FORALL indx IN books_in.FIRST..books_in.LAST Allows processing of
SAVE EXCEPTIONS
all rows, even after an
INSERT INTO book values (books_in(indx));
EXCEPTION
error occurs.
WHEN BULK_ERRORS THEN
FOR indx in 1..SQL%BULK_EXCEPTIONS.COUNT New cursor
LOOP attribute, a
log_error (SQL%BULK_EXCEPTIONS(indx)); pseudo-collection
END LOOP;
END;
Tips and Fine Points
• Use bulk binds in these circumstances:
– Recurring SQL statement in PL/SQL loop. Oracle recommended
threshold: five rows!
• Bulk bind rules:
– Can be used with any kind of collection; Collection subscripts cannot
be expressions.

• Bulk collects:
– Can be used with implicit and explicit cursors
– Collection is always filled sequentially, starting at row 1

• Also stick with a cursor FOR loop....


– If you want to do complex DML processing on each row as it is
queried – and possibly halt further fetching, and you can't use SAVE
EXCEPTIONS.
• Otherwise, moving to BULK COLLECT and FORALL is a smart move!

You might also like