How to check if foreign key constraint fails when deleting record - sql

I have created a database in SQL Server and front end is PHP - CodeIgniter. In the database I have created multiple foreign keys with other tables. Now when the user tries to delete the record, instead of really deleting I want to flag the record as deleted = 1, this should only be done when there will be no reference records are available in child table. Below are example tables:
Parent_Table
Id INT(PK), Name Varchar, deleted INT
Child_Table
Id INT(PK), FK_Parent_Table_ID INT, address varchar, deleted INT
Above is just example of my tables. Now whenever a user tries to delete a record from the parent table foreign key will check for constraint and then delete the record, here instead of actual deletion I want it flag as deleted = 1.
I have tried using transaction->start and transaction->complete so if foreign key fails the transaction gets aborted but here the problem is if the foreign key not failing then the rollback will occur and in that case the PRIMARY KEY of the record will be changed that should not be done.
So, I want a way to check the foreign key conflict before transaction starts without actual deletion of the record

To implement what you are asking, just check for the existence of a record in the child table e.g.
declare #RecordToDelete int = 123;
-- Delete the record if no child records exist
delete
from Parent_Table
where id = #RecordToDelete
and not exists (select 1 from Child_Table where FK_Parent_Table_ID = #RecordToDelete);
-- Flag the record as deleted if child records exist
update Parent_Table set
Deleted = 1
where id = #RecordToDelete
and exists (select 1 from Child_Table where FK_Parent_Table_ID = #RecordToDelete);
Depending on whether you really need to keep the record, because you could always create your foreign keys with a cascade delete.
With 15+ child tables I would seriously consider just always flagging the record as deleted and never bothering to actually delete those without child records. A few extra records is unlikely to make much difference to your database.
Actually in my experience child tables fall into 2 categories:
Those that can automatically be deleted using a cascade delete
Those that should prevent us from deleting the parent record
If this is the case the checks required should become more manageable.
Also for these situations I recommend encapsulating the delete logic within a stored procedure in order to keep it all in one place, and be easy to modify if the database schemes changes in future.
Note: Personally I would make the Deleted column a bit rather than an int as it more accurately reflects the intention.

Related

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.

ON DELETE SET NULL on self referencing relationship

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;

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.

How to update 2 columns in 2 tables that have foreign key

I know the question of how to update multiple tables in SQL has been asked before and the common answer seems to be do them separately in a transaction.
However, the 2 columns I need to update have a foreign key so cannot be updated separately.
e.g.
Table1.a is a foreign key to Table2.a
One of the entries in the tables is wrong, e.g. both columns are 'xxx' and should be 'yyy'
How do I update Table1.a and Table2.a to be 'yyy'?
I know I could temp remove the key and replace but surely there's another way.
Thanks
You can't do the update simultaneously, however you can force SQL to do the update. You need to make sure your foreign keys have the referential triggered action ON UPDATE CASCADE
e.g.
ALTER TABLE YourTable
ADD CONSTRAINT FK_YourForeignKey
FOREIGN KEY (YourForeignKeyColumn)
REFERENCES YourPrimaryTable (YourPrimaryKeyColumn) ON UPDATE CASCADE
Not being a fan of on update cascade, I would suggest a different route.
First you do not update the Parent table, you add a new record with the value you want (and the same data as the other record for all other fields). Then you have no difficulty updating the child tables to use this value instead of that value. Further you now have the ability to to do the work in batches to avoid locking the system up while the change promulgates through it. Once all the child tables have been updated, you can delete the original bad record.
my answer is based on the following link: http://msdn.microsoft.com/en-us/library/ms174123%28v=SQL.90%29.aspx
You need to make sure that your table_constraint will be defined as ON UPDATE CASCADE
CREATE TABLE works_on1
(emp_no INTEGER NOT NULL,
project_no CHAR(4) NOT NULL,
job CHAR (15) NULL,
enter_date DATETIME NULL,
CONSTRAINT prim_works1 PRIMARY KEY(emp_no, project_no),
CONSTRAINT foreign1_works1 FOREIGN KEY(emp_no) REFERENCES employee(emp_no) ON DELETE CASCADE,
CONSTRAINT foreign2_works1 FOREIGN KEY(project_no) REFERENCES project(project_no) ON UPDATE CASCADE)
and then when you will change the value of your primary key
see the following quote:
For ON DELETE or ON UPDATE, if the CASCADE option is specified, the
row is updated in the referencing table if the corresponding
referenced row is updated in the parent table. If NO ACTION is
specified, SQL Server Compact Edition returns an error, and the update
action on the referenced row in the parent table is rolled back.
For example, you might have two tables, A and B, in a database. Table
A has a referential relationship with table B: the A.ItemID foreign
key references the B.ItemID primary key.
If an UPDATE statement is executed on a row in table B and an ON
UPDATE CASCADE action is specified for A.ItemID, SQL Server Compact
Edition checks for one or more dependent rows in table A. If any
exist, the dependent rows in table A are updated, as is the row
referenced in table B.
Alternatively, if NO ACTION is specified, SQL Server Compact Edition
returns an error and rolls back the update action on the referenced
row in table B when there is at least one row in table A that
references it.

SQL Delete if the row is not affected by constraints

First note that I have seen this question:TSQL delete with an inner join
I have a large table and several foreign key relations, each of which have data of a given age. We need to remove data older than a given data on a regular basis to stop the DB from growing without bound.
I'm writing a query that will delete from each point on the star if you will by the given parameters (unfortunately these are configurable and different between the tables).
After this first deletion, I have a central table that I'm worried that I'm doing twice the work attempting to delete, as on delete the database checks the conditionals. I have a set of:
AND NOT EXISTS
(SELECT key
FROM table
WHERE table.key = centretable.key)
which TSQL is making into a right anti semi join and doing it nicely on the indexes. The problem is it creates a list of stuff to delete and then does the same checks again as it performs the delete.
I guess my question is whether there is a try delete by row, (I'm not going to do that in a cursor as I know how slow it would be), but you would think that such a keyword would exist, I haven't had any luck finding it though.
In terms of a single command that only checks the relationships once (rather than twice in your example - once for the NOT EXISTS, once for the DELETE), then I expect the answer is a big fat no, sorry.
(off the wall idea):
If this is a major problem, you could try some kind of reference-counting implementation, using triggers to update the counter - but in reality I expect this will be a lot more overhead to maintain than simply checking the keys like you are already.
You could also investigate NOCHECK during the delete (since you are checking it yourself); but you can only do this at the table level (so probably OK for admin scripts, but not for production code) - i.e.:
-- disable
alter table ChildTableName nocheck constraint ForeignKeyName
-- enable
alter table ChildTableName check constraint ForeignKeyName
A quick test shows that with it enabled it does an extra Clustered Index Scan on the foreign key; with it disabled, this is omitted.
Here's a full example; you can look at the query plan of the two DELETE operations... (ideally in isolation from the rest of the code):
create table parent (id int primary key)
create table child (id int primary key, pid int)
alter table child add constraint fk_parent foreign key (pid)
references parent (id)
insert parent values (1)
insert parent values (2)
insert child values (1,1)
insert child values (2,1)
-- ******************* THIS ONE CHECKS THE FOREIGN KEY
delete from parent
where not exists (select 1 from child where pid = parent.id)
-- reset
delete from child
delete from parent
insert parent values (1)
insert parent values (2)
insert child values (1,1)
insert child values (2,1)
-- re-run with check disabled
alter table child nocheck constraint fk_parent
-- ******************* THIS ONE DOESN'T CHECK THE FOREIGN KEY
delete from parent
where not exists (select 1 from child where pid = parent.id)
-- re-enable
alter table child check constraint fk_parent
Again - I stress this should only be run from things like admin scripts.
You could create an Indexed view of your select sentence:
SELECT key FROM table WHERE table.key = centretable.key
The indexed view is a physical copy of the data it would therefore be very fast to check.
You do have the overhead of updating the view, so you would need to test this against your usage pattern.
If you're reusing the same list of stuff to delete then you could consider inserting the keys to delete into a temp table and then using this in the second query.
SELECT Key, ...
INTO #ToDelete
FROM Table T
WHERE ...
Then something like this
...
LEFT OUTER JOIN #ToDelete D
ON T.Key=D.Key
WHERE D.Key IS NULL
DROP #ToDelete
If you specified the foreign key as a constraint when creating the table in the database you can tell the database what to do in case of a delete, by setting the delete rule. This rule specifies what happens if a user tries to delete a row with data that is involved in a foreign key relationship. The "No action" setting tells the user that the deletion is not allowed and the DELETE is rolled back. Implementing it like that would keep you from checking it yourself before deleting it, and thus could be seen as some kind of try.
Well, at least it works like that in MS SQL. http://msdn.microsoft.com/en-us/library/ms177288.aspx
I did find one article that discusses using an outer join in a delete:
http://www.bennadel.com/blog/939-Using-A-SQL-JOIN-In-A-SQL-DELETE-Statement-Thanks-Pinal-Dave-.htm
I hope this works for you!
The short answer to your question is no, there is no standard RDBMS keyword for deleting a master record when all foreign key references to it go away (and certainly none that would account for foreign keys in multiple tables).
Your most efficient option is a second query that is run on an as-needed basis to delete from "centre" based on a series of NOT EXISTS() clauses for each of the tables with foreign keys.
This is based on two statements I believe are both true for your situation:
You will delete more "related" records than "centre" (parent) records. Thus, any operation that attempts to adjust "centre" every time you delete from one of the other tables will result in an instantaneous update to "centre", but will require much wasted querying to delete a "centre" record only occasionally.
Given that there are multiple points on the star from "centre," any "wasted effort" checking for a foreign key in one of them is minimal compared to the whole. For instance, if there are four foreign keys to check before deleting from "centre", you can only save, at best, 25% of the time.