“Als ik mijn VBA code rijen laat verwijderen dan duurt dit nogal lang… is hier een oplossing voor?”
Omdat ik deze vraag zo vaak tegen kom op fora, leek het me een goed idee dit eens uitgebreid te bespreken. Dit bleek niet zo eenvoudig… Er zijn namelijk veel verschillende methodes bekend.
Stel, je hebt een werkblad met 100.000 rijen. Daarin wil je gaan controleren bij welke rijen kolom “C” de waarde “x” bevat. Als dit zo is, dan moet die volledige rij verwijderd worden.
Volgende methodes gaan we uitproberen:
- Methode 1: Achterwaarts door alle rijen lopen en rijen één voor één verwijderen
- Methode 2: Screenupdating / Calculation uitgeschakelen
- Methode 3: Achterwaarts door alle rijen lopen met Union
- Methode 4: Autofilter gebruiken
- Methode 5: SpecialCells
We voorzien 4 testbladen waarop we de verschillende methodes zullen testen:
- Blad 1: een werkblad met 8 kolommen en 100.000 gevulde cellen per kolom. We vullen de cellen met willekeurige getallen, tekst en formules. Ik plaats in 30 cellen in kolom “C” de waarde “x”.
- Blad 2: een werkblad met 8 kolommen en 100.000 gevulde cellen per kolom. We vullen de cellen met willekeurige getallen, tekst en formules. Ik plaats in 300 cellen in kolom “C” de waarde “x”.
- Blad 3: een werkblad met 8 kolommen en 100.000 gevulde cellen per kolom. We vullen de cellen met willekeurige getallen, tekst en formules. Ik plaats in 3000 cellen in kolom “C” de waarde “x”.
- Blad 3: een werkblad met 8 kolommen en 100.000 gevulde cellen per kolom. We vullen de cellen met willekeurige getallen, tekst en formules. Ik plaats in 30000 cellen in kolom “C” de waarde “x”.
Methode 1: Achterwaarts door alle rijen lopen
We kunnen Excel over het blad laten lopen, laten controleren of rij “C” een “x” bevat in een bepaalde kolom, deze rij laten schrappen en dan naar de volgende rij te gaan.
Opgelet: om deze code succesvol te laten verlopen moeten we achterwaarts (van beneden naar boven) gaan met onze loop. Zo niet wordt steeds de rij na een geschrapte rij overgeslagen!
Sub Methode1() Dim i As Long With ActiveWorkbook.Sheets(1) For i = 100000 To 1 step -1 If .Cells(i, "C") = "x" Then .Cells(i, "C").EntireRow.Delete End If Next i End With End Sub
We laten onze code los op onze 4 testbladen en krijgen volgend resultaat:
De 30000 lijnen heb ik niet getest. Deze zal langer dan 20.000 seconden duren uitgaande van de resultaten van de 30, 300 en 3000 lijnen. Hier wordt dus duidelijk dat dit absoluut niet de goede methode is om dit te doen.
Methode 2: Zet Calculation / ScreenUpdating uit!
De meeste tijd gaat verloren als volgt: elke keer als er een lijn geschrapt wordt gaat Excel opnieuw alle formules berekenen. Ook het vernieuwen van het scherm neemt telkens tijd in beslag.
Dit kunnen we voorkomen door de automatische berekening van formules en het vernieuwen van het scherm even uit te zetten, en op het einde van onze code terug aan te zetten:
Sub Methode2() Dim i As Long With Application .Calculation = xlCalculationManual .ScreenUpdating = False End With With ActiveWorkbook.Sheets(1) For i = 100000 To 1 step -1 If .Cells(i, "C") = "x" Then .Cells(i, "C").EntireRow.Delete End If Next i End With With Application .Calculation = xlCalculationAutomatic .ScreenUpdating = True End With End Sub
We testen deze code opnieuw op onze testbladen:
Methode 3: Achterwaarts door alle rijen lopen met Union
We gaan proberen dit nog wat te versnellen door tijdens de loop enkel ‘waar te nemen’ welke rijen verwijderd moeten worden. Achteraf zullen we pas de nodige rijen schrappen.
Opgelet! Er is een limiet van 8192 areas wanneer je Union gebruikt: http://support.microsoft.com/default.aspx?scid=kb;en-us;832293. Sinds Excel 2010 is dit probleem opgelost.
Sub Methode3() Dim i As Long Dim rng As Range With ActiveWorkbook.Sheets(1) For i = 100000 To 1 step -1 With .Cells(i, "C") If .Value = "x" Then If rng Is Nothing Then Set rng = .Cells Else Set rng = Application.Union(rng, .Cells) End If End If End With Next i If Not rng Is Nothing Then rng.EntireRow.Delete End With End Sub
Met deze code krijgen we volgende resultaten:
Merk op dat enkel bij het schrappen van 3000 rijen deze methode niet sneller is dan Methode 2!
Methode 4: Autofilter gebruiken
Quentemy herinnerde me eraan dat we ook Autofilter kunnen gebruiken om rijen te schrappen. Deze ingebouwde functionaliteit van Excel kan de lege rijen eruit filteren en dan het geheel ineens schrappen.
Het is niet nodig om een Loop over het werkblad uit te voeren, wat tijdbesparend werkt.
Sub Methode4() Dim rng As Range With Application .Calculation = xlCalculationManual .ScreenUpdating = False End With With ActiveWorkbook.Sheets(1) .Rows(1).Insert Shift:=xlDown .Range("A1:I100000").AutoFilter Field:=3, Criteria1:="x" With .AutoFilter.Range On Error Resume Next Set rng = .Offset(1, 0).Resize(.Rows.Count - 1, 1) _ .SpecialCells(xlCellTypeVisible) On Error GoTo 0 If Not rng Is Nothing Then rng.EntireRow.Delete End With .AutoFilterMode = False .Rows(1).EntireRow.Delete End With With Application .Calculation = xlCalculationAutomatic .ScreenUpdating = True End With End Sub
Zoals verwacht doet deze methode het ook heel goed:
Methode 5: Als de voorwaarde een lege cel is
Bovenstaande code kan je voor allerlei voorwaarden gebruiken. Echter, als de voorwaarde een lege cel is, dan bestaat er nog een andere methode. Excel heeft namelijk een ingebouwde functie die lege cellen opspoort in een bereik, zijnde Specialcells(xlCellTypeBlanks).
Opgelet! Er is een limiet van 8192 areas wanneer je SpecialCells gebruikt: http://support.microsoft.com/default.aspx?scid=kb;en-us;832293. Sinds Excel 2010 is dit probleem opgelost.
Opnieuw: geen Loop uit te voeren, wat tijdbesparend werkt.
Sub Methode5() ActiveWorkbook.Sheets(1).Range("C:C"). _ SpecialCells(xlCellTypeBlanks).EntireRow.Delete On Error GoTo 0 End Sub
Met als resultaat:
Conclusie
Uit mijn testen blijkt dat het niet zo eenvoudig is om te bepalen wat nu de beste manier is. Deze is afhankelijk van heel wat factoren. Het is dus onmogelijk om een methode aan te duiden die in alle gevallen het snelste werkt.
Een volledig overzicht van de looptijden van onze macro’s:
Om het wat overzichelijker te maken heb ik het in een scoretabel gegoten. De snelste per testblad krijgt 5 punten, de tweede snelste krijgt 4 punten, enzovoort.
Wanneer je lege cellen gaat gebruiken als voorwaarde, raad ik aan om methode 5 te gebruiken. Deze komt als koploper uit de tests. Moest je hier tegen het maximum van 8192 stoten, dan kan je kiezen voor Methode 4.
Moet je op een andere waarde gaan controleren, dan valt Methode 5 weg als mogelijkheid, en kunnen we best kiezen voor Methode 4 (Autofilter). Gemiddeld is deze methode vrij snel en we lopen geen gevaar om een limiet te bereiken zoals bij Methode 3 en 5.
Kies je toch liever een andere manier? Wanneer het gaat om weinig rijen, maakt het misschien niet zoveel uit of het nu 1 seconde langer duurt of niet. Vergeet enkel niet om ScreenUpdating en Calculation uit te schakelen.
Met dank aan BSALV, voor zijn waardevolle bijdrage aan dit artikel.
Waarom niet het autofilter gebruiken, alleen de waarde x in kolom c selecteren, al die rijen in één handeling selecteren en delete. Dan alleen nog even in het autofilter sorteren op de/een kolom, klaar ………………
Veel sneller m.i.
Dat lijkt me inderdaad een goede aanvulling. Ik zal zo snel mogelijk de proef op de som nemen!
Is het ook mogelijk te zoeken naar een waarde die door de gebruiker in een VBA word ingevoerd? Stel de gebruiker voert een naam in bijvoorbeeld Michiel, dan moet hij de rij zoeken waarin Michiel staat. Een andere keer zou ook Kees ingevoerd kunnen worden, dan moet dus de rij verwijderd worden waarin Kees staat.
Michiel,
Dat kan zeker. Welke methode wilde je gaan gebruiken? Dan maak ik je even een voorbeeldje.