wake-up-neo.com

Gespeicherte SQL-Aufrufprozedur für jede Zeile ohne Verwendung eines Cursors

Wie kann eine gespeicherte Prozedur für jede Zeile in einer Tabelle aufgerufen werden, wobei die Spalten einer Zeile Eingabeparameter für das spsind, ohne dasseinen Cursor verwendet

137

Im Allgemeinen suche ich immer nach einem satzbasierten Ansatz (manchmal auf Kosten des Schemas ändern). 

Dieses Snippet hat jedoch seinen Platz.

-- Declare & init (2008 syntax)
DECLARE @CustomerID INT = 0

-- Iterate over all customers
WHILE (1 = 1) 
BEGIN  

  -- Get next customerId
  SELECT TOP 1 @CustomerID = CustomerID
  FROM Sales.Customer
  WHERE CustomerID > @CustomerId 
  ORDER BY CustomerID

  -- Exit loop if no more customers
  IF @@ROWCOUNT = 0 BREAK;

  -- call your sproc
  EXEC dbo.YOURSPROC @CustomerId

END
176
Mark Powell

Sie könnten so etwas tun: Bestellen Sie Ihren Tisch z. CustomerID (mit der AdventureWorks Sales.Customer-Beispieltabelle) und diese Kunden mithilfe einer WHILE-Schleife durchlaufen:

-- define the last customer ID handled
DECLARE @LastCustomerID INT
SET @LastCustomerID = 0

-- define the customer ID to be handled now
DECLARE @CustomerIDToHandle INT

-- select the next customer to handle    
SELECT TOP 1 @CustomerIDToHandle = CustomerID
FROM Sales.Customer
WHERE CustomerID > @LastCustomerID
ORDER BY CustomerID

-- as long as we have customers......    
WHILE @CustomerIDToHandle IS NOT NULL
BEGIN
    -- call your sproc

    -- set the last customer handled to the one we just handled
    SET @LastCustomerID = @CustomerIDToHandle
    SET @CustomerIDToHandle = NULL

    -- select the next customer to handle    
    SELECT TOP 1 @CustomerIDToHandle = CustomerID
    FROM Sales.Customer
    WHERE CustomerID > @LastCustomerID
    ORDER BY CustomerID
END

Das sollte mit jeder Tabelle funktionieren, solange Sie eine Art von ORDER BY in einer Spalte definieren können.

37
marc_s
DECLARE @SQL varchar(max)=''

-- MyTable has fields fld1 & fld2

Select @SQL = @SQL + 'exec myproc ' + convert(varchar(10),fld1) + ',' 
                   + convert(varchar(10),fld2) + ';'
From MyTable

EXEC (@SQL)

Ok, ich würde diesen Code niemals in die Produktion bringen, aber er erfüllt Ihre Anforderungen. 

23
Thomas Gabriel

Marc's Antwort ist gut (ich würde es kommentieren, wenn ich herausfinden könnte, wie es geht!)
Ich dachte nur, ich würde darauf hinweisen, dass es vielleicht besser ist, die Schleife so zu ändern, dass der SELECT nur einmal existiert (in einem echten Fall, in dem ich dies tun musste, war der SELECT ziemlich komplex, und das zweimalige Schreiben war riskant Wartungsproblem).

-- define the last customer ID handled
DECLARE @LastCustomerID INT
SET @LastCustomerID = 0
-- define the customer ID to be handled now
DECLARE @CustomerIDToHandle INT
SET @CustomerIDToHandle = 1

-- as long as we have customers......    
WHILE @LastCustomerID <> @CustomerIDToHandle
BEGIN  
  SET @LastCustomerId = @CustomerIDToHandle
  -- select the next customer to handle    
  SELECT TOP 1 @CustomerIDToHandle = CustomerID
  FROM Sales.Customer
  WHERE CustomerID > @LastCustomerId 
  ORDER BY CustomerID

  IF @CustomerIDToHandle <> @LastCustomerID
  BEGIN
      -- call your sproc
  END

END
9
Maxxx

Wenn Sie aus der gespeicherten Prozedur eine Funktion erstellen können, die eine Tabelle zurückgibt, können Sie Cross-Apply verwenden.

Angenommen, Sie haben eine Tabelle mit Kunden, und Sie möchten die Summe ihrer Bestellungen berechnen. Sie würden eine Funktion erstellen, die eine CustomerID verwendet und die Summe zurückgegeben hat.

Und du könntest das tun:

SELECT CustomerID, CustomerSum.Total

FROM Customers
CROSS APPLY ufn_ComputeCustomerTotal(Customers.CustomerID) AS CustomerSum

Wo würde die Funktion aussehen:

CREATE FUNCTION ComputeCustomerTotal
(
    @CustomerID INT
)
RETURNS TABLE
AS
RETURN
(
    SELECT SUM(CustomerOrder.Amount) AS Total FROM CustomerOrder WHERE CustomerID = @CustomerID
)

