Reduce the value of a vehicle every month - sql

What I am trying to do is to reduce the value of every vehicle in the 'Vehicle' table so that when the date reaches one month past the 'LastUpdate' value the 'Value' column is reduced by 2.5%. The trigger should run AFTER LOGON ON DATABASE. The problem is, a DBA may not logon to the database every day, so if it is has been six months since the last logon, the trigger will loop through, reduce the value by 2.5% and do ADD_MONTHS(LastUpdate, 1).
My code for the function that will calculate the value is:
REATE OR REPLACE FUNCTION fn_Vehicle_Value
(VehicleNumber IN NUMBER,
VehicleValue IN OUT NUMBER)
RETURN NUMERIC
IS
BEGIN
VehicleValue := VehicleValue - (VehicleValue * 0.025);
RETURN VehicleValue;
END;
/
This is my attempt at creating the system trigger:
CREATE OR REPLACE TRIGGER tg_VehicleDepreciate
AFTER LOGON ON DATABASE
IS
CURSOR vehicle_cur IS SELECT "VALUE", LastUpdate FROM Vehicle;
BEGIN
FOR vehicle_rec IN vehicle_cur LOOP
WHILE LastUpdate < SYSDATE LOOP
LastUpdate."Value" := fn_Vehicle_Update("VALUE");
UPDATE Vehicle
SET LastUpdate := ADD_MONTHS(LastUpdate, 1)
WHERE Vehicle# = vehicle_cur.Vehicle#;
END LOOP;
EXIT WHEN vehicle_cur%NOTFOUND;
END LOOP
END;
/
From what I can tell, my function is right. But the trigger does not compile and produces the following error report:
Error report -
ORA-04079: invalid trigger specification
04079. 00000 - "invalid trigger specification"
*Cause: The create TRIGGER statement is invalid.
*Action: Check the statement for correct syntax.
I am guessing it is a syntax error, but there may also be a logical error that I cannot work out.

SHOW ERRORS after compiling your trigger would reveal all errors.
Here are few..
1) in update, it is just SET somecolumn = Somevalue
2) oracle implicitly opens the cursor and closes to, when used with a FOR loop.
3) after using FOR loop,the fetched results should be used like.. Forloopvariable.column-name
4) added Vehicle# to your cursor.
5) added V_date logic.
CREATE OR REPLACE TRIGGER tg_VehicleDepreciate
AFTER LOGON ON DATABASE
IS
CURSOR vehicle_cur IS SELECT "VALUE", LastUpdate ,Vehicle# FROM Vehicle;
V_date DATE;
BEGIN
FOR vehicle_rec IN vehicle_cur LOOP
V_date := vehicle_rec.LastUpdate;
WHILE V_date < SYSDATE LOOP
/* what are you trying here? */
---vehicle_rec."Value" := fn_Vehicle_Update("VALUE");
UPDATE Vehicle
SET LastUpdate = ADD_MONTHS(LastUpdate, 1)
WHERE Vehicle# = vehicle_rec.Vehicle#;
V_date := ADD_MONTHS( V_date,1);
END LOOP;
END LOOP
END;
/

Related

Oracle - Trigger and Procedure compilation errors

I am trying to write a procedure to display a day of the week, but the error I get is that the "ORA-01841: The (full) year must be between -4713 and +9999 and cannot be 0".
The other problem is I made a trigger that checks if column in SCOTT.BONUS.SALARY has been updated and calculates "howmuch" - raise and returns it. It says that NEW.SAL should be declared, but how can it be declared if its a column name... ?
I think im pretty close but I am missing something, Can anyone help please? Much Appreciated.
-- trigger --
CREATE OR REPLACE TRIGGER Raise
BEFORE DELETE OR INSERT OR UPDATE ON SCOTT.BONUS
FOR EACH ROW
WHEN (NEW.SAL > 0.1*OLD.SAL)
DECLARE
howmuch number;
BEGIN
howmuch := 0.1*NEW.SAL;
dbms_output.put_line('Bonus changed to 10% - : ' || howmuch);
END;
/
-- Procedure --
CREATE OR REPLACE PROCEDURE Birth_Day(data IN varchar, Dey OUT varchar) IS
BEGIN
select to_char(date'data', 'Day') INTO Dey from dual;
END;
/
-- Starting procedure Birth_Day --
DECLARE
Dey varchar(20);
begin
Birth_Day('10/11/2020',Dey);
end;
This expression is not right:
to_char(date'data', 'Day')
The database tries to evaluate literal string 'data' as a date in ISO format ('YYYY-MM-DD'), and fails.
You need to use to_date() first to turn your variable string to a date, and then to_char():
to_char(to_date(data, 'DD/MM/YYYY'), 'Day')

Trying to Create a Trigger to let the user insert only 5 times in a day

