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.
| 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.JOBSJob_Numberdbo.COLUMNSJOBS_Job_NumberColumn_Numberdbo.ROWSJOBS_Job_NumberRow_Numberdbo.GRID_DATAJOBS_Job_NumberColumn_NumberRow_NumberDataCurrently 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 MosesConEst 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. |
 |
|
|
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 MosesConEst Software Systems |
 |
|
|
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 saysDELETE * FROM JOBS WHERE Job_Number = 12My 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 INTSELECT @Job_Number = (SELECT Job_Number FROM Deleted)DELETE FROM COLS WHERE JOBS_Job_Number = @Job_NumberDELETE FROM ROWS WHERE JOBS_Job_Number = @Job_NumberSo... 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 MosesConEst Software Systems |
 |
|
|
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 thisDELETE 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. |
 |
|
|
ron2112
Starting Member
44 Posts |
Posted - 2006-11-03 : 12:42:06
|
| Awesome. Thank you so much for your help!Ron MosesConEst Software Systems |
 |
|
|
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 grFROM 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_NumberWHERE JOBS_Job_Number = @JobToDelAND ( co.JOBS_Job_Number IS NOT NULL OR ro.JOBS_Job_Number IS NOT NULL )DELETE FROM dbo.ROWSWHERE JOBS_Job_Number = @JobToDelDELETE FROM dbo.COLUMNSWHERE JOBS_Job_Number = @JobToDelDELETE FROM dbo.JOBSWHERE Job_Number = @JobToDel In procedure to Delete column @ColToDel for job @JobNo:DELETE FROM dbo.Grid_Data grWHERE gr.Column_Number = @ColToDelAND gr.JOBS_Job_Number = @JobNoDELETEFROM dbo.Columns coWHERE co.Column_Number = @ColToDelAND co.JOBS_Job_Number = @JobNo In procedure to Delete row @RowToDel for job @JobNo:DELETE FROM dbo.Grid_Data grWHERE gr.Row_Number = @RowToDelAND gr.JOBS_Job_Number = @JobNoDELETEFROM dbo.Rows roWHERE ro.Column_Number = @RowToDelAND ro.JOBS_Job_Number = @JobNo |
 |
|
|
rockmoose
SQL Natt Alfen
3279 Posts |
Posted - 2006-11-03 : 17:30:49
|
| dbo.JOBSJob_Numberdbo.COLUMNSJOBS_Job_NumberColumn_Numberdbo.ROWSJOBS_Job_NumberRow_Numberdbo.GRID_DATAJOBS_Job_NumberColumn_NumberRow_NumberDataI 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 |
 |
|
|
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 MosesConEst Software Systems |
 |
|
|
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... |
 |
|
|
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. |
 |
|
|
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 pubsif object_id ('dbo.grid_data') > 0 drop table grid_dataif object_id ('dbo.[rows]') > 0 drop table rowsif object_id ('dbo.[columns]') > 0 drop table columnsif object_id ('dbo.jobs') > 0 drop table jobsgocreate table dbo.JOBS (Job_Number int primary key clustered)gocreate 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))gocreate 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))gocreate 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) )goBe One with the OptimizerTG |
 |
|
|
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 MosesConEst Software Systems |
 |
|
|
|
|
|
|
|