web-dev-qa-db-ja.com

T-SQLマージステートメントを使用するときに重複レコードの挿入を回避する方法

T-SQLのMERGEステートメントを使用して多くのレコードを挿入しようとしていますが、ソーステーブルに重複するレコードがある場合、クエリはINSERTに失敗します。失敗の原因は次のとおりです。

  1. ターゲットテーブルには、2つの列に基づく主キーがあります
  2. ソーステーブルに、ターゲットテーブルの主キー制約に違反する重複レコードが含まれている可能性があります(「主キー制約の違反」がスローされます)

ソーステーブル内の重複レコードを無視するか、発生する可能性のある例外をキャッチするためにINSERTステートメントを試行/キャッチするようにMERGEステートメントを変更する方法を探しています(つまり、他のすべてのINSERTステートメントは発生する可能性のあるいくつかの悪い卵)-または、おそらく、この問題を回避するためのより良い方法がありますか?

これが私が説明しようとしていることのクエリ例です。以下の例では、一時テーブルに100kレコードを追加してから、それらのレコードをターゲットテーブルに挿入しようとします-

[〜#〜] edit [〜#〜]私の元の投稿では、例のテーブルに2つのフィールドのみを含めて、SO friends to give DISTINCT MERGEステートメントでの重複を回避するための解決策。私の実際の問題では、テーブルに15個のフィールドがあり、そのうち2個のフィールドがCLUSTERED PRIMARY KEYであることに言及する必要があります。したがって、DISTINCTキーワードは機能しません。 15のフィールドすべてを選択し、2つのフィールドに基づく重複を無視する必要があります。

以下のクエリを更新して、もう1つのフィールドcol4を含めました。 MERGEにcol4を含める必要がありますが、col2とcol3のみが一意であることを確認するだけで済みます。

-- Create the source table
CREATE TABLE #tmp (
col2 datetime NOT NULL,
col3 int NOT NULL,
col4 int
)
GO

-- Add a bunch of test data to the source table
-- For testing purposes, allow duplicate records to be added to this table
DECLARE @loopCount int = 100000
DECLARE @loopCounter int = 0
DECLARE @randDateOffset int
DECLARE @col2 datetime
DECLARE @col3 int
DECLARE @col4 int

WHILE (@loopCounter) < @loopCount
BEGIN
    SET @randDateOffset = Rand() * 100000
    SET @col2 = DATEADD(MI,@randDateOffset,GETDATE())
    SET @col3 = Rand() * 1000
    SET @col4 = Rand() * 10
    INSERT INTO #tmp
    (col2,col3,col4)
    VALUES
    (@col2,@col3,@col4);

    SET @loopCounter = @loopCounter + 1
END

-- Insert the source data into the target table
-- How do we make sure we don't attempt to INSERT a duplicate record? Or how can we 
-- catch exceptions? Or?
MERGE INTO dbo.tbl1 AS tbl
    USING (SELECT * FROM #tmp) AS src
    ON (tbl.col2 = src.col2 AND tbl.col3 = src.col3)
    WHEN NOT MATCHED THEN 
        INSERT (col2,col3,col4)
        VALUES (src.col2,src.col3,src.col4);
GO
16
Jed

新しい仕様に解決しました。 col4の最大値のみを挿入する:今回は、重複する行を防ぐためにgroupbyを使用しました。

MERGE INTO dbo.tbl1 AS tbl 
USING (SELECT col2,col3, max(col4) col4 FROM #tmp group by col2,col3) AS src 
ON (tbl.col2 = src.col2 AND tbl.col3 = src.col3) 
WHEN NOT MATCHED THEN  
    INSERT (col2,col3,col4) 
    VALUES (src.col2,src.col3,src.col4); 
21
t-clausen.dk

ソースに重複があり、MERGEを完全に使用していない場合は、INSERTを使用します。

 INSERT dbo.tbl1 (col2,col3) 
 SELECT DISTINCT col2,col3
 FROM #tmp src
 WHERE NOT EXISTS (
       SELECT *
       FROM dbo.tbl1 tbl
       WHERE tbl.col2 = src.col2 AND tbl.col3 = src.col3)

MERGEが失敗する理由は、行ごとにチェックされないためです。一致しないものがすべて見つかった場合、これらすべてを挿入しようとします。同じバッチ内ですでに一致している行はチェックされません。

これは、アトミック操作の初期のデータ変更が後のデータ変更に影響を与える "ハロウィーンの問題" を少し思い出させます:それは正しくありません

8
gbn

GROUP BYの代わりに、分析関数を使用して、マージする重複レコードのセットから特定のレコードを選択できます。

MERGE INTO dbo.tbl1 AS tbl
USING (
    SELECT *
    FROM (
        SELECT *, ROW_NUMBER() OVER (PARTITION BY col2, col3 ORDER BY ModifiedDate DESC) AS Rn
        FROM #tmp
    ) t
    WHERE Rn = 1    --choose the most recently modified record
) AS src
ON (tbl.col2 = src.col2 AND tbl.col3 = src.col3)
6
Hai Phan