ORA-00984 on an after update pl/sql trigger - plsql

I have been fighting with this trigger for a while now, im trying to send to the table "MOVIMIENTO" the old stock, the new one, the difference between them and what kind of operation it is, only problem it's that i get an ORA-00984 error on line 12 and a sql statement ignored on line 11, any ideas?
CREATE OR REPLACE TRIGGER TRG_MOVIMIENTO
AFTER UPDATE OF "STOCK" ON PRODUCTO
FOR EACH ROW
DECLARE
v_tmov VARCHAR2(7);
v_cant NUMBER(6);
BEGIN
v_cant:=:OLD.STOCK-:NEW.STOCK;
IF :OLD.STOCK>:NEW.STOCK THEN
v_tmov:='SALIDA';
ELSE
v_tmov:='ENTRADA';
END IF;
INSERT INTO MOVIMIENTO(N_MOV,TIPO_MOV,STOCK_NEW,STOCK_OLD,CANT_MOV)
VALUES(s_NUMEROMOV.nextval,v_tmod,:NEW.STOCK,:OLD.STOCK,v_cant);
END;
/

Is v_tmod a column on the MOVIMIENTO table. Because there is no variable with that name. That would explain the error.
Edit: OK, looking at the comments, I think I know what is going on.
The variables in the declare section are v_tmov and v_cant.
But in your INSERT statement you have made a typo and used v_tmod.
Oracle sees this is not a known variable so assumes it must be a column name. So it then reports the ORA-00984 error, without even checking to see if its a valid column name. This is because you cannot use a column here.
So the real error is v_tmod is not a defined variable

Related

How To Create a PL/SQL Trigger That Detects an Inserted or Updated Row and updates a Record in a Different Table?

I am creating a book tracking database for myself that holds information about my books and allows me to keep track of who is borrowing them. I am trying to create a trigger on my Checkouts table that runs if a record is added or updated that will determine if a checkout data has been entered or if a checkin date has been entered and change the "available" field in my Books table to "Y" or "N".
I have created a trigger called "update_book_availablility" on my Checkouts table but I keep getting this error:
"PLS-00103: Encountered the symbol 'end-of-file' when expecting one of the following: ( begin case declare and exception exit for goto if loop mod null pragma raise return select update while with <<continue close current delete fetch lock insert open rollback savepoint set sql execute commit forall merge standard pipe purge json_object
Errors: check compiler log"
Here is my trigger code:
CREATE OR REPLACE NONEDITIONABLE TRIGGER "UPDATE_BOOK_AVAILABILITY"
AFTER INSERT OR UPDATE OF ISBN, PersonID, checkout_date, checkin_date
ON Checkouts
FOR EACH ROW
BEGIN
IF :NEW.checkout_date = NULL
THEN
UPDATE Book
SET available = 'N'
WHERE ISBN IN (SELECT :NEW.ISBN FROM Checkouts);
END IF;
END;
Here is an image of my ERD:
ERD
I have been looking into and double checking my trigger syntax, If condition syntax, subquery syntax, and googling this error but have found nothing that has helped. I am new to PL/SQL and would appreciate any help in understanding what I have done wrong or missed.
PLS-00103: Encountered the symbol end-of-file error is SYNTAX ERROR
Copied your trigger and adjusted it to one of my test tables - it works. I removed NONEDITIONABLE and changed trigger table name as well as column names and table/column beeing updated by trigger.
To Do:
Check your syntax again or write the trigger from scratch once more
"...WHERE ISBN IN (SELECT :NEW.ISBN FROM Checkouts)..." selects one fixed value (FOR EACH ROW) :NEW.ISBN of triggering table, better ->> "... WHERE ISBN = :NEW.ISBN ..."
Prety sure that you don't need NONEDITIONABLE trigger for your books tracking app...
Regards...

age control 2 for pl/sql

