Please start any new threads on our new site at https://forums.sqlteam.com. We've got lots of great SQL Server experts to answer whatever question you can come up with.

 All Forums
 General SQL Server Forums
 New to SQL Server Programming
 Cascade delete problem

Author  Topic 

ron2112
Starting Member

44 Posts

Posted - 2006-11-02 : 17:35:37
Hi all, I've been reading for a couple of weeks but this is my first question so please be gentle with me.

I'm pretty new to SQL Server, though I've worked with Access for years. I've got four tables (I'm simplifying) and I need to set up cascade deletes between them. I understand how to create foreign keys and all that, but SQL Server is telling me it can't create my keys because it will create multiple cascade paths. I understand that too, I just need to find a way around it. Here are the tables...

dbo.JOBS
Job_Number

dbo.COLUMNS
JOBS_Job_Number
Column_Number

dbo.ROWS
JOBS_Job_Number
Row_Number

dbo.GRID_DATA
JOBS_Job_Number
Column_Number
Row_Number
Data

Currently I have keys set to cascade delete between JOBS>COLUMNS and JOBS>ROWS so that when the user deletes a job, the columns and rows for that job are deleted as well. No problem there.

If the user deletes a column, I have to delete all the grid data for that column and job. Same with rows. So I tried to establish cascade deletes between COLUMNS>GRID_DATA and ROWS>GRID_DATA and that's where I got in trouble. I assume the reason is that if the user deletes a job, it's going to delete the columns and rows, and I've got multiple cascade paths coming from COLUMNS and ROWS to GRID_DATA. I get it.

The question is, what can I do about it? I don't have a cascade delete between JOBS>GRID_DATA, although I do want that behavior, but I was assuming it just would flow through COLUMNS or ROWS. I haven't bothered to add that key as I'm guessing that would just make the situation worse. Anyone got any advice for me on this one?

Thanks!
Ron Moses
ConEst Software Systems

snSQL
Master Smack Fu Yak Hacker

1837 Posts

Posted - 2006-11-02 : 18:19:08
You should create a trigger on the jobs table that performs all the other deletes and turn off the cascades for your relationships.
Go to Top of Page

ron2112
Starting Member

44 Posts

Posted - 2006-11-03 : 10:02:27
Hey, my first trigger! Thanks, that works really well, but I have a follow-up question.

I would like to maintain integrity between JOBS and COLS, and JOBS and ROWS. But the trigger I added happens AFTER the JOBS record is deleted. This means that I can't have a foreign key constraint between JOBS and COLS because the job record will be deleted and the column records are not. So I had to remove the foreign keys in order to implement the trigger.

My question is, is there a way to make the trigger happen BEFORE the JOBS record is deleted? That way I could maintain integrity and still automate that record deletion. Or maybe there's another way of accomplishing this...?

Thanks!
Ron Moses
ConEst Software Systems
Go to Top of Page

ron2112
Starting Member

44 Posts

Posted - 2006-11-03 : 10:21:25
I should say that I'm aware of INSTEAD OF triggers, and I know that I can delete the COLS and ROWS records first, and then presumably I can delete the JOBS record from within the INSTEAD OF trigger. What I don't know is, how do I capture the original delete transaction that fired the trigger? For example, if the original transaction says

DELETE * FROM JOBS WHERE Job_Number = 12

My INSTEAD OF trigger can delete the records in COLS and ROWS where JOBS_Job_Number = 12. What I don't know is how I then delete the record from JOBS.

Currently the SQL in my AFTER trigger looks like this...

DECLARE @Job_Number INT
SELECT @Job_Number = (SELECT Job_Number FROM Deleted)
DELETE FROM COLS
WHERE JOBS_Job_Number = @Job_Number
DELETE FROM ROWS
WHERE JOBS_Job_Number = @Job_Number

So... if I make this an INSTEAD OF trigger, does the virtual table "Deleted" still exist? If so, then it's a no-brainer, I can just tack on the delete for JOBS. But if not, then I'm not sure where to go.

Ron Moses
ConEst Software Systems
Go to Top of Page

snSQL
Master Smack Fu Yak Hacker

1837 Posts

Posted - 2006-11-03 : 12:33:32
Yes the deleted table does exist in an INSTEAD OF trigger and you will need to use it to delete not only the rows from columns and rows, but from jobs too because whereas an AFTER trigger occurs after the delete as part of the transaction, an INSTEAD OF trigger replaces the original delete.

You should use set operations for this rather than using a variable, so that if multiple jobs are deleted then all of the columns and rows for all the jobs are deleted (your current code will only delete for a single job).