create or replace trigger trg_check_placement
before insert
on lds_placement
for each row
declare
plt_count number;
begin
select count(*) into plt_count from lds_placement where TO_CHAR(sysdate,'HH24:MI:SS');
if plt_count >5 then
raise_application_error(-20000, 'Sorry! you cannot create more than five placement a day');
end if;
end;
Keep getting the error invalid relational operator at line 4.Is this the correct way of solving this or am i completely wrong ?

PL SQL trigger using raise_application_error thows error.

I have a few things of code I need help debugging but I feel that if I can get one of them running i'll be able to get the rest(oh how i hope).
create or replace
trigger minimumwage
before insert or update on Employee
for each row
begin
if :new.Wage < 7.25
then raise_application_error('-20000,Pay is below Texas minimum wage!');
end if;
end;
/
I'm trying to do this on a table ran on my school's server through sqlplus if that helps.
When you're getting an error, it's always helpful to specify what error. There is a syntax error in the raise_application_error call in your trigger. That procedure takes two arguments, a number and a string. You are passing in a single argument that is one long string.
create or replace trigger minimumwage
before insert or update on Employee
for each row
begin
if :new.Wage < 7.25
then
raise_application_error(-20000,'Pay is below Texas minimum wage!');
end if;
end;
should be valid assuming there is a WAGE column in your EMPLOYEE table.
create or replace trigger deny_dec_pu before update of PU on ARTICLE
for each row
declare
erreur_pu exception;
begin
*insert into erreur values ('Operation de MAJ',sysdate);*
-- this intruction is never executec why ?
if (:new.pu < :old.pu) then
raise erreur_pu ;
end if;
exception
when erreur_pu then
Raise_application_error(-20100, 'rrrrrrrr', FALSE);
end;
/

Why is the exception NO_DATA_FOUND not being triggered?

