3

我有以下问题。我有一个存储过程,用于更新或将数据插入到名为 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";
 }

发生的情况是,一旦运行任何更新语句,随后所有插入的操作描述都会从“插入”截断为“插入”。我认为这是因为字符串 '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,插入,20

4,更新,5

关于为什么执行不独立以及解决此问题的最佳方法是什么的任何想法。我知道我可以使动作相同,但我想要一个更好的解决方案。

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

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

2 回答 2

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) 导出 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 驱动程序或数据库如何知道缓冲区应该是多大?

于 2012-11-20T08:41:20.887 回答
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();
 }
于 2012-11-19T20:01:54.960 回答