Come eseguire una procedura memorizzata una volta per ogni riga restituita dalla query?


206

Ho una procedura memorizzata che modifica i dati dell'utente in un certo modo. Lo passo user_id e lo fa. Voglio eseguire una query su una tabella e quindi per ogni user_id trovo eseguire la procedura memorizzata una volta su tale user_id

Come scriverei una query per questo?


5
Devi specificare quale RDBMS - la risposta sarà diversa per SQL Server, Oracle, MySql, ecc.
Gary.Ray

5
È probabile che non sia necessaria alcuna procedura memorizzata. Puoi delineare "cosa" fa esattamente la procedura memorizzata? Forse l'intero processo può essere espresso come una singola istruzione di aggiornamento. Il modello "fai una volta per ogni record" dovrebbe essere generalmente evitato, se possibile.
Tomalak,

Quale database stai usando?
Utente SO

1
Dovresti leggere questo articolo ... l'articolo 2 dice NON usare i cursori codeproject.com/KB/database/sqldodont.aspx...mind Sono anche contrario all'ottimizzazione prematura.
Michael Prewecki,

7
@MichaelPrewecki: Se leggi più avanti in quell'articolo scritto male, vedrai che l'articolo 10 è "NON usare i cursori sul lato server se non sai cosa stai facendo." Penso che questo sia un caso di "So cosa sto facendo".
Gabe,

Risposte:


246

usa un cursore

ADDENDUM: [esempio cursore MS SQL]

declare @field1 int
declare @field2 int
declare cur CURSOR LOCAL for
    select field1, field2 from sometable where someotherfield is null

open cur

fetch next from cur into @field1, @field2

while @@FETCH_STATUS = 0 BEGIN

    --execute your sproc on each row
    exec uspYourSproc @field1, @field2

    fetch next from cur into @field1, @field2
END

close cur
deallocate cur

in MS SQL, ecco un articolo di esempio

si noti che i cursori sono più lenti delle operazioni basate su set, ma più veloci dei cicli while manuali; maggiori dettagli in questa domanda SO

ADDENDUM 2: se elaborerai più di pochi record, prima inseriscili in una tabella temporanea ed esegui il cursore sulla tabella temporanea; questo eviterà che l'SQL si trasformi in blocchi di tabelle e accelererà le operazioni

ADDENDUM 3: e, naturalmente, se è possibile integrare qualsiasi cosa la procedura memorizzata stia facendo per ciascun ID utente ed eseguire il tutto come una singola istruzione di aggiornamento SQL, sarebbe ottimale


21
Ti sei perso "cur aperta" dopo la dichiarazione - questo mi stava dando errori "il cursore non è aperto". Non ho il rappresentante per fare una modifica.
Fiona - myaccessible.website

5
Puoi ringraziare le persone votando per favore il loro commento. Chissà, forse in questo modo avranno il rappresentante per fare la modifica, la prossima volta! :-)
Robino,

Assicurarsi di controllare gli indici nelle clausole JOINS e WHERE nei campi utilizzati nella procedura memorizzata. Ho accelerato notevolmente chiamando il mio SP in loop dopo aver aggiunto gli indici appropriati.
Matteo,

1
Grazie per il promemoria dell'utilizzo della tabella temporanea per evitare potenziali problemi di blocco causati da una lunga esecuzione.
Tony,

A volte la procedura memorizzata è troppo grande o complicata per essere integrata senza rischiare di introdurre bug. Laddove le prestazioni non sono la priorità assoluta, l'esecuzione dell'SP in un loop del cursore è spesso la scelta più pratica.
Suncat2000,

55

prova a cambiare metodo se hai bisogno di fare il ciclo!

all'interno della stored procedure principale, creare una tabella #temp che contenga i dati che è necessario elaborare. Chiamare la stored procedure figlio, la tabella #temp sarà visibile e sarà possibile elaborarla, si spera che funzioni con l'intero set di dati e senza cursore o loop.

questo dipende davvero da cosa sta facendo questa procedura memorizzata figlio. Se si esegue l'AGGIORNAMENTO, è possibile "aggiornare da" unendosi nella tabella #temp ed eseguire tutto il lavoro in un'unica istruzione senza un ciclo. Lo stesso può essere fatto per INSERT e DELETE. Se è necessario eseguire più aggiornamenti con gli IF, è possibile convertirli in multipliUPDATE FROM con la tabella #temp e utilizzare le istruzioni CASE o le condizioni WHERE.

Quando si lavora in un database, provare a perdere la mentalità del looping, si tratta di un vero consumo di prestazioni, causerà il blocco / blocco e rallenterà l'elaborazione. Se esegui il loop ovunque, il tuo sistema non si ridimensionerà molto bene e sarà molto difficile accelerare quando gli utenti iniziano a lamentarsi di aggiornamenti lenti.

Pubblica il contenuto di questa procedura che desideri chiamare in un ciclo e scommetto 9 volte su 10, potresti scriverlo per lavorare su un set di righe.


3
+1 per una soluzione alternativa molto buona, supponendo che controlli lo sproc bambino
Steven A. Lowe,

un po 'di pensiero, questo sollution è di gran lunga superiore!
encc,

7
Le operazioni basate su set sono sempre preferibili. Tuttavia, tieni presente che la modifica di un SP non è sempre un'opzione: pensa alle soluzioni fornite dal fornitore. Alcuni utenti potrebbero anche non avere visibilità, lasciando solo le opzioni cursore o loop. Nel mio negozio, i nostri sviluppatori possono vedere tutto ma ci sono molti ostacoli da chiarire se una soluzione è costruita al di fuori dell'applicazione del fornitore a causa di trigger, processi annidati, numero di record manipolati ecc. Molte volte la loro migliore opzione, a causa di complessità dell'applicazione, è semplicemente di scorrere i record.
Steve Mangiameli,

