3

次の問題があります。UpdateData と呼ばれる DB にデータを更新または挿入するために使用するストアド プロシージャがあります。おおよそ次のようになります (簡略化されていますが)。

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」よりも 1 文字少なく、列内のその文字列で fetchrow_array が呼び出されると、何らかの制限がリセットされるために発生していると思います。2 つの説明文字列の違いが 1 つ以上ある場合は、ストア プロシージャを変更して、「更新済み」ではなく「更新」を使用するようにします (「挿入済み」に対する 2 文字の違い)。

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 で両方の結果セットを取得することは可能ですか? 1 つは内側の手順から、もう 1 つは外側の手順から来ます。現在、>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 が 8 ではなく 7 を返すことをお勧めします。これは、DBI と DBD::ODBC がどのくらい新しいかによって異なります。最近の DBI と DBD::ODBC をお持ちの場合:

DBI_TRACE=DBD=x.log を設定する (Windows) または DBI_TRACE=DBD=x.log をエクスポートする (UNIX)

それが x.log であまり生成されない場合

set 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ドライバーまたはデータベースがバッファーの大きさをどのように知るかなど、何かが壊れているとは思いませんか?

于 2012-11-20T08:41:20.887 に答える
0

動作が何であるかはまだわかりませんが、解決策を見つけました。ストアド プロシージャの IF で、出力が配置される変数が宣言され、サイズが十分に大きい場合、すべてが正常に機能します。つまり、これにより次のことが修正されます。

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 に答える