Программно извлекать отношения между таблицами в СУБД без внешних ключей? - PullRequest
1 голос
/ 27 февраля 2009

Я занимаюсь обратным проектированием отношений между средним числом таблиц (более 50) в базе данных Oracle, где между таблицами не определены внешние ключи. Я могу рассчитывать (несколько) на возможность сопоставления имен столбцов в разных таблицах. Например, имя столбца «SomeDescriptiveName», вероятно, одинаково во всем наборе таблиц.

Что я хотел бы сделать, так это найти лучший способ извлечь некоторый набор отношений на основе этих совпадающих имен столбцов, чем вручную проходить таблицы по очереди. Я мог бы что-то сделать с методами Java DatabaseMetaData, но кажется, что это одна из тех задач, которые кто-то, вероятно, должен был писать раньше. Может быть, извлечь имена столбцов с помощью Perl или какого-либо другого языка сценариев, использовать имена столбцов в качестве хеш-ключа и добавить таблицы в массив, на который указывает хеш-ключ?

У кого-нибудь есть какие-либо советы или предложения, которые могут сделать это проще или обеспечить хорошую отправную точку? Это ужасная необходимость, если бы внешние ключи уже были определены, понимание отношений было бы намного проще.

Спасибо.

Ответы [ 4 ]

1 голос
/ 28 февраля 2009

Вы можете использовать комбинацию из трех (или четырех) подходов, в зависимости от того, насколько запутана схема:

  • динамические методы
    • Наблюдение :
      • включить трассировку в СУБД (или уровне ODBC), затем
      • выполняет различные действия в приложении (в идеале создание записей), затем
      • определяет, какие таблицы были изменены в строгой последовательности и с какими парами столбец-значение
      • значения, встречающиеся в нескольких столбцах в течение интервала последовательности, могут указывать на отношение внешнего ключа
  • статические методы (просто анализ существующих данных, нет необходимости иметь работающее приложение)
    • номенклатура : попытаться вывести связи из имен столбцов
    • статистический : посмотреть минимальное / максимальное (и, возможно, среднее) уникальных значений во всех числовых столбцах и попытаться выполнить сопоставление
    • реверс-инжиниринг кода : ваше последнее средство (если вы не имеете дело со сценариями) - не для слабонервных:)
1 голос
/ 28 февраля 2009

Моя стратегия заключается в том, чтобы использовать системный каталог 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;

Конечно, это не приведет ни к какому случаю столбцов, которые связаны, но имеют разные имена.

1 голос
/ 27 февраля 2009

Вы в значительной степени написали ответ на свой вопрос.

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 голосов
/ 28 февраля 2009

Это интересный вопрос. Подход, который я выбрал, заключался в грубом поиске столбцов, соответствующих типам и значениям для небольшого набора выборок. Вам, вероятно, придется настроить эвристику, чтобы обеспечить хорошие результаты для вашей схемы. Я запустил это на схеме, в которой не использовались автоматически увеличиваемые ключи, и это работало хорошо. Код написан для 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;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...