2010-09-25 117 views
71

Tengo tres procedimientos almacenados Sp1, Sp2 y Sp3.Errores: "La declaración INSERT EXEC no se puede anidar". y "No se puede usar la instrucción ROLLBACK dentro de una instrucción INSERT-EXEC". ¿Cómo resolver esto?

La primera (Sp1) se ejecutará la segunda (Sp2) y guardar los datos devueltos en @tempTB1 y el segundo se ejecutará la tercera (Sp3) y guardar los datos en @tempTB2.

Si ejecuto el Sp2 va a trabajar y me devolverá todos mis datos de la Sp3, pero el problema está en el Sp1, cuando yo haga que se mostrará este error:

INSERT EXEC statement cannot be nested

I trató de cambiar el lugar de execute Sp2 y me muestra otro error:

Cannot use the ROLLBACK statement within an INSERT-EXEC statement.

Respuesta

75

Este es un problema común cuando se trata de datos 'burbuja' a partir de una cadena de procedimientos almacenados. Una restricción en SQL Server es que solo puede tener un INSERT-EXEC activo a la vez. Recomiendo mirar How to Share Data Between Stored Procedures que es un artículo muy completo sobre patrones para evitar este tipo de problema.

Por ejemplo, una solución alternativa podría ser convertir Sp3 en una función con valores de tabla.

+0

enlace roto O sitio que no responde. – SouravA

15

Ésta es la única forma "simple" para hacer esto en SQL Server sin alguna función creada contorneado gigante o ejecutados llamada cadena SQL, ambos de los cuales son soluciones terribles:

  1. crear una tabla temporal
  2. OpenRowset sus datos de procedimiento almacenado en él

Ejemplo:

INSERT INTO #YOUR_TEMP_TABLE 
SELECT * FROM OPENROWSET ('SQLOLEDB','Server=(local);TRUSTED_CONNECTION=YES;','set fmtonly off EXEC [ServerName].dbo.[StoredProcedureName] 1,2,3') 

Nota: DEBE usar 'set fmtonly off', Y NO PUEDE agregar sql dinámico a esto dentro de la llamada openrowset, ya sea para la cadena que contiene los parámetros del procedimiento almacenado o para el nombre de la tabla. Es por eso que debe usar una tabla temporal en lugar de variables de tabla, lo que hubiera sido mejor, ya que supera la tabla temporal en la mayoría de los casos.

+0

No es obligatorio usar SET FMTONLY OFF. Solo puede agregar un IF (1 = 0) que devuelve una tabla vacía con los mismos tipos de datos que normalmente devuelve el procedimiento. –

+0

Las tablas temporales y las variables de tabla almacenan sus datos de forma diferente. Se supone que las variables de tabla se utilizan para pequeños conjuntos de resultados, ya que el optimizador de consultas no mantiene estadísticas sobre las variables de tabla. Entonces, para conjuntos de datos grandes, casi siempre es mejor usar tablas Temp. Aquí hay un buen artículo de blog sobre él http://www.mssqltips.com/sqlservertip/2825/sql-server-temp-table-vs-table-variable-performance-testing/ – gh9

+0

@ gh9 sí, pero esto es horrible idea de grandes conjuntos de resultados de todos modos. Las estadísticas y el uso de una tabla real en la base de datos temporal pueden causar una sobrecarga significativa. Tengo un procedimiento que devuelve un conjunto de registros con 1 fila de valores actuales (consultar varias tablas) y un procedimiento que almacena eso en una variable de tabla y lo compara con valores en otra tabla con el mismo formato. El cambio de una tabla temporal de una variable de tabla aceleró el tiempo promedio hasta de 8 ms a 2 ms, lo cual es importante cuando se llama varias veces por segundo durante todo el día y 100.000 veces en un proceso nocturno. –

1

Tuve el mismo problema y preocupación por el código duplicado en dos o más sprocs. Terminé agregando un atributo adicional para "modo". Esto permitió que existiera un código común dentro de un sproc y el flujo de modo dirigido y el conjunto de resultados del sproc.

3

Encontré un trabajo alrededor es convertir uno de los prods en una función de tabla de valores. Me doy cuenta de que no siempre es posible, y presenta sus propias limitaciones. Sin embargo, siempre he podido encontrar que al menos uno de los procedimientos es un buen candidato para esto. Me gusta esta solución, porque no introduce ningún "ataque" a la solución.

