在没有定义外键的情况下搜索和反向工程数据库中的外键关系

Search for and reverse engineer foreign key relationships in DB without foreign keys defined

我继承了一个非常旧的数据库,大约有 100 tables。我通过查看 table 知道存在外键关系,但我还确定数据库中没有实际定义的关系。此外,没有一致的命名约定。

我已经尝试通过查看 tables 并尝试加入来解决这个问题,但这很耗时,而且我没有那么多时间。所以现在我正在研究一些自动化的想法。

有没有人试过运行查询数据库来查找外键?

我对查询的一般逻辑的看法是:

对于每个 table: 如果它有主键,请检查该键名是否存在于所有其他 table 中。如果是这样,请检查两组 ID 之间是否存在高百分比匹配。如果是这样,将其输出为潜在的 FK 关系。指定是一对一还是一对多。

如果其他 table 中不存在相同的名称,请尝试在所有 table 中搜索具有完全相同数据类型的字段。尝试查看两组 ID 中是否存在高百分比匹配。如果是这样,将其输出为潜在的 FK 关系。指定是一对一还是一对多。

我知道这可能会导致很多误报,但这比手动搜索要好。

我的逻辑是否合理,或者我在尝试自动执行此搜索时完全没有根据?

我的最终目标是生成一个 ER 图,我可以在构建一些新查询时使用它。

使用 MSSQL

让我们找对:

SELECT * FROM
  information_schema.columns cl
  INNER JOIN
  information_schema.columns cr
  ON
    cl.table_name < cr.table_name AND
    cl.data_type = cr.data_type

这会生成一个列表,其中每个 table 中的每一列都与其他 table 中的所有其他列交叉,其中数据类型相同。希望在 table 名称上使用 < 意味着 tableA.somenumber 将与 tableB.someothernumber 配对但不是相反(除非你真的想要相反) - 没有太多意义询问数据库如何a 中的许多值等于 b,然后询问 b 中有多少等于 a

现在让它写一个SQL:

SELECT 
  REPLACE(REPLACE(REPLACE(REPLACE(
    'SELECT ''{Ltable}.{Lcol}'' as lefty, ''{Rtable}.{Rcol}'' as righty, count(l.{Lcol}) as countLefty, count(r.{Rcol}) as countRighty, case when count(r.{Rcol}) = 0 then 0 else count(l.{Lcol})/count(r.{Rcol}) end as percenty
     FROM {Ltable} l LEFT JOIN {Rtable} r ON l.{Lcol} = r.{Rcol} UNION ALL',
    '{Ltable}', cl.table_name),
    '{Rtable}', cr.table_name),
    '{Lcol}', cl.column_name),
    '{Rcol}', cr.column_name)
FROM
  information_schema.columns cl
  INNER JOIN
  information_schema.columns cr
  ON
    cl.table_name < cr.table_name AND
    cl.data_type = cr.data_type

如果您 运行 这个 SQL,它会生成一个结果网格,每行都有一个 SQL - 将其复制出网格并将其粘贴回查询编辑器,删除最终的 UNION ALL 然后 运行 它

在我相当小的 90 tables 数据库上,每列约 8 列,它生成了 62,000 个它想要做的组合;谨慎使用.. 或者设置它在备份服务器上运行一晚并在第二天回来

感谢 Caius Jard 出色的解决方案。我已经扩展它以根据我的需要对其进行自定义。有兴趣的可以看这里:

-- Setup types to ignore
DECLARE @ignore table (ignorefield varchar(20));

-- Note: Must ignore 'text', must also ignore any other blob data types used in db. Others are optional.
INSERT @ignore(ignorefield) values('char'),('datetime'),('money'),('image'),('bit'),('binary'),('text');

-- Write queries to find fields that are potential foreign keys
SELECT 
  REPLACE(REPLACE(REPLACE(REPLACE(
    'SELECT ''{Ltable}.{Lcol}'' as lefty, ''{Rtable}.{Rcol}'' as righty, count(l.{Lcol}) as countLefty, count(r.{Rcol}) as countRighty, case when count(r.{Rcol}) = 0 then 0 else count(l.{Lcol})/count(r.{Rcol}) end as percenty
     FROM {Ltable} l LEFT JOIN {Rtable} r ON l.{Lcol} = r.{Rcol} UNION ALL',
    '{Ltable}', QUOTENAME(cl.table_name)),
    '{Rtable}', QUOTENAME(cr.table_name)),
    '{Lcol}', QUOTENAME(cl.column_name)),
    '{Rcol}', QUOTENAME(cr.column_name))
FROM
  information_schema.columns cl
  INNER JOIN
  information_schema.columns cr
  ON
    cl.table_name < cr.table_name AND
    cl.data_type = cr.data_type
WHERE
    cl.data_type NOT IN (SELECT ignorefield from @ignore)
    AND cl.is_nullable = 'NO' -- Maybe remove for some db designs.
ORDER BY
    cl.data_type ASC