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.
Related
I have a problem when I try to add constraints to my tables. I get the error:
Introducing FOREIGN KEY constraint 'FK74988DB24B3C886' on table 'Employee' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
My constraint is between a Code table and an employee table. The Code table contains Id, Name, FriendlyName, Type and a Value. The employee has a number of fields that reference codes, so that there can be a reference for each type of code.
I need for the fields to be set to null if the code that is referenced is deleted.
Any ideas how I can do this?
SQL Server does simple counting of cascade paths and, rather than trying to work out whether any cycles actually exist, it assumes the worst and refuses to create the referential actions (CASCADE): you can and should still create the constraints without the referential actions. If you can't alter your design (or doing so would compromise things) then you should consider using triggers as a last resort.
FWIW resolving cascade paths is a complex problem. Other SQL products will simply ignore the problem and allow you to create cycles, in which case it will be a race to see which will overwrite the value last, probably to the ignorance of the designer (e.g. ACE/Jet does this). I understand some SQL products will attempt to resolve simple cases. Fact remains, SQL Server doesn't even try, plays it ultra safe by disallowing more than one path and at least it tells you so.
Microsoft themselves advises the use of triggers instead of FK constraints.
A typical situation with multiple cascasing paths will be this:
A master table with two details, let's say "Master" and "Detail1" and "Detail2". Both details are cascade delete. So far no problems. But what if both details have a one-to-many-relation with some other table (say "SomeOtherTable"). SomeOtherTable has a Detail1ID-column AND a Detail2ID-column.
Master { ID, masterfields }
Detail1 { ID, MasterID, detail1fields }
Detail2 { ID, MasterID, detail2fields }
SomeOtherTable {ID, Detail1ID, Detail2ID, someothertablefields }
In other words: some of the records in SomeOtherTable are linked with Detail1-records and some of the records in SomeOtherTable are linked with Detail2 records. Even if it is guaranteed that SomeOtherTable-records never belong to both Details, it is now impossible to make SomeOhterTable's records cascade delete for both details, because there are multiple cascading paths from Master to SomeOtherTable (one via Detail1 and one via Detail2).
Now you may already have understood this. Here is a possible solution:
Master { ID, masterfields }
DetailMain { ID, MasterID }
Detail1 { DetailMainID, detail1fields }
Detail2 { DetailMainID, detail2fields }
SomeOtherTable {ID, DetailMainID, someothertablefields }
All ID fields are key-fields and auto-increment. The crux lies in the DetailMainId fields of the Detail tables. These fields are both key and referential contraint. It is now possible to cascade delete everything by only deleting master-records. The downside is that for each detail1-record AND for each detail2 record, there must also be a DetailMain-record (which is actually created first to get the correct and unique id).
I would point out that (functionally) there's a BIG difference between cycles and/or multiple paths in the SCHEMA and the DATA. While cycles and perhaps multipaths in the DATA could certainly complicated processing and cause performance problems (cost of "properly" handling), the cost of these characteristics in the schema should be close to zero.
Since most apparent cycles in RDBs occur in hierarchical structures (org chart, part, subpart, etc.) it is unfortunate that SQL Server assumes the worst; i.e., schema cycle == data cycle. In fact, if you're using RI constraints you can't actually build a cycle in the data!
I suspect the multipath problem is similar; i.e., multiple paths in the schema don't necessarily imply multiple paths in the data, but I have less experience with the multipath problem.
Of course if SQL Server did allow cycles it'd still be subject to a depth of 32, but that's probably adequate for most cases. (Too bad that's not a database setting however!)
"Instead of Delete" triggers don't work either. The second time a table is visited, the trigger is ignored. So, if you really want to simulate a cascade you'll have to use stored procedures in the presence of cycles. The Instead-of-Delete-Trigger would work for multipath cases however.
Celko suggests a "better" way to represent hierarchies that doesn't introduce cycles, but there are tradeoffs.
There is an article available in which explains how to perform multiple deletion paths using triggers. Maybe this is useful for complex scenarios.
http://www.mssqltips.com/sqlservertip/2733/solving-the-sql-server-multiple-cascade-path-issue-with-a-trigger/
By the sounds of it you have an OnDelete/OnUpdate action on one of your existing Foreign Keys, that will modify your codes table.
So by creating this Foreign Key, you'd be creating a cyclic problem,
E.g. Updating Employees, causes Codes to changed by an On Update Action, causes Employees to be changed by an On Update Action... etc...
If you post your Table Definitions for both tables, & your Foreign Key/constraint definitions we should be able to tell you where the problem is...
This is because Emplyee might have Collection of other entity say Qualifications and Qualification might have some other collection Universities
e.g.
public class Employee{
public virtual ICollection<Qualification> Qualifications {get;set;}
}
public class Qualification{
public Employee Employee {get;set;}
public virtual ICollection<University> Universities {get;set;}
}
public class University{
public Qualification Qualification {get;set;}
}
On DataContext it could be like below
protected override void OnModelCreating(DbModelBuilder modelBuilder){
modelBuilder.Entity<Qualification>().HasRequired(x=> x.Employee).WithMany(e => e.Qualifications);
modelBuilder.Entity<University>.HasRequired(x => x.Qualification).WithMany(e => e.Universities);
}
in this case there is chain from Employee to Qualification and From Qualification to Universities. So it was throwing same exception to me.
It worked for me when I changed
modelBuilder.Entity<Qualification>().**HasRequired**(x=> x.Employee).WithMany(e => e.Qualifications);
To
modelBuilder.Entity<Qualification>().**HasOptional**(x=> x.Employee).WithMany(e => e.Qualifications);
Trigger is solution for this problem:
IF OBJECT_ID('dbo.fktest2', 'U') IS NOT NULL
drop table fktest2
IF OBJECT_ID('dbo.fktest1', 'U') IS NOT NULL
drop table fktest1
IF EXISTS (SELECT name FROM sysobjects WHERE name = 'fkTest1Trigger' AND type = 'TR')
DROP TRIGGER dbo.fkTest1Trigger
go
create table fktest1 (id int primary key, anQId int identity)
go
create table fktest2 (id1 int, id2 int, anQId int identity,
FOREIGN KEY (id1) REFERENCES fktest1 (id)
ON DELETE CASCADE
ON UPDATE CASCADE/*,
FOREIGN KEY (id2) REFERENCES fktest1 (id) this causes compile error so we have to use triggers
ON DELETE CASCADE
ON UPDATE CASCADE*/
)
go
CREATE TRIGGER fkTest1Trigger
ON fkTest1
AFTER INSERT, UPDATE, DELETE
AS
if ##ROWCOUNT = 0
return
set nocount on
-- This code is replacement for foreign key cascade (auto update of field in destination table when its referenced primary key in source table changes.
-- Compiler complains only when you use multiple cascased. It throws this compile error:
-- Rrigger 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.
IF ((UPDATE (id) and exists(select 1 from fktest1 A join deleted B on B.anqid = A.anqid where B.id <> A.id)))
begin
update fktest2 set id2 = i.id
from deleted d
join fktest2 on d.id = fktest2.id2
join inserted i on i.anqid = d.anqid
end
if exists (select 1 from deleted)
DELETE one FROM fktest2 one LEFT JOIN fktest1 two ON two.id = one.id2 where two.id is null -- drop all from dest table which are not in source table
GO
insert into fktest1 (id) values (1)
insert into fktest1 (id) values (2)
insert into fktest1 (id) values (3)
insert into fktest2 (id1, id2) values (1,1)
insert into fktest2 (id1, id2) values (2,2)
insert into fktest2 (id1, id2) values (1,3)
select * from fktest1
select * from fktest2
update fktest1 set id=11 where id=1
update fktest1 set id=22 where id=2
update fktest1 set id=33 where id=3
delete from fktest1 where id > 22
select * from fktest1
select * from fktest2
This is an error of type database trigger policies. A trigger is code and can add some intelligences or conditions to a Cascade relation like Cascade Deletion. You may need to specialize the related tables options around this like Turning off CascadeOnDelete:
protected override void OnModelCreating( DbModelBuilder modelBuilder )
{
modelBuilder.Entity<TableName>().HasMany(i => i.Member).WithRequired().WillCascadeOnDelete(false);
}
Or Turn off this feature completely:
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
Some databases, most notably SQL Server, have limitations on the cascade behaviors that form cycles.
There are two ways to handle this situation:
1.Change one or more of the relationships to not cascade delete.
2.Configure the database without one or more of these cascade deletes, then ensure all dependent entities are loaded so that EF Core can perform the cascading behavior.
please refer to this link:
Database cascade limitations
Mass database update to offset PKs: make a copy of the database instead.
Special use case: company A uses a database with the same schema as company B. Because they have merged, they want to use a single database. Hence, many tables from company B's database must have their primary keys offset to avoid collision with company A's records.
One solution could have been to define foreign keys as ON UPDATE CASCADE, and offset the primary keys having the foreign keys follow. But there are many hurdles if you do that (Msg 1785, Msg 8102, ...).
So a better idea that occurs to me is simply to make a copy of the database, DROP and re CREATE the tables that must have their PKs|FKs offset, and copy the data (and while doing so, offset the primary keys and the foreign keys).
Avoiding all the hassle.
My solution to this problem encountered using ASP.NET Core 2.0 and EF Core 2.0 was to perform the following in order:
Run update-database command in Package Management Console (PMC) to create the database (this results in the "Introducing FOREIGN KEY constraint ... may cause cycles or multiple cascade paths." error)
Run script-migration -Idempotent command in PMC to create a script that can be run regardless of the existing tables/constraints
Take the resulting script and find ON DELETE CASCADE and replace with ON DELETE NO ACTION
Execute the modified SQL against the database
Now, your migrations should be up-to-date and the cascading deletes should not occur.
Too bad I was not able to find any way to do this in Entity Framework Core 2.0.
Good luck!
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.
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.
I would like to delete rows which contain a foreign key, but when I try something like this:
DELETE FROM osoby WHERE id_osoby='1'
I get this statement:
ERROR: update or delete on table "osoby" violates foreign key constraint "kontakty_ibfk_1" on table "kontakty"
DETAIL: Key (id_osoby)=(1) is still referenced from table "kontakty".
How can I delete these rows?
To automate this, you could define the foreign key constraint with ON DELETE CASCADE.
I quote the the manual for foreign key constraints:
CASCADE specifies that when a referenced row is deleted, row(s)
referencing it should be automatically deleted as well.
Look up the current FK definition like this:
SELECT pg_get_constraintdef(oid) AS constraint_def
FROM pg_constraint
WHERE conrelid = 'public.kontakty'::regclass -- assuming public schema
AND conname = 'kontakty_ibfk_1';
Then add or modify the ON DELETE ... part to ON DELETE CASCADE (preserving everything else as is) in a statement like:
ALTER TABLE kontakty
DROP CONSTRAINT kontakty_ibfk_1
, ADD CONSTRAINT kontakty_ibfk_1
FOREIGN KEY (id_osoby) REFERENCES osoby (id_osoby) ON DELETE CASCADE;
There is no ALTER CONSTRAINT command. Drop and recreate the constraint in a single ALTER TABLE statement to avoid possible race conditions with concurrent write access.
You need the privileges to do so, obviously. The operation takes an ACCESS EXCLUSIVE lock on table kontakty and a SHARE ROW EXCLUSIVE lock on table osoby.
If you can't ALTER the table, then deleting by hand (once) or by trigger BEFORE DELETE (every time) are the remaining options.
One should not recommend this as a general solution, but for one-off deletion of rows in a database that is not in production or in active use, you may be able to temporarily disable triggers on the tables in question.
In my case, I'm in development mode and have a couple of tables that reference one another via foreign keys. Thus, deleting their contents isn't quite as simple as removing all of the rows from one table before the other. So, for me, it worked fine to delete their contents as follows:
ALTER TABLE table1 DISABLE TRIGGER ALL;
ALTER TABLE table2 DISABLE TRIGGER ALL;
DELETE FROM table1;
DELETE FROM table2;
ALTER TABLE table1 ENABLE TRIGGER ALL;
ALTER TABLE table2 ENABLE TRIGGER ALL;
You should be able to add WHERE clauses as desired, of course with care to avoid undermining the integrity of the database.
There's some good, related discussion at http://www.openscope.net/2012/08/23/subverting-foreign-key-constraints-in-postgres-or-mysql/
You can't delete a foreign key if it still references another table.
First delete the reference
delete from kontakty
where id_osoby = 1;
DELETE FROM osoby
WHERE id_osoby = 1;
It's been a while since this question was asked, hope can help.
Because you can not change or alter the db structure, you can do this. according the postgresql docs.
TRUNCATE -- empty a table or set of tables.
TRUNCATE [ TABLE ] [ ONLY ] name [ * ] [, ... ]
[ RESTART IDENTITY | CONTINUE IDENTITY ] [ CASCADE | RESTRICT ]
Description
TRUNCATE quickly removes all rows from a set of tables. It has the same effect as an unqualified DELETE on each table, but since it does not actually scan the tables it is faster. Furthermore, it reclaims disk space immediately, rather than requiring a subsequent VACUUM operation. This is most useful on large tables.
Truncate the table othertable, and cascade to any tables that reference othertable via foreign-key constraints:
TRUNCATE othertable CASCADE;
The same, and also reset any associated sequence generators:
TRUNCATE bigtable, fattable RESTART IDENTITY;
Truncate and reset any associated sequence generators:
TRUNCATE revinfo RESTART IDENTITY CASCADE ;
It means that in table kontakty you have a row referencing the row in osoby you want to delete. You have do delete that row first or set a cascade delete on the relation between tables.
Powodzenia!
One can achieve this by issueing an extra SQL script that deletes the records related via the FK.
For this, using subselect in WHERE clause of a DELETE command can simply do what is needed.
Something similar to:
DELETE FROM kontakty
WHERE fk_column_from_kontakty_matching_id_osoby IN (
SELECT id_osoby FROM osoby WHERE id_osoby = '1'
);
DELETE FROM osoby WHERE id_osoby = '1';
the example assumes the FK column from kontakty matching osoby.id_osoby is called fk_column_from_kontakty_matching_id_osoby as it cannot be interpolated from the error message provided
For the case of OP's the query could by simplified a lot, but I have decided to leave it like this for it demonstrates accomplishing more complex scenarios
This approach is quite useful for SQL migrations, where one cannot depend on an assigned id and where ids cannot be easily fetched first and acted upon later.
I have a database table called Lesson:
columns: [LessonID, LessonNumber, Description] ...plus some other columns
I have another table called Lesson_ScoreBasedSelection:
columns: [LessonID,NextLessonID_1,NextLessonID_2,NextLessonID_3]
When a lesson is completed, its LessonID is looked up in the Lesson_ScoreBasedSelection table to get the three possible next lessons, each of which are associated with a particular range of scores. If the score was 0-33, the LessonID stored in NextLessonID_1 would be used. If the score was 34-66, the LessonID stored in NextLessonID_2 would be used, and so on.
I want to constrain all the columns in the Lesson_ScoreBasedSelection table with foreign keys referencing the LessonID column in the lesson table, since every value in the Lesson_ScoreBasedSelection table must have an entry in the LessonID column of the Lesson table. I also want cascade updates turned on, so that if a LessonID changes in the Lesson table, all references to it in the Lesson_ScoreBasedSelection table get updated.
This particular cascade update seems like a very straightforward, one-way update, but when I try to apply a foreign key constraint to each field in the Lesson_ScoreBasedSelection table referencing the LessonID field in the Lesson table, I get the error:
Introducing FOREIGN KEY constraint 'c_name' on table 'Lesson_ScoreBasedSelection' may cause cycles or multiple cascade paths.
Can anyone explain why I'm getting this error or how I can achieve the constraints and cascading updating I described?
You can't have more than one cascading RI link to a single table in any given linked table. Microsoft explains this:
You receive this error message because
in SQL Server, a table cannot appear
more than one time in a list of all
the cascading referential actions that
are started by either a DELETE or an
UPDATE statement. For example, the
tree of cascading referential actions
must only have one path to a
particular table on the cascading
referential actions tree.
Given the SQL Server constraint on this, why don't you solve this problem by creating a table with SelectionID (PK), LessonID, Next_LessonID, QualifyingScore as the columns. Use a constraint to ensure LessonID and QualifyingScore are unique.
In the QualifyingScore column, I'd use a tinyint, and make it 0, 1, or 2. That, or you could do a QualifyingMinScore and QualifyingMaxScore column so you could say,
SELECT * FROM NextLesson
WHERE LessonID = #MyLesson
AND QualifyingMinScore <= #MyScore
AND #MyScore <= QualifyingMaxScore
Cheers,
Eric