2009-10-19 10 views
58

Voici mon scénario:T-SQL: Looping par un tableau de valeurs connues

Disons que j'ai une procédure stockée dans laquelle je dois appeler une autre procédure stockée sur un ensemble de ids spécifiques; Y a-t-il un moyen de faire cela?

-à-dire au lieu d'avoir besoin de faire ceci:

exec p_MyInnerProcedure 4 
exec p_MyInnerProcedure 7 
exec p_MyInnerProcedure 12 
exec p_MyInnerProcedure 22 
exec p_MyInnerProcedure 19 

Faire quelque chose comme ceci:

*magic where I specify my list contains 4,7,12,22,19* 

DECLARE my_cursor CURSOR FAST_FORWARD FOR 
*magic select* 

OPEN my_cursor 
FETCH NEXT FROM my_cursor INTO @MyId 
WHILE @@FETCH_STATUS = 0 
BEGIN 

exec p_MyInnerProcedure @MyId 

FETCH NEXT FROM my_cursor INTO @MyId 
END 

Mon objectif principal ici est tout simplement maintenabilité (facile à enlever/ajouter id comme les changements d'affaires) , étant capable d'énumérer tous les Id sur une seule ligne ... Les performances ne devraient pas être aussi grandes d'un problème

+0

Quelle version de SQL Server? –

+0

, si vous avez besoin d'itérer sur une liste non-entière comme varchars, solution avec curseur: [itérer-à-une-liste-de-chaînes-dans-sql-server] (https://stackoverflow.com/questions/38388144/itérer-par-une-liste-de-chaînes-dans-sql-server) – Pac0

Répondre

74
declare @ids table(idx int identity(1,1), id int) 

insert into @ids (id) 
    select 4 union 
    select 7 union 
    select 12 union 
    select 22 union 
    select 19 

declare @i int 
declare @cnt int 

select @i = min(idx) - 1, @cnt = max(idx) from @ids 

while @i < @cnt 
begin 
    select @i = @i + 1 

    declare @id = select id from @ids where idx = @i 

    exec p_MyInnerProcedure @id 
end 
+0

J'espérais qu'il y aurait une manière plus élégante, mais je pense que ce sera aussi proche que je peux obtenir: Terminé en utilisant un hybride entre l'utilisation des select/unions ici, et le curseur de la Exemple. Merci! – John

+11

@john: si vous utilisez 2008, vous pouvez faire quelque chose comme INSERT @ids VALUES (4), (7), (12), (22), (19) –

+2

Juste pour info, les tables de mémoire sont généralement plus rapides que les curseurs (bien que pour 5 valeurs je puisse difficilement voir cela faire une différence), mais la plus grande raison pour laquelle je les aime est que je trouve la syntaxe similaire à celle que vous trouveriez dans le code de l'application, alors que les curseurs relativement différent. –

33

Ce que je fais dans ce scénario est de créer une variable de table pour contenir les ID.

Declare @Ids Table (id integer primary Key not null) 
    Insert @Ids(id) values (4),(7),(12),(22),(19) 

- (ou appeler une autre fonction table pour générer ce tableau)

ensuite en boucle sur la base des lignes dans ce tableau

Declare @Id Integer 
    While exists (Select * From @Ids) 
    Begin 
     Select @Id = Min(id) from @Ids 
     exec p_MyInnerProcedure @Id 
     Delete from @Ids Where id = @Id 
    End 

ou ...

Declare @Id Integer = 0 -- assuming all Ids are > 0 
    While exists (Select * From @Ids 
       where id > @Id) 
    Begin 
     Select @Id = Min(id) 
     from @Ids Where id > @Id 
     exec p_MyInnerProcedure @Id 
    End 

L'une ou l'autre des approches ci-dessus est beaucoup plus rapide qu'un curseur (déclaré par rapport à la (aux) table (s) utilisateur (s) habituelle (s)). Les variables de table ont une mauvaise réputation car lorsqu'elles ne sont pas utilisées correctement (pour les tables très larges avec un grand nombre de lignes), elles ne sont pas performantes. Mais si vous les utilisez uniquement pour contenir une valeur de clé ou un entier de 4 octets, avec un index (comme dans ce cas), ils sont extrêmement rapides.

+0

L'approche ci-dessus est équivalente ou plus lente qu'un curseur déclaré sur une variable de table. Ce n'est certainement pas plus rapide. Il serait cependant plus rapide qu'un curseur déclaré w/options par défaut sur les tables d'utilisateurs réguliers. –

+0

@Peter, ahhh, oui vous avez raison, je suppose à tort que l'utilisation d'un curseur implique une table utilisateur régulière, pas une variable de table ..Je l'ai édité pour clarifier la distinction –

14

utiliser une variable de curseur statique et un split function:

declare @comma_delimited_list varchar(4000) 
set @comma_delimited_list = '4,7,12,22,19' 

declare @cursor cursor 
set @cursor = cursor static for 
    select convert(int, Value) as Id from dbo.Split(@comma_delimited_list) a 

declare @id int 
open @cursor 
while 1=1 begin 
    fetch next from @cursor into @id 
    if @@fetch_status <> 0 break 
    ....do something.... 
end 
-- not strictly necessary w/ cursor variables since they will go out of scope like a normal var 
close @cursor 
deallocate @cursor 

ont Curseurs une mauvaise réputation depuis les options par défaut lorsqu'ils sont déclarés contre les tables utilisateur peut générer beaucoup de frais généraux.

Mais dans ce cas, la surcharge est assez minime, moins que toute autre méthode ici. STATIC indique à SQL Server de matérialiser les résultats dans tempdb, puis d'itérer sur ces résultats. Pour les petites listes comme celle-ci, c'est la solution optimale.

4

Je l'habitude d'utiliser l'approche suivante

DECLARE @calls TABLE (
    id INT IDENTITY(1,1) 
    ,parameter INT 
    ) 

INSERT INTO @calls 
select parameter from some_table where some_condition -- here you populate your parameters 

declare @i int 
declare @n int 
declare @myId int 
select @i = min(id), @n = max(id) from @calls 
while @i <= @n 
begin 
    select 
     @myId = parameter 
    from 
     @calls 
    where id = @i 

     EXECUTE p_MyInnerProcedure @myId 
    set @i = @i+1 
end 
2
CREATE TABLE #ListOfIDs (IDValue INT) 

DECLARE @IDs VARCHAR(50), @ID VARCHAR(5) 
SET @IDs = @OriginalListOfIDs + ',' 

WHILE LEN(@IDs) > 1 
BEGIN 
SET @ID = SUBSTRING(@IDs, 0, CHARINDEX(',', @IDs)); 
INSERT INTO #ListOfIDs (IDValue) VALUES(@ID); 
SET @IDs = REPLACE(',' + @IDs, ',' + @ID + ',', '') 
END 

SELECT * 
FROM #ListOfIDs 
1

Vous pouvez essayer comme ci-dessous:

declare @list varchar(MAX), @i int 
select @i=0, @list ='4,7,12,22,19,' 

while(@i < LEN(@list)) 
begin 
    declare @item varchar(MAX) 
    SELECT @item = SUBSTRING(@list, @i,CHARINDEX(',',@list,@i)[email protected]) 
    select @item 

    --do your stuff here with @item 
    exec p_MyInnerProcedure @item 

    set @i = CHARINDEX(',',@list,@i)+1 
    if(@i = 0) set @i = LEN(@list) 
end 
+2

Je ferais cette déclaration de liste comme ceci: '@list = '4,7,12,22,19' + ','' - de sorte qu'il est tout à fait clair que la liste doit se terminer par une virgule (il doesn ' t travail sans elle!). –

Questions connexes