i am trying to find out at what age an employee started working.
if he started under 16 he should report this 'Error when entering the date of birth' mistake. so my trigger is created but my trigger is not working
I get ever this error: ORA-01422: Exact retrieval returns more than the requested number of lines
i can't find the problem
Here is the code:
SET SERVEROUTPUT ON
ACCEPT Birthday PROMPT ' Pleas give you Date of birth: '
CREATE OR REPLACE TRIGGER T_Controll
before INSERT ON meine_Firma -- Table
FOR EACH ROW
DECLARE
V_Berufstart meine_Firma.Hiredate%TYPE; --Job begin
V_Geburtsdatum DATE; -- Date of birth
V_Alter number:=0; -- AGE
SELECT HIREDATE INTO V_Berufstart FROM meine_Firma;
BEGIN
V_Geburtsdatum:=('&Birthday');
V_Alter:= Round(MONTHS_BETWEEN(V_Berufstart,V_Geburtsdatum)-2)/12;
IF 16 > V_Alter THEN
RAISE_APPLICATION_ERROR(-20201,'Error when entering the date of birth');
END IF;
END;
/
SET SERVEROUTPUT OFF
If he under 16 then he may not work
sorry my english is not good (=
You have a much bigger issue in this script than the error you are getting. Even after correcting as #ShaunPeterson suggested it will still fail
, it WILL NOT generate an error it will just not run as you expect. The issue is you failed to understand substitution variables - the use of &name (Specifically here &Birthday.) I'll actually use &Birthday in the following but the discussion applies to ANY/ALL substitution variables.
people fail to understand why they can't use the "&" substitution
variables in their PL/SQL procedures and functions to prompt for input
at run time. This article will hopefully help clarify in your mind
what the differences are so that you can understand where and when to
use these.
Substitution Variables The clue here is in the name... "substitution". It relates to values being substituted into the code
before it is submitted to the database. These substitutions are
carried out by the interface being used
The effect of this substitution is the the line containing the substitution variable is physically rewritten by the interface replacing %Birthday. In this case if you don't enter a value or the date 2000-05-19 the statement before and after substitution is
BEFORE: V_Geburtsdatum:=('&Birthday');
AFTER: V_Geburtsdatum:=(''); OR V_Geburtsdatum:=('2000-05-19');
Either way the after is what the compiler sees; it does NOT see %Birthday at all. Moreover when run the trigger will not prompt for a value. As far as the compiler is concerned it is a hard coded value that will never change. Beyond that a trigger, or any other PLSQL script (stored or anonymous) never prompts for values, they are actually incapable of doing so as it is not part of the language. Any prompt is via your interface software not plsql.
I'm going to suggest a way to avoid the trigger altogether. Getting on soap box: Triggers are BAD, they have some usefulness for assigning auto incrementing keys (before 12c),logging, very limited auditing, etc. However, for business rules they should be the option of last resort. Ok Get off soap box.
The first thing is to make the columns meine_Firma.Hiredate and meine_Firma.Geburtsdatum NOT null (if not already). If either are NULL you cannot calculate anything with them, the result would be NULL.
Second create a new column age_at_hire (or whatever) as a virtual column then put a check constraint on it. And voila trigger no longer needed. See fiddle for demo.
So the proposed change (YES you will probably have to clean up the bad data first):
alter table meine_Firma modify
( hiredate not null
, Geburtsdatum not null
) ;
alter table meine_Firma add
( age_at_hire integer generated always as (trunc(months_between(hiredate,Geburtsdatum))) virtual
, constraint check_age_at_hire check (age_at_hire >= 16*12)
);
Anyway, I hope you get an understanding of substitution variables for the future. And learn to avoid triggers. Good luck.
The reason you are getting that specific error is that the below select will select ALL rows from meine_Firma as there is no where clause
SELECT HIREDATE INTO V_Berufstart FROM meine_Firma;
However because you are in a trigger you do not need to select anything you use the :NEW bind variable. So you can just use
V_Berufstart := :NEW.HIREDATE;
If this was an update trigger there would be both a :NEW and :OLD bind variable declared so that you can access the OLD and NEW values. As this is an Insert trigger the :OLD will just be null as there is no old values.

PL/SQL: Run package after checking all rows in trigger

I have an inventory table with expected quantities and actually received quantities. Let's say inv.q_ex and inv.q_rd.
The INSERT to the table has a positive value in q_ex and a zero in q_rd because it hasn't arrived yet. I'd like to run a package when I detect that the q_rd value changes from 0 to something else, indicating it's been received and stored.
Making a trigger to detect after update and checking each row is easy, but I'm not sure how to ensure it only runs once.
The skeleton is:
CREATE OR REPLACE TRIGGER example
AFTER UPDATE ON inv
FOR EACH ROW
BEGIN
IF :OLD.q_rd = 0 AND :NEW.q_rd > 0 THEN
pkg.proc();
END IF;
END;
/
The problem I see is I only want it to run one time. I just need to identify when it needs to be executed. Ideally, on the first row where my condition is met, I would exit the loop (seems like a waste to keep checking when I already know I need to execute) and call my procedure.
I couldn't find a way to "exit" the for each and treat it as a normal AFTER UPDATE, so then I tried using both BEFORE UPDATE and AFTER UPDATE. The BEFORE portion would check each row and update a boolean. The AFTER portion would wait for that to happen and if it was true, call the procedure.
CREATE OR REPLACE TRIGGER example
BEFORE UPDATE ON inv
FOR EACH ROW
DECLARE
shouldExecute BOOLEAN;
BEGIN
IF :OLD.q_rd = 0 AND :NEW.q_rd > 0 THEN
shouldExecute := TRUE;
END IF;
END;
AFTER UPDATE ON inv
BEGIN
IF shouldExecute THEN
pkg.proc();
END IF;
END;
/
I suspect this wouldn't work anyway because, according to the syntax, it redeclares the boolean variable on each row. I thought that maybe I could make it "global" but regardless, turns out I can't add both BEFORE and AFTER to the same trigger for some reason (unless I didn't research enough), so I broke it out into two triggers. The problem now is I can't share that boolean between the two triggers. Can I share the value, or am I going about this all wrong?
There's a lot of little questions here so I'll try to answer them all :)
Regarding "FOR EACH ROW", Oracle triggers support two different triggering methods, STATEMENT or ROW. If you include the "FOR EACH ROW" in the definition, you'll get a row trigger, which is fired once per row affected by the query, which is what you seem to want here. Statement level triggers get fired only once for each query. An advantage to using row triggers is that you can use the :OLD and :NEW metavariables which refer to the previous and current row values.
As you've discovered, you can't add BEFORE and AFTER to the same trigger - you'll need to break them out into two triggers.
Unfortunately there isn't a simple way of sharing the boolean variable between the two triggers. The easiest way is probably to create a package with a public variable, which you can set in the BEFORE trigger, and check in the AFTER trigger.
The package would look something like this:
CREATE OR REPLACE PACKAGE PCKG_DEMO
AS
shouldExecute BOOLEAN;
END;
/
Then your BEFORE UPDATE trigger:
CREATE OR REPLACE TRIGGER beforeexample
BEFORE UPDATE ON inv
FOR EACH ROW
BEGIN
IF :OLD.q_rd = 0 AND :NEW.q_rd > 0 THEN
PCKG_DEMO.shouldExecute := TRUE;
END IF;
END;
/
And your AFTER UPDATE trigger:
CREATE OR REPLACE TRIGGER afterexample
AFTER UPDATE ON inv
FOR EACH ROW
BEGIN
IF PCKG_DEMO.shouldExecute THEN
pkg.proc();
END IF;
END;
/
Those are a bit pseudocode-y - I don't have access to an Oracle database right now. You can read more about triggers here. Hope this helps!
You need to do more research. The link above takes you to trigger discussion for Oracle 9. I hope you are not actually using that version; support ended in 2007. Since version 11g Oracle has provided "Compound Triggers" where you can fire the same trigger both Before and After for both statement and row level. Compound triggers do allow sharing variables between the different invocations.

PL/SQL Oracle Stored Procedure (No Data Found)

CODE
-- Stored Procedure
CREATE OR REPLACE PROCEDURE DEVELOPMENT_PROCEDURE
(
P_JOB_TYPE IN JOBS.JOB_TITLE%type DEFAULT 'Manager'
, P_JOB_ID OUT JOBS.JOB_ID%type
, P_JOB_TITLE OUT JOBS.JOB_TITLE%type
) AS
BEGIN
SELECT JOB_ID, JOB_TITLE
INTO P_JOB_ID, P_JOB_TITLE
FROM JOBS
WHERE JOB_TITLE = P_JOB_TYPE;
END DEVELOPMENT_PROCEDURE;
CODE
-- Calling the Stored Procedure
DECLARE
P_JOB_ID JOBS.JOB_ID%type;
P_JOB_TITLE JOBS.JOB_TITLE%type;
P_JOB_TYPE VARCHAR2(25) := 'Manager';
BEGIN
DEVELOPMENT_PROCEDURE(P_JOB_TYPE,P_JOB_ID,P_JOB_TITLE);
SYS.DBMS_OUTPUT.PUT_LINE(P_JOB_ID);
SYS.DBMS_OUTPUT.PUT_LINE(P_JOB_TITLE);
END;
Problem
This is the error message I am receiving after running. I am following a tutorial to the letter, but still receiving errors; solved most ... stuck here.
ORA-01403: no data found
ORA-06512: at "HR.DEVELOPMENT_PROCEDURE", line 8
ORA-06512: at line 8
There is no exception setup, but I should be seeing 15 records. Any ideas?
Could this be an issue with declaration? I need the parameter to read Manager and not MANAGER. Could this be the issue? If so, how can I fix it so the variable read like Manager. Thank you.
As #Drumbeg had pointed out, I am actually pulling too many records, but even after fixing SQL to select single record I am still receiving same error. Could there be an issue with the database itself?
From the comments above, I think we can deduce there is a difference in case between P_JOB_TYPE and the JOB_TYPE data for your expected rows in the database, hence why applying lower() to both sides of the predicate starts to bring data back.
It's worth pointing out that applying a lower() to both sides of the predicate is not a good solution on it's own as there may be performance implications if this is not supported by a functional index. Probably better to be sure of the case you are storing in the database and just apply an upper() or lower() to the right hand side of the predicate (i.e. P_JOB_TYPE side).

trigger for updating a value

I am a newbie in PLSQL and I would like to create a trigger that checks first if there is a record in a table before making an update.
The code I got so far is:
CREATE OR REPLACE TRIGGER table_bu
BEFORE UPDATE ON employee
FOR EACH ROW
DECLARE
v_employee_id:=employee.employee_ID%TYPE;
BEGIN
SELECT employee_id INTO v_employee_id FROM employee;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RAISE_APPLICATION_ERROR (-20001,'data not found');
END;
How I can create a trigger that checks up if a record exists in the table and if it does not exists does not allow the update.
My table estructure is:
employee_id NUMBER
employee_name VARCHAR(20)
employee_salary NUMBER
...
Thanks
You are on a wrong way. The trigger as it is will throw runtime 'Mutating table' error even after fixing syntax error - you missed semicolon after raise_application_error(also it should take 2 arguments, not one). Correct syntax :
EXCEPTION
WHEN NO_DATA_FOUND THEN
RAISE_APPLICATION_ERROR (-20001, 'data not found'); -- 1st parameter -error code
Update
As far as I understand the updated version of the question, you want to show error if record doesn't exist. The problem with row level trigger approach is that it won't be executed if nothing is found due to condition in WHERE. The simplest way is to check number of rows affected on client side and raise an error there. Or you can write a procedure that checks sql%rowcount after executing desired update, and then throw an exception if it's 0.
If you prefer to do in a hard way, you can create package variable which of type employee.employee_ID%TYPE, before update statement level trigger that resets variable (say set it to null), after update row level trigger that sets this variable to NEW.employee_ID, and after update statement level trigger that throws an exception if the variable is null. Note: this will properly work for individual updates only.
"How I can create a trigger that checks up if a record exists in the table and if it does not exists does not allow the update."
There is really only one practical way to do this - use a referential constraint (foreign key).

Resources