web-dev-qa-db-ja.com

マージステートメントで外部キー制約を処理する方法

私は現在、次のテーブルでtest_dbtarget)およびtablebackupssource)と呼ばれる別のデータベース上の同じテーブル.

    IF OBJECT_ID('[dbo].[tblBCatalogueType]') IS NOT NULL 
    DROP TABLE [dbo].[tblBCatalogueType] 
    GO
    CREATE TABLE [dbo].[tblBCatalogueType] ( 
    [sintCatalogueTypeID]  SMALLINT         IDENTITY(1,1)   NOT NULL,
    [blnIsCurrent]         BIT                              NOT NULL,
    [tsRowVersion]         TIMESTAMP                        NOT NULL,
    CONSTRAINT   [PK_tblProdCatalogueType]  
PRIMARY KEY CLUSTERED    ([sintCatalogueTypeID] asc) 
WITH FILLFACTOR = 97)

私のtlbBCatalogueTypeテーブルを参照しているテーブルを見ると、次のリストが表示されます。

外部キー制約を見つけるスクリプト:

----------------------------------------------------------------------------
declare @referenced_table sysname 
declare @ref_Obj int 

declare @table sysname 
declare @Obj int 

select @table            = 'dbo.tblBCatalogueType'   --'tblBCataloguePriceSetItem'   
select @referenced_table = 'dbo.tblBCatalogueType'   --'dbo.tblBCataloguePriceSet'   

select @obj     = OBJECT_ID(@table)
select @ref_obj = OBJECT_ID(@referenced_table)

SELECT  obj.name AS FK_NAME,
    sch.name AS [schema_name],
    tab1.name AS [table],
    col1.name AS [column],
    sch.name AS [referenced_schema],
    tab2.name AS [referenced_table],
    col2.name AS [referenced_column]
FROM sys.foreign_key_columns fkc
INNER JOIN sys.objects obj
    ON obj.object_id = fkc.constraint_object_id
INNER JOIN sys.tables tab1
    ON tab1.object_id = fkc.parent_object_id
INNER JOIN sys.schemas sch
    ON tab1.schema_id = sch.schema_id
INNER JOIN sys.columns col1
    ON col1.column_id = parent_column_id AND col1.object_id = tab1.object_id
INNER JOIN sys.tables tab2
    ON tab2.object_id = fkc.referenced_object_id
INNER JOIN sys.schemas sch2
    ON tab2.schema_id = sch2.schema_id
INNER JOIN sys.columns col2
    ON col2.column_id = referenced_column_id AND col2.object_id = tab2.object_id
where 1=1
  AND (     ( @OBJ IS NULL OR @obj = tab1.object_id) 
         OR ( @ref_obj IS NULL OR @ref_obj = tab2.object_id )) 

enter image description here

これは私のマージステートメントです:

    SET NOCOUNT ON;

    DECLARE @TranCountAtStart INT;
    SET @TranCountAtStart = @@TRANCOUNT;

    --  SELECT @@TRANCOUNT,XACT_STATE();

    IF @TranCountAtStart = 0
        BEGIN TRANSACTION
    ELSE
        SAVE TRANSACTION USP_Procedure_Name;

     BEGIN TRY
-------------------------------------------------------------------------------------------         

-- the dbo.tblBCataloguePriceSet must be run before this merge.

-- Message: The MERGE statement conflicted with the REFERENCE constraint "fk_CataloguePriceSet_CatalogueType". 
-- The conflict occurred in database "Bocss2", table "dbo.tblBCataloguePriceSet", column 'sintCatalogueTypeID'.  





                IF OBJECT_ID( 'tablebackups.dbo.tblBCatalogueType_log') IS NOT NULL DROP TABLE tablebackups.dbo.tblBCatalogueType_log;

                CREATE TABLE tablebackups.dbo.tblBCatalogueType_log(
                  ChangeType         NVARCHAR(10)
                 ,sintCatalogueTypeID        SMALLINT NOT NULL
                 ,DateTimeChanged    DateTime NOT NULL);



                BEGIN TRANSACTION T1

                SET IDENTITY_INSERT dbo.tblBCatalogueType  ON;

                SELECT @@TRANCOUNT
                SELECT XACT_STATE()

                MERGE dbo.tblBCatalogueType  AS TARGET
                USING tablebackups.dbo.tblBCatalogueType AS SOURCE
                   ON TARGET.sintCatalogueTypeID = SOURCE.sintCatalogueTypeID

                WHEN MATCHED

                            THEN UPDATE SET 
                                  [blnIsCurrent] = SOURCE.blnIsCurrent

                WHEN NOT MATCHED BY TARGET


                             THEN INSERT(   [sintCatalogueTypeID]
                                           ,[blnIsCurrent]
                                )
                                VALUES( 
                                            SOURCE.[sintCatalogueTypeID]
                                           ,SOURCE.[blnIsCurrent]
                                        )

                WHEN NOT MATCHED BY SOURCE

                            THEN DELETE 
                --$action specifies a column of type nvarchar(10) 
                --in the OUTPUT clause that returns one of three 
                --values for each row: 'INSERT', 'UPDATE', or 'DELETE', 
                --according to the action that was performed on that row
                -------------------------------------
                OUTPUT
                   $ACTION ChangeType,
                   coalesce (inserted.sintCatalogueTypeID, deleted.sintCatalogueTypeID) sintCatalogueTypeID,
                   Getdate () DateTimeChanged
                    INTO tablebackups.dbo.tblBCatalogueType_log
                -------------------------------------
                ;


                SELECT @@ROWCOUNT;

                SET IDENTITY_INSERT dbo.tblBCatalogueType  OFF;

                COMMIT TRANSACTION T1 

