2
votes

I have two tables in my project: accounts and transactions (one-to-many relationship). In every transaction I store the balance of the associated account (after the transaction is executed). Additionally in every transaction I store a value of the transaction.

So I needed a trigger fired when someone adds new transaction. It should check whether new account balance will be correct (old account balance + transaction value = new account balance stored in transaction).

So I was suggested, I should use a compound trigger which would:

  • in before each row section: save a row's PK (made of two columns) somewhere,
  • in after statement section: check whether all inserted transactions where correct.

Now I can't find anywhere how could I implement the first point.

What I already have:

CREATE OR REPLACE TRIGGER check_account_balance_is_valid
FOR INSERT
ON Transactions
COMPOUND TRIGGER
  TYPE Modified_transactions_T IS TABLE OF Transactions%ROWTYPE;
  Modified_transactions       Modified_transactions_T;

  BEFORE STATEMENT IS BEGIN
    Modified_transactions := Modified_transactions_T();
  END BEFORE STATEMENT;

  BEFORE EACH ROW IS BEGIN
    Modified_transactions.extend;
    Modified_transactions(Modified_transactions.last) := :NEW;
  END BEFORE EACH ROW;

  AFTER STATEMENT IS BEGIN
    NULL; -- I will write something here later
  END AFTER STATEMENT;
END check_account_balance_is_valid;
/

However, I got that: Warning: execution completed with warning 11/58 PLS-00049: bad bind variable 'NEW'

Could someone tell me, how to fix it? Or maybe my whole "compound trigger" idea is wrong and you have better suggestions.

Update 1

Here is my ddl script: http://pastebin.com/MW0Eqf9J

2
Not related to the question you asked, but in general i believe triggers are not a good solution. They should be used only when you absolutely cannot modify the applications in the database. In your case, if you have access to all the application code, isn't it possible to add that check in the piece of code that is actually inserting rows into the transactions table?(for example , you have a stored procedure for inserting into transactions table, call a function in that procedure to get this check done) - A Nice Guy

2 Answers

1
votes

Maybe try this one:

TYPE Modified_transactions_T IS TABLE OF ROWID;
Modified_transactions       Modified_transactions_T;

BEFORE STATEMENT IS BEGIN
  Modified_transactions := Modified_transactions_T();
END BEFORE STATEMENT;

BEFORE EACH ROW IS BEGIN
  Modified_transactions.extend;
  Modified_transactions(Modified_transactions.last) := :NEW.ROWID;
END BEFORE EACH ROW;

or this

TYPE PrimaryKeyRecType IS RECORD (
   Col1 Transactions.PK_COL_1%TYPE, Col2 Transactions.PK_COL_2%TYPE);
TYPE Modified_transactions_T IS TABLE OF PrimaryKeyRecType;

...

Modified_transactions(Modified_transactions.last) := PrimaryKeyRecType(:NEW.PK_COL_1, :NEW.PK_COL_2);
0
votes

Your immediate problem is that :new is not a real record so it is not of type Transactions%ROWTYPE. If you're really going to go down this path, you would generally want to declare a collection of the primary key of the table

TYPE Modified_transactions_T IS TABLE OF Transactions.Primary_Key%TYPE;

and then put just the primary key in the collection

BEFORE EACH ROW IS BEGIN
    Modified_transactions.extend;
    Modified_transactions(Modified_transactions.last) := :NEW.Primary_Key;
  END BEFORE EACH ROW;

The fact that you are trying to work around a mutating table exception in the first place, however, almost always indicates that you have an underlying data modeling problem that you should really be solving. If you need to query other rows in the table in order to figure out what you want to do with the new rows, that's a pretty good indication that you have improperly normalized your data model and that one row has some dependency on another row in the same table rather than being an autonomous fact. Fixing the data model is almost always preferrable to working around the mutating table exception.