用于在数据库中发现事实上的外键的工具?

时间:2011-08-11 18:48:58

标签: foreign-keys foreign-key-relationship database-diagramming database-diagram

快速调查数据库中信息的一个好方法是应用一个工具,自动创建所有表的数据库图表以及它们之间的所有关系。

根据我的经验,这些工具使用外键作为关系,我尝试它们的大多数数据库都不包含这些关系。当然,它们满足与外键相对应的约束,但不强制执行它们。而且我最终会得到一个由一堆不相关的表组成的“图表”。

所以我正在寻找的是能够计算“未声明的外键”和

的软件
  • 将它们用作数据库图表中的表关系,或
  • 为相应的外键声明生成SQL代码

您是否知道任何已经可以执行此操作的免费工具?

4 个答案:

答案 0 :(得分:4)

有趣的问题。您正在寻找解析数据库模式和数据以确定哪些表相关或应该彼此相关,而没有任何严格的关系定义。实际上,你试图推断出一种关系。

我看到两种方式可以推断出这种关系。首先,请允许我说您的方法可能会因您使用的数据库而异。我想到了许多问题(我不想要答案,但值得反思)

  • 这些内部企业系统遵循一致的命名约定或模式吗?
  • 或者他们是在任何时间任何地方遇到的'野外'数据库?
  • 你准备做什么样的假设?
  • 您希望在结果中获得更多误报或漏报吗?

请注意,这种类型的推断几乎肯定会给出错误的结果,并且基于很多假设。

所以我提供两种方法,我将在演唱会中使用。

通过结构/命名(符号分析)推断关系

通用数据库设计是在表名后面命名一个PK列(例如CustomerId上的Customer),或者只是将PK列命名为Id

与另一个表具有FK关系的表通常将其相关列命名为与相关表相同。在Order表中,我希望CustomerId列引用CustomerId表中的Id / Customer列。

