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>
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.
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
In TOAD, I can see my table said doesn't have PK. But there is an unique constraint for the PK candidate.
I try to make the field PK but toad said there is already a constraint for it on the table.
And can't remove the constraint because said someone else depend on it.
So should I leave it like that. Or go the extra mile disable all dependencies remove the unique constraint and create a PK?
Let me try to explain it. As Eric said, unique key constraint will accept (many) nulls for the constrained column.
First, create a table with unique key constraint (the one you have now):
SQL> create table test (id number constraint uk_test unique, --> unique key constraint
2 name varchar2(20));
Table created.
SQL> -- This is the first record - no problem with it
SQL> insert into test (id, name) values (1, 'Little');
1 row created.
SQL> -- Uniqueness violated
SQL> insert into test (id, name) values (1, 'Foot');
insert into test (id, name) values (1, 'Foot')
*
ERROR at line 1:
ORA-00001: unique constraint (SCOTT.UK_TEST) violated
SQL> -- Let's insert some NULL values into the constrained column
SQL> insert into test (id, name) values (null, 'Foot');
1 row created.
SQL> insert into test (id, name) values (null, 'Big');
1 row created.
SQL> -- The result: not too pretty, eh?
SQL> select * From test;
ID NAME
---------- --------------------
1 Little
Foot
Big
A step further: apply a new, NOT NULL constraint to the unique key column, so that it "acts" as if it were a primary key:
SQL> delete from test;
3 rows deleted.
SQL> -- add NOT NULL constraint
SQL> alter table test modify id not null;
Table altered.
SQL> -- The first record is OK
SQL> insert into test (id, name) values (1, 'Little');
1 row created.
SQL> -- Uniqueness violated
SQL> insert into test (id, name) values (1, 'Foot');
insert into test (id, name) values (1, 'Foot')
*
ERROR at line 1:
ORA-00001: unique constraint (SCOTT.UK_TEST) violated
SQL> -- This worked previously, but won't any longer because of the NOT NULL constraint
SQL> insert into test (id, name) values (null, 'Foot');
insert into test (id, name) values (null, 'Foot')
*
ERROR at line 1:
ORA-01400: cannot insert NULL into ("SCOTT"."TEST"."ID")
Finally, to show that now it acts as if it were a primary key constraint:
SQL> delete from test;
1 row deleted.
SQL> -- Let's drop the unique key constraint
SQL> alter table test drop constraint uk_test;
Table altered.
SQL> -- Add the primary key constraint (no duplicates, no nulls)
SQL> alter table test add constraint pk_test primary key (id);
Table altered.
SQL> -- The first record is OK
SQL> insert into test (id, name) values (1, 'Little');
1 row created.
SQL> -- Uniqueness violated
SQL> insert into test (id, name) values (1, 'Foot');
insert into test (id, name) values (1, 'Foot')
*
ERROR at line 1:
ORA-00001: unique constraint (SCOTT.PK_TEST) violated
SQL> -- Not null violated
SQL> insert into test (id, name) values (null, 'Foot');
insert into test (id, name) values (null, 'Foot')
*
ERROR at line 1:
ORA-01400: cannot insert NULL into ("SCOTT"."TEST"."ID")
Basically, now you got the same errors as previously, i.e. primary key = unique key + NOT NULL.
You can't create a primary key if the column is already constrained by the unique key - you already know that.
As you can't drop the unique key constraint (because foreign keys reference it), apply the NOT NULL constraint to that column.
Alternatively,
drop all foreign key constraints
drop unique key
create primary key
recreate foreign key constraints
You can replace the UNIQUE constraint for a PK in three easy steps:
Just create the PK with the same column(s).
Then change all FKs to make them point to the PK constraint instead of the UNIQUE constraint.
Finally, drop the UNIQUE constraint.
Easy.
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