ストアドプロシージャを使う

さて、今度はストアドプロシージャを使ってみよう。

●とりあえず一発。
パラメータなし、変数なし、返り値なし、一コマンド実行するだけ、という 超シンプル版で動作確認してみよう。
1> CREATE PROCEDURE PriceDown AS
2>     Update BookTable SET Price = Price/2
3> GO
ストアドプロシージャができた。はたして動くのか?
1> EXEC PriceDown
2> GO
(4 件処理されました)
1> SELECT * from BookTable
2> GO
 BookID      Title                AuthID      Price
 ----------- -------------------- ----------- -----------
           1 本その1                     102         250
           2 本その2                     101          15
           3 本その3                     103         500
           4 Book No.Four                 101        5000
動いた。各本の価格も半額になっている。 SELECTの出力は例によってずれるので整形してある。

●引数を持たせてみよう
引数をもつストアドプロシージャを作ってみる。
各本の金額を第一引数でかけて新しい金額にする。
%指定にして100にすると金額が変わらないことにしよう。
デフォルトは100だ。
1> CREATE PROCEDURE PriceChange (@NewRate INTEGER = 100 ) AS
2>     Update BookTable SET Price = Price*@NewRate / 100
3> GO
1> EXEC PriceChange 200
2> GO
(4 件処理されました)
1> SELECT * from BookTable
2> GO
 BookID      Title                AuthID      Price
 ----------- -------------------- ----------- -----------
           1 本その1                     102         500
           2 本その2                     101          30
           3 本その3                     103        1000
           4 Book No.Four                 101       10000
(4 件処理されました)
倍になって、もとの価格に戻った。
値を指定しないと価格は変わらないはず。
1> EXEC PriceChange
2> GO
(4 件処理されました)
1> SELECT * FROM BookTable
2> GO
 BookID      Title                AuthID      Price
 ----------- -------------------- ----------- -----------
           1 本その1                     102         500
           2 本その2                     101          30
           3 本その3                     103        1000
           4 Book No.Four                 101       10000
(4 件処理されました)
おーけー。

●値を返す。
値を返すストアドプロシージャを書いてみよう。
T-SQLでは値を返す方法は2通りある。
単一のINTEGERならRETURN文でプロシージャの返り値として戻せる。
また、複数の値やINTEGER以外の値を返すために引数にOUTPUT指定もできる。

まずはRETURN文を使ってみよう。
指定したBOOKIDの著者の年齢を返す。
いつまでも1行プロシージャじゃつまらないので、 BookIDがなかったら-1歳を返すくらいのことはしてみよう。
1> CREATE PROCEDURE GetAuthorAge (@BookID INTEGER ) AS
2>    DECLARE @AuthorAge INTEGER
3>    SET @AuthorAge = -1
4>    IF ( SELECT COUNT(*) FROM BookTable WHERE BookID=@BookID ) > 0
5>        SELECT @AuthorAge = AuthorTable.Age From AuthorTable, BookTable
6>               WHERE BookTable.BookID = @BookID
7>    RETURN @AuthorAge
8> GO
実行するだ。
1> DECLARE @Result INTEGER
2> EXECUTE @Result = GetAuthorAge 3
3> PRINT @Result
4> GO
10
1>
BookID=3の本の著者は、三郎さんで10歳なのであっている。
次に存在しないBookIDを与えてみよう。
1> DECLARE @Result INTEGER
2> EXECUTE @Result = GetAuthorAge 10
3> PRINT @Result
4> GO
-1
おーけー。

では次にOUTPUTで値を返すのを試してみよう。
全著者の年齢の平均値を返す。
1> CREATE PROCEDURE GetAuthorAve (@AuthorAve FLOAT OUTPUT) AS
2>    DECLARE @AuthorCount INTEGER
3>    SET @AuthorAve = 0
4>    SELECT @AuthorCount = COUNT(*) FROM AuthorTable
5>    IF @AuthorCount > 0
6>        SELECT @AuthorAve=SUM(Age)*1.0/@AuthorCount FROM AuthorTable
7> GO
実行してみる。
1> DECLARE @Result FLOAT
2> EXECUTE GetAuthorAve @Result OUTPUT
3> PRINT @Result
4> GO
53.3333
1>
3人の年齢を足すと160歳だから、3人で割ると53と1/3才。
あってる。よし。

