2009-02-27 2 views
1

Ich bin Reverse Engineering der Beziehungen zwischen einer mittelgroßen Anzahl von Tabellen (50+) in einer Oracle-Datenbank, wo keine Fremdschlüssel zwischen den Tabellen definiert sind. Ich kann (etwas) darauf zählen, Spaltennamen tabellenübergreifend zuordnen zu können. Zum Beispiel ist der Spaltenname "SomeDescriptiveName" wahrscheinlich für alle Tabellen identisch.Programmatische Extraktion von Beziehungen zwischen Tabellen in einem RDBMS ohne Fremdschlüssel?

Was ich gerne tun könnte, ist eine bessere Möglichkeit zu finden, einige Beziehungen basierend auf diesen übereinstimmenden Spaltennamen zu extrahieren, als manuell nacheinander durch die Tabellen zu gehen. Ich könnte etwas mit den Java DatabaseMetaData-Methoden machen, aber es scheint, dass dies eine dieser Aufgaben ist, die jemand wahrscheinlich vorher skripten musste. Vielleicht die Spaltennamen mit Perl oder einer anderen Skriptsprache extrahieren, die Spaltennamen als Hash-Schlüssel verwenden und Tabellen zu einem Array hinzufügen, auf das der Hash-Schlüssel zeigt?

Jeder hat irgendwelche Tipps oder Vorschläge, die dies vereinfachen oder einen guten Ausgangspunkt bieten könnten? Es ist ein hässliches Bedürfnis, wenn fremde Schlüssel bereits definiert worden wären, wäre das Verstehen der Beziehungen viel einfacher gewesen.

Danke.

Antwort

1

Sie haben so ziemlich die Antwort in Ihrer Frage geschrieben.

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

Natürlich würde dies funktionieren, solange alle Spalten, die logisch verknüpft sind, den gleichen Namen haben. Sonst müssten Sie eine umfangreiche Analyse der Daten machen, um zu versuchen, die Beziehungen zu bekommen ... eine schwierige Aufgabe, die ich in der Tat nicht wollen würde. –

1

Sie eine Kombination (oder vier) annähert, je nachdem, wie das Schema verschleierten ist von drei verwenden können:

  • dynamische Methoden
    • Beobachtung:
      • Aktivieren Sie die Ablaufverfolgung in der RDBMS (oder ODBC-Schicht), dann
      • führen verschiedene Aktivitäten in der Anwendung (im Idealfall Datensatzerstellung), dann
      • identifizieren, welche Tabellen in enger Sequenz verändert wurden und mit welchen column-Wert-Paaren
      • Werte in mehr als eine Spalte während der Sequenz Intervalls auftreten können darauf hindeuten, eine Fremdschlüsselbeziehung
  • statische Methoden (gerade vorhandene Daten zu analysieren, keine Notwendigkeit eine Anwendung benutzt wird)
    • Nomenklatur: versuchen, Beziehungen von Spaltennamen
    • statistische ableiten: Blick auf Minimum/Maximum (und möglicherweise die durchschnittliche) der eindeutigen Werte in allen numerischen Spalten, und versuchen, eine Übereinstimmung
    • Code Reverse auszuführen Engineering: Ihr letzter Ausweg (es sei denn, mit Skripten zu tun) - nicht für das schwache des Herzens :)
1

Meine Strategie wäre, das Oracle-System-Katalog zu verwenden, col zu finden Umns, die in Spalte Name und Datentyp identisch sind, aber in Tabelle unterscheidet sich Name. Auch welche der Spalten Teil des primären oder eindeutigen Schlüssels einer Tabelle ist.

Hier ist eine Abfrage, die dies zu tun nahe sein können, aber ich habe nicht eine Oracle-Instanz praktisch zu testen:

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; 

Natürlich ist dies kein Fall von Spalten erhalten, die verwandt sind aber haben unterschiedliche Namen.

0

Dies ist eine interessante Frage. Der Ansatz, den ich gewählt habe, war eine Brute-Force-Suche nach Spalten, die Typen und Werte für einen kleinen Stichprobensatz abgeglichen haben. Sie müssen wahrscheinlich die Heuristik optimieren, um gute Ergebnisse für Ihr Schema zu erzielen. Ich habe dies auf einem Schema ausgeführt, das nicht automatisch inkrementierte Schlüssel verwendet und es hat gut funktioniert. Der Code ist für MySQL geschrieben, aber es ist sehr einfach, sich an Oracle anzupassen.

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; 
} 
Verwandte Themen