Offensichtlich könnte das obige Beispiel ohne eine benutzerdefinierte Funktion in einer einzelnen Abfrage ausgeführt werden.

Der Nachteil ist, dass Funktionen sehr eingeschränkt sind - viele Funktionen einer gespeicherten Prozedur sind in einer benutzerdefinierten Funktion nicht verfügbar, und das Konvertieren einer gespeicherten Prozedur in eine Funktion funktioniert nicht immer.

7
David Griffiths

Ab SQL Server 2005 können Sie dies mit CROSS APPLY und einer Tabellenwertfunktion tun.

Ich beziehe mich nur auf die Fälle, in denen die gespeicherte Prozedur in eine Tabellenwertfunktion konvertiert werden kann.

6
Mitch Wheat

Ich würde die akzeptierte Antwort verwenden, aber eine andere Möglichkeit besteht darin, eine Tabellenvariable zu verwenden, um eine nummerierte Menge von Werten zu speichern (in diesem Fall nur das ID-Feld einer Tabelle) und diese durch Zeilennummern mit einem JOIN zur Tabelle durchlaufen Rufen Sie alles ab, was Sie für die Aktion innerhalb der Schleife benötigen.

DECLARE @RowCnt int; SET @RowCnt = 0 -- Loop Counter

-- Use a table variable to hold numbered rows containg MyTable's ID values
DECLARE @tblLoop TABLE (RowNum int IDENTITY (1, 1) Primary key NOT NULL,
     ID INT )
INSERT INTO @tblLoop (ID)  SELECT ID FROM MyTable

  -- Vars to use within the loop
  DECLARE @Code NVarChar(10); DECLARE @Name NVarChar(100);

WHILE @RowCnt < (SELECT COUNT(RowNum) FROM @tblLoop)
BEGIN
    SET @RowCnt = @RowCnt + 1
    -- Do what you want here with the data stored in tblLoop for the given RowNum
    SELECT @Code=Code, @Name=LongName
      FROM MyTable INNER JOIN @tblLoop tL on MyTable.ID=tL.ID
      WHERE [email protected]
    PRINT Convert(NVarChar(10),@RowCnt) +' '+ @Code +' '+ @Name
END
4
AjV Jsy

Dies ist eine Variation der oben genannten Lösung von n3rds. Es ist keine Sortierung mit ORDER BY erforderlich, da MIN () verwendet wird.

Denken Sie daran, dass CustomerID (oder eine andere numerische Spalte, die Sie für den Fortschritt verwenden) eine eindeutige Einschränkung haben muss. Um es so schnell wie möglich zu machen, muss CustomerID indiziert werden. 

-- Declare & init
DECLARE @CustomerID INT = (SELECT MIN(CustomerID) FROM Sales.Customer); -- First ID
DECLARE @Data1 VARCHAR(200);
DECLARE @Data2 VARCHAR(200);

-- Iterate over all customers
WHILE @CustomerID IS NOT NULL
BEGIN  

  -- Get data based on ID
  SELECT @Data1 = Data1, @Data2 = Data2
    FROM Sales.Customer
    WHERE [ID] = @CustomerID ;

  -- call your sproc
  EXEC dbo.YOURSPROC @Data1, @Data2

  -- Get next customerId
  SELECT @CustomerID = MIN(CustomerID)
    FROM Sales.Customer
    WHERE CustomerID > @CustomerId 

END

Ich verwende diesen Ansatz bei einigen Varchars, die ich durchsehen muss, indem ich sie zuerst in eine temporäre Tabelle einsetze, um ihnen eine ID zu geben.

3
beruic

Wenn Sie nicht wissen, was Sie mit einem Cursor machen sollen, müssen Sie ihn extern ausführen (holen Sie sich die Tabelle und führen Sie dann jede Anweisung aus und rufen Sie jedes Mal sp auf.). aber nur außerhalb von SQL. Warum benutzt du keinen Cursor?

2
Dani

DELIMITER //

CREATE PROCEDURE setFakeUsers (OUT output VARCHAR(100))
BEGIN

    -- define the last customer ID handled
    DECLARE LastGameID INT;
    DECLARE CurrentGameID INT;
    DECLARE userID INT;

    SET @LastGameID = 0; 

    -- define the customer ID to be handled now

    SET @userID = 0;

    -- select the next game to handle    
    SELECT @CurrentGameID = id
    FROM online_games
    WHERE id > LastGameID
    ORDER BY id LIMIT 0,1;

    -- as long as we have customers......    
    WHILE (@CurrentGameID IS NOT NULL) 
    DO
        -- call your sproc

        -- set the last customer handled to the one we just handled
        SET @LastGameID = @CurrentGameID;
        SET @CurrentGameID = NULL;

        -- select the random bot
        SELECT @userID = userID
        FROM users
        WHERE FIND_IN_SET('bot',baseInfo)
        ORDER BY Rand() LIMIT 0,1;

        -- update the game
        UPDATE online_games SET userID = @userID WHERE id = @CurrentGameID;

        -- select the next game to handle    
        SELECT @CurrentGameID = id
         FROM online_games
         WHERE id > LastGameID
         ORDER BY id LIMIT 0,1;
    END WHILE;
    SET output = "done";
