Scripting out composite foreign keys

For a while now, we have been maintaining our production database which feeds thousand of websites by utilizing home grown log shipping process + sync process. Our log shipping process occurs every 15 minutes, and sync process occurs every week. This sync process ensures that all the websites has the most current information, and fix any errors from the past, and expire old products. Before we sync the old database, we truncate/delete all the tables. We use a combination of:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
EXEC sp_MSForEachTable 'ALTER TABLE ? NOCHECK CONSTRAINT ALL'
GO
 
EXEC sp_MSForEachTable --'DELETE TABLE ?'
'BEGIN TRY
TRUNCATE TABLE ?
END TRY
BEGIN CATCH
DELETE FROM ?
END CATCH;' 
GO
 
-- enable referential integrity again
EXEC sp_MSForEachTable 'ALTER TABLE ? CHECK CONSTRAINT ALL'
GO

Although this disables all the constraint and allows you to delete from tables where foreign key is present, it still does not allow you to truncate the tables (parent tables) until the child table data is deleted. I decided to tweak this process a bit so that we could use the unlogged truncate operation in order to save time and decrease the resources needed.

I found a lot of scripts to script out the foreign keys, but I could not find any which worked on a composite key; because the scripts would provide column names for the same relationships in different rows. So, decided to write a script which will do this. Please freely use this, modify it to your needs, and let me know if there are any bugs so that I can update them.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
 
--Build Common Table Expression which can be used multiple times
;WITH table_cte(ForeignKeyName, parentTableName, primaryKeyList, ChildTableName, ForeignKeyList)
AS
(
SELECT DISTINCT
        a.name AS ForeignKeyName 
        , OBJECT_NAME(a.referenced_object_id) AS parentTableName
        , d.name AS PrimaryKeyList 
        , OBJECT_NAME(a.parent_object_id) AS ChildTableName
        , c.name AS ForeignKeyList
 
FROM    sys.foreign_keys a
        JOIN sys.foreign_key_columns b ON a.object_id = b.constraint_object_id
        JOIN sys.COLUMNS c ON b.parent_column_id = c.column_id
                              AND a.parent_object_id = c.object_id
        JOIN sys.COLUMNS d ON b.referenced_column_id = d.column_id
                              AND a.referenced_object_id = d.object_id
 
)
SELECT DISTINCT ForeignKeyName ,
        parentTableName ,
        --primaryKeyList ,
        ChildTableName ,
        --ForeignKeyList,
         PrimaryKeys = LEFT(col.list, LEN(col.list) -1)
        , ForeignKeys = LEFT(ab.list, LEN(ab.list) -1)
INTO #temp
FROM table_cte t
CROSS APPLY
(
       SELECT QuoteNAME(CONVERT(VARCHAR(100), d.Name)) + ', ' AS [text()]
       FROM   sys.foreign_keys a
       JOIN sys.foreign_key_columns b
                  ON a.object_id=b.constraint_object_id
       JOIN sys.COLUMNS c
                  ON b.parent_column_id = c.column_id
                AND a.parent_object_id=c.object_id
       JOIN sys.COLUMNS d
                  ON b.referenced_column_id = d.column_id
               AND a.referenced_object_id = d.object_id
          WHERE t.ForeignKeyName = a.name
          ORDER BY b.constraint_object_id
           FOR XML PATH('')
) AS col(list)
CROSS APPLY
(
 
       SELECT QuoteNAME(CONVERT(VARCHAR(100), c.Name)) + ', ' AS [text()]
       FROM   sys.foreign_keys a
       JOIN sys.foreign_key_columns b
                  ON a.object_id=b.constraint_object_id
       JOIN sys.COLUMNS c
                  ON b.parent_column_id = c.column_id
                AND a.parent_object_id=c.object_id
       JOIN sys.COLUMNS d
                  ON b.referenced_column_id = d.column_id
               AND a.referenced_object_id = d.object_id
          WHERE t.ForeignKeyName = a.name
           ORDER BY b.constraint_object_id
           FOR XML PATH('')
) AS ab(list)
 
SELECT 'ALTER TABLE ' + ChildTableName + ' WITH CHECK ADD CONSTRAINT ' + ForeignKeyName + ' FOREIGN KEY (' + ForeignKeys + ') REFERENCES ' + ParentTableName + ' (' + PrimaryKeys + ')'
,ForeignKeyName ,
        parentTableName ,
        ChildTableName ,
        PrimaryKeys ,
        ForeignKeys
FROM #temp
 
 
--Work completed. Drop table.
DROP TABLE #temp

You can find the drop constraint scripts all throughout. But I could not find one which allows you to script out composite foreign keys.

Please be sure to make a copy of all the constraints above before you go and run the drop statements from below:

1
2
3
select 'ALTER TABLE ' + table_name + ' DROP CONSTRAINT ' + CONSTRAINT_NAME
from INFORMATION_SCHEMA.TABLE_CONSTRAINTS
WHERE CONSTRAINT_TYPE = 'Foreign Key'

Happy T-SQLing!!

2 thoughts on “Scripting out composite foreign keys

  1. Mike Motyl

    EXCELENT SCRIPT!

    I ran in to the exact same problem where the scripts I was finding were not handling composite keys. Your script was 95% of what. I just needed to add 3 things to get the other 5%.

    1) When I ran the script I was getting an extra comma at the end of each column so I changed:
    SELECT QuoteNAME(CONVERT(VARCHAR(100), d.Name)) + ‘, ‘ AS [text()]
    TO
    SELECT COALESCE(QuoteNAME(CONVERT(VARCHAR(100), d.Name)) + ‘,’,”) AS [text()]

    AND

    SELECT QuoteNAME(CONVERT(VARCHAR(100), c.Name)) + ‘, ‘ AS [text()]
    TO
    SELECT COALESCE(QuoteNAME(CONVERT(VARCHAR(100), c.Name)) + ‘,’,”) AS [text()]

    2) I took into account [delete_referential_action] and [delete_referential_action_desc] from [sys.foreign_keys] so I could know whether or not to add ‘ON DELETE’ to the ADD statement.

    3) I included the schema name in the script

    Final product was:

    ALTER TABLE [dbo].[ChildTable] WITH CHECK ADD CONSTRAINT FK_Blah FOREIGN KEY ([Column1],[Column2]) REFERENCES [dbo].[ParentTable] ([Column1],[Column2]) ON DELETE CASCADE

    In the end, I created a Table-Valued Function that in addition to the ADD statement also generated a DROP statement so I could do something like:

    SELECT SchemaName, ParentTable, ChildTable, DropStatement, AddStatement
    FROM udfUtility_ScriptForeignKeyAlterStatements()
    WHERE ParentTable = ‘Blah’

    Thanks for posting your script!

Leave a Reply

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

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

What is 10 + 4 ?
Please leave these two fields as-is:
IMPORTANT! To be able to proceed, you need to solve the following simple math (so we know that you are a human) :-)