So it will end something like this
DELETE FROM COLS 
WHERE EXISTS (SELECT * FROM deleted WHERE Job_Number = COLS.JOBS_Job_Number)
DELETE FROM ROWS
WHERE EXISTS (SELECT * FROM deleted WHERE Job_Number = ROWS.JOBS_Job_Number)
DELETE FROM JOBS
WHERE EXISTS (SELECT * FROM deleted WHERE Job_Number = JOBS.Job_Number)


Also, note that a benefit of using an INSTEAD OF trigger in this case is that you will still be able to add the foreign key constraints between COLS and JOBS and ROWS and JOBS so that insert and update integrity is maintained too.
Go to Top of Page

ron2112
Starting Member

44 Posts

Posted - 2006-11-03 : 12:42:06
Awesome. Thank you so much for your help!

Ron Moses
ConEst Software Systems
Go to Top of Page

samuelclay
Yak Posting Veteran

71 Posts

Posted - 2006-11-03 : 14:49:50
You will need to make sure that the delete within the trigger does not cause the trigger to fire (recursive trigger).

Instead of a trigger to clean out the related tables, you could delete the records from those tables directly in your stored procedure. I can't tell from your description of Column_Number and Row_Number are unique, I'll procede with the assumption that they are not, and a job # is required for any delete.

In procedure to delete job @JobToDel (input parameter):

DELETE gr
FROM dbo.Grid_Data gr
LEFT OUTER JOIN dbo.Columns co
ON gr.Column_Number = co.Column_Number
AND gr.JOBS_Job_Number = co.JOBS_Job_Number
LEFT OUTER JOIN dbo.ROWS ro
ON gr.Row_Number = ro.Row_Number
AND gr.JOBS_Job_Number = ro.JOBS_Job_Number
WHERE JOBS_Job_Number = @JobToDel
AND ( co.JOBS_Job_Number IS NOT NULL
OR ro.JOBS_Job_Number IS NOT NULL
)

DELETE FROM dbo.ROWS
WHERE JOBS_Job_Number = @JobToDel

DELETE FROM dbo.COLUMNS
WHERE JOBS_Job_Number = @JobToDel

DELETE FROM dbo.JOBS
WHERE Job_Number = @JobToDel

In procedure to Delete column @ColToDel for job @JobNo:

DELETE
FROM dbo.Grid_Data gr
WHERE gr.Column_Number = @ColToDel
AND gr.JOBS_Job_Number = @JobNo

DELETE
FROM dbo.Columns co
WHERE co.Column_Number = @ColToDel
AND co.JOBS_Job_Number = @JobNo

In procedure to Delete row @RowToDel for job @JobNo:

DELETE
FROM dbo.Grid_Data gr
WHERE gr.Row_Number = @RowToDel
AND gr.JOBS_Job_Number = @JobNo

DELETE
FROM dbo.Rows ro
WHERE ro.Column_Number = @RowToDel
AND ro.JOBS_Job_Number = @JobNo
Go to Top of Page

rockmoose
SQL Natt Alfen

3279 Posts

Posted - 2006-11-03 : 17:30:49
dbo.JOBS
Job_Number

dbo.COLUMNS
JOBS_Job_Number
Column_Number

dbo.ROWS
JOBS_Job_Number
Row_Number

dbo.GRID_DATA
JOBS_Job_Number
Column_Number
Row_Number
Data

I would have set up cascade delete fk relationships all the way.
Except on COLUMNS or ROWS (to avoid the multiple paths), where I would do a AFTER trigger to delete from dbo.GRID_DATA.

Why?
because I prefer the declarative constraints as far as possible,
when the database can't handle our business rules declaratively, then procedural constraints are necessary, such as triggers.

snSQL's example does not cater for cascading deletes when only a row in COLUMNS or ROWS is deleted.
samuelclay's example pre-supposes that the only DML operations occur through the stored proc.

rockmoose
Go to Top of Page

ron2112
Starting Member

44 Posts

Posted - 2006-11-03 : 17:45:00
So if I understand correctly, rockmoose, you're saying that given the chain JOBS > [COLUMNS and ROWS] > GRID_DATA, I'm better off using the cascades in the earlier part of the chain and the triggers in the latter, rather than the other way around? Does it really make that big a difference?

Wouldn't doing an AFTER trigger on COLUMNS and ROWS to delete the GRID_DATA records prevent me from maintaining integrity in GRID_DATA? Seems like if I do an INSTEAD OF trigger, it allows me to have a non-cascading fk relationship between COLUMNS>GRID_DATA and ROWS>GRID_DATA. Then I can delete the COLUMNS or ROWS record at the end of the trigger. Am I way off-base on that idea?

Thanks!
Ron Moses
ConEst Software Systems
Go to Top of Page

samuelclay
Yak Posting Veteran

71 Posts

Posted - 2006-11-03 : 18:52:48
Don't get me wrong, I am all for RI and allowing the system to police itself, I'm just giving an alternative method...

