Oracle SQL: Simple Trigger Problem affecting change of values in if clause - sql

I want to create a trigger in a table (called seat) with two attributes, seat number and seat class. If the seat is higher than let's say 50, the class should be 'high', otherwise it should be 'low'. I want the trigger to automatically give the class when i enter a number!
See what I've got till now, tried some alternates as well ..
CREATE TRIGGER trigger_class
AFTER INSERT OR UPDATE ON seat
FOR EACH ROW
BEGIN
IF :NEW.seat_no <=50 THEN :NEW.class_code ='high';
ELSE :NEW.class_code = 'low';
END IF;
END; /
I'm very new into database coding, so... any help would be great!

Change "=" to ":=", put the "/" on a new line, change "AFTER" to "BEFORE", and change "<= 50" to "> 50":
CREATE OR REPLACE TRIGGER trigger_class
BEFORE INSERT OR UPDATE ON seat
FOR EACH ROW
BEGIN
IF :NEW.seat_no > 50 THEN :NEW.class_code :='high';
ELSE :NEW.class_code := 'low';
END IF;
END;
/

Apart from the syntax problems that jonearles already described:
Unless you are just playing around with triggers (or this is some kind of homework), this is not a very good design.
One rule in relational databases is that you should not store information that can be derived from existing data.
You can easily select the class_code during a select, there is no need to store it:
SELECT seat_no,
CASE
WHEN seat_no > 50 THEN 'high'
ELSE 'low'
END as class_code
FROM seat;
With Oracle 11 you can even define a virtual column that will do the "computation" automatically during retrieval, otherwise you could define a view that will return that information.

Related

PL/SQL - Simple trigger, take from one column and IF-THEN-ELSE to another column

I am trying to figure out where I am going wrong on this simple trigger. I am quite new to triggers and trying to get used to using them with IFTTT statements.
I want the trigger to watch for a new row entry, and if the value is within a certain range within a column (col_a) it will then enter a certain value in the same row, but different column (col_b), which will be NULL up until this is entered. Please can you help?
CREATE TRIGGER trg_test
BEFORE INSERT
ON test_table
FOR EACH ROW
BEGIN
IF :new.col_a >= 10
THEN :new.col_b := 'High';
ELSE
:new.col_b := 'Low';
END IF;
END;
It just keeps coming back with "success with compilation error".
You can easily do this using a virtual column:
alter table test_table
add col_b generated always as (case when col_a >= 10 then 'High' else 'Low' end);
I wish that exercises in triggers used reasonable use-cases. This is not one -- you need both an insert and an update trigger, for instance.

Compound trigger: collecting mutating rows into nested table

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
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);
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.

Writing an SQL trigger to find if number appears in column more than X times?

I want to write a Postgres SQL trigger that will basically find if a number appears in a column 5 or more times. If it appears a 5th time, I want to throw an exception. Here is how the table looks:
create table tab(
first integer not null constraint pk_part_id primary key,
second integer constraint fk_super_part_id references bom,
price integer);
insert into tab values(1,NULL,100), (2,1,50), (3,1,30), (4,2,20), (5,2,10), (6,3,20);
Above are the original inserts into the table. My trigger will occur upon inserting more values into the table.
Basically if a number appears in the 'second' column more than 4 times after inserting into the table, I want to raise an exception. Here is my attempt at writing the trigger:
create function check() return trigger as '
begin
if(select first, second, price
from tab
where second in (
select second from tab
group by second
having count(second) > 4)
) then
raise exception ''Error, there are more than 5 parts.'';
end if;
return null;
end
'language plpgsql;
create trigger check
after insert or update on tab
for each row execute procedure check();
Could anyone help me out? If so that would be great! Thanks!
CREATE FUNCTION trg_upbef()
RETURN trigger as
$func$
BEGIN
IF (SELECT count(*)
FROM tab
WHERE second = NEW.second ) > 3 THEN
RAISE EXCEPTION 'Error: there are more than 5 parts.';
END IF;
RETURN NEW; -- must be NEW for BEFORE trigger
END
$func$ LANGUAGE plpgsql;
CREATE TRIGGER upbef
BEFORE INSERT OR UPDATE ON tab
FOR EACH ROW EXECUTE procedure trg_upbef();
Major points
Keyword is RETURNS, not RETURN.
Use the special variable NEW to refer to the newly inserted / updated row.
Use a BEFORE trigger. Better skip early in case of an exception.
Don't count everything for your test, just what you need. Much faster.
Use dollar-quoting. Makes your live easier.
Concurrency:
If you want to be absolutely sure, you'll have to take an exclusive lock on the table before counting. Else, concurrent inserts / updates might outfox each other under heavy concurrent load. While this is rather unlikely, it's possible.

PL/SQL Triggers Library Infotainment System

