I have the following table called employees
create table employees
(
eno number(4) not null primary key,
ename varchar2(30),
zip number(5) references zipcodes,
hdate date
);
And I'm trying to set a trigger on the table that will fire before an update or delete that will check the system time first to see if the time is between 12:00-13:00, if it is it will allow the insertion, otherwise prevent it.
My guess is so far:
CREATE or REPLACE TRIGGER twelve_one
BEFORE INSERT or Update ON employees
BEGIN
IF
....
ELSE
....
END;
But that's how far I've gotten into unfortunately. Can someone please help me to retrieve the system time first? Then how can I set up that IF ELSE block? And finally How to abort the transaction/insertion/update?
I'm using Oracle SQL Developer 4.02.15
Many Thanks
I assume you must declare temporary variable to set time now, and then compare the temporary variable.
CREATE OR REPLACE TRIGGER TWELVE_ONE
BEFORE INSERT OR UPDATE
ON EMPLOYEES
FOR EACH ROW
DECLARE
V_DATE VARCHAR2 (10);
BEGIN
SELECT TO_CHAR (SYSDATE, 'hh24:mi:ss') INTO V_DATE FROM DUAL;
IF (V_DATE >= '12:00:01' AND V_DATE < '13:00:00')
THEN
INSERT INTO TABLE ..
ELSE
UPDATE TABLE...
END IF;
END;
Related
I am trying to create a trigger on a table for an insert. Here is the creation script:
CREATE OR REPLACE TRIGGER INTEG_QRY_NEW
AFTER INSERT
ON INTEG_LOG
FOR EACH ROW
DECLARE
VAR2 NUMBER(10);
LOG_TEXT1 VARCHAR2(1000);
RESULT1 VARCHAR2(1000);
QUERY_NUM1 VARCHAR2(1000);
QUERY_TIME1 VARCHAR2(1000);
LOG_INDEX1 NUMBER(10);
BEGIN
SELECT COUNT(*) INTO VAR2 FROM USER_TAB_COLS WHERE (COLUMN_NAME = 'RESULT' OR COLUMN_NAME = 'QUERY_NUM' OR COLUMN_NAME = 'DATE_TIME' ) AND TABLE_NAME = 'INTEG_LOG';
IF VAR2=0 THEN
EXECUTE IMMEDIATE 'ALTER TABLE INTEG_LOG ADD RESULT VARCHAR2(50); ALTER TABLE INTEG_LOG ADD DATE_TIME DATE; ALTER TABLE INTEG_LOG ADD QUERY_NUM VARCHAR2(50);';
END IF;
LOG_TEXT1 := :NEW.LOG_TEXT;
QUERY_TIME1 := :NEW.QUERY_TIME;
LOG_INDEX1 := :NEW.LOG_INDEX;
IF QUERY_TIME1 = '' THEN
RESULT1 := '-1';
QUERY_NUM1 := '';
UPDATE INTEG_LOG
SET RESULT = RESULT1,
DATE_TIME = CURRENT_DATE,
QUERY_NUM = QUERY_NUM1
WHERE LOG_INDEX = LOG_INDEX1;
ELSE
RESULT1 := SUBSTR(LOG_TEXT1,(INSTR(LOG_TEXT1,'Result = ')+9),9);
QUERY_NUM1 := SUBSTR(SUBSTR(LOG_TEXT1,INSTR(LOG_TEXT1,'Q# ')+3,20),1,INSTR(SUBSTR(LOG_TEXT1,INSTR(LOG_TEXT1,'Q# ')+3,20),' '));
UPDATE INTEG_LOG
SET RESULT = RESULT1,
DATE_TIME = CURRENT_DATE,
QUERY_NUM = QUERY_NUM1
WHERE LOG_INDEX = LOG_INDEX1;
END IF;
END;
/
The trigger is taking care of a regular insert for the following columns (that are coming from an application):
LOG_DATE NUMBER(10),
LOG_TIME NUMBER(10),
LOG_TYPE NUMBER(10),
LOG_TEXT VARCHAR2(2000 BYTE),
LOG_INDEX NUMBER(10),
QUERY_TIME VARCHAR2(8 BYTE)
the trigger checks for the following column to whether they exist or not (if not, it adds them):
RESULT VARCHAR2(50 BYTE),
DATE_TIME DATE,
QUERY_NUM VARCHAR2(50 BYTE)
Now it is taking the string (LOG_TEXT column), and withdraws the result value and query number (what comes after '#Q ').
Next, it should update the same insert opperation, and just adds these values to the new 3 columns (result,date_time and query_num).
The trigger does get created successfully but the problem is, when i am trying to insert into the table (INTEG_LOG), I get the following error:
table string.string is mutating, trigger/function may not see it
Cause: A trigger (or a user defined plsql function that is referenced
in this statement) attempted to look at (or modify) a table that was
in the middle of being modified by the statement which fired it.
Action: Rewrite the trigger (or function) so it does not read that
table.
Might this be a syntax error ? Please help. Thank you.
The only reason the trigger compiles is because INTEG_LOG already has the columns you're dynamic SQL is trying to add. If the table didn't have those columns the trigger wouldn't add them because the trigger would be invalid as those UPDATE statements wouldn't compile.
One of the reasons why dynamic SQL should be avoid is that it turns compilation errors into runtime errors.
But trying to add columns to a table in a trigger built on that table is an astonishingly bad idea. Apart from anything else, DDL statement issue implicit commits and we cannot commit in triggers because that would play havoc with the transactions. (yes there is pragma autonomous_transaction but using that is usually a code smell).
The correct solution is:
Write a one-off DDL script to add those columns
If necessary make the script idempotent by checking for the prior existence of those columns before executing the ALTER TABLE statements
Run the script.
Populate the table with all the columns.
Of course this has nothing to do with the mutating table error which is due to those UPDATE statements. We cannot execute DML (including selects) on the table which hosts the trigger. I'm not sure what business rule you are trying to implement but you need a different way of doing it.
I've been working around this trigger and when I run the script it tells me the previous error message. I can't seem to figure out why it won't compile correctly, every pl/sql trigger tutorial seems to have the structure my trigger has. Code is the following:
create
or replace trigger new_artist before insert
on
Artist referencing new as nvartist declare counter number;
begin select
count( * ) into
counter
from
Performer
where
Stage_name = nvartist.Stage_name;
if counter > 0 then signal sqlstate '45000';
else insert
into
Artist
values(
nvartist.Stage_name,
nvartist.Name
);
insert
into
Performer
values(nvartist.Stage_name);
end if;
end;
It checks if the new artist already exists in its supertype (Performer), if it does exist it gives an error if it doesn't it inserts both into artist(Stage_name varchar2, Name varchar2) and Performer(Stage_name). Another subtype of Performer (and sibling to Artist) is Band(Stage_name), which in turn has a relationship with Artist. Why does the compiler yell at me for this trigger?
Thanks in advance
You may want to try this variant (I slightly modified names of your tables).
Creating tables with sample data:
CREATE table test_artist(
stage_name varchar2(100)
, name varchar2(100)
);
create table test_performer(
stage_name varchar2(100)
);
/*inserting test performer on which trigger will rise an error*/
insert into test_performer
select 'performer_1' from dual;
Creating trigger:
create or replace trigger new_artist
before insert
on TEST_ARTIST
referencing new as nvartist
for each row
declare
counter number;
begin
select count(*)
into counter
from test_performer
where Stage_name = :nvartist.Stage_name;
if counter > 0 then
--signal sqlstate '45000' ;
raise_application_error( -20001, 'No insertion with existing Performer');
else
/*you cant update the same table, in other case you'll get
ora-04091 table mutating error.
But nevertheless this values will be inserted by sql triggered this trigger.*/
--insert into test_artist values(:nvartist.Stage_name, :nvartist.Name);
insert into test_performer values(:nvartist.Stage_name);
end if;
end new_artist;
After that this insert will work, cause the is no 'performer_2' in 'test_performer' table:
insert into test_artist
select 'performer_2', 'name_2' from dual;
And this will fail:
insert into test_artist
select 'performer_1', 'name_1' from dual;
Essentially I wish to create a trigger that keeps track and edits the date_created column of a specific row after every insert or update.
These are the columns in my table:
| customer_id | store_id | Quantity | date_created |
the customer_id and store_id together are the primary key of the table
What I have so far:
CREATE OR REPLACE TRIGGER date_trig
BEFORE INSERT ON customer_table
FOR EACH ROW
DECLARE
BEGIN
-- This is where I assume the date will be set or edited
END;
I am brand new to PL/SQL so I am struggling with the actual body of this trigger.
Also, do I have the structure of a trigger correctly formed?
Hi Please find sample code.
create or replace trigger emp_mod_date
before update or insert on emp
for each row
begin
:new.mdate := sysdate;
end;
Use DEFAULT SYSDATE on the colum date_created like already suggested
if you insist to use a trigger, just write :NEW.date_created := SYSDATE;
I'm trying to create a trigger but I have learned I can not design it as in my first attempt, which I'm showing below. This will cause a 'mutating table' error due to selecting from the table as it is being modified. It actually didn't cause this error when inserting only one record at a time, but when I insert multiple records at once it does.
The purpose of the trigger is to count the number of records in the table where the customer is equal to the customer about to be inserted, and to set the new order_num value as count+1. I also have a public key value set by the trigger which draws from a sequence. This part works ok once I remove the order_num part of the trigger and the associated SELECT. How can I achieve what I am trying to do here? Thanks in advance.
CREATE OR REPLACE TRIGGER t_trg
BEFORE INSERT ON t
FOR EACH ROW
DECLARE
rec_count NUMBER(2,0);
BEGIN
SELECT COUNT(*) INTO rec_count
FROM t
WHERE customer_id = :NEW.customer_id;
:NEW.order_num:= rec_count+1;
:NEW.order_pk_id:= table_seq.NEXTVAL;
END;
Two triggers and temp table approach can provide solution to you seek, preventing mutating table error. However performance will most likely suffer.
create global temporary table cust_temp(customer_id number, cust_cnt number);
create or replace trigger t_trig1
before insert on t
declare
begin
insert into cust_temp select customer_id, count(*) from t group by customer_id;
end;
/
CREATE OR REPLACE TRIGGER t_trg2
BEFORE INSERT ON t
FOR EACH ROW
DECLARE
rec_count number;
BEGIN
BEGIN
SELECT cust_cnt INTO rec_count
FROM cust_temp
WHERE customer_id = :NEW.customer_id;
EXCEPTION when no_data_found then rec_count := 0;
END;
:NEW.order_num:= rec_count+1;
:NEW.order_pk_id:= table_seq.NEXTVAL;
update cust_temp set cust_cnt = rec_count + 1
where customer_id = :NEW.customer_id;
END;
/
I have this procedure
PROCEDURE insertSample
(
return_code_out OUT VARCHAR2,
return_msg_out OUT VARCHAR2,
sample_id_in IN table1.sample_id%TYPE,
name_in IN table1.name%TYPE,
address_in IN table1.address%TYPE
)
IS
BEGIN
return_code_out := '0000';
return_msg_out := 'OK';
INSERT INTO table1
sample_id, name, address)
VALUES
(sample_id_in, name_in, address_in);
EXCEPTION
WHEN OTHERS
THEN
return_code_out := SQLCODE;
return_msg_out := SQLERRM;
END insertSample;
I want to add 4th column in table1 like day_time and add current day timestamp in it.. ho can i do that in this procedure.. thank you
Assuming you you have (or add) a column to the table outside of the procedure, i.e.
ALTER TABLE table1
ADD( insert_timestamp TIMESTAMP );
you could modify your INSERT statement to be
INSERT INTO table1
sample_id, name, address, insert_timestamp)
VALUES
(sample_id_in, name_in, address_in, systimestamp);
In general, however, I would strongly suggest that you not return error codes and error messages from procedures. If you cannot handle the error in your procedure, you should let the exception propagate up to the caller. That is a much more sustainable method of writing code than trying to ensure that every caller to every procedure always correctly checks the return code.
Using Sysdate can provide all sorts of manipulation including the current date, or future and past dates.
http://edwardawebb.com/database-tips/sysdate-determine-start-previous-month-year-oracle-sql
SYSDATE will give you the current data and time.
and if you add the column with a default value you can leave your procedure as it is
ALTER TABLE table1 ADD when_created DATE DEFAULT SYSDATE;