2009-07-30 4 views
2

J'ai ajouté une nouvelle colonne, packageNo, à ma table:Mises à jour SQL lentes dans un curseur

create table Packages(
    id varchar(20) primary key, -- ok, I know :) 
    orderNo uniqueIdentifier not null, 
    orderlineNo int not null, 
    packageNo int not null default(0) 
) 

Maintenant, je veux générer le packageNo avec les règles suivantes:

    remis à zéro pour chaque commande
    ascendantpour l'ordre, la ligne de commande

Mon problème est que le script que j'ai écrit utilise 15 minutes pour 26500 lignes sur mon testServer. Ici, il est:

set NoCount ON 
declare @Counter int 
declare @handledCounter int 
declare @currentorder uniqueIdentifier 
declare @fetchedOrder uniqueIdentifier 
declare @fetchedId varchar(20) -- will using PK speed up things? 

declare PackageNo_Cursor cursor for 
    select orderNo, id from packages order by orderNo, orderlineNo for update of packageNo 

open PackageNo_Cursor 
fetch next from PackageNo_Cursor into @fetchedOrder, @fetchedId 

set @currentOrder = @fetchedOrder 
set @counter = 0 
set @handledCounter = 0 
while @@fetch_status = 0 
begin 
    if (@currentOrder <> @fetchedOrder) 
     begin -- reset counter for each order 
     set @currentOrder = @fetchedOrder 
     set @counter = 0 
     end 
    set @counter = @counter + 1 
    set @handledCounter = @handledCounter +1 
    if (@handledCounter % 50 = 0) 
     begin 
     print 'handled = ' + cast(@handledCounter as varchar) 
     end 
    update packages set packageNo = @counter where current of PackageNo_Cursor  
    fetch next from PackageNo_Cursor into @fetchedOrder, @fetchedId 
    end 

close PackageNo_Cursor 
deallocate PackageNo_Cursor 

Cela devrait se traduire par:

id - orderno - lineNo - packageNo (what I need to set) 
ean1 - guid1 - 1  - 1 
ean2 - guid1 - 2  - 2 
ean3 - guid2 - 1  - 1 
ean15- guid2 - 3  - 2 
ean15- guid2 - 4  - 3 

Puis-je faire fonctionner ce plus vite?

+0

Êtes-vous particulièrement préoccupé par l'impression sur chaque paquet 50 vous avez géré? –

+0

pas vraiment, je l'ai utilisé pour vérifier que quelque chose se passait et de voir que la consommation de temps était linéaire. –

Répondre

8

Vous ne voulez pas utiliser un curseur, vous voulez faire cela comme un ensemble, comme ceci:

update p set 
    packageNo = r.packageNo 
from 
    packages p 
    inner join 
     (select orderNo, orderLineNo, 
     ROW_NUMBER() OVER(PARTITION BY orderNo ORDER BY orderLineNo) as packageNo 
     from packages) r on 
     p.orderNo = r.orderNo 
     and p.orderLineNo = r.orderLineNo 

Cela tirer parti de la fonction de SQL Server ROW_NUMBER pour vous obtenir le nombre correct de chaque ligne. En utilisant UPDATE...FROM, nous pouvons créer un inner join à partir de laquelle mettre à jour. C'est beaucoup plus efficace que le curseur. Voir, les curseurs ajoutent une capacité itérative à un langage basé sur des procédures et des ensembles (SQL). Les deux ne jouent pas bien ensemble. Cette instruction update est appelée pour chaque ligne (et pour ouvrir/valider une transaction, pour démarrer). Si vous le faites en une seule instruction, SQL peut paralléliser ceci pour le rendre beaucoup plus rapide.

La règle standard est la suivante: Utilisez les curseurs aussi parcimonieusement que possible. Il y a des cas marginaux où ils sont nécessaires, mais si vous ne faites pas beaucoup d'administration SQL par jour, il est douteux que vous rencontriez ces cas.

+0

cela fonctionnera mais il sera également lent car pour chaque ligne dans le paquet vous devrez exécuter select count (*) séparément ... cela nécessitera des opérations O^2 ... –

+0

@Bogdan: Vous avez raison. Changé d'utiliser une jointure interne à la place. – Eric

+1

Salut Eric, Merci pour votre solution qui résolvent la mise à jour en 15s :-) –

-1

Dans le cas où vous avez vraiment besoin d'utiliser des curseurs, définir votre curseur avec certains attributs, comme ceci:

DECLARE _cursor CURSOR LOCAL FAST_FORWARD FOR 
.... 
+0

cela va se confilier avec le "pour mise à jour" (ou toute mise à jour de courant) –

1

Quelque chose comme, non testé

update 
    p 
set 
    packageNo = p2.Ranking 
from 
    packages p 
    JOIN 
    (SELECT 
     orderNo, orderlineNo, 
     ROW_NUMBER() OVER (PARTITION BY orderNo ORDER BY orderlineNo) AS Ranking 
    FROM 
     packages) p2 ON p.orderNo = p2.orderNo AND p.orderlineNo= p2.orderlineNo 
+1

Merci gbn, cela fonctionne bien (15s). c'est la même solution que bogdan –

0
WITH cteA AS (
    SELECT 
     packageNo, 
     row_number() over (partition by orderNo order by orderNo, orderlineNo) rn 
    FROM packages 
) 

UPDATE cteA 
SET packageNo = rn - 1; 

Vous devez également créer un index clusterisé sur orderNo, orderlineNo.

(j'espère que vous utilisez sql2005 ou plus récent)

+0

Salut > (J'espère que vous utilisez sql2005 ou plus récent) Je fais réellement pour cette application :) mais nous avons encore des applications en cours d'exécution sur sqlserver200 (et même quelques sybase 5.quelque chose!) - Dom –

Questions connexes