744 / 5 000
Most of the queries provided in these answers probably overlook the fact that a referential integrity constraint (FK) can consist of multiple columns!
Some use uppercase names, while the SQL identifiers (table names, column names, etc.) of system objects are lowercase, which can be problematic in an instance with CASE-SENSITIVE collation.
Others cast object names by limiting them to 30 characters, whereas the standard SQL and SQL Server uses 128 characters.
Some other omits the management clause ON UPDATE/DELETE...
The query below lists all FK constraints for all tables in the current database, along with the list of foreign key columns and the list of reference keys (PK or UNIQUE).
SELECT sfk.name AS FK_TABLE_SCHEMA, fkt.name AS FK_TABLE_NAME,
fk.name AS FK_CONSTRAINT_NAME,
substring(CFK.column_names, 1, len(CFK.column_names)-1) AS FK_TARGET_COLUMN_LIST,
spk.name AS REF_TABLE_SCHEMA, pkt.name AS REF_TABLE_NAME,
substring(CPK.column_names, 1, len(CPK.column_names)-1) AS PK_SOURCE_COLUMN_LIST,
delete_referential_action_desc AS DELETE_ACTION,
update_referential_action_desc AS UPDATE_ACTION
FROM sys.foreign_keys fk
INNER JOIN sys.tables fkt
ON fkt.object_id = fk.parent_object_id
INNER JOIN sys.schemas AS sfk
ON fkt.schema_id = sfk.schema_id
INNER JOIN sys.tables pkt
ON pkt.object_id = fk.referenced_object_id
INNER JOIN sys.schemas AS spk
ON pkt.schema_id = spk.schema_id
CROSS APPLY (SELECT QUOTENAME(col.name) + ', '
FROM sys.foreign_key_columns AS fkcol
INNER JOIN sys.columns AS col
ON fkcol.parent_object_id = col.object_id
and fkcol.parent_column_id = col.column_id
where fkcol.parent_object_id = fkt.object_id
and fkcol.constraint_object_id = fk.object_id
order by fkcol.constraint_column_id
for xml path ('') ) AS CFK (column_names)
CROSS APPLY (select QUOTENAME(col.name) + ', '
from sys.foreign_key_columns pkcol
inner join sys.columns col
on pkcol.referenced_object_id = col.object_id
and pkcol.referenced_column_id = col.column_id
where pkcol.referenced_object_id = pkt.object_id
and pkcol.constraint_object_id = fk.object_id
order by pkcol.constraint_column_id
for xml path ('') ) AS CPK (column_names);
You can test this query with the following tables :
CREATE TABLE A
(
CA int NOT NULL,
CB int NOT NULL,
CC int NOT NULL,
PRIMARY KEY (CB, CA, CC)
);
CREATE TABLE B
(
XX int NOT NULL,
YY int NOT NULL,
ZZ int NOT NULL,
FOREIGN KEY(ZZ, XX, YY) REFERENCES A (CB, CA, CC)
);
To have the DROP CONSTRAINT and, if you want, the ADD CONSTRAINT for this list, you can use the CTE :
WITH
T0 AS
(
)
SELECT N'ALTER TABLE ' + QUOTENAME(FK_TABLE_SCHEMA) + N'.' + QUOTENAME(FK_TABLE_NAME)
+ N' DROP CONSTRAINT ' + QUOTENAME(FK_CONSTRAINT_NAME) + ';' AS DROP_FK,
N'ALTER TABLE ' + QUOTENAME(FK_TABLE_SCHEMA) + N'.' + QUOTENAME(FK_TABLE_NAME)
+ N' ADD CONSTRAINT ' + QUOTENAME(FK_CONSTRAINT_NAME) + N'(' + FK_TARGET_COLUMN_LIST + N')'
+ N' REFERENCES ' + QUOTENAME(REF_TABLE_SCHEMA) + N'.' + QUOTENAME(REF_TABLE_NAME) + N')'
+ N' ON UPDATE ' + UPDATE_ACTION COLLATE database_default
+ N' ON DELETE ' + DELETE_ACTION COLLATE database_default
+ ';' AS ADD_FK
FROM T0;
And add the firts query between the parenthesis of the CTE...
Another solution is to use the standard ISO SQL views of the INFORMATION_SCHEMA SQL schema and standardized functionnality (no CROSS APPLY as an example) :
WITH
T0 AS
(
SELECT fkt.TABLE_SCHEMA AS FK_TABLE_SCHEMA, fkt.TABLE_NAME AS FK_TABLE_NAME,
fk.CONSTRAINT_NAME AS FK_CONSTRAINT_NAME,
pkt.TABLE_SCHEMA AS REF_TABLE_SCHEMA, pkt.TABLE_NAME AS REF_TABLE_NAME,
pkt.CONSTRAINT_NAME AS REF_CONSTRAINT_NAME,
fk.DELETE_RULE AS DELETE_ACTION,
fk.UPDATE_RULE AS UPDATE_ACTION,
(SELECT COUNT(*)
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS KCU
WHERE KCU.CONSTRAINT_SCHEMA = fkt.TABLE_SCHEMA
AND KCU.CONSTRAINT_NAME = fk.CONSTRAINT_NAME) AS N
FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS fk
INNER JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS fkt
ON fkt.CONSTRAINT_SCHEMA = fk.CONSTRAINT_SCHEMA
AND fkt.CONSTRAINT_NAME = fk.CONSTRAINT_NAME
INNER JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS pkt
ON pkt.CONSTRAINT_SCHEMA = fk.UNIQUE_CONSTRAINT_SCHEMA
AND pkt.CONSTRAINT_NAME = fk.UNIQUE_CONSTRAINT_NAME
),
-- recursive query to list columns of FK
FKC AS
(
SELECT KCU.CONSTRAINT_SCHEMA , KCU.CONSTRAINT_NAME, KCU.ORDINAL_POSITION,
CAST(KCU.COLUMN_NAME AS NVARCHAR(max)) AS FK_COLS
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS KCU
JOIN T0
ON KCU.CONSTRAINT_SCHEMA = T0.FK_TABLE_SCHEMA
AND KCU.CONSTRAINT_NAME = T0.FK_CONSTRAINT_NAME
WHERE ORDINAL_POSITION = 1
UNION ALL
SELECT KCU.CONSTRAINT_SCHEMA , KCU.CONSTRAINT_NAME, KCU.ORDINAL_POSITION,
FKC.FK_COLS + ', ' + KCU.COLUMN_NAME
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS KCU
JOIN T0
ON KCU.CONSTRAINT_SCHEMA = T0.FK_TABLE_SCHEMA
AND KCU.CONSTRAINT_NAME = T0.FK_CONSTRAINT_NAME
JOIN FKC
ON FKC.CONSTRAINT_SCHEMA = KCU.CONSTRAINT_SCHEMA
AND FKC.CONSTRAINT_NAME = KCU.CONSTRAINT_NAME
AND FKC.ORDINAL_POSITION + 1 = KCU.ORDINAL_POSITION
),
-- recursive query to list columns of PK or UK
PKC AS
(
SELECT KCU.CONSTRAINT_SCHEMA , KCU.CONSTRAINT_NAME, KCU.ORDINAL_POSITION,
CAST(KCU.COLUMN_NAME AS NVARCHAR(max)) AS PK_COLS
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS KCU
JOIN T0
ON KCU.CONSTRAINT_SCHEMA = T0.REF_TABLE_SCHEMA
AND KCU.CONSTRAINT_NAME = T0.REF_CONSTRAINT_NAME
WHERE ORDINAL_POSITION = 1
UNION ALL
SELECT KCU.CONSTRAINT_SCHEMA , KCU.CONSTRAINT_NAME, KCU.ORDINAL_POSITION,
PKC.PK_COLS + ', ' + KCU.COLUMN_NAME
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS KCU
JOIN T0
ON KCU.CONSTRAINT_SCHEMA = T0.REF_TABLE_SCHEMA
AND KCU.CONSTRAINT_NAME = T0.REF_CONSTRAINT_NAME
JOIN PKC
ON PKC.CONSTRAINT_SCHEMA = KCU.CONSTRAINT_SCHEMA
AND PKC.CONSTRAINT_NAME = KCU.CONSTRAINT_NAME
AND PKC.ORDINAL_POSITION + 1 = KCU.ORDINAL_POSITION
)
SELECT FK_TABLE_SCHEMA, FK_TABLE_NAME, FK_CONSTRAINT_NAME,
FK_COLS,
REF_TABLE_SCHEMA, REF_TABLE_NAME,
PK_COLS,
UPDATE_ACTION,
DELETE_ACTION
FROM T0
JOIN FKC
ON T0.FK_TABLE_SCHEMA = FKC.CONSTRAINT_SCHEMA
AND T0.FK_CONSTRAINT_NAME = FKC.CONSTRAINT_NAME
JOIN PKC
ON T0.REF_TABLE_SCHEMA = PKC.CONSTRAINT_SCHEMA
AND T0.REF_CONSTRAINT_NAME = PKC.CONSTRAINT_NAME
WHERE N = FKC.ORDINAL_POSITION AND N = PKC.ORDINAL_POSITION;