Creating a trigger to automatically calculate the value of total tour time - sql

I am facing some issue while creating a trigger that can automatically calculate the total time it takes for a tour to finish, below is the text question
The event holder has decided to record the shortest tour time for each event type for
all future exhibitions. Add two new attributes in the EVENTTYPE table named
eventype_record and eventtype_recordholder to store the shortest tour time for
each event type and the participant (participant number) who holds the record. You
may assume that only a single participant will hold each record and that the record
holder will only be replaced by a new single participant if their tour time is less
than the current eventtype_record. Calculate the tour time attribute added in previous task when the tour finish time is updated (ie. the participant finished the tour)
What I currently have is
CREATE OR REPLACE TRIGGER tour_time_updater
AFTER UPDATE OF tour_finishtime ON entry
FOR EACH ROW
BEGIN
UPDATE entry
SET tour_time = to_char(((:new.tour_finishtime - :old.tour_starttime) * 24 * 60), '9999.99');
END;
/
But after I try to insert a fake participant in my entry table with no finish time with this code
INSERT INTO entry (
event_id,
entry_no,
tour_starttime,
tour_finishtime,
part_no,
group_id,
tour_time
) VALUES (
9,
6,
to_date('09:05:43', 'HH:MI:SS'),
NULL,
5,
NULL,
NULL
);
and only update its tour_finishtime later
UPDATE entry SET tour_finishtime = to_date('10:05:43', 'HH:MI:SS')
where part_no = 5 and event_id = 9;
it is giving me errors like
**UPDATE entry SET tour_finishtime = to_date('10:05:43', 'HH:MI:SS')
where part_no = 5 and event_id = 9
Error report -
ORA-04091: table XXXXXXXX.ENTRY is mutating, trigger/function may not see it
ORA-06512: at "XXXXXXXX.TOUR_TIME_UPDATER", line 5
ORA-04088: error during execution of trigger 'XXXXXXXX.TOUR_TIME_UPDATER'**
Can anyone help me with that? Thank you in advance!

Don't update the table (literally), but
CREATE OR REPLACE TRIGGER tour_time_updater
BEFORE UPDATE OF tour_finishtime ON entry
FOR EACH ROW
BEGIN
:new.tour_time := to_char(((:new.tour_finishtime - :new.tour_starttime) * 24 * 60), '9999.99');
END;
/
Code you wrote is trying to update the same table whose modification fired the trigger; for the trigger, that table is "mutating" and that's an invalid condition.
You could "fix" it by writing a compound trigger (if your database version supports it) or using a package, but - why bother, if correct way to do it is as suggested in code above?

