How to add new foreign key column with default value to an existing table with data - sql

I'm trying to achieve this:
Table A that has almost 100 records - to add a new column (eg: ID
with default value 0)
Creating a new table B for which ID is PK
I tried this:
added ID column to Table A as null
updated the old values as 0 for existing rows
created Table B with ID as PK
made ID column as not null in Table A
tried to add FK constraint to Table A
And got this error:
ORA-02298: cannot validate parent keys not found.
What am I missing here? Also, I cannot delete child records in Table A

According to this Ask TOM article : Constraint ENABLE/ DISABLE
You can enable or disable integrity constraints at the table level
using the CREATE TABLE or ALTER TABLE statement. You can also set
constraints to VALIDATE or NOVALIDATE, in any combination with ENABLE
or DISABLE
And from a related artice by Tim Hall: ORACLE-BASE
ENABLE VALIDATE is the same as ENABLE. The constraint is checked and is guaranteed to hold for all rows.
ENABLE NOVALIDATE means the constraint is checked for new or modified rows, but existing data may violate the constraint.
DISABLE NOVALIDATE is the same as DISABLE. The constraint is not checked so data may violate the constraint.
DISABLE VALIDATE means the constraint is not checked but disallows any modification of the constrained columns.
So, to answer your question
"How to add new foreign key column with default value to an existing
table with data"
you may use option 2 ONLY if you want that the constraint is checked, but it does not have to be
true for all rows. This allows existing rows to violate the constraint, while ensuring
that all new or modified rows are valid.
ALTER TABLE A ADD FOREIGN KEY (ID) REFERENCES B(ID) ENABLE NOVALIDATE;

Starting scenario - CHILD_TABLE with 100 rows and no link to PARENT_TABLE:
create table parent_table (id integer constraint parent_pk primary key);
create table child_table (somecolumn integer);
insert into child_table select rownum from dual connect by rownum <= 100;
Now we want to link CHILD_TABLE to PARENT_TABLE with the default parent set to 0.
Add a value to PARENT_TABLE so that CHILD_TABLE can refer to it:
insert into parent_table (id) values (0);
If you don't add this value, it's like having a VEHICLES table with the default manufacturer as 'HONDA', but not defining 'HONDA' in the MANUFACTURERS table. The whole point of foreign keys is to stop you doing that.
Now add a foreign key to CHILD_TABLE (default on null is new in Oracle 12.1, but the plain default also works, however only recent versions of Oracle apply the default when adding a new column - I forget which version exactly.)
alter table child_table
add parent_id integer
default on null 0
constraint child_parent_fk references parent_table ;
Check what we have in CHILD_TABLE now:
select * from child_table;
SOMECOLUMN PARENT_ID
---------- ---------
1 0
2 0
3 0
4 0
5 0
6 0
7 0
8 0
9 0
10 0
11 0
...
100 rows selected
Check the status of the new FK:
select fk.constraint_name, fkc.column_name, fk.status
, pk.table_name, fk.r_constraint_name
from user_constraints fk
join user_constraints pk
on pk.constraint_name = fk.r_constraint_name
and pk.owner = fk.r_owner
join user_cons_columns fkc
on fkc.table_name = fk.table_name
and fkc.constraint_name = fk.constraint_name
where fk.table_name = 'CHILD_TABLE'
and fk.constraint_type = 'R'
and pk.constraint_type = 'P';
CONSTRAINT_NAME COLUMN_NAME STATUS TABLE_NAME R_CONSTRAINT_NAME
------------------ ------------------- -------- ------------------- -----------------
CHILD_PARENT_FK PARENT_ID ENABLED PARENT_TABLE PARENT_PK

Related

Prevent row insertion if foreign_key is not null in the referenced row

Simple table with primary key as the first column and foreign key referencing the same table as the second column.
1 | null
2 | 1
I want to prevent insertion of a row whose fk would reference a row whose fk is not null. E.g. insertion of the row below should be prevented:
3 | 2
How can it be achieved? I'v tried the following
ALTER TABLE t
ADD CONSTRAINT t_check
CHECK (
fk not in (select t.pk from t1 t where t.pk = fk and t.fk is not null)
);
but got
ERROR: cannot use subquery in check constraint
as expected.

NULL values for referential_constraints.unique_constraint_* columns in information schema

