Deleting a row from self-referencing table

Posted by Jake Rutherford on Geeks with Blogs See other posts from Geeks with Blogs or by Jake Rutherford
Published on Wed, 11 Apr 2012 08:02:52 GMT Indexed on 2012/04/11 17:30 UTC
Read the original article Hit count: 323

Filed under:

Came across this the other day and thought “this would be a great interview question!”

I’d created a table with a self-referencing foreign key. The application was calling a stored procedure I’d created to delete a row which caused but of course…a foreign key exception.

You may say “why not just use a the cascade delete option?” Good question, easy answer. With a typical foreign key relationship between different tables which would work. However, even SQL Server cannot do a cascade delete of a row on a table with self-referencing foreign key.

So, what do you do?……

In my case I re-wrote the stored procedure to take advantage of recursion:

 

-- recursively deletes a Foo
ALTER PROCEDURE [dbo].[usp_DeleteFoo]
     @ID int
    ,@Debug bit = 0   
AS
    SET NOCOUNT ON;

    BEGIN TRANSACTION
    BEGIN TRY
        DECLARE @ChildFoos TABLE
        (
            ID int
        )
       
        DECLARE @ChildFooID int       
       
        INSERT INTO @ChildFoos
        SELECT ID FROM Foo WHERE ParentFooID = @ID
       
        WHILE EXISTS (SELECT ID FROM @ChildFoos)
        BEGIN
            SELECT TOP 1
                @ChildFooID = ID
            FROM
                @ChildFoos
               
            DELETE FROM @ChildFoos WHERE ID = @ChildFooID
           
            EXEC usp_DeleteFoo @ChildFooID
        END   
       
               
        DELETE FROM dbo.[Foo]
        WHERE [ID] = @ID
       
        IF @Debug = 1 PRINT 'DEBUG:usp_DeleteFoo, deleted - ID: ' + CONVERT(VARCHAR, @ID)
        COMMIT TRANSACTION
    END TRY
    BEGIN CATCH
        ROLLBACK TRANSACTION
        DECLARE @ErrorMessage VARCHAR(4000), @ErrorSeverity INT, @ErrorState INT
        SELECT @ErrorMessage = ERROR_MESSAGE(), @ErrorSeverity = ERROR_SEVERITY(), @ErrorState = ERROR_STATE()

        IF @ErrorState <= 0 SET @ErrorState = 1

        INSERT INTO ErrorLog(ErrorNumber,ErrorSeverity,ErrorState,ErrorProcedure,ErrorLine,ErrorMessage)
        VALUES(ERROR_NUMBER(), @ErrorSeverity, @ErrorState, ERROR_PROCEDURE(), ERROR_LINE(), @ErrorMessage)

        RAISERROR (@ErrorMessage, @ErrorSeverity, @ErrorState)
    END CATCH

 

This procedure will first determine any rows which have the row we wish to delete as it’s parent. It then simply iterates each child row calling the procedure recursively in order to delete all ancestors before eventually deleting the row we wish to delete.

© Geeks with Blogs or respective owner