2010-06-28 8 views
9

Hier ist, was ich als VBScript-Unterroutine haben:Ist eine rekursiv aufgerufene gespeicherte Prozedur in SQL Server möglich?

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 

Gibt es trotzdem, dies in eine gespeicherte Prozedur zu drehen, da sie den rekursiven Aufruf in der Unterroutine hat?

Hier ist, was ich versucht habe ...

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 

Aber ich folgende Fehlermeldung erhalten, wenn die gespeicherte Prozedur ... Ein Cursor mit dem gleichen Namen ‚TableCursor‘ existiert bereits genannt wird.

+3

Ich vermute, dass Ihr Fehler auftritt, weil der rekursive Aufruf erfolgt, bevor der Cursor "TableCursor" geschlossen ist. Wäre es möglich, dem Cursor einen dynamischen Namen zu geben (vielleicht 'TableCursorN', wobei N die Tiefe der Rekursion ist - müssten Sie das als zusätzlichen Parameter machen)? – FrustratedWithFormsDesigner

+2

Das Problem ist nicht Rekursion, die sicherlich erlaubt ist (http://msdn.microsoft.com/en-us/library/aa175801(SQL.80).aspx), es ist, dass Sie einen statischen Cursor-Namen verwenden. Ich weiß nicht genug über Cursor in MS SQL-Servern, um dies als Antwort zu posten, da es * hätte * zu sagen, wie man einen Cursor in dieser Situation benutzt, um nützlich zu sein! :-) –

Antwort

7

Das Problem ist, dass, während der Cursor nicht global ist, ein Sitzungscursor ist. Da Sie eine Rekursion durchführen, obwohl jede Iteration einen Cursor in einem neuen proc-Bereich erstellt, werden alle gleichzeitig in derselben PID (Verbindung) erstellt, also die Kollision.

Sie müssen eindeutige Cursor-Namen in jeder Iteration der Prozedur basierend auf einigen Kriterien generieren, die während der Rekursion nicht reproduziert werden.

Oder, vorzugsweise, finden Sie einen Weg, um zu tun, was Sie brauchen, Set-Logik, und behandeln Sie alle notwendigen Rekursion mit einem rekursiven CTE.

+3

Was ist der beste Weg, um eindeutige Cursornamen zu generieren? Ich bin nicht gut mit dynamischen SQL. – Ryan

2

Sie können, aber es ist in der Regel keine gute Idee. SQL wird für satzbasierte Operationen erstellt. Auch in MS SQL Server ist die Rekursion auf die Anzahl der rekursiven Aufrufe beschränkt, die sie ausführen kann. Sie können nur bis zu 32 Ebenen tief verschachteln.

Das Problem in Ihrem Fall ist, dass der CURSOR durch jeden Anruf dauert, so dass Sie am Ende es mehr als einmal erstellen.

34

können Sie geben einen LOCAL Cursor, wie folgt aus:

DECLARE TableCursor CURSOR LOCAL FOR 
SELECT ... 

Mindestens in SQL Server 2008 R2 (meine Maschine), damit können Sie rekursiv die sproc aufrufen, ohne in "Cursor ist bereits vorhanden" Fehler .

+2

Dies löste mein Problem dank viel Blorgbeard – Javier

+4

Ich kann bestätigen, dass dies auch für 2005 funktioniert. http://msdn.microsoft.com/en-us/library/ms180169(v=sql.90).aspx Diese Antwort sollte wirklich als richtig markiert werden. –

+1

Bestätigt erneut –