Oracle composite key deletion very slow - oracle

I have one table with a composite key (REGION) and another table (CITY) that references that table. Inserts, queries, and individual deletions work quickly. The problem is that when I try to bulk-delete the contents of CITY using sqlplus, delete from CITY, it takes forever. This table will have ~400,000 entries and it takes 15-20 minutes just to delete 50,000 entries. Here is my setup using Oracle 11:
create table COUNTRY
(
id varchar2(32) NOT NULL -- PK
...
);
create table REGION -- about 4000 entries
(
country varchar2(32) NOT NULL -- PK, FK to COUNTRY
regionCode char(2) NOT NULL -- PK
...
);
create table CITY -- about 400,000 entries
(
id number NOT NULL -- PK
country varchar2(32) NOT NULL -- FK to COUNTRY
regionCountry varchar2(32) NULL -- FK to REGION
regionCode char(2) NULL -- FK to REGION
...
);
create table LOCATION -- about 2,500,000 entries
(
id varchar2(32) NOT NULL -- PK
country varchar2(32) NOT NULL -- FK to COUNTRY
city number NULL -- FK to CITY
...
);
ALTER TABLE COUNTRY ADD CONSTRAINT PK_COUNTRY PRIMARY KEY (id) USING INDEX;
ALTER TABLE REGION ADD CONSTRAINT PK_REGION PRIMARY KEY (country, regionCode) USING INDEX;
ALTER TABLE CITY ADD CONSTRAINT PK_CITY PRIMARY KEY (id) USING INDEX;
ALTER TABLE IPGeoLoc ADD CONSTRAINT PK_LOCATION PRIMARY KEY (id) USING INDEX;
ALTER TABLE REGION ADD CONSTRAINT FK_REGION_COUNTRY
FOREIGN KEY (country) REFERENCES COUNTRY (id);
ALTER TABLE CITY ADD CONSTRAINT FK_CITY_COUNTRY
FOREIGN KEY (country) REFERENCES COUNTRY (id);
ALTER TABLE CITY ADD CONSTRAINT FK_CITY_REGION
FOREIGN KEY (regionCountry, regionCode) REFERENCES REGION (country, regionCode);
ALTER TABLE LOCATION ADD CONSTRAINT FK_LOCATION_COUNTRY
FOREIGN KEY (country) REFERENCES COUNTRY (id);
ALTER TABLE LOCATION ADD CONSTRAINT FK_LOCATION_CITY
FOREIGN KEY (city) REFERENCES CITY (id);
The varchar2(32) fields are GUIDs. I know I should not use GUIDs as a PK but I cannot change that unless I have proof that this is the problem.
I can bulk-delete entries from LOCATION with no problem, 300,000 in a couple of seconds, so this leads me to believe it is the composite key that is giving me trouble.
The secondary issue is that I currently have two country columns in the CITY table - one linked directly to COUNTRY and the other linked as part of the composite key to REGION. I know how I would do this in code and only have one country column but I have to use Hibernate. This works the way it is except for the delete problem so I can't change it unless I can prove this is causing an issue. I'm using sqlplus to try the deletions so I know Hibernate is not causing the delete problem.

My wager is that the problem has nothing to do with the presence of a composite key and everything to do with an unindexed foreign key.
Unless you've omitted it from your question, the CITY column in the LOCATION table is not indexed. That means that every time you try to delete a row from CITY, Oracle has to do a full table scan on the LOCATION table looking for rows in LOCATION that would be orphaned in order to enforce the foreign key constraint. In general, if you want to ever delete from the parent, the foreign key in the child table needs to be indexed. So LOCATION should have indexes on both CITY and COUNTRY, the CITY table should have indexes on COUNTRY and (regionCountry, regionCode), etc.
Even if all the rows from LOCATION have been deleted, if Oracle has to do a full table scan on LOCATION, it has to read up to the high water mark of the table. If the table previously had 2.5 million rows and you just did a DELETE, you would still have to read however many blocks were required to store those 2.5 million rows every time you delete a row from CITY.
You can test whether my hunch is correct in a few different ways
You can index the CITY column in the LOCATION table.
You can drop the foreign key constraint on LOCATION that references the CITY table.
You can truncate the LOCATION table instead of deleting the rows so that the high water mark gets reset and a table scan will take much less time.

Related

Oracle Partial Foreign Key that is not unique

