Комплекс TSQL Merge - PullRequest
       21

Комплекс TSQL Merge

5 голосов
/ 21 сентября 2011

У меня есть унаследованный 'блестящий кусок кода TSQL, который делает это:

  • Перемещает строку за строкой над курсором.
  • Курсор содержит данные, которые необходимо объединить (Upserted) в таблице A
  • Для каждого цикла строки в курсоре вызывается сохраненный процесс. Процесс:
    • Если соответствующая строка существует в таблице A, то она обновляется
    • Если такой строки не существует, то:
      • Вставляет одну строку в другую таблицу B.
      • Извлекает вновь сгенерированный идентификатор (скажем, его называют IDB)
      • Вставляет одну строку в таблицу A. Вставки таблицы A требуется IDB (поле не является нулевым, предполагается, что оно имеет значения ТОЛЬКО из таблицы 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

Заранее спасибо за любые ответы

George

Ответы [ 2 ]

3 голосов
/ 21 сентября 2011

Если у вас есть автоматически сгенерированный PK в TableB, вы можете использовать код, подобный этому. В противном случае просто измените INSERT на TableA, чтобы сначала получить PK из TableB.

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
1 голос
/ 21 сентября 2011

Чтобы ответить на ваш общий вопрос - «Могу ли я вставить и вернуть PK в одном утверждении, которое может быть вложено в другие утверждения» - да, абсолютно. Но это зависит от логики создания вашего ПК. В этом случае кажется, что нужно сгенерировать PK, вам нужно вставить в другую таблицу, а затем взять оттуда только что сгенерированный идентификатор. Это не очень эффективно (ИМХО), если для этого нет особых причин. Автоинкременты, GUID и т. Д., Как правило, работают лучше, чем PK. Если вы можете упростить / изменить логику, стоящую за этим, и вы можете найти более простой способ сделать это, то PK 'CAN' может быть сгенерирован в одной инструкции / функции и, таким образом, может использоваться в других операторах.

...