DB2 cascade delete command? - sql

Is there a special syntax to execute a cascade delete on DB2 or is it only possible to create "cascadable" tables by defining them with the "ON DELETE CASCADE" option?
What I am trying to achieve is delete other table rows based on a same foreign key when I delete that key, but this is done on an already existing and data-filled database.

As you stated, you either have to create FKs with an ON DELETE CASCADE clause or to pre-delete the other rows with a subselect-delete.
So, if you don't have an ON DELETE CASCADE clause you have to do
DELETE FROM ORDERS_ITEMS WHERE ORDER_ID in (
SELECT ORDER_ID FROM ORDERS WHERE STATUS = 'Canceled'
);
DELETE FROM ORDERS_ADDRESS WHERE ORDER_ID in (
SELECT ORDER_ID FROM ORDERS WHERE STATUS = 'Canceled'
);
DELETE FROM ORDERS WHERE STATUS = 'Canceled';
It is simple, but is somewhat redundant so you may use the WITH statement.
If the request to select the required rows is quite big, and if you don't have at least a RR isolation level, you may have to use a TEMPORARY table :
DECLARE GLOBAL TEMPORARY TABLE TMP_IDS_TO_DELETE (ID BIGINT) NOT LOGGED;
INSERT INTO SESSION.TMP_IDS_TO_DELETE (ID)
SELECT ORDER_ID FROM ORDERS WHERE STATUS = 'Canceled';
DELETE FROM ORDERS_ITEMS WHERE ORDER_ID in (
SELECT ID FROM SESSION.TMP_IDS_TO_DELETE
);
DELETE FROM ORDERS_ADDRESS WHERE ORDER_ID in (
SELECT ID FROM SESSION.TMP_IDS_TO_DELETE
);
DELETE FROM ORDERS WHERE ORDER_ID in (
SELECT ID FROM SESSION.TMP_IDS_TO_DELETE
);
This way you are sure that you will delete the same rows in each table, and a FK error will still fire up if you miss something. Per default, the temporary table will empty itself on commit.

Related

T-SQL - Adding FK to table, make it do drop/null/efault automagically?

