8

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

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

所以我正在寻找的是可以计算“未声明的外键”的软件

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

你知道任何工具,如果可能的话,免费的,已经可以做到这一点?

4

4 回答 4

6

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

我看到有两种方法可以推断出这种关系。首先让我说,您的方法可能会因您正在使用的数据库而异。脑海中浮现出许多问题(我不想要答案,但值得思考)

  • 这些内部企业系统是否遵循一些一致的命名约定或模式?
  • 或者它们是您随时随地遇到的“野外”数据库?
  • 你准备做出什么样的假设?
  • 您希望在结果中得到更多的误报还是误报?

请注意,这种类型的推断几乎肯定会给出错误的结果,并且建立在许多假设之上。

所以我提供了两种我会一起使用的方法。

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

常见的数据库设计是在表名之后命名一个 PK 列(例如CustomerIdon table Customer),或者简单地命名 PK 列Id

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

这种类型的分析将包括

  • 检查表中的列是否有相似的短语/单词
  • 查找与其他表的名称相似的列名称
  • 检查包含其他列名称的列名称(例如FirstCustomerId&SecondCustomerId都指表CustomerId中的列Customer

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

正如您在评论中建议的那样,查看数据将使您能够确定“可能的”参考。如果表中的CustomerId列包含Order表中不存在的值,那么质疑这是一个有效的关系是合理的(尽管你永远不知道!)IdCustomer

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

更详细的分析可能会着眼于使用值的范围和分布。

例如,如果您的Order表有一个St_Id列 - 您可以使用符号分析推断该列可能与一个State表或一个Status表相关。该St_Id列有 6 个离散值,90% 的记录被 2 个值覆盖。表State有 200 行,Status表有 9 行。您可以相当合理地推断该St_Id列与Status表相关 - 它提供了对表行的更大覆盖(2/3 的行被“使用”,而State表中只有 3% 的行会被使用)。

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

总之

祝你好运。这是一个有趣的问题,我刚刚提出了一些想法,但这在很大程度上是一个试错、数据收集和性能调整情况。

于 2011-08-22T03:48:45.180 回答
5

在大多数情况下,这是一项重要的练习。如果您有幸为现代框架(例如 Ruby on Rails 或 CakePHP 或类似框架)分析模式,并且开发人员一直严格遵循列约定,那么您有合理的机会找到许多但不是全部, 的隐含关系。

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

请注意:某些实体名称可能会不规则地复数(entity作为一个很好的例子:entities, not 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)
;

这将返回如下结果: 在此处输入图像描述

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

-- 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)
;

这将返回如下结果,您可以手动处理: 在此处输入图像描述

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

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

在此处输入图像描述

我没有在我的脚本中处理不规则复数,但我可能也会尝试一下,至少对于以 -y 结尾的实体的情况。如果您想自己尝试一下,我建议编写一个存储函数,该函数将<name>_id列名作为参数,剥离_id部分,然后应用一些启发式方法来尝试正确地进行复数。

希望这有用!

于 2014-08-29T12:13:30.690 回答
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

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

于 2015-09-21T13:11:08.073 回答
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

希望这可以帮助。

于 2011-08-21T10:46:53.753 回答