Je veux un moyen d'établir quelles colonnes d'une base de données donnée sont jointes via des relations PK/FK. Je peux retourner les informations PK/FK pour une table donnée via
SELECT *
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS cu
WHERE EXISTS (
SELECT tc.*
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS tc
WHERE tc.CONSTRAINT_CATALOG = 'MyDatabase'
AND tc.TABLE_NAME = 'MyTable'
/*AND tc.CONSTRAINT_TYPE = 'PRIMARY KEY'*/
AND tc.CONSTRAINT_NAME = cu.CONSTRAINT_NAME);
GO
mais pour un PK renvoyé à partir d'une telle requête, comment puis-je établir le FK associé (en supposant qu'il y en ait un)?
Je sais que vous pouvez également obtenir les tables référencées via:
SELECT CONSTRAINT_NAME = name,
FOREIGN_SCHEMA = OBJECT_SCHEMA_NAME(parent_object_id),
FOREIGN_TABLE = OBJECT_NAME(parent_object_id),
REFERENCED_SCHEMA = OBJECT_SCHEMA_NAME(referenced_object_id),
REFERENCED_TABLE = OBJECT_NAME(referenced_object_id)
FROM sys.foreign_keys
WHERE OBJECT_NAME(referenced_object_id) = 'MyTable';
GO
mais je me bats maintenant pour obtenir les références explicites des colonnes.
Je crée un générateur de script pour QlikView. Pour générer le script j'ai besoin des contraintes et des liens associés. J'ai besoin de toutes les informations de contrainte pour une colonne donnée (le cas échéant).
Je veux construire une classe de base de données qui contient toutes les informations d'une base de données donnée. Cette structure de classe database.table.column.constraints
sera ensuite utilisé pour obtenir les correspondances entre les différentes colonnes sur PK/FK.
Il est clair que certaines colonnes auront uniquement des FK et dans ce cas, je veux également récupérer les informations PK de la clé correspondante; certains n'auront que des PK et je veux l'inverse. Certains peuvent bien sûr avoir les deux.
Voici une requête simple pour faire correspondre les clés étrangères à leurs tables/colonnes référencées:
SELECT
o1.name AS FK_table,
c1.name AS FK_column,
fk.name AS FK_name,
o2.name AS PK_table,
c2.name AS PK_column,
pk.name AS PK_name,
fk.delete_referential_action_desc AS Delete_Action,
fk.update_referential_action_desc AS Update_Action
FROM sys.objects o1
INNER JOIN sys.foreign_keys fk
ON o1.object_id = fk.parent_object_id
INNER JOIN sys.foreign_key_columns fkc
ON fk.object_id = fkc.constraint_object_id
INNER JOIN sys.columns c1
ON fkc.parent_object_id = c1.object_id
AND fkc.parent_column_id = c1.column_id
INNER JOIN sys.columns c2
ON fkc.referenced_object_id = c2.object_id
AND fkc.referenced_column_id = c2.column_id
INNER JOIN sys.objects o2
ON fk.referenced_object_id = o2.object_id
INNER JOIN sys.key_constraints pk
ON fk.referenced_object_id = pk.parent_object_id
AND fk.key_index_id = pk.unique_index_id
ORDER BY o1.name, o2.name, fkc.constraint_column_id
La sortie comporte huit colonnes: les noms de table et de colonne pour les clés étrangères (FK_table, FK_column), les noms des contraintes de clé étrangère (FK_name), le PK référencé ou les noms de table et de colonne d'index unique (PK_table, PK_column), le nom du PK référencé ou de l'index unique (PK_name) et les actions de mise à jour/suppression en cascade (Delete_Action, Update_Action).
(Modifié pour ajouter des colonnes de sortie supplémentaires.)
EDIT: Je suis de retour 6 ans plus tard avec une version améliorée de cela. J'ai réalisé que la requête d'origine ne gérait pas vraiment bien les clés étrangères multi-colonnes, et je voulais également pouvoir identifier rapidement les clés étrangères désactivées, non approuvées ou non indexées. Voici donc la nouvelle version qui corrige tout cela.
Les clés multi-colonnes sont affichées sous forme de listes séparées par des virgules dans FK_columns
et PK_columns
, en utilisant le traditionnel FOR XML
/STUFF
abus. Le FK_indexes
La colonne affiche les noms de tous les index de la table de clé étrangère qui pourraient potentiellement être utilisés pour satisfaire les recherches utilisant les colonnes de clé étrangère (principalement pour optimiser les suppressions ou les mises à jour de la table de clé primaire). Si c'est NULL
, alors vous avez une clé étrangère non indexée. Vous pouvez modifier le ORDER BY
, ou ajoutez une clause WHERE
(commentée ci-dessous) si vous souhaitez trier par nom de table PK, filtrer pour des tables PK/FK spécifiques, etc.
SELECT
fk.is_disabled,
fk.is_not_trusted,
OBJECT_SCHEMA_NAME(o1.object_id) AS FK_schema,
o1.name AS FK_table,
--Generate list of columns in referring side of foreign key
STUFF(
(
SELECT ', ' + c1.name AS [text()]
FROM sys.columns c1 INNER
JOIN sys.foreign_key_columns fkc
ON c1.object_id = fkc.parent_object_id
AND c1.column_id = fkc.parent_column_id
WHERE fkc.constraint_object_id = fk.object_id
FOR XML PATH('')
), 1, 2, '') AS FK_columns,
--Look for any indexes that will fully satisfy the foreign key columns
STUFF(
(
SELECT ', ' + i.name AS [text()]
FROM sys.indexes i
WHERE i.object_id = o1.object_id
AND NOT EXISTS ( --Find foreign key columns that don't match the index key columns
SELECT fkc.constraint_column_id, fkc.parent_column_id
FROM sys.foreign_key_columns fkc
WHERE fkc.constraint_object_id = fk.object_id
EXCEPT
SELECT ic.key_ordinal, ic.column_id
FROM sys.index_columns ic
WHERE ic.object_id = i.object_id AND ic.index_id = i.index_id
)
FOR XML PATH('')
), 1, 2, '') AS FK_indexes,
fk.name AS FK_name,
OBJECT_SCHEMA_NAME(o2.object_id) AS PK_schema,
o2.name AS PK_table,
--Generate list of columns in referenced (i.e. PK) side of foreign key
STUFF(
(
SELECT ', ' + c2.name AS [text()]
FROM sys.columns c2
INNER JOIN sys.foreign_key_columns fkc
ON c2.object_id = fkc.referenced_object_id
AND c2.column_id = fkc.referenced_column_id
WHERE fkc.constraint_object_id = fk.object_id
FOR XML PATH('')
), 1, 2, '') AS PK_columns,
pk.name AS PK_name,
fk.delete_referential_action_desc AS Delete_Action,
fk.update_referential_action_desc AS Update_Action
FROM sys.objects o1
INNER JOIN sys.foreign_keys fk
ON o1.object_id = fk.parent_object_id
INNER JOIN sys.objects o2
ON fk.referenced_object_id = o2.object_id
INNER JOIN sys.key_constraints pk
ON fk.referenced_object_id = pk.parent_object_id
AND fk.key_index_id = pk.unique_index_id
--WHERE o2.name = 'Company_Address'
ORDER BY o1.name, o2.name
Cette requête vous met en réseau toutes les relations FK dans la base de données - nom de contrainte FK, schéma/table de table de référence, nom de colonne de référence, schéma/table de table référencée et nom de colonne référencée. Il y aura plusieurs lignes pour une contrainte multi-colonnes.
SELECT
FK = OBJECT_NAME(pt.constraint_object_id),
Referencing_table = QUOTENAME(OBJECT_SCHEMA_NAME(pt.parent_object_id))
+ '.' + QUOTENAME(OBJECT_NAME(pt.parent_object_id)),
Referencing_col = QUOTENAME(pc.name),
Referenced_table = QUOTENAME(OBJECT_SCHEMA_NAME(pt.referenced_object_id))
+ '.' + QUOTENAME(OBJECT_NAME(pt.referenced_object_id)),
Referenced_col = QUOTENAME(rc.name)
FROM sys.foreign_key_columns AS pt
INNER JOIN sys.columns AS pc
ON pt.parent_object_id = pc.[object_id]
AND pt.parent_column_id = pc.column_id
INNER JOIN sys.columns AS rc
ON pt.referenced_column_id = rc.column_id
AND pt.referenced_object_id = rc.[object_id]
ORDER BY Referencing_table, FK, pt.constraint_column_id;
Si vous recherchez les colonnes d'une contrainte de clé primaire spécifique et que vous connaissez déjà le nom de cette contrainte PK, vous pouvez écrire ceci:
DECLARE @PK_Constraint SYSNAME = N'Name of PK constraint';
SELECT
FK = OBJECT_NAME(fkc.constraint_object_id),
Referencing_table = QUOTENAME(OBJECT_SCHEMA_NAME(fkc.parent_object_id))
+ '.' + QUOTENAME(OBJECT_NAME(fkc.parent_object_id)),
Referencing_col = QUOTENAME(pc.name),
Referenced_table = QUOTENAME(OBJECT_SCHEMA_NAME(fkc.referenced_object_id))
+ '.' + QUOTENAME(OBJECT_NAME(fkc.referenced_object_id)),
Referenced_col = QUOTENAME(rc.name)
FROM sys.foreign_key_columns AS fkc
INNER JOIN sys.columns AS pc
ON fkc.parent_object_id = pc.[object_id]
AND fkc.parent_column_id = pc.column_id
INNER JOIN sys.columns AS rc
ON fkc.referenced_column_id = rc.column_id
AND fkc.referenced_object_id = rc.[object_id]
WHERE EXISTS
(
SELECT 1 FROM sys.indexes AS i
INNER JOIN sys.foreign_keys AS fk
ON i.[object_id] = fk.referenced_object_id
AND i.index_id = fk.key_index_id
AND fk.[object_id] = fkc.constraint_object_id
AND i.name = @PK_Constraint
)
ORDER BY Referencing_table, FK, fkc.constraint_column_id;
Si vous souhaitez simplement inclure le nom PK avec les autres informations:
SELECT
FK = OBJECT_NAME(fkc.constraint_object_id),
Referencing_table = QUOTENAME(OBJECT_SCHEMA_NAME(fkc.parent_object_id))
+ '.' + QUOTENAME(OBJECT_NAME(fkc.parent_object_id)),
Referencing_col = QUOTENAME(pc.name),
Referenced_table = QUOTENAME(OBJECT_SCHEMA_NAME(fkc.referenced_object_id))
+ '.' + QUOTENAME(OBJECT_NAME(fkc.referenced_object_id)),
Referenced_col = QUOTENAME(rc.name),
PK = pk.name
FROM sys.foreign_key_columns AS fkc
INNER JOIN sys.columns AS pc
ON fkc.parent_object_id = pc.[object_id]
AND fkc.parent_column_id = pc.column_id
INNER JOIN sys.columns AS rc
ON fkc.referenced_column_id = rc.column_id
AND fkc.referenced_object_id = rc.[object_id]
INNER JOIN (SELECT i.name, fk.[object_id]
FROM sys.indexes AS i
INNER JOIN sys.foreign_keys AS fk
ON i.[object_id] = fk.referenced_object_id
AND i.index_id = fk.key_index_id
) AS pk
ON pk.[object_id] = fkc.constraint_object_id
ORDER BY Referencing_table, FK, fkc.constraint_column_id;
Il existe également des astuces pour obtenir la liste des colonnes dans, disons, une liste séparée par des virgules ou des colonnes individuelles, au lieu d'être réparties sur plusieurs lignes, mais je ne vais pas investir dans la modification de ces requêtes pour produire cela jusqu'à ce que je sache exactement quelle forme vous êtes après.