I am trying to create some triggers and procedures to auto populate some tables in my database. I have two tables, Users and Utilities.
Users Table:
CREATE TABLE USERS (
User_id Number(38,0) NOT NULL PRIMARY KEY,
User_name char(18) NULL ,
Storage_Size varchar(18) NULL ,
Memory_Usage Number(38,0) NULL
);
Utilities Table
CREATE TABLE UTILITIES (
Utility_id Number(38,0) NOT NULL PRIMARY KEY,
Utility_Name varchar(18) NULL ,
Utility_Cost Number(38,0) NULL ,
Running char(4) NULL ,
User_id Number(38,0) NULL ,
);
Now what I would like to take place in my DB.
When a User is INSERTED into USERS table it fires off a trigger.
This trigger will then insert a bunch of Utilities into the UTILITIES table (User_ID will match the User_ID that was just created) for the User that was created.
After Utilities have been inserted into UTILITIES table I would then like to run a procedure that will SUM(Utility_Cost) in the UTILITIES table and store the SUM in USERS.Memory_Usage WHERE the USER_ID matches.
What I have created so far:
Trigger to fire after INSERT on USERS table:
CREATE OR REPLACE TRIGGER users_after_insert
AFTER INSERT ON USERS
BEGIN
INSERT INTO UTILITIES (UTILITY_NAME, RUNNING, USER_ID, UTILITY_ID, UTILITY_COST)
VALUES
('Javaw.exe', 'YES', :new.USER_ID, seq_utility_id.nextval
, round(dbms_random.value(25000, 100000)));
sum_data();
END;
Procedure to be called from trigger:
CREATE OR REPLACE PROCEDURE sum_data
IS
BEGIN
UPDATE USERS
SET MEMORY_USAGE = (SELECT SUM(UTILITY_COST)
FROM UTILITIES
WHERE USERS.USER_ID = UTILITIES.USER_ID)
WHERE USERS.User_id = :new.User_id;
END;
However when I try and INSERT into the USERS table I get:
ORA-04091: table STUDENT052.USERS is mutating, trigger/function may not see it
ORA-06512: at "STUDENT052.SUM_DATA", line 4
ORA-06512: at "STUDENT052.USERS_AFTER_INSERT", line 5
ORA-04088: error during execution of trigger 'STUDENT052.USERS_AFTER_INSERT'
Does anyone have any idea why this is happening? Any information would be greatly appreciated. Thanks!
You will have to call the procedure separately and update the table, since the data is still not committed and the procedure will try update the same table, it wont work. Apart from that I have executed the trigger and it will populate your utilities table.
SQL> CREATE TABLE USERS (
2 User_id Number(38,0) NOT NULL PRIMARY KEY,
3 User_name char(18) NULL ,
4 Storage_Size varchar(18) NULL ,
5 Memory_Usage Number(38,0) NULL
6 );
Table created.
SQL> CREATE TABLE UTILITIES (
2 Utility_id Number(38,0) NOT NULL PRIMARY KEY,
3 Utility_Name varchar(18) NULL ,
4 Utility_Cost Number(38,0) NULL ,
5 Running char(4) NULL ,
6 User_id Number(38,0) NULL
7 );
Table created.
SQL> CREATE SEQUENCE seq_utility
2 MINVALUE 1
3 MAXVALUE 100
4 START WITH 1
5 INCREMENT BY 1
6 CACHE 5;
Sequence created.
SQL> CREATE OR REPLACE PROCEDURE sum_data ( user_id number) is
2 luser_id number:=user_id;
3 BEGIN
4 UPDATE USERS
5 SET MEMORY_USAGE = (SELECT SUM(UTILITY_COST) FROM UTILITIES WHERE USERS.USER_ID =luser_id);
6 END;
7 /
Procedure created.
SQL> CREATE OR REPLACE TRIGGER users_after_insert
2 AFTER INSERT ON USERS
3 FOR EACH ROW
4 BEGIN
5 INSERT INTO UTILITIES (UTILITY_NAME, RUNNING, USER_ID, UTILITY_ID, UTILITY_COST)
6 VALUES
7 ('Javaw.exe', 'YES', :New.USER_ID, seq_utility_id.nextval, round(dbms_random.value(25000, 100000)));
8 --sum_data(:New.USER_ID);
9 END;
10
11 /
Trigger created.
SQL> insert into users (user_id,user_name) values (125,'TESTUSER');
1 row created.
SQL> select * from utilities;
UTILITY_ID UTILITY_NAME UTILITY_COST RUNN USER_ID
---------- ------------------ ------------ ---- ----------
4 Javaw.exe 68271 YES
5 Javaw.exe 62481 YES 124
10 Javaw.exe 60727 YES 125
SQL> select * from users;
USER_ID USER_NAME STORAGE_SIZE MEMORY_USAGE
---------- ------------------ ------------------ ------------
123 TESTUSER
124 TESTUSER
125 TESTUSER
EDIT
SQL> CREATE OR REPLACE TRIGGER users_after_insert
2 AFTER INSERT ON USERS
3 FOR EACH ROW
4 BEGIN
5 INSERT INTO UTILITIES (UTILITY_NAME, RUNNING, USER_ID, UTILITY_ID, UTILITY_COST)
6 VALUES
7 ('Javaw.exe', 'YES', :New.USER_ID, seq_utility_id.nextval, round(dbms_random.value(25000, 100000)));
8 commit;
9 sum_data(:New.USER_ID);
10 END;
11
12 /
Trigger created.
SQL> insert into users (user_id,user_name) values (126,'TESTUSER');
insert into users (user_id,user_name) values (126,'TESTUSER')
*
ERROR at line 1:
ORA-04092: cannot COMMIT in a trigger
ORA-06512: at "SCOTT.USERS_AFTER_INSERT", line 5
ORA-04088: error during execution of trigger 'SCOTT.USERS_AFTER_INSERT'
A commit within autonomous pragma transaction:
SQL> CREATE OR REPLACE TRIGGER users_after_insert
2 AFTER INSERT ON USERS
3 FOR EACH ROW
4 declare
5 PRAGMA AUTONOMOUS_TRANSACTION;
6 BEGIN
7 commit;
8 INSERT INTO UTILITIES (UTILITY_NAME, RUNNING, USER_ID, UTILITY_ID, UTILITY_COST)
9 VALUES
10 ('Javaw.exe', 'YES', :New.USER_ID, seq_utility_id.nextval, round(dbms_random.value(25000, 100000)));
11 sum_data(:New.USER_ID);
12 END;
13
14 /
Trigger created.
SQL> insert into users (user_id,user_name) values (126,'TESTUSER');
insert into users (user_id,user_name) values (126,'TESTUSER')
*
ERROR at line 1:
ORA-06519: active autonomous transaction detected and rolled back
ORA-06512: at "SCOTT.USERS_AFTER_INSERT", line 9
ORA-04088: error during execution of trigger 'SCOTT.USERS_AFTER_INSERT'
EDIT2
SQL> create view users_views as select a.user_id,a.user_name,SUM(p.UTILITY_COST) as "memory_usage"
2 from users a,utilities p where a.user_id=p.user_id group by a.user_id,a.user_name;
View created.
SQL> select * from users_views;
USER_ID USER_NAME memory_usage
---------- ------------------ ------------
125 TESTUSER 60727
124 TESTUSER 62481
SQL>
Yes, read up on mutating triggers. You're trying to update a record being inserted at the same time, and if you're trying to insert multiple utilities, well, it becomes a real mess.
Why not instead create a view on the tables that includes a column computing the memory usage? Or try a virtual column (or function-based column) on the users table that's based on the other table. (I haven't tested that across tables, so I can't say whether it would work or not.)
Related
I have two tables: teachers and advisers, which have some repeating values(a person can be both teacher and adviser)
teacher has primary key: Teachers_id, which is foreign key in advisers table
I want to make a trigger that would delete values from adviser, if they were deleted in teacher
I tried to do it like this, but I have a mistake, which I cannot find
create or replace TRIGGER delete_teacher
AFTER
DELETE on TEACHER
FOR EACH ROW
declare
pragma autonomous_transaction;
BEGIN
DELETE FROM ADVISER
WHERE ADVISER.T_id = TEACHER.T_id;
END;
I'm working in oracle apex
That's
CREATE OR REPLACE TRIGGER delete_teacher
AFTER DELETE
ON teacher
FOR EACH ROW
BEGIN
DELETE FROM adviser where t_id = :old.t_id;
END;
trigger doesn't have to be (and shouldn't be) an autonomous transaction (why do you think it should be?)
reference deleted row with its :old pseudorecord
On the other hand, if you created the foreign key constraint so that it deletes child records automatically, you wouldn't have to create any triggers.
SQL> create table teacher
2 (t_id number primary key,
3 name varchar2(10));
Table created.
SQL> create table adviser
2 (a_id number primary key,
3 t_id number references teacher on delete cascade, --> this
4 name varchar2(10));
Table created.
Sample rows:
SQL> insert all
2 into teacher values (1, 'Little')
3 into teacher values (2, 'Foot')
4 into adviser values (100, 1, 'Little')
5 select * from dual;
3 rows created.
Delete teacher whose t_id = 1; Oracle will delete both master and detail records:
SQL> delete from teacher where t_id = 1;
1 row deleted.
SQL> select * from teacher;
T_ID NAME
---------- ----------
2 Foot
SQL> select * from adviser;
no rows selected --> see? No Little here.
SQL>
For this scenario, I have a table like this: ID (Autoincrement, PK), PartType (VarChar), and DesignItemID (VarChar). I would like to combine the columns ID and PartType into column DesignItemID using a single INSERT statement.
Is this possible?
The purpose for this scenario spawns from trying to use an external SQL database for a part library in Altium Designer. Altium Designer needs a unique ID to maintain a proper link to the part that is placed and the DB. Ordinarily, an autoincrement PK could work, however, I need to keep the different types of parts in separate tables (such at resistors in a resistor table and capacitors in a capacitor table, etc.). So, if I have two or more different tables with an autoincrement PK ID column, I will have multiple IDs all starting at 1.
My proposed solution is to make a table with column ID using autoincrement for the PK, column PartType using a char or varchar, and column DesignItemID also using a char or varchar. Upon an INSERT command, I will enter the value RES for resistor or CAP for capacitor for column PartType and somehow LPAD ID to about 6 places and CONCAT with PartType to create DesignItemID RES000001 or CAP000001 for example. Both tables have 1 as PK ID, but, with the part type and padding, a unique column can be made for Altium Designer.
I understand that in a SQL admin interface, I could structure a query to create this unique piece of data, but Altium Designer requires this unique ID to be in a column.
I can accomplish this task in Access by using a calculate field, but Access is limited to number of concurrent users and cannot scale like an external SQL DB can.
Please note that I will have far more columns in the Database that corresponds to the part. I am only focusing on the columns that I do not know if what I am asking can be done.
depending on your database,
it seems you are asking for a unique number that spans across multiple tables. This could be called ultimately a GUID - if it should also be unique across databases.
this could be done with a single SEQUENCE. or you can look up GUID generators.
exporting multiple tables with such a GUID would be no problem - you just query from wherever they live and send them to your output stream.
Importing on the other hand is more difficult - since you will need to know where each GUID lives (in which table). You can do this with another table that maps each GUID to the table it belongs in.
A little bit of walking instead of just talking. Code you'll see is Oracle, but I guess other databases offer the same or similar options. Note that I don't know Altium Designer.
Question you asked was:
can I combine two or more fields into one field during the same insert statement?
Yes, you can; you already know the operator - it is concatenation. In Oracle, it is either the concat function or double pipe || operator. Here's how.
First, two sample tables (resistors and capacitors):
SQL> create table resistor
2 (id_res varchar2(10) constraint pk_res primary key,
3 name varchar2(10) not null
4 );
Table created.
SQL> create table capacitor
2 (id_cap varchar2(10) constraint pk_cap primary key,
3 name varchar2(10) not null
4 );
Table created.
Sequence will be used to create unique numbers:
SQL> create sequence seqalt;
Sequence created.
Database trigger which creates the primary key value by concatenating a constant (RES for resistors) and the sequence number, left-padded with zeros up to 7 characters in length (so that the full value length is 10 characters):
SQL> create or replace trigger trg_bi_res
2 before insert on resistor
3 for each row
4 begin
5 :new.id_res := 'RES' || lpad(seqalt.nextval, 7, '0');
6 end trg_bi_res;
7 /
Trigger created.
SQL> create or replace trigger trg_bi_cap
2 before insert on capacitor
3 for each row
4 begin
5 :new.id_cap := 'CAP' || lpad(seqalt.nextval, 7, '0');
6 end trg_bi_cap;
7 /
Trigger created.
Let's insert some rows:
SQL> insert into resistor (name) values ('resistor 1');
1 row created.
SQL> select * from resistor;
ID_RES NAME
---------- ----------
RES0000001 resistor 1
Capacitors:
SQL> insert into capacitor (name) values ('capac 1');
1 row created.
SQL> insert into capacitor (name) values ('capac 2');
1 row created.
SQL> select * From capacitor;
ID_CAP NAME
---------- ----------
CAP0000002 capac 1
CAP0000003 capac 2
My suggestion is a view instead of a new table to be used by the Altium Designer - of course, if it is possible (maybe Designer requires a table, and nothing but a table ...):
SQL> create or replace view v_altium (designitemid, name) as
2 select id_res, name from resistor
3 union all
4 select id_cap, name from capacitor;
View created.
SQL> /
View created.
SQL> select * from v_altium;
DESIGNITEM NAME
---------- ----------
RES0000001 resistor 1
CAP0000002 capac 1
CAP0000003 capac 2
You'd now make the Altium Designer read the view and - from my point of view - it should work just fine.
If it has to be a table (let's call it altium), then it would look like this:
SQL> create table altium
2 (designitemid varchar2(10) constraint pk_alt primary key,
3 name varchar2(10)
4 );
Table created.
Triggers will now be changed so that they also insert a row into the altium table (see line #7):
SQL> create or replace trigger trg_bi_res
2 before insert on resistor
3 for each row
4 begin
5 :new.id_res := 'RES' || lpad(seqalt.nextval, 7, '0');
6 insert into altium (designitemid, name) values (:new.id_res, :new.name);
7 end trg_bi_res;
8 /
Trigger created.
SQL> create or replace trigger trg_bi_cap
2 before insert on capacitor
3 for each row
4 begin
5 :new.id_cap := 'CAP' || lpad(seqalt.nextval, 7, '0');
6 insert into altium (designitemid, name) values (:new.id_cap, :new.name);
7 end trg_bi_cap;
8 /
Trigger created.
Let's try it:
SQL> insert into resistor (name) values ('resistor 4');
1 row created.
SQL> insert into resistor (name) values ('resistor 5');
1 row created.
SQL> insert into capacitor (name) values ('capac 5');
1 row created.
Altium table contents reflects contents of resistor and capacitor:
SQL> select * from altium;
DESIGNITEM NAME
---------- ----------
RES0000011 resistor 4
RES0000012 resistor 5
CAP0000013 capac 5
SQL>
However: why do I prefer a view over a table? Because consistency might suffer. What if you delete a row from the capacitor table? You'd have to delete appropriate row from the new altium table as well, and vice versa.
You can't create a foreign key constraint from the altium table to reference primary keys in other tables because as soon as you try to insert a row into the altium table that references resistor, it would fail as there's no such a primary key in capacitor. You can create constraints, but - that's pretty much useless:
SQL> drop table altium;
Table dropped.
SQL> create table altium
2 (designitemid varchar2(10) constraint pk_alt primary key,
3 name varchar2(10),
4 --
5 constraint fk_alt_res foreign key (designitemid) references resistor (id_res),
6 constraint fk_alt_cap foreign key (designitemid) references capacitor (id_cap)
7 );
Table created.
OK, table was successfully created, but - will it work?
SQL> insert into resistor (name) values ('resistor 7');
insert into resistor (name) values ('resistor 7')
*
ERROR at line 1:
ORA-02291: integrity constraint (SCOTT.FK_ALT_CAP) violated - parent key not
found
ORA-06512: at "SCOTT.TRG_BI_RES", line 3
ORA-04088: error during execution of trigger 'SCOTT.TRG_BI_RES'
SQL>
Nope, it won't as such a primary key doesn't exist in the capacitor table.
It means that you'd have to maintain consistency manually, and that's always tricky.
Therefore, if possible, use a view.
Using: Application Express 18.1.0.00.45
Please note: I am very new to Oracle Apex and SQL.
For a project I have to create an Application straight in Apex.
I was trying to create a table that works with a Primary Key that auto-increments itself.
Yesterday I created my first Application with a page for user input in a table, a page with table display and filter option and was playing around with forms, dashboard and authentication methods.
I removed the app after playing to start the "Real Work", but I my enthusiasm went quickly away when I realized that I am doing something very wrong but am not sure what.. :)
After lots of googling / reading etc, I am still not sure what is wrong..
See below the code:
-- Create Sequence
CREATE SEQUENCE seq_odm_for_pk
START WITH 1
INCREMENT BY 1
CACHE 100;
-- Create Table for User Input
CREATE TABLE ODM_Progress_v1 (
-- General Details:
ID int NOT NULL AUTO_INCREMENT,
TRAINEE varchar(50) NOT NULL, --1
COACH varchar(50) NOT NULL, --2
STATUS varchar(50) NOT NULL, --3
REGION varchar(5) NOT NULL, --4
-- Actions:
ACTION_TAKEN varchar(100) NOT NULL, --5
ACTION_DETAILS varchar(250), --6
ACTIONED_BY varchar(50) NOT NULL, --7
ACTIONED_DATE DATE NOT NULL, --8
-- Constraints that perform checks for each column:
CONSTRAINT CHK_GeneralDetails CHECK (TRAINEE!=COACH AND (STATUS IN('New', 'In Progress', 'Completed')) AND (REGION IN('EMEA', 'APAC', 'AMER'))),
-- Set Primary Key (Trainee+Coach):
CONSTRAINT PK_ODMProgress PRIMARY KEY (TRAINEE,REGION,ID)
);
-- Create Trigger
CREATE trigger_for_pk_odm_progress
BEFORE INSERT ON ODM_Progress_v1
FOR EACH ROW
WHEN (new.ID is null)
BEGIN
select seq_odm_for_pk.nextval into :new.ID from DUAL;
-- :new.PK_ODMProgress := seq_odm_for_pk.nextval;
END;
The script finishes running with 3 errors, see below:
CREATE OR REPLACE SEQUENCE seq_odm_for_pk START WITH 1
INCREMENT BY 1 CACHE 100
ORA-00922: missing or invalid option
CREATE TABLE ODM_Progress_v1 ( -- General Details: ID int NOT
NULL AUTO_INCREMENT, TRAINEE varchar(50) NOT NULL, --1 COACH
varchar(50) NOT NULL, --2 STATUS varchar(50) NOT NULL, --3
REGION varchar(5) NOT NULL, --4 -- Actions: ACTION_TAKEN
varchar(100) NOT NULL, --5 ACTION_DETAILS varchar(250), --6
ACTIONED_BY varchar(50) NOT NULL, --7 ACTIONED_DATE DATE NOT NULL,
--8 -- Constraints that perform checks for each column: CONSTRAINT CHK_GeneralDetails CHECK (TRAINEE!=COACH AND (STATUS
IN('New', 'In Progress', 'Completed')) AND (REGION IN('EMEA', 'APAC',
'AMER'))), -- Set Primary Key (Trainee+Coach): CONSTRAINT
PK_ODMProgress PRIMARY KEY (TRAINEE,REGION,ID) )
ORA-00907: missing right parenthesis
CREATE OR REPLACE trigger_for_pk_odm_progress BEFORE INSERT ON
ODM_Progress_v1 FOR EACH ROW WHEN (new.ID is null) BEGIN SELECT
seq_odm_for_pk.nextval INTO :new.ID FROM DUAL; --
:new.PK_ODMProgress := seq_odm_for_pk.nextval; END;
ORA-00922: missing or invalid option
Can you please help me unravel this (to me, complete) mystery?
The final application should at least contain 1 table with primary key and sequence (created from scratch, see above) and have at least 2 pages, one is for data input, the other is for data display with use tabs or navigation menu.
Thank you in advance!
That code looks OK, more or less. Here you go:
SQL> CREATE SEQUENCE seq_odm_for_pk
2 START WITH 1
3 INCREMENT BY 1
4 CACHE 100;
Sequence created.
Table: I'm on 11g which doesn't support auto-incremented columns, so I removed that clause:
SQL> CREATE TABLE ODM_Progress_v1 (
2 -- General Details:
3 ID int NOT NULL AUTO_INCREMENT,
4 TRAINEE varchar(50) NOT NULL, --1
5 COACH varchar(50) NOT NULL, --2
6 STATUS varchar(50) NOT NULL, --3
7 REGION varchar(5) NOT NULL, --4
8 -- Actions:
9 ACTION_TAKEN varchar(100) NOT NULL, --5
10 ACTION_DETAILS varchar(250), --6
11 ACTIONED_BY varchar(50) NOT NULL, --7
12 ACTIONED_DATE DATE NOT NULL, --8
13 -- Constraints that perform checks for each column:
14 CONSTRAINT CHK_GeneralDetails CHECK (TRAINEE!=COACH AND (STATUS IN('New', 'In Progress', 'Completed')) AND (REG
ION IN('EMEA', 'APAC', 'AMER'))),
15 -- Set Primary Key (Trainee+Coach):
16 CONSTRAINT PK_ODMProgress PRIMARY KEY (TRAINEE,REGION,ID)
17 );
ID int NOT NULL AUTO_INCREMENT,
*
ERROR at line 3:
ORA-00907: missing right parenthesis
SQL> l3
3* ID int NOT NULL AUTO_INCREMENT,
SQL> c/auto_increment//
3* ID int NOT NULL ,
SQL> /
Table created.
Trigger contains an error in line #1: it is not "trigger_for" but "trigger for" (no underscore):
SQL> CREATE trigger_for_pk_odm_progress
2 BEFORE INSERT ON ODM_Progress_v1
3 FOR EACH ROW
4 WHEN (new.ID is null)
5 BEGIN
6 select seq_odm_for_pk.nextval into :new.ID from DUAL;
7 -- :new.PK_ODMProgress := seq_odm_for_pk.nextval;
8 END;
9 /
CREATE trigger_for_pk_odm_progress
*
ERROR at line 1:
ORA-00901: invalid CREATE command
SQL> l1
1* CREATE trigger_for_pk_odm_progress
SQL> c/er_/er /
1* CREATE trigger for_pk_odm_progress
SQL> l
1 CREATE trigger for_pk_odm_progress
2 BEFORE INSERT ON ODM_Progress_v1
3 FOR EACH ROW
4 WHEN (new.ID is null)
5 BEGIN
6 select seq_odm_for_pk.nextval into :new.ID from DUAL;
7 -- :new.PK_ODMProgress := seq_odm_for_pk.nextval;
8* END;
SQL> /
Trigger created.
SQL>
So:
sequence is OK, but - for vast majority of cases - a simple create sequence seq_odm_for_pk; is enough
for CREATE TABLE remove AUTO_INCREMENT (if you aren't on 12c)
trigger: remove underscore
Now, depending on where you executed those commands, you might have got errors. If you ran them in Apex SQL Workshop, run them one-by-one (and keep only one command in the window). Doing so, it should be OK.
Also, I've noticed that you used VARCHAR datatype - switch to VARCHAR2.
Finally, there's no use in constraining primary key columns with the NOT NULL clause - primary key will enforce it by default.
As of Apex itself: the way you described it, you should create an Interactive Report; the Wizard will create a report (to view data), along with a form (to insert/modify/delete data).
The query you ran in your script is not the same as you posted in your code, as can be read in error text.
Code for creating your sequence as you wrote it in your code should be fine:
CREATE SEQUENCE seq_odm_for_pk
START WITH 1
INCREMENT BY 1
CACHE 100;
As of 11g you cannot use AUTO_INCREMENT in Oracle when creating table. It is not even necessary since you're having a trigger populating your :new.ID with nextval from sequence. So, in your CREATE TABLE remove AUTO_INCREMENT for your ID and everything should be fine.
While creating a trigger you omitted TRIGGER keyword (CREATE OR REPLACE TRIGGER trigger_for_pk_odm_progress).
Also, I'm not sure if you did or did not put END; at the end of your create trigger command. if not, put it.
I hope that helped :)
I have a table called Absence which records periods of Staff absence from work
CREATE TABLE Absence
(
absence_id_pk varchar(6) NOT NULL,
staff_id_fk varchar(6),
start_date date,
end_date date,
reason varchar(30),
PRIMARY KEY (absence_id_pk),
FOREIGN KEY (staff_id_fk) REFERENCES Full_Time_Employee(staff_id_fk)
);
and I have created a view to count the total number of days an employee has been absent like so:
CREATE VIEW employee_absence
AS
SELECT staff_id_pk,
staff.first_name,
staff.last_name,
SUM(end_date -start_date) AS "Total Days Absent"
FROM Staff, Absence
WHERE Absence.staff_id_fk = Staff.staff_id_pk
GROUP BY staff_id_pk, staff.first_name, staff.last_name
ORDER BY staff_id_pk;
I am new to Triggers and what I want to have is a Trigger that prints out a message to the screen when a Staff's total days absent > 20 days. Being completely new to Triggers, I don't have much idea how to go about this.
Any help or ideas would be greatly appreciated!
You could do it in either of the two ways:
Check constraint on the base table.
Trigger on the base table
I would chose the check constraint over the trigger, I would simply not allow an employee to enter record into the table if his absence is more than 20 days.
CHECK constraint
SQL> DROP TABLE absence PURGE;
Table dropped.
SQL>
SQL> CREATE TABLE Absence
2 (
3 absence_id_pk varchar(6) NOT NULL,
4 staff_id_fk varchar(6),
5 start_date date,
6 end_date date,
7 reason varchar(30),
8 PRIMARY KEY (absence_id_pk)
9 );
Table created.
SQL>
SQL> ALTER TABLE Absence ADD CONSTRAINT chk CHECK(end_date - start_date <= 20);
Table altered.
SQL>
SQL> INSERT INTO absence(absence_id_pk, start_date, end_date) VALUES(1, SYSDATE -20, SYSDATE);
1 row created.
SQL> INSERT INTO absence(absence_id_pk, start_date, end_date) VALUES(2, SYSDATE -21, SYSDATE);
INSERT INTO absence(absence_id_pk, start_date, end_date) VALUES(2, SYSDATE -21, SYSDATE)
*
ERROR at line 1:
ORA-02290: check constraint (LALIT.CHK) violated
SQL>
TRIGGER appraoch
SQL> DROP TABLE absence PURGE;
Table dropped.
SQL>
SQL> CREATE TABLE Absence
2 (
3 absence_id_pk varchar(6) NOT NULL,
4 staff_id_fk varchar(6),
5 start_date date,
6 end_date date,
7 reason varchar(30),
8 PRIMARY KEY (absence_id_pk)
9 );
Table created.
SQL> CREATE OR REPLACE TRIGGER trg
2 BEFORE INSERT
3 ON absence
4 FOR EACH ROW
5 BEGIN
6 IF :NEW.end_date - :NEW.start_date > 20
7 THEN
8 RAISE_APPLICATION_ERROR(-20001, 'Total days absent are more than 20');
9 END IF;
10 END;
11 /
Trigger created.
SQL>
SQL> INSERT INTO absence(absence_id_pk, start_date, end_date) VALUES(1, SYSDATE -20, SYSDATE);
1 row created.
SQL> INSERT INTO absence(absence_id_pk, start_date, end_date) VALUES(2, SYSDATE -21, SYSDATE);
INSERT INTO absence(absence_id_pk, start_date, end_date) VALUES(2, SYSDATE -21, SYSDATE)
*
ERROR at line 1:
ORA-20001: Total days absent are more than 20
ORA-06512: at "LALIT.TRG", line 4
ORA-04088: error during execution of trigger 'LALIT.TRG'
SQL>
If you still want to allow the insert, however just display message. Then, remove the RAISE_APPLICATION_ERROR and put a DBMS_OUTPUT instead.
SQL> DROP TABLE absence PURGE;
Table dropped.
SQL>
SQL> CREATE TABLE Absence
2 (
3 absence_id_pk varchar(6) NOT NULL,
4 staff_id_fk varchar(6),
5 start_date date,
6 end_date date,
7 reason varchar(30),
8 PRIMARY KEY (absence_id_pk)
9 );
Table created.
SQL> CREATE OR REPLACE TRIGGER trg
2 BEFORE INSERT
3 ON absence
4 FOR EACH ROW
5 BEGIN
6 IF :NEW.end_date - :NEW.start_date > 20
7 THEN
8 DBMS_OUTPUT.PUT_LINE('Total days absent are more than 20');
9 END IF;
10 END;
11 /
Trigger created.
SQL>
SQL> INSERT INTO absence(absence_id_pk, start_date, end_date) VALUES(1, SYSDATE -20, SYSDATE);
1 row created.
SQL> INSERT INTO absence(absence_id_pk, start_date, end_date) VALUES(2, SYSDATE -21, SYSDATE);
Total days absent are more than 20
1 row created.
SQL>
There are several problems with your solution.
Never use DBMS_OUTPUT in a trigger. This may work fine when you write it and test from your IDE, but it generally will not result in any messages getting out to any application. Triggers write to log tables.
Your view trigger only catches the problem on the next insert after the problem happens. This could be days or months afterward and absences could have become much larger than 20 by then.
There is really no reason to perform an insert to the view. You can't insert a new absence using the view because the view doesn't expose the dates. Or are you performing an INSERT just to check the days absent? In that case, why don't you just query from the view where days > 20?
Your best course of action is to have the After Insert and Update trigger on the table itself check the number of days absent and write a log entry if the days > 20. This log entry can contain any information the trigger can obtain: if the operation was an Insert or Update, the employee this effects, the user who performed the operation, the date and time the operation was performed, the number of absent days before the operation took affect, the number of absent days after the operation took affect and so on.
A scheduled job could check the log table and do something (send email, whatever) when it comes across new entries.
I'm doing a method that inserts into the table which has a unique column. What I don't know is if I can access the insert value that made the insert fail.
For example:
table1(id,name, phone);
name is unique.
insert (1,a,123);
insert (2,a,1234);
What I want is when I do the second insert I to return the id value '1' without having to recur to a query.
Thank you in advance.
From oracle 10g r2 you can use log errors clause of insert command to log errors in a separate table. Here is an example:
SQL> create table test_table(
2 id number primary key,
3 col1 varchar2(7)
4 )
5 ;
Table created
-- creates a table for logging errors (table name will be prefaced with err$_)
SQL> begin dbms_errlog.create_error_log('TEST_TABLE'); end;
2 /
PL/SQL procedure successfully completed
-- violates primary key constraint
SQL> insert into test_table(id, col1)
2 ( select 1, level
3 from dual
4 connect by level <= 3)
5 log errors reject limit unlimited;
1 row inserted
SQL> commit;
SQL> select * from test_table;
ID COL1
---------- -------
1 1
SQL> select * from err$_test_table;
ORA_ERR_NUMBER$ ORA_ERR_MESG$ ORA_ERR_ROWID$ ORA_ERR_OPTYP$ ORA_ERR_TAG$ ID COL1
--------------- ------------------------------------------------------------------------------------------------------------
1 ORA-00001: unique constraint (HR.SYS_C008315) violated I 1 2
1 ORA-00001: unique constraint (HR.SYS_C008315) violated I 1 3
maybe you can write a trigger(before insert) on your table, on which insert about to happen. In this you can check if the column value(name) already exists in table.
In case it does you may insert this duplicate record in another table for further reference
Another approach is to write the insert in a procedure where the name may be checked and the duplicate name could be stored in a table.
Hope it helps