Comment puis-je exécuter une procédure stockée une fois pour chaque ligne retournée par la requête?

j'ai une procédure stockée qui modifie les données de l'utilisateur dans une certaine manière. Je le passe à user_id et il fait son truc. Je veux lancer une requête sur une table et ensuite pour chaque user_id je trouve lancer la procédure stockée une fois sur ce user_id

Comment écrire une requête pour ça?

173
demandé sur HaveNoDisplayName 2009-05-20 09:30:31

7 réponses

utiliser un curseur

ADDENDUM: [MS SQL cursor example]

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

dans MS SQL, voici un exemple d'article

notez que les curseurs sont plus lents que les opérations basées sur des ensembles, mais plus rapides que les boucles manuelles; plus de détails dans cette question

ADDENDUM 2: Si vous traitez plus que quelques enregistrements, retirez - les dans une table de température tout d'abord, et d'exécuter le curseur sur la table de température; cela empêchera SQL de monter dans la table-serrures et d'accélérer l'opération

ADDITIF 3: et bien sûr, si vous pouvez inline quel que soit votre procédure stockée pour chaque ID d'utilisateur et exécuter l'ensemble comme une seule instruction SQL update, qui serait optimal

211
répondu Steven A. Lowe 2017-05-23 12:18:14

essayez de changer votre méthode si vous avez besoin de boucle!

dans la procédure parent stockée, créez une table #temp qui contient les données que vous devez traiter. Appelez la procédure stockée par l'enfant, la table # temp sera visible et vous pouvez la traiter, en travaillant avec l'ensemble des données et sans curseur ou boucle.

cela dépend vraiment de ce que cet enfant stocké procédure est en train de faire. Si vous êtes UPDATE-ing, vous pouvez "update from" En rejoignant la table #temp et faire tout le travail en une seule instruction sans boucle. Il en va de même pour les insertions et les suppressions. Si vous avez besoin de faire plusieurs mises à jour avec IFs, vous pouvez les convertir en plusieurs UPDATE FROM avec la table #temp et utiliser des déclarations de cas ou des conditions.

lorsque vous travaillez dans une base de données essayer de perdre la mentalité de boucle, il s'agit d'un véritable drain de performance, va provoquer le verrouillage/blocage et ralentir le traitement. Si vous bouclez partout, votre système ne sera pas très bien dimensionné, et sera très difficile à accélérer lorsque les utilisateurs commencent à se plaindre de rafraîchissements lents.

postez le contenu de cette procédure que vous voulez appeler dans une boucle, et je parie 9 sur 10 fois, vous pourriez l'écrire pour travailler sur un ensemble de lignes.

51
répondu KM. 2015-02-27 19:27:21

quelque chose comme ces substitutions seront nécessaires pour vos tables et noms de champs.

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
7
répondu u07ch 2009-05-20 05:42:46

est-ce que cela ne peut pas être fait avec une fonction définie par l'utilisateur pour répliquer ce que fait votre procédure stockée?

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

où udfMyFunction est une fonction que vous créez qui prend L'ID utilisateur et fait tout ce que vous avez besoin de faire avec.

Voir http://www.sqlteam.com/article/user-defined-functions pour un peu plus de fond

je suis d'accord que les curseurs devraient vraiment être évités dans la mesure du possible. Et c'est généralement possible!

(bien sûr, ma réponse présuppose que vous êtes seulement intéressé à obtenir la sortie du SP et que vous ne modifiez pas les données réelles. Je trouve" altère les données de l'utilisateur d'une certaine façon " un peu ambigu de la question originale, donc pensé que je voudrais offrir cette solution possible. Totalement dépend de ce que vous faites!)

5
répondu randomsequence 2010-03-24 17:11:21

Vous pouvez le faire avec une requête dynamique.

declare @cadena varchar(max) = ''
select @cadena = @cadena + 'exec spAPI ' + ltrim(id) + ';'
from sysobjects;
exec(@cadena);
5
répondu Dave Rincon 2017-02-08 16:51:34

j'aime la façon de requête dynamique de Dave Rincon car il n'utilise pas de curseurs et est petit et facile. Merci Dave pour le partage.

Mais pour mes besoins sur Azure SQL et avec un "distinct" dans la requête, j'ai dû modifier le code comme ceci:

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;

j'espère que cela aidera quelqu'un...

2
répondu Tom 2017-08-29 12:30:12

Utiliser une variable de table ou une table temporaire.

comme nous l'avons déjà mentionné, un curseur est un dernier recours. Principalement parce qu'il utilise beaucoup de ressources, problèmes serrures et pourrait être un signe que vous ne comprenez pas comment utiliser SQL correctement.

(note latérale: une fois je suis tombé sur une solution qui a utilisé des curseurs pour mettre à jour les lignes dans une table. Après un examen minutieux, il s'est avéré que l'ensemble pouvait être remplacé par une seule commande de mise à jour.)

cependant, dans ce cas, où une procédure stockée doit être exécutée, une seule commande SQL ne fonctionnera pas.

créer une variable de table comme ceci (si vous travaillez avec beaucoup de données ou êtes à court de mémoire, utilisez un table temporaire à la place):

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

le id est important.

remplacer parent et child par quelques bonnes données, p.ex. pertinentes. identificateurs ou l'ensemble des données à exploiter.

insérer les données dans le tableau, p.ex.:

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

déclarer certaines variables:

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

et enfin, créer une boucle while sur les données du tableau:

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

le premier sélectionne les données de la table temporaire. Le second select met à jour le @id. MIN retourne nul si aucune ligne n'a été sélectionnée.

une autre méthode consiste à effectuer une boucle alors que le tableau comporte des lignes SELECT TOP 1 et à supprimer la ligne choisie du tableau des températures.

Update : comme il s'avère, j'ai eu besoin d'une itération où je supprime aussi, donc voici:

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;
0
répondu Erk 2018-06-07 12:17:00