5

私はこれを行う素晴らしいTSQLコードを「継承」しました。

  • カーソル上で行ごとにループします。
  • カーソルには、表Aでマージ(アップサート)する必要のあるデータが含まれています
  • カーソルの行ループごとに、ストアドプロシージャが呼び出されます。手順:
    • 対応する行がテーブルAに存在する場合、それは更新されます
    • そのような行が存在しない場合は、次のようにします。
      • 別のテーブルBに単一の行を挿入します。
      • 新しく生成されたIDをフェッチします(たとえば、IDBと呼ばれます)
      • テーブルAに単一の行を挿入します。テーブルAの挿入にはIDBが必要です(フィールドはnullではなく、テーブルBからの値のみを持つことになっていますが、FK制約はありません)

明らかにこれは最悪です(パフォーマンスとエレガンスの理由)!!

質問 最初は、これはMERGEの使用の標準的なケースのように見えます。私はやってみました:

MERGE [dbo].[TableA] AS Target
USING <cursor data set as a select statement> as Src on target.IDA = Src.IDA
WHEN MATCHED 
  //update
WHEN NOT MATCHED
//insert <------ Fails because obviously a new IDB is required

また、のようなさまざまなアプローチを試しましたnested select that sends IDB on the OUTPUTが、IDBがPKであるため失敗します。

他の種類のマージも失敗しました。例:

MERGE Table A with <cursor data set as a select statement>
...
MERGE Table A with Table B
WHEN NOT MATCHED
//insert on Table A
WHEN NOT MATCHED
// Update Table B

誰かがこれについて考えを持っていますか?基本的に、質問を一般化すると、次のようになると思います。

Can I insert and return the PK in one statement that can be nested in other statements

返信をよろしくお願いします

ジョージ

4

2 に答える 2

3

TableBに自動生成されたPKがある場合は、これと同様のコードを使用できます。それ以外の場合は、INSERTをTableAに変更して、最初にTableBからPKを取得します。

DECLARE @OldData CHAR(10)
SET @OldData = 'Old'
DECLARE @NewData CHAR(10)
SET @NewData = 'New'

CREATE TABLE #TableA 
(
    IDA INT IDENTITY(1,1) PRIMARY KEY,
    IDB INT NOT NULL,
    DataA CHAR(10)
)

CREATE TABLE #TableB 
(
    IDB INT IDENTITY(1,1) PRIMARY KEY,
    DataB CHAR(10)
)

DECLARE @IDsToUpsert TABLE
(
    ID INT
)

-- Add test values for existing rows 
INSERT INTO #TableB
OUTPUT INSERTED.IDB, @OldData
INTO #TableA
SELECT @OldData UNION ALL
SELECT @OldData UNION ALL
SELECT @OldData UNION ALL
SELECT @OldData 

-- Add test values for the rows to upsert
INSERT INTO @IDsToUpsert
SELECT 1 UNION -- exists
SELECT 3 UNION -- exists
SELECT 5 UNION -- does not exist
SELECT 7 UNION -- does not exist
SELECT 9       -- does not exist

-- Data Before
SELECT * From #TableA
SELECT * From #TableB

DECLARE rows_to_update CURSOR
    FOR SELECT ID FROM @IDsToUpsert

DECLARE @rowToUpdate INT
DECLARE @existingIDB INT

OPEN rows_to_update;

FETCH NEXT FROM rows_to_update 
INTO @rowToUpdate;

WHILE @@FETCH_STATUS = 0
BEGIN
    BEGIN TRANSACTION

        IF NOT EXISTS 
        (
            SELECT 1 FROM #TableA WITH (UPDLOCK, ROWLOCK, HOLDLOCK)
            WHERE IDA = @rowToUpdate            
        )
        BEGIN
            -- Insert into B, then insert new val into A
            INSERT INTO #TableB
            OUTPUT INSERTED.IDB, INSERTED.DataB 
            INTO #TableA
            SELECT @NewData
            -- Change code here if PK on TableB is not autogenerated
        END
        ELSE
        BEGIN
            -- Update
            UPDATE #TableA
            SET DataA = @NewData
            WHERE IDA = @rowToUpdate
        END

    COMMIT TRANSACTION

    FETCH NEXT FROM rows_to_update 
    INTO @rowToUpdate;
END

CLOSE rows_to_update;
DEALLOCATE rows_to_update;

SELECT * FROM #TableA
SELECT * FROM #TableB

DROP TABLE #TableA
DROP TABLE #TableB
于 2011-09-21T19:16:25.863 に答える
1

あなたの一般的な質問に答えるために-'他のステートメントにネストできる1つのステートメントにPKを挿入して返すことはできますか'-はい、絶対に。ただし、PKの作成の背後にあるロジックによって異なります。この場合、PKを生成しているように見えます。別のテーブルに挿入して、そこから新しく生成されたIDを取得する必要があります。特別な理由がない限り、これはあまり効率的ではありません(IMHO)。自動インクリメント、GUIDなどは、PKとしてより適切に機能する傾向があります。この背後にあるロジックを単純化/変更でき、それを実現するためのより簡単な方法を見つけることができれば、PK'CAN'は1つのステートメント/関数で生成され、他のステートメントで使用できます。

于 2011-09-21T16:22:53.710 に答える