END;//

CALL setFakeUsers(@status);
SELECT @status;
1
radixxko

Ich mache es normalerweise so, wenn es einige Reihen gibt:

  1. Wählen Sie mit SQL Management Studio alle Sproc-Parameter in einer Datenmenge aus
  2. Rechtsklick -> Kopieren
  3. Einfügen in Excel
  4. Erstellen Sie einzeilige SQL-Anweisungen mit einer Formel wie '= "EXEC schema.mysproc @ param =" & A2 "in einer neuen Excel-Spalte. (Wobei A2 Ihre Excel-Spalte ist, die den Parameter enthält)
  5. Kopieren Sie die Liste der Excel-Anweisungen in eine neue Abfrage in SQL Management Studio und führen Sie sie aus.
  6. Erledigt.

(Bei größeren Datensätzen würde ich jedoch eine der oben genannten Lösungen verwenden).

1
Jonas Stensved

Ich hatte einen Produktionscode, der nur 20 Mitarbeiter gleichzeitig bearbeiten konnte. Nachfolgend finden Sie den Rahmen für den Code. Ich habe gerade den Produktionscode kopiert und das Zeug unten entfernt.

ALTER procedure GetEmployees
    @ClientId varchar(50)
as
begin
    declare @EEList table (employeeId varchar(50));
    declare @EE20 table (employeeId varchar(50));

    insert into @EEList select employeeId from Employee where (ClientId = @ClientId);

    -- Do 20 at a time
    while (select count(*) from @EEList) > 0
    BEGIN
      insert into @EE20 select top 20 employeeId from @EEList;

      -- Call sp here

      delete @EEList where employeeId in (select employeeId from @EE20)
      delete @EE20;
    END;

  RETURN
end
0
William Egge

Für den Fall, dass die Reihenfolge wichtig ist

--declare counter
DECLARE     @CurrentRowNum BIGINT = 0;
--Iterate over all rows in [DataTable]
WHILE (1 = 1)
    BEGIN
        --Get next row by number of row
        SELECT TOP 1 @CurrentRowNum = extendedData.RowNum
                    --here also you can store another values
                    --for following usage
                    [email protected] = extendedData.Value
        FROM    (
                    SELECT 
                        data.*
                        ,ROW_NUMBER() OVER(ORDER BY (SELECT 0)) RowNum
                    FROM [DataTable] data
                ) extendedData
        WHERE extendedData.RowNum > @CurrentRowNum
        ORDER BY extendedData.RowNum

        --Exit loop if no more rows
        IF @@ROWCOUNT = 0 BREAK;

        --call your sproc
        --EXEC dbo.YOURSPROC @MyVariable
    END
0
isxaker

Dies ist eine Variation der Antworten, die bereits bereitgestellt wurde, sollte aber besser sein, da ORDER BY, COUNT oder MIN/MAX nicht erforderlich sind. Der einzige Nachteil bei diesem Ansatz besteht darin, dass Sie eine temporäre Tabelle erstellen müssen, um alle IDs zu speichern.

Allerdings stimme ich @ Mark Powell zu, obwohl ein satzbasierter Ansatz im Allgemeinen noch besser sein sollte.

DECLARE @tmp table (Id INT IDENTITY(1,1) PRIMARY KEY NOT NULL, CustomerID INT NOT NULL)
DECLARE @CustomerId INT 
DECLARE @Id INT = 0

INSERT INTO @tmp SELECT CustomerId FROM Sales.Customer

WHILE (1=1)
BEGIN
    SELECT @CustomerId = CustomerId, @Id = Id
    FROM @tmp
    WHERE Id = @Id + 1

    IF @@rowcount = 0 BREAK;

    -- call your sproc
    EXEC dbo.YOURSPROC @CustomerId;
END
0
Adriaan de Beer

Eine bessere Lösung dafür ist zu 

  1. Code der gespeicherten Prozedur kopieren
  2. Verknüpfen Sie den Code mit der Tabelle, für die Sie ihn erneut ausführen möchten (für jede Zeile).

Dies war eine saubere, tabellenformatierte Ausgabe. Wenn Sie SP für jede Zeile ausführen, erhalten Sie für jede Iteration ein separates Abfrageergebnis, das hässlich ist.

0
Hammad Khan