If I had two tables where I need to add a new foreign key to, say:
ALTER TABLE [release_user]
ADD CONSTRAINT [user_release]
FOREIGN KEY ([user_id]) REFERENCES [user_table] ([user_id])
ON DELETE CASCADE
If there are records in the release_user table that do not have a matching ID in the user_table, the command will fail. I expect that.
However, is there a way to create the FK, and have it automatically delete (DELETE CASCADE) the records that would cause the FK to fail?
(Or, for example, if I used DELETE SET NULL, would null out the record's field in the release_user table?)
You could delete them first?
DB_FIDDLE
delete from ru
from release_user ru
left join user_table ut
on ut.user_id = ru.user_id
where ut.user_id is null
Then, add your constraint.
Can try by using trigger as below
(can refer the actual syntax below sample just gives the logic via trigger )
Create Trigger delete_check
Before Insert on
release_user
For EACH Row
If( Select count( * ) from
inserted where :New.ID NOT In
(Select Id from user_table ) >=1)
{ dbms_output.put_line("dont insert")
either
< delete from release_user where
id =:new.id>
or <insert into user_table
values(:New.id)> (to avoid
constraint error on insert)
}
END

On delete cascade for self-referencing table

I have a comment table that is self-referencing.
I tried to write on delete cascade but it take some exception
Introducing FOREIGN KEY constraint 'FK_Comments_Comments' on table 'Comments' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
and then try to write a trigger but it take exception again
CREATE TRIGGER [dbo].[T_comment_Trigger]
ON [dbo].[Comments]
FOR DELETE
AS
DELETE FROM Comments
WHERE ParentId =(SELECT deleted.id FROM deleted)
couldn't delete rows that have children
how can I do on delete cascade for my self-referencing table?
Assuming you're keeping your FOREIGN KEY constraint in place, you cannot fix the issue in a FOR DELETE trigger. FOR triggers (also known as AFTER triggers) fire after the activity has taken place. And a foreign key will prevent a row from being deleted if it has references. Foreign key checks occur before deletion.
What you need is an INSTEAD OF trigger. You also need to bear in mind that your current trigger only tried to deal with one "level" of referencing. (So, if row 3 references row 2 and row 2 references row 1, and you delete row 1, your trigger only tried to remove row 2)
So, something like:
CREATE TRIGGER [dbo].[T_comment_Trigger]
ON [dbo].[Comments]
INSTEAD OF DELETE
AS
;WITH IDs as (
select id from deleted
union all
select c.id
from Comments c
inner join
IDs i
on
c.ParentID = i.id
)
DELETE FROM Comments
WHERE id in (select id from IDs);
If there are other (non-self-referencing) cascading foreign key constraints, they all have to be replaced by actions in this trigger. In such a case, I'd recommend introducing a table variable to hold the list of all IDs that will eventually be deleted from the Comments table:
CREATE TRIGGER [dbo].[T_comment_Trigger]
ON [dbo].[Comments]
INSTEAD OF DELETE
AS
declare #deletions table (ID varchar(7) not null);
;WITH IDs as (
select id from deleted
union all
select c.id
from Comments c
inner join
IDs i
on
c.ParentID = i.id
)
insert into #deletions(ID)
select ID from IDs
-- Delete from FK referenced table
DELETE FROM OtherTable
WHERE CommentID in (select ID from #deletions)
--This delete comes last
DELETE FROM Comments
WHERE id in (select ID from #deletions);

Is it safe to drop and then create the foreign key constraints inside a transaction?

I have a table A that references a table B. Table B needs to be populated with updated data from an external source and for efficiency I use TRUNCATE followed by a COPY. This is done even when the application is live.
To further improve efficiency, as suggested in the documentation, I want to drop and then recreate the foreign keys.
However I have some doubts.
If I drop the FKs, COPY and then recreate FKs inside the same transaction, can I be sure that the constraint is preserved even on data inserted in table A during the transaction? I ask this because in theory a transaction is atomic, but in the docs, about the temporary removal of FKs say:
there is a trade-off between data load speed and loss of error checking while the constraint is missing.
If there's a chance that a wrong reference is inserted in the meantime, what happens when you try to recreate the FK constraints?
TRUNCATE is not allowed on any table referenced by a foreign key, unless you use TRUNCATE CASCADE, which will also truncate the referencing tables. The DEFERRABLE status of the constraint does not affect this. I don't think there is any way around this; you will need to drop the constraint.
However, there is no risk of an integrity violation in doing so. ALTER TABLE ... ADD CONSTRAINT locks the table in question (as does TRUNCATE), so your import process is guaranteed to have exclusive access to the table for the duration of its transaction. Any attempts at concurrent inserts will simply hang until the import has committed, and by the time they are allowed to proceeed, the constraint will be back in place.
You can make the foreign key constraint deferrable (initially deferred). That way it will be checked just once at the end of the transaction.
ALTER TABLE
xxx
ADD CONSTRAINT
xxx_yyy_id_fk FOREIGN KEY (yyy_id)
REFERENCES
yyy
DEFERRABLE INITIALLY DEFERRED;
In all the cases, transactions are fully atomic in PostgreSQL (not only in theory), including DDL statements (such as CREATE/DROP constraint), so even if you drop a foreign key, then insert data, then create the foreign key and do everything in one transaction, then you are safe - if the recreation of the foreign key constraint fails, then the inserted data will also be dismissed.
Still, it is better to switch to deferred foreign keys, rather than dropping and then creating them.
Analytic answer: measure the number of new/same/updated/deleted records.
There are four cases:
The key in the B table is not present in the b_import: delete
The key in the b_import is not present on the old B: insert
The key is present in both old B and new B, but the contents are the same: ignore
The keys are the same, but the attribete values differ: Update
-- some test data for `A`, `B` and `B_import`:
CREATE TABLE b
( id INTEGER NOT NULL PRIMARY KEY
, payload varchar
);
INSERT INTO b(id,payload) SELECT gs, 'bb_' || gs::varchar
FROM generate_series(1,20) gs;
CREATE TABLE b_import
( id INTEGER NOT NULL PRIMARY KEY
, payload varchar
);
INSERT INTO b_import(id,payload) SELECT gs, 'bb_' || gs::varchar
FROM generate_series(10,15) gs;
-- In real life this table will be filled by a `COPY b_import FROM ...`
INSERT INTO b_import(id,payload) SELECT gs, 'b2_' || gs::varchar
FROM generate_series(16,25) gs;
CREATE TABLE a
( id SERIAL NOT NULL PRIMARY KEY
, b_id INTEGER references b(id) ON DELETE SET NULL
, aaaaa varchar
);
INSERT INTO a(b_id,aaaaa)
SELECT gs,'aaaaa_' || gs::text FROM generate_series(1,20) gs;
CREATE INDEX ON a(b_id); -- index supporting the FK
-- show it
SELECT a.id, a.aaaaa
,b.id, b.payload AS oldpayload
FROM a
FULL JOIN b ON a.b_id=b.id
ORDER BY a.id;
-- Do the actual I/U/D and report the numbers of affected rows
-- EXPLAIN
WITH ins AS ( -- INSERTS
INSERT INTO b(id, payload)
SELECT b_import.id, b_import.payload
FROM b_import
WHERE NOT EXISTS (
SELECT 1 FROM b
WHERE b.id = b_import.id
)
RETURNING b.id
)
, del AS ( -- DELETES
DELETE FROM b
WHERE NOT EXISTS (
SELECT 2 FROM b_import
WHERE b_import.id = b.id
)
RETURNING b.id
)
, upd AS ( -- UPDATES
UPDATE b
SET payload=b_import.payload
FROM b_import
WHERE b_import.id = b.id
AND b_import.payload IS DISTINCT FROM b.payload -- exclude idempotent updates
-- AND NOT EXISTS ( -- exclude deleted records
-- SELECT 3 FROM del
-- WHERE del.id = b_import.id
-- )
-- AND NOT EXISTS ( -- avoid touching freshly inserted rows
-- SELECT 4 FROM ins
-- WHERE ins.id = b_import.id
-- )
RETURNING b.id
)
SELECT COUNT(*) AS orgb
, (SELECT COUNT(*) FROM b_import) AS newb
, (SELECT COUNT(*) FROM ins) AS ninserted
, (SELECT COUNT(*) FROM del) AS ndeleted
, (SELECT COUNT(*) FROM upd) AS nupdated
FROM b
;
Dropping a constraint and rebuilding it after the import is expensive: all the records in both A and B are involved.
temporally ignoring the constraint is dangerous: the new B table could miss some rows that are still referenced by A's FK.
ergo: You could end up with a crippled model, and you'd have to rebuild As references (which is basically impossible, without additional information (which would be redundant, BTW))

SQL Server using triggers and geting rid of ON DELETE CASCADE

I have 2 tables, A and B.
Table B has a foreign key pointing to primary key of table A. The foreign key on table B has ON DELETE CASCADE, so the corresponding records from B is deleted when a record from A is deleted.
My requirement is to track all added/updated/deleted records in history tables. I have trigger on each table to insert the records into history tables(AHistories and BHistories tables).
I do not like the order ON DELETE CASCADE deletes the records. Trigger A is executed after trigger B, so I have to work around to get the ID of AHistory into BHistory record.
I am wanting to get rid of ON DELETE CASCADE and perform Delete on the records of B in trigger A then insert the deleted record of B into BHistories there.
To demonstrate the idea, I made the case simple, but I have a few more tables that have a foreign key pointing to the primary key in table A. Personally, I would like if I can specify the order and what I do on delete cascade.
Does this stink as an approach? Any comments are appreciated.
As bad as triggers are but sometimes they are the only way to implement complex business requirements. I would do something as follows in the following example PK_ID refers to Primary Key Column.
CREATE TRIGGER tr_Table_A_InsteadOfDelete
ON dbo.TableA
INSTEAD OF DELETE
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRANSACTION;
-- Insert into History Table from TableB
INSERT INTO TABLE_B_History
SELECT B.*
FROM TableB B INNER JOIN deleted d
ON B.PK_ID = d.PK_ID
-- Delete rows from TableB
DELETE FROM TableB
WHERE EXISTS (SELECT 1
FROM deleted
WHERE PK_ID = TableB.PK_ID)
-- Insert into History Table from TableA
INSERT INTO TABLE_A_History
SELECT A.*
FROM TableA A INNER JOIN deleted d
ON A.PK_ID = d.PK_ID
-- Delete rows from TableA
DELETE FROM TableA
WHERE EXISTS (SELECT 1
FROM deleted
WHERE PK_ID = TableA.PK_ID)
COMMIT TRANSACTION;
END
Using triggers for audit purposes is as obvious as it's wrong. If you have sufficient permissions, they can be disabled too easily.
Nevertheless, what you want to do can be achieved with the combination of:
INSTEAD OF DELETE triggers, where you can delete or move rows around in any order;
Disabling nested triggers. This might has unpleasant side effects, if you use triggers for more than just the audit, because it's a database-wide option.
Just don't forget that in such a trigger, you have to manually delete rows from its underlying table, as well.

Self-referencing constraint in MS SQL

Is it true that MS SQL restrict self-referencing constraints with ON DELETE CASCADE option?
I have a table with parent-child relation, PARENT_ID column is foreign key for ID. Creating it with ON DELETE CASCADE option causes error
"Introducing FOREIGN KEY constraint
may cause cycles or multiple cascade
paths. Specify ON DELETE NO ACTION or
ON UPDATE NO ACTION, or modify other
FOREIGN KEY constraints."
I can't believe that I have to delete this hierarchy in recursive mode. Is there any issue except triggers?
It is the case that you cannot set up ON DELETE CASCADE on a table with self-referencing constraints. There is a potential of cyclical logic problems, hence it won't allow it.
There's a good article here - though it's for version 8 rather than 9 of SQL - though the same rules apply.
I just answered another question where this question was bound as duplicate. I think it's worth to place my answer here too:
This is not possible. You can solve this with an INSTEAD OF TRIGGER
create table locations
(
id int identity(1, 1),
name varchar(255) not null,
parent_id int,
constraint pk__locations
primary key clustered (id)
)
GO
INSERT INTO locations(name,parent_id) VALUES
('world',null)
,('Europe',1)
,('Asia',1)
,('France',2)
,('Paris',4)
,('Lyon',4);
GO
--This trigger will use a recursive CTE to get all IDs following all ids you are deleting. These IDs are deleted.
CREATE TRIGGER dbo.DeleteCascadeLocations ON locations
INSTEAD OF DELETE
AS
BEGIN
WITH recCTE AS
(
SELECT id,parent_id
FROM deleted
UNION ALL
SELECT nxt.id,nxt.parent_id
FROM recCTE AS prv
INNER JOIN locations AS nxt ON nxt.parent_id=prv.id
)
DELETE FROM locations WHERE id IN(SELECT id FROM recCTE);
END
GO
--Test it here, try with different IDs. You can try WHERE id IN(4,3) also...
SELECT * FROM locations;
DELETE FROM locations WHERE id=4;
SELECT * FROM locations
GO
--Clean-Up (Carefull with real data!)
if exists(select 1 from INFORMATION_SCHEMA.TABLES where TABLE_NAME='locations')
---DROP TABLE locations;
CREATE TRIGGER MyTable_OnDelete ON MyTable
INSTEAD OF DELETE
AS
BEGIN
SET NOCOUNT ON;
DELETE FROM mt
FROM deleted AS D
JOIN MyTable AS mt
ON d.Id = mt.ParentId
DELETE FROM mt
FROM deleted AS D
JOIN MyTable AS mt
ON d.Id = mt.Id
END