0:00 0 1
An INSERT EXEC statement cannot be nested

An INSERT EXEC statement cannot be nested

  Sergi |  enero 222019

¿Alguna vez te ha pasado que dentro de un Proceso Almacenado (SP desde ahora) te da un error cuando llamas a otro SP? A mi me ha pasado varias veces, y siempre he encontrado como "esquivar" ese problema, pero en estea ocasión ya no me valía con "esquivar" el problema, ya que necesitaba SI o SI que me ejecutara el anidamiento de SP.

Este error ocurre cuando intentas hacer INSERT , ya sea en tablas dinmácias, temporales o fijas desde un SP que es llamado desde otro SP. El error que te da MS'SQL es

Msg 8164, Level 16, State 1, Procedure Rep_Accuracy_Achievement_Analysis_Report_PRR, Line 461
An INSERT EXEC statement cannot be nested.

Y este puede llegar a ser un gran problema en SP grandes y complejos que necesitan mover muchos datos, o crear muchas tablas dinámicas, para finalmente devolver el conjunto de datos deseado.

Hoy te propongo la solución que he encotrado al problema, no se si será la mejor, pero a mí me esta funcionando y el rendimiento del SP ha mejorado muhco, ha pasado de casi 17 segundos de ejecución a sólo 3.

Planteamiento del problema

Primero quiero explicar que es lo que yo necesitaba hacer, para que así te sea un poco más fácil comprender la lógica del código que te voy a proponer para solucionarlo.

Se necesita calcular el costo de una mensajeria, dependiendo del codigo postal, tipo de caja y día de reparto. Cada mensajero puede tener varias propiedades, por lo que encarece el costo, como puede ser la urgencia del reparto (mi caso, 4 horas o inmediato).

Para ello se han definido varias tablas, todo esto si que ya no es necesario explicar ni mostrar. Pero en el SP principal, se calcula primero el costo para la ruta por defecto, sin las propiedades. Para después saber cuántas propiedades tiene. Luego en un segundo SP, se calcula el costo de cada una de las propiedades, y aqui es donde comenzó el problema con el anidamiento de intrucciones INSERT. Ya que se pretendía algo como esto

insert @costos exec calculaPropiedad

Es en ésta líinea donde MS-SQL nos devolvía el error, ya que previamente hay otros INSERT en otras tablas dinámicas.

Reproduciendo el error

Para que entendais un poco mejor lo expuesto hasta ahora, voy a mostraros un ejemplo de cómo y cúando se obtiene el error

CREATE PROCEDURE CalculaCostoPropiedad @storeid varchar(30) AS
declare @mytemp table (costo decimal(10,4), qty int)
INSERT INTO @mytemp (costo, qty) VALUES (2,2)
select * from @mytemp
go
CREATE PROCEDURE CalculaCosto @storeid varchar(30) AS
declare @mytemp table (costo decimal(10,4), qty int)
INSERT INTO @mytemp (costo, qty) VALUES (1,1)
INSERT INTO @mytemp EXEC CalculaCostoPropiedad @storeid
SELECT * FROM @mytemp
go
EXEC CalculaCosto '7131'
go
DROP PROCEDURE CalculaCosto, CalculaCostoPropiedad

Evidentemente esto es un ejemplo muy simple, pero ilustra lo que estamos explicando. Si no te da error, es precisamente por lo simple que es, pero si estas leyendo estas lineas seguro que has recibido el error que estoy tratando de explicar.

El porqué del error

Este error ocurre cuiando un SP tiene que insertar datos desde otro SP en una tabla, ya sea temporal, dinamica o de la base de datos. Esto no está permidido al intentar aumentar los datos de una cadena de procedimientos almacenados. Solo se permite hacer una inserción al mismo tiempo. Esta sería la explicación que encontrartemos en la documentación.

La solucion propuesta

Después de leer documentación, buscar en internet, y sobre todo, ir viendo posibilidades, me he quedado con esta. La mejor forma de solucionar este problema es tener una tabla en la base de datos en la cual se van a ir almacenando los costos. 

   CREATE TABLE mytemp (col1 int     NOT NULL,
                         col2 char(5) NULL,
                        ...)