I am trying to make a Library Infotainment System using PL/SQL. Before any of you speculate, yes it is a homework assignment but I've tried hard and asking a question here only after trying hard enough.
Basically, I have few tables, two of which are:
Issue(Bookid, borrowerid, issuedate, returndate) and
Borrower(borrowerid, name, status).
The status in Borrower table can be either 'student' or 'faculty'. I have to implement a restriction using trigger, that per student, I can issue only 2 books at any point of time and per faculty, 3 books at any point of time.
I am totally new to PL/SQL. It might be easy, and I have an idea of how to do it. This is the best I could do. Please help me in finding design/compiler errors.
CREATE OR REPLACE TRIGGER trg_maxbooks
AFTER INSERT ON ISSUE
FOR EACH ROW
DECLARE
BORROWERCOUNT INTEGER;
SORF VARCHAR2(20);
BEGIN
SELECT COUNT(*) INTO BORROWERCOUNT
FROM ISSUE
WHERE BORROWER_ID = :NEW.BORROWER_ID;
SELECT STATUS INTO SORF
FROM BORROWER
WHERE BORROWER_ID = :NEW.BORROWER_ID;
IF ((BORROWERCOUNT=2 AND SORF='STUDENT')
OR (BORROWERCOUNT=3 AND SORF='FACULTY')) THEN
ROLLBACK TRANSACTION;
END IF;
END;
Try something like this:
CREATE OR REPLACE TRIGGER TRG_MAXBOOKS
BEFORE INSERT
ON ISSUE
FOR EACH ROW
BEGIN
IF ( :NEW.BORROWERCOUNT > 2
AND :NEW.SORF = 'STUDENT' )
OR ( :NEW.BORROWERCOUNT > 3
AND :NEW.SORF = 'FACULTY' )
THEN
RAISE_APPLICATION_ERROR (
-20001,
'Cannot issue beyond the limit, retry as per the limit' );
END IF;
END;
/
There should not be a commit or rollback inside a trigger. The logical exception is equivalent to ROLLBACK
This is so ugly I can't believe you're being asked to do something like this. Triggers are one of the worst ways to implement business logic. They will often fail utterly when confronted with more than one user. They are also hard to debug because they have hard-to-anticipate side effects.
In your example for instance what happens if two people insert at the same time? (hint: they won't see the each other's modification until they both commit, nice way to generate corrupt data :)
Furthermore, as you are probably aware, you can't reference other rows of a table inside a row level trigger (this will raise a mutating error).
That being said, in your case you could use an extra column in Borrower to record the number of books being borrowed. You'll have to make sure that the trigger correctly updates this value. This will also take care of the multi-user problem since as you know only one session can update a single row at the same time. So only one person could update a borrower's count at the same time.
This should help you with the insert trigger (you'll also need a delete trigger and to be on the safe side an update trigger in case someone updates Issue.borrowerid):
CREATE OR REPLACE TRIGGER issue_borrower_trg
AFTER INSERT ON issue
FOR EACH ROW
DECLARE
l_total_borrowed NUMBER;
l_status borrower.status%type;
BEGIN
SELECT nvl(total_borrowed, 0) + 1, status
INTO l_total_borrowed, l_status
FROM borrower
WHERE borrower_id = :new.borrower_id
FOR UPDATE;
-- business rule
IF l_status = 'student' and l_total_borrowed >= 3
/* OR faculty */ THEN
raise_application_error(-20001, 'limit reached!');
END IF;
UPDATE borrower
SET total_borrowed = l_total_borrowed
WHERE borrower_id = :new.borrower_id;
END;
Update: the above approach won't even work in your case because you record the issue date/return date in the issue table so the number of books borrowed is not a constant over time. In that case I would go with a table-level POST-DML trigger. After each DML verify that every row in the table validates your business rules (it won't scale nicely though, for a solution that scales, see this post by Tom Kyte).

Oracle 11g Trigger that sets a flag when number below x

I need to create a TRIGGER that sets a flag when a product’s quantity on hand falls below 5.
I've created a table "STP_STOCK" in Oracle 11g and it has the following column:
STP_QUANTITY, NUMBER(4,0), RANGE 0-9999
How do I create a trigger that sets a flag in SQL?
I've got as far as the following... but have no idea re setting a flag.
CREATE OR REPLACE TRIGGER STP_STOCK
AFTER UPDATE
ON orders
FOR STP_QUANTITY
BEGIN
END;
Assuming the flag is in the same table, I would suggest you use a before update trigger. Then you can do something like:
CREATE OR REPLACE TRIGGER STP_STOCK
before UPDATE
ON orders
FOR each row
BEGIN
if :new.stp_quantity < 5
then :new.flag := 'Y';
end if;
END;
(I don't have Oracle on hand to test the syntax, so the syntax might have an error.)
You want to do a before trigger, so you can just set the value in :new. It is bad practice to update the same table in an after trigger.
Presumably, you also want to check if the quantity is larger than 5 and set the flag to 'N', but that is not specified in the question.
Finally, instead of a trigger and a flag, you probably really want to do this with a view or virtual column. Something like:
select o.*, (case when stp_quantity < 5 then 'Y' else 'N' end) as Flag
from orders o
(Note: I'm using * as a convenience. In practice, you would want to list each column in the view.)