Perl DBI fetchrow_array问题

时间:2012-11-19 17:10:26

标签: perl dbi

我遇到以下问题。我有一个存储过程,用于更新数据或将数据插入名为UpdateData的数据库中。它看起来像这样(虽然简化):

CREATE PROCEDURE [dbo].[UpdateData] 
   @dataId as int,
   @data as int,
AS
BEGIN 
SET NOCOUNT ON;
declare @count int
select @count = (select COUNT(*) from DataTable data where data.id = @dataId)
if @count = 1
begin
update DataTable set data = @data from DataTable where  data.id = @dataId
select 'Updated' [operation] ,  @@ROWCOUNT [count]
end
else
begin
insert into DataTable (id, data)  values(@dataId, @data)
select 'Inserted' [operation] , @@ROWCOUNT [count]
end
END

我使用DBI通过迭代我的数据的预准备语句从perl调用此存储过程。然后我使用fetchrow_array调用来获取有关执行哪个操作的信息:

my $dbh = getDBHandle($debug);
foreach (@Data) {
     $updateData->execute($->[0], $_->[1]);
     my @row = $updateData->fetchrow_array;
     my ($action, $count) = ($row[0], $row[1]);
     print $row[0] .",$action, $count\n";
 }

一旦运行任何更新语句,随后所有插入的操作描述都被截断为'inserted'到'inserte'。我认为这种情况正在发生,因为字符串'updated'比'inserted'少了一个字符,一旦fetchrow_array在列中调用了该字符串,它就会重置某种限制。如果我在两个描述字符串之间做出差异,那么就像修改存储过程一样使用“更新”而不是“更新”(两个字符的差异为'已插入')

select 'Update' [operation] ,  @@ROWCOUNT [count]

我收到错误:

   DBD::ODBC::st fetchrow_array failed: [Microsoft][ODBC SQL Server Driver]String data, right truncation (SQL-01004)

总而言之,输出看起来像

1,插入10

2,更新,15

3,Inserte,20

4,更新,5

关于为什么执行不是独立的任何想法以及解决此问题的最佳方法是什么。我知道我可以采取相同的行动,但我希望有更好的解决方案。

编辑: 跟进问题。如果UpdateData过程需要调用另一个也返回数据的过程。在Perl中是否可以获得两个结果集。一个来自内部程序,一个来自外部。现在,> fetchrow_array只获取内部结果集。

编辑2:关于数据截断的原始问题,我想知道为什么在每次执行后调用$ updateData->都不会导致每次执行时都重置宽度。 IE

  foreach (@Data) {
       $updateData->execute($->[0], $_->[1]);
       my @row = $updateData->fetchrow_array;
       my ($action, $count) = ($row[0], $row[1]);
       print $row[0] .",$action, $count\n";
       $updateData->finish;
   }

2 个答案:

答案 0 :(得分:1)

当我们不知道表中的数据时,很难尝试重现您的问题。像往常这样的问题,最好尝试编写一个小的自包含脚本来演示问题。当我试图猜测你的场景并运行时:

use DBI;
use strict;
use warnings;

my $h = DBI->connect('dbi:ODBC:xx','xx','xx',
                     {RaiseError => 1, PrintError => 0});

eval {
    $h->do(q/drop table mje/);
    $h->do(q/drop procedure pmje/);
};

$h->do(<<'EOT');
create table mje (id int, data int)
EOT

#my @data = (1,1,2);
#my $s = $h->prepare(q/insert into mje (id, data) values(?,?)/);
#foreach (@data) {
#    $s->execute($_, $_);
#}

$h->do(<<'EOT');
CREATE PROCEDURE pmje (
    @dataId as int,
    @data as int)
AS
BEGIN
SET NOCOUNT ON;
declare @count int
select @count = (select COUNT(*) from mje where id = @dataId)
if @count = 1
begin
update mje set data = @data from mje where id = @dataId
select 'Updated' [operation] ,  @@ROWCOUNT [count]
end
else
begin
insert into mje (id, data)  values(@dataId, @data)
select 'Inserted' [operation] , @@ROWCOUNT [count]
end
END
EOT