+0

pero una desventaja es un problema con el manejo de excepciones si la función es compleja, ¿no? – Muflix

3

Mi trabajo alrededor de este problema siempre ha sido utilizar el principio de que las tablas temporales hash únicas están en el alcance de cualquier proceso llamado. Entonces, tengo un interruptor de opción en los parámetros de proceso (por defecto está desactivado). Si esto está activado, el proceso llamado insertará los resultados en la tabla temporal creada en el proceso de llamada.Creo que en el pasado he dado un paso más allá y poner un poco de código en la llamada proc para comprobar si existe la tabla hash único en su alcance, si lo hace entonces insertar el código, de lo contrario devuelve el conjunto de resultados. Parece funcionar bien: la mejor forma de pasar grandes conjuntos de datos entre los procesos.

+0

Me gusta esta respuesta y apostaría a que obtendrá más votos si tuviera que proporcionar un ejemplo. – jimhark

3

bien, animado por jimhark aquí es un ejemplo de la tabla hash único enfoque de edad: -

CREATE PROCEDURE SP3 as 

BEGIN 

    SELECT 1, 'Data1' 
    UNION ALL 
    SELECT 2, 'Data2' 

END 
go 


CREATE PROCEDURE SP2 as 

BEGIN 

    if exists (select * from tempdb.dbo.sysobjects o where o.xtype in ('U') and o.id = object_id(N'tempdb..#tmp1')) 
     INSERT INTO #tmp1 
     EXEC SP3 
    else 
     EXEC SP3 

END 
go 

CREATE PROCEDURE SP1 as 

BEGIN 

    EXEC SP2 

END 
GO 


/* 
--I want some data back from SP3 

-- Just run the SP1 

EXEC SP1 
*/ 


/* 
--I want some data back from SP3 into a table to do something useful 
--Try run this - get an error - can't nest Execs 

if exists (select * from tempdb.dbo.sysobjects o where o.xtype in ('U') and o.id = object_id(N'tempdb..#tmp1')) 
    DROP TABLE #tmp1 

CREATE TABLE #tmp1 (ID INT, Data VARCHAR(20)) 

INSERT INTO #tmp1 
EXEC SP1 


*/ 

/* 
--I want some data back from SP3 into a table to do something useful 
--However, if we run this single hash temp table it is in scope anyway so 
--no need for the exec insert 

if exists (select * from tempdb.dbo.sysobjects o where o.xtype in ('U') and o.id = object_id(N'tempdb..#tmp1')) 
    DROP TABLE #tmp1 

CREATE TABLE #tmp1 (ID INT, Data VARCHAR(20)) 

EXEC SP1 

SELECT * FROM #tmp1 

*/ 
2

Este truco funciona para mí.

Usted no tiene este problema en el servidor remoto, porque en el servidor remoto, espera el último comando de inserción para el resultado del comando anterior para ejecutar. No es el caso en el mismo servidor.

Beneficio esa situación para una solución.

Si tiene los permisos adecuados para crear un servidor vinculado, lo hace. Crea el mismo servidor como servidor vinculado.

  • en SSMS, inicie sesión en el servidor de
  • vaya a "objetos de servidor
  • Haga clic derecho en 'servidores vinculados', luego 'Nuevo servidor vinculado'
  • en el diálogo, dar cualquier nombre de su servidor vinculado: por ejemplo: THISSERVER
  • tipo de servidor es "Otra fuente de datos"
  • Proveedor: Proveedor Microsoft OLE DB para SQL Server
  • fuente de datos: su dirección IP, que puede ser también acaba de anuncio OT (.), porque es localhost
  • Ir a la pestaña "Seguridad" y elija el tercero uno "pueden hacer utilizando el contexto de seguridad actual del inicio de sesión"
  • Puede editar las opciones del servidor (tercera pestaña) si quieres
  • Pulse OK, el servidor vinculado se crea

ahora su comando SQL en el SP1 es

insert into @myTempTable 
exec THISSERVER.MY_DATABASE_NAME.MY_SCHEMA.SP2 

Créeme, funciona incluso tienes inserción dinámica en el SP2

-1

