MSDE FunClub | 最終更新日 : 2000/08/17 | |
Microsoft Data Engine FunClub |
|
|
SQLServer7.0/MSDE 完全トレーニングテキスト(上巻) | ||
【第6章366p 〜 373p掲載】 |
-- [SQL06_05.SQL] -- SQLServer7.0 Transact-SQL言語 -- 日本技術ソフト開発 堀川 明 -- http://www.horikawa.ne.jp/msde/ -- -- このSQLプログラムは、 -- 外部参照のリレーションシップが定義されたテーブル間の -- 連鎖更新・連鎖削除を実行するものです -- -- カレントデータベースを MySampleTest にする USE MySampleTest GO -- (件処理されました)のメッセージを抑止する SET NOCOUNT ON GO -- ******************************************************* -- [見本テーブルの作成] -- テーブルを削除するときは、多側のテーブルから削除します -- ******************************************************* -- 多側の既存テーブルが存在したら削除します IF( (object_id('T_多側') IS NOT NULL ) AND OBJECTPROPERTY( object_id('T_多側'),'ISTABLE') = 1 ) BEGIN PRINT 'T_多側 テーブルが存在したので削除しました' DROP TABLE T_多側 END GO -- 主キー側(1側)の既存テーブルが存在したら削除します IF( (object_id('T_主側') IS NOT NULL ) AND OBJECTPROPERTY( object_id('T_主側'),'ISTABLE') = 1 ) BEGIN PRINT 'T_主側 テーブルが存在したので削除しました' DROP TABLE T_主側 END GO -- ************************************** -- 主キー側(1側)テーブルを作成する -- ************************************** CREATE TABLE T_主側 ( ID char(4) CONSTRAINT PK_T_主側 PRIMARY KEY , DATA varchar(20) ) -- ************************************** -- 参照側(多側)テーブルを作成する -- ************************************** CREATE TABLE T_多側 ( ID2 char(4) CONSTRAINT PK_T_多側 PRIMARY KEY , ID char(4) , DATA varchar(20) -- 参照関係を定義する CONSTRAINT REF1 FOREIGN KEY( ID ) REFERENCES T_主側( ID ) ) -- 外部キーにインデックスを設定する CREATE INDEX IDX_T_多側_ID ON T_多側 ( ID ) -- REF1リレーションを無効化します -- INSERT と UPDATE に対するリレーションを無効にする -- この命令は、参照整合性を SQLServer側ではチェックしないことを要求します -- そのかわりトリガで管理します ALTER TABLE T_多側 NOCHECK CONSTRAINT REF1 GO -- ************************************************* -- 連鎖更新トリガの作成 -- ************************************************* CREATE TRIGGER TR_連鎖更新 -- トリガの名前 ON T_主側 -- トリガを組み込むテーブル ----------- FOR UPDATE ----------- AS -- ID列の更新がありましたか? -- 更新が発生したら、多側テーブルも修正します IF UPDATE(ID) BEGIN -- 更新対象のレコード数は、1レコード分ですか? -- 1レコードの時は簡単です IF( (SELECT COUNT(inserted.ID) FROM inserted) = 1 ) BEGIN -- 多側テーブルの連鎖更新 --PRINT '1レコード分の連鎖更新処理' UPDATE T_多側 SET T_多側.ID = inserted.ID -- 更新後の値を設定する -- 交差結合(直積 CROSS JOIN)形式ですが inserted と deleted は -- 1レコ−ドしかないので、直積表のレコード数は T_多側 と一致する FROM T_多側 , inserted , deleted WHERE T_多側.ID = deleted.ID -- 更新前の値を検索する END ELSE BEGIN -- 【複数レコードの一括連鎖更新】 --PRINT '複数レコード分の連鎖更新処理' -- inserted と deleted の各行がお互い対応します -- 表を結合させて、SQL文で更新させる -- このような命令を作ることはできません -- FROM inserted , deleted -- のFROM句は、複数レコード間の直積になります -- insertedやdeletedをシステムが作成するときに、シーケンシャルな -- 行番号を振ってくれる機能があれば、その番号で等結合できるのに。 -- しかたないので、最後の手段のカーソルを使います DECLARE @id_ins char(4) , @id_del char(4) --inserted用 DECLARE INSERT_CUR INSENSITIVE CURSOR FOR SELECT ID FROM inserted FOR READ ONLY --deleted用 DECLARE DELETE_CUR INSENSITIVE CURSOR FOR SELECT ID FROM deleted FOR READ ONLY --カーソルを開く OPEN INSERT_CUR OPEN DELETE_CUR --先頭レコードの読み出し FETCH NEXT FROM INSERT_CUR INTO @id_ins FETCH NEXT FROM DELETE_CUR INTO @id_del --レコードの存在する間、繰り返す --(2つの表はまったく同じレコード数) WHILE( @@FETCH_STATUS = 0 ) BEGIN -- id_del の値が id_ins に変更となりました -- 主キー1個分の連鎖更新処理 UPDATE T_多側 SET T_多側.ID = @id_ins -- 更新後の値を設定する WHERE T_多側.ID = @id_del -- 更新前の値を検索する -- 次のレコードへ FETCH NEXT FROM INSERT_CUR INTO @id_ins FETCH NEXT FROM DELETE_CUR INTO @id_del END --カーソルを閉じる CLOSE INSERT_CUR CLOSE DELETE_CUR --カーソルを破棄する DEALLOCATE INSERT_CUR DEALLOCATE DELETE_CUR END END RETURN GO -- ************************************************* -- 連鎖削除トリガの作成 -- 複数レコード処理に対応(deleted) -- ************************************************* CREATE TRIGGER TR_連鎖削除 -- トリガの名前 ON T_主側 -- トリガを組み込むテーブル ----------- FOR DELETE ----------- AS DELETE T_多側 FROM T_多側 , deleted WHERE T_多側.ID = deleted.Id -- 削除IDと一致する部分の削除 GO -- ************************************************* -- 参照整合性の維持トリガの作成 -- 複数レコード操作には対応していません -- insertedテーブルは1レコード限定です -- ************************************************* CREATE TRIGGER TR_参照維持 -- トリガの名前 ON T_多側 -- トリガを組み込むテーブル ----------- FOR INSERT , UPDATE ----------- AS DECLARE @ID char(4) -- ID列の更新ですか? IF UPDATE(ID) BEGIN -- TR_連鎖更新トリガ の中のUPDATE文から呼び出される -- 主キー側の変更が発生すると必ず呼ばれる -- [T_多側]テーブルの外部キーに登録されていない主キーの値が変更 -- された場合は、insertedテーブルにレコードはない IF (SELECT COUNT(ID) FROM inserted) = 0 RETURN -- [主キー側ID] [iserted.ID] -- 001 001 -- NULL 002 <= 違反 -- 003 003 -- NULL 004 <= 違反 -- -- このように、insertedテーブルのID列を全部表示して、それに対応する -- 主キー側のID列を付き合わせて、対応しないNULL値があったら、 -- 参照整合性違反になる IF EXISTS( SELECT T_主側.ID , inserted.ID FROM T_主側 RIGHT OUTER JOIN inserted ON T_主側.ID = inserted.ID WHERE(T_主側.ID IS NULL ) ) BEGIN DECLARE @msg varchar(256) , @FK char(4) SELECT @msg = '(T_多側)入力で参照整合性違反:' -- 参照整合性違反を検出しました -- 親切なメッセージを作成します DECLARE TRI_CUR INSENSITIVE CURSOR FOR SELECT inserted.ID FROM T_主側 RIGHT OUTER JOIN inserted ON T_主側.ID = inserted.ID WHERE(T_主側.ID IS NULL ) FOR READ ONLY --カーソルを作成する OPEN TRI_CUR --先頭レコードを取得する FETCH NEXT FROM TRI_CUR INTO @FK --レコードの存在する間、繰り返す WHILE( @@FETCH_STATUS = 0 ) BEGIN --出力用文字列の作成 SELECT @msg = @msg + '[' + @FK + ']' -- 次のレコードを取得 FETCH NEXT FROM TRI_CUR INTO @FK END SELECT @msg = @msg + ':この値が主キー側で未登録です' --カーソルを閉じる CLOSE TRI_CUR --カーソルを破棄する DEALLOCATE TRI_CUR --入力をロールバックする ROLLBACK TRANSACTION --エラーメッセージの出力 RAISERROR('%s',16,1,@msg) RETURN END END GO --/////////////////////////////////// -- テスト --/////////////////////////////////// --主キー側の値を登録する INSERT INTO T_主側 VALUES( '1' , '001' ) INSERT INTO T_主側 VALUES( '2' , '002' ) INSERT INTO T_主側 VALUES( '3' , '003' ) INSERT INTO T_主側 VALUES( '4' , '004' ) INSERT INTO T_主側 VALUES( '5' , '005' ) GO --多側テーブルにレコードを一括登録する CREATE TABLE #TMP_T_多側 ( ID char(4) , ID2 char(4) , DATA varchar(20) ) INSERT INTO #TMP_T_多側 VALUES( '1' , '1' , '001' ) INSERT INTO #TMP_T_多側 VALUES( '2' , '1' , '001' ) INSERT INTO #TMP_T_多側 VALUES( '3' , '2' , '002' ) INSERT INTO #TMP_T_多側 VALUES( '4' , '2' , '002' ) INSERT INTO #TMP_T_多側 VALUES( '5' , '3' , '003' ) GO INSERT INTO T_多側 SELECT * FROM #TMP_T_多側 GO IF( @@error = 0 ) BEGIN PRINT '(1)挿入処理は正常終了です' END SELECT * FROM T_多側 GO DELETE FROM T_多側 DELETE FROM #TMP_T_多側 GO -- **************************************** -- レコード挿入時に参照整合性違反の発生 -- **************************************** INSERT INTO #TMP_T_多側 VALUES( '1' , '1' , '001' ) INSERT INTO #TMP_T_多側 VALUES( '2' , '8' , '008' ) -- 違反!! INSERT INTO #TMP_T_多側 VALUES( '3' , '2' , '002' ) INSERT INTO #TMP_T_多側 VALUES( '4' , '9' , '009' ) -- 違反!! INSERT INTO #TMP_T_多側 VALUES( '5' , '10' , '003' ) -- 違反!! INSERT INTO T_多側 SELECT * FROM #TMP_T_多側 GO IF( @@error = 50000 ) BEGIN PRINT '(2)挿入処理は失敗しました' END GO -- **************************************** -- 連鎖更新のテスト -- **************************************** DELETE FROM #TMP_T_多側 INSERT INTO #TMP_T_多側 VALUES( '1' , '1' , '001' ) INSERT INTO #TMP_T_多側 VALUES( '2' , '1' , '001' ) INSERT INTO #TMP_T_多側 VALUES( '3' , '2' , '002' ) INSERT INTO #TMP_T_多側 VALUES( '4' , '2' , '002' ) INSERT INTO #TMP_T_多側 VALUES( '5' , '3' , '003' ) INSERT INTO #TMP_T_多側 VALUES( '6' , '4' , '004' ) INSERT INTO #TMP_T_多側 VALUES( '7' , '5' , '005' ) INSERT INTO T_多側 SELECT * FROM #TMP_T_多側 PRINT '連鎖更新前' SELECT * FROM T_多側 --主キーの変更 UPDATE T_主側 SET ID = 'A'+ID WHERE( ID IN ( '1' , '3' , '5' ) ) GO PRINT '連鎖更新後(1,3,5が、A1,A3,A5に変更)' SELECT * FROM T_多側 GO -- **************************************** -- 連鎖削除のテスト -- **************************************** --主キーの削除 DELETE T_主側 WHERE( ID IN ( 'A1' , 'A3' , 'A5' ) ) PRINT '連鎖削除後(A1,A3,A5を削除)' SELECT * FROM T_多側 GO -- ローカル一時テーブルの削除 DROP TABLE #TMP_T_多側 GO