-------------------------------------------------------------------------------------------


    END TRY

    BEGIN CATCH

    SET IDENTITY_INSERT dbo.tblBCatalogueType  OFF;

    DECLARE @ERRORMESSAGE    NVARCHAR(512),
            @ERRORSEVERITY   INT,
            @ERRORNUMBER     INT,
            @ERRORSTATE      INT,
            @ERRORPROCEDURE  SYSNAME,
            @ERRORLINE       INT,
            @XASTATE         INT

    SELECT
            @ERRORMESSAGE     = ERROR_MESSAGE(),
            @ERRORSEVERITY    = ERROR_SEVERITY(),
            @ERRORNUMBER      = ERROR_NUMBER(),
            @ERRORSTATE       = ERROR_STATE(),
            @ERRORPROCEDURE   = ERROR_PROCEDURE(),
            @ERRORLINE        = ERROR_LINE()

    SET @ERRORMESSAGE = 
    (
    SELECT                    CHAR(13) +
      'Message:'         +    SPACE(1) + @ErrorMessage                           + SPACE(2) + CHAR(13) +
      'Error:'           +    SPACE(1) + CONVERT(NVARCHAR(50),@ErrorNumber)      + SPACE(1) + CHAR(13) +
      'Severity:'        +    SPACE(1) + CONVERT(NVARCHAR(50),@ErrorSeverity)    + SPACE(1) + CHAR(13) +
      'State:'           +    SPACE(1) + CONVERT(NVARCHAR(50),@ErrorState)       + SPACE(1) + CHAR(13) +
      'Routine_Name:'    +    SPACE(1) + coalesce(@ErrorProcedure,'')            + SPACE(1) + CHAR(13) +
      'Line:'            +    SPACE(1) + CONVERT(NVARCHAR(50),@ErrorLine)        + SPACE(1) + CHAR(13) +
      'Executed As:'     +    SPACE(1) + SYSTEM_USER + SPACE(1)                             + CHAR(13) +
      'Database:'        +    SPACE(1) + DB_NAME() + SPACE(1)                               + CHAR(13) +
      'OSTime:'          +    SPACE(1) + CONVERT(NVARCHAR(25),CURRENT_TIMESTAMP,121)        + CHAR(13) 
    )

        SELECT @XASTATE = XACT_STATE();

        IF @XASTATE = - 1
            ROLLBACK;

        IF @XASTATE = 1
            AND @TranCountAtStart = 0
            ROLLBACK

        IF @XASTATE = 1
            AND @TranCountAtStart > 0
            ROLLBACK TRANSACTION USP_Procedure_Name;
            --We can also save the error details to a table for later reference here.
            RAISERROR (@ERRORMESSAGE,16,1)

    END CATCH

どうすればマージにエラー処理プロセスを追加できますか?

おそらくそれは削除で壊れます。

       WHEN NOT MATCHED BY SOURCE

                    THEN DELETE 

ここにそれを処理する何かがあるべきです。

単純にして、制約違反で壊れた場合は、レコードを削除しないでください。

どうすればこれを達成できますか?

1

さて、「レコードを削除しないでください」はさておき、外部キーを 削除カスケードまたは削除セットnullの場合 に変更できます。これにより、親を削除したり、子キーをnullに設定したりすると、すべてが削除されます。

それらは常に素晴らしい行動とは限りません。たとえば、顧客がアカウントを削除した場合、おそらくすべての注文履歴と税金請求書も削除したくないでしょう。

醜い、恐ろしいが一般的な代替手段は次のとおりです。

  • スキーマが変更されないことがわかっている場合(またはそれを実行する場合)、削除したくないキーを除外するサブクエリを含めることができます。これは悪くないです。

  • 本当に醜くしたい場合は、そこでプロシージャを使用して動的SQLを作成し、同じことを行うことができます(それを一時テーブルに入れて、結果から除外します)。必要がない限り、これを行わないでください。

  • これらを[削除ではなく]トリガーと組み合わせて使用​​できます。これはおそらく最悪です。

1
Cody Konior