はじめに
SQLステートメントのデバッグは、Visual Studio .NET環境でやるよりもSQL Server 2000のクエリアナライザでやった方がよいことがあります。私自身、バグがSQLストアドプロシージャにあることがわかっているときは、デバッグにSQLクエリアナライザを使うことがほとんどです。問題の所在がSQLだとわかっているのに、わざわざVisual Studioのようなオーバーヘッドの大きなアプリケーション開発環境を使っても、メリットはありません。問題を手早く突き止めるためには、呼び出し側アプリケーションを省いてマシンに余計な負荷をかけないようにする、というのも有益なテクニックです。ただしそのためには、使用している権限を偽造もしくはバイパスできること――つまり、セキュリティコンテキストを呼び出し側アプリケーションの元の状態(バグのある状態)と同じにできること――が前提となります。
サーバー/クライアント/ネットワークのセットアップ
使用するWindowsオペレーティングシステムやSQL Serverのバージョン、インストールしてあるサービスパックなどによっては、デバッグが一筋縄ではいかないこともあります。また、サーバー上でのデバッグなら1台のマシンをチェックするだけで済みますが、クライアント上でのデバッグだと、クライアントとサーバーの両方がインストールされ、正しく設定されていることを確認しなければなりません。さらに、単純なLAN以外のものを使用しているときは、PRC設定やファイアウォール設定も含め、ネットワーク問題のチェックも必要となります。こうした問題のトラブルシューティングには、Microsoftのサポート技術情報の文書番号280101『INF - Transact-SQL Debugger Limitations and Troubleshooting Tips for SQL Server 2000』が役に立ちます。
また、端末/リモートセッションを通じてのデバッグは当面できません。Microsoftによるバグの解決待ちです。これについては、文書番号280100『 BUG - Transact-SQL Debugger Is Not Available Through Terminal Server Session』を参照してください。
セキュリティ/権限
ローカルマシンでデバッグするためには、クエリアナライザへのログオンに使用するユーザアカウントが、masterデータベースにあるsp_sdidebug拡張ストアドプロシージャの実行権限を持っている必要があります。その他のSQL権限や認証がすべて引き続き有効であることを確認し、そうでないときは、バグを生じたユーザアカウント以外のユーザアカウントをデバッグに使用してください。
とにかくやってみる
上記の設定その他は、いかにも面倒そうに聞こえるかもしれません。Visual Studioからのデバッグでは、おそらく、そうした面倒な作業の全部またはほとんどが既に済んでいて、だからこそVisual Studioの内部からデバッグできるわけです。SQL Serverのデバッグ設定をまったくやっていない方は、まずストアドプロシージャのデバッグをやってみてください。それなら最初のトライから成功するかもしれません。
デバッガを探す
SQL 2000のクエリアナライザには、「オブジェクトブラウザ」と呼ばれるツリー表示機能があり、接続先のSQL Serverにあるすべてのものを表示できます。オブジェクトブラウザをまだ使ったことがない方は、[Tools]→[Object Browser]→[Show/Hide]メニューオプションを選択して表示してください。
オブジェクトブラウザのツリー表示が現れたら、データベースからストアドプロシージャを選び、そのストアドプロシージャ名を右クリックします。右クリックメニューが現れ、一番下に[Debug]オプションがあります。
このメニューオプションが灰色になっているときは、現ユーザに付与されている権限に不備があります。
例1:単純なストアドプロシージャのデバッグ
デバッガ機能を試すために、まず次のストアドプロシージャを作成します。
CREATE procedure DebugA ( @IntIn int, @IntOut int OUTPUT ) as Set @IntOut = @IntIn + 10 Return
これは、ある数を受け取り、それに10を加えて、新しい数を出力変数に返すという単純なストアドプロシージャです。このストアドプロシージャは、クエリアナライザを使って次のコードで実行できます。
Declare @NewInt int exec DebugA 5,@NewInt OUTPUT Select @NewInt
この例では、ストアドプロシージャに値5を与えているため、それに10を加えた値15が返されます。
[Debug Procedure]ウィンドウ
オブジェクトブラウザのツリー表示でストアドプロシージャ名を右クリックし、[Debug]を選択すると、[Debug Procedure]ウィンドウが現れます。ここで各パラメータに初期値を指定できます。
右下の[Value]ボックスに値を入れてください。[Set to null]チェックボックスをオンにすれば、値をNULLに設定できます。
出力専用として扱いたいパラメータの場合は、必ず[Set to null]チェックボックスをオンにしてください。
デバッグセッションの結果をデータベースに反映させたくないときは、[Auto roll back]チェックボックスをオンにしておきます。これで、ストアドプロシージャのデバッグ中にデータベースに加えた変更がすべてロールバックされます。
初期値の設定が終わったら、[Execute]をクリックして、デバッガに入ります。デバッガは、実行すべき最初のコード行で停止しています。
デバッガウィンドウ
デバッガのメインウィンドウの上に、いくつかのツールアイコンが並んでいます。これらのツールアイコンを使用して、どのコード行で実行を一時停止するかや、どのコード部分を細かく分析するかを制御できます。ツールアイコンの下にあるのがメインウィンドウで、ここにストアドプロシージャのコードが表示されています。このコードウィンドウの下に、さらに2つのウィンドウセクションがあります。
中段のセクションは3つに分かれていて、それぞれにローカル変数、グローバル変数、コールスタックが表示されています。ローカル変数は、デバッグ内部で変更できます。グローバル変数は、デバッグ内部で追加もしくは除去できます。この例では@@rowcount
と@@identity
という2つの変数を追加しています。どの@@変数がデバッグに役立つかを判断するのには、慣れが必要かもしれません。
下段のセクションは結果ウィンドウで、クエリアナライザがデバッグモードにないときのウィンドウと同じです。つまり、結果セットやPrint
ステートメントを使用しているときは、それがここに表示されます。
再び例1へ
ここまで説明どおりに操作してきた場合は、デバッグウィンドウのローカル変数セクションでは@InInt
の値が5、@OutInt
の値がNULLにになっているはずです。では、すべてのコード行をreturn
ステートメントまで1つ1つ実行していきましょう。@OutInt
の値が15になるはずです。この例のストアドプロシージャは入れ子ではないので、[Callstack]ウィンドウは無視してかまいません。
例2:入れ子になったストアドプロシージャ
今度は、[Callstack]ウィンドウの理解を深めるために、T-SQLコードは単純なままで、別のストアドプロシージャを追加することにします。次のコードを実行して、必要なストアドプロシージャを作成してください。
CREATE TABLE [Table1] ( [TestId] [int] IDENTITY (1, 1) NOT NULL , [A] [int] NOT NULL , [B] [int] NOT NULL , [Total] [int] NOT NULL ) ALTER procedure DebugA ( @IntIn int, @IntOut int OUTPUT ) as Declare @Id int exec DebugB @IntIn, 10, @IntOut OUTPUT, @Id OUTPUT return Create procedure DebugB ( @IntIn int, @IntConst int, @Total int OUTPUT, @Id int OUTPUT ) as Set @Total = @IntIn + @IntConst Insert into Table1 (A,B,Total) values (@IntIn,@IntConst,@Total) Select @Id = @@identity Return
これで「Table1」テーブルが作成され、DebugA
ストアドプロシージャがもう1つのストアドプロシージャDebugB
に入力値を引き渡すように変更されます。DebugB
ストアドプロシージャは新しいテーブルに1行を挿入し、その行の自動採番ID値(Id
)と、入力変数に10を加えた値を返します。この新しい合計値とID値がDebugA
ストアドプロシージャに戻され、そこから出力変数に返されます。
コードの実行後、オブジェクトブラウザでDebugA
ストアドプロシージャを右クリックして、[Debug]を選択してください。最初のパラメータを5、2番目のパラメータをNULLと指定し、[Execute]ボタンをクリックします。
ローカル変数ウィンドウとグローバル変数ウィンドウには、例1と同じ値が表示されるはずです。デバッグツールバーにある[Step Into]ツール()をクリックするか、[F11]キーを押してください。
コードウィンドウに、DebugB
ストアドプロシージャのコードが表示されます。ローカル変数、グローバル変数、コールスタックの3ウィンドウは、デバッガの新しい状態に合わせてすべて変化します。コール前のデバッガ状態をもう一度見たいときは、コールスタックからDebugA
を選択します。
DebugB
のグローバル変数ウィンドウで、@@rowcount
変数と@@identity
変数を追加します。追加直後の値はゼロのはずです。
では、最初のステートメントをステップ実行し、@Total
の値を設定してみましょう。@Total
は15、@@rowcount
は1となります。次のステートメントをステップ実行すると、これらの値がテーブルに挿入されます。@@transaction
が1に増え、@@rowcount
の値が1(1行挿入の意味)になり、@@identity
にはId
列の値が入ります。他に行を追加していなければ、Id
は1になっているはずです。
挿入・更新・削除のいずれかのステートメントにバグがある場合は、当該ステートメント実行後の@@rowcount
が0となるため、すぐにそれとわかります。この方法は、Where
句が目的の行だけを選択するよう適切に機能しているかどうかを確認するときにも役立ちます。
次のSelect
ステートメントをステップ実行すると、@Id
フィールドの値が設定されます。これですべての出力値が設定され、プロシージャは実質的に完了です。return
ステートメントをステップ実行すると、DebugA
に戻ります。
DebugA
では、出力パラメータ@OutInt
の値が15になり、return
ステートメントのところで実行が一時停止しているはずです。ローカル変数の値を変更できることの実証として、ローカル変数の@OutInt
をクリックし、値を20に変えてみてください。これでreturn
ステートメントをクリックすると、いま設定したとおり、@OutInt
パラメータの値が20になります。
デバッグモードでローカル変数の値を変更することの意義は何でしょうか? ストアドプロシージャに含まれる問題がいつも1つとは限りません。1つを修正するたびに初めからやり直していたのでは、時間が無駄になります。最初の問題の所在がわかったら、影響を受けたローカル変数を変更して、すぐ次のバグに移動した方が便利です。
カーソルなどの複雑なコード
コードが複雑になればなるほど、デバッグのありがたみがわかります。たとえば、カーソルでは実に多くのことが行われているため、デバッグは必須です。いくつものテーブルが関係する入れ子式のカーソルなどは、その最たるものでしょう。SQLクエリアナライザを使わずにデバッグをする場合は、カーソルや入れ子式プロシージャを完全に排除し、そういう複雑な作業を呼び出し側アプリケーションに任せ、そちらのデバッガ(たとえば、Visual Studio)を使いたくなる人もいるかもしれません。SQL Server 2000のデバッガは完璧には程遠い代物ですが、正しい方向への大きな1歩だとは言えるでしょう。このおかげで、T-SQLコードのロジックをあるべきところに置き、コードを強力に管理できるのです。
例3:カーソル
次のコードでカーソルを作成してみましょう。
create procedure DebugCursor as DECLARE cursorDebug Cursor FOR Select TestId, A, B, Total From Table1 Open cursorDebug Declare @cursorTestId int Declare @cursorA int Declare @cursorB int Declare @cursorTotal int Declare @randomDifference int Fetch NEXT FROM cursorDebug INTO @cursorTestId, @cursorA, @cursorB, @cursorTotal While (@@FETCH_STATUS =0) BEGIN -- generate random number seeded by row id Set @randomDifference = Convert(int,Rand(@cursorTestId)) Update Table1 Set @cursorTotal = @cursorA + (@cursorB * @randomDifference) Where TestId = @cursorTestId END FETCH NEXT From cursorDebug INTO @cursorTestId, @cursorA, @cursorB, @cursorTotal CLOSE cursorDebug DEALLOCATE cursorDebug GO
このカーソルは例2の変形です。データベースに挿入される合計値を、行ごとに、その行のIDに基づくランダムなパーセンテージで変更します。
オブジェクトブラウザでDebugCursor
ストアドプロシージャを右クリックし、[Debug]をクリックします。このストアドプロシージャには入力パラメータも出力パラメータもないので、設定は不要です。[Execute]ボタンをクリックします。
これでSQLクエリアナライザのデバッガが呼び出され、実行可能な最初のステートメントが示されます。
グローバル変数に@@fetch_status
を追加します。@@fetch_status
は、カーソル変数に行が返されたかどうかを示す変数で、Select
ステートメントが意図どおりの働きをしてくれているかどうかの目安になります。
カーソルループ内のステートメントを1つずつステップ実行していき、Set
ステートメントまで進みます。@randomDifference
変数がNULLであることに注意してください。Set
ステートメントを実行すると、これがあるパーセンテージ値に変わります(float
型)。
次はUpdate
ステートメントで、B
列の値を更新します。新しい値は、B
の旧値に新しいランダム値をかけた値になります。こうして、結果的に、B
列の値はどれも他行とは異なる値となり、それにともなって合計も変化します。本稿の説明どおりに操作してきた場合、テーブルには1行しかないので、更新後の@@rowcount
は1になるはずです。
このカーソルがさらに別のストアドプロシージャを呼び出したとしても、そのストアドプロシージャの中までコードと値を追いかけていくことは難しくありません。
まとめ
SQL Server 2000によるデバッグは、大きな前進です。たとえストアドプロシージャのコードが複雑で、入れ子になっていても、ステートメントごとにデータベースの状態がどう変化するかを確認しながら、テストを進めることができます。この方法ならば、SQL Serverの持つトランザクション能力と処理速度をフルに生かすことができ、同様の機能を呼び出し側のアプリケーションで作り直す必要はなくなります。