SQLite on delete cascade with two foreign keys - sql

If I have two tables and a third table that is the foreign key to the first and second table as follows:
CREATE TABLE A
(
name VARCHAR(255),
PRIMARY KEY(name)
);
CREATE TABLE B
(
number INT,
PRIMARY KEY(number)
);
CREATE TABLE C
(
cname VARCHAR(255),
cnumber INT,
PRIMARY KEY(cname, cnumber),
FOREIGN KEY(cname) REFERENCES A(name) ON DELETE CASCADE,
FOREIGN KEY(cnumber) REFERENCES B(number) ON DELETE CASCADE
);
INSERT INTO A values("John");
INSERT INTO A values("Sam");
INSERT INTO B values(1);
INSERT INTO B values(2);
INSERT INTO C values("John", 1);
INSERT INTO C values("John", 2);
INSERT INTO C values("Sam", 2);
I want to delete 1 such that (1) is deleted from B and the entry (John,1) is also deleted from C and (John) in A is also deleted.
Because there is DELETE CASCADE I should be able to do it but:
DELETE FROM B WHERE number = 1;
only removes 1 from B and (John,1) from C but (John) in A is not deleted.
So far I was only able to delete an entry from 1 table and have the other table with the foreign key to delete its entry, but I'm not sure how to delete another table that also references this table with the foreign key with JUST 1 query.

What you want to achieve is not possible using foreign keys, at least not the way you have set it up.
Table C has a foreign-key on A, so entries from C will be removed if the corresponding key in A is removed, not the other way around.
If you want to delete from A when entries in C are deleted, A would need to have a foreign key on C.
BUT this requires, that cname would be a unique key in C. Sqlite will allow you to set up and insert data, but it won't allow you to delete if there are multiple entries for something that is referenced as foreign-key.
PRAGMA foreign_key = true;
CREATE TABLE B
(
number INT,
PRIMARY KEY(number)
);
CREATE TABLE C
(
cname VARCHAR(255),
cnumber INT,
PRIMARY KEY(cname, cnumber),
FOREIGN KEY(cnumber) REFERENCES B(number) ON DELETE CASCADE
);
CREATE TABLE A
(
name VARCHAR(255),
PRIMARY KEY(name)
FOREIGN KEY(name) REFERENCES C(came) ON DELETE CASCADE
);
INSERT INTO B values(1);
INSERT INTO B values(2);
INSERT INTO C values("John", 1);
INSERT INTO C values("John", 2);
INSERT INTO C values("Sam", 2);
INSERT INTO A values("John");
INSERT INTO A values("Sam");
INSERT INTO C values("John", 1);
INSERT INTO C values("John", 2);
INSERT INTO C values("Sam", 2);
DELETE FROM B where number = 1;
Error: foreign key mismatch - "A" referencing "C"

Related

Create two foreign key constraints with on update/delete cascade

Example of database (SQL Server):
Table A
-colA (PK, int)
Table B
-ColB (FK, int) --> points to Table X
Table C
-ColAC (PK, FK, int not null) --> points to Table A
-ColBC (PK, FK, int not null) --> points to Table B
Table C's primary key is both ColAC and ColBC. Each column has foreign key that points to a different table. I need to have ON UPDATE/DELETE CASCADE constraints on both foreign keys for Table C. So when either Table A or B has changes then it is cascaded in Table C.
Table A and B are already in use. I can create Table C with only either one of the constraints but not both, this will return an error message. How do I create both FK constraints?
Example of SQL:
CREATE TABLE Table_C (
ColAC INT NOT NULL,
ColBC INT NOT NULL,
PRIMARY KEY (
ColAC,
ColBC
)
)
GO
ALTER TABLE Table_C WITH CHECK ADD FOREIGN KEY (ColAC)
REFERENCES Table_A (ColA)
ON UPDATE CASCADE
ON DELETE CASCADE
GO
--The second constraint will always fail (does not matter which one is first).
ALTER TABLE Table_C WITH CHECK ADD FOREIGN KEY (ColBC)
REFERENCES Table_B (ColB)
ON UPDATE CASCADE
ON DELETE CASCADE
GO
Error message:
Introducing FOREIGN KEY constraint .... on table 'Table_C' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
Could not create constraint or index. See previous errors.
You can go for two different tables, instead of single TableC
Table C1
-ColAC (PK, FK, int not null) --> points to Table A
Table C2
-ColBC (PK, FK, int not null) --> points to Table B
Now, you can have UNION ALL between these two tables
CREATE VIEW vw_TableC
AS
SELECT ColAC, 'TableA' as parent ... FROM TableC1
UNION ALL
SELECT ColBC, 'TableB' as parent ... FROM TableC2

