MSDE FunClub | 最終更新日 : 2000/08/18 | |
Microsoft Data Engine FunClub |
|
|
SQLServer7.0/MSDE 完全トレーニングテキスト(上巻) | ||
【第7章423p 〜 428p掲載】 |
-- [SQL07_06.SQL] -- SQLServer7.0 Transact-SQL言語 -- 日本技術ソフト開発 堀川 明 -- http://www.horikawa.ne.jp/msde/ -- -- このSQLプログラムは、キーセットドリブンカーソル -- を使った現在行の修正です -- -- カレントデータベースを MySampleTest にする USE MySampleTest GO -- (件処理されました)のメッセージを抑止する SET NOCOUNT ON GO -- ********************************************************* -- 【事前確認チェック】 -- [社員]表の[社員コード]に主キーが設定されていますか? -- Accessのテーブルを単純に転送コピー(Export)しただけでは -- 主キーは設定されません -- 主キーが設定されていないテーブルに対して、キーセットドリブン -- カーソルは使うことができません -- [注意]ストアドプロシージャ sp_pkeys を使えばもっと簡単に -- 調査することができます(SQL08_01を参照) -- ********************************************************* DECLARE @pk_name varchar(80) -- 主キーに付けられた制約名 DECLARE @msg varchar(256) DECLARE @cnt int DECLARE @cl_name varchar(80) -- 社員表の主キーに付けられた制約名を、システムビュー表から取得する SELECT @pk_name = CONSTRAINT_NAME FROM MySampleTest.INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE (TABLE_NAME = '社員' AND CONSTRAINT_TYPE = 'PRIMARY KEY') IF( @pk_name is NULL ) BEGIN PRINT '' PRINT '社員表に主キーが設定されていません。' PRINT 'キーセットドリブンカーソルを使う時は、主キーが必要です。' PRINT 'ACCESSのテーブルを転送しただけでは、主キーは設定されません。' PRINT '社員コードに主キーを設定してください' PRINT 'SQL07_06B.SQL プログラムを先に実行しましょう' PRINT '' RAISERROR('主キーがありません。中止します',16,127) RETURN END -- 主キーがあります。その主キーは、社員コードに付けられたものでしょうか? -- 連結主キーの場合は、エラーとする SELECT @cnt = count(*) FROM MySampleTest.INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE (TABLE_NAME = '社員' AND CONSTRAINT_NAME = @pk_name ) IF( @cnt <> 1 ) BEGIN RAISERROR('列数(%d)個 に振られた連結主キーです。単一列に設定してください',16,127,@cnt) RETURN END -- 主キーが付けられた列の名前を取得する SELECT @cl_name = COLUMN_NAME FROM MySampleTest.INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE (TABLE_NAME = '社員' AND CONSTRAINT_NAME = @pk_name ) IF( @cl_name <> '社員コード' ) BEGIN RAISERROR('主キーが列名(%s)に付けられていますが、''社員コード'' 列に変更してください' ,16,127,@cl_name) RETURN END PRINT '' PRINT '社員コード列に主キーが設定されています(事前確認OK!)' PRINT 'キーセットドリブンカーソルを使った修正を行ないます' GO -- ********************************************************* -- キーセットドリブンカーソルを使った修正を行ないます -- ********************************************************* -- 作業用変数の定義 DECLARE @total int , @cnt int , @code int DECLARE @社員コード int , @社員コード2 int DECLARE @氏名 varchar(40) , @氏名2 varchar(40) DECLARE @FETCH int DECLARE @OPTIMISTIC int -- 次の値を =1 にすると、エラーが起こります SET @FETCH = 0 -- FETCHエラーの発生 SET @OPTIMISTIC = 1 -- OPTIMISTICロック違反の発生 PRINT '' PRINT '【@FETCH=' + CAST(@FETCH AS varchar) + ' @OPTIMISTIC=' + CAST(@OPTIMISTIC AS varchar) + ' 】で実行中' --//////////////////////////////////////////////////////////////// -- 【キーセットドリブンカーソルを定義】 -- キーセット内容は固定されますので、レコード総数の増減はありません -- OPTIMISTICロックのため、更新エラーが起こる可能性があります --//////////////////////////////////////////////////////////////// DECLARE My_cur CURSOR LOCAL SCROLL KEYSET OPTIMISTIC TYPE_WARNING FOR SELECT 社員コード,氏名 FROM MySampleTest..社員 WHERE 社員コード BETWEEN 100 AND 299 ORDER BY 社員コード FOR UPDATE of 氏名 -- ローカルカーソルを開く OPEN My_cur -- 結果セットの行数を取得する SET @total = @@CURSOR_ROWS PRINT '' RAISERROR('結果セット内のレコード総数 = %d',0,1,@total) WITH NOWAIT IF( @total = 0 ) BEGIN PRINT 'レコードの取得に失敗しました' PRINT 'もう1度、[社員]テーブルをAccessから転送してください' RAISERROR('中止します',0,127) RETURN END -- ************************************************ -- 静的キーセットの管理内容と矛盾を起こさせます -- レコードを削除したり主キーの値を変更するとカーソルの -- 管理内容と現実が異なり、FETCHエラーが起こります -- ************************************************ --** 待機している間に手動で変更してもよい --** RAISERROR('30秒間待機する間に、他接続からレコードを削除や主キーの内容変更しましょう' --** , 0 , 1 ) WITH NOWAIT --** WAITFOR DELAY '00:00:30' --** RAISERROR('待機終了' , 0 , 1 ) WITH NOWAIT -- 自分から故意に変更した方が早いです IF( @FETCH = 1 ) BEGIN -- 一番小さい社員コードの主キーの値を変更する UPDATE 社員 SET 社員コード = 社員コード * 10 WHERE 社員コード = (SELECT MIN(社員コード) FROM 社員 ) END -- 正の値のときは、結果セットがあります IF( @total > 0 ) BEGIN SET @cnt = 0 -- 結果セットを1行づつ取得する WHILE( @cnt <> @total ) BEGIN -- 1行進めて、その内容を取得 FETCH NEXT FROM My_cur INTO @社員コード,@氏名 --///////////////////////////////////////////////////////// -- FETCHが正しくできましたか? -- キーセットドリブンカーソルでは以下のFETCHエラーが起こる -- 外部の接続から -- (1)レコードが削除された場合 -- (2)主キーの値が変更された場合 -- この事象が起こると、@@FETCH_STATUS は -2 を返します --//////////////////////////////////////////////////////// IF( @@FETCH_STATUS <> 0 ) BEGIN PRINT '' PRINT '******************************' PRINT '**** FETCH ERROR 発生!! ****' PRINT '******************************' PRINT '' PRINT '外部変数@@FETCH_STATUS = ' + CAST(@@FETCH_STATUS as varchar) IF( @@FETCH_STATUS = -2 ) BEGIN PRINT '' PRINT 'FETCH命令で取得しようとしたレコードが、' PRINT '実際のテーブルにありません' PRINT '外部の者によってそのレコードが変更・削除されました' END --終了します CLOSE My_cur DEALLOCATE My_cur RAISERROR('中止します',0,127) RETURN END SET @cnt = @cnt + 1 RAISERROR('%2d:%4d %s',0,1,@cnt,@社員コード,@氏名) WITH NOWAIT -- ****************************************** -- OPTIMISTICロック違反を起こします -- 部外者によって、現在行の内容を変更します -- もちろん、自分から故意に変更することもできます -- ****************************************** IF( @OPTIMISTIC = 1 ) BEGIN --**RAISERROR('15秒間待機する間に、レコードの氏名の値を変更しましょう' --** , 0 , 1 ) WITH NOWAIT --**WAITFOR DELAY '00:00:15' --**RAISERROR('待機終了' , 0 , 1 ) WITH NOWAIT -- 自分から故意に変更した方が早いです UPDATE 社員 SET 氏名 = '直接更新しました' WHERE 社員コード = @社員コード END --************************************** -- カーソル現在行の内容を修正する --************************************** UPDATE 社員 SET 氏名 = CAST(@社員コード AS varchar ) WHERE CURRENT OF My_cur --///////////////////////////////////////////////////// -- 更新が失敗しましたか? -- その時は、 -- サーバー : メッセージ 16947、レベル 10、状態 1 -- 行は更新または削除されませんでした。 -- エラーが起こります --///////////////////////////////////////////////////// SET @code = @@ERROR -- エラー番号の取得 IF( @code <> 0 ) BEGIN PRINT '' PRINT '****************************************' PRINT '**** Optimistic更新エラーの発生!! ****' PRINT '****************************************' PRINT '' --更新エラーが発生しました RAISERROR( 'エラーコードは 16947 ですか? ErrCode=%d' ,0,127,@code) --終了します CLOSE My_cur DEALLOCATE My_cur RETURN END -- 現在行の内容を取得する FETCH RELATIVE 0 FROM My_cur INTO @社員コード2 , @氏名2 -- 確認します IF( @氏名2 <> CAST(@社員コード AS varchar ) ) BEGIN RAISERROR( 'あれ? 更新が反映されていないよ?' , 16 , 127 ) RETURN END -- 元の氏名に戻す UPDATE 社員 SET 氏名 = @氏名 WHERE CURRENT OF My_cur -- 上記の更新命令ではエラーは起きないでしょう -- エラーチェックは省略します END END -- カーソルを閉じる CLOSE My_cur -- 参照関係を解除する DEALLOCATE My_cur GO