Newsgroup: it.comp.lang.visual-basic
Titolo: Domanda (forse) banale VBA

Forse č inutile premettere che sono un principiante.
Il problema č il seguente:
ho un foglio di Excel contenente un database (circa 20.000 righe) e vorrei
eliminare solo le righe pari.
Ho risolto con un ciclo for next, ma il tempo di elaborazione č di circa 8
minuti sul mio PC, poichč il ciclo individua le righe una per una e le
elimina una per una.
Esiste un'istruzione per selezionare contemporaneamente tutte le righe pari
(o dispari) ed eliminarle tutte insieme?
Grazie infinite a chi potrą fornirmi una risposta.
Francesco G.

 Esiste un'istruzione per selezionare contemporaneamente tutte le righe
pari (o dispari) ed eliminarle tutte insieme?
  Mica tanto banale 'sta domanda!...
  Separiamo i due problemi, quello della velocita` e quello della "selezione
contemporanea", ovvero della selezione di intervalli non adiacenti e
incominciamo dal secondo.
  Come ben saprai per selezionare piu` intervalli, o singole celle, non
adiacenti e` possibile usare il tasto Ctrl oppure andarsi ad impegolare con
i tasti F8 e MAIUSC+F8.
  Se usi il registratore di macro mentre effettui una tale selezione ottieni
un codice simile a questo:
    Range("2:2,4:4,6:6").Select
  In questo esempio verranno selezionate le righe 2, 4 e 6.
  Potresti quindi scrivere una procedura che componga la stringa
"2:2,4:4,6:6,<...>". Ma... Ma 20.000 righe no. E` troppo per vb, per excel e
per un normale Pentium messi insieme. Altro che 8 minuti, in questo modo!
  Un altro metodo per ottenere una selezione di intervalli non adiacenti e`
mediante l'uso di Union. Ma soffre delle stesse limitazioni del metodo visto
prima. Vale comunque la pena vederne un esempio perche' puo` essere utile in
circostanze meno "stressanti":
Option Explicit
Public Sub SelezionaRighePari( _
)
  Dim objRange            As Excel.Range
  Dim lngIndex            As Long
  Dim lngRows             As Long
  Dim lngCalculation      As Excel.XlCalculation
  Dim blnDisplayStatusBar As Boolean
  With Application
    lngCalculation = .Calculation
    blnDisplayStatusBar = .DisplayStatusBar
    .Calculation = xlCalculationManual
    .DisplayStatusBar = True
    .StatusBar = "Esecuzione macro in corso..."
    .ScreenUpdating = False
  End With
  With Application.ActiveSheet.UsedRange
    lngRows = .Rows.Count
    Set objRange = .Cells(2, 1).EntireRow
    For lngIndex = 4 To lngRows Step 2
      .Application.StatusBar = "Riga: " & lngIndex
      Set objRange _
        = .Application.Union( _
            objRange, .Cells(lngIndex, 1).EntireRow)
    Next
  End With
  objRange.Select
  Set objRange = Nothing
  With Application
    .StatusBar = False
    .ScreenUpdating = True
    .Calculation = lngCalculation
    .DisplayStatusBar = blnDisplayStatusBar
  End With
End Sub
  Questo codice effettua solo la selezione, come vedi. Delle righe intere.
Ma per l'eliminazione delle righe intere potremmo anche selezionare solo la
prima cella, quindi eliminare le relative righe intere.
  Come vedi ho anche "spento" lo schermo ed inibito il ricalcolo, anche se
in questo caso non sarebbe necessario perche' l'unico "Select" e` alla fine
della procedura e nessun valore delle celle viene modificato. Ma l'ho messo
per evidenziare due cose che e` sempre opportuno fare per aumentare la
velocita` di esecuzione. A meno che il ricalcolo non sia necessario ma anche
in tal caso lo si potra` attivare e disattivare al bisogno.
--
Ciao.
Maurizio
--------
?SPQR(C)
 X
--------

Ti ringrazio per la procedura che mi hai proposto e con la quale ho risolto
brillantemente il problema.
Sostituendo "objRange.Select" con "objRange.Delete" l'operazione di
cancellazione diviene unica per tutte le righe individuate e quindi il
guadagno in velocità è sensibilissimo.
Ancora grazie
Francesco G.

> Francesco G.:

  Ne sono lietissimo, grazie a te per la cortese replica. Toglimi una
curiosita`, con che processore e con quanta RAM hai "lavorato" quelle 20.000
righe?


 ha scritto nel messaggio
news:9d1f5d$g4s3f$3...@ID-52090.news.dfncis.de...


 > [...] Toglimi una curiosita`, con che processore e con quanta
