NOCOPY in PL/SQL

The NOCOPY hint to Oracle is a bit misleading and not very intuitive.

NOCOPY can only be used with IN OUT parameters.

Any time you use an IN OUT parameter, regardless of whether you use NOCOPY, changes made inside of the procedure will be visible outside of the procedure when no exception is raised by the procedure.

The sole difference between IN OUT and IN OUT NOCOPY is what happens when an exception is raised by the procedure. With an IN OUT parameter, changes made to the collection are reversed when an exception is raised. With an IN OUT NOCOPY parameter, changes are not reversed.

Let's first examine an IN OUT parameter with no exceptions to prove that changes made inside of a collection are visible outside, even without NOCOPY:

DECLARE
    TYPE t_numbers IS TABLE OF NUMBER;
    v_numbers t_numbers;

    PROCEDURE work(
        v_numbers IN OUT t_numbers
    )
    IS
    BEGIN
        FOR i IN 1 .. v_numbers.COUNT LOOP
            v_numbers(i) := v_numbers(i) ** 2;
        END LOOP;
    END work;

BEGIN
    v_numbers := t_numbers(1,2,3,4,5);

    work(v_numbers);

    FOR i IN 1 .. v_numbers.COUNT LOOP
        DBMS_OUTPUT.PUT_LINE(v_numbers(i));
    END LOOP;
END;
/

This outputs the following, proving that changes made inside the procedure are visible outside of the procedure:

1
4
9
16
25

If we change our work procedure to use IN OUT NOCOPY:

v_numbers IN OUT NOCOPY t_numbers

It results in the exact same output.

So there is no effective difference between IN OUT and IN OUT NOCOPY in the case where the procedure does not raise an exception.


However, what happens if an exception is raised? Let's get rid of NOCOPY and raise an exception.

DECLARE
    TYPE t_numbers IS TABLE OF NUMBER;
    v_numbers t_numbers;

    PROCEDURE work(
        v_numbers IN OUT t_numbers
    )
    IS
    BEGIN
        FOR i IN 1 .. v_numbers.COUNT LOOP
            v_numbers(i) := v_numbers(i) ** 2;
        END LOOP;

        RAISE_APPLICATION_ERROR(-20001, 'error'); 
    END work;

BEGIN
    v_numbers := t_numbers(1,2,3,4,5);

    work(v_numbers);

    FOR i IN 1 .. v_numbers.COUNT LOOP
        DBMS_OUTPUT.PUT_LINE(v_numbers(i));
    END LOOP;
EXCEPTION
    WHEN OTHERS THEN
        FOR i IN 1 .. v_numbers.COUNT LOOP
            DBMS_OUTPUT.PUT_LINE(v_numbers(i));
        END LOOP;
END;
/

This outputs the following:

1
2
3
4
5

So, when using IN OUT instead of IN OUT NOCOPY, no changes where made to the collection when the procedure raised an exception.

However, if we change it to NOCOPY:

v_numbers IN OUT NOCOPY t_numbers

it will output the following:

1
4
9
16
25

This shows that the sole difference between IN OUT and IN OUT NOCOPY is that the former will reverse changes when an exception is raised while the latter does not.


So what is NOCOPY doing?

It's not very intuitive.

Without NOCOPY, Oracle will pass in a reference and that reference will be used for all read operations that take place on the collection. The exact same thing occurs when you use IN instead of IN OUT. There is nothing special about this when it comes to read operations.

However, when a write operation takes place on an IN OUT parameter that does not have NOCOPY, Oracle will make a temporary copy, and all write operations will operate on the copy of this collection. If the procedure finishes without raising an exception, Oracle will then copy it back a second time into the parameter, and the changes will be visible.

If we instead use an IN OUT NOCOPY, no copying takes place.

This explains why raising an exception with NOCOPY doesn't reverse the changes.

At least for the code that I write, I almost always use IN OUT NOCOPY instead of IN OUT with collections. The only case in which I would prefer to use IN OUT is if I wanted to be able to catch an exception and roll back any changes a procedure made to the collection. But this is a rare case for me. I would almost always prefer the performance improvement from NOCOPY.

The NOCOPY behavior with collections is the exact same behavior that happens by default in most other programming language: changes made to a container inside of a function reflect outside of that function even if the function raises an exception. In most other languages, if you want to do a rollback you would manually copy and return a new function instead.


Comments

Add Comment

Name

Email

Comment

Are you human? - one = 5