11

Qualcosa di simile a queste sostituzioni sarà necessario per le tabelle e i nomi dei campi.

Declare @TableUsers Table (User_ID, MyRowCount Int Identity(1,1)
Declare @i Int, @MaxI Int, @UserID nVarchar(50)

Insert into @TableUser
Select User_ID
From Users 
Where (My Criteria)
Select @MaxI = @@RowCount, @i = 1

While @i <= @MaxI
Begin
Select @UserID = UserID from @TableUsers Where MyRowCount = @i
Exec prMyStoredProc @UserID
Select

 @i = @i + 1, @UserID = null
End

2
mentre i loop sono più lenti dei cursori
Steven A. Lowe,

Il costrutto o l'istruzione SQL del cursore Declare non è supportato (??)
MetaGuru

9

Puoi farlo con una query dinamica.

declare @cadena varchar(max) = ''
select @cadena = @cadena + 'exec spAPI ' + ltrim(id) + ';'
from sysobjects;
exec(@cadena);

6

Non è possibile farlo con una funzione definita dall'utente per replicare qualunque cosa stia facendo la procedura memorizzata?

SELECT udfMyFunction(user_id), someOtherField, etc FROM MyTable WHERE WhateverCondition

dove udfMyFunction è una funzione che esegui che accetta l'ID utente e fa tutto ciò di cui hai bisogno.

Vedi http://www.sqlteam.com/article/user-defined-functions per un po 'più di background

Concordo sul fatto che i cursori dovrebbero davvero essere evitati laddove possibile. E di solito è possibile!

(ovviamente, la mia risposta presuppone che tu sia interessato solo a ottenere l'output dal SP e che non stai modificando i dati effettivi. Trovo che "modifica i dati dell'utente in un certo modo" un po 'ambiguo rispetto alla domanda originale, quindi ho pensato di offrirlo come una possibile soluzione. Dipende totalmente da quello che stai facendo!)


1
OP: "stored procedure che modifica i dati dell'utente in un certo modo" MSDN : le funzioni definite dall'utente non possono essere utilizzate per eseguire azioni che modificano lo stato del database. Tuttavia, SQLSVR 2014 non sembra avere problemi con esso
johnny 5

6

Utilizzare una variabile di tabella o una tabella temporanea.

Come accennato in precedenza, un cursore è l'ultima risorsa. Soprattutto perché utilizza molte risorse, genera blocchi e potrebbe essere un segno che non stai capendo come usare correttamente SQL.

Nota a margine: una volta mi sono imbattuto in una soluzione che utilizzava i cursori per aggiornare le righe in una tabella. Dopo un po 'di controllo, si è scoperto che il tutto poteva essere sostituito con un singolo comando UPDATE. Tuttavia, in questo caso, dove deve essere eseguita una procedura memorizzata, un singolo comando SQL non funzionerà.

Crea una variabile di tabella come questa (se stai lavorando con molti dati o hai poca memoria, usa invece una tabella temporanea ):

DECLARE @menus AS TABLE (
    id INT IDENTITY(1,1),
    parent NVARCHAR(128),
    child NVARCHAR(128));

Il idè importante.

Sostituire parente childcon alcuni dati validi, ad esempio identificatori pertinenti o l'intero insieme di dati su cui operare.

Inserisci i dati nella tabella, ad es .:

INSERT INTO @menus (parent, child) 
  VALUES ('Some name',  'Child name');
...
INSERT INTO @menus (parent,child) 
  VALUES ('Some other name', 'Some other child name');

Dichiarare alcune variabili:

DECLARE @id INT = 1;
DECLARE @parentName NVARCHAR(128);
DECLARE @childName NVARCHAR(128);

E infine, crea un ciclo while sui dati nella tabella:

WHILE @id IS NOT NULL
BEGIN
    SELECT @parentName = parent,
           @childName = child 
        FROM @menus WHERE id = @id;

    EXEC myProcedure @parent=@parentName, @child=@childName;

    SELECT @id = MIN(id) FROM @menus WHERE id > @id;
END

La prima selezione recupera i dati dalla tabella temporanea. La seconda selezione aggiorna @id.MINrestituisce null se non sono state selezionate righe.

Un approccio alternativo è quello di eseguire il ciclo mentre la tabella ha righe SELECT TOP 1e rimuovere la riga selezionata dalla tabella temporanea:

WHILE EXISTS(SELECT 1 FROM @menuIDs) 
BEGIN
    SELECT TOP 1 @menuID = menuID FROM @menuIDs;

    EXEC myProcedure @menuID=@menuID;

    DELETE FROM @menuIDs WHERE menuID = @menuID;
END;

3

Mi piace il modo di query dinamico di Dave Rincon in quanto non utilizza i cursori ed è piccolo e facile. Grazie Dave per la condivisione.

Ma per le mie esigenze su Azure SQL e con un "distinto" nella query, ho dovuto modificare il codice in questo modo:

Declare @SQL nvarchar(max);
-- Set SQL Variable
-- Prepare exec command for each distinctive tenantid found in Machines 
SELECT @SQL = (Select distinct 'exec dbo.sp_S2_Laser_to_cache ' + 
              convert(varchar(8),tenantid) + ';' 
              from Dim_Machine
              where iscurrent = 1
              FOR XML PATH(''))

--for debugging print the sql 
print @SQL;

--execute the generated sql script
exec sp_executesql @SQL;

Spero che questo aiuti qualcuno ...

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.