0

I have tables in a SQL Server database with following 2 scenarios:

  1. Many to one relations between tables (2 columns of child table pointing to one column of the parent)
  2. A table is referring to itself as:
create table dbo.Employee 
(
    id int primary key, 
    BossId int null
);

alter table dbo.Employee 
    add constraint FK_Employee_Boss 
        foreign key (BossId) references dbo.Employee (id);

enter image description here

I have a working query for the 1st scenario:

USE Your_DB_Name -- change to your proper db

IF OBJECT_ID('dbo.udfGetFullQualName') IS NOT NULL
    DROP FUNCTION dbo.udfGetFullQualName;
GO

CREATE FUNCTION dbo.udfGetFullQualName
    (@ObjectId INT)
RETURNS VARCHAR (300)
AS
BEGIN
    DECLARE @schema_id AS BIGINT;

    SELECT @schema_id = schema_id
    FROM sys.tables
    WHERE object_id = @ObjectId;

    RETURN '[' + SCHEMA_NAME(@schema_id) + '].[' + OBJECT_NAME(@ObjectId) + ']';
END
GO

--============ Supporting Function dbo.udfGetOnJoinClause
IF OBJECT_ID('dbo.udfGetOnJoinClause') IS NOT NULL
    DROP FUNCTION dbo.udfGetOnJoinClause;
GO

CREATE FUNCTION dbo.udfGetOnJoinClause
    (@fkNameId INT)
RETURNS VARCHAR (1000)
AS
BEGIN
    DECLARE @OnClauseTemplate AS VARCHAR (1000);
    SET @OnClauseTemplate = '[<@pTable>].[<@pCol>] = [<@cTable>].[<@cCol>] AND ';

    DECLARE @str AS VARCHAR (1000);
    SET @str = '';

    SELECT @str = @str + REPLACE(REPLACE(REPLACE(REPLACE(@OnClauseTemplate, '<@pTable>', OBJECT_NAME(rkeyid)), '<@pCol>', COL_NAME(rkeyid, rkey)), '<@cTable>', OBJECT_NAME(fkeyid)), '<@cCol>', COL_NAME(fkeyid, fkey))
    FROM   dbo.sysforeignkeys AS fk
    WHERE  fk.constid = @fkNameId; --OBJECT_ID('FK_ProductArrearsMe_ProductArrears')

    RETURN LEFT(@str, LEN(@str) - LEN(' AND '));
END
GO

--=========== CASCADE DELETE STORED PROCEDURE dbo.uspCascadeDelete
IF OBJECT_ID('dbo.uspCascadeDelete') IS NOT NULL
    DROP PROCEDURE dbo.uspCascadeDelete;
GO

CREATE PROCEDURE dbo.uspCascadeDelete
    @ParentTableId VARCHAR(300), 
    @WhereClause VARCHAR(2000), 
    @ExecuteDelete CHAR (1) = 'N', --'N' IF YOU NEED DELETE SCRIPT
    @FromClause VARCHAR (8000) = '', 
    @Level INT = 0 -- TABLE NAME OR OBJECT (TABLE) ID (Production.Location) WHERE CLAUSE (Location.LocationID = 7) 'Y' IF WANT TO DELETE DIRECTLY FROM SP,  IF LEVEL 0, THEN KEEP DEFAULT
