2011-09-21 19 views
5

he ' heredada' una brillante pieza de código TSQL que hace esto:TSQL Complejo Merge

  • Loops fila por fila sobre un cursor.
  • El cursor contiene datos que deben fusionarse (Upserted) en la Tabla A
  • Para cada ciclo de fila en el cursor se llama a un proceso almacenado. El proc:
    • Si existe una fila correspondiente en la tabla A a continuación, se actualiza
    • Si una fila tal no existe, entonces:
      • inserta una sola fila en en una tabla diferente B.
      • de obtenciones el ID recién generado (digamos su IDB llamado)
      • Inserta una sola fila en la Tabla A. Las inserciones en la Tabla A necesitan un IDB (el campo no es nulo, se supone que tiene valores SÓLO de la tabla B, pero no FK) restricción está en su lugar)

Obviamente esto es una mierda (razones de rendimiento & elegancia) !!

Pregunta Al principio esto parece un caso estándar de uso de MERGE. Intenté hacer:

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 

intentado también diversos enfoques como un nested select that sends IDB on the OUTPUT pero falla porque BID es una PK.

Otros tipos de fusiones también fracasaron por ejemplo:

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 

¿Alguien tiene una idea sobre esto? Esencialmente Creo que si se generaliza la pregunta sería:

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

Gracias de antemano por cualquier respuesta

George

+2

Usted puede fundirse en la Tabla B y utilice los botones [técnica aquí] (http://stackoverflow.com/q/5365629/73226) creo. –

+0

Gracias, lo vi. Es realmente una buena idea. Básicamente, el chico almacena Ids generados recientemente en variables temporales. No es lo mismo que lo que estaba buscando (hacer todo el proceso en 1 Merge) pero sigue siendo un enfoque interesante. Gracias Martin. – George

+0

+1 por usar la palabra 'upserted' ... :) –

Respuesta

3

Si tiene una PK autogenerada en TableB, puede usar un código similar a este. De lo contrario, simplemente cambie el INSERT en TableA para tomar PK de TableB primero.

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 
+0

Creo que es una respuesta muy buena; este <100point poster gracias ;-) – George

+0

Gracias, mal día ... lo siento. – GalacticJello

1

Para responder a su pregunta general - '¿Puedo insertar y devolver el PK en una declaración que se puede anidar en otras declaraciones '- sí, absolutamente. Pero depende de la lógica detrás de la creación de su PK. En este caso, parece generar un PK, debe insertarlo en una tabla diferente y luego tomar el ID generado desde allí. Esto no es muy eficiente (en mi humilde opinión) a menos que haya una razón muy específica para hacerlo. Autoincrements, GUIDs, etc. tienden a funcionar mejor como PKs. Si puede simplificar/cambiar la lógica detrás de esto, y puede encontrar una forma más sencilla de lograr eso, entonces el PK 'CAN' se puede generar en una declaración/función y, por lo tanto, se puede usar en otras declaraciones.