RAM hai "lavorato" quelle 20.000 righe?

  Il concetto e` giusto. Ero arrivato anch'io a quella conclusione
ma non mi piaceva l'idea di effettuare l'operazione a pezzi e
volevo vedere se riuscivo a selezionare tutte le celle (o le
righe). Con il codice seguente ci sono riuscito e mi sa che sia
anche notevolmente piu` veloce. Non ho avuto il tempo di testare,
soprattutto verifica che non vengano saltate delle celle. Facci
sapere, grazie.
Public Sub SelezionaRighePari2( _
)
  Dim lngRows             As Long
  Dim objRange            As Excel.Range
  Dim objTmpRange         As Excel.Range
  Dim lngIndex            As Long
  Dim lngCalculation      As Excel.XlCalculation
  Dim blnDisplayStatusBar As Boolean
  With Application
    lngCalculation = .Calculation
    blnDisplayStatusBar = .DisplayStatusBar
    .Calculation = xlCalculationManual
    .DisplayStatusBar = True
    .StatusBar = "Esecuzione macro in corso..."
    .ScreenUpdating = False
  End With
  With Application.ActiveSheet.UsedRange
    lngRows = .Rows.Count
    For lngIndex = 2 To lngRows Step 2
      Application.StatusBar = "Riga: " & lngIndex
      If objTmpRange Is Nothing Then
        Set objTmpRange = .Cells(lngIndex, 1)
      Else
        Set objTmpRange _
          = .Application.Union( _
              objTmpRange, .Cells(lngIndex, 1))
      End If
      If (lngIndex Mod 2000) = 0 Then
        If objRange Is Nothing Then
          Set objRange = objTmpRange
        Else
          Set objRange _
            = .Application.Union(objRange, objTmpRange)
        End If
        Set objTmpRange = Nothing
      End If
    Next
  End With
  If Not (objTmpRange Is Nothing) Then
    Set objRange = Application.Union(objRange, objTmpRange)
  End If
  Set objTmpRange = Nothing
  objRange.Select
  Set objRange = Nothing
  With Application
    .StatusBar = False
    .ScreenUpdating = True
    .Calculation = lngCalculation
    .DisplayStatusBar = blnDisplayStatusBar
  End With
End Sub
--


 --------