Don't use a TRIGGER, use a virtual column:
CREATE TABLE entry (
event_id NUMBER PRIMARY KEY,
entry_no NUMBER,
tour_starttime DATE,
tour_finishtime DATE,
part_no NUMBER,
group_id NUMBER
);
ALTER TABLE entry ADD tour_time INTERVAL DAY(0) TO SECOND (0)
GENERATED ALWAYS AS ((tour_finishtime - tour_starttime) DAY(0) TO SECOND(0));
or
ALTER TABLE entry ADD tour_time NUMBER(7,2)
GENERATED ALWAYS AS ((tour_finishtime - tour_starttime) *24 * 60);
If you must use a trigger (don't) then:
CREATE TABLE entry (
event_id NUMBER PRIMARY KEY,
entry_no NUMBER,
tour_starttime DATE,
tour_finishtime DATE,
part_no NUMBER,
group_id NUMBER,
tour_time INTERVAL DAY(0) TO SECOND(0)
);
CREATE OR REPLACE TRIGGER tour_time_updater
BEFORE INSERT OR UPDATE OF tour_finishtime ON entry
FOR EACH ROW
BEGIN
:NEW.tour_time := (:new.tour_finishtime - :new.tour_starttime) DAY(0) TO SECOND(0);
END;
/
db<>fiddle here

Related

Why is my codes having error 00049 Bad Bind Variable

create or replace trigger addDate
before insert or update on Employee
for each row
begin
for EmployeeRec in
(
select * from employee
)
loop
if (EmployeeRec.DateLimit > sysdate) then
raise_application_error (-20001, 'You are only allowed to insert once a day, please try again
tomorrow');
end if;
end loop EmployeeRec;
:NEW.DateLimit := sysdate + 1;
end;
/
show errors;
Need to be able to insert a record for a day only. Any other records must wait till the next day
There is PLS 00049 ERROR at New.DateLimit
Maybe column name is not DateLimit, but Date_Limit.
If I am following you correctly, you want to allow just one record per day, as defined by column date_limit.
You don't need a trigger for this. One option uses a computed column and a unique constraint.
create table employee (
employee_id int primary key,
name varchar2(100),
date_limit date default sysdate,
day_limit date generated always as (trunc(date_limit)) unique
);
day_limit is a computed column, that contains the date portion of date_limit (with the time portion removed). A unique constraint is set on this column, so two rows cannot have date_limits that belong to the same day.
Demo on DB Fiddle

Trigger for before insert to add in x number of days to a date

I am trying to add x number of days to a variable within a table by deriving another date from the same table.
For example, in my BILLING table, it has 2 dates - BillDate and DueDate.
And so, I am trying to add a trigger before the insertion, such that it will takes in the BillDate and add 30 days to derive the DueDate.
While doing so, I got a bunch of errors, as follows:
dbfiddle
CREATE TABLE BILLING
(
BillDate DATE NOT NULL,
DueDate DATE NULL
);
-- Got ORA-24344: success with compilation error
CREATE TRIGGER duedate_trigger BEFORE INSERT ON BILLING
FOR EACH ROW
begin
set DueDate = :new.DueDate: + 30
end;
-- Got ORA-04098: trigger 'FIDDLE_FBHUOBXMWRPYBBXPIKTW.DUEDATE_TRIGGER' is invalid and failed re-validation
INSERT INTO BILLING
VALUES ((Date '2020-07-23'), NULL);
For the insertion, I have tried removing the NULL, but still I am getting a bunch of errors.
Any ideas?
Also, in the event, if the insertion statement also does includes in the due date too, will this affects the trigger? Trying to cater for 2 scenarios, generate a due date if not give, else if given, check if it is within 30 days from BillDate and update it... (likely I may have overthink/ overestimated that this is doable?)
CREATE OR REPLACE TRIGGER duedate_trigger BEFORE INSERT ON BILLING
FOR EACH ROW
begin
:new.DueDate := :new.BillDate + 30;
end;
INSERT INTO BILLING (BillDate ) values (sysdate);
CREATE OR REPLACE TRIGGER duedate_trigger
BEFORE INSERT ON billing
FOR EACH ROW
DECLARE
v_dueDate_derive NUMBER;
BEGIN
v_dueDate_derive = 30;
:new.DueDate = :new.BillDate + v_dueDate_derive;
END;
Days can be easily added by +, so it should not be the problem.
I believe there may be something wrong with INSERT itself.
Could you try to put INSERT like this?
INSERT INTO BILLING
VALUES (TO_DATE('2020-07-23'), NULL);

Calculate average after insert in PostgreSQL

I'm trying to get the average of the field pm10_ug_m3 of all the values introduced in the last 24 hours by a sensor, but with my current code the average does not include the value of the row inserted.
Currently as the trigger is done before the insert, it is not taking into account the last value inserted in the field pm10_ug_m3. For this reason the average introduced in the field is 3 and not 6.5 obtained from (10+3)/2
1) Creation of table and addition of some dates:
(
ID bigint NOT NULL,
SensorID character(10),
pm10_ug_m3 numeric(10,2),
tense timestamp without time zone,
average float,
CONSTRAINT id_pk PRIMARY KEY (ID)
);
INSERT INTO sensor (ID,SensorID,pm10_ug_m3,tense) VALUES
(1,'S16',1,'2019-07-10 04:25:59'),
(2,'S20',3,'2017-07-10 02:25:59');
2) Creation of the trigger to calculate the average of pm10_ug_m3 of the records captured in the last 24h from the same sensor:
CREATE OR REPLACE FUNCTION calculate_avg() RETURNS TRIGGER AS $BODY$
BEGIN
NEW.average := ( SELECT AVG(pm10_ug_m3)
FROM sensor
WHERE SensorID=NEW.SensorID
AND tense>= NEW.tense - INTERVAL '24 HOURS');
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql;
CREATE TRIGGER calculate_avg_trigger BEFORE INSERT OR UPDATE
ON sensor FOR EACH ROW
EXECUTE PROCEDURE calculate_avg();
3) Insert of a new row, where it will be populated the field average:
INSERT INTO sensor (ID,SensorID,pm10_ug_m3,tense) VALUES
(3,'S20',10,'2017-07-10 04:25:59')
This does not work because the AVG() function only considers the data which is still inserted, not the new data which will be inserted.
Changing the trigger point from BEFORE to AFTER would deliver a correct result indeed, but it will not be set because the INSERT already has been done at this point.
So one way to achieve your result is to calculate the average manually in your trigger function:
SELECT (SUM(pm10_ug_M3) + NEW.pm10_ug_m3) / (COUNT(pm10_ug_m3) + 1)
FROM ...
SUM() of the current values + the new divided by the COUNT() of the current values + the new one.
demo:db<>fiddle