So the problem i am having is that if i execute the following procedure and the cursor doesnt find the parameter being passed, it continues to execute the block (insert statement) but instead of throwing the NO_DATA_FOUND exception error it throws a parent/foreign key error.
CREATE OR REPLACE PACKAGE ASSIGNMENT3 IS
PROCEDURE END_CAMPAIGN(CTITLE IN CAMPAIGN.CAMPAIGNTITLE%TYPE);
END ASSIGNMENT3;
/
CREATE OR REPLACE PACKAGE BODY ASSIGNMENT3 AS
PROCEDURE END_CAMPAIGN(CTITLE IN CAMPAIGN.CAMPAIGNTITLE%TYPE) IS
CURSOR ADCOST_CUR IS
SELECT ACTUALCOST
FROM ADVERTISEMENT
WHERE ADVERTISEMENT.CAMPAIGNTITLE = CTITLE;
V_TOTALCOST NUMBER;
BEGIN
V_TOTALCOST := 0;
FOR INVOICE_REC IN ADCOST_CUR
LOOP
V_TOTALCOST := V_TOTALCOST + INVOICE_REC.ACTUALCOST;
END LOOP;
INSERT INTO INVOICE(INVOICENO, CAMPAIGNTITLE, DATEISSUED, DATEPAID, BALANCEOWING, STATUS)
VALUES (AUTOINCREMENTINVOICE.nextval, CTITLE, SYSDATE, NULL,V_TOTALCOST,NULL);
EXCEPTION WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('ERROR:The campaign title you entered returned no record(s), please enter a valid campaign title.');
COMMIT;
END END_CAMPAIGN;
END ASSIGNMENT3;
/
SET SERVEROUTPUT ON
EXECUTE ASSIGNMENT3.END_CAMPAIGN('Panasonic 3D TV');
While the parent foreign key error is correct, i dont want the block to execeute if the cursor doesnt return a row. Why is this happening?
Also, in terms of placing the COMMIT, where exactly do i tell it to COMMIT? Before the exception or after?
This is for a uni assignment.
When you loop over a cursor like that, if the cursor finds no matching rows, the loop simply doesn't execute at all. A NO_DATA_FOUND exception would only be raised if you had a SELECT ... INTO ... statement inside the BEGIN/END block that did not return any rows.
Where you have the COMMIT placed now, it is part of the EXCEPTION block -- but your indentation implies that you want it to execute whether the exception occurred or not. In this case, I would just put the COMMIT immediately after the INSERT, since it only matters if the INSERT is successful.
"So is there no way to have the NODATAFOUND exception trigger when
using a cursor, if the CTITLE parameter isnt found in the table"
What you could do is test the value of V_TOTAL_COST. If it is zero raise an exception, like this:
PROCEDURE END_CAMPAIGN(CTITLE IN CAMPAIGN.CAMPAIGNTITLE%TYPE) IS
CURSOR ADCOST_CUR IS
SELECT ACTUALCOST
FROM ADVERTISEMENT
WHERE ADVERTISEMENT.CAMPAIGNTITLE = CTITLE;
V_TOTALCOST NUMBER;
BEGIN
V_TOTALCOST := 0;
FOR INVOICE_REC IN ADCOST_CUR
LOOP
V_TOTALCOST := V_TOTALCOST + INVOICE_REC.ACTUALCOST;
END LOOP;
if v_total_cost = 0 then
raise no_data_found;
end if;
INSERT INTO INVOICE(INVOICENO, CAMPAIGNTITLE, DATEISSUED, DATEPAID, BALANCEOWING, STATUS)
VALUES (AUTOINCREMENTINVOICE.nextval, CTITLE, SYSDATE, NULL,V_TOTALCOST,NULL);
COMMIT;
EXCEPTION WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('ERROR:The campaign title you entered returned no record(s), please enter a valid campaign title.');
END END_CAMPAIGN;
This assumes you have a business rule that ACTUAL_COST cannot be zero.
Alternatively, there is the clunkier workaround of incrementing a counter in the loop and testing whether it is zero after the loop.
As for where to place the commit I would say the answer is not inside the procedure. The client (sqlplus in this case) should determine if the transaction will commit or rollback as the call to end the campaign may be just a part of a wider process. Also assuming that a campaign can exist without any advertisements then I would have an explicit check that the campaign title is valid perhaps against the table of CAMPAIGN? as suggested below:
CREATE OR REPLACE PACKAGE ASSIGNMENT3 IS
PROCEDURE END_CAMPAIGN(CTITLE IN CAMPAIGN.CAMPAIGNTITLE%TYPE);
END ASSIGNMENT3;
/
CREATE OR REPLACE PACKAGE BODY ASSIGNMENT3 AS
PROCEDURE END_CAMPAIGN(CTITLE IN CAMPAIGN.CAMPAIGNTITLE%TYPE) IS
V_VALID_CAMPAIGN INTEGER;
V_TOTALCOST NUMBER;
BEGIN
-- Check this campaign title is valid
/* Will get you NO_DATA_FOUND here if CTITLE is invalid so wrap in
another BEGIN END block to throw own custom error that the client
of this procedure can handle (if it wants) */
BEGIN
SELECT 1
INTO V_VALID_CAMPAIGN
FROM CAMPAIGN
WHERE CAMPAIGNTITLE = CTITLE;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RAISE_APPLICATION_ERROR(-20000,'The campaign title you entered returned no record(s), please enter a valid campaign title.');
END;
-- Now tot up the cost of ads in this campaign and raise the invoice
SELECT SUM(ACTUALCOST)
INTO V_TOTALCOST
FROM ADVERTISEMENT
WHERE ADVERTISEMENT.CAMPAIGNTITLE = CTITLE;
INSERT INTO INVOICE(INVOICENO, CAMPAIGNTITLE, DATEISSUED, DATEPAID, BALANCEOWING, STATUS)
VALUES (AUTOINCREMENTINVOICE.nextval, CTITLE, SYSDATE, NULL,V_TOTALCOST,NULL);
END END_CAMPAIGN;
END ASSIGNMENT3;
/
EXECUTE ASSIGNMENT3.END_CAMPAIGN('Panasonic 3D TV');
COMMIT;

Oracle SQL trigger on update of column

I'm trying to create a trigger for my table which automatically adds a published date based on when a certain flag is set to 'Y'
I don't have much experience with creating triggers but so far this is what i've got
create or replace
TRIGGER ADD_CREATE_DT
after UPDATE of approved ON articles
for each row
BEGIN
:new.create_dt := sysdate where approved = 'Y';
END;
I get this error when updating the column
trigger 'USER.ADD_CREATE_DT' is invalid and failed re-validation
Any ideas?
Thanks
Use the WHEN clause:
create or replace
TRIGGER ADD_CREATE_DT
after UPDATE of approved ON articles
for each row
when (new.approved = 'Y')
BEGIN
:new.create_dt := sysdate;
END;
Or use IF:
create or replace
TRIGGER ADD_CREATE_DT
after UPDATE of approved ON articles
for each row
BEGIN
if :new.approved = 'Y' then
:new.create_dt := sysdate;
end if;
END;
In this case, WHEN is more appropriate and efficient.
create or replace
TRIGGER ADD_CREATE_DT
after UPDATE of approved ON articles
for each row
BEGIN
IF :NEW.approved = 'Y' THEN
:new.create_dt := sysdate;
END IF;
END;
I don't know What version of Oracle do you use?
In Oracle 10g I got the following error:
ORA-04084: cannot change NEW values for this trigger type
04084. 00000 - "cannot change NEW values for this trigger type"
*Cause: New trigger variables can only be changed in before row
insert or update triggers.
*Action: Change the trigger type or remove the variable reference.
It does not allow to change the field on AFTER triggers.