2010-06-28 13 views
9

Ecco quello che ho come VBScript sottoprogrammi:È possibile che una stored procedure venga chiamata in modo ricorsivo in 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 

Esiste un modo per trasformare questo in una stored procedure dal momento che è ricevuto la chiamata ricorsiva nella subroutine?

Ecco che cosa ho provato ...

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 

ma ottengo il seguente errore quando che stored procedure viene chiamata ... Un cursore con lo stesso nome 'TableCursor' esiste già.

+3

Suppongo che l'errore si verifichi perché la chiamata ricorsiva viene eseguita prima che il cursore "TableCursor" sia chiuso. Sarebbe possibile dare al cursore un nome dinamico (forse 'TableCursorN', dove N è la profondità della ricorsione - dovresti farlo come parametro extra)? – FrustratedWithFormsDesigner

+2

Il problema non è la ricorsione, che è certamente consentita (http://msdn.microsoft.com/en-us/library/aa175801(SQL.80).aspx), è che stai usando un nome di cursore statico. Non ne so abbastanza sui cursori in MS SQL Server per postare questo come una risposta, però, dal momento che * dovrebbe * dire come utilizzare un cursore in questa situazione per essere utile! :-) –

risposta

7

Il problema è che mentre il cursore non è globale, è un cursore di sessione. Dato che stai facendo la ricorsione, anche se ogni iterazione sta creando un cursore in un nuovo ambito proc, vengono tutti creati nello stesso PID (connessione) allo stesso tempo, quindi la collisione.

È necessario generare nomi di cursore univoci in ogni iterazione della procedura in base a determinati criteri che non verranno riprodotti durante la ricorsione.

Oppure, preferibilmente, trovare un modo per eseguire ciò che è necessario utilizzando la logica impostata e gestire qualsiasi ricorsione necessaria utilizzando un CTE ricorsivo.

+3

Qual è il modo migliore per generare nomi di cursore univoci? Non sono troppo bravo con SQL dinamico. – Ryan

2

È possibile, ma di solito non è una buona idea. SQL è realizzato per operazioni basate su set. Inoltre, in MS SQL Server almeno, la ricorsione è limitata al numero di chiamate ricorsive che può effettuare. Puoi solo annidare fino a 32 livelli in profondità.

Il problema nel tuo caso è che il CURSORE dura attraverso ogni chiamata, quindi si finisce per crearlo più di una volta.

34

È possibile specificare un cursore LOCAL, in questo modo:

DECLARE TableCursor CURSOR LOCAL FOR 
SELECT ... 

Almeno in SQL Server 2008 R2 (la mia macchina), questo consente di chiamare in modo ricorsivo lo sproc senza incorrere in "Cursore esiste già" errori .

+2

Questo ha risolto il mio problema grazie mille Blorgbeard – Javier

+4

Posso confermare che funziona anche per il 2005. http://msdn.microsoft.com/en-us/library/ms180169(v=sql.90).aspx Questa risposta dovrebbe essere contrassegnata come corretta in realtà. –

+1

Confermato di nuovo –

Problemi correlati