In Postgres 10 I have declared the following:
create table test_abc (
pk integer not null,
id integer not NULL,
id2 integer not null,
PRIMARY KEY (pk)
);
CREATE UNIQUE INDEX test_abc_ids ON test_abc(id,id2);
And then a second table with a FK referencing the first:
create table test_def (
id integer not null,
abc_id integer,
abc_id2 integer,
PRIMARY KEY (id),
FOREIGN KEY (abc_id,abc_id2) references test_abc(id,id2)
);
Now consider the output of this query:
SELECT unique_constraint_catalog, unique_constraint_schema, unique_constraint_name
FROM information_schema.referential_constraints r
WHERE r.constraint_name = 'test_def_abc_id_fkey'
----------------------
NULL NULL NULL
All unique_constraint_* columns have a null value.
From the Postgres documentation it seems these meta columns should contain the
name of the [object] that contains the unique or primary key constraint that the foreign key constraint references (always the current database)
Question:
I'm surely in the same database, and the unique index declared on test_abc table is a unique constraint (otherwise I wouldn't be able to declare the FK to begin with), so why are these columns empty?
I'm using the referential_constraints with some joins to get information about the columns referenced by my foreign keys, but this way I'm missing all those where the unique constraint is set with an index.
Test setup
You assume the constraint name test_def_abc_id_fkey, the default name resulting from your setup in Postgres 11 or older. Worth noting, though, that default names have been improved for Postgres 12, where the same setup results in test_def_abc_id_abc_id2_fkey. The release notes for Postgres 12:
Use all key columns' names when selecting default constraint names for foreign keys (Peter Eisentraut)
Previously, only the first column name was included in the constraint name, resulting in ambiguity for multi-column foreign keys.
See:
db<>fiddle here
So let's use the explicit name test_def_abc_fkey for the FK constraint to avoid confusion:
CREATE TABLE test_abc (
pk int PRIMARY KEY
, id int NOT NULL
, id2 int NOT NULL
);
CREATE UNIQUE INDEX test_abc_ids ON test_abc(id,id2);
CREATE TABLE test_def (
id int PRIMARY KEY
, abc_id int
, abc_id2 int
, CONSTRAINT test_def_abc_fkey -- !
FOREIGN KEY (abc_id,abc_id2) REFERENCES test_abc(id,id2)
);
And that works in Postgres 9.5 - Postgres 12, even in Postgres 9.3.
(I had been under the wrong impression an actual constraint would be required.)
Answer
Your observation from querying the information schema holds:
SELECT *
FROM information_schema.referential_constraints
WHERE constraint_name = 'test_def_abc_fkey'; -- unequivocal name
We get a row, but the three fields unique_constraint_catalog, unique_constraint_schema and unique_constraint_name are NULL.
The explanation seems simple. Those columns describe, as the manual puts it:
... the unique or primary key constraint that the foreign key constraint references
But there is no UNIQUE constraint, just a UNIQUE index. A UNIQUE constraint is implemented using a UNIQUE index in Postgres. Constraints are defined by the SQL standard, indexes are implementation details. There are differences like the one you discovered. Related:
How does PostgreSQL enforce the UNIQUE constraint / what type of index does it use?
The same test with an actual UNIQUE constraint shows data as expected:
db<>fiddle here
So this seems to make sense. Especially since the information schema is also defined by the SQL standards committee and indexes are not standardized, only constraints. (No index information in information schema views.)
All clear? Not quite.
However
There is another information schema view key_column_usage. Its last column is described as:
position_in_unique_constraint ... For a foreign-key constraint, ordinal position of the referenced column within its unique constraint (count starts at 1); otherwise null
Bold emphasis mine. Here, the ordinal position of the column in the index is listed anyway:
SELECT *
FROM information_schema.key_column_usage
WHERE constraint_name = 'test_def_abc_fkey';
See:
db<>fiddle here
Seems inconsistent.
What's worse, the manual claims that an actual PRIMARY KEY or UNIQUE constraint would be required for the creation of a FOREIGN KEY constraint:
A foreign key must reference columns that either are a primary key or
form a unique constraint. This means that the referenced columns
always have an index (the one underlying the primary key or unique
constraint); so checks on whether a referencing row has a match will
be efficient.
Seems to be a documentation bug? If nobody can point out where I am going wrong here, I'll file a bug report.
Related:
Postgres unique constraint vs index
Solution
I'm using the referential_constraints with some joins to get information about the columns referenced by my foreign keys, but this way I'm missing all those where the unique constraint is set with an index.
In Postgres, the system catalog is the actual source of truth. See:
Information schema vs. system catalogs
So you could use something like this (like I also added in the fiddle above):
SELECT c.conname
, c.conrelid::regclass AS fk_table, k1.fk_columns
, c.confrelid::regclass AS ref_table, k2.ref_key_columns
FROM pg_catalog.pg_constraint c
LEFT JOIN LATERAL (
SELECT ARRAY (
SELECT a.attname
FROM pg_catalog.pg_attribute a
, unnest(c.conkey) WITH ORDINALITY AS k(attnum, ord)
WHERE a.attrelid = c.conrelid
AND a.attnum = k.attnum
ORDER BY k.ord
) AS fk_columns
) k1 ON true
LEFT JOIN LATERAL (
SELECT ARRAY (
SELECT a.attname
FROM pg_catalog.pg_attribute a
, unnest(c.confkey) WITH ORDINALITY AS k(attnum, ord)
WHERE a.attrelid = c.confrelid
AND a.attnum = k.attnum
ORDER BY k.ord
) AS ref_key_columns
) k2 ON true
WHERE conname = 'test_def_abc_fkey';
Returns:
conname | fk_table | fk_columns | ref_table | ref_key_columns
:---------------- | :------- | :--------------- | :-------- | :--------------
test_def_abc_fkey | test_def | {abc_id,abc_id2} | test_abc | {id,id2}
Related:
Find the referenced table name using table, field and schema name
Find referenced field(s) of foreign key constraint
How can I find tables which reference a particular row via a foreign key?