Ho testato la tua nuova procedura (ho aggiunto l'EntireRow); i tempi si
riducono per la selezione delle righe, ma si allungano sensibilmente per la
cancellazione delle stesse. Probabilmente č proprio l'eliminazione delle
righe l'elemento che rallenta.
Sono riuscito a guadagnare qualcosa operando su gruppi di 1000 righe (vedi
modifica alla tua istruzione), ma rimango oltre i 5 minuti di elaborazione
totale.
Sub EliminaRighePari()
  Dim lngRows             As Long
  Dim objRange            As Excel.Range
  Dim objTmpRange         As Excel.Range
  Dim lngCalculation      As Excel.XlCalculation
  Dim lngIndex            As Long
  Dim blnDisplayStatusBar As Boolean
  With Application
    lngCalculation = .Calculation
    blnDisplayStatusBar = .DisplayStatusBar
    .Calculation = xlCalculationManual
    .DisplayStatusBar = True
    .StatusBar = "Esecuzione macro in corso..."
    .ScreenUpdating = False
  End With
  With Application.ActiveSheet.UsedRange
    lngRows = .Rows.Count
    For lngIndex = 2 To lngRows Step 2
      Application.StatusBar = "Riga: " & lngIndex
      If objTmpRange Is Nothing Then
        Set objTmpRange = .Cells(lngIndex, 1).EntireRow
      Else
        Set objTmpRange = .Application.Union(objTmpRange, .Cells(lngIndex,
1)).EntireRow
      End If
      If (lngIndex Mod 1000) = 0 Then
        If objRange Is Nothing Then
          Set objRange = objTmpRange
        Else
          Set objRange = .Application.Union(objRange, objTmpRange)
        End If
        Set objTmpRange = Nothing
      End If
    Next
  End With
  If Not (objTmpRange Is Nothing) Then
    Set objRange = Application.Union(objRange, objTmpRange)
  End If
  Set objTmpRange = Nothing
  Application.StatusBar = "Cancellazione delle righe: attendere"
  objRange.Delete
  Set objRange = Nothing
  With Application
    .StatusBar = False
    .ScreenUpdating = True
    .Calculation = lngCalculation
    .DisplayStatusBar = blnDisplayStatusBar
  End With
End Sub
Ora riproverň con la procedura con cui avevamo ridotto il tempo a poco piů
di 3 minuti articolandola su gruppi di 1000 e non di 2000 righe, per
ottimizzare il tempo di memorizzazione delle righe da eliminare (forse
potrei scendere sotto i 3 minuti).
Ti ringrazio nuovamente anche perchč ho imparato con questo scambio un buon
numero di cose nuove (molto elegante il Tmp che hai creato per liberare
memoria).
Ciao
Francesco

 >   Il concetto e` giusto. Ero arrivato anch'io a quella
conclusione ma non mi piaceva l'idea di effettuare l'operazione a
pezzi e volevo vedere se riuscivo a selezionare tutte le celle (o
le righe). Con il codice seguente ci sono riuscito e mi sa che sia
anche notevolmente piu` veloce. Non ho avuto il tempo di testare,
soprattutto verifica che non vengano saltate delle celle. Facci
sapere, grazie.

  Ho testato, con entirerow e senza, a blocchi di 1.000, con un
celeron 128MB, quindi un processore molto piu lento del tuo
suppongo, e non ho mai superato i tre minuti totali. Di cui circa
3/4 di minuto per il popolamento di objRange ed il restante per il
delete.
--


 delete.
  Confesso che fin dall'inizio la questione della lentezza mi
aveva fatto venire delle perplessita` ma, in parte per ragioni di
tempo, in parte per sperimentare il metodo Union con un cosi`
grande numero di righe, le avevo messe da parte.
 Oggi invece ho voluto "andare a vedere". Ho scritto cosi` la
procedura che vedi sotto e... sorpresa, i tempi non sono cosi`
drammatici. meno di due minuti col celeron di cui sopra. Penso che
dovresti rivedere la tua procedura originaria che forse compie
qualche operazione pesante insieme all'eliminazione.
Option Explicit
Public Sub EliminaRighePari( _
)
  Dim dtmStart            As Date
  Dim lngCalculation      As Excel.XlCalculation
  Dim blnDisplayStatusBar As Boolean
  Dim lngRow              As Long
  Dim lngRows             As Long
  dtmStart = Now
  With Application
    lngCalculation = .Calculation
    blnDisplayStatusBar = .DisplayStatusBar
    .Calculation = xlCalculationManual
    .DisplayStatusBar = True
    .ScreenUpdating = False
  End With
  With Application.ActiveSheet
    lngRow = 2
    lngRows = (.UsedRange.Rows.Count \ 2) + 1
    Do Until lngRow > lngRows
      Application.StatusBar = "Riga: " & lngRow
      .Cells(lngRow, 1).EntireRow.Delete
      lngRow = lngRow + 1
    Loop
  End With
  With Application
    .StatusBar = False
    .ScreenUpdating = True
    .Calculation = lngCalculation
    .DisplayStatusBar = blnDisplayStatusBar
  End With
  MsgBox Format$(dtmStart - Now, "h.mm.ss")
End Sub


 ha scritto nel
messaggio news:9defn5$hfpih$1...@ID-52090.news.dfncis.de...


 > > Ti ringrazio nuovamente anche perchè ho imparato con questo


Ho ancora qualche domandina relativa alla tua routine (spero di non
approfittare troppo della tua disponibilità):
perché dichiari una variabile boolean per la statusbar?
Ho snellito il codice in questo modo e funziona ugualmente:
Public Sub EliminaRighePari( )
Dim dtmStart            As Date
Dim lngCalculation      As Excel.XlCalculation
Dim lngRow              As Long
Dim lngRows             As Long
dtmStart = Now
With Application
     lngCalculation = .Calculation
    .Calculation = xlCalculationManual
    .ScreenUpdating = False
End With
    'ciclo do loop
With Application
    .StatusBar = False
    .ScreenUpdating = True
    .Calculation = lngCalculation
End With
End sub
Altra domanda: perché è necessario richiamare l'oggetto "Application" prima
di StatusBar se siamo già in un'istruzione "With Application" (vedi quinta
riga)? Ho provato solo con ".StatusBar" ma mi dà errore.
With Application.ActiveSheet
    lngRow = 2
    lngRows = (.UsedRange.Rows.Count \ 2) + 1
    Do Until lngRow > lngRows
        Application.StatusBar = "Riga: " & lngRow
        .Cells(lngRow, 1).EntireRow.Delete
        lngRow = lngRow + 1
    Loop
End With
(Queste sono probabilmente davvero domande banali, ma io sono un autodidatta
a tempo perso e quindi ho sicuramente molte lacune)
Ti ringrazio
Francesco

 Ti ringrazio
  :-)


 Scusa la (ulteriore) domanda banale: i cicli "do loop" sono più veloci dei
"for next"?
  Penso di si` ma in questo contesto l'eventuale differenza non dovrebbe
essere apprezzabile. Se ti interessa documentazione in proposito dovresti
trovarla spulciando fra i vecchi post del ng:
 
http://groups.google.com/advanced_group_search
(su unica riga).
  Qui ho preferito Do Until .. Loop perche' sintatticamente (semanticamente?
metaforicamente?) meglio traduce l'algoritmo: il ciclo deve essere
effettuato *finche`*. Finche` il valore di una variabile non supera il
valore di un'altra variabile. Ovviamente, _mutata mutandis_, avrei potuto
usare anche un ciclo Do While .. Loop oppure For .. Next.