En SQL Server 2008 R2, que tenían un desajuste en las columnas de tabla que provocó el error Rollback. Se fue cuando yo fijo mi variable de tabla sqlcmd poblado por la declaración de inserción Exec para que coincida devuelto por el procedimiento almacenado. Faltaba org_code. En un archivo cmd de Windows, carga el resultado del procedimiento almacenado y lo selecciona.

set SQLTXT= declare @resets as table (org_id nvarchar(9), org_code char(4),^
tin(char9), old_strt_dt char(10), strt_dt char(10));^
insert @resets exec rsp_reset;^
select * from @resets; 

sqlcmd -U user -P pass -d database -S server -Q "%SQLTXT%" -o "OrgReport.txt" 
+0

OP preguntaba sobre un error que se produce cuando se utilizan instrucciones insert-exec en procedimientos almacenados anidados. Su problema arrojaría un error diferente, como "La lista de selección para la instrucción INSERT contiene menos elementos que la lista de inserción. El número de valores SELECT debe coincidir con el número de columnas INSERT". – Losbear

+0

Esto es más una advertencia de que es posible obtener este mensaje erróneamente. – user3448451

0

qué pasa simplemente almacenar el resultado en la tabla estática? Como

-- SubProcedure: subProcedureName 
--------------------------------- 
-- Save the value 
DELETE lastValue_subProcedureName 
INSERT INTO lastValue_subProcedureName (Value) 
SELECT @Value 
-- Return the value 
SELECT @Value 

-- Procedure 
-------------------------------------------- 
-- get last value of subProcedureName 
SELECT Value FROM lastValue_subProcedureName 

no es ideal, pero es tan simple y no es necesario volver a escribir todo.

ACTUALIZACIÓN: la solución anterior no funciona bien con las consultas paralelas (asíncrono y el acceso multiusuario), por lo tanto ahora Iam usando tablas temporales

-- A local temporary table created in a stored procedure is dropped automatically when the stored procedure is finished. 
-- The table can be referenced by any nested stored procedures executed by the stored procedure that created the table. 
-- The table cannot be referenced by the process that called the stored procedure that created the table. 
IF OBJECT_ID('tempdb..#lastValue_spGetData') IS NULL 
CREATE TABLE #lastValue_spGetData (Value INT) 

-- trigger stored procedure with special silent parameter 
EXEC dbo.spGetData 1 --silent mode parameter 

anidada spGetData contenido procedimiento almacenado

-- Save the output if temporary table exists. 
IF OBJECT_ID('tempdb..#lastValue_spGetData') IS NOT NULL 
BEGIN 
    DELETE #lastValue_spGetData 
    INSERT INTO #lastValue_spGetData(Value) 
    SELECT Col1 FROM dbo.Table1 
END 

-- stored procedure return 
IF @silentMode = 0 
SELECT Col1 FROM dbo.Table1 
+0

Generalmente, no se puede crear un SPROC ad-hoc como se puede con Tablas. Tendrá que ampliar su ejemplo con más referencias, ya que este enfoque no es muy conocido ni aceptado. Además, se asemeja a una expresión Lambda más que a una ejecución SProc, que ANSI-SQL no permite para aproximaciones Lambda Expression. – GoldBishop

+0

Funciona, pero me parece que tampoco funciona bien con consultas paralelas (accesos asincrónicos y multiusuario). Por lo tanto, ahora estoy usando el enfoque de tabla temporal. Actualicé mi respuesta. – Muflix

+1

La lógica de la tabla Temp es buena, me preocupaba la referencia SProc. Sproc no se puede consultar inherentemente directamente. Las funciones con valores de tabla se pueden consultar directamente desde.Al igual que aludió en su lógica actualizada, el mejor enfoque es una tabla de temperatura, sesión, instancia o global, y operar desde ese punto. – GoldBishop

0

Declarar una variable de cursor de salida a la sp interior:

@c CURSOR VARYING OUTPUT 

A continuación, declare un cursor c para la selección que desea devolver. A continuación, abra el cursor. asentando después la referencia:

DECLARE c CURSOR LOCAL FAST_FORWARD READ_ONLY FOR 
SELECT ... 
OPEN c 
SET @c = c 

No cierre o reasignar.

ahora llamamos el sp interior de la exterior suministrar un parámetro de cursor como:

exec sp_abc a,b,c,, @cOUT OUTPUT 

Una vez que el SP interno se ejecuta, su @cOUT está dispuesto a buscar. Bucle y luego cerrar y desasignar.

Cuestiones relacionadas