Technical Article

Check FK Violations

,

Identify orphaned records in a datebase.

You may use this script to indetify orphaned record in a database.

This script generate some sql queries that can help you to take a decision regarding orphaned records.

 

Best regards,
Lungeanu Adrian-Liviu

/************************************************************************ Documentation header *** 
Module : [ERP.Tools] 

Author : [Lungeanu Adrian-Liviu] 
Created: [2008.06.03] 
Summary: [Check FK Violations] 
[SQL 2000]

== Change log == 
Author : []
Date : []
Summary: []
*** Documentation header *************************************************************************/ 

--// Set environment
SET NOCOUNT ON
SET ANSI_WARNINGS OFF

--// Declare variables
DECLARE 
 @tbl_ForeignKey nvarchar(255)
, @tbl_LastForeignKey nvarchar(255)
, @tbl_TableSchema nvarchar(255)
, @tbl_LastTableSchema nvarchar(255)
, @tbl_TableName nvarchar(255)
, @tbl_LastTableName nvarchar(255)
, @tbl_ColumnName nvarchar(255)
, @tbl_LastColumnName nvarchar(255)
, @tbl_ReferenceTableNameSchema nvarchar(255)
, @tbl_LastReferenceTableNameSchema nvarchar(255)
, @tbl_ReferenceTableName nvarchar(255)
, @tbl_LastReferenceTableName nvarchar(255)
, @tbl_ReferenceColumnName nvarchar(255)
, @tbl_LastReferenceColumnName nvarchar(255)
, @tbl_UpdateAction nvarchar(255)
, @tbl_LastUpdateAction nvarchar(255)
, @tbl_DeleteAction nvarchar(255)
, @tbl_LastDeleteAction nvarchar(255)
, @statement_init nvarchar(800)
, @statement_last nvarchar(800)????
, @statement_final nvarchar(800)
, @crlf nchar(2)
, @RecordsNumber bigint
, @ParmDefinition nvarchar(255)
, @MaxRows int
, @RowCnt int 

--// Declare table variables
DECLARE @FKTable TABLE 
( 
rownum int IDENTITY (1, 1) PRIMARY KEY NOT NULL
, tbl_ForeignKey nvarchar(255)
, tbl_TableSchema nvarchar(255)
, tbl_TableName nvarchar(255)
, tbl_ColumnName nvarchar(255)
, tbl_ReferenceTableNameSchema nvarchar(255)
, tbl_ReferenceTableName nvarchar(255)
, tbl_ReferenceColumnName nvarchar(255)
, tbl_UpdateAction nvarchar(255)
, tbl_DeleteAction nvarchar(255)
) 

--// Initialize variables
SET @statement_init = N''
SET @statement_last = N''
SET @statement_final = N''
SET @tbl_LastForeignKey = N''
SET @tbl_LastTableSchema = N''
SET @tbl_LastTableName = N''
SET @tbl_LastColumnName = N''
SET @tbl_LastReferenceTableNameSchema = N''
SET @tbl_LastReferenceTableName = N''
SET @tbl_LastReferenceColumnName = N''
SET @tbl_LastUpdateAction = N''
SET @tbl_LastDeleteAction = N''
SET @ParmDefinition = N'@RecordsOUT bigint OUTPUT' --// In @RecordsOUT we receive the output of sp_executesql 
SET @RecordsNumber = 0
SET @crlf = nchar(13) + nchar(10) 
SET @RowCnt = 1

--// Declare cursor that store FK references
INSERT INTO @FKTable (tbl_ForeignKey, tbl_TableSchema, tbl_TableName, tbl_ColumnName, tbl_ReferenceTableNameSchema, tbl_ReferenceTableName, tbl_ReferenceColumnName, tbl_UpdateAction, tbl_DeleteAction)
SELECT --sc.constid [FKId]
-- , sc.colid
so.name [ForeignKey]
, 'dbo' [TableSchema]
-- , sc.id [FKTableId]
, so2.name [TableName]
-- , sfk.fkey [FKColumnId]
, sco.name [ColumnName]
, 'dbo' AS [ReferenceTableNameSchema]
-- , sfk.fkeyid
-- , sfk.rkeyid [PKTableId]
, so3.name [ReferenceTableName]
-- , sfk.rkey [PKColumnId]
, sco2.name [ReferenceColumnName]
, CASE objectproperty(sc.constid, 'CnstIsUpdateCascade') WHEN 0 THEN 'NO ACTION' WHEN 1 THEN 'CASCADE' WHEN 2 THEN 'SET NULL' WHEN 3 THEN 'SET DEFAULT' ELSE '' END [UpdateAction]
, CASE objectproperty(sc.constid, 'CnstIsDeleteCascade') WHEN 0 THEN 'NO ACTION' WHEN 1 THEN 'CASCADE' WHEN 2 THEN 'SET NULL' WHEN 3 THEN 'SET DEFAULT' ELSE '' END [DeleteAction]
-- , CASE objectproperty(sc.constid, 'CnstIsNotRepl') WHEN 1 THEN 'NOT FOR REPLICATION' ELSE '' END AS [EnforceForReplication]
-- , sfk.keyno
FROM sysconstraints sc 
INNER JOIN sysobjects so
ON so.id = sc.constid
INNER JOIN sysobjects so2
ON so2.id = sc.id 
INNER JOIN sysforeignkeys sfk
ON sfk.constid = sc.constid 
INNER JOIN sysobjects so3
ON so3.id = sfk.rkeyid 
INNER JOIN syscolumns sco
ON sco.id = sc.id AND
sco.colid = sfk.fkey
INNER JOIN syscolumns sco2
ON sco2.id = sfk.rkeyid AND
sco2.colid = sfk.rkey
WHERE (sc.status & 3) = 3
ORDER BY so.name ASC
, sfk.keyno ASC 