此类分析包括

  • 检查表格中的列以查找类似的短语/单词
  • 查找与其他表名称类似的列名称
  • 检查包含其他列名称的列名称(例如FirstCustomerId& SecondCustomerId均引用CustomerId表中的Customer

通过数据推断关系(统计分析)

根据您的建议,在您的评论中查看数据,将允许您确定“可能的”参考。如果CustomerId表中的Order列包含Id表的Customer列中不存在的值,则可以合理地质疑这是一个有效的关系(虽然你永远不知道!)

一种简单的数据分析形式是使用日期和时间。彼此非常接近而创建的行更可能彼此相关。如果,对于创建的每个Order行,在几秒钟内创建的行之间也存在2到5行Item,那么两者之间可能存在关系。

更详细的分析可能会考虑使用值的范围和分布。

例如,如果您的Order表格中有St_Id列 - 您可能会推断使用符号分析表明该列可能与State表或{{1表。 Status列有6个离散值,90%的记录由2个值覆盖。 St_Id表有200行,State表有9行。您可以非常合理地推断出Status列与St_Id表相关 - 它更广泛地覆盖了表的行(2/3的行是'使用',而只有3将使用Status表中的行的百分比。)

如果您对现有数据库进行数据分析以收集“真实数据”,我希望可以使用一些模式作为结构推理的指南。当具有大量记录的表具有重复多次的少量值的列(不一定按顺序)时,此列更可能与具有相应少量行的表相关。

总结

祝你好运。这是一个感兴趣的问题,我只是抛出一些想法,但这是一个非常好的试验&错误,数据收集和性能调整情况。

答案 1 :(得分:3)

在大多数情况下,这是一项非平凡练习。如果你足够幸运能够分析现代框架的模式,例如Ruby on Rails,或CakePHP或类似的,并且开发人员一直严格遵守列约定,那么你有合理的机会找到很多,但不是全部,暗示的关系。

即。如果您的表使用user_id之类的列来引用users表中的条目。

请注意:某些实体名称可能会多次不规则entity是一个很好的例子:entities,而不是entitys)而这些更难捕捉(但仍然可能)。但是,无法推断开发人员与admin_id上的用户表相关联的user.id等密钥。您需要手动处理这些案例。

你没有指定RDBMS,但我经常使用MySQL,而且我现在正在为自己解决这个问题。

以下MySQL脚本将推断列名隐含的大多数关系。然后它列出了找不到表名的任何关系,所以至少你知道哪些是你缺少的。列出推断的父级和子级,以及单数和复数名称,以及隐含的关系:

-- this DB is where MySQL keeps schema information
use information_schema;

-- change this to the DB you want to analyse
set @db_name = "example_DB";

-- infer relationships 
-- NB: this won't catch names that pluralise irregularly like category -> categories or bus_id -> buses etc.
select  LEFT(COLUMN_NAME, CHAR_LENGTH(COLUMN_NAME) - 3 )              as inferred_parent_singular
,       CONCAT(LEFT(COLUMN_NAME, CHAR_LENGTH(COLUMN_NAME) - 3 ),"s")  as inferred_parent_plural 
,       C.TABLE_NAME                                                  as child_table
,       CONCAT(LEFT(COLUMN_NAME, CHAR_LENGTH(COLUMN_NAME)-3), "s has many ", C.TABLE_NAME) as inferred_relationship
from    COLUMNS C
JOIN    TABLES T on C.TABLE_NAME = T.TABLE_NAME 
        and C.TABLE_SCHEMA = T.TABLE_SCHEMA 
        and T.TABLE_TYPE != "VIEW"  -- filter out views; comment this line if you want to include them
where   COLUMN_NAME like "%_id"     -- look for columns of the form <name>_id
and     C.TABLE_SCHEMA = T.TABLE_SCHEMA and T.TABLE_SCHEMA = @db_name 
-- and     C.TABLE_NAME not like "wwp%"  -- uncomment and set a pattern to filter out any tables you DON'T want included, e.g. wordpress tables e.g. wordpress tables
-- finally make sure to filter out any inferred names that aren't really tables
and     CONCAT(LEFT(COLUMN_NAME, CHAR_LENGTH(COLUMN_NAME) - 3 ),"s") -- this is the inferred_parent_plural, but can't use column aliases in the where clause sadly
          in (select TABLE_NAME from TABLES where TABLE_SCHEMA = @db_name)
;

这将返回如下结果: enter image description here

然后,您可以检查检测到的任何命名约定异常:

-- Now list any inferred parents that weren't real tables to see see why (irregular plurals and columns not named according to convention)
select  LEFT(COLUMN_NAME, CHAR_LENGTH(COLUMN_NAME) - 3 ) as inferred_parent_singular
,       CONCAT(LEFT(COLUMN_NAME, CHAR_LENGTH(COLUMN_NAME) - 3 ),"s") as inferred_parent_plural 
,       C.TABLE_NAME as child_table
from    COLUMNS C
JOIN    TABLES T  on  C.TABLE_NAME    = T.TABLE_NAME 
                  and C.TABLE_SCHEMA  = T.TABLE_SCHEMA 
                  and T.TABLE_TYPE   != "VIEW"            -- filter out views, comment this line if you want to include them
where   COLUMN_NAME like "%_id"
and     C.TABLE_SCHEMA = T.TABLE_SCHEMA and T.TABLE_SCHEMA = @db_name 
-- and     C.TABLE_NAME not like "wwp%"  -- uncomment and set a pattern to filter out any tables you DON'T want included, e.g. wordpress tables e.g. wordpress tables
-- this time only include inferred names that aren't real tables
and     CONCAT(LEFT(COLUMN_NAME, CHAR_LENGTH(COLUMN_NAME) - 3 ),"s")
          not in (select TABLE_NAME from TABLES where TABLE_SCHEMA = @db_name)
;

这将返回这样的结果,您可以手动处理: enter image description here

您可以修改这些脚本以吐出对您有用的任何内容,如果您愿意,还可以包含外键创建语句。在这里,最后一栏是一个简单的有很多&#39;关系声明。我在我构建的名为pidgin的工具中使用它,这是一种快速建模工具,它基于用非常简单的语法编写的关系语句(称为&#39; pidgin&#39;)来动态绘制关系图。您可以在http://pidgin.gruffdavies.com

查看

我在演示数据库上运行上述脚本,向您展示您可以期待的结果:

enter image description here

我还没有在我的剧本中提供不规则的复数形式,但我也可能会这样做,至少对于以-y结尾的实体的情况。如果您想亲自尝试一下,我建议您编写一个以<name>_id列名称作为参数的存储函数,剥离_id部分,然后应用一些启发式尝试正确复数。

希望这很有用!

答案 2 :(得分:1)

以下产品均声称提供外键发现功能:

ERwin的 http://www.ascent.co.za/products/ca_erwin_data_profiler.html

的Informatica https://community.informatica.com/onlinehelp/analyst/961/en/index.htm#page/data-discovery-guide/GUID-33EAF039-ECFC-49FD-96F4-A2C2A4EB857F.1.148.html

和 XCaseForI http://xcasefori.com/discovering/index.html

统计方法能够提供一种相似性等级,如Kirk所建议的范围分布和创建时间, 似乎是正确的方式。  ..我需要使用SAS EG或任何免费工具来实现它。

答案 3 :(得分:0)

我不知道哪些软件可能有助于搜索您的需求,但以下查询将帮助您入门。它列出了当前数据库中的所有外键关系。

SELECT
    K_Table = FK.TABLE_NAME,
    FK_Column = CU.COLUMN_NAME,
    PK_Table = PK.TABLE_NAME,
    PK_Column = PT.COLUMN_NAME,
    Constraint_Name = C.CONSTRAINT_NAME
FROM
    INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS C
INNER JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS FK
    ON C.CONSTRAINT_NAME = FK.CONSTRAINT_NAME
INNER JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS PK
    ON C.UNIQUE_CONSTRAINT_NAME = PK.CONSTRAINT_NAME
INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE CU
    ON C.CONSTRAINT_NAME = CU.CONSTRAINT_NAME
INNER JOIN (
            SELECT
                i1.TABLE_NAME,
                i2.COLUMN_NAME
            FROM
                INFORMATION_SCHEMA.TABLE_CONSTRAINTS i1
            INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE i2
                ON i1.CONSTRAINT_NAME = i2.CONSTRAINT_NAME
            WHERE
                i1.CONSTRAINT_TYPE = 'PRIMARY KEY'
           ) PT
    ON PT.TABLE_NAME = PK.TABLE_NAME

希望这有帮助。