Find representatives of circilar 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, listed in the another table.

************************ Representatives.sql *************************
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]

************************ End of Representatives.sql, v.2 *************************


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

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.