Just remember the more control you give to the system, the less control you retain yourself. I know that stored procedures are not the only method of access to the data, but access should be limited and controlled...
Go to Top of Page

rockmoose
SQL Natt Alfen

3279 Posts

Posted - 2006-11-03 : 19:22:09
>> Wouldn't doing an AFTER trigger on COLUMNS and ROWS to delete the GRID_DATA records prevent me from maintaining integrity in GRID_DATA?

Yes you are right, I was a bit quick in posting the reply there.

* You could either have instead of triggers on JOBS, COLUMNS and ROWS.
* Or CASCADE delete on the chain JOBS -< ROWS -< GRID_DATA, and instead of triggers on JOBS + COLUMNS.
* Or CASCADE delete on JOBS + COLUMNS, an AFTER trigger on ROWS, have a NO CHECK FK on ROWS -< GRID_DATA,
and an AFTER trigger (INSERT/UPDATE) on GRID_DATA to enforce integrity for ROWS -< GRID_DATA.

These multiple paths are somewhat of a pain to work around
Having complete RI integrity and enforcing the cascading deletes through instead of triggers start to seem more palatable

>> Don't get me wrong, I am all for RI and allowing the system to police itself, I'm just giving an alternative method...
Of course not.
Cascading deletes is somewhat of a shortcut for manipulating the data, but can also cause problems in the case of an "accidental" delete.
Go to Top of Page

TG
Master Smack Fu Yak Hacker

6065 Posts

Posted - 2006-11-04 : 08:47:36
No one has mentioned this so I must be over simplifiying your requirments.
Would you get what you need by having cascade deletes between Jobs and all other tables, then have non cascading FK constraints on grid_data to maintain RI? That way your cascade deletes will flow from jobs to all the other tables and RI is still maintained between grid_data and cols and rows. So if a job was deleted the cascade would flow through the model but if someone tried to delete just a job's row or column (that existed in the grid_data) they would recieve a FK constraint violation.

use pubs

if object_id ('dbo.grid_data') > 0 drop table grid_data
if object_id ('dbo.[rows]') > 0 drop table rows
if object_id ('dbo.[columns]') > 0 drop table columns
if object_id ('dbo.jobs') > 0 drop table jobs
go

create table dbo.JOBS (Job_Number int primary key clustered)
go
create table dbo.[COLUMNS]
(JOBS_Job_Number int references jobs (job_number) on delete cascade
,Column_Number int
,primary key clustered(jobs_job_number, column_number))
go
create table dbo.[ROWS]
(JOBS_Job_Number int references jobs (job_number) on delete cascade
,Row_Number int
,primary key clustered(jobs_job_number, row_number))
go
create table dbo.GRID_DATA
(JOBS_Job_Number int references jobs (job_number) on delete cascade
,Column_Number int
,Row_Number int
,Data int
,primary key clustered (jobs_job_number, column_number, row_number)
,constraint fk_1 foreign key (jobs_job_number, row_number) references [rows] (jobs_job_number, row_number)
,constraint fk_2 foreign key (jobs_job_number, column_number) references [columns] (jobs_job_number, column_number)
)
go



Be One with the Optimizer
TG
Go to Top of Page

ron2112
Starting Member

44 Posts

Posted - 2006-11-06 : 09:11:20
quote:
Originally posted by TG

Would you get what you need by having cascade deletes between Jobs and all other tables, then have non cascading FK constraints on grid_data to maintain RI? That way your cascade deletes will flow from jobs to all the other tables and RI is still maintained between grid_data and cols and rows. So if a job was deleted the cascade would flow through the model but if someone tried to delete just a job's row or column (that existed in the grid_data) they would recieve a FK constraint violation.

I do need the ability to delete a row or column, and have that delete cascade (or trigger) down to the grid data. So I wouldn't want to get a constraint violation in that case.

It sounds like there are a good half-dozen ways to do this, all of them valid (and thanks to everyone for your suggestions). Right now I have cascades set up between COLUMNS/ROWS and GRID_DATA, and I have the INSTEAD OF delete trigger on JOBS. I did it that way because this is not the only example of this scenario in my database relating to the deletion of JOBS records. I figured it would be preferable to use the cascades between all these secondary tables, and do one big delete trigger on JOBS that covers all of these instances, rather than the other way around. It just seems cleaner to me to have one trigger and everything else running off fk cascades. Anyway, it seems to be working, so unless there's a fundamental reason not to do it this way, I think I'm going to stick with it.

quote:
Originally posted by samuelclay

Just remember the more control you give to the system, the less control you retain yourself.

Definitely, I agree. In this case, there is no scenario in which we would delete a job and not delete all this related data. But there is some job-related data that we would maintain for historical purposes, and we're handling that completely differently.

Thanks everyone, you've all been extremely helpful!
Ron Moses
ConEst Software Systems
Go to Top of Page
   

- Advertisement -