Office Guru

Egy egyszerű csapda a For ciklusban, amibe könnyedén belefuthatunk

2016. szeptember 02. - Office Guru

Bármilyen apró VBA-kódunk megírása során kétfajta hibát követhetünk el, egyrészt véthetünk szintaktikai (alaki) hibát, amikor a program nem felel meg a VBA szabályainak, illetve véthetünk szemantikai (tartalmi) hibát, amikor a program lefut, de rossz eredménnyel, vagy futás közben le is áll. Ezeknek vannak könnyen és kevésbé könnyen felismerhető előfordulásai, a mai posztban megnézünk egy roppant egyszerű kis kódszeletet, az általa tartalmazott hibát és persze a megoldásról is ejtünk szót.

Adott a következő roppant egyszerű kis tábla (a példa egyszerűsége ne tévesszen meg minket, sokkal komolyabb tábláknál és programoknál is belefuthatunk ebbe a hibába):

debug1.jpgValamilyen komplexebb programunkba beépítve vagy akár önmagában is azt szeretnénk elérni, hogy ha a felhasználó megad egy cellaértéket és megnyom egy gombot, akkor a megadott cellaértéknek megfelelő sorok kerüljenek törlésre.

Erre elkészítjük a következő egyszerű kis kódot:

Sub sortorles()
Dim Tartomany As Range
Set Tartomany = ActiveSheet.Range("A1:A10")
For Each cell In Tartomany
If cell.Value = "Haza" Then
cell.EntireRow.Delete
End If
Next cell
End Sub

debug2.jpgBoldogok is vagyunk, ott van szépen a For ciklusunk, ami a megadott tartomány összes celláján fog végigmenni és megnézi a feltétel teljesülését majd ennek megfelelően jár el, ha szükséges - az összes soron már nem megyek végig, az eddigi posztok alapján feltételezem, hogy ez már senkinek nem okoz komoly problémát. Futtassuk hát le! Előzetes becslésünk alapján a példánál azt várnánk, hogy maradjon két darab Otthon értékünk, semmi más, ehelyett a következőt kapjuk:

debug3.jpgVajon hol mehettünk félre? Hiszen szépen lefutott a program, nem is dobott hibát, mégsem az elvárt eredményt kaptuk. Azért a probléma önmagában egy tapasztalt VBA-felhasználónak nem okoz gondot, sőt valószínűleg elő sem idézi, de ettől függetlenül érdemes megvizsgálni ezt a kérdést.

A probléma a For ciklus "előrehaladásában" rejlik, ugyanis a ciklus mindig csak a következő sort vizsgálja meg a feltételnek megfelelően, tehát ha a kettes sort törölte, akkor újra már nem vizsgálja meg, miközben pedig a törlés miatt a hármas sorunk beugrott a kettes helyére és ez a sor vizsgálatlan maradt.

Jópár különféle megoldás van ennek a problémának a kiküszöbölésére, talán a legegyszerűbb, ha nem is megyünk bele ebbe a ciklusba, hanem egyszerűen tartományként meghatározzuk a törlendő cellákat és úgy hajtjuk végre azt a lépést, akár ki is színezhetjük a cellák hátterét, majd szín alapján törölhetünk, de akár csinálhatjuk azt is, hogy a For ciklust visszafelé futtatjuk (hiszen akkor nem lesz "előrehaladási" probléma), mondjuk így:

Sub sortorles()
Dim Tartomany As Range
Set Tartomany = ActiveSheet.Range("A1:A10")
For i = 10 To 1 Step -1
If ActiveSheet.Cells(i, 1) = "Haza" Then
ActiveSheet.Cells(i, 1).EntireRow.Delete
End If
Next i
End Sub

debug4.jpg

debug5.jpg

Ez sem túl bonyolult kód, ahogy látható, annyit változtattunk az előzőhöz képest, hogy az aktív sheetünk első oszlopában indulunk el a 10. cellától egyesével visszafelé az első celláig, és minden esetben megvizsgáljuk a ciklus éppen aktív cellájában a feltétel teljesülését majd ennek megfelelően törlünk.

Mondjuk ez 50 ezer sornál már nem lesz annyira szupergyors, úgyhogy inkább váltsunk ilyen esetekben egy másik megoldásra. Ötletek, javaslatok jöhetnek kommentben vagy akár Facebookon is.

A bejegyzés trackback címe:

https://officeguru.blog.hu/api/trackback/id/tr5911672418

Kommentek:

A hozzászólások a vonatkozó jogszabályok  értelmében felhasználói tartalomnak minősülnek, értük a szolgáltatás technikai  üzemeltetője semmilyen felelősséget nem vállal, azokat nem ellenőrzi. Kifogás esetén forduljon a blog szerkesztőjéhez. Részletek a  Felhasználási feltételekben és az adatvédelmi tájékoztatóban.

Andras02 2016.09.08. 11:36:45

Szia,
Szuper az Office Blog sok hasznos ötletet találok rajta.
Azért írok, mert a fenti problémával nincs gondom, de én tízezernél több sorral rendelkező táblázatot használok, és említetted vannak más megoldások is a for ciklushoz.
Próbáltam tömbbe rakni, és ubuond(varray, 1) feldolgozni, de az háromszor hosszabb ideig tartott a feldolgozás.

Jelenleg 11000 sort kilenc perc alatt dolgoz fel a vba script-em (MSOffice 2010). Az adathalmazban vannak 2015, és 2016 adatok, két sheet-re átpakolgatom őket "2015" "2016", copy -> destiny - > delete.entirerow. Működik rendesen, csak lassú... :(

Szóval mit tudsz még ajánlani az adathalmaz feldolgozásához?
Köszönöm.
süti beállítások módosítása