2010-01-16 7 views
35

Je cherche un moyen d'appeler une procédure stockée pour chaque enregistrement d'une instruction select. Une telle chose ci-dessus ne fonctionne pas bien sûr, mais y a-t-il un moyen de faire quelque chose comme ça?SQL - Procédure stockée d'appel pour chaque enregistrement

+1

Tout le monde vous supplie d'éviter l'itération (curseurs ou while). Que fait le sproc, donc nous pouvons définir une solution basée sur un ensemble? – Precipitous

+0

Je n'ai pas supprimé mon approche basée sur les ensembles - j'étais pressé auparavant et je ne voulais pas continuer sans une 'note' à la fin. –

Répondre

59

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 
+3

A l'OP, notez que les curseurs sont maléfiques et vous devriez les éviter à tout prix, mais je leur donne encore +1 car leur utilisation peut être justifiée dans certains cas comme celui-ci. Espérons que ceci est une chose ponctuelle et que vous n'allez pas placer ce curseur dans un autre proc :) –

+1

(-1) Les curseurs sont HORRIBLE surtout depuis les nouvelles versions de SQL Server. Cette syntaxe basée sur Set et Set est beaucoup plus élégante et succincte - sans parler des ordres sur des ordres de grandeur plus rapides. –

+22

Les curseurs ne sont pas mauvais - c'est simpliste. Si possible, réécrivez des solutions itératives (curseurs ou en boucle) en utilisant une solution basée sur un ensemble. Sans informations supplémentaires, vous devez itérer dans ce cas et un curseur est bon. – Precipitous

1

Vous aurez besoin d'utiliser un curseur: SQL Server Cursor Examples

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 
14

Mettez le Ids dans une variable de table temporaire, puis itérer throught chaque ligne: (Vous n'êtes pas obligé 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 Supprimer ne tarde pas lorsqu'il est utilisé avec un prédicat de filtre dirigé contre un index unique très étroit, comme c'est le cas. Mais il peut facilement être évité, juste en faisant 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  
+0

Ceci est une approche intéressante. :) – treaschf

+1

ok, les curseurs sont lents, mais est-ce plus rapide? –

+0

J'aime ça. Les curseurs sont mauvais. :) –

2

Les deux réponses ci-dessus RE curseurs sont corrects. Cependant, en fonction de la complexité du code qui s'exécute à l'intérieur du curseur, il est préférable de laisser tomber ceci dans la langue de votre choix et d'effectuer vos calculs dans le code avant de déposer les résultats dans une base de données.

Je me suis retrouvé à revenir en arrière et à passer en revue un grand nombre d'opérations de curseur, et dans la plupart des cas, je les ai fait passer au code pour des raisons de performances.

2

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 
+0

Ceci est un dogme sans raison - lettre de la loi au lieu de l'esprit de la loi. La raison principale pour éviter les curseurs est qu'il existe presque toujours une solution basée sur un ensemble plus efficace. Cela évite les curseurs mais ne fournit pas de solution basée sur un ensemble. Bien sûr, nous ne savons pas qu'il existe une solution basée sur un ensemble à ce problème. – Precipitous

+0

(+1) J'ai été informé par mes collègues qui font du traitement de données à l'échelle du téraoctet en SQL qu'un 'WHILE' pour l'itération fonctionne assez bien - surtout quand il n'y a pas de choix - de nos jours cependant TVF + CROSS APPLY = king (voir ma réponse). –

0

-vous vraiment besoin de faire un traitement ligne par ligne lorsque le traitement est mis disponible?

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

18

Surpris personne ne vous a donné une réponse à jour. Les curseurs sont mauvais. Qu'est-ce que vous voulez est de déplacer la logique du SP dans un table-valued-function(TVF) and then use CROSS APPLY

Voici une requête que j'ai écrit hier (ne pas s'attarder sur les détails, il suffit de regarder le CROSS APPLY). Le CROSS APPLY crée une union de tables. Chaque élément de cette union est généré à partir du TVF qui est paramétré sur 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 si elles étaient tirés dans les tables des clauses FROM ou JOIN. Si je devais écrire cette requête sans TVF, cela couvrirait quelques centaines de lignes.

Note:

Si vous ne pouvez pas réécrire la SP: vous devriez être en mesure d'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 les constructeurs de serveurs SQL différents ont des réserves - Donc, à moins que quelqu'un dise le contraire, je suppose que c'est le cas.

+0

(+1) Je suis toujours ouvert aux nouvelles solutions, surtout si elles sont définies base. –

+0

Question sur ce produit? Est-ce que le 'CROSS APPLY' se produit avant ou après la clause where? Je sais où le mettre dans la déclaration, mais est-ce qu'il * exécute * en utilisant les éléments pré-filtrés ou post-filtrés du 'WHERE' en dessous? – SnareChops

+0

Comment cela s'applique-t-il si vous utilisez un proc qui ne retourne pas un ensemble de résultats? Supposons que vous fournissiez une liste d'identifiants utilisateur dans un proc appelé deleteUser (comme asp.net dans le cadre d'adhésion), vous êtes presque bloqué avec un curseur ou une instruction while, non? – Andrew

0

La solution de curseur standard est le mal sur le mal. Deux instructions FETCH NEXT identiques ne sont qu'un cauchemar de maintenance.

mieux 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 l'envoi d'e-mails de notification à l'aide de sp_send_dbmail ou d'une autre procédure stockée.

Questions connexes