AS -- written by Daniel Crowther 16 Dec 2004 - handles composite primary keys

    SET NOCOUNT ON;

    /* Set up debug */
    DECLARE @DebugMsg AS VARCHAR (4000), 
            @DebugIndent AS VARCHAR (50);

    SET @DebugIndent = REPLICATE('---', @@NESTLEVEL) + '> ';

    IF ISNUMERIC(@ParentTableId) = 0
    BEGIN -- assume owner is dbo and calculate id
        IF CHARINDEX('.', @ParentTableId) = 0
            SET @ParentTableId = OBJECT_ID('[dbo].[' + @ParentTableId + ']');
        ELSE
            SET @ParentTableId = OBJECT_ID(@ParentTableId);
    END

    IF @Level = 0
    BEGIN
        PRINT @DebugIndent + ' **************************************************************************';
        PRINT @DebugIndent + ' *** Cascade delete ALL data from ' + dbo.udfGetFullQualName(@ParentTableId);
        IF @ExecuteDelete = 'Y'
            PRINT @DebugIndent + ' *** @ExecuteDelete = Y *** deleting data...';
        ELSE
            PRINT @DebugIndent + ' *** Cut and paste output into another window and execute ***';
    END

    DECLARE @CRLF AS CHAR (2);
    SET @CRLF = CHAR(13) + CHAR(10);

    DECLARE @strSQL AS VARCHAR (4000);

    IF @Level = 0
        SET @strSQL = 'SET NOCOUNT ON' + @CRLF;
    ELSE
        SET @strSQL = '';

    SET @strSQL = @strSQL + 'PRINT ''' + @DebugIndent + dbo.udfGetFullQualName(@ParentTableId) + ' Level=' + CAST (@@NESTLEVEL AS VARCHAR) + '''';

    IF @ExecuteDelete = 'Y'
        EXECUTE (@strSQL);
    ELSE
        PRINT @strSQL;

    DECLARE curs_children CURSOR LOCAL FORWARD_ONLY FOR 
        SELECT DISTINCT 
            constid AS fkNameId, -- constraint name
            fkeyid AS cTableId
        FROM
            dbo.sysforeignkeys AS fk
        WHERE  
            fk.rkeyid <> fk.fkeyid -- WE DO NOT HANDLE self referencing tables!!!
            AND fk.rkeyid = @ParentTableId;

    OPEN curs_children;

    DECLARE @fkNameId AS INT, 
            @cTableId AS INT, 
            @cColId AS INT, 
            @pTableId AS INT, 
            @pColId AS INT;

    FETCH NEXT FROM curs_children INTO @fkNameId, @cTableId; --, @cColId, @pTableId, @pColId
DECLARE @strFromClause AS VARCHAR (1000);
DECLARE @nLevel AS INT;
IF @Level = 0
    BEGIN
        SET @FromClause = 'FROM ' + dbo.udfGetFullQualName(@ParentTableId);
    END
WHILE @@FETCH_STATUS = 0
    BEGIN
        SELECT @strFromClause = @FromClause + @CRLF + '      INNER JOIN ' + dbo.udfGetFullQualName(@cTableId) + @CRLF + '       ON ' + dbo.udfGetOnJoinClause(@fkNameId);
        SET @nLevel = @Level + 1;
        EXECUTE dbo.uspCascadeDelete @ParentTableId = @cTableId, @WhereClause = @WhereClause, @ExecuteDelete = @ExecuteDelete, @FromClause = @strFromClause, @Level = @nLevel;
        SET @strSQL = 'DELETE FROM ' + dbo.udfGetFullQualName(@cTableId) + @CRLF + @strFromClause + @CRLF + 'WHERE   ' + @WhereClause + @CRLF;
        SET @strSQL = @strSQL + 'PRINT ''---' + @DebugIndent + 'DELETE FROM ' + dbo.udfGetFullQualName(@cTableId) + '     Rows Deleted: '' + CAST(@@ROWCOUNT AS VARCHAR)' + @CRLF + @CRLF;
        IF @ExecuteDelete = 'Y'
            EXECUTE (@strSQL);
        ELSE
            PRINT @strSQL;
        FETCH NEXT FROM curs_children INTO @fkNameId, @cTableId;
    --, @cColId, @pTableId, @pColId
    END
IF @Level = 0
    BEGIN
        SET @strSQL = @CRLF + 'PRINT ''' + @DebugIndent + dbo.udfGetFullQualName(@ParentTableId) + ' Level=' + CAST (@@NESTLEVEL AS VARCHAR) + ' TOP LEVEL PARENT TABLE''' + @CRLF;
        SET @strSQL = @strSQL + 'DELETE FROM ' + dbo.udfGetFullQualName(@ParentTableId) + ' WHERE ' + @WhereClause + @CRLF;
        SET @strSQL = @strSQL + 'PRINT ''' + @DebugIndent + 'DELETE FROM ' + dbo.udfGetFullQualName(@ParentTableId) + ' Rows Deleted: '' + CAST(@@ROWCOUNT AS VARCHAR)' + @CRLF;
        IF @ExecuteDelete = 'Y'
            EXECUTE (@strSQL);
        ELSE
            PRINT @strSQL;
    END
CLOSE curs_children;
DEALLOCATE curs_children;

However, I want to modify my above query to include the 2nd scenario(delete the data for the self referencing tables).

How can I modify the above query to generate delete statements for the data from the self referencing tables?

  • 1
    I'm not certain what you want to accomplish: is it just to clean out all records from Employee table? You can do that in two stages: 1. Update Employee Set BossID=Null Where BossID is not null. Then, 2. Delete from Employee, or Truncate table Employee. Maybe you want to clean circular references? That's harder, because if ID1 is the boss of ID2 in one record and ID2 is the boss of ID1 in another record there is no way to determine which is right and which wrong. – Chris Maurer Apr 27 '23 at 03:58
  • SQL Server supports cascade delete for foreign keys out of the box, so I really don't understand what you're attempting to do here. – Zohar Peled Apr 27 '23 at 06:59
  • @ZoharPeled Cascading self-referencing FKs are not supported, you can only use `NO ACTION` – Charlieface Apr 27 '23 at 12:36
  • This answer may be helpful https://stackoverflow.com/a/73398441/14868997 – Charlieface Apr 27 '23 at 12:37
  • Thanks for the replies, @Charlieface, My situation is somewhat similar to stackoverflow.com/a/73398441/14868997. But the major issue is - In my DB, there are several self referencing tables. I want to modify the above query in such a way that it could generate delete statements for the self referencing tables as well. – Anand Mohan Singh May 01 '23 at 01:38

0 Answers0