【问题标题】:Perl sql query statementPerl sql查询语句
【发布时间】:2013-12-16 16:24:29
【问题描述】:

我的sql语句如下:

if not exists (select col_a from t_a where co_b = 'v_b')
begin
    insert into t_a (col_a  ,col_b  ) 
        VALUES(v_a,v_b)
end
else
begin
    update t_a set col_a = v_a, col_b = v_b where col_b = 'v_b'
end

由于我有数百行要更新,我怎样才能在 Perl 中以最少的时间成本进行更新?

如果我使用 Prepare + Execute,如何使用占位符 ? 编写语句?

$dbh->prepare($statement); 是否支持像上面那样的多个复合 SQL 行?还是我必须将这些行保存到一个 sql 文件中并使用 SQL server 运行它?

为了让问题更清楚,我的 Perl 行如下所示:

$statement = "if ... VALUES(?,?)...update t_a set col_a = ?, col_b = ?"; 
# better to use one binding values(v_a, v_b) couplets mapping 
# the 2 placeholders of insert and update both?
foreach (@$va_arr) {
    my $values_for_one_row = $_;            
    $dbh->prepare($statement);
    $execute->execute($values_for_one_row->{col_a }, $values_for_one_row->{col_b });
}

我忘了一件事:'whatever' 也是 $va_arr 中的一个值,每次迭代都会更改:如果不存在(从 t_a 中选择 col_a,其中 co_b = 'v_b')。此外,更新部分应该是: update t_a set col_a = ?, col_b = ?其中 col_b = "v_b"。似乎没有更好的方法然后将准备包含到循环中?抱歉,我认为示例不完整。不过我觉得simbabque的回答已经够好了。

【问题讨论】:

  • DBI 支持每次调用 execute 运行单个 SQL 语句。这对我来说似乎是一个单一的陈述。您应该阅读documentation of DBI。
  • 对不起,我没听清楚你的话。我的语句有一些由 SQL 服务器引擎更好地处理的 if-else 逻辑。我没想到将逻辑包含在 Perl 行中。我听说准备很昂贵,因为我每次调用 @$va_arr 循环都有数百行。
  • 你说得对。请在下面查看我的答案。
  • 什么版本的 SQL Server?你能"upsert" via MERGE吗?

标签: sql sql-server perl


【解决方案1】:

您可以毫无问题地使用您的 SQL。您需要准备一次声明。我假设你的 $va_arr 看起来像这样:

my $va_arr = [
  {
    col_a => 1,
    col_b => 2,
  },
  {
    col_a => 'foo',
    col_b => 'bar',
  },
];

运行它的代码如下。 请注意,您必须将 col_n 参数传递两次,因为它需要在每次执行时将它们两次填充到每个 ? 中。它们在查询中按? 的顺序填写,因此我们需要col_a、col_b 用于INSERT,另一个col_a、col_b 用于UPDATE。

my $sql = <<'EOSQL';
if not exists (select col_a from t_a where co_b = 'whatever')
begin
    insert into t_a (col_a  ,col_b  ) 
        VALUES(?, ?)
end
else
begin
    update t_a set col_a = ?, col_b = ?
end
EOSQL

my $sth = $dbi->prepare($sql);
foreach ($values = @{ $va_arr }) {
  $dbh->execute($values->{col_a }, $values->{col_b }, 
                $values->{col_a }, $values->{col_b });
}

如果您有很长的列列表并且您知道顺序,请考虑以下事项:

my @columns = qw( col_a col_b col_c col_n );
my $va_arr = [
  {
    col_a => 1,
    col_b => 2,
    col_n => 99,
  },
  {
    col_a => 'foo',
    col_b => 'bar',
    col_n => 'baz',
  },
];

# build the sql dynamically based on columns
my $sql = q{
if not exists (select col_a from t_a where co_b = 'whatever')
begin
    insert into t_a (} . join(',' @columns) . q{) 
        VALUES(} . join(',', map '?', @columns) . q{)
end
else
begin
    update t_a set } . join(',' map { "$_ => ?" } @columns) . q{
end
};
my $sth = $dbi->prepare($sql);
foreach ($values = @{ $va_arr }) {
  $dbh->execute(@{$values}{@columns}, @{$values}{@columns});
}

让我们看看这是做什么的。如果您有很长的列列表,这将很有帮助。

  • 您知道他们的姓名和顺序,并将其输入到@columns。
  • 基于这些列构建 SQL。我们必须将列名和? 添加到INSERT,并将两者的组合添加到每个列的UPDATE。
  • 使用hash ref slice 执行它

请注意,我没有运行它,只是在这里破解了它。

【讨论】:

  • 我忘了一件事:'whatever' 也是 $va_arr 中的一个值,每次迭代都会更改:如果不存在(从 t_a 中选择 col_a,其中 co_b = 'v_b')。此外,更新部分应该是: update t_a set col_a = ?, col_b = ?其中 col_b = "v_b"。似乎没有更好的方法将准备包含在循环中?
  • 我不确定我明白了。如果查询发生变化(即= 或like 的左侧),则需要重新准备。如果你只是在一个条件的右边有一个变量,你可以在那里放一个?,不需要重新准备。
  • 算了。对于另一个问题:事实上,正如我所尝试的那样,应该像这样调用执行函数: $dbh->execute($values->{col_a }, $values->{col_b }, $values->{col_a } , $values->{col_b });绑定值应该一一列出,即使它们在插入和更新相同的序列中重复。否则 Perl 解释器会报错指出占位符的个数和绑定值不匹配。
  • 啊,该死的。当然,您需要将它们放入两次。忘记了,现在补上。对不起。
【解决方案2】:

你应该把准备语句放在循环之外并使用事务

例如:

my $sql1 = qq(select col_a from t_a where col_b = ?);
my $sql2 = qq(insert into t_a (col_a, col_b) VALUES(?, ?));
my $sql3 = qq(update t_a set col_a = ? where col_b = ?);

my $query = $dbh->prepare($sql1);

$dbh->begin_work();

foreach (@$va_arr) {

    my $values_for_one_row = $_;
    $query->execute($values_for_one_row->{col_b});
    my @out = $query->fetchrow_array();
    $query->finish();

    if ( not defined $out[0] )
      {
        $dbh->do($sql2, undef, $values_for_one_row->{col_a}, $values_for_one_row->{col_b});
      }
      else
      {
        $dbh->do($sql3, undef, $values_for_one_row->{col_a}, $values_for_one_row->{col_b});
      }
}

$dbh->commit();

【讨论】:

  • 您在循环外对prepare 的评论很好,但是您为什么要分解OP 在他的SQL 查询中的逻辑? SQL Server 能够对此进行优化,因此无需进行额外的查询。此外,每次在循环内使用do 都会prepare。
【解决方案3】:

如果 upsert 不可用,我可以这样做:

  1. 将数据批量加载到临时表中。

  2. 删除连接到目标表的所有数据。

  3. 将数据从暂存区插入到目标。

或者,您可以从暂存更新到目标,从连接的暂存数据中删除,然后插入暂存中剩余的内容。

或者,几百行并不多,所以我可以:在循环外准备一个插入和更新语句句柄。然后在循环中:

my $rows = $upd_sth->execute(...);
$ins_sth->execute(...) if $rows == 0;

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-12-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多