Как я могу повторно использовать логику предложения WHERE с DBI? - PullRequest
4 голосов
/ 13 февраля 2010

Отказ от ответственности: впервые я использовал DBI.

У меня есть таблица MySQL с большим количеством индексированных полей (f1, f2, f3 и т. Д.), Которые используются для генерации предложений WHERE длительными процессами, которые выполняют итерации по частям базы данных, выполняющим различные операции очистки и тестирования.

Текущая версия этого кода работает примерно так:

sub get_list_of_ids() {
    my ($value1, $value2, $value3...) = @_;

    my $stmt = 'SELECT * FROM files WHERE 1';
    my @args;

    if (defined($value1)) {
        $stmt .= ' AND f1 = ?';
        push(@args, $value1);
    }
    # Repeat for all the different fields and values

    my $select_sth = $dbh->prepare($stmt) or die $dbh->errstr;
    $select_sth->execute(@args) or die $select_sth->errstr;

    my @result;
    while (my $array = $select_sth->fetch) {
        push(@result, $$array[0]);
    }
    return \@result;
}

sub function_A() {
    my ($value1, $value2, $value3...) = @_;

    my $id_aref = get_list_of_ids($value1, $value2, $value3...);
    foreach my $id (@$id_aref) {
        # Do something with $id
        # And something else with $id
    }
}

sub function_B() {
    my ($value1, $value2, $value3...) = @_;

    my $id_aref = get_list_of_ids($value1, $value2, $value3...);
    foreach my $id (@$id_aref) {
        # Do something different with $id
        # Maybe even delete the row
    }
}

В любом случае, я собираюсь выбросить в базу намного больше строк, и хорошо знаю, что приведенный выше код не увеличится. Я могу придумать несколько способов исправить это, основываясь на других языках. Как лучше всего справиться с этим в Perl?

Ключевым моментом, на который следует обратить внимание, является то, что логика в get_list_of_ids() слишком длинна для репликации в каждой функции; и что операции с выбранными строками очень разнообразны.

Заранее спасибо.

1 Ответ

6 голосов
/ 13 февраля 2010

Полагаю, под «масштабированием» вы подразумеваете не просто производительность, а техническое обслуживание.

Ключевым изменением вашего кода является передача аргументов в виде пар столбец / значение, а не списка значений с предполагаемым набором столбцов. Это позволит вашему коду обрабатывать любые новые столбцы, которые вы можете добавить.

DBI->selectcol_arrayref удобен и немного быстрее, будучи написанным на C.

Если вы включите RaiseError в своем вызове connect, DBI сгенерирует исключение из-за ошибок, вместо того, чтобы постоянно писать or die .... Ты должен это сделать.

Наконец, так как мы пишем SQL из, возможно, ненадежного пользовательского ввода, я позаботился о том, чтобы избежать имени столбца.

Остальное объясняется в на этом Etherpad , вы можете наблюдать, как ваш код будет постепенно преобразовываться.

sub get_ids {
    my %search = @_;

    my $sql = 'SELECT id FROM files';

    if( keys %search ) {
        $sql .= " WHERE ";
        $sql .= join " AND ", map { "$_ = ?" }
                              map { $dbh->quote_identifier($_) }
                              keys %search;
    }

    return $dbh->selectcol_arrayref($sql, undef, values %search);
}

my $ids = get_ids( foo => 42, bar => 23 );

Если вы ожидаете, что get_ids вернет огромный список, слишком большой для хранения в памяти, тогда вместо извлечения всего массива и сохранения его в памяти вы можете вернуть дескриптор оператора и повторить с ним.

sub get_ids {
    my %search = @_;

    my $sql = 'SELECT id FROM files';

    if( keys %search ) {
        $sql .= " WHERE ";
        $sql .= join " AND ", map { "$_ = ?" }
                              map { $dbh->quote_identifier($_) }
                              keys %search;
    }

    my $sth = $dbh->prepare($sql);
    $sth->execute(values %search);
    return $sth;
}

my $sth = get_ids( foo => 42, bar => 23 );
while( my $id = $sth->fetch ) {
    ...
}

Вы можете комбинировать оба подхода, возвращая список идентификаторов в контексте массива или дескриптор оператора в скаляре.

sub get_ids {
    my %search = @_;

    my $sql = 'SELECT id FROM files';

    if( keys %search ) {
        $sql .= " WHERE ";
        $sql .= join " AND ", map { "$_ = ?" }
                              map { $dbh->quote_identifier($_) }
                              keys %search;
    }

    # Convenient for small lists.
    if( wantarray ) {
        my $ids = $dbh->selectcol_arrayref($sql, undef, values %search);
        return @$ids;
    }
    # Efficient for large ones.
    else {
        my $sth = $dbh->prepare($sql);
        $sth->execute(values %search);
        return $sth;
    }
}

my $sth = get_ids( foo => 42, bar => 23 );
while( my $id = $sth->fetch ) {
    ...
}

my @ids = get_ids( baz => 99 );

В конечном итоге вы захотите прекратить ручное кодирование SQL и использовать сопоставитель объектных отношений (ORM), например DBIx :: Class . Одним из основных преимуществ ORM является то, что он очень гибкий и может сделать все для вас. DBIx :: Class может возвращать простой список результатов или очень мощный итератор. Итератор ленив, он не будет выполнять запрос, пока вы не начнете извлекать строки, что позволит вам изменять запрос по мере необходимости, не усложняя процедуру извлечения.

my $ids = get_ids( foo => 23, bar => 42 );
$ids->rows(20)->all;  # equivalent to adding LIMIT 20
...