Luego en los respectivos SP se hace el INSERT dentro de esta tabla temporal para luego realizar las consultas necesarias para sacar los costes. Un ejemplo de código podría ser algo como esto

CREATE PROCEDURE CalculaCostoPropiedad @storeid varchar(30) AS
   INSERT INTO mytemp (costo, qty)
      SELECT t.costo, s.qty
      FROM   mensajeros s
      JOIN   costos t ON t.mensajero_id = s.mensajero_id
      WHERE  s.stor_id = @storeid
go
CREATE PROCEDURE CalculaCosto @storeid varchar(30) AS
   INSERT INTO mytemp (costo, qty)
      SELECT t.costo, s.qty
      FROM   mensajeros s
      JOIN   costos t ON t.mensajero_id = s.mensajero_id
      WHERE  s.stor_id = @storeid
   EXEC SalesByStore_core @storeid
   SELECT * FROM mytemp 
   delete from mytemp where clienteid = @storeid 
go
EXEC CalculaCosto '7131'
go
DROP PROCEDURE CalculaCosto, CalculaCostoPropiedad

Evidentemente esto es un ejemplo muy simple, mis SP eran mucho mas grandes y complejos, Pero se ve claramente el pequeño cambio en los SP para solucionar el problema. Esper que estas líneas te hayan servido de ayuda, se que no soy un buen escritor, asi que si no entiendes algo, comenta para que te pueda resolver la duda. Como siempre os dejo algunos enlaces de interés, de donde mezclando de aqui y de allí más algo de mi cosecha es la solución que os propongo.

Happy codding

#SQL #errores

Actualizacion 23 de Enero, por aporte de Thirone Bermudez

No es necesario crear la tabla en la base de datos, ya que se puede hacer perfectametne con una tabla temporal de sesion. De hecho, en mi codigo, mejoro un poco ya que no necesito estar borrando las lineas de la tabla. Aqui os pongo el codigo con los cambios propuestos.

CREATE PROCEDURE CalculaCostoPropiedad @storeid varchar(30) AS
   INSERT INTO #mytemp (costo, qty)
      SELECT t.costo, s.qty
      FROM   mensajeros s
      JOIN   costos t ON t.mensajero_id = s.mensajero_id
      WHERE  s.stor_id = @storeid
go
CREATE PROCEDURE CalculaCosto @storeid varchar(30) AS
CREATE TABLE #mytemp (costo int, qty int)
   INSERT INTO #mytemp (costo, qty)
      SELECT t.costo, s.qty
      FROM   mensajeros s
      JOIN   costos t ON t.mensajero_id = s.mensajero_id
      WHERE  s.stor_id = @storeid
   EXEC SalesByStore_core @storeid
   SELECT * FROM #mytemp 
   DROP TABLE #mytemp 
go
EXEC CalculaCosto '7131'
go
DROP PROCEDURE CalculaCosto, CalculaCostoPropiedad

De esta manera si se ejecuta el procedimiento varias veces la tabla es diferente en cada sesion, y aunque al finalizar la sesion la tabla se destruye, es mejor hacer el CROP TABLE #tabla para asi liberar el objeto correctamente despues de usarlo, me paso en mis pruebas, que en la misma venta de codigo si no hacia el DROP TABLE me reconocia como si fuera la misma sesion de trabajo y la tabla no me la dejaba crear de nuevo ya que ya estaba creada.

1 Comentarios

    • Thirone Bermúdez
      miércoles, 23 de enero de 2019 3:44

      Hay otra forma, para evitar crear la tabla en BD, resulta mejor crear una tabla temporal de sesión y ésta es la que se irá llenando de registros a través de los SP. Tal vez no te resulte tan rápido como quieres (que en este caso creo que no hay mucha diferencia), pero además de resolver el problema también te evita crear N tablas físicas por N cantidad de consultas que tengas con el mismo problema, no sé si me explico.

 
 
 

Archivo