Deleting row on Foreign key relation

I have table on which I have a foreign key constraint like below
ALTER TABLE [dbo].[Element] WITH CHECK
ADD CONSTRAINT [FK_Element_Band]
FOREIGN KEY([BandID]) REFERENCES [dbo].[Band] ([BandID])
GO
ALTER TABLE [dbo].[Element] CHECK CONSTRAINT [FK_Element_Band]
GO
Now I am trying to delete a row from the band table like this
DELETE FROM Band
WHERE TypeID = 21 AND BandUpperLimit = 10000 AND PID = 61
But I am getting a reference constraint error:
The DELETE statement conflicted with the REFERENCE constraint "FK_Element_Band". The conflict occurred in database "pricingModified", table "dbo.Element", column 'BandID'.
So this should happen if I have anything in the Elements table which is referencing Band table with the ID 21 but that's not the case since the SQL
SELECT *
FROM Element
WHERE BandID = 21
returns nothing.
Can someone please tell me why I am not able to delete row data from the parent table even though there is no reference present in the child table?
Thanks
Just specify ON DELETE CASCADE in your foreign key constraint. Then when you delete a row from the Band table it will automatically delete any child records in the Element table.
ALTER TABLE [dbo].[Element] WITH CHECK
ADD CONSTRAINT [FK_Element_Band]
FOREIGN KEY([BandID]) REFERENCES [dbo].[Band] ([BandID]) ON DELETE CASCADE
GO
Check with this query instead:
select b.*
from Element e
inner join Band b
on e.BandId = b.BandId
where b.TypeId = 21
and b.BandUpperLimit = 10000
and b.PID = 61
while creating child table
create table <child_table_name>
(
_______,
_______,
([FOREIGN KEY column_name]) [size of FOREIGN KEY column] references [parent table(primary key column_name)] on delete cascade
)
Using this you can easily delete record of fk using delete operation..
Try this, if it is successful then mark it positive...

Column value must exist in non primary column of another table

Suppose I have two tables:
TABLE_1
ID AGE_T1 NAME
1 5 A
2 23 B
3 5 C
4 9 D
TABLE_2
AGE_T2 FREQUENCY
5 2
9 1
23 1
How can I ensure that a AGE_T2 value must be one of the values of AGE_T1 (non-unique column of TABLE_1)? Need to mention that both TABLE_1 and TABLE_2 are physical tables (not any logical structure as VIEW).
Note: If AGE_T1 were a primary key then a foreign key constraint would be enough for AGE_T2. I also have a plan to use INSERT TRIGGER if there is no better solution.
Create a materialized view to contain only the distinct ages in TABLE_1:
CREATE MATERIALIZED VIEW LOG ON TABLE_1
WITH SEQUENCE, ROWID(AGE_T1)
INCLUDING NEW VALUES;
CREATE MATERIALIZED VIEW TABLE_1_MV
BUILD IMMEDIATE
REFRESH FAST ON COMMIT
AS SELECT DISTINCT AGE_T1
FROM TABLE_1;
ALTER TABLE TABLE_1_MV ADD CONSTRAINT t1_mv__age_t1__pk PRIMARY KEY ( AGE_T1 );
Then you can add a FOREIGN KEY on TABLE_2 referencing this as the primary key:
ALTER TABLE TABLE_2 ADD CONSTRAINT t2__age__fk FOREIGN KEY ( AGE_T2 )
REFERENCES TABLE_1_MV ( AGE_T1 );
I think this should work. Not tested.
It's also based on a materialized view; the MV must refresh on commit as in MT0's solution. Let the MV select only rows that violate your lookup condition - and let the MV have a check condition that always evaluates to FALSE, so that it will always reject the insert/update/delete if your original lookup condition is violated. You can make the "always false" constraint deferrable if needed.
This way you will only need one MV, and you won't need to maintain anything.
create materialized view TABLE_2_MV
build immediate
refresh fast on commit
as select age_t2
from table_2
where age_t2 not in (select age_t1 from table_1);
alter table TABLE_2_MV add constraint t2_mv_chk (0 != 0);
Good luck!

