动态多插入,带有DBI占位符,适用于多组VALUES

时间:2012-10-04 16:43:35

标签: mysql sql perl dbi

我正在构建一个动态SQL语句,它将通过准备好的DBI语句插入一组或多组VALUES,我的问题是:

由于我有动态数量的VALUES集,因此我需要添加尽可能多的( ?, ?, ?),( ?, ?, ?)等以扩展语句INSERT INTO `tblname` ( $columnsString ) VALUES,以便仅使用一个查询提交占位符和绑定值 - 这是首选方法(效率最高等等 - 如果可能,效率背后的推理会对您的答案有所帮助),或者我应该将其构建为sprintf和{{的查询字符串1}?

(作为一点额外信息:我现在实际上正在使用AnyEvent :: DBI,它只暴露占位符和绑定值而不是dbh->quote()方法,所以这对我来说不容易实现不创建另一个直接DBI quote()并使用另一个数据库服务器连接只是为了使用$dbh方法,或者不自行更改AnyEvent :: DBI模块。)

通常情况下,我会根据需要执行这些语句,但在这种繁重的工作负载情况下,我正在尝试批量插入以获得一些数据库效率。

此外,如果有人可以回答是否可能(以及如何)使用占位符插入sql quote()值并绑定值非常棒的值。通常情况下,如果我需要这样做,我会将DEFAULT直接附加到字符串中,并仅对非DEFAULT值使用sprintf和$dbh->quote()

更新:

通过快速聊天解决了误解。用户ikegami建议不要在没有占位符的情况下自己构建查询字符串,而只是将VALUES和占位符混合在一起,例如:

DEFAULT

我第一次在SO上提出这个问题背后的一些推理是因为我有点反对这种混合,因为我认为这使得代码的可读性降低了,尽管我确信sql'DEFAULT'不能在占位符绑定值,这是我开始实现的方法。

在可能的情况下使用占位符似乎是更可接受的构建查询的方法,如果您需要SQL $queryString .= '(DEFAULT,?,?),(DEFAULT,DEFAULT,DEFAULT)';,则只需将其包含在与占位符相同的查询构建中。这不适用于DEFAULT值,因为这些值可以插入占位符并且绑定值为NULL

更新2:

我询问性能的原因,使用quote()构建自己的查询与使用占位符构建的“接受”,以及为什么我选择了使用SQL undef的所有列的解决方案是因为我每天大约有2-4百万行进入一个可怕的数据库服务器,而我的代码运行在同样糟糕的服务器上。由于我需要INSERT INTO tblname (cols) sql值,以及这些糟糕的性能限制,我现在选择了一个解决方案。

对于偶然发现这种情况的未来开发者 - 看看@ emazep使用SQL :: Abstract的解决方案,或者如果由于某种原因你需要构建自己的解决方案,你可以考虑使用@ Schwern的子程序解决方案或者可能考虑使用一些@ ikegami对它的回答,因为这些都是关于DBI使用和构建动态查询的“当前状态”的很好答案。

4 个答案:

答案 0 :(得分:2)

我偶尔会使用像

这样的结构
#!/usr/bin/env perl

use strict; use warnings;

# ...

my @columns = ('a' .. 'z');

my $sql = sprintf(q{INSERT INTO sometable (%s) VALUES (%s)},
    join(',', map $dbh->quote($_), @columns),
    join(',', ('?') x @columns),
);

至于处理DEFAULT,是否不会将该列留出来确保数据库将其设置为默认值?

答案 1 :(得分:2)

除非有一个特定的理由重新发明轮子(可能有一些),SQL::Abstract(以及其他)已经解决了我们所有人的动态SQL生成问题:

my %data = (
    name    => 'Jimbo Bobson',
    phone   => '123-456-7890',
    address => '42 Sister Lane',
    city    => 'St. Louis',
    state   => 'Louisiana'
);

use SQL::Abstract;
my ($stmt, @bind)
    = SQL::Abstract->new->insert('people', \%data);

print $stmt, "\n";
print join ', ', @bind;

打印:

INSERT INTO people ( address, city, name, phone, state)
VALUES ( ?, ?, ?, ?, ? )
42 Sister Lane, St. Louis, Jimbo Bobson, 123-456-7890, Louisiana

SQL::Abstract然后提供a nice trick迭代多行以插入而不是每次都重新生成SQL,但对于批量插入,还有SQL::Abstract::Plugin::InsertMulti

use SQL::Abstract;    
use SQL::Abstract::Plugin::InsertMulti;

my ($stmt, @bind)
    = SQL::Abstract->new->insert_multi( 'people', [
        { name => 'foo', age => 23 },
        { name => 'bar', age => 40 },
    ]);

# INSERT INTO people ( age, name ) VALUES ( ?, ? ), ( ?, ? )
# 23, foo, 40, bar

答案 2 :(得分:1)

如果您使用占位符进行“静态”查询,则应将它们用于“动态”查询。查询是一种查询。

my $stmt = 'UPDATE Widget SET foo=?'
my @params = $foo;

if ($set_far) {
   $stmt .= ', far=?';
   push @params, $far;
}

{
   my @where;

   if ($check_boo) {
      push @where, 'boo=?';
      push @params, $boo;
   }

   if ($check_bar) {
      push @where, 'bar=?';
      push @params, $bar;
   }

   $stmt .= ' WHERE ' . join ' AND ', map "($_)", @where
      if @where;
}

$dbh->do($stmt, undef, @params);

我使用UPDATE因为它允许我演示更多,但一切也适用于INSERT。

my @fields = ('foo');
my @params = ($foo);

if ($set_far) {
   push @fields, 'bar';
   push @params, $far;
}

$stmt = 'INSERT INTO Widget ('
      . join(',', @fields)
      . ') VALUES ('
      . join(',', ('?')x@fields)
      . ')';

$dbh->do($stmt, undef, @params);

答案 3 :(得分:1)

您对代码的可读性以及能够传入DEFAULT表示担忧。我会更进一步了解@ ikegami的答案......

sub insert {
    my($dbh, $table, $fields, $values) = @_;

    my $q_table      = $dbh->quote($table);
    my @q_fields     = map { $dbh->quote($_) } @$fields;
    my @placeholders = map { "?" } @q_fields;

    my $sql = qq{
        INSERT INTO $q_table
               ( @{[ join(', ', @q_fields)    ]} )
        VALUES ( @{[ join(', ', @placeholders ]} )
    };

    return $dbh->do($sql, undef, @$values);
}

现在您有一个通用的多值插入例程。

# INSERT INTO foo ('bar', 'baz') VALUES ( 23, 42 )
insert( $dbh, "foo", ['bar', 'baz'], [23, 43] );

要指明默认值,请不要传入该列。

# INSERT INTO foo ('bar') VALUES ( 23 )
# 'baz' will use its default
insert( $dbh, "foo", ['bar'], [23] );

您可以对此进行优化,使子例程通过一个子例程调用和一个预准备语句在客户端执行多个插入(如果它支持准备好的句柄,则可能在数据库端有一些)。

sub insert {
    my($dbh, $table, $fields, @rows) = @_;

    my $q_table      = $dbh->quote($table);
    my @q_fields     = map { $dbh->quote($_) } @$fields;
    my @placeholders = map { "?" } @q_fields;

    my $sql = qq{
        INSERT INTO $q_table
               ( @{[ join(', ', @q_fields)    ]} )
        VALUES ( @{[ join(', ', @placeholders ]} )
    };

    my $sth = $dbh->prepare_cached($sql);
    for my $values (@rows) {
        $sth->execute(@$values);
    }
}

# INSERT INTO foo ('bar', 'baz') VALUES ( 23, 42 )
# INSERT INTO foo ('bar', 'baz') VALUES ( 99, 12 )
insert( $dbh, "foo", ['bar', 'baz'], [23, 43], [99, 12] );

最后,您可以编写在单个语句中传递多个值的批量插入。这可能是执行大量插入的最有效方法。这是一个固定的列集和传入DEFAULT标记的地方派上用场。我已经使用了作为标量引用传递的值被视为原始SQL值的习语。现在,您可以灵活地传递任何您喜欢的内容。

sub insert {
    my($dbh, $table, $fields, @rows) = @_;

    my $q_table      = $dbh->quote($table);
    my @q_fields     = map { $dbh->quote($_) } @$fields;

    my $sql = qq{
        INSERT INTO $q_table
               ( @{[ join(', ', @q_fields)    ]} )
        VALUES
    };

    # This would be more elegant building an array and then joining it together
    # on ",\n", but that would double the memory usage and there might be
    # a lot of values.
    for my $values (@rows) {
        $sql .= "( ";
        # Scalar refs are treated as bare SQL.
        $sql .= join ", ", map { ref $value ? $$_ : $dbh->quote($_) } @$values;
        $sql .= "),\n";
    }
    $sql =~ s{,\n$}{};

    return $dbh->do($sql);
}

# INSERT INTO foo ('bar', 'baz') VALUES ( 23, NOW ), ( DEFAULT, 12 )
insert( $dbh, "foo", ['bar', 'baz'], [23, \"NOW"], [\"DEFAULT", 12] );

缺点是这会在内存中构建一个字符串,可能非常大。要解决这个问题,你必须参与database specific bulk insert from file syntax

不要自己编写所有这些SQL生成内容,而是使用@ emazep的答案并使用SQL :: Abstract和SQL::Abstract::Plugin::InsertMulti

请确保profile