最近、
PerlからSQL Serverへの接続を行うコードを書いていたので、その覚え書きです。
実は、ストアドや.NET系のコーディングを始める前は、PerlやJavaがメインで、DB操作のためのSQLは、コードや設定ファイルの中に書くことが中心でした。(ただしOracleとかでした)
その後、SQL Serverを中心にお仕事をするようになってから、ストアドを多用するようになり、コードもC#などで書くようになっていきました。
さて今回、XML-RPCで外部APIと連携を行う必要が出たので、PerlでSQL Serverの操作を行うことになりました。(C#でもXML-RPCは実装可能だったのですが、私以外のメンテナーがいないので、同僚がメンテナンスしやすいPerlで行きましょう、ということになったのでした...)
ただ、私としては、できるだけ面倒なSQLはコードに書かず、サーバ側のストアドでカバーしたいというのが希望でした。同僚も、SQL(ストアド)の方は操作/修正には問題無いので、この方針で。
- ストアドを使うメリットは、まずは、Perl中にSQLを書かなくて済むこと。(コード内で、@や\といった特殊文エスケープしたりするのが面倒なんですよね....)
- 同じストアドは(データ取得用ですけど)、ManagementStudioからも呼べるし、WebServiceでも呼び出せます。JavaのJDBCでも呼べますね。
さて、問題は、PerlのDBIで、SELECTステートメントを返すストアドをどう実行するか、ということと、複数SELECTの結果が返る場合、値をどうやって取得するかです。
- どうやらDBI + ODBCだとうまくできません....。(というか、とんでもなく久しぶりなので、よく分らない)
- かと言って、またもPerl中に長~いSQLは書きたくない。
一方、ADO .NETだと、SELECT結果は、複数STATEMENTも含め、DataSetというオブジェクトで返してくれるので、それぞれテーブル名や順番に取り出すことができます。(ここが、私が.NETのASPにしたいよ~と言った理由のひとつでした)
で、かなり時間がかかってしまったのですが、調べた結果、Win32::OLEを使うと、ADOを用できることが判明。
若干C#などで扱うのよりは面倒ではありますが、これでいくらか幸せになりました。
use strict;
use Win32::OLE;
use vars qw($dsn);
# Data Source Setting
$dsn = "driver=SQL Server;".
"Server=localhost;".
"database=TestDB;".
"Trusted_Connection=no;".
"AutoTranslate=No;";
# Connection Open
my $Conn = Win32::OLE->new("ADODB.Connection");
$Conn->{CursorLocation} = 3;? # the same as adUseClient
$Conn->{Open} = $dsn;
if (Win32::OLE->LastError() != 0) {
print "Failed creating ADODB.Connection object -> "
. Win32::OLE->LastError();
exit 0;
}
my $Cmd = Win32::OLE->new("ADODB.Command");
if (Win32::OLE->LastError() != 0) {
print "Failed creating ADODB.Command object -> "
. Win32::OLE->LastError();
exit 0;
}
my $Id;
my $ReferenceId;
####
# For Test
#
# 実際は、CGIのRequest Parameterやコマンドライン引数などから受け取ります。
#
###
$Id = 8;
$ReferenceId= 300;
# Command Execute
$Cmd->{CommandType} = 4; # Execute as Stored
# Prepared Statementを使わない場合はこんなかんじ。
$Cmd->{CommandText} =
"dbo.getSampleData ($Id, $ReferenceId )";
$Cmd->{CursorLocation} = 3;
$Cmd->{ActiveConnection} = $Conn;
$Cmd->{Prepared} = 1;
$Cmd->{CommandTimeout} = 15;
my $rs = $Cmd->Execute();
if (Win32::OLE->LastError() != 0) {
print "Failed opening ADODB.Recordset object for Command -> ". Win32::OLE->LastError();
exit 0;
}
my $rs_count = $rs->{RecordCount};
print $rs_count . "\n";
if (!$rs_count) {
print "Record not found to post or close.\n";
exit 0;
}
print "\n------------1st SELECT Result-------------\n";
while ($rs->EOF != 1) {
print "Id = ". $rs->Fields('Id')->{Value}."\n";
print "Name = ". $rs->Fields('Name')->{Value}."\n";
:
$rs->MoveNext();
}
my $rs1 = $rs->NextRecordSet();
print "\n------------2nd SELECT Result-------------\n";
while ($rs1->EOF != 1) {
print "ReferenceId = ". $rs->Fields('ReferenceId')->{Value}."\n";
print "ProductName = ". $rs->Fields('ProductName')->{Value}."\n";
:
$rs1->MoveNext();
}
$rs->Close();
$Conn->Close();
exit 0;
Prepared Statementを使ってストアド実行時の引数を渡したい場合は、こんな感じ。
$Cmd->{Prepared} = 1;
#ストアドの戻り値を指定する場合 (Direction = 4)
$Cmd->Parameters->Append(
$Cmd->CreateParameter('@retval', 3, 4));
# ストアドへの引数はこちら
$Cmd->Parameters->Append($Cmd->CreateParameter('@Id', 3,
1, , $Id));
$Cmd->Parameters('@Id')->{Value} = $Id;
:
my $rs = $Cmd->Execute();
if (Win32::OLE->LastError() != 0) {
adoError(Win32::OLE->LastError());
}
# 戻り値を取得します
$retval = $Cmd->Parameters('@retval')->{Value};
ただ、通常のADOのように、PerlからでもResultSetにテーブル名を付けて、明示的にアクセスできるのかは、まだ分りませんでした....。
また、ストアドへのパラメータを指定する部分は、Win32::OLE::Variantを使えばもっといいのかもしれませんが、うまく動かなかったので、以下のサイトを参考に直接値を指定しました。
http://msdn.microsoft.com/library/ja/default.asp?url=/library/ja/jpdnvid/htm/VintrDev/VISQL/visql.asp
また、コーディングの際は、ActivePerlを使い、eclipse + Perl EPICというPerl用のプラグインを使ってみました。これもなかなか便利でした。(Visual Perlっていうのもあるんですね)