MSDE FunClub 現在までのアクセスカウント数 最終更新日 : 2000/08/17
Microsoft Data Engine FunClub
Since 2000.08.17
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




技術評論社の書籍ガイドへ
上巻:ISBN4-7741-0965-7

ウィンドウを閉じる


(株)日本技術ソフト開発 責任編集:堀川 明
MSDE FunClubに関するご意見・ご要望等ございましたら、 msdefun@horikawa.ne.jp までご連絡下さい。
HOME: http://www.horikawa.ne.jp/msde/


MSDE FunClubの運営は、マイクロソフト社とは一切の関係はありません