web-dev-qa-db-fra.com

SQL - Appelez la procédure stockée pour chaque enregistrement

Je cherche un moyen d'appeler une procédure stockée pour chaque enregistrement d'une instruction select.

SELECT @SomeIds = (
    SELECT spro.Id 
    FROM SomeTable as spro
    INNER JOIN [Address] addr ON addr.Id = spro.Id 
    INNER JOIN City cty ON cty.CityId = addr.CityId
    WHERE cty.CityId = @CityId
)


WHILE @SomeIds  IS NOT NULL
BEGIN
    EXEC UpdateComputedFullText @SomeIds
END

Une telle chose ne fonctionne pas, bien sûr, mais existe-t-il un moyen de faire quelque chose comme ça?

43
Chris

Vous devez utiliser un curseur pour cela.

DECLARE @oneid int -- or the appropriate type

DECLARE the_cursor CURSOR FAST_FORWARD
FOR SELECT spro.Id  
    FROM SomeTable as spro 
        INNER JOIN [Address] addr ON addr.Id = spro.Id  
        INNER JOIN City cty ON cty.CityId = addr.CityId 
    WHERE cty.CityId = @CityId

OPEN the_cursor
FETCH NEXT FROM the_cursor INTO @oneid

WHILE @@FETCH_STATUS = 0
BEGIN
    EXEC UpdateComputedFullText @oneid

    FETCH NEXT FROM the_cursor INTO @oneid
END

CLOSE the_cursor
DEALLOCATE the_cursor
65
treaschf

Surpris, personne ne vous a donné une réponse à jour. Les curseurs sont mauvais. Ce que vous voulez, c'est déplacer la logique du SP dans une fonction TVF ( table-values-function) puis utiliser CROSS APPLY

Voici une requête que j'ai écrite hier (ne vous attardez pas sur les détails, regardez simplement le CROSS APPLY). Le CROSS APPLY crée une union de tables. Chaque élément de cette union est généré à partir de la TVF qui est paramétrée dans les entrées de ligne de l'instruction select. 

SELECT supt.hostname,supt.scriptname, COUNT(*)
FROM Event_Pagehit eph
    INNER JOIN Symboltable_urlpair supf
    ON eph.fromPagePair=supf.id
    INNER JOIN Symboltable_urlpair supt
    ON supt.id=eph.toPagePair
CROSS APPLY dbo.TDFCompanyFormationsUrlClassification(supf.hostname,supf.scriptname) as x
CROSS APPLY dbo.TDFCompanyFormationsUrlClassification(supt.hostname,supt.scriptname) as y
WHERE x.isCompanyFormations=1
AND y.isCompanyFormations=0
GROUP BY supt.hostname,supt.scriptname
ORDER BY COUNT(*) desc

Je peux utiliser x et y comme s’il s’agissait de tables extraites des clauses FROM ou JOIN. Si je devais écrire cette requête sans TVF, cela couvrirait quelques centaines de lignes. 

Remarque: 

Si vous ne pouvez pas réécrire le SP, vous devriez pouvoir insérer le résultat d'une procédure stockée dans la table de résultats à partir d'une fonction table. Je n'ai jamais fait cela, et parfois la construction différente du serveur SQL a des mises en garde - Donc, à moins que quelqu'un ne dit le contraire, je suppose que c'est le cas.

21
Hassan Syed

Placez les ID dans une variable de table temporaire, puis parcourez chaque ligne: (vous n'avez pas besoin d'utiliser un curseur qui sera beaucoup plus lent)

   Declare @Keys Table (key integer Primary Key Not Null)
   Insert @Keys(key)
   SELECT spro.Id  
   FROM SomeTable as spro 
       JOIN [Address] addr ON addr.Id = spro.Id  
       JOIN City cty ON cty.CityId = addr.CityId 
   WHERE cty.CityId = @CityId
   -- -------------------------------------------
   Declare @Key Integer
   While Exists (Select * From @Keys)
     Begin
         Select @Key = Max(Key) From @Keys
         EXEC UpdateComputedFullText @Key
         Delete @Keys Where Key = @Key
     End 

EDIT Delete n'est pas lent lorsqu'il est utilisé avec un prédicat de filtre piloté par un index unique très étroit, comme c'est le cas. Mais cela peut facilement être évité, simplement en faisant la boucle comme suit:

Declare @Key Integer = 0
While Exists (Select * From @Keys
              Where key > @Key)
 Begin
     Select @Key = Min(Key) From @Keys
                   Where key > @Key
     EXEC UpdateComputedFullText @Key
     -- Delete @Keys Where Key = @Key No Longer necessary 
 End    
18
Charles Bretana

Essayez celui-ci sans curseur

DECLARE @id int 

SELECT top 1 @id = spro.Id   
    FROM SomeTable as spro  
        INNER JOIN [Address] addr ON addr.Id = spro.Id   
        INNER JOIN City cty ON cty.CityId = addr.CityId  
    WHERE cty.CityId = @CityId
    ORDER BY spro.id

WHILE @@ROWCOUNT > 0 
BEGIN 
    EXEC UpdateComputedFullText @id 

    SELECT top 1 @id = spro.Id   
    FROM SomeTable as spro  
        INNER JOIN [Address] addr ON addr.Id = spro.Id   
        INNER JOIN City cty ON cty.CityId = addr.CityId  
    WHERE cty.CityId = @CityId 
    and spro.id > @id
    ORDER BY spro.id
END 
4
Jose Chama

Les deux réponses ci-dessus les curseurs RE sont correctes. Toutefois, en fonction de la complexité du code exécuté à l'intérieur du curseur, il serait peut-être préférable de le déposer dans la langue de votre choix et d'effectuer vos calculs en code avant de transférer les résultats dans une base de données.

Je me suis retrouvé à revenir en arrière et à passer en revue de nombreuses opérations de curseur et, dans de nombreux cas, à les convertir en code pour des raisons de performances.

2
Bob Palmer

Vous devrez utiliser un curseur: Exemples de curseurs SQL Server

DECLARE @id int
DECLARE cursor_sample CURSOR FOR  
SELECT spro.Id 
FROM SomeTable as spro
    INNER JOIN [Address] addr ON addr.Id = spro.Id 
    INNER JOIN City cty ON cty.CityId = addr.CityId
WHERE cty.CityId = @CityId

OPEN cursor_sample
FETCH NEXT FROM cursor_sample INTO @id 
WHILE @@FETCH_STATUS = 0   
BEGIN  
    EXEC UpdateComputedFullText @id
    FETCH NEXT FROM cursor_sample INTO @id
END   

CLOSE cursor_sample
DEALLOCATE cursor_sample
1
Rubens Farias

Avez-vous vraiment besoin d'effectuer un traitement ligne par ligne lorsque le traitement des ensembles est disponible? 

Vous pouvez placer les résultats de SELECT dans une table temporaire, puis appeler un proc pour exécuter du SQL en bloc sur le contenu de la table temporaire. La table temporaire sera disponible pour le processus appelé en fonction des règles de portée T-SQL.

0
bretlowery

La solution de curseur standard est un mal pour un mal ... Deux déclarations identiques de FETCH NEXT ne sont qu'un cauchemar de maintenance.

mieux c'est

...declare cursor etc.
While 1=1
 Fetch ...
 if @@FETCH_STATUS <> 0  BREAK
...
End -- While 
..Close cursor etc.

Un mal parfois justifié. Essayez simplement de concevoir une approche basée sur un ensemble pour envoyer des courriels de notification en utilisant sp_send_dbmail ou une autre procédure stockée.

0
user3326308