ORA-02437: cannot validate <name> - primary key violated

I have a table:
CREATE TABLE MY_TABLE (
MY_ID NUMBER NOT NULL,
COLUMN_1 NUMBER,
COLUMN_2 NUMBER
);
ALTER TABLE MY_TABLE ADD CONSTRAINT PK_FOO PRIMARY KEY (MY_ID);
at a later point, when executing the following sql, I get an error:
ALTER TABLE MY_TABLE DROP PRIMARY KEY DROP INDEX;
ALTER TABLE MY_TABLE ADD CONSTRAINT PK_FOO PRIMARY KEY (MY_ID)
ORA-02437: cannot validate PK_FOO - primary key violated
My table only contains 3 entries all with a different primary key which is also not null.
Anyone has an idea what this could be?
Thanks,
Peter
My table only contains 3 entries all with a different primary key which is also not null.
You must forgive a certain amount of scepticism on our part. Because that error definitely indicates a duplicate value.
What you need to do is use the exceptions clause. This will show you the ROWIDs of the records which are violating your constraint. You may need to create the target table: by default the script creates a table called EXCEPTIONS:
SQL> ALTER TABLE MY_TABLE ADD CONSTRAINT PK_FOO PRIMARY KEY (MY_ID);
ALTER TABLE MY_TABLE ADD CONSTRAINT PK_FOO PRIMARY KEY (MY_ID)
*
ERROR at line 1:
ORA-02437: cannot validate (APC.PK_FOO) - primary key violated
SQL> #%ORACLE_HOME%\rdbms\admin\utlexpt1.sql
Table created.
SQL> ALTER TABLE MY_TABLE ADD CONSTRAINT PK_FOO PRIMARY KEY (MY_ID)
2 exceptions into exceptions
3 /
ALTER TABLE MY_TABLE ADD CONSTRAINT PK_FOO PRIMARY KEY (MY_ID)
*
ERROR at line 1:
ORA-02437: cannot validate (APC.PK_FOO) - primary key violated
SQL> select * from exceptions
2 /
ROW_ID OWNER TABLE_NAME CONSTRAINT
------ ----- ---------- ----------
AABQXcAAEAAAXUPAAD APC MY_TABLE PK_FOO
AABQXcAAEAAAXUPAAB APC MY_TABLE PK_FOO
SQL>
Edit
You need to figure out what is different between your install code and the simplification you posted here. The chances are you have one or more INSERT statements which are accidentally executed more than once while the constraint is not in force. Adding the EXCEPTIONS INTO clause to your code might help you track it down.
from here
Cause: You tried to tried to enable a
primary key constraint, but the
columns in the primary key either
contained NULL values or duplicates..
The maim reason of violation of primary ki is the atrrbute you making is nullable or you have some dupliactes in table for that id or attribute
create table client_master
(
client_no varchar2(6),
client_name varchar2(20),
city varchar2(25),
state varchar2(15),
pin number(6),
balance_due number(10,2));
for e.g,
after inserting values
0008 Mukul NYCT NYC 199560 2500
0006 Rukmini bombay maharastra 100001 0
0007 Krishna mathura up 900050 100000
0008 Mukul NYCT NYC 199560 2500
or your id is nullable
so just MODIFY THE ATTRIBUTE is and set to NOT NULL
also remove duplicates if any
DELETE client_master
WHERE rowid NOT IN
(SELECT MAX(rowid)
FROM client_master
GROUP BY client_no,client_name,city,state,pin,balance_due);
then you we will be able to add primary key to client _no
Are 2 primary keys identical?
This error is usually thrown when you try to create/enable a primary key on a table
I'm upvoting APC's answer, but since you seem to have some problems implementing it, could you just post the results of this query:
select my_id, count(*) from my_table group by my_id having count(*) >1
This will give us (and you) some idea of the problematic keys.