●カーソルを使ってみる。
プロシージャの中でカーソル変数を使ってループさせてみよう。
行ごとに処理するときの基本形。
ここでは、さっき作った PriceChange プロシージャと同じものを作る。
もちろん、一行でかけるわけだが、実際には、新しい値の計算に 他のプロシージャを使うなど一行ごとに処理しなければならない場合もある。

プロシージャはこんな感じだ。
CREATE PROCEDURE PriceChange2 (@NewRate INTEGER = 100 ) AS

	DECLARE @cur CURSOR

	SET @cur = CURSOR FOR
	    	SELECT Price
			FROM BookTable
			FOR UPDATE OF Price

	-- データフェッチ用変数の宣言
	DECLARE @old_price int

	OPEN @cur
  
	-- FETCHでデータが終わるまで次々とレコードを取り出す。

	FETCH NEXT FROM @cur Into @old_price
	WHILE @@FETCH_STATUS = 0
	BEGIN
		-- 現在のカーソルのデータを更新する
		UPDATE BookTable SET Price = @old_price*@NewRate/100
			WHERE CURRENT OF @cur

		FETCH NEXT FROM @cur Into @old_price
	END
		
	CLOSE @cur
	DEALLOCATE @cur	
GO
結果は、PriceChangeの時の同じなので省略。
カーソル変数を宣言して、SETで割り当ててからOPENする。
FETCHでレコードを取り出し、@@FETCH_STATUSを見ながらループさせるのが基本のようだ。
最後にCLOSEして、DEALLOCATEする。

ここでちょっと疑問。
FETCHで値は取り出さずにUPDATEで値をセットするだけのフィールドは、SELECT文にリストする必要はあるのだろうか。
というわけで、BookIDをそのまま値段にしてしまうプロシージャを作ってみよう。
CREATE PROCEDURE ChangePriceToBookID  AS

	DECLARE @cur CURSOR

	SET @cur = CURSOR FOR
	    	SELECT BookID
			FROM BookTable
			FOR UPDATE OF Price

	-- データフェッチ用変数の宣言
	DECLARE @tmp_BookID int

	OPEN @cur
  
	-- FETCHでデータが終わるまで次々とレコードを取り出す。

	FETCH NEXT FROM @cur Into @tmp_BookID
	WHILE @@FETCH_STATUS = 0
	BEGIN
		-- 現在のカーソルのデータを更新する
		UPDATE BookTable SET Price = @tmp_BookID
			WHERE CURRENT OF @cur

		FETCH NEXT FROM @cur INTO @tmp_BookID
	END
		
	CLOSE @cur
	DEALLOCATE @cur	
GO
こいつを実行してどうなるか見てみよう。
1> EXEC ChangePriceToBookID
2> GO
(1 件処理されました)
(1 件処理されました)
(1 件処理されました)
(1 件処理されました)
1> SELECT * FROM BookTable
2> GO
 BookID      Title                AuthID      Price
 ----------- -------------------- ----------- -----------
           1 本その1                     102           1
           2 本その2                     101           2
           3 本その3                     103           3
           4 Book No.Four                 101           4
(4 件処理されました)
大丈夫みたい。

Priceの値が腐ってしまったので元のデータをセットしておこう。
1> UPDATE BookTable SET Price=500   WHERE BookID=1
2> UPDATE BookTable SET Price=30    WHERE BookID=2
3> UPDATE BookTable SET Price=1000  WHERE BookID=3
4> UPDATE BookTable SET Price=10000 WHERE BookID=4
5> GO
(1 件処理されました)
(1 件処理されました)
(1 件処理されました)
(1 件処理されました)
というわけでこの項おわり。


MSDE 実験ページへ戻る。