Skip to Main Content
  • Questions
  • All of your articles in Oracle Magazine

Breadcrumb

Question and Answer

Tom Kyte

Thanks for the question, Jimmy.

Asked: August 28, 2012 - 4:59 pm UTC

Last updated: February 25, 2013 - 7:52 am UTC

Version: 11.2.0.2.0

Viewed 1000+ times

You Asked

Steve Feuerstein posted a URL that contains all of his past articles that have been published in Oracle magazine.

http://www.oracle.com/technetwork/issue-archive/index-087690.html

I was wondering if you had a similar link and if you could post it here. It sure would be a handy reference.

and Tom said...

Here you go:

http://www.oracle.com/technetwork/issue-archive/index-093676.html


you can also go here:
https://asktom.oracle.com/magazine-archive.htm

and they have back issues as well as links to these lists - near the bottom of that page in FEATURED COLUMNS

Rating

  (10 ratings)

Is this answer out of date? If it is, please let us know via a Comment

Comments

Very useful -

A reader, August 29, 2012 - 7:59 pm UTC

Steven and Tom have been my Gurus in Oracle since I started learning oracle in early 90's.

Its been almost around 22 years and I am still learning from them and mentoring others from what I learned from them.
What a recurring blessing !!!

Thanks to both of them and also for posting these links

I agree

A reader, August 30, 2012 - 7:48 am UTC

I asked my first question on Asktom was in 2003. Since then, I have asked many more. Mainly, Tom has dispersed the myth that Oracle is a black box and works in mysterious ways. Now I know that whatever happens in database can be explained, one just needs to find the explanation. I truly consider Tom Kyte my online guru. Bulk of my understanding about Oracle database engine is attributed to Mr. Kyte.

Steve Feuerstein's grasp on PL/SQL is amazing. Though I do not write PL/SQL code any more, it is still refreshing to read articles by Mr. Feuerstein. He brings out the true power in PL/SQL.

???

DJB, August 31, 2012 - 3:49 am UTC

Been on your hols,Tom ?
Tom Kyte
September 10, 2012 - 6:31 pm UTC

Or been working on the road - and then labor day holiday - and then working on the road again..

BULK COLLECT

Mihail Bratu, September 05, 2012 - 9:29 am UTC

Hi Last Reader,
Concerning Mr. Steven Feuerstein I do not entirely agree; let’s look at the last post of Mr. Steven Feuerstein in Oracle Magazine September/October 2012 about BULK COLLECT and FORALL. The intent of Mr. Feuerstein is to illustrate the usage of BULK COLLECT but the BULK COLLECT clause should not have been used under the conditions of the test case.
The statement,
SELECT employee_id
BULK COLLECT INTO l_employee_ids
FROM employees
WHERE department_id = increase_salary.department_id_in;

is only resource consuming, having no benefit because the slowest point of the procedure, the call of check_eligibility, acts the same both in the cursor loop and in the collection loop but using BULK COLLECT will use up unnecessary resources. A better option would be to use a cursor, as in 10g and above a cursor fetches 100 rows at a time, using less resources than BULK COLLECT.

The procedure could be:

PROCEDURE increase_salary
(department_id_in IN employees.department_id%TYPE
,increase_pct_in IN NUMBER
)
IS
TYPE employee_ids_t IS TABLE OF employees.employee_id%TYPE;
l_eligible BOOLEAN;
l_eligible_ids employee_ids_t;
BEGIN
FOR employee_rec IN (
SELECT employee_id
FROM employees
WHERE department_id = increase_salary.department_id_in
)
LOOP
check_eligibility
(employee_rec.employee_id
,increase_pct_in
,l_eligible);
IF l_eligible
THEN
l_eligible_ids(l_eligible_ids.COUNT + 1) := employee_rec.employee_id;
END IF;
END LOOP;

FORALL indx IN 1 .. l_eligible_ids.COUNT
UPDATE employees emp
SET emp.salary = emp.salary + emp.salary * increase_salary.increase_pct_in
WHERE emp.employee_id = l_eligible_ids (indx);

END increase_salary;

Currently, BULK COLLECT is most useful in the RETURNING clause.

Regards
Tom Kyte
September 10, 2012 - 7:48 pm UTC

ummm, what happens when there are more than a handful of employees in that departement.

Your code is *scary*, your code is the code that causes the server to swap every now and then.


i don't like your code.

it should fetch bulk collect about 1000 rows at a time (at most).

Slow function in PL/SQL

Mihail Bratu, September 11, 2012 - 1:57 am UTC

Hi Tom,
Can you provide, please, a test case to illustrate your assertion?
Thank you

Tom Kyte
September 14, 2012 - 4:17 pm UTC

search on this site, I've done so at least 1,000 times over and over and over...

