2009-02-27 42 views
1

我正在反向設計Oracle數據庫中中等數量的表(50+)之間的關係,這些表之間沒有定義外鍵。我可以計算(有點)能夠跨表匹配列名稱。例如,列名稱「SomeDescriptiveName」在整個表格中可能是相同的。編程提取RDBMS中的表與外鍵之間的關係?

我希望能夠做的是找到一種更好的方式,根據那些匹配列名稱來提取一些關係集,而不是逐個手動遍歷表。我可以使用Java DatabaseMetaData方法做一些事情,但似乎這是某人以前可能需要編寫腳本的任務之一。也許用Perl或其他腳本語言lang提取列名稱,使用列名作爲散列鍵並將表添加到由散列鍵指向的數組?

任何人有任何提示或建議,可能會使這個更簡單或提供一個良好的起點?這是一個醜陋的需求,如果外鍵已經被定義,理解關係會容易得多。

謝謝。

回答

1

你幾乎在你的問題中寫了答案。

my %column_tables; 
foreach my $table (@tables) { 
    foreach my $column ($table->columns) { 
     push @{$column_tables[$column]}, $table; 
    } 
} 
print "Likely foreign key relationships:\n"; 
foreach my $column (keys %column_tables) { 
    my @tables = @{$column_tables[$column]}; 
    next 
     if @tables < 2; 
    print $column, ': '; 
    foreach my $table (@tables) { 
     print $table->name, ' '; 
    } 
    print "\n"; 
} 
+0

當然,只要邏輯鏈接的所有列具有相同的名稱,就會工作。否則,你必須對數據進行廣泛的分析,試圖獲得這些關係......的確是一件我不想要的艱鉅任務。 – 2009-02-28 05:42:49

1

您可以使用組合的三個(或四個)接近,這取決於如何混淆的模式是:

  • 動態方法
    • 觀察
      • 在RDBMS(或ODBC層)中啓用跟蹤,然後
      • 在應用程序(理想地記錄創建)執行各種活動,然後
      • 識別緊序列被改變哪些表,以怎樣的列值對序列的時間間隔期間在一個以上的列存在
      • 值可以指示一個外鍵關係
  • 靜態方法(只分析現有數據,沒有必要有一個運行的應用程序)
    • 命名:儘量推斷列名
    • 統計關係:看看最小/最大(也可能是平均值)唯一值的所有數值列,並嘗試進行匹配
    • 代碼反向工程:您的最後一招(除非使用腳本處理) - 不是爲微弱的心臟:)
1

我的策略是使用Oracle系統目錄查找關口在列相同的名稱數據類型但在不同,表名稱。另外哪一列是表的主鍵或唯一鍵的一部分。

這裏有可能接近這樣一個查詢,但我沒有一個Oracle實例方便對其進行測試:

SELECT col1.table_name || '.' || col1.column_name || ' -> ' 
    || col2.table_name || '.' || col2.column_name 
FROM all_tab_columns col1 
    JOIN all_tab_columns col2 
    ON (col1.column_name = col2.column_name 
    AND col1.data_type = col2.data_type) 
    JOIN all_cons_columns cc 
    ON (col2.table_name = cc.table_name 
    AND col2.column_name = cc.column_name) 
    JOIN all_constraints con 
    ON (cc.constraint_name = con.constraint_name 
    AND cc.table_name = con.table_name 
    AND con.constraint_type IN ('P', 'U') 
WHERE col1.table_name != col2.table_name; 

當然,這不會得到那些相關列任何情況下,但名稱不同。

0

這是一個有趣的問題。我採取的方法是強力搜索匹配小樣本集的類型和值的列。您可能必須調整啓發式才能爲您的架構提供良好的結果。我在一個沒有使用自動遞增鍵的模式上運行它,它運行良好。該代碼是爲MySQL編寫的,但適應Oracle很容易。

use strict; 
use warnings; 
use DBI; 

my $dbh = DBI->connect("dbi:mysql:host=localhost;database=SCHEMA", "USER", "PASS"); 

my @list; 
foreach my $table (show_tables()) { 
    foreach my $column (show_columns($table)) { 
     push @list, { table => $table, column => $column }; 
    } 
} 

foreach my $m (@list) { 
    my @match; 
    foreach my $f (@list) { 
     if (($m->{table} ne $f->{table}) && 
      ($m->{column}{type} eq $f->{column}{type}) && 
      (samples_found($m->{table}, $m->{column}{name}, $f->{column}{samples}))) 
     { 
      # For better confidence, add other heuristics such as 
      # joining the tables and verifying that every value 
      # appears in the master. Also it may be useful to exclude 
      # columns in large tables without an index although that 
      # heuristic may fail for composite keys. 
      # 
      # Heuristics such as columns having the same name are too 
      # brittle for many of the schemas I've worked with. It may 
      # be too much to even require identical types. 

      push @match, "$f->{table}.$f->{column}{name}"; 
     } 
    } 
    if (@match) { 
     print "$m->{table}.$m->{column}{name} $m->{column}{type} <-- @match\n"; 
    } 
} 

$dbh->disconnect(); 

exit; 

sub show_tables { 
    my $result = query("show tables"); 
    return ($result) ? @$result :(); 
} 

sub show_columns { 
    my ($table) = @_; 
    my $result = query("desc $table"); 
    my @columns; 
    if ($result) { 
     @columns = map { 
      { name => $_->[0], 
       type => $_->[1], 
       samples => query("select distinct $_->[0] from $table limit 10") } 
     } @$result; 
    } 
    return @columns; 
} 

sub samples_found { 
    my ($table, $column, $samples) = @_; 
    foreach my $v (@$samples) { 
     my $result = query("select count(1) from $table where $column=?", $v); 
     if (!$result || $result->[0] == 0) { 
      return 0; 
     } 
    } 
    return 1; 
} 

sub query { 
    my ($sql, @binding) = @_; 
    my $result = $dbh->selectall_arrayref($sql, undef, @binding); 
    if ($result && $result->[0] && @{$result->[0]} == 1) { 
     foreach my $row (@$result) { 
      $row = $row->[0]; 
     } 
    } 
    return $result; 
}