I have some problems with Oracle Foreign Composite keys. I have an application that is somewhat big (you know, 5000+ tables, that kind of thing) and we store some 'static' (it actually can change) data into some set of tables. This data is referenced by a lot of the tables through the database, so it worked like this:
TABLE StaticData
ID(PK) Data
1 StaticData1
2 StaticData2
...
n StaticDataN
TABLE TypicalTable
ID(PK) StaticDataID(FK to StaticData)
1 1
2 1
3 7
4 2
...
n n
And all was well in Wonderland.
But some changes of spec, and some meetings with the client after, we were tasked with having different 'versions' of the data ready to replace the static data when some time arrives. Last part was easy, we can create jobs that will check every day/week for a date and change the data, but we will have to maintain older and newer versions of the data... in the same table. So now StaticData looks like:
TABLE StaticData
ID(PK) Data KickInDate(Also PK)
1 StaticData1.1 01/01/1900
1 StaticData1.2 10/07/2014
1 StaticData1.3 12/12/2015
2 StaticData2.1 01/01/1900
...
n StaticDataN.1 01/01/1900
And of course all integrity reference has gone off the board. And of course, since I cannot put a UNIQUE constraint in the ID, I cannot keep the foreign keys.
I have searched the net for a solution for this (less restrictive kind of foreign keys) and most of the time the solution is to use triggers checking BEFORE INSERT|UPDATE|DELETE
But that will be kind of a very, very, very big job.
So I ask, Do I have other solutions?
Is there any way to tell Oracle to reference another column of another table even thought is not UNIQUE? (it will definitely be NOT NULL).
Primary keys in Oracle can have duplicates. Primary keys can be built with non-unique indexes and existing values can be excluded by creating the constraint with NOVALIDATE. It is a rarely used feature, will confuse people, and is not a clean solution. But in the real world sometimes data isn't clean and there's no time for the perfect solution.
Sample schema and data.
create table staticData
(
id number not null,
data varchar2(100),
constraint staticData_pk primary key (id)
);
create table typicalTable
(
id number not null,
staticDataID number,
constraint typicalTable_pk primary key (id),
constraint typicalTable_fk foreign key (staticDataID)
references staticData(id)
);
insert into staticData values (1, 'StaticData1');
insert into staticData values (2, 'StaticData2');
insert into typicalTable values(1, 1);
insert into typicalTable values(2, 1);
Process to drop constraints, add duplicate data, and re-enable constraints.
--Drop constraints.
alter table typicalTable drop constraint typicalTable_fk;
alter table staticData drop constraint staticData_pk;
--Add semi-duplicate data.
insert into staticData values (1, 'StaticData1.2');
--Use a non-unique index to build a NOVALIDATE primary key.
create index staticData_pk on staticData(id);
alter table staticData add constraint staticData_pk primary key (id) novalidate;
alter table typicalTable add constraint typicalTable_fk foreign key(staticDataID)
references staticData(id);
No, the target column(s) must be unique, that's the whole idea. However, you can propagate an additional version column from StaticData to TypicalTable:
CREATE TABLE StaticData (
id NUMBER,
version NUMBER,
col1 ... coln,
PRIMARY KEY (id,version)
);
CREATE TABLE TypicalTable (
StaticDataID NUMBER,
version NUMBER,
colx ... coly,
FOREIGN KEY (StaticDataID, version) REFERENCES StaticData(id, version)
);

Not able to add a new column to the existing primary key

I have a primary key PK1 for the table TABLE1. Need to add one more column to the existing primary key.
I was tying the following alter script
ALTER TABLE TABLE1
ADD CONSTRAINT "PRIMARYKEYS" PRIMARY KEY
("PK1",
"PK2");
Error report:
SQL Error: ORA-02260: table can have only one primary key
02260. 00000 - "table can have only one primary key"
*Cause: Self-evident.
*Action: Remove the extra primary key.
How can I add one more column to the primary key, without affecting the data(data has been verified , there are no duplication.)
If you have concern about new data that violates PK can be added during the time interval when the old PK dropped, but the new one is not created, you can create a unique index first :
CREATE UNIQUE INDEX IDXU_TABLE1_PK ON TABLE1(PK1,PK2);
ALTER TABLE TABLE1 DROP CONSTRAINT [old_pk_constraint_name] ;
ALTER TABLE TABLE1 ADD CONSTRAINT "PRIMARYKEYS" PRIMARY KEY
(PK1,PK2) USING INDEX IDXU_TABLE1_PK;
Another option is to keep index associated with the old PK constraint until new PK is created :
ALTER TABLE TABLE1 DROP CONSTRAINT [old_pk_constraint_name] KEEP INDEX;
ALTER TABLE TABLE1 ADD CONSTRAINT "PRIMARYKEYS" PRIMARY KEY
(PK1,PK2) ;
DROP INDEX [name of unique index associated with the old PK constraint];
You need to drop old primary key first.

FK on a single column referencing a column from composite PK

