I have a Product table with 4 columns. 2 columns are price. If the ListPrice column is updated to below a specified amount (StandardCost * 1.2) then the update should fail and the old ListPrice should remain. I am attempting to use a SIGNAL SQLSTATE error to prevent the update from occurring if the criteria are met.
I've been combing Google and tried various variations in the syntax, but I keep hitting the following error while compiling my trigger - "PLS-00103 - Encountered the symbol 'SQLSTATE' when expecting one of the following: := , ( # %"
Any help is greatly appreciated.
CREATE OR REPLACE TRIGGER Product_Price_Check
BEFORE INSERT OR UPDATE OF ListPrice ON Product
FOR EACH ROW
DECLARE
min_price NUMBER(10, 2);
new_price NUMBER(10, 2);
BEGIN
min_price := (:OLD.StandardCost*1.2);
new_price := (:NEW.ListPrice);
IF (new_price < min_price) THEN
-- Rolls back an explicit or implicit transaction to the beginning of the transaction
dbms_output.put_line('the price can’t be below ' || TO_CHAR(min_price));
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Insert/update failed';
END IF;
END;
As mustaccio said, you're mixing MySQL syntax with an Oracle trigger. You want raise_application_error:
BEGIN
IF :NEW.ListPrice < (:OLD.StandardCost*1.2) THEN
raise_application_error(-20001,
'the price can’t be below ' || TO_CHAR(:OLD.StandardCost*1.2));
END IF;
END;
/
This won't roll back the transaction, just the update statement. The caller will receive the exception and decide how to handle it - whether to try again, roll back, or commit any other changes already made.
This assumes the old standard cost cannot be null. You might also want to specify a format model for the to_char().
Also don't rely on dbms_output for informing the caller about anything, as you won't know if the caller is looking at or doing anything with the buffer.
Related
I am new to PL/SQL and I have an issue regarding a trigger I am trying to implement.
The triggers purpose is to check a monetary value before it's inserted into the table to see if someone made a mistake during inserting. If they have, they will be given a message stating the value is incorrect. The values are in the billions so for now I am just checking if the value entered is above 10000 or not.
The trigger I currently have is;
CREATE TRIGGER Check_Value
BEFORE INSERT OR UPDATE OF "Potential Annual Value By 2026" ON AIPOTENTIALVALUEFORHEALTHCARE
BEGIN
IF (NEW."Potential Annual Value By 2026" < 10000.00) THEN
DBMS_OUTPUT.put_line('Value typed was incorrect');
ELSIF (NEW."Potential Annual Value By 2026" >= 10000.00) THEN
INSERT INTO AIPOTENTIALVALUEFORHEALTHCARE VALUES(NEW.ValueID, NEW.ApplicationID, NEW."Application Name", NEW.KeyDriverForAdoptionID, NEW."KeyDriverDescription", NEW."Potential Annual Value By 2026");
END IF;
END;
This will not work due to an error:
PLS-00201: identifier NEW.'Potential Annual Value By 2026' must be declared
My guess is that I have set the trigger incorrectly and that it doesn't know which value to check when it runs the trigger. From some research, I tried to use .NEW to pass the values of the statement into the trigger however I am not sure if this is the correct implementation.
I had tried the method already posted;
CREATE TRIGGER Check_Value
BEFORE INSERT OR UPDATE OF "Potential Annual Value By 2026" ON AIPOTENTIALVALUEFORHEALTHCARE
BEGIN
IF (:NEW."Potential Annual Value By 2026" < 10000.00) THEN
DBMS_OUTPUT.put_line('Value typed was incorrect');
ELSIF (:NEW."Potential Annual Value By 2026" >= 10000.00) THEN
INSERT INTO AIPOTENTIALVALUEFORHEALTHCARE VALUES
(:NEW.ValueID, :NEW.ApplicationID, :NEW."Application Name",
:NEW.KeyDriverForAdoptionID, :NEW."KeyDriverDescription",
:NEW."Potential Annual Value By 2026");
END IF;
END;
and recieved a different error:
ORA-04082: NEW or OLD references not allowed in table level triggers
04082. 00000 - "NEW or OLD references not allowed in table level triggers"
*Cause: The trigger is accessing "new" or "old" values in a table trigger.
*Action: Remove any new or old references.
If this error is stating I can't use NEW references in a table level trigger, how would I be able to verify the contents of the insert statement before it is committed?
You are missing colons before the NEW keywords and the FOR EACH ROW clause. Also you do not need to (and must not) re-issue the INSERT within the trigger, it will happen anyway (if no error is raised):
CREATE TRIGGER Check_Value
BEFORE INSERT OR UPDATE OF "Potential Annual Value By 2026" ON AIPOTENTIALVALUEFORHEALTHCARE
FOR EACH ROW
BEGIN
IF (:NEW."Potential Annual Value By 2026" < 10000.00) THEN
RAISE_APPLICATION_ERROR(-20001, 'Value typed was incorrect');
END IF;
END;
I'm sure this is just a training example, but DBMS_OUTPUT.PUT_LINE is not a suitable method for raising errors to users as its output can only be seen when using develpper tools like SQL Developer. Use RAISE_APPLICATION_ERROR. Also it doesn't actually raise an exception, so it won't prevent the insert at all.
In fact, this check might be better done with a CHECK constraint - assuming the column value must never be under 10000:
ALTER TABLE AIPOTENTIALVALUEFORHEALTHCARE
ADD CONSTRAINT AIPOTENTIALVALUEFORHEALTHCARE_CHK_VALUE
CHECK ("Potential Annual Value By 2026" >= 10000);
I am trying to implement a statement level trigger to enforce the following "An applicant cannot apply for more than two positions in one day".
I am able to enforce it using a row level trigger (as shown below) but I have no clue how to do so using a statement level trigger when I can't use :NEW or :OLD.
I know there are alternatives to using a trigger but I am revising for my exam that would have a similar question so I would appreciate any help.
CREATE TABLE APPLIES(
anumber NUMBER(6) NOT NULL, /* applicant number */
pnumber NUMBER(8) NOT NULL, /* position number */
appDate DATE NOT NULL, /* application date*/
CONSTRAINT APPLIES_pkey PRIMARY KEY(anumber, pnumber)
);
CREATE OR REPLACE TRIGGER app_trigger
BEFORE INSERT ON APPLIES
FOR EACH ROW
DECLARE
counter NUMBER;
BEGIN
SELECT COUNT(*) INTO counter
FROM APPLIES
WHERE anumber = :NEW.anumber
AND to_char(appDate, 'DD-MON-YYYY') = to_char(:NEW.appDate, 'DD-MON-YYYY');
IF counter = 2 THEN
RAISE_APPLICATION_ERROR(-20001, 'error msg');
END IF;
END;
You're correct that you don't have :OLD and :NEW values - so you need to check the entire table to see if the condition (let's not call it a "constraint", as that term has specific meaning in the sense of a relational database) has been violated:
CREATE OR REPLACE TRIGGER APPLIES_AIU
AFTER INSERT OR UPDATE ON APPLIES
BEGIN
FOR aRow IN (SELECT ANUMBER,
TRUNC(APPDATE) AS APPDATE,
COUNT(*) AS APPLICATION_COUNT
FROM APPLIES
GROUP BY ANUMBER, TRUNC(APPDATE)
HAVING COUNT(*) > 2)
LOOP
-- If we get to here it means we have at least one user who has applied
-- for more than two jobs in a single day.
RAISE_APPLICATION_ERROR(-20002, 'Applicant ' || aRow.ANUMBER ||
' applied for ' || aRow.APPLICATION_COUNT ||
' jobs on ' ||
TO_CHAR(aRow.APPDATE, 'DD-MON-YYYY'));
END LOOP;
END APPLIES_AIU;
It's a good idea to add an index to support this query so it will run efficiently:
CREATE INDEX APPLIES_BIU_INDEX
ON APPLIES(ANUMBER, TRUNC(APPDATE));
dbfiddle here
Best of luck.
Your rule involves more than one row at the same time. So you cannot use a FOR ROW LEVEL trigger: querying on APPLIES as you propose would hurl ORA-04091: table is mutating exception.
So, AFTER statement it is.
CREATE OR REPLACE TRIGGER app_trigger
AFTER INSERT OR UPDATE ON APPLIES
DECLARE
cursor c_cnt is
SELECT 1 INTO counter
FROM APPLIES
group by anumber, trunc(appDate) having count(*) > 2;
dummy number;
BEGIN
open c_cnt;
fetch c_cnt in dummy;
if c_cnt%found then
close c_cnt;
RAISE_APPLICATION_ERROR(-20001, 'error msg');
end if;
close c_cnt;
END;
Obviously, querying the whole table will be inefficient at scale. (One of the reasons why triggers are not recommended for this sort of thing). So this is a situation in which we might want to use a compound trigger (assuming we're on 11g or later).
I have a table call OUTGOING which has many fields but the ones to be populated in this situation is:
FILENUMBER
OUTGOINGDATE
DEPARTMENT
now i have a report which whas an sql
SELECT APEX_ITEM.CHECKBOX2(1,registry.filenumber) "Select",
INCOMINGREQUESTNOTIFICATION.REQUESTEDFILE as REQUESTEDFILE,
INCOMINGREQUESTNOTIFICATION.FILENUMBER as FILENUMBER,
INCOMINGREQUESTNOTIFICATION.REQUESTEDDEPARTMENT as REQUESTEDDEPARTMENT,
INCOMINGREQUESTNOTIFICATION.REQUESTDATE as REQUESTDATE,
REGISTRY.STATUS as STATUS
from REGISTRY REGISTRY,
INCOMINGREQUESTNOTIFICATION INCOMINGREQUESTNOTIFICATION
where REGISTRY.FILENUMBER(+) =INCOMINGREQUESTNOTIFICATION .FILENUMBER
and INCOMINGREQUESTNOTIFICATION.STATUS ='PENDING'
which is fine .. what i need is for
INCOMINGREQUESTNOTIFICATION.FILENUMBER as FILENUMBER
INCOMINGREQUESTNOTIFICATION.REQUESTEDDEPARTMENT as REQUESTEDDEPARTMENT
and sysdate
to be inserted in the outgoing table under the relevant names of course.
I have a pl/sql
DECLARE
L_FILENUMBER WWV_FLOW_GLOBAL.VC_ARR2;
BEGIN
L_FILENUMBER := APEX_APPLICATION.G_F01;
FOR IDX IN 1 .. L_FILENUMBER.COUNT
LOOP
IF L_FILENUMBER(IDX) IS NOT NULL THEN
INSERT INTO OUTGOING
(FILENUMBER,OUTGOINGDATE,DEPARTMENT)
VALUES
((to_number(APEX_APPLICATION.G_F01(1)))
,SYSDATE
,to_char(APEX_APPLICATION.G_F02(2)) )
;
END IF;
END LOOP;
END;
which is not working.. However if i leave only filenumber
DECLARE
L_FILENUMBER WWV_FLOW_GLOBAL.VC_ARR2;
BEGIN
L_FILENUMBER := APEX_APPLICATION.G_F01;
FOR IDX IN 1 .. L_FILENUMBER.COUNT
LOOP
IF L_FILENUMBER(IDX) IS NOT NULL THEN
INSERT INTO OUTGOING
(FILENUMBER)
VALUES
((to_number(APEX_APPLICATION.G_F01(1)))
;
END IF;
END LOOP;
END;
its inserting only the file number fine . This is all being done via a submit button.
NB: i also tried putting outgoing date and department in the declare statement but it still doesnt work
I'd suggest you to learn how to use table aliases. SELECT you wrote is difficult to read due to VERY long table & column names; alias would certainly help.
As of your question: saying that "it is not working" doesn't help at all. What exactly doesn't work? Is there any error? If so, which one? Did you run the page in debug mode and check what's going on? If not, do that.
Apart from that, code you wrote doesn't make much sense - you're trying to insert the same values all over again. E.g. shouldn't APEX_APPLICATION.G_F01(1) be APEX_APPLICATION.G_F01(IDX)? Something like this:
begin
for idx in 1 .. apex_application.g_f01.count
loop
if apex_application.g_f01(idx) is not null then
insert into outgoing
(filenumber,
outgoingdate,
department
)
values
(apex_application.g_f01(idx),
sysdate,
apex_application.g_f02(idx)
);
end if;
end loop;
end;
Check (by inspecting the page) whether (tabular form?) items really are those that you've used (G_F01 and G_F02). If not, you'll have to fix that.
I didn't use any TO_NUMBER nor TO_CHAR functions; I don't know whether your really need them. Even if you don't have them, Oracle will perform implicit datatype conversion when possible, but it'll fail if you try to put e.g. 'abc123' into a NUMBER datatype column. You didn't share that information so - I left those out. Apply them if necessary.
I am new to things related to Database. I have a homework from my school. It requires me to create a trigger for when there is an update for employee salary. The question is:
"Suppose STA has a rule stating that an employee’s salary cannot be changed by more than 20% of the original salary. Create a trigger salary_change to enforce this constraint. The trigger fires whenever there is an update on salaryin employeetableand outputs a suitable error message when the rule is violated. "
The structure of the table is available here
Below is the code that I have made but created with compilation errors.
create or replace trigger salary_change
before update of emp_salary on employee
for each row
begin
if :new.emp_salary > :emp_salary * 1.2 then
raise_application_error(-20000, ('New salary for employee ' || emp_name || ' is getting more than 20% raise'));
end if;
end;
/
You can (and should) use :old to refer to the record before the update:
if :new.emp_salary > :old.emp_salary * 1.2 then
-- Here ---------^
create or replace trigger enroll_bef_ins_row
before insert on enrollments
for each row
declare
original number;
seatsremain_already_full exception;
begin
Select seatsremain into original from offering where offerno= :new.offerno;
if original > 0 then
update offering set seatsremain= seatsremain - 1;
dmbs_output.put_line ('Seats available in offering' |offerno| 'have decreased from' |:old.seatsremain| to |:new.seatsremain|);
else if original = 0 then
dbms_output.put_line ('Offering' |offerno| 'is already full!');
raise seatsremain_already_full
end if;
exception
when seatsremain_already_full
raise_application_error (-20001, 'Cannot allow insertion');
end
/
I keep getting a "trigger created with compilation error" message and every time I try to insert in values I get a ORA 04098 - SYSTEM.ENROLL_BEFORE_INS_ROW IS INVALID AND FAILED REVALIDATION message.
My task is to write a trigger that performs the following tasks before a row has been inserted into the enrollments table:
If the seats are available for the particular offering, the trigger should automatically decrease the number of seats for the offering and display the message: seats availabe in offering (offering number) have decreased from (number of seats available prior to insertion) to (number of seats available after insertion). If the number of seats available after the insertion is equal to 0, display message: 'No more seats in offering (offering number should be entered here)
If the seats available for a particular offering were equal to 0 prior the insertion of the row in enrollments, the trigger should display the following:
insertion not allowed
and use the raise_application_error procedure to prevent the execution of the INSERT statement
The concatenation operator is ||, and all statements must end with a semi-colon. There was also the odd apostrophe missing here and there.
Try this:
create or replace trigger enroll_bef_ins_row
before insert on enrollments
for each row
declare
original number;
seatsremain_already_full exception;
begin
Select seatsremain
into original
from offering
where offerno = :new.offerno;
if original > 0 then
update offering
set seatsremain = seatsremain - 1
WHERE OFFERNO = :new.OFFERNO;
dmbs_output.put_line ('Seats available in offering' || :new.offerno ||
'have decreased from' || original ||
' to ' || original-1);
elsif original = 0 then
dbms_output.put_line ('Offering' || offerno ||
' is already full!');
raise seatsremain_already_full;
end if;
exception
when seatsremain_already_full
raise_application_error (-20001, 'Cannot allow insertion');
end;
I suggest that you might want to NOT decrement SEATSREMAIN but instead compute the number of seats remaining by taking OFFERING.SEATSAVAILABLE and substracting the sum of the seats taken - otherwise there's a race condition in there on OFFERING.SEATSREMAIN and you can end up overbooking - but this will probably work for simple cases. In addition, you might want to raise an error if SEATSREMAIN ends up negative.
Share and enjoy.