web-dev-qa-db-ja.com

IDが主キーでない場合、IDENTITY_INSERT ONを指定したMERGEは機能しません

私は頻繁にMERGEステートメントを使用しており、それに慣れています。ここで、一部のテーブルに主キーではないIDENTITY列がある状況に遭遇しました。この状況では、マージステートメントの生成スクリプトでID列の存在がチェックされ、マージの前にidentity_insertが明示的にオンになっていても、スクリプトは失敗しました。ただし、それでも失敗します。

失敗してIDENTITY列について不平を言うデモ用の小さなサンプルを作成しました。

ID列 'aid'を更新できません。

Identity_Insert ONをオンにして以来、INSERT列の値を好きなようにUPDATEまたはIDENTITYできると期待しています。しかし、それは機能しません。

これがサンプルコードです:

CREATE TABLE [dbo].[tm2]
(
    [id] [int] NOT NULL,
    [aid] [int] IDENTITY(1,1) NOT NULL,
    [txt] [nchar](10) NULL,

    CONSTRAINT [PK_tm2] 
       PRIMARY KEY CLUSTERED ([id] ASC) 
            WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]
GO

SET IDENTITY_INSERT [dbo].tm2 ON

MERGE INTO [dbo].tm2 AS Target
USING (VALUES
  (1,2,'qdqewqf'),
  (2,3,'#ED7F00')
) AS Source ([ID], [aid], [txt]) ON (Target.[ID] = Source.[ID])

WHEN MATCHED AND (Target.aid <> Source.aid OR Target.txt <> Source.txt ) THEN
    UPDATE 
    SET
       aid = Source.aid, 
       txt = Source.txt

WHEN NOT MATCHED BY TARGET THEN
    INSERT([ID], aid, txt)
    VALUES(Source.[ID], Source.aid, Source.txt)

WHEN NOT MATCHED BY SOURCE THEN 
    DELETE;

SET IDENTITY_INSERT [dbo].tm2 OFF

技術的な詳細:

  • SQL Server 2008 R2
  • 照合順序SQL_Latin1_General_CP1_CI_AS
2
Magier

ID値は更新できません。それらはcanbeinsertedif IDENTITY_INSERTは、そのテーブルに対してオンになっています。

このコードはそれがどのように機能するかを示しています:

USE tempdb;

CREATE TABLE dbo.TestIdentity
(
    ID INT NOT NULL
        IDENTITY(1,1)
    , SomeData VARCHAR(255) NOT NULL
);

INSERT INTO dbo.TestIdentity (SomeData)
VALUES ('This is a test');

--This works

SET IDENTITY_INSERT dbo.TestIdentity ON;

INSERT INTO dbo.TestIdentity (ID, SomeData)
VALUES (1, 'This is a test');

SET IDENTITY_INSERT dbo.TestIdentity OFF;

/*
    This fails with:

    Msg 8102, Level 16, State 1, Line 15
    Cannot update identity column 'ID'.
*/
SET IDENTITY_INSERT dbo.TestIdentity ON;

UPDATE dbo.TestIdentity
SET ID = 2
WHERE ID = 1;

SET IDENTITY_INSERT dbo.TestIdentity OFF;

IDENTITY列を、手動で増分した値を使用する列に置き換えることができます。 SQL Server 2012+を使用している場合は、 SEQUENCE を使用して値を入力できます。 SQL Server 2008 R2を使用しているため、独自のソリューションをロールして、IDを置き換える値を生成する必要があります。そのような方法の1つは詳細 here です。

OUTPUT句を使用して行を同時に削除し、変更されたID値でテーブルに挿入することで、問題を解決できる可能性があります。ただし、これはmerge構文を使用しないことを前提としています。

/*
    This works, but cannot be used with MERGE
*/
TRUNCATE TABLE dbo.TestIdentity;

INSERT INTO dbo.TestIdentity (SomeData)
VALUES ('This is a test');

SET IDENTITY_INSERT dbo.TestIdentity ON;

DELETE
FROM dbo.TestIdentity
OUTPUT 2 /* The new identity value */
    , deleted.SomeData
INTO dbo.TestIdentity (ID, SomeData)
WHERE ID = 1 /* the old identity value */;

SET IDENTITY_INSERT dbo.TestIdentity OFF;

SELECT *
FROM dbo.TestIdentity
5
Max Vernon