Perl DBI - 使用多个语句运行 SQL 脚本
Posted
技术标签:
【中文标题】Perl DBI - 使用多个语句运行 SQL 脚本【英文标题】:Perl DBI - run SQL Script with multiple statements 【发布时间】:2010-11-17 00:07:33 【问题描述】:我有一个 sql 文件 test.sql 用于运行一些看起来像这样的 SQL(创建对象/更新/删除/插入)
CREATE TABLE test_dbi1 (
test_dbi_intr_no NUMBER(15)
, test_dbi_name VARCHAR2(100);
UPDATE mytable
SET col1=1;
CREATE TABLE test_dbi2 (
test_dbi_intr_no NUMBER(15)
, test_dbi_name VARCHAR2(100);
通常,我会使用 SQLPLUS(在 Perl 中)使用以下命令执行此 test.sql: @test.sql
有没有办法在 Perl 中使用 DBI 来做同样的事情? 到目前为止,我发现 DBI 一次只能执行一条语句,并且没有“;”最后。
【问题讨论】:
【参考方案1】:数据库控制一次可以执行多少条语句。我不记得 Oracle 是否允许每个 prepare
多个语句(mysql 允许)。试试这个:
my $dbh = DBI->connect(
"dbi:Oracle:dbname",
"username",
"password",
ChopBlanks => 1,
AutoCommit => 1,
RaiseError => 1,
PrintError => 1,
FetchHashKeyName => 'NAME_lc',
);
$dbh->do("
CREATE TABLE test_dbi1 (
test_dbi_intr_no NUMBER(15),
test_dbi_name VARCHAR2(100)
);
UPDATE mytable
SET col1=1;
CREATE TABLE test_dbi2 (
test_dbi_intr_no NUMBER(15),
test_dbi_name VARCHAR2(100)
);
");
$dbh->disconnect;
当然,如果你把语句分解,你会得到更好的错误处理。您可以使用简单的解析器将字符串分解为单独的语句:
#!/usr/bin/perl
use strict;
use warnings;
my $sql = "
CREATE TABLE test_dbi1 (
test_dbi_intr_no NUMBER(15),
test_dbi_name VARCHAR2(100)
);
UPDATE mytable
SET col1=';yes;'
WHERE col2=1;
UPDATE mytable
SET col1='Don\\'t use ;s and \\'s together, it is a pain'
WHERE col2=1;
CREATE TABLE test_dbi2 (
test_dbi_intr_no NUMBER(15),
test_dbi_name VARCHAR2(100)
);
";
my @statements = ("");
#split the string into interesting pieces (i.e. tokens):
# ' delimits strings
# \ pass on the next character if inside a string
# ; delimits statements unless it is in a string
# and anything else
# NOTE: the grep ord is to get rid of the nul
# characters the split seems to be adding
my @tokens = grep ord split /([\\';])/, $sql;
# NOTE: this ' fixes the stupid SO syntax highlighter
#this is true if we are in a string and should ignore ;
my $in_string = 0;
my $escape = 0;
#while there are still tokens to process
while (@tokens)
#grab the next token
my $token = shift @tokens;
#if we are in a string
if ($in_string)
#add the token to the last statement
$statements[-1] .= $token;
#setup the escape if the token is \
if ($token eq "\\")
$escape = 1;
next;
#turn off $in_string if the token is ' and it isn't escaped
$in_string = 0 if not $escape and $token eq "'";
$escape = 0; #turn off escape if it was on
#loop again to get the next token
next;
#if the token is ; and we aren't in a string
if ($token eq ';')
#create a new statement
push @statements, "";
#loop again to get the next token
next;
#add the token to the last statement
$statements[-1] .= $token;
#if the token is ' then turn on $in_string
$in_string = 1 if $token eq "'";
#only keep statements that are not blank
@statements = grep /\S/ @statements;
for my $i (0 .. $#statements)
print "statement $i:\n$statements[$i]\n\n";
【讨论】:
不幸的是,这不起作用,我得到“ORA-00911:无效字符”,因为“;”问题是,我有这个 test.sql 文件,我需要一种使用 DBI 将其加载到 Oracle 中的方法。唯一的办法就是像你说的那样打破它。但是由于我永远不会确切地知道这个文件中会包含什么,我该如何分解它呢?如果我使用“;”分割文件如果我有这样的更新可能会导致问题:UPDATE mytable SET col1=';yes;' WHERE col2=1; 那是 Oracle 的抱怨,而不是 Perl。我对 Oracle 不允许多条语句并不感到特别惊讶。它们很危险,允许某些形式的 SQL 注入攻击。 CPAN 有SQL::SplitStatement
,这在这里可能很有用。【参考方案2】:
请看一下这个新的 CPAN 模块:DBIx::MultiStatementDo
正是为此而设计的。
【讨论】:
【参考方案3】:Oracle 可以使用匿名 PL/SQL 块在一个准备中运行多个 SQL 语句。
例如
$dbh->do("
BEGIN
UPDATE table_1 SET col_a = col_a -1;
DELETE FROM table_2 where id in (select id from table_1 where col_a = 0);
END;
");
DDL(创建或删除对象)更复杂,主要是因为它是您不应该临时做的事情。
【讨论】:
是的,但正如你所说,DDL 更复杂,我们需要它们(用于自动安装脚本)。【参考方案4】:您可以在 Perl 中添加另一层逻辑,解析 SQL 脚本,将其拆分为语句并使用上述技术逐个执行
--sql file
-- [statement1]
SQLCODE...
-- [statement2]
SQLCODE...
#Gets queries from file.
sub sql_q
my ($self) = @_;
return $self->sql_q if $self->sql_q;
my $file = $self->sql_queries_file;
$self->sql_q || do
-e $file || croak( 'Queries file ' . $file . ' can not be found.' );
my $fh = IO::File->new("< $file");
my @lines;
( $fh->binmode and @lines = $fh->getlines and $fh->close ) or croak $!;
my ($key);
foreach ( 0 .. @lines - 1 )
next if ( $lines[$_] =~ /^;/ );
if ( $lines[$_] =~ /^--\s*?\[(\w+)\]/ )
$key = $1;
$self->sql_q$key .= $lines[$_] if $key;
;
return $self->sql_q;
#then in your script
#foreach statement something like
$dbh->prepare($sql_obj->sql_q->statement_name)->execute(@bindvars);
【讨论】:
以上是关于Perl DBI - 使用多个语句运行 SQL 脚本的主要内容,如果未能解决你的问题,请参考以下文章