How to create something to update end date by starting day and duration column automaticly SQL

I want to create something that will update my data in table automaticly but i don't know what to use.
i have nr1 table
create table NR1
(
id INTEGER not null,
price INTEGER,
price2 INTEGER,
start_date DATE,
end_date DATE,
duration NUMBER
)
and i tried to create trigger which will update always after inserting or updateing my table, end_date, so i tried something like this:
create or replace trigger update_nr1_date
after update or insert on nr1
for each row
when (new.id>0)
declare
begin
UPDATE nr1
set nr1.end_date =add_months(nr1.start_date, nr1.duration);
end;
but it have mutation problems, and i read something about that, and i udersatnd the concept of this. I want to make trigger like this (not sheduler, because i want to get it automaticly after inserting or updating some rows). Is it possible while inserting data to table it itself refill missing columns?
Your trigger needs to before update in order to change the value in the row the trigger is fired again; and it needs to modify the current row's (new) pseudorecord via an assignment statement - you should not be issuing a separate update statement inside the trigger. It will cascade, for a start, and in your example woudl try to update every row in the table. But that is also causing the mutating table error - you're trying to update rows in the table the trigger is against. There are workarounds when that is actually necessary, but it isn't here, that update should just not be there.
So you would do:
create or replace trigger update_nr1_date
before update or insert on nr1
for each row
when (new.id>0)
begin
:new.end_date := add_months(:new.start_date, :new.duration);
end;
/
But if you're on 11g or higher you can use a virtual column instead, without needing a trigger at all:
create table NR1
(
id INTEGER not null,
price INTEGER,
price2 INTEGER,
start_date DATE,
end_date DATE generated always as (add_months(start_date, duration)) virtual,
duration NUMBER
)
Then when you insert, skip that column in the statement:
insert into nr1 (id, price, price2, start_date, duration)
values (1, 2, 3, date '2018-06-01', 3);
1 row inserted.
select * from nr1 where id = 1;
ID PRICE PRICE2 START_DATE END_DATE DURATION
---------- ---------- ---------- ---------- ---------- ----------
1 2 3 2018-06-01 2018-09-01 3
The end date always reflects the values of the other two columns.

INSERT TRIGGER IN ORACLE

I am new to triggers in Oracle. I created an EVENT table with this syntax:
CREATE TABLE Event
(event_id NUMBER (3) NOT NULL,
event_date DATE NOT NULL,
venue_id NUMBER (2) NOT NULL,
concert_id NUMBER (3) NOT NULL
);
I want to create a trigger to ensure that concerts cannot run during the month of August. I tried the following code to create the trigger. The trigger was created successfully but after inserting a date with the month of August, it was inserted. This is not suppose to be.
CREATE OR REPLACE TRIGGER check_date
BEFORE INSERT ON event
DECLARE
event_date date;
BEGIN
IF (to_char(event_date, 'mm') = 8) THEN
raise_application_error(-20000, 'Concerts cannot be run during August');
END IF;
END;
First, the trigger needs to be a row-level trigger not a statement-level trigger. You want the trigger to be fired for every row that is inserted not just once for every statement. Declaring the trigger a row-level trigger allows you to see the data for each row that is being inserted.
Second, you don't want to declare a local variable event_date. You want to look at :new.event_date which is the event_date for the row that is being inserted.
If I put those two together
CREATE OR REPLACE TRIGGER check_date
BEFORE INSERT ON event
FOR EACH ROW
BEGIN
IF (to_char(:new.event_date, 'mm') = 8) THEN
raise_application_error(-20000, 'Concerts cannot be run during August');
END IF;
END;
then you'll get the behavior you want
SQL> insert into event values( 1, date '2012-08-01', 1, 1 );
insert into event values( 1, date '2012-08-01', 1, 1 )
*
ERROR at line 1:
ORA-20000: Concerts cannot be run during August
ORA-06512: at "SCOTT.CHECK_DATE", line 3
ORA-04088: error during execution of trigger 'SCOTT.CHECK_DATE'
As a general matter of cleanliness, you also want to compare strings with strings and numbers with numbers. So you would want either
to_number( to_char(:new.event_date, 'mm') ) = 8
or
to_char(:new.event_date, 'fmmm') = '8'
or
to_char(:new.event_date, 'mm') = '08'
change:
IF (to_char(event_date, 'mm') = 8) THEN
to:
IF (to_char(event_date, 'mm') = '08') THEN
You're comparing between string and number.