SQL Server Relation table parent->child and cycle cascade - sql

I have a sql table (let's call it Person) like, for example :
TABLE Person
Id Firstname Lastname
I want to make relations beetwen somme of this person (parent/child relation) so I built another table (let's call it Person_Relation) like that :
TABLE Person_Relation
Id_person_parent Id_person_child
I made a constraint to avoid the case where parent = child (it would be awkward !) and now i try to make foreign key beetwen table Person and table Person_Relation.I am currently able to make one foreign key, but when i try to set the second I get a : may cause cycles or multiple cascade paths error.
Knowing that I would keep the 'Delete cascade' to automatically delete links in Person_Relation table when an entry in Person is deleted, is there any clean solution to do that ?
Thank you in advance.

SQL Server won't let you create multiple cascade paths that could theoretically conflict. For more on that, see this answer.
One way to still achieve your goal is to use a trigger in place of a foreign key with a cascade action.
CREATE TRIGGER dbo.Person_Delete
ON dbo.Person
FOR DELETE
AS
DELETE dbo.Person_Relation
WHERE Id_person_parent IN (SELECT Id FROM deleted)
OR Id_person_child IN (SELECT Id FROM deleted);

Related

How to delete records from two different tables that are linked with FK? SQL

I have two tables City and Buildings in my database. They are linked with city_number that is Primary Key in City table and Foreign Key in Buildings table. If user wants to delete record from City table I want to remove any records from the Buildings table that is tied to that City. I use unique auto incremented id passed through the argument to remove these records. My SQL Query looks like this:
DELETE C.*, B.*
FROM City AS C
INNER JOIN Buildings AS B
ON C.c_number = B.b_district
WHERE D.c_id = 'some id example: 107';
Query above won't work sicne SQL allow only records from one table to be removed with INNER JOIN so i will have to use two separate DELETE statements like this:
DELETE
FROM City
WHERE c_id = '107'
DELETE
FROM Buildings
WHERE b_city = 'city that is tied to unique id 107'
My question is, what is the best practice to remove records that are tied in two tables? Since I have to use two separate SQL statements, should I pass City and then delete record(s) from Buildings table? or Should I create another query that will pull City from City table based on unique id and then remove record(s) from Buildings? If anyone knows better way to do this please let me know.
I believe the easiest way to accomplish your goal would be to set up your foreign key with ON DELETE CASCADE. That way, whenever a row in the parent table is deleted, any related rows in the child table will be deleted automatically.
Here is an example of a way to alter a table in order to create a foreign key with ON DELETE CASCADE:
ALTER TABLE child_table
ADD CONSTRAINT fk_name
FOREIGN KEY (child_col1, child_col2, ... child_col_n)
REFERENCES parent_table (parent_col1, parent_col2, ... parent_col_n)
ON DELETE CASCADE;
In your case, the child table would be Buildings and the parent table would be City. It sounds like you would have just city_number for the column. You'll have to fill in the name of your foreign key.
Like Shannon mentioned, you can use ON DELETE CASCADE to delete data from parent and child tables.
Here is a working example:
http://sqlfiddle.com/#!18/f5860/10
Without writing out the code, here's what I would do:
Select all the ids to be deleted for buildings belonging to a city
Delete all the buildings
Delete the city
Put it in a stored procedure
Re-usable, self-contained, and clear.
This is a violation of SRP however, let me know if you care about that and I'll post a SRP based SQL solution.

Create SQL one-to-many relationship with itself

I'm trying to create a one-to-many relationship on a table A with it's own records.
I thought I could just create a second table B, with two foreign keys linking to table A. It appears that this is not sufficient, as LINQ-to-SQL generated a one-to-one relationship. What am I missing?
Some context: table A contains tags, and tags can be sub tags of tags.
Edit: Maybe I need a many-to-many relationship?
Assuming you have a table with the following columns itemid and MasterID
in the column masterid you have the itemid of the item it links to
select a.Itemid,b.itemid from table1 as a inner join table1 as b on a.Itemid=b.MasterID
Above query will give you a result like this :
a_itemid b_itemid
3020079 3020334
3020079 3020335
3045396 3045397
3045396 3045398
3045396 3045401
3020079 3577149
SQL server won't let you create a foreign key for the table referencing itself with ON DELETE CASCADE or ON UPDATE CASCADE since it may cause cycles or multiple cascade paths. But you can create foreign key with ON DELETE NO ACTION and ON UPDATE NO ACTION.
ALTER TABLE tagtablename ADD FOREIGN KEY ([parent_id]) REFERENCES [tagtablename] ([id]) ON DELETE NO ACTION ON UPDATE NO ACTION
You also need to make the two keys in your link table B, the primary key of this table.
Also, Linq2SQL seems to generate properties based on the names of your keys. This resulted in some odd names for me.

Delete entry in table that is referenced by another table

I have two tables, Games and Sport_Games. Sport_Games has a foreign key that references Games.
Now when I want to delete an entry from Games I get an error saying:
"FK__Sport_Gam__game___1D7C2B7C". The conflict occurred in database "DatabasesProject", table "dbo.Sport_Games", column 'game_id'.
The statement has been terminated.
Im assuming this is because I can't delete something from a table with a constraint without dropping it. I tried adding ON DELETE CASCADE in table Games in hopes that when I delete a game it is also deleted from Sport_Games but that didn't work either.
Here are the tables for reference:
CREATE TABLE Games(
game_id INT IDENTITY PRIMARY KEY,
name VARCHAR(50),
release_date date,
rating INT,
min_age INT,
development_team_email VARCHAR(50) FOREIGN KEY REFERENCES Development_Teams,
release_conference INT FOREIGN KEY REFERENCES Conferences
--ON DELETE CASCADE ---this is what I added
)
CREATE TABLE Sport_Games(
game_id INT PRIMARY KEY FOREIGN KEY REFERENCES Games,
sport_type VARCHAR(50),
)
I have other tables referencing Games as well, action_games, strategy_games, But they're similar to sport_games
If you know the GameID of the records you are deleting, simply delete records in the Sport_Games table that have the same GameID first, then delete from Games table.
For the cascade to work, you need to ad that to the FK definition on the Sport_Games table. That way when the Games record is deleted, the sport_Games record will be deleted as well.
Your ON DELETE CASCADE will work for you if you put it on the correct Foreign Key. But to answer your question...
Since Games.Game_ID has a constraining reference against Sport_Games.Game_ID, you need to either turn the constraint off so it will not prevent you from deleting, but this will leave orphans, or delete the children before you delete the parent. Since you must already know the PK value to delete the parent, you simply:
DELETE FROM Sport_Games WHERE Game_ID = [The id you are using to delete from Games]
Then you delete the parent:
DELETE FROM Games WHERE Game_ID = [Same id used above]
What I'm about to say might not directly answer your question, but it could be beneficial for you moving forward or in future designs.
I've found placing "delete_date" and "delete_id" in tables to be a useful way to work around these FK constraint issues. If your application is only calling existing stored procedures and table functions you could update the queries to include "AND delete_date IS NULL" in their conditions. If your application is writing adhoc queries, it might also be as simple as creating or modifying a base class to inject the condition that omits soft-deleted rows.

Deleting Related Rows in a Many-to-Many Relationship

I'm deleting a row in a table that is on one site of a many-to-many relationship. I would also like to delete any related rows on the other side of that relationship.
For example, let's say I have the following tables and I want to delete a row from Cars. I would also want to delete any related rows from Drivers and, of course, any rows no longer needed in CarDrivers.
Table Cars:
CarID int
CarName nvarchar(100)
Table Drivers:
DriverID int
DriverName nvarchar(100)
Table CarDrivers:
CarID int
Driver int
I know how to join the tables above in a SELECT query. But I don't see how to delete data across the relationship.
Note: Both sides of the relationship implement cascading deletes. So, for example, deleting a row from Cars will delete any related rows in CarDrivers. But obviously that doesn't propagate to the Drivers table.
I think the best approach would be that you would have to delete the related table's data first. In other words, if you wanted to delete a Car and the corresponding Drivers that utilize that car, you'd have to delete the Drivers first, and then the Car. The join table will delete the correct records because of ON CASCADE DELETE.
Try this:
delete
from Drivers
where DriverID in
(
select d.DriverID
from Drivers d
inner join CarDrivers cd
on d.DriverID = cd.Driver
inner join Cars c
on c.CarID = cd.CarID
where c.CarID = 1
)
delete
from Cars
where CarID = 1
Naturally, you don't need to hardcode the 1 there, you could use anything including a parameter if you are utilizing this code snippet in a stored proc.
Your request doesn't make sense
Drivers as entities exists separately from Cars. Cars can be driven by many drivers, drivers can drive many cars. This is why you have the many-many table.
Note the "drivers can drive many cars" bit. This means if you delete the Drivers row, you need to delete other rows in CarDrivers.
If you still want to do this, you need a trigger on CarDrivers. The CASCADE from Drivers to CarDrivers will delete other CarDrivers rows for you. Can't remember the default behaviour for trigger recursion too.
What a mess.
Note: this almost makes sense if you have uniqueness on one of the columns in the many-many table then it should be a foreign key between Cars and Drivers (Unique on Car means "at most one driver per car" means NULLable FK column in Cars)
There is no relationship between the Drivers and the Cars table. This relationship is via the CarDrivers table. Thus, the problem still exists.
The only way I know to automate the CASCADE delete is to remove the FK between CarDrivers and Drivers table and add a before or after delete trigger to CarDrivers to delete the entry in drivers where the driver_id is the one of the row being deleted in CarDrivers.
This is not clean in so many ways. If the delete is actually required across the join table, then the relationship is probably modeled wrong and a cleaner relationship would have been to have modeled the relationship simply as 'there are many drivers of a car' or a FK of Cars in the Drivers table. As noted above, for the actual cars and drivers relationship a many-to-many relationship is actually correct and you would never delete a driver just because the car was totalled/deleted.
Put cascading deletes on the CarDrivers table.
If you have access to database and have permissions to alter the tables, I would just create foreign keys and specify onupdate and oncascade as so:
ALTER TABLE [dbo].[Drivers] WITH CHECK ADD CONSTRAINT [FK__Cars] FOREIGN KEY([CarID])
REFERENCES [dbo].[Car] ([CarID])
ON UPDATE CASCADE
ON DELETE CASCADE
ALTER TABLE [dbo].[CarDrivers] WITH CHECK ADD CONSTRAINT [FK_Drivers_Cars] FOREIGN KEY([CarID])
REFERENCES [dbo].[Car] ([CarID])
ON UPDATE CASCADE
ON DELETE CASCADE
The benefit of this approach is that you don't need to worry about orphan records. The moment you delete one record from the Car table, all related in the other tables are automatically deleted and updated. Your SQL statements are shorter, too.
In Oracle you can handle it using triggers, specifically compound triggers
alter table CarDrivers add CONSTRAINT CarFK FOREIGN KEY (CarID)
REFERENCES Cars (CarID) on delete cascade enable
/
create or replace TRIGGER "CarDrivers_delete"
for delete ON CarDrivers
compound trigger
type driver_ids is table of Drivers.DriverID%type INDEX BY PLS_INTEGER;
ids driver_ids;
AFTER EACH ROW IS
BEGIN
ids (ids.COUNT + 1) := :NEW.Driver;
END AFTER EACH ROW;
AFTER STATEMENT IS
BEGIN
FOR i IN 1 .. ids.COUNT
LOOP
delete from Drivers
WHERE DriverID = ids (i);
END LOOP;
END AFTER STATEMENT;
END;
/
This way it's enough to issue
delete from Cars where CarID = 666
and the deletion would be cascade to the CarDrivers table by the constraint and to the Drivers table by the trigger.
The use of compound triggers is necessary to avoid ORA-04091, that is mutating table errors. Compound triggers are available since Oracle11g. See here.
I had a similar issue with my project (using node.js with knex and postgres).
My many-to-many table foreign keys however were both not-nullable, which may be different from your example as presumably a car can exist in the database without a driver.
With the not-nullable, restrict and cascade applied to the many-to-many, I found deleting from each table individually, in reverse order from which they were migrated, worked well. With node/knex, I imported these functions to other files where they were needed to be used as callbacks, and avoid repetition.
It's probably not the optimal way, (sub-queries in knex don't read great) but suffices to get things working at a baseline.

How to delete FK one to many?

I have a table (aspnet_Membership) specifically that has spam records in it that I need to delete. There are some foreign keys and I'm trying to write a simple SQL statement to remove the FK's so I can delete the primary record.
So theres a table called 'aspnet_UsersInRoles' that has UserID and RoleID, and in my 'aspnet_Membership' table is the UserID. I can't delete the User without orphaning a record(wouldn't let me do that anyways due to contraints).
How can I essentially run the following:
'delete from 'aspnet_UsersInRoles' where UserID in 'aspnet_Membership' and 'aspnet_Membership.CreateDate >= '03/15/2009'?
Thank you for any suggestions.
Well, you probably could have set up the foreign keys to do a cascading delete, so that you didn't need to worry about it, but your try at the query was pretty close to one that would work, just use a subquery:
DELETE FROM aspnet_UsersInRoles WHERE UserID IN (SELECT UserID FROM aspnet_Membership WHERE aspnet_Membership.CreateDate >= '03/15/2009')
You can alter the constraints to do a cascade all upon deletion:
link
Otherwise you can look in the sys.foreign_keys table to get all the foreign keys using the primary key and auto generate sql to do deletes there first.