trigger to delete from two tables simultaneously in sql oracle apex - sql

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>

Related

In SQL, can I combine two or more fields into one field during the same insert statement?

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.

Check for constraints before deletion

I have a very similar question to this one here, but it was asked 12 years ago and I'm sure things have changed since then.
Basically, I would like to be able to check for foreign key constraints before deleting. I don't want to just do a try/catch or rollback, because I'd like to present the information on the front end to the user to tell them what they need to do in order to be able to delete the item they're trying to remove.
I would like it to be able to continue to work going forward, if new constraints are added.
In a perfect world, I would like to be able to get back a list of primary keys from the rows in the other tables that are dependent on the row being deleted.
Well, why would you complicate things for users? As far as I understood, you'd want to inform them that the can't delete a master record until they delete detail records first. If that's so, why wouldn't you let the database do it for you (them, that is)? Hint: on delete cascade.
This is what you have now (a very simplified example):
SQL> create table tmaster
2 (id_mas number constraint pk_mas primary key);
Table created.
SQL> create table tdetail
2 (id_det number constraint pk_det primary key,
3 id_mas number constraint fk_det_mas references tmaster (id_mas));
Table created.
SQL> insert all
2 into tmaster values (1)
3 into tmaster values (2)
4 --
5 into tdetail values (100, 1) -- references master 1
6 into tdetail values (101, 1) -- references master 1
7 into tdetail values (200, 2) -- references master 2
8 select * from dual;
5 rows created.
Deleting master whose details exist won't work:
SQL> delete from tmaster where id_mas = 1;
delete from tmaster where id_mas = 1
*
ERROR at line 1:
ORA-02292: integrity constraint (SCOTT.FK_DET_MAS) violated - child record
found
SQL>
You'd have to
SQL> delete from tdetail where id_mas = 1;
2 rows deleted.
SQL> delete from tmaster where id_mas = 1;
1 row deleted.
SQL>
But, as I said, let the database work. Note line #4 in create table tdetail:
SQL> create table tmaster
2 (id_mas number constraint pk_mas primary key);
Table created.
SQL> create table tdetail
2 (id_det number constraint pk_det primary key,
3 id_mas number constraint fk_det_mas references tmaster (id_mas)
4 on delete cascade); --> this
Table created.
SQL> insert all
2 into tmaster values (1)
3 into tmaster values (2)
4 --
5 into tdetail values (100, 1) -- references master 1
6 into tdetail values (101, 1) -- references master 1
7 into tdetail values (200, 2) -- references master 2
8 select * from dual;
5 rows created.
OK, let's delete master (and master only):
SQL> delete from tmaster where id_mas = 1;
1 row deleted.
Whoa, it works! I don't have to do anything about it:
SQL> select * from tmaster;
ID_MAS
----------
2
SQL> select * from tdetail;
ID_DET ID_MAS
---------- ----------
200 2
SQL>

Generate data to datebase

I have a question about generating data to databese. Generally I don't have problem with it but I don't know how to generate correct foreign keys. Example: I have three tables: Factory, Worker and Product. Table Product has two foreign keys: to Factory and to Worker and table Worker has primary key to Worker and foreign key to Factory so if I generated data to Product first I have links between Factory and Worker so how I can genereta date to Worker now?
If foreign keys are enabled, then you must insert parent records first, child next.
SQL> create table factory
2 (id number primary key);
Table created.
SQL> create table worker
2 (id number primary key,
3 id_fact number references factory
4 );
Table created.
SQL> create table product
2 (id number primary key,
3 id_fact number references factory,
4 id_work number references worker
5 );
Table created.
SQL>
SQL> insert into factory values (1);
1 row created.
SQL> insert into worker values (100, 1);
1 row created.
SQL> insert into product values (1000, 1, 100);
1 row created.
SQL>
You can't - as you said - insert values into product first because parent row(s) doesn't exist yet:
SQL> rollback;
Rollback complete.
SQL> insert into product values (1000, 1, 100);
insert into product values (1000, 1, 100)
*
ERROR at line 1:
ORA-02291: integrity constraint (SCOTT.SYS_C007766) violated - parent key not
found
SQL>
If you created foreign key constraints to be deferrable, then you can insert rows in any order you want as Oracle will check integrity on commit:
SQL> create table factory
2 (id number primary key);
Table created.
SQL> create table worker
2 (id number primary key,
3 id_fact number references factory initially deferred deferrable
4 );
Table created.
SQL> create table product
2 (id number primary key,
3 id_fact number references factory initially deferred deferrable,
4 id_work number references worker initially deferred deferrable
5 );
Table created.
SQL> insert into product values (1000, 1, 100);
1 row created.
SQL> insert into factory values (1);
1 row created.
SQL> insert into worker values (100, 1);
1 row created.
SQL>
You need to generate data for the tables in the correct order: if a table is referenced by an FK then it needs to be populated before the table that holds the FK.
So you would need to generate data in the order:
Factory
Worker
Product

