4

免责声明:我第一次使用 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 中处理它的最佳方法是什么?

需要注意的关键点是 in 的逻辑get_list_of_ids()太长,无法在每个函数中复制;并且对所选行的操作非常多样化。

提前致谢。

4

1 回答 1

6

我认为“扩大规模”是指维护术语而不是性能。

代码的关键更改是将参数作为列/值对传递,而不是具有假定列集的值列表。这将允许您的代码处理您可能添加的任何新列。

DBI->selectcol_arrayref用 C 编写,既方便又快一点。

如果您RaiseErrorconnect调用中打开,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 ) {
    ...
}

您可以通过返回数组上下文中的 ID 列表或标量中的语句句柄来组合这两种方法。

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
于 2010-02-13T09:22:57.223 回答