48

In SQL Server, how can I get the referenced table + column name from a foreign key?

Note: Not the table/column where the key is in, but the key it refers to.

Example:

When the key [FA_MDT_ID] in table [T_ALV_Ref_FilterDisplay]. refers to [T_AP_Ref_Customer].[MDT_ID]

such as when creating a constraint like this:

ALTER TABLE [dbo].[T_ALV_Ref_FilterDisplay]  WITH CHECK ADD  CONSTRAINT [FK_T_ALV_Ref_FilterDisplay_T_AP_Ref_Customer] FOREIGN KEY([FA_MDT_ID])
REFERENCES [dbo].[T_AP_Ref_Customer] ([MDT_ID])
GO

I need to get [T_AP_Ref_Customer].[MDT_ID] when given [T_ALV_Ref_FilterAnzeige].[FA_MDT_ID] as input

Stefan Steiger
  • 78,642
  • 66
  • 377
  • 442

4 Answers4

95

Never mind, this is the correct answer:
http://msdn.microsoft.com/en-us/library/aa175805(SQL.80).aspx

SELECT 
     KCU1.CONSTRAINT_SCHEMA AS FK_CONSTRAINT_SCHEMA 
    ,KCU1.CONSTRAINT_NAME AS FK_CONSTRAINT_NAME 
    ,KCU1.TABLE_SCHEMA AS FK_TABLE_SCHEMA 
    ,KCU1.TABLE_NAME AS FK_TABLE_NAME 
    ,KCU1.COLUMN_NAME AS FK_COLUMN_NAME 
    ,KCU1.ORDINAL_POSITION AS FK_ORDINAL_POSITION 
    ,KCU2.CONSTRAINT_SCHEMA AS REFERENCED_CONSTRAINT_SCHEMA 
    ,KCU2.CONSTRAINT_NAME AS REFERENCED_CONSTRAINT_NAME 
    ,KCU2.TABLE_SCHEMA AS REFERENCED_TABLE_SCHEMA 
    ,KCU2.TABLE_NAME AS REFERENCED_TABLE_NAME 
    ,KCU2.COLUMN_NAME AS REFERENCED_COLUMN_NAME 
    ,KCU2.ORDINAL_POSITION AS REFERENCED_ORDINAL_POSITION 
FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS AS RC 

INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS KCU1 
    ON KCU1.CONSTRAINT_CATALOG = RC.CONSTRAINT_CATALOG  
    AND KCU1.CONSTRAINT_SCHEMA = RC.CONSTRAINT_SCHEMA 
    AND KCU1.CONSTRAINT_NAME = RC.CONSTRAINT_NAME 

INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS KCU2 
    ON KCU2.CONSTRAINT_CATALOG = RC.UNIQUE_CONSTRAINT_CATALOG  
    AND KCU2.CONSTRAINT_SCHEMA = RC.UNIQUE_CONSTRAINT_SCHEMA 
    AND KCU2.CONSTRAINT_NAME = RC.UNIQUE_CONSTRAINT_NAME 
    AND KCU2.ORDINAL_POSITION = KCU1.ORDINAL_POSITION 

Note:
Information_schema doesn't contain indices (it does find unique-contraints).
So if you want to find foreign-keys based on unique-indices, you need to go over the microsoft proprietary tables:

SELECT  
     fksch.name AS FK_CONSTRAINT_SCHEMA 
    ,fk.name AS FK_CONSTRAINT_NAME 

    ,sch1.name AS FK_TABLE_SCHEMA 
    ,t1.name AS FK_TABLE_NAME 
    ,c1.name AS FK_COLUMN_NAME 
    -- The column_id is not the ordinal, it can be dropped and then there's a gap... 
    ,COLUMNPROPERTY(c1.object_id, c1.name, 'ordinal') AS FK_ORDINAL_POSITION 

    ,COALESCE(pksch.name,sch2.name) AS REFERENCED_CONSTRAINT_SCHEMA 
    ,COALESCE(pk.name, sysi.name) AS REFERENCED_CONSTRAINT_NAME 

    ,sch2.name AS REFERENCED_TABLE_SCHEMA 
    ,t2.name AS REFERENCED_TABLE_NAME 
    ,c2.name AS REFERENCED_COLUMN_NAME 
    ,COLUMNPROPERTY(c2.object_id, c2.name, 'ordinal') AS REFERENCED_ORDINAL_POSITION 