Ensure combination of two columns existing in another table

How could one ensure the combination of two columns existing in another table?
CREATE TABLE a (
aid INTEGER PRIMARY KEY
);
CREATE TABLE b (
aid INTEGER REFERENCES a(aid),
bid INTEGER,
PRIMARY KEY (aid, bid)
);
CREATE TABLE c (
aid INTEGER REFERENCES a(aid),
cid INTEGER,
PRIMARY KEY (aid, cid)
);
-- combination of (aid, bid) must come from b
-- combination of (aid, cid) must come from c
CREATE TABLE d (
aid INTEGER REFERENCES a(aid),
bid INTEGER REFERENCES b(bid),
cid INTEGER REFERENCES c(bid),
PRIMARY KEY (aid, bid)
);
INSERT INTO a values(1);
INSERT INTO a values(2);
INSERT INTO b values(1, 1);
INSERT INTO b values(2, 2);
INSERT INTO c values(1, 1);
INSERT INTO c values(2, 2);
INSERT INTO d values(1, 2, 2);
Obviously the above "CREATE TABLE d" coding failed to ensure
combination of (aid, bid) must come from b
combination of (aid, cid) must come from c
Thanks.
In addition to Sticky bit and Vladimir Baranov's answers about using FOREIGN KEY and REFERENCES, for sqlite3 users:
"Foreign key constraints are disabled by default (for backwards compatibility), so must be enabled separately for each database connection." That means, the user has to open a database first, then run command "PRAGMA foreign_keys = ON;".
The pragma will not work if "the version of SQLite you are using does not support foreign keys (either because it is older than 3.6.19 or because it was compiled with SQLITE_OMIT_FOREIGN_KEY or SQLITE_OMIT_TRIGGER defined)".
You cannot define multicolumn foreign key constraints inline. Define them on their own.
CREATE TABLE d
(aid integer,
bid integer,
cid integer,
PRIMARY KEY (aid,
bid),
FOREIGN KEY (aid,
bid) REFERENCES b (aid,
bid),
FOREIGN KEY (aid,
cid) REFERENCES c (aid,
cid));
You didn't specify what RDBMS you are using. In SQL Server you can create a foreign key using more than one column. The key should point to columns that are in a unique or primary key and you do have the required primary keys in tables B and C.
The definitions of tables A, B and C can remain as they are in the question.
The definition of table D would look like this in SQL Server syntax. Here the foreign key constraints are created using separate ALTER TABLE statements. Different DBMS may use somewhat different syntax.
CREATE TABLE [dbo].[d](
[aid] [int] NULL,
[bid] [int] NULL,
[cid] [int] NULL
)
GO
ALTER TABLE [dbo].[d] WITH CHECK ADD CONSTRAINT [FK_d_b] FOREIGN KEY([aid], [bid])
REFERENCES [dbo].[b] ([aid], [bid])
GO
ALTER TABLE [dbo].[d] CHECK CONSTRAINT [FK_d_b]
GO
ALTER TABLE [dbo].[d] WITH CHECK ADD CONSTRAINT [FK_d_c] FOREIGN KEY([aid], [cid])
REFERENCES [dbo].[c] ([aid], [cid])
GO
ALTER TABLE [dbo].[d] CHECK CONSTRAINT [FK_d_c]
GO
It works as expected in my test:
INSERT INTO a values(1);
INSERT INTO a values(2);
INSERT INTO b values(1, 1);
INSERT INTO b values(2, 2);
INSERT INTO c values(1, 1);
INSERT INTO c values(2, 2);
(1 row affected)
(1 row affected)
(1 row affected)
(1 row affected)
(1 row affected)
(1 row affected)
Now let's try to insert into D:
INSERT INTO d values(1, 2, 2);
Msg 547, Level 16, State 0, Line 1
The INSERT statement conflicted with the FOREIGN KEY constraint "FK_d_b".
The conflict occurred in database "TestDB", table "dbo.b".
The statement has been terminated.
These inserts go through, as expected:
INSERT INTO d values(1, 1, 1);
INSERT INTO d values(2, 2, 2);
(1 row affected)
(1 row affected)
OR you can use this syntax:
CREATE TABLE d2 (
aid INTEGER,
bid INTEGER,
cid INTEGER,
FOREIGN KEY (aid,bid) REFERENCES b (aid,bid),
FOREIGN KEY (aid,cid) REFERENCES c (aid,cid)
);
In this case the foreign keys would get some autogenerated names, but they will work the same:
INSERT INTO d2 values(1, 2, 2);
Msg 547, Level 16, State 0, Line 1
The INSERT statement conflicted with the FOREIGN KEY constraint "FK__d2__351DDF8C".
The conflict occurred in database "TestDB", table "dbo.b".
The statement has been terminated.
INSERT INTO d2 values(1, 1, 2);
Msg 547, Level 16, State 0, Line 1
The INSERT statement conflicted with the FOREIGN KEY constraint "FK__d2__361203C5".
The conflict occurred in database "TestDB", table "dbo.c".
The statement has been terminated.
These inserts work, as expected:
INSERT INTO d2 values(1, 1, 1);
INSERT INTO d2 values(2, 2, 2);
(1 row affected)
(1 row affected)
OR, you can use this syntax to give the constraints some meaningful names:
CREATE TABLE d3 (
aid INTEGER,
bid INTEGER,
cid INTEGER,
CONSTRAINT FK_d3_b FOREIGN KEY (aid,bid) REFERENCES b (aid,bid),
CONSTRAINT FK_d3_c FOREIGN KEY (aid,cid) REFERENCES c (aid,cid)
);

