【问题标题】:Perl DBI - run SQL Script with multiple statementsPerl DBI - 使用多个语句运行 SQL 脚本
【发布时间】: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 一次只能执行一条语句,并且没有“;”最后。

【问题讨论】:

    标签: perl oracle 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);
      

      【讨论】:

        猜你喜欢
        • 2013-04-29
        • 1970-01-01
        • 2014-05-07
        • 2013-11-20
        • 2013-04-06
        • 1970-01-01
        • 2012-08-09
        • 2014-01-28
        • 1970-01-01
        相关资源
        最近更新 更多