ON DELETE SET NULL on self referencing relationship - sql

I have the table with one primary key and one foreign key referencing the same table primary key.
i.e there are parents and childs in the same table. In sql sever there are three options for the delete rule. But it is only possible to set "NO ACTION" delete rule. I understand that it is not possible to set the "cascade" delete because of cycles and chaining. But why the other options are not allowed? Especially the "SET NULL" one.
Right now I have to do this manually. I have to find the child records and set the foreign key on null. After that I can delete the parent. Why is it not possible to set the rule for it?

Because it cannot perform two actions on the same table together which are:
-delete the parent.
-update the children.
A mutating table is a table that is being modified by an UPDATE, DELETE, or INSERT statement, or a table that might be updated by the effects of a DELETE CASCADE constraint.
you can overcome doing it manually by creating a procedure that would hold the parent key to delete the record and set the children to NULL.
procedure(parent_id) --takes the id as a parameter
update table set null where foreign_key = parent_id;
delete from table where id = parent_id;
end;

Related

Delete records of table which has 2 foreign keys that reference to same table

I have 2 tables, first one is Compartment and second one is AboveCompartment. Please see the below. Above compartment has 2 columns which are foreign keys and reference to the Compartment table. When I set the delete and update action as cascade for 2 foreign keys, I get the error below.
Introducing FOREIGN KEY constraint 'FK_AboveCompartment_Compartment1' on table 'AboveCompartment' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
Below CompId and AboveCompId are foreign keys and reference to the Compartment table. Which way should I follow to add delete cascading? I used the trigger below but it also didn't work and get error added below.
AboveCompartment
Compartment
Trigger
ALTER TRIGGER [dbo].[delFromCompartment] on [dbo].[Compartment]
FOR DELETE
AS
DELETE FROM dbo.AboveCompartment
WHERE CompId IN(SELECT deleted.Id FROM deleted)
Error
You cannot implement this using cascades, as SQL Server does not let you.
You also cannot implement it using triggers, because the foreign key is enforced before you get to the trigger.
You need to write a stored procedure that first deletes the parent table rows, then the child table
CREATE OR ALTER PROCEDURE dbo.Delete_Compartment
#CompartmentId int
AS
SET XACT_ABORT, NOCOUNT ON; -- always use XACT_ABORT if you have a transaction
BEGIN TRAN;
DELETE AboveCompartment
WHERE CompId = #CompartmentId;
DELETE AboveCompartment
WHERE AboveCompId = #CompartmentId;
DELETE Compartment
WHERE Id = #CompartmentId;
COMMIT;
I must say, this table design is somewhat suspect. AboveCompId as a column name implies that it represents a single parent for multiple children, rather than multiple parents for multiple children.
If so then you should instead implement this as a self-referencing foreign key. Drop the AboveCompartment table, and add a column
ALTER TABLE Compartment
ADD AboveCompId int NULL REFERENCES Compartment (Id);
This foreign key also cannot be cascading. But now the delete is only on one table, but you can do it in a recursive fashion. As long as you delete all rows in one go, you shouldn't have an issue with foreign key conflicts.
CREATE OR ALTER PROCEDURE dbo.Delete_Compartment
#CompartmentId int
AS
SET NOCOUNT ON;
-- no transaction needed as it's one statement
WITH cte AS (
SELECT #CompartmentId AS Id
UNION ALL
SELECT c.Id
FROM Compartment c
JOIN cte ON cte.Id = c.AboveCompId;
)
DELETE c
FROM Compartment c
JOIN cte ON cte.Id = c.Id;

If I delete a record from a table it is deleted at many other places

I am working on a database built by the previous team. I have to delete certain records from a table (Example shown below).
DELETE FROM table WHERE id = 5541
While doing this process, some records from the other tables with the same id is getting deleted. Could someone help how to overcome this problem?
In SQL Server, there is statement called ON DELETE CASCADE which deletes the record from the child table if the record is deleted from the parent table. This can be set using the ALTER STATEMENT as shown below. To remove the cascading, try altering the child table back to default. The default is NO CASCADING.
ALTER TABLE ChildTable
ADD CONSTRAINT FKey
FOREIGN KEY (col1, col2, ... coln)
REFERENCES ParentTable (Pcol1, Pcol2, ... Pcoln)
ON DELETE CASCADE
There is UPDATE CASCADE as well if the data in the child table should be updated when the parent table is updated.
You database most certainly contains foreign key constraints with cascading deletes see docs.
You may be able to remove these foreign keys, but of course, then deleting some rows will leave you with inconsistent data.
Another possibilty is to just remove the cascading deletes. But then of course, you won't be able to delete any rows which are referenced by records from other tables, as SQL server will ensure the consistency of your data.

How to delete records from parent table which is referenced by multiple child tables?