my $s = $h->prepare(q/{call pmje(?,?)}/);
my @data = (1,2,1);
foreach (@data) {
    $s->execute($_, $_);
    my @row = $s->fetchrow_array;
    my ($action, $count) = ($row[0], $row[1]);
    print $row[0] .",$action, $count\n";
 }

我明白了:

Inserted,Inserted, 1
Updated,Updated, 1
Inserted,Inserted, 1

但是,我可能没有使用与您相同的ODBC驱动程序(我在Linux上使用的是Easysoft SQL Server驱动程序)。您看到的截断错误表明DBD :: ODBC使用太小的缓冲区绑定了列。 DBD :: ODBC使用SQLDescribeCol来获取所需缓冲区的大小,所以在你的情况下,我建议SQLDescribeCol返回7而不是8.这应该是相当直接的证明你是否启用了DBD :: ODBC跟踪,但是你怎么做这取决于您的DBI和DBD :: ODBC最近的情况。如果您最近有DBI和DBD :: ODBC,那么:

设置DBI_TRACE = DBD = x.log(windows) 或导出DBI_TRACE = DBD = x.log(unix)

如果在x.log中产生的不多

设置DBI_TRACE = 15 = x.log(windows) export DBI_TRACE = 15 = x.log(unix)

并运行您的脚本。当我这样做时,我得到一条这样的一行:

   DescribeCol column = 1, name = operation, namelen = 9, type = VARCHAR(12), precision/column size = 8, scale = 0, nullable = 0

告诉我为列返回的缓冲区大小为8.您可能之前有7个,现在有128个。我认为没有什么会破坏,因为ODBC驱动程序或数据库将如何知道缓冲区应该是多大?

答案 1 :(得分:0)

虽然我仍然不知道为什么行为是这样的,但我确实找到了解决方案。 如果在存储过程中,一个声明了一个变量,输出将被放入其中并且大小足够大,那么一切都按预期工作。也就是说,这解决了问题:

CREATE PROCEDURE [dbo].[UpdateData] 
    @dataId as int,
    @data as int,
AS
BEGIN 
SET NOCOUNT ON;
declare @operation varchar(128)
declare @count int
select @count = (select COUNT(*) from DataTable data where data.id = @dataId)
if @count = 1
begin
update DataTable set data = @data from DataTable where  data.id = @dataId
set @operation = 'Updated'
select @operation [operation] ,  @@ROWCOUNT [count]
end
else
begin
insert into DataTable (id, data)  values(@dataId, @data)
set @operation = 'Inserted'
select @operation [operation] ,  @@ROWCOUNT [count]
end
END

编辑: 这是对bohicas帖子的回复。以下脚本(这是对他发布的内容的略微修改,在我的机器上重现问题)。

use DBI;
use strict;
use warnings;

my $debug = 0;
my $h = DBI->connect('dbi:ODBC:xx','xx','xx',
                 {RaiseError => 1, PrintError => 0});

eval {
    $h->do(q/drop table mje/);
    $h->do(q/drop procedure pmje/);
};

$h->do(<<'EOT');
create table mje (id int, data int)
EOT

$h->do(<<'EOT');
CREATE PROCEDURE pmje (
    @dataId as int,
    @data as int)
AS
BEGIN
SET NOCOUNT ON;
declare @count int
select @count = (select COUNT(*) from mje where id = @dataId)
if @count = 1
begin
update mje set data = @data from mje where id = @dataId
select 'Updated' [operation] ,  @@ROWCOUNT [count]
end
else
begin
insert into mje (id, data)  values(@dataId, @data)
select 'Inserted' [operation] , @@ROWCOUNT [count]
end
END
EOT

my $s = $h->prepare(q/{call pmje(?,?)}/);
my @data = (1);
foreach (@data) {
    $s->execute($_, $_);
    my @row = $s->fetchrow_array;
    my ($action, $count) = ($row[0], $row[1]);
    print $row[0] .",$action, $count\n";
    $s->finish();
 }
 $h->disconnect();

 $h = getDBHandle($debug);
 $s = $h->prepare(q/{call pmje(?,?)}/);

@data = (1,2);
foreach (@data) {
    $s->execute($_, $_);
    my @row = $s->fetchrow_array;
    my ($action, $count) = ($row[0], $row[1]);
    print $row[0] .",$action, $count\n";
    $s->finish();
 }