how about we do this in reverse, prove me wrong.

Files link is failing

sPh, September 11, 2012 - 9:21 am UTC

The Files link on AskTom is currently generating the following error message:

=====
failed to parse SQL query:
ORA-00904: "APEX_APPLICATION"."GET_CURRENT_FLOW_SGID": invalid identifier
=====

I changed the URL NO to YES and reloaded, so there should be error message in the log.

sPh

Mihail Bratu, September 18, 2012 - 9:27 am UTC

Very interesting thing, I wrote my observation based on your 1,000 times over and over and over posted test cases and your three books.
Where do you see the gain or the advantage when slow by slow processing procedure “check_eligibility” acts in a collection loop opposite to a cursor fetching loop?
What is the difference between:
FOR indx IN 1 .. l_employee_ids.COUNT
LOOP
-- slow by slow processing
check_eligibility (l_employee_ids (indx),
increase_pct_in,
l_eligible);

IF l_eligible
THEN
l_eligible_ids.extend(1);
l_eligible_ids (l_eligible_ids.COUNT) := l_employee_ids (indx);
END IF;
END LOOP;
and
FOR employee_rec IN (
SELECT employee_id
FROM employees
WHERE department_id = increase_salary_1.department_id_in
)
LOOP
-- slow by slow processing
check_eligibility
(employee_rec.employee_id
,increase_pct_in
,l_eligible);
IF l_eligible
THEN
l_eligible_ids.extend(1);
l_eligible_ids(l_eligible_ids.COUNT) := employee_rec.employee_id;
END IF;
END LOOP;
I am surprised that you suggest there is a good approach to bulk collect and then slow by slow process each record in a specific procedure (or function).
I’ll create a new test case as you suggested although between your thousands there are enough examples:

CREATE TABLE EMPLOYEES
AS
SELECT OBJECT_ID employee_id
,OWNER department_id
,TRUNC(dbms_random.value(1000,4000)) salary
FROM ALL_OBJECTS
/
ALTER TABLE EMPLOYEES
ADD CONSTRAINT pk_employee_id PRIMARY KEY (employee_id)
/
CREATE INDEX ix_department_id ON EMPLOYEES
(department_id ASC
)
/
CREATE TABLE EMPLOYEES_INCREASE_ALLOWED
AS
SELECT OBJECT_ID employee_id
,TRUNC(dbms_random.value,2) increase_pct_allowed
FROM ALL_OBJECTS
/
CREATE UNIQUE INDEX ix_employee_id ON EMPLOYEES_INCREASE_ALLOWED
(employee_id ASC
)
/

Tom Kyte
September 20, 2012 - 5:10 am UTC

you are not comparing slow by slow to bulk

you are comparing an in memory array to fetching data from a table???

I don't know what you are doing here.


bad - slow by slow - fetch a row, process a row

less worse - bulk - fetch N rows, process N rows

good - set based - do it in a single sql


bad:


create or replace procedure slow_by_slow
as
begin
    for x in (select rowid rid, object_name 
                from t t_slow_by_slow)
    loop
        x.object_name := substr(x.object_name,2)
                      ||substr(x.object_name,1,1);
        update t 
           set object_name = x.object_name 
         where rowid = x.rid;
    end loop;
end;


less worse:

create or replace procedure bulk
as
    type ridArray is table of rowid;
    type onameArray is table 
             of t.object_name%type;

    cursor c is select rowid rid, object_name 
                  from t t_bulk;
    l_rids      ridArray;
    l_onames    onameArray;
    N           number := 100;
begin
    open c;
    loop
        fetch c bulk collect 
        into l_rids, l_onames limit N;
        for i in 1 .. l_rids.count
        loop
            l_onames(i) := substr(l_onames(i),2)
                        ||substr(l_onames(i),1,1);
        end loop;
        forall i in 1 .. l_rids.count
            update t 
               set object_name = l_onames(i) 
             where rowid = l_rids(i);
        exit when c%notfound;
    end loop;
    close c;
end;




good:

update t set object_name = substr(object_name,2) || substr(object_name,1,1)





table T is just a copy of all objects.

Mihail Bratu, September 18, 2012 - 9:31 am UTC

