Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Programming PL SQL.doc
Скачиваний:
2
Добавлен:
01.07.2025
Размер:
5.06 Mб
Скачать

18.1.5.2 Getting around the mutating table error

So what should you do when confronted with this error? Some developers throw up their hands, abandon triggers, and seek another way of getting the same logic enforced. This is not necessary. Instead, you can rely on the fact that statement-level triggers are not restricted from accessing the firing table, and that package-level data structures persist for the duration of your session.

Here's the best way to look at it: within a row-level trigger, you need to take a certain action. But Oracle won't let you take that action. Instead, you must defer your action until it can be performed in a statement-level trigger. Fine. But if you are going to wait until later, you have to remember what it is you needed to do. PL/SQL collections (array-like structures, which we cover in Chapter 11) serve very well as your to-do list.

So in the row-level trigger, instead of taking the necessary action, you write all the information you need to remember in order to take that action at the statement level. Then when all the row-level triggers have fired, control passes to the statement-level trigger. And inside that trigger, you can scan through the contents of the collection and execute the code you originally wanted to perform at the row level. These steps are shown in Figure 18-2. Let's take a look at how Kay can apply this technique to her situation.

Figure 18-2. Using a collection as a work list to bypass mutating trigger errors

Kay can implement the following algorithm to subvert any chances of mutating:

  1. Initialize collections in a PL/SQL package when the statement begins.

  2. Store record identifiers in these collections as each row is processed.

  3. Process each entry in the collections after the statement ends.

First a PL/SQL package is required. Here is the start of one that will help Kay out:

/* File on web: mutation_zone.sql */

CREATE OR REPLACE PACKAGE give_away_money AS

PROCEDURE init_tables;

END give_away_money;

CREATE OR REPLACE PACKAGE BODY give_away_money

AS

-- structure to hold account numbers

TYPE v_account_table_type IS TABLE OF NUMBER

INDEX BY BINARY_INTEGER;

v_account_table v_account_table_type;

/*----------------------------------------*/

PROCEDURE init_tables IS

/*----------------------------------------*/

BEGIN -- Initialize the account list to empty

v_account_table.DELETE;

END init_tables;

END give_away_money;

This simple beginning takes care of cleaning out the collection to store account numbers as they are inserted. Next we have to hook up the init_tables procedure with a BEFORE statement trigger:

CREATE OR REPLACE TRIGGER before_insert_statement

BEFORE INSERT ON account

BEGIN

/*

|| Initialize collections to hold accounts that

|| will get 100 free dollars when we are done!

*/

give_away_money.init_tables;

END;

Now whenever an INSERT statement is executed against the account table, the collection will be emptied.

The next step is to capture the account numbers as they are processed. Here's a possible procedure to add to the give_away_money package:

PROCEDURE add_account_to_list ( p_account NUMBER ) IS

BEGIN

v_account_table(NVL(v_account_table.LAST,0) + 1) := p_account;

END add_account_to_list;

It attaches to an AFTER INSERT row trigger like this:

CREATE OR REPLACE TRIGGER after_insert_row

AFTER INSERT ON account

FOR EACH ROW

BEGIN

/*

|| Add the new account to the list of those in line for

|| 100 dollars.

*/

give_away_money.add_account_to_list(:NEW.account_id);

END;

Last but not least, a procedure in the package finally gives the money away:

PROCEDURE give_it_away_now

IS

-- Create $100 deposits for accounts that have been created

v_element PLS_INTEGER;

BEGIN

v_element := v_account_table.FIRST;

LOOP

EXIT WHEN v_element IS NULL;

INSERT INTO account_transaction

(transaction_id,

account_id,

transaction_type,

transaction_amount,

comments)

VALUES(account_transaction_seq.nextval,

v_account_table(v_element),

'DEP',

100,

'Free Money!');

v_element := v_account_table.NEXT(v_element);

END LOOP;

END give_it_away_now;

This procedure attaches to an AFTER INSERT statement trigger as such:

CREATE OR REPLACE TRIGGER after_insert_statement

AFTER INSERT ON account

BEGIN

-- At long last we can give away the money!

give_away_money.give_it_away_now;

END;

Now after dropping Kay's original trigger and replacing it with the new package and triggers, testing reveals the following behavior:

SQL> INSERT INTO account (account_id,account_owner)

2 VALUES (1,'Test');

SQL> SELECT * FROM account_transaction

2 WHERE account_id = 1;

TRANSACTION_ID ACCOUNT_ID TRA TRANSACTION_AMOUNT COMMENTS

-------------- ---------- --- ------------------ ------------------------------

1 1 DEP 100 Free Money!

SQL> BEGIN

2 FOR counter IN 20..25 LOOP

3 INSERT INTO account

4 (account_id,account_owner)

5 VALUES(counter,'Test');

6 END LOOP;

7 END;

SQL> SELECT *

2 FROM account_transaction

3 WHERE account_id BETWEEN 20 AND 25

TRANSACTION_ID ACCOUNT_ID TRA TRANSACTION_AMOUNT COMMENTS

-------------- ---------- --- ------------------ ------------------------------

2 20 DEP 100 Free Money!

3 21 DEP 100 Free Money!

4 22 DEP 100 Free Money!

5 23 DEP 100 Free Money!

6 24 DEP 100 Free Money!

7 25 DEP 100 Free Money!

6 rows selected.

Thanks to the powerful interaction of statement-level triggers, row-level triggers and PL/SQL packages, Kay is free to enjoy the rest of her weekend without worrying about any mutation problems.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]