I have a table which is referenced by multiple tables (around 52) and further,few of the child tables have multiple foreign keys also that is referencing other tables too.
I want to delete a record from parent table, I am unable to do so, as I am getting error "The DELETE statement conflicted with the REFERENCE constraint "FK_xxx". The conflict occurred in database "MyDB", table "dbo.A", column 'x'."
I want a generalized T-SQL solution which is irrespective of tables and number of references.
You have to look at the "on delete" keyword which is a part of the foreign key constraint definition.
Basically you have 4 options:
NO ACTION (does nothing)
CASCADE (deletes the child aswell)
SET NULL (sets the reference field to null)
SET DEFAULT (sets the reference field to the default value)
An example would be:
CREATE TABLE parent (
id INT NOT NULL,
PRIMARY KEY (id)
) ENGINE=INNODB;
CREATE TABLE child (
id INT,
parent_id INT,
INDEX par_ind (parent_id),
FOREIGN KEY (parent_id)
REFERENCES parent(id)
ON DELETE CASCADE -- replace CASCADE with your choice
) ENGINE=INNODB;
(for this example and more details look here: http://dev.mysql.com/doc/refman/5.7/en/create-table-foreign-keys.html )
If you now want to modify your constraint, you first have to drop it, and create a new one like for example:
ALTER TABLE child
ADD CONSTRAINT fk_name
FOREIGN KEY (parent_id)
REFERENCES parent(id)
ON DELETE CASCADE; -- replace CASCADE with your choice
I hope this helped. Also to mention it, you should think about maybe not really deleting your parent, and instead creating another boolean column "deleted", which you fill with "yes" if someone clicks the delete. In the "Select"-query you filter then by that "deleted" column.
The advantage is, that you do not lose the history of this entry.
Your problem is this: A FK constraint is designed to prevent you from creating an orphaned child record in any of the 52 tables. I can provide you with the script you seek, but you must realise first that when you try to re-enable the FK constraints the constraints will fail to re-enable because of the orphaned data (which the FK constraints are designed to prevent). For your next step, will have to delete the orphaned data in each of the 52 tables first anyway. It is actually much easier just to redo the constraints with ON DELETE CASCADE, or drop the constraints and forget about referential integrity altogether. You can't have it both ways.

How to delete a row ONLY in parent table, which is referenced by a Foregin Key from the child table

I want to delete a row/tuple from a parent table, but it is throwing an error message because it has a FOREIGN KEY reference in its child table.
However, in my case I want to delete the record only from the parent table and maintain the data in the child table.
Is it possible to achieve this?
I know the usage of ON DELETE CASCADE, but I want to know if there is a solution for the secenario I described?
It is possible with some agreements in your data. To maintain child table data you'll have to do ON DELETE SET NULL. This will leave data, but set FK to NULL value (in child table). And that is because of data-integrity: while you can keep your data, your FK can not refer to non-existent row of parent table in terms of enforcing FK constraint. Thus, it will be set to NULL by this.
If you want to "save" value of FK - then you definitely should not use FK at all because such behavior violates what FK is. So then just don't use that constraint, but be aware of possible integrity fails.
The point of a foreign key constraint is to prevent orphan records in the child table. So, no, it's not possible to do that, unless you drop the foreign key relationship.
If you rely on 'ON DELETE CASCADE', then deleting the parent record will result in all the corresponding children to be deleted.
If you want to delete the parent, but keep the children, you need to drop the foreign key constraint, or set the constraint to be 'ON DELETE SET NULL'. If you set 'ON DELETE SET NULL', then when you delete the parent record, the child records will remain, but the foreign key column value will be set to NULL.
delete a row ONLY in parent table, which is referenced by a Foregin Key from the child table
If Multiple table has been mapped in one table in that case all foreign key i.e :-
$table->integer('customer_id')->unsigned()->nullable();
$table->foreign('customer_id')->references('id')
->on('customers')->onDelete(`SET NULL`);

ON DELETE CASCADE alternatives and performance (SQL Server)

In my program I store entries in a table and an entry may also have child items.
id uniqueidentifier not null primary key
parent uniqueidentifier null (another id from the same table or null)
... other columns
In this table only top-level entries can have child items, so cycles or recursion are not possible.
If I delete an entry, I want also delete child items. Unfortunately, there is no way to add ON DELETE CASCADE to such table:
Introducing FOREIGN KEY constraint '...' on table '...' 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, however, just specify an additional condition in my delete statement to do the same:
DELETE FROM mytable WHERE id = #GUID OR parent = #GUID
The problem is that parent column is not indexed. This column also has a lot of duplicate values (NULLs) and as I know, duplicate values are very bad when used with indexes. I would like to know, what is the best solution for this problem
Is there a better solution to remove both child and parent items?
Should I use index on parent column and if yes, what type of index should I use in this case - there are a lot of duplicate values
Not sure, but I suggest that OR-statement in the above solution make primary key index useless and SQL server will just scan the entire table.
PS
I cannot create another table to store child items.