PL/SQL developer how to get the row that made the insert fail?

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

Oracle - How to enforce rules on relationships depending on attributes records (simple example)

In a school system I have 2 tables, one called Staff which holds records of all member of staff for a school, teachers, admin, cooks and cleaners etc. Then I have a second table called Course with a foreign key relating to Staff to state who is the Course leader, now I only want to allow teachers to be the Course Leader, i.e. a cook can't be, but am not sure how to restrict this on the database level.
Note : I asked a more complicated wrong question here - Oracle Unique Constraint - Trigger to check value of property in new relation
You could check this restriction within an after insert or update triger on curses tabelle.
CREATE or replace TRIGGER check_leader
AFTER INSERT OR UPDATE ON Course
FOR EACH ROW
declare
v_type varchar2(30);
BEGIN
select type into v_type from stuff where :NEW.leader_id = stuff.stuff_id;
if v_type != 'teacher' then
RAISE_APPLICATION_ERROR(-20000, 'course leader must be teacher');
end if;
end;
/
But you need another trigger on the staff table.
In the case of a change of stuff type (from teacher to cleaner ) It must be checked for the entries in curses table.
CREATE or replace TRIGGER check_courses
AFTER UPDATE ON STUFF
FOR EACH ROW
declare
v_num number;
BEGIN
if :OLD.type = 'teacher' and :NEW.type != 'teacher' then
select count(*) into v_num from curses where courses.leader_id = :NEW.stuff_id;
if v_num > 0 then
RAISE_APPLICATION_ERROR(-20000, 'there are courses assigned ');
end if;
end if;
end;
/
In this scenario I would revise the data model. I would take the generic table staff and add under it tables for each STAFF_TYPE: CATERERS, TEACHERS, ADMIN, etc. Then it is a simple matter to enforce a foreign key between COURSES and TEACHERS without the need for triggers.
This is a standard solution to this kind of problem. As you further investigate the requirements you'll find there will be similar foreign key issues with cooks and janitors. It is also likely that you will find there are attributes which teachers have that administrators lack. That's why separate tables for each type are useful. At the same time they all have things in common. That's why you need a table for the STAFF super type.
Of course, in proposing this answer I am echoing #JeffreyKemp's suggestion in your previous question. Well, as #RobVanWijk has borrowed mine, why not? :)
You could add another table named COURSE_LEADERS to track everyone who is currently able to lead a course, then have a relation from COURSE to COURSE_LEADERS, as well as a relation from COURSE_LEADER to STAFF. This way, a cook can only lead a course if they are in COURSE_LEADER, but if it's a course aboue cooking, maybe that's OK...? ;)
This method of course, means that there must be some way to add/remove staff members from the course_leader. If you want it to be automatic, you could have a trigger that inserts a record to COURSE_LEADER when a new STAFF is added if the staff is a teacher - assuming that it's possible to determine if a staff is a teacher when their record is inserted.
You can do it like APC said in the other thread.
Here is an example:
SQL> create table staff
2 ( id number(10) not null primary key
3 , name varchar2(10) not null
4 , staff_type varchar2(10) not null check (staff_type in ('TEACHER','COOK','ADMIN','CLEANER'))
5 , constraint staff_uk unique (id,staff_type)
6 )
7 /
Table created.
SQL> insert into staff values (1, 'ALAN', 'COOK')
2 /
1 row created.
SQL> insert into staff values (2, 'BOB', 'TEACHER')
2 /
1 row created.
SQL> create table course
2 ( id number(10) not null primary key
3 , name varchar2(30) not null
4 , staff_id number(10) not null
5 , staff_type varchar2(10) default 'TEACHER' not null
6 , constraint course_staff_fk
7 foreign key (staff_id,staff_type)
8 references staff (id,staff_type)
9 )
10 /
Table created.
SQL> insert into course (id,name,staff_id) values (1, 'Mathematics', 1)
2 /
insert into course (id,name,staff_id) values (1, 'Mathematics', 1)
*
ERROR at line 1:
ORA-02291: integrity constraint (SCHEMA.COURSE_STAFF_FK) violated - parent key not found
SQL> insert into course (id,name,staff_id) values (2, 'Physics', 2)
2 /
1 row created.
Regards,
Rob.
I don't see the point of doing this at database level, or, at least, complicate your life with triggers and so on.
Just create those objects:
a table named STAFF, that has a "type" field (type can be NULL or "COURSE LEADER")
a table named COURSE, that has a foreign key to STAFF
a view named COURSE_LEADERS, that is select * from STAFF where type="COURSE LEADER"
Then, use only COURSE and COURSE_LEADERS objects in your application when dealing with courses and course leaders. As simple as that.