Find representatives of circular references in T-SQL

If you deal with a table that has foreign key to itself, circular references may appear. The query that lists all rows with circular references is listed at the page.

Sometimes it is necessary to list only representatives of each set of circular referenced rows. In the following query the first select statement orders representatives by its IDs; the second one – by the latest create time, inputed to another table.

USE [Example]
GO

-- create tables
CREATE TABLE [dbo].[List]
    ([ID] [int] IDENTITY(1,1) NOT NULL
    ,[Name] [varchar](50) NULL
    ,[ParentID] [int] NULL
    ,CONSTRAINT [PK_List] PRIMARY KEY CLUSTERED ([ID] ASC));
GO

CREATE TABLE [dbo].[ListTime]
    ([ID] [int] IDENTITY(1,1) NOT NULL
    ,[ListID] [int] NOT NULL
    ,[CurrentDate] [datetime] NOT NULL
    ,CONSTRAINT [PK_ListTime] PRIMARY KEY CLUSTERED ([ID] ASC));
GO

ALTER TABLE [dbo].[ListTime]
    ADD  CONSTRAINT [DF_ListTime_CurrentDate]  DEFAULT (SYSDATETIME()) FOR [CurrentDate];
ALTER TABLE [dbo].[ListTime] WITH CHECK
    ADD  CONSTRAINT [FK_ListTime_List] FOREIGN KEY([ListID]) REFERENCES [dbo].[List] ([ID]);
GO

-- insert values
INSERT INTO [dbo].[List]
VALUES
     ('text 1', 3)
    ,('text 2', NULL)
    ,('text 3', 1)
    ,('text 4', 7)
    ,('text 5', 4)
    ,('text 6', 5)
    ,('text 7', 6)
    ,('text 8', 9)
    ,('text 9', 8)
    ,('text 10', 11)
    ,('text 11', 13)
    ,('text 12', 13)
    ,('text 13', 13)

INSERT INTO [dbo].[ListTime]
VALUES
     (1, '2013-12-01')
    ,(2, '2013-12-02')
    ,(3, '2013-12-03')
    ,(4, '2013-12-04')
    ,(5, '2013-12-05')
    ,(6, '2013-12-06')
    ,(7, '2013-12-07')
GO

-- temporary table for all pairs
DECLARE @Pairs TABLE
    ([ID] int
    ,[ParentID] int);

WITH Pairs ([ID], [ParentID]) AS
(
    SELECT    [ID], [ParentID]
    FROM    [List]
    WHERE    [ParentID] IS NOT NULL
    UNION ALL
    SELECT    t1.[ID], t2.[ParentID]
    FROM    List t1
            INNER JOIN Pairs t2 ON t1.[ParentID] = t2.[ID] AND t1.[ParentID] <> t1.[ID] AND t2.[ParentID] <> t2.[ID]
)
INSERT INTO @Pairs
SELECT *
FROM Pairs

-- test all ordered pairs
SELECT *
FROM @Pairs
-- uncomment in order to see all circular references
--WHERE [id] = [parentid]
ORDER BY [id]

-- select representatives from each loop by minimum id
SELECT     DISTINCT MIN(t1.[ParentID])
FROM     @Pairs AS t1
WHERE     t1.[ID] IN (SELECT s1.[ID] FROM @Pairs AS s1 WHERE s1.[ID] = s1.[ParentID])
GROUP BY t1.[ID]

-- select representatives from each loop by maximum time from referenced table
SELECT DISTINCT
        (SELECT  TOP(1) s1.[ParentID]
         FROM     @Pairs AS s1
                 LEFT OUTER JOIN dbo.[ListTime] AS s2 ON s1.[ParentID] = s2.[ListID]
        WHERE     s1.id = t1.id
        ORDER BY ISNULL(s2.[CurrentDate], DATEADD(MS, s1.[ParentID], '1900-01-01')) DESC) AS [Origin]
FROM    @Pairs AS t1
WHERE    t1.[ID] = t1.[ParentID]

  1. All used IP-addresses, names of servers, workstations, domains, are fictional and are used exclusively as a demonstration only.
  2. Information is provided «AS IS».

Leave a Reply

Your email address will not be published. Required fields are marked *