Use two foreign key in table with 'on delete cascade'

CREATE TABLE Comments(
Id INT PRIMARY KEY IDENTITY(0,1),
TEXT NOT NULL,
Date Date NOT NULL ,
Point INT NOT NULL DEFAULT(0),
ID_User INT FOREIGN KEY REFERENCES Users(Id) ON DELETE CASCADE NOT NULL,
ID_Post INT FOREIGN KEY REFERENCES Posts(Id) NOT NULL
)
When I delete User from Users table it show me error that Comments table has
other Reference Key. What i have to do ?
The DELETE statement conflicted with the REFERENCE constraint "FK__Comments__ID_Pos__76969D2E". The conflict occurred in database "Facebook", table "dbo.Comments", column 'ID_Post'.
If you want to delete a user record, you need to delete the records in the foreign key tables.
In this case, you need to delete records in Comments table.
DELETE from dbo.Commnts
Where ID_User = "userid"
Then, you can remove the user record from Users table
I did some work on this, there is no error in the foreign keys you referenced here. you may probably have reference to comment id in some other tables.
this is what I tried
CREATE TABLE Users(
Id int primary key
)
CREATE TABLE posts(
Id int primary key
)
insert into Users values(1);
insert into Users values(2);
insert into posts values(3);
insert into posts values(4);
CREATE TABLE Comments(
Id INT PRIMARY KEY IDENTITY(0,1),
ID_User INT FOREIGN KEY REFERENCES Users(Id) ON DELETE CASCADE NOT NULL,
ID_Post INT FOREIGN KEY REFERENCES Posts(Id) NOT NULL
)
insert into Comments values(1,3);
insert into Comments values(2,4);
DELETE
FROM Users
WHERE id = 1 --this works fine

Relational table that is constrained to a subset of its referee