FROM sys.foreign_keys AS fk 

LEFT JOIN sys.schemas AS fksch 
    ON fksch.schema_id = fk.schema_id 

-- not inner join: unique indices 
LEFT JOIN sys.key_constraints AS pk
    ON pk.parent_object_id = fk.referenced_object_id 
    AND pk.unique_index_id = fk.key_index_id 

LEFT JOIN sys.schemas AS pksch 
    ON pksch.schema_id = pk.schema_id 

LEFT JOIN sys.indexes AS sysi 
    ON sysi.object_id = fk.referenced_object_id 
    AND sysi.index_id = fk.key_index_id 

INNER JOIN sys.foreign_key_columns AS fkc 
    ON fkc.constraint_object_id = fk.object_id 

INNER JOIN sys.tables AS t1 
    ON t1.object_id = fkc.parent_object_id 

INNER JOIN sys.schemas AS sch1 
    ON sch1.schema_id = t1.schema_id 

INNER JOIN sys.columns AS c1 
    ON c1.column_id = fkc.parent_column_id 
    AND c1.object_id = fkc.parent_object_id 

INNER JOIN sys.tables AS t2 
    ON t2.object_id = fkc.referenced_object_id 

INNER JOIN sys.schemas AS sch2 
    ON sch2.schema_id = t2.schema_id 

INNER JOIN sys.columns AS c2 
    ON c2.column_id = fkc.referenced_column_id 
    AND c2.object_id = fkc.referenced_object_id

Proof-test for edge-cases:

CREATE TABLE __groups ( grp_id int, grp_name varchar(50), grp_name2 varchar(50) )
ALTER TABLE __groups ADD CONSTRAINT UQ___groups_grp_name2 UNIQUE (grp_name2)
CREATE UNIQUE INDEX IX___groups_grp_name ON __groups(grp_name)

GO
CREATE TABLE __group_mappings( map_id int, map_grp_name varchar(50), map_grp_name2 varchar(50), map_usr_name varchar(50) )
GO

ALTER TABLE __group_mappings  ADD  CONSTRAINT FK___group_mappings___groups FOREIGN KEY(map_grp_name)
REFERENCES __groups (grp_name)
GO


ALTER TABLE __group_mappings  ADD  CONSTRAINT FK___group_mappings___groups2 FOREIGN KEY(map_grp_name2)
REFERENCES __groups (grp_name2)
GO


SELECT @@VERSION -- Microsoft SQL Server 2016 (SP1-GDR) (KB4458842)
SELECT version() -- PostgreSQL 9.6.6 on x86_64-pc-linux-gnu
GO
Stefan Steiger
  • 78,642
  • 66
  • 377
  • 442
  • 5
    Warning! - This does not return fks that reference unique index columns. See http://stackoverflow.com/questions/2895219/can-we-have-a-foreign-key-which-is-not-a-primary-key-in-any-other-table. – Seth Reno Feb 28 '14 at 17:36
  • 2
    @Seth Reno: This is correct in Microsoft SQL Server, because there you can reference a unique index in a foreign key. But the SQL-standard doesn't allow this, and this isn't supported by all other DBMSs. Also, there is no index information available in information_schema, so there's no way to correct this. I'd say if you don't reference a primary key as foreign key, you're doing something wrong schema-wise. – Stefan Steiger Mar 17 '14 at 12:02
  • This query almost works correctly for me. I had to add `AND KCU2.TABLE_NAME = RC.REFERENCED_TABLE_NAME` to the ON clause of the KCU2 JOIN in order to eliminate incorrect records due to many tables in my database with a primary key named `PRIMARY`. I happen to be running MariaDB 5.5, but I suspect other DBMS will have a similar problem. – JSmitty Jan 22 '15 at 18:55
  • @JSmitty: Actually rc.referenced_table_name is just an alias for kcu2.table_name, so this statement should not run. if it does, it's a MySQL/MariaDb bug. And it gives back false additional rows. Same for multiple primary keys with the same name - not allowed. primary keys are unique within the database. You cannot create two tables with the same primary key name. If you can, the database doesn't catch this case, which is a bug, because it leads to bugs. And no, I'd wager this problem is very specific to MySQL/MariaDb users, and doesn't occur in other, proper, relational ACID-compliant DBMS's. – Stefan Steiger Mar 05 '15 at 09:01
  • I think postgresql also allows this duplicating constraint names across tables. – Joshua May 30 '17 at 22:43
  • @Joshua: No it doesn't. It allows duplicate PK names in different schemas, though. – Stefan Steiger Jul 04 '19 at 16:49
  • Despite this is "universal" way to get FK info, it misses MS specific flag IS_ENABLED for FK. – Vincent Jun 17 '23 at 13:04
