SQL Server:如何从信息架构中获取外键引用?

50

在SQL Server中,我如何从外键中获取引用表格和列名?

注意:不是包含该键的表/列,而是它所引用的键。

示例:

当表格[T_ALV_Ref_FilterDisplay]中的键 [FA_MDT_ID]引用[T_AP_Ref_Customer].[MDT_ID]时,如创建此类约束条件:

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

当给定 [T_ALV_Ref_FilterAnzeige].[FA_MDT_ID] 作为输入时,我需要获取 [T_AP_Ref_Customer].[MDT_ID]

4个回答

97

没关系,这是正确的答案:
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 

注意:
Information_schema不包含索引信息(但可以找到唯一约束)。
因此,如果您想基于唯一索引查找外键,则需要查阅微软专有的表:

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

边界条件的验证测试:

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

5
警告!- 这个操作不能返回引用唯一索引列的外键。请参考https://dev59.com/QXE85IYBdhLWcg3wMQlm。 - Seth Reno
2
@Seth Reno:在Microsoft SQL Server中是正确的,因为你可以在外键中引用唯一索引。但是SQL标准不允许这样做,并且并非所有其他DBMS都支持此功能。此外,在information_schema中没有索引信息可用,因此无法进行更正。我认为,如果你不将主键引用为外键,那么在模式方面你就有问题了。 - Stefan Steiger
这个查询对我来说几乎是正确的。我不得不在KCU2 JOIN的ON子句中添加“AND KCU2.TABLE_NAME = RC.REFERENCED_TABLE_NAME”,以消除由于我的数据库中有许多名为“PRIMARY”的主键而导致的错误记录。我碰巧正在运行MariaDB 5.5,但我怀疑其他DBMS也会有类似的问题。 - JSmitty
@JSmitty:实际上,rc.referenced_table_name只是kcu2.table_name的别名,所以这个语句不应该运行。如果它确实运行了,那么这是一个MySQL/MariaDb的bug。并且它会返回错误的额外行。同样,对于具有相同名称的多个主键-不允许。主键在数据库中是唯一的。您不能创建两个具有相同主键名称的表。如果可以,那么数据库没有捕获此情况,这是一个bug,因为它会导致错误。而且,我敢打赌,这个问题非常特定于MySQL/MariaDb用户,并且不会发生在其他适当的关系ACID兼容的DBMS中。 - Stefan Steiger
我认为PostgreSQL也允许在不同的表中复制约束名称。 - Joshua
显示剩余2条评论

23

如果您可以使用SQL Server特定的架构目录视图,则此查询将返回您要查找的内容:

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

不确定是否可以通过 INFORMATION_SCHEMA 视图获取相同的信息...


我的数据库中有大约100个表,有37,874个外键?我不确定,但我认为这不正确。我得到了349个,我认为这更可能是正确的... - Stefan Steiger
2
@Quandary:抱歉,我错过了一些ON条件 - 现在应该更好了(反正对我来说是有效的) - marc_s
如何使用INNER JOIN将其与INFORMATION_SCHEMA.KEY_COLUMN_USAGE连接起来,以获取字段的ORDINAL_POSITION? - Ulysses Alves
@Ulysses Alves:如果你有模式、表和列名,你可以使用information_schema.columns进行内连接,然后你就有了ordinal_position。 - user6038265
@Ulysses Alves:在column_id上使用COLUMNPROPERTY(column_id不总是顺序的-它可能有间隙...):选择 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
请注意:如果您已经进入了sys模式,那么在sys.syscolumns表中也会有colorder列。 - Cosmin Sontu

0

我想要一个版本,让我可以查找所有具有/缺少约束的“Key”和“ID”列。因此,我想将所有列与所有PK OR FK OR Null列表进行比较,这是我的查询语句。希望它能帮助其他人!

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 
; 

格式化由http://www.dpriver.com/pp/sqlformat.htm提供


请在使用sqlformat时花些时间切换到正确的选项。您的版本完全无法阅读。已经修复了这个问题。此外,在声明别名时,请使用AS。这将帮助您避免许多潜在的问题。SqlFormat可以为您格式化,但它不能替您思考。 - Stefan Steiger
@StefanSteiger 感谢您的反馈。我在SO上很少发布长查询,所以不确定使用最佳选项。下次我会记住这一点。我也会尝试记住包括“AS”限定符,这是我一直在努力改进的坏习惯! - Justin

-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*

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接