CREATE OR REPLACE
PROCEDURE check_eligibility
(p_employee_id IN number
,p_increase_pct_in IN NUMBER
,p_is_eligible OUT BOOLEAN
)
IS
l_dummy NUMBER;
BEGIN
SELECT EMPLOYEE_ID
INTO l_dummy
FROM EMPLOYEES_INCREASE_ALLOWED
WHERE EMPLOYEE_ID = p_employee_id
AND ABS(INCREASE_PCT_ALLOWED - p_increase_pct_in) <= .1;
p_is_eligible := TRUE;
EXCEPTION
WHEN NO_DATA_FOUND
THEN p_is_eligible := FALSE;
END;
/
CREATE OR REPLACE
PROCEDURE increase_salary (
department_id_in IN employees.department_id%TYPE,
increase_pct_in IN NUMBER)
IS
TYPE employee_ids_t IS TABLE OF employees.employee_id%TYPE;
l_employee_ids employee_ids_t;
l_eligible_ids employee_ids_t := employee_ids_t();
l_eligible BOOLEAN;
BEGIN
SELECT employee_id
BULK COLLECT INTO l_employee_ids
FROM employees
WHERE department_id = increase_salary.department_id_in;
FOR indx IN 1 .. l_employee_ids.COUNT
LOOP
-- slow by slow processing
check_eligibility (l_employee_ids (indx),
increase_pct_in,
l_eligible);
IF l_eligible
THEN
l_eligible_ids.extend(1);
l_eligible_ids (l_eligible_ids.COUNT) := l_employee_ids (indx);
END IF;
END LOOP;
FORALL indx IN 1 .. l_eligible_ids.COUNT
UPDATE employees emp
SET emp.salary = emp.salary + emp.salary * increase_salary.increase_pct_in
WHERE emp.employee_id = l_eligible_ids (indx);
dbms_output.put_line('rows updated: ' || SQL%ROWCOUNT);
END;
/


Mihail Bratu, September 18, 2012 - 9:32 am UTC

CREATE OR REPLACE
PROCEDURE increase_salary_1
(department_id_in IN employees.department_id%TYPE
,increase_pct_in IN NUMBER
)
IS
TYPE employee_ids_t IS TABLE OF employees.employee_id%TYPE;
l_eligible BOOLEAN;
l_eligible_ids employee_ids_t := employee_ids_t();
BEGIN
FOR employee_rec IN (
SELECT employee_id
FROM employees
WHERE department_id = increase_salary_1.department_id_in
)
LOOP
-- slow by slow processing
check_eligibility
(employee_rec.employee_id
,increase_pct_in
,l_eligible);
IF l_eligible
THEN
l_eligible_ids.extend(1);
l_eligible_ids(l_eligible_ids.COUNT) := employee_rec.employee_id;
END IF;
END LOOP;
FORALL indx IN 1 .. l_eligible_ids.COUNT
UPDATE employees emp
SET emp.salary = emp.salary + emp.salary * increase_salary_1.increase_pct_in
WHERE emp.employee_id = l_eligible_ids (indx);
dbms_output.put_line('rows updated: ' || SQL%ROWCOUNT);
END increase_salary_1;
/

var DEPARTMENT_ID VARCHAR2(30)
var INCREASE_PCT NUMBER
var time_0 NUMBER
var time_1 NUMBER

EXEC :DEPARTMENT_ID := 'PUBLIC'; :INCREASE_PCT := 0.5;


DECLARE
l_start NUMBER;
BEGIN
DBMS_OUTPUT.PUT_LINE( ' ');
DBMS_OUTPUT.PUT_LINE( 'Bulk collect');
DBMS_OUTPUT.PUT_LINE( '------------');
l_start := DBMS_UTILITY.GET_TIME;
increase_salary(:DEPARTMENT_ID, :INCREASE_PCT);
:time_0 := DBMS_UTILITY.GET_TIME-l_start;
DBMS_OUTPUT.PUT_LINE( 'time 0: '|| :time_0);
END;
/
rollback;

DECLARE
l_start NUMBER;
BEGIN
DBMS_OUTPUT.PUT_LINE( ' ');
DBMS_OUTPUT.PUT_LINE( 'Cursor');
DBMS_OUTPUT.PUT_LINE( '------');
l_start := DBMS_UTILITY.GET_TIME;
increase_salary_1(:DEPARTMENT_ID, :INCREASE_PCT);
:time_1 := DBMS_UTILITY.GET_TIME-l_start;
DBMS_OUTPUT.PUT_LINE( 'time 1: '|| :time_1);
END;
/
rollback;


Bulk collect
------------
rows updated: 5881
time 0: 162


Cursor
------
rows updated: 5881
time 1: 162

How to get even older asktom columns

Doug Cowles, February 18, 2013 - 2:39 pm UTC

Tom - I have a note that says this was one of my favorite Tom Kyte articles - https://asktom.oracle.com/Misc/oramag/oracle-availability-options.html
So I assume it was May 2000. Your link above shows that was your first column.. is that correct?
Tom Kyte
February 25, 2013 - 7:52 am UTC

Yes, that was the first printed article. Asktom.oracle.com came online in early 2000 and I wrote the first article in March/April of 2000 and it was printed in the May/June 2000 release...