23

If you can live with using the SQL Server specific schema catalog views, this query will return what you're looking for:

SELECT  
    fk.name,
    OBJECT_NAME(fk.parent_object_id) 'Parent table',
    c1.name 'Parent column',
    OBJECT_NAME(fk.referenced_object_id) 'Referenced table',
    c2.name 'Referenced column'
FROM 
    sys.foreign_keys fk
INNER JOIN 
    sys.foreign_key_columns fkc ON fkc.constraint_object_id = fk.object_id
INNER JOIN
    sys.columns c1 ON fkc.parent_column_id = c1.column_id AND fkc.parent_object_id = c1.object_id
INNER JOIN
    sys.columns c2 ON fkc.referenced_column_id = c2.column_id AND fkc.referenced_object_id = c2.object_id

Not sure how - if at all - you can get the same information from the INFORMATION_SCHEMA views....

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
  • 37'874 foreign keys in my database with about 100 tables? I'm not sure, but I don't think it's correct. I get 349, which I think is more likely... – Stefan Steiger Oct 11 '10 at 16:17
  • 2
    @Quandary: sorry, missed a few ON conditions - this should be better now (works for me, anyway) – marc_s Oct 11 '10 at 16:26
  • How to inner join this with INFORMATION_SCHEMA.KEY_COLUMN_USAGE to get the ORDINAL_POSITION of the field? – Ulysses Alves Jan 24 '17 at 16:03
  • @Ulysses Alves: I you have the schema, table and column name, you can inner join with information_schema.columns, then you have the ordinal_position. –  Jan 24 '17 at 16:14
  • @Ulysses Alves: use COLUMNPROPERTY on column_id (column_id is not always the ordinal - it can have gaps...): SELECT syst.name, sysc.name, sysc.column_id ,ROW_NUMBER() OVER (PARTITION BY OBJECT_NAME(syst.object_id) ORDER BY sysc.column_id ) as ORDINAL ,COLUMNPROPERTY(sysc.object_id, sysc.name, 'ordinal') AS ORDINAL_POSITION FROM sys.tables AS syst LEFT JOIN sys.columns AS sysc ON sysc.object_id = syst.object_id WHERE syst.name = 'T_BO_Menu' -- AND sysc.name = 'ME_LangEN_IsVisible' -- AND sysc.column_id > 100 – Stefan Steiger Jun 19 '19 at 09:50
  • fyi: if you already stepped into sys schema, you also have colorder column in sys.syscolumns table. – Cosmin Sontu Aug 29 '21 at 15:42
0

I wanted a a version that would let me find all the "Key" and "ID" columns having/missing a constraint. So i wanted all columns compared to the list of all PK OR FK OR Null, here is my query. Hope it helps someone else!

SELECT 
     c.table_schema
    ,c.table_name
    ,c.column_name
    ,KeyConstraints.constraint_type
    ,KeyConstraints.constraint_schema
    ,KeyConstraints.constraint_name
    ,KeyConstraints.referenced_table_schema
    ,KeyConstraints.referenced_table_name
    ,KeyConstraints.referenced_column_name
    ,KeyConstraints.update_rule
    ,KeyConstraints.delete_rule