Say I have the following table:
create table foo (
id integer primary key,
mode integer not null check (mode in (1, 2))
);
Now I want to store additional information about records from this table, but only those that have, say, mode = 1. The way I thought I could do this is as follows:
create table foo_bar (
id integer primary key,
foo_id integer,
_mode integer not null default (1) check (_mode = 1),
foreign key (foo_id, _mode) references foo(id, mode)
);
That is, we have a dummy column in foo_bar that is forcibly always equal to 1 and include that in the foreign key constraint with foo.
However, not only does this not work (with pragma foreign_keys = ON;), but that foreign key constraint can be violated when you insert multiple values into foo!
sqlite> insert into foo(mode) values (1);
sqlite> insert into foo(mode) values (2);
sqlite> select * from foo;
1|1
2|2
sqlite> insert into foo_bar(foo_id) values (1);
Error: foreign key mismatch - "foo_bar" referencing "foo"
sqlite> insert into foo_bar(foo_id) values (2);
Error: foreign key mismatch - "foo_bar" referencing "foo"
sqlite> insert into foo(mode) values (1), (2);
Error: foreign key mismatch - "foo_bar" referencing "foo"
Is this a bug in SQLite (I'm using 3.17, it it matters), or am I doing it wrong?
An alternative option, without the foreign key, might be to use a trigger:
create trigger bad_mode
before insert on foo_bar when (select mode from foo where id = NEW.foo_id) <> 1
begin
select raise(fail, "Invalid mode");
end;
...but this seems a bit gross!
The documentation says:
Usually, the parent key of a foreign key constraint is the primary key of the parent table. If they are not the primary key, then the parent key columns must be collectively subject to a UNIQUE constraint or have a UNIQUE index.
That missing UNIQUE constraint is what causes the "foreign key mismatch" error.
It is not possible to use a subquery in a CHECK constraint, so the only way to enforce the mode = 1 constraint is to make it part of the foreign key constraint, or to use triggers.
I think you could improve the design, and no constraints other than FK are needed:
First of all foo.mode could be a foreign key to another table, say modes, no need to use a check constraint. You would store the values 1 and 2 there.
A second table, say bar_modes, could have a field mode, a FK to modes. You would store the value 1 in this table.
Finally, foo_bar.foo_id would be FK to foo.id, and foo_bar.mode a FK to bar_modes.mode.
Let me know what you think!
SQL Server example for M = 3, N = 5
create table modes (
id int primary key
)
create table foo (
id int primary key,
mode int not null foreign key references modes(id)
);
create table bar_modes (
id int primary key foreign key references modes(id)
)
create table foo_bar (
id int primary key,
foo_id int foreign key references foo(id),
other_data varchar(20)
)
insert modes (id) values(1)
insert modes (id) values(2)
insert modes (id) values(3)
insert modes (id) values(4)
insert modes (id) values(5)
insert bar_modes (id) values (1)
insert bar_modes (id) values (2)
insert bar_modes (id) values (3)
insert foo (id, mode) values (1000, 1)
insert foo (id, mode) values (2000, 2)
insert foo (id, mode) values (2500, 2)
insert foo (id, mode) values (5000, 5)
insert foo_bar (id, foo_id, other_data) values (100, 1000, 'data for foo 1000')
insert foo_bar (id, foo_id, other_data) values (200, 2000, 'data for foo 2000')
insert foo_bar (id, foo_id, other_data) values (250, 2500, 'data for foo 2500')

Reaching the tree END in oracle

I have one requirement...Suppose we have three table
1.A
primary key a_id
2.B
primary key b_id
foreign key a_id
3.C
primary key c_id
foreign key b_id
these tables contain 10 records each.
Need : Now i want to delete a specific record from each table.The record will be delete on a value supplied as a_id(primary key of table A).It will delete from A alright but how i can delete it from c as it does not contain a_id and this is only value known to us.
So how to do it...can anyone please let me know nay approach or anything.I need to delete a specific record from each table.starting point is a_id which is only thing known to us.
I am using oracle 10g and I cannot use any sys table. Thats a requirement as dba has given admin access.
you can do with the help of ON DELETE CASCADE option
see here here
CREATE TABLE A (a_id NUMBER, CONSTRAINT a_pk PRIMARY KEY (a_id));
CREATE TABLE B
(
b_id NUMBER
,a_id
,CONSTRAINT b_pk PRIMARY KEY (b_id)
,CONSTRAINT b_fk FOREIGN KEY (a_id) REFERENCES A (a_id)
);
CREATE TABLE C
(
C_id NUMBER
,b_id
,CONSTRAINT c_pk PRIMARY KEY (c_id)
,CONSTRAINT c_fk FOREIGN KEY (b_id) REFERENCES B (b_id)
);
--creating dummy data
insert into A select level from dual connect by level<11 ;
insert into B(b_id,a_id) select level+10,level from dual connect by level<11;
insert into C(c_id,b_id) select level+20,level+10 from dual connect by level<11;
CREATE OR REPLACE PROCEDURE delete_a(p_a_id IN a.id%TYPE)
IS
BEGIN
--delete record from C first
DELETE FROM C WHERE b_id IN (SELECT b_id from B where a_id =p_a_id);
--delete record from B
DELET FROM B WHERE a_id = p_a_id;
--delete from the main table
DELETE FROM C where a_id = p_a_id;
COMMIT;
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('Error while delete the recods '||SQLERRM);
ROLLBACK;
END delete_a;
Note:I have not tested the code ,but will work with this approach
I use the table script as follows:
CREATE TABLE A (a_id NUMBER CONSTRAINT a_pk PRIMARY KEY,a_grade CHAR(1));
CREATE TABLE b
( b_id NUMBER ,
b_grade CHAR(1),
CONSTRAINT b_pk PRIMARY KEY(b_id),
CONSTRAINT b_fk FOREIGN KEY (b_id) REFERENCES A(a_id)
ON DELETE CASCADE );
CREATE TABLE c
( c_id NUMBER,
c_grade CHAR(1),
CONSTRAINT c_pk PRIMARY KEY(c_id),
CONSTRAINT c_fk FOREIGN KEY(c_id)
REFERENCES b(b_id)
ON DELETE CASCADE );
BEGIN
INSERT INTO A VALUES(1,'a');
INSERT INTO b VALUES(1,'b');
INSERT INTO c VALUES(1,'c');
end;
/*below is the query that you provide in the table a to delete */
DELETE FROM A WHERE a_id=1;-- all tables values are deleted (parent and childs)
SELECT * FROM A;
SELECT * FROM b;
SELECT * FROM c;
hope this is what you needed.