--// OPEN cursor
SELECT @MaxRows = COUNT(*)
FROM @FKTable

--// Check if we have what to parse, if not exit
IF @MaxRows = 0
BEGIN
PRINT N'No ForeignKeys to process...'
GOTO EXIT_TAG
END

--//
WHILE @RowCnt <= @MaxRows
BEGIN --// Main loop

SELECT @tbl_ForeignKey = tbl_ForeignKey
, @tbl_TableSchema = tbl_TableSchema
, @tbl_TableName = tbl_TableName
, @tbl_ColumnName = tbl_ColumnName
, @tbl_ReferenceTableNameSchema = tbl_ReferenceTableNameSchema
, @tbl_ReferenceTableName = tbl_ReferenceTableName
, @tbl_ReferenceColumnName = tbl_ReferenceColumnName
, @tbl_UpdateAction = tbl_UpdateAction
, @tbl_DeleteAction = tbl_DeleteAction
FROM @FKTable
WHERE rownum = @RowCnt 

IF @tbl_LastForeignKey = @tbl_ForeignKey --// FK with more than a column
BEGIN
SET @statement_init = @statement_init + @crlf + N' AND aa.[' + @tbl_ColumnName + N'] = bb.[' + @tbl_ReferenceColumnName + N'] '
SET @statement_last = @statement_last + @crlf + N' AND aa.[' + @tbl_ColumnName + N'] IS NOT NULL '
END

ELSE --// We are on other FK
BEGIN
IF @tbl_LastForeignKey <> '' --// We avoid empty string
BEGIN --// Skipped once at begining
SET @statement_final = @statement_init + @crlf + N' WHERE bb.[' + @tbl_LastReferenceColumnName + N'] IS NULL ' + @crlf + @statement_last + N') '
SET @RecordsNumber = 0 --// Reset @RecordsNumber to 0 
EXEC dbo.sp_executesql @statement_final, @ParmDefinition, @RecordsOUT = @RecordsNumber OUTPUT
IF @RecordsNumber > 0
PRINT @crlf + N'--// FK Violation! (' + CAST(@RecordsNumber AS nvarchar(20)) + N') ' + 
@tbl_LastForeignKey + @crlf + REPLACE (@statement_final , N' @RecordsOUT = COUNT(*) ' , N' * ')
END

--// Save last record
SET @tbl_LastForeignKey = @tbl_ForeignKey
SET @tbl_LastTableSchema = @tbl_TableSchema
SET @tbl_LastTableName = @tbl_TableName
SET @tbl_LastColumnName = @tbl_ColumnName
SET @tbl_LastReferenceTableNameSchema = @tbl_ReferenceTableNameSchema
SET @tbl_LastReferenceTableName = @tbl_ReferenceTableName
SET @tbl_LastReferenceColumnName = @tbl_ReferenceColumnName
--SET @tbl_LastUpdateAction = @tbl_UpdateAction
--SET @tbl_LastDeleteAction = @tbl_DeleteAction

--// Prepare begining of next statement
SET @statement_init = N'SELECT @RecordsOUT = COUNT(*) ' + @crlf + 
N' FROM [' + @tbl_TableSchema + N'].[' + @tbl_TableName + N'] AS aa (NOLOCK) ' + @crlf + 
N' LEFT JOIN [' +@tbl_ReferenceTableNameSchema + N'].[' + @tbl_ReferenceTableName + N'] AS bb (NOLOCK) ' + @crlf + 
N' ON aa.[' + @tbl_ColumnName + N'] = bb.[' + @tbl_ReferenceColumnName + N'] '

--// Need this for fields that accept NULL values
SET @statement_last = N' AND (aa.[' + @tbl_ColumnName + N'] IS NOT NULL'
END

--// Increment @RowCnt
SELECT @RowCnt = @RowCnt + 1
END

--// Process last record
SET @statement_final = @statement_init + @crlf + N' WHERE bb.' + @tbl_LastReferenceColumnName + N' IS NULL ' + @crlf + @statement_last + N') '
SET @RecordsNumber = 0 --// set @RecordsNumber to 0 
EXEC dbo.sp_executesql @statement_final, @ParmDefinition, @RecordsOUT = @RecordsNumber OUTPUT
IF @RecordsNumber > 0
PRINT @crlf + N'--// FK Violation! (' + CAST(@RecordsNumber AS nvarchar(20)) + N') ' + 
@tbl_LastForeignKey + @crlf + REPLACE (@statement_final , N' @RecordsOUT = COUNT(*) ' , N' * ')

EXIT_TAG:
--// Restore environment
SET NOCOUNT OFF
SET ANSI_WARNINGS ON

Rate

4 (1)

You rated this post out of 5. Change rating

Share

Share

Rate

4 (1)

You rated this post out of 5. Change rating