FROM information_schema.columns AS c 
LEFT JOIN 
    (
        SELECT 
             FK.table_schema AS TABLE_SCHEMA
            ,FK.table_name
            ,CU.column_name
            ,FK.constraint_type
            ,c.constraint_schema
            ,C.constraint_name
            ,PK.table_schema AS REFERENCED_TABLE_SCHEMA
            ,PK.table_name AS REFERENCED_TABLE_NAME
            ,CCU.column_name AS REFERENCED_COLUMN_NAME
            ,C.update_rule
            ,C.delete_rule
        FROM information_schema.referential_constraints AS C 

        INNER JOIN information_schema.table_constraints AS FK 
            ON C.constraint_name = FK.constraint_name 

        INNER JOIN information_schema.table_constraints AS PK 
            ON C.unique_constraint_name = PK.constraint_name 

        INNER JOIN information_schema.key_column_usage AS CU 
            ON C.constraint_name = CU.constraint_name 

        INNER JOIN information_schema.constraint_column_usage AS CCU 
            ON PK.constraint_name = CCU.constraint_name 

        WHERE ( FK.constraint_type = 'FOREIGN KEY' ) 

        UNION 

        SELECT 
             ccu.table_schema
            ,ccu.table_name
            ,ccu.column_name
            ,tc.constraint_type
            ,ccu.constraint_schema
            ,ccu.constraint_name
            ,NULL
            ,NULL
            ,NULL
            ,NULL
            ,NULL
        FROM information_schema.constraint_column_usage ccu 

        INNER JOIN information_schema.table_constraints tc 
            ON ccu.table_schema = tc.table_schema 
            AND ccu.table_name = tc.table_name 

        WHERE tc.constraint_type = 'PRIMARY KEY'

    ) AS KeyConstraints 
    ON c.table_schema = KeyConstraints.table_schema 
    AND c.table_name = KeyConstraints.table_name 
    AND c.column_name = KeyConstraints.column_name 

WHERE c.column_name LIKE '%ID' OR c.column_name LIKE '%Key' 
ORDER BY  c.table_schema 
         ,c.table_name 
         ,c.column_name 
; 

formatting courtesy of: http://www.dpriver.com/pp/sqlformat.htm

Stefan Steiger
  • 78,642
  • 66
  • 377
  • 442
Justin
  • 1,303
  • 15
  • 30
  • Please take the time to switch to the proper options when using sqlformat. Your version was utterly unreadable. Fixed that. Also, use AS when declaring an alias. That will help you avoid a lot of potential problems.SqlFormat will format for you, but it doesn't think for you. – Stefan Steiger Sep 21 '17 at 19:54
  • @StefanSteiger thanks for the feedback. I haven't posted up long queries on SO often so wasn't sure of the best options to use. i'll keep this in mind for next time. I will also try and remember to include the "AS" qualifier, this is a bad habit i'm constantly working to improve on! – Justin Sep 25 '17 at 14:31
-1
you can use the following script in order to find all the fk,pk relationship for specific table 

    *DECLARE @tablename VARCHAR(100)
    SET @tablename='xxxxxxx'
    Select 'Referenced by FK table' AS Type,  FK.TABLE_SCHEMA, FK.TABLE_NAME AS 
    'FK_TABLE_NAME' ,cu.COLUMN_NAME AS 'FK_ReferencingColumn',PK.TABLE_NAME AS 
    'PK_TABLE_NAME',
   ku.COLUMN_NAME AS 'PK_ReferencedColumn'
    From INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS As RC
        Join INFORMATION_SCHEMA.TABLE_CONSTRAINTS As PK
            On PK.CONSTRAINT_NAME = RC.UNIQUE_CONSTRAINT_NAME
        Join INFORMATION_SCHEMA.TABLE_CONSTRAINTS As FK
            On FK.CONSTRAINT_NAME = RC.CONSTRAINT_NAME
       JOIN INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE cu
       ON cu.CONSTRAINT_NAME = Rc.CONSTRAINT_NAME
         JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE ku
    ON ku.CONSTRAINT_NAME = RC.UNIQUE_CONSTRAINT_NAME
    Where 
        PK.TABLE_NAME = @tablename
    UNION  
    SELECT 'Referencing PK table' AS Type, FK.TABLE_SCHEMA, FK.TABLE_NAME AS 
    'FK_TABLE_NAME' ,cu.COLUMN_NAME AS 'FK_ReferencingColumn',PK.TABLE_NAME AS 
    'PK_TABLE_NAME',
     ku.COLUMN_NAME AS 'PK_ReferencedColumn'
    From INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS As RC
        Join INFORMATION_SCHEMA.TABLE_CONSTRAINTS As PK
            On PK.CONSTRAINT_NAME = RC.UNIQUE_CONSTRAINT_NAME
        Join INFORMATION_SCHEMA.TABLE_CONSTRAINTS As FK
            On FK.CONSTRAINT_NAME = RC.CONSTRAINT_NAME
       JOIN INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE cu
       ON cu.CONSTRAINT_NAME = Rc.CONSTRAINT_NAME
         JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE ku
    ON ku.CONSTRAINT_NAME = RC.UNIQUE_CONSTRAINT_NAME
   Where 
        fk.TABLE_NAME = @tablename*