byrmol Blog

Garbage

Determining RI Sequence

My project is getting ready to roll, so data versioning has commenced.. Backup, restore, BCP etc... Digging around in the toolkit, I dredge up an old procedure I wrote for load/unload in version 7.  It didn't have the “owner” of the table and avoided circular references.  So I set out to bring it up to speed ...

It should work in both 2000 and 2005 (and 7 when turned into a proc). Can someone give it a whirl on 2005 and tell me how it goes ? 'Cause if they have messed with the INFORMATION_SCHEMA views I 'll.........#%#%#$#$%................

CREATE FUNCTION RISequence()
RETURNS @RISEQUENCE TABLE (TABLE_NAME SYSNAME, PRIORITY SMALLINT NOT NULL, PRIMARY KEY(TABLE_NAME))
AS
--Returns The user tables and the "depth" of the table in the RI chain.
--Tables with no FK's are given a PRIORITY of 0
--Circular reference tables have a minimum PRIORITY of 1
BEGIN
--COUNTER
DECLARE @COUNTER INT, @PRIORITY INT
--RI Table Matching
DECLARE @RIMATCH TABLE (PK_TABLE SYSNAME NOT NULL, FK_TABLE SYSNAME NOT NULL)
--Populate @RIMATCH -- Remove Self referenced entities (WHERE expression)
INSERT @RIMATCH(PK_TABLE, FK_TABLE)
SELECT PK.TABLE_SCHEMA + '.' + PK.TABLE_NAME  AS PK_TABLE
 , FK.TABLE_SCHEMA + '.' + FK.TABLE_NAME AS FK_TABLE
FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS AS RC
INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS FK ON FK.CONSTRAINT_NAME = RC.CONSTRAINT_NAME
INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS PK ON PK.CONSTRAINT_NAME = RC.UNIQUE_CONSTRAINT_NAME
WHERE FK.TABLE_SCHEMA + FK.TABLE_NAME != PK.TABLE_SCHEMA + PK.TABLE_NAME

--No FK's (PRIORITY=0)
SET @PRIORITY = 0
INSERT @RISequence(TABLE_NAME, PRIORITY)
 SELECT TABLE_SCHEMA + '.' + TABLE_NAME, @PRIORITY
 FROM INFORMATION_SCHEMA.TABLES AS T
 WHERE TABLE_TYPE = 'BASE TABLE' AND TABLE_NAME != 'dtproperties'
  AND NOT EXISTS (SELECT 1 FROM @RIMATCH C WHERE C.FK_TABLE = T.TABLE_SCHEMA + '.' + T.TABLE_NAME)

--Circular Reference (CR) PRIORITY=1 ie The only FK is the circular FK
SET @PRIORITY = @PRIORITY + 1
INSERT @RISequence(TABLE_NAME, PRIORITY)
 SELECT S.FK_TABLE, @PRIORITY 
 FROM @RIMATCH S
 WHERE EXISTS (SELECT 1 FROM @RIMATCH X WHERE X.PK_TABLE = S.FK_TABLE AND X.FK_TABLE = S.PK_TABLE)
  AND NOT EXISTS (SELECT 1 FROM @RISequence C WHERE C.TABLE_NAME = S.FK_TABLE)
 GROUP BY S.FK_TABLE
 HAVING COUNT(*) = (SELECT COUNT(*) FROM @RIMATCH K WHERE S.FK_TABLE = K.FK_TABLE)


--Fudge a DO operation
SET @COUNTER = 1
WHILE @Counter > 0
BEGIN
 SET @COUNTER = 0
 --Standard RI
 INSERT @RISequence (TABLE_NAME, PRIORITY)
  SELECT K.FK_TABLE , @PRIORITY
  FROM @RIMATCH AS K
  INNER JOIN @RISequence O ON O.TABLE_NAME = K.PK_TABLE
  WHERE NOT EXISTS(SELECT 1 FROM @RISequence F WHERE K.FK_TABLE = F.TABLE_NAME)
  GROUP BY K.FK_TABLE
  HAVING COUNT(*) = (SELECT COUNT(*) FROM @RIMATCH S WHERE S.FK_TABLE = K.FK_TABLE)
 SET @COUNTER = CASE @@ROWCOUNT WHEN 0 THEN 0 ELSE 1 END
 
 --Because of the forced discovery of PRIORITY 1 CR, increment takes place here
 SET @PRIORITY = @PRIORITY + 1

 --Circular References with related RI at the current PRIORITY Level
 INSERT @RISequence(TABLE_NAME, PRIORITY)
  SELECT S.FK_TABLE, @PRIORITY
  FROM @RIMATCH S
  INNER JOIN @RIMATCH X ON X.PK_TABLE = S.FK_TABLE AND X.FK_TABLE = S.PK_TABLE
   WHERE NOT EXISTS (SELECT 1 FROM @RISequence W WHERE S.FK_TABLE  = W.TABLE_NAME )
  AND EXISTS (SELECT 1 FROM @RIMATCH Z WHERE S.PK_TABLE = Z.FK_TABLE
    AND Z.PK_TABLE != S.FK_TABLE
    AND EXISTS (SELECT 1 from @RISequence A WHERE Z.PK_TABLE = A.TABLE_NAME))
 SET @COUNTER = @COUNTER + CASE @@ROWCOUNT WHEN 0 THEN 0 ELSE 1 END
END
RETURN
END
GO

--In Use
SELECT * from dbo.RISequence() ORDER BY PRIORITY, TABLE_NAME
--DROP FUNCTION RISequence

Enjoy!

Legacy Comments


Tim Toennies
2005-10-27
re: Determining RI Sequence
Looks like it works on the September SQL2005 CTP. Nice work.