Not able to create /find the logic to apply FK on a column in child table referencing a column from composite PK of parent table.
create table product(prod_id number,
prod_name varchar2(20),
price number,
constraint PK12 primary key(prod_id,prod_name));
Table created.
create table purchase(prod_id number,
purchase_price number,
constraint FK12 foreign key(prod_id) references product(prod_id));
create table purchase(prod_id number,
purchase_price number,
constraint FK12 foreign key(prod_id) references product(prod_id))
ERROR at line 1:
ORA-02270: no matching unique or primary key for this column-list
Kinldy suggest how i can incorporate this logic.
Thanks.
You can't.
As the error says there's no matching primary key for that column list; you must have one. You have three options:
Remove PROD_NAME from the primary key of PRODUCT. On the face of it this seems like the logical solution, if this is not required in order to make the primary key unique.
Add PROD_NAME to the PURCHASE table.
Create a unique index on PURCHASE.PROD_ID. This seems excessive if it would be a primary key candidate anyway.
I suspect that this is not unique to Oracle. Considering you have a composite primary key in the referenced table, that implies that only one of the columns comprising the composite key is not enough to uniquely identify the record in that table. Therefore, it's impossible to reference only a single column of the primary key in a foreign key relationship that's one-to-many (e.g. one record in the referenced table can have many records in the referencing table--the one with the FK). However, if the relationship to be established is many-to-many, this may be possible.
HTH.

Bidirectional Foreign Keys Design

Say there are two tables, Company and Employee. Employee has a foreign key to Company and Company has a foreign key to Employee. How should I insert and delete data into these tables without getting referential integrity errors?
COMPANIES
ID
NAME
CONTACT_EMPLOYEE_ID --FK
EMPLOYEES
ID
NAME
COMPANY_ID --FK
I imagine this is a fairly common problem. I have researched it but have been unable to find much information. Perhaps the problem comes under a more common name I am not aware of.
There are several methods available:
Is the CONTACT_EMPLOYEE_ID column nullable? If it is, just insert company, insert employee and then update the company record.
You could also set one of the constraints as deferrable. You could then set the constraint as deferred, insert both records and then commit.
There are generally 2 strategies:
Leave one of the FKs NULL-able (and then insert NULL into that table, insert row into other table and finally update the NULL).
Defer one of the FKs.
You could even leave both FKs NULL-able or deferrable (or even a combination of the two), so you can perform the insertion in both directions.
You could also consider placing all the EMPLOYEES fields into COMPANIES.
Apart from the other suggestions already made, which are good (make one of the FK columns NULLable, or make the FK constraint deferrable), another one is to make the NOT NULL constraint deferrable, e.g.:
create table COMPANIES (
ID number not null,
NAME varchar2(100) not null,
CONTACT_EMPLOYEE_ID number,
constraint contact_not_null
check (CONTACT_EMPLOYEE_ID not null)
deferrable
initially deferred
);
Now, you can insert a row with NULL for the employee id, insert the employee, then update companies.contact_employee_id with the new employee ID, then COMMIT.

does foreign key always reference to a unique key in another table?

Is it not possible that foreign key(single column) in a child table references to a parent key which has some duplicate values?
By the SQL standard, a foreign key must reference either the primary key or a unique key of the parent table. If the primary key has multiple columns, the foreign key must have the same number and order of columns. Therefore the foreign key references a unique row in the parent table; there can be no duplicates.
Re your comment:
If T.A is a primary key, then no you can't have any duplicates. Any primary key must be unique and non-null. Therefore if the child table has a foreign key referencing the parent's primary key, it must match a non-null, unique value, and therefore references exactly one row in the parent table. In this case you can't make a child row that references multiple parent rows.
You can create a child row whose foreign key column is NULL, in which case it references no row in the parent table.
No, it is not possible.
When you define a foreign key constraint on a table, it means there is only one corresponding key on the foreign table. If multiples existed on the foreign table which one would be meant?
Wikipedia has this definition on the Foreign key entry:
A foreign key is a field in a relational table that matches a candidate key of another table
Candidate keys are unique within a table.
Yes, it is possible for a foreign key to reference a column with duplicate values.
This can happen if the primary key uses a non-unique index and is not validated when it is created. (But I have never seen a situation like this in real life. As #Bill Karwin pointed out, it would be very confusing. So this may not be a situation you really need to worry about.)
--Create a table with two duplicate rows
create table test1(a number);
insert into test1 values(1);
insert into test1 values(1);
commit;
--Create a non-unique index
create index test1_index on test1(a);
--Use the non-unique index for the primary key, do not validate
alter table test1 add constraint test1_pk primary key (a)
using index test1_index novalidate;
--Build another table with a foreign key to TABLE1
create table test2(a number,
constraint test2_fk foreign key (a) references test1(a));
--Inserting a value that refers to the duplicate value still works.
insert into test2 values(1);
commit;
--The foreign key still works:
--ORA-02291: integrity constraint (TEST2_FK) violated - parent key not found
insert into test2 values(2);
--The primary key works as expected, but only for new values:
--ORA-00001: unique constraint (TEST1_PK) violated
insert into test1 values(1);

Resources