2010-06-28 8 views
9

Aquí es lo que tengo como VBScript Subrutina:¿Es posible recurrir a un procedimiento almacenado recursivamente en SQL Server?

sub buildChildAdminStringHierarchical(byval pAdminID, byref adminString) 
    set rsx = conn.execute ("select admin_id from administrator_owners where admin_id not in (" & adminString & ") and owner_id = " & pAdminID) 

    do while not rsx.eof 
     adminString = adminString & "," & rsx(0) 
     call buildChildAdminStringHierarchical(rsx(0),adminString) 
     rsx.movenext 
    loop 
end sub 

¿Hay alguna forma de convertir esto en un procedimiento almacenado, ya que tiene la llamada recursiva en la subrutina?

Aquí es lo que he intentado ...

CREATE PROCEDURE usp_build_child_admin_string_hierarchically 
    @ID AS INT, 
    @ADMIN_STRING AS VARCHAR(8000), 
    @ID_STRING AS VARCHAR(8000) OUTPUT 
AS 
BEGIN 
    -- SET NOCOUNT ON added to prevent extra result sets from 
    -- interfering with SELECT statements. 
    SET NOCOUNT ON; 

    DECLARE @index int; 
    DECLARE @length int; 
    DECLARE @admin_id int; 
    DECLARE @new_string varchar(8000); 

    SET @index = 1; 
    SET @length = 0; 
    SET @new_string = @ADMIN_STRING; 

    CREATE TABLE #Temp (ID int) 

    WHILE @index <= LEN(@new_string) 
    BEGIN 
     IF CHARINDEX(',', @new_string, @index) = 0 
      SELECT @length = (LEN(@new_string) + 1) - @index; 
     ELSE 
      SELECT @length = (CHARINDEX(',', @new_string, @index) - @index); 
     SELECT @admin_id = CONVERT(INT,SUBSTRING(@new_string, @index, @length)); 
     SET @index = @index + @length + 1; 
     INSERT INTO #temp VALUES(@admin_id); 
    END 

    DECLARE TableCursor CURSOR FOR 
     SELECT Admin_ID FROM Administrator_Owners WHERE Admin_ID NOT IN (SELECT ID FROM #temp) AND Owner_ID = @ID; 

    OPEN TableCursor; 
    FETCH NEXT FROM TableCursor INTO @admin_id; 

    WHILE @@FETCH_STATUS = 0 
    BEGIN 
     IF LEN(@ID_STRING) > 0 
     SET @ID_STRING = @ID_STRING + ',' + CONVERT(VARCHAR, @admin_id); 
     ELSE 
     SET @ID_STRING = CONVERT(VARCHAR, @admin_id); 

     EXEC usp_build_child_admin_string_hierarchically @admin_id, @ID_STRING, @ID_STRING; 

     FETCH NEXT FROM TableCursor INTO @admin_id; 
    END 

    CLOSE TableCursor; 
    DEALLOCATE TableCursor; 

    DROP TABLE #temp; 
END 
GO 

pero me da el siguiente error al procedimiento almacenado que se llama ... Un cursor con el mismo nombre 'TableCursor' ya existe.

+3

Supongo que su error se produce porque la llamada recursiva se realiza antes de que el cursor 'TableCursor' esté cerrado. ¿Sería posible darle al cursor un nombre dinámico (quizás 'TableCursorN', donde N es la profundidad de la recursión? ¿Tendría que hacer eso como un parámetro extra)? – FrustratedWithFormsDesigner

+2

El problema no es la recursión, que sin duda está permitida (http://msdn.microsoft.com/en-us/library/aa175801(SQL.80).aspx), es que está utilizando un nombre de cursor estático. No sé lo suficiente sobre los cursores en los servidores MS SQL para publicar esto como una respuesta, sin embargo, ya que * tendría * que decir cómo usar un cursor en esta situación para ser útil. :-) –

Respuesta

7

El problema es que mientras el cursor no es global, es un cursor de sesión. Dado que está haciendo recursividad, aunque cada iteración está creando un cursor en un nuevo ámbito de proceso, todos se crean en el mismo PID (conexión) al mismo tiempo, por lo tanto, la colisión.

Tendrá que generar nombres de cursor únicos en cada iteración del procedimiento en función de algunos criterios que no se reproducirán durante la recursión.

O, preferiblemente, encuentre una manera de hacer lo que necesita usando la lógica de conjunto, y maneje cualquier recursión necesaria usando un CTE recursivo.

+3

¿Cuál es la mejor manera de generar nombres de cursor únicos? No soy muy bueno con SQL dinámico. – Ryan

2

Puede, pero generalmente no es una buena idea. SQL está hecho para operaciones basadas en conjuntos. Además, al menos en MS SQL Server, la recursión se limita a la cantidad de llamadas recursivas que puede realizar. Solo puedes anidar hasta 32 niveles de profundidad.

El problema en su caso es que el CURSOR dura cada llamada, por lo que termina creando más de una vez.

34

Puede especificar un cursor LOCAL, así:

DECLARE TableCursor CURSOR LOCAL FOR 
SELECT ... 

Al menos en SQL Server 2008 R2 (mi máquina), esto le permite llamar de forma recursiva el sproc sin caer en "Cursor ya existe" errores .

+2

Esto resolvió mi problema muchas gracias Blorgbeard – Javier

+4

Puedo confirmar que esto también funciona en 2005. http://msdn.microsoft.com/en-us/library/ms180169(v=sql.90).aspx Esta respuesta debe marcarse como correcta realmente. –

+1

Confirmado de nuevo –

Cuestiones relacionadas