Sommare valori di dati uguali.
-
pagina vista: volte
ovvero: ottenere
il totale dei valori di ogni serie di dati uguali presenti in una tabella,
ricreando una nuova tabella.
Requisiti:
conoscenza dei cicli For..Next e While...Wend
Questo esercizio, in pratica un SOMMA.SE realizzato in vba, differisce
dall'articolo sull'altro sito ("SommaSE
in Vba") dove, disponendo di due tabelle, si scorrevano i nomi di una
tabella (tabella da scorrere), cercando in un'altra tabella (tabella dove
cercare) il nome letto in quel momento, sommando i valori che si trovato
nella cella a lato del nome trovato.
Caratteristica
tipica di quell'esempio era che nell'elenco da scorrere i "nomi" erano
"univoci", cioè figuravano una sola volta.
Qui invece ci
troviamo con una unica tabella, dove i dati che servono come il "criterio di ricerca" sono
formati da più elementi posti in colonne di una stessa riga, e che si
possono ripetere più volte . La tabella
sotto ci aiuta a capire meglio: ogni "criterio di ricerca" è formato ad
esempio, dall'elemento "tavolo", di colore "verde", col
codice "FF", posizione di magazzino "A1", righe
(evidenziate in verde); e così via per ogni riga, dove dovremo unire,
concatenare ( & ) i quattro elementi che formano il "criterio di ricerca" per
ricercarli all'interno della stessa tabella, in modo da poter ottenere il
totale dei valori presenti nella stessa riga, colonna A (quantità). In
pratica cercheremo tavolo+verde+FF+A1 come se
fosse un'unica parola.
|
A |
B |
C |
D |
E |
1 |
quantità |
descriz |
colore |
codice |
posiz |
2 |
6 |
tavolo |
verde |
FF |
A1 |
3 |
2 |
tavolo |
verde |
FF |
A1 |
4 |
3 |
sedia |
rosso |
NN |
A2 |
5 |
4 |
sedia |
rosso |
NN |
A2 |
6 |
1 |
tavolo |
nero |
UU |
A4 |
7 |
5 |
panca |
verde |
GG |
A6 |
8 |
2 |
tavolo |
rosso |
UU |
B2 |
9 |
6 |
tavolo |
verde |
FF |
A1 |
10 |
3 |
tavolo |
verde |
FF |
A2 |
Il risultato di
una simile ricerca dovrà restituire in un'altra tabella, che posizioneremo
dove vorremo, una cosa del genere:
|
G |
H |
I |
J |
K |
1 |
Totali |
descriz |
colore |
codice |
posiz |
2 |
14 |
tavolo |
verde |
FF |
A1 |
3 |
7 |
sedia |
rosso |
FF |
A1 |
ecc. ecc.
Come procediamo?
Intanto, visto che il vba è CaseSensitive e non
riconoscerebbe una stringa se scritta in maniera diversa per quanto riguarda
maiuscole/minuscole (basta anche un solo carattere diverso per fare la
differenza) inseriamo nella sezione "Generale - dichiarazioni" del modulo
che ospita la nostra macro, l'istruzione Option
Compare Text
poi creeremo
quattro cicli: il primo ciclo For..Next che si occuperà di scorrere tutte le
righe della tabella, leggendo concatenati i quattro elementi da ricercare, e
per ogni riga letta, inseriamo un ciclo While..Wend che cercherà, a partire
dalla riga successiva a quella letta dal ciclo esterno, corrispondenze al
criterio di ricerca; nel caso trovi una corrispondenza, aggiungerà il valore
corrispondente della colonna A ad un totalizzatore iniziato a zero, ed
incrementerà un flag di una unità; all'uscita del ciclo interno while..wend,
se il flag sarà maggiore del valore iniziale assegnatoli (vuol dire che almeno un
altro valore è stato
trovato) allora si inizia un terzo ciclo interno while.. wend che cercherà
la prima riga vuota dove copiare (altra tabella) il totale ottenuto e i dati
che formano il criterio di ricerca; anzichè usare un copia/incolla, uso un
quarto ciclo For Next che scorrendo gli indici di colonna dei quattro
elementi che formano il criterio, "depositi" in altrettante celle
destinazione i relativi dati.
Sembra complicato,
in effetti un pò lo è, ma vedremo riga per riga le istruzioni con i
commenti, e poi la routine completa.
Importante: perchè
i cicli NON rileggano più volte i dati simili già letti, useremo una
colonna dove inserire un marcatore (un flag) esterno, io ho usato un asterisco (*), e lo
faccio scrivere nella cella a lato di quelle lette, colonna F ;a
inizio routine pulisco la colonna F per successivi rilanci. Ognuno potrà usare la colonna che vuole,
basterà indicare il numero di colonna appropriato.
Le istruzioni
passo passo:
-
uro = [A65536].End(xlUp).Row - è
necessario sapere quanto è lungo l'elenco, quindi usiamo la variabile "uro"
che restituisce l'ultima riga occupata, colonna A
-
Range("F:F").ClearContents - si
pulisce tutta la colonna che avremo usato per inserire il flag asterisco
(*), usato come indicatore di riga già letta dai cicli
-
For N = 2 To uro - iniziamo il
primo ciclo esterno For Next partendo dalla riga 2, dove il contatore
"N" indicherà il numero di riga da leggere ad ogni ciclo
-
If Cells(N, 6) <>
"*" Then - controlliamo subito se la cella riga N, colonna 6, (la
colonna F, quella dove faremo scrivere l'asterisco che indica che la riga è
già stata letta)-
istruzione determinante: se la riga non è stata letta, quindi diversa da
asterisco, si prosegue con le istruzioni sottostanti altrimenti
si passa, con End If (che chiude questa condizione) all'istruzione Next,
cioè alla riga N successiva.
-
Cosa = Trim(Cells(N,
2) & Cells(N, 3) & Cells(N, 4) & Cells(N, 5)) - usiamo la
variabile "Cosa" per concatenare i dati che sono nella riga N, colonne
B,C,D,E quindi "cosa" ad esempio, all'inizio sarà uguale a
"tavoloverdeFFA1", cioè una stringa unica, ed usiamo Trim per
eliminare eventuali spazi iniziali e finali che potrebbero falsare il
riconoscimento su righe successive.
-
Cells(N, 6) = "*" - visto che
stiamo leggendo i dati nella riga N, impostiamo subito, stessa riga, nella
colonna F (la n. 6) il marcatore asterisco (*).
-
tot = Cells(N, 1)
- impostiamo la variabile "tot" come totalizzatore dei valori letti
nella colonna A, stessa riga, questi valori sono le "quantità" di cui ci
interessa ottenere
appunto il totale dei valori simili alla variabile "cosa".
-
riga = N + 1
- ora impostiamo la variabile "riga" al valore che ha il contatore "N" più
1; ci serve per definire il numero di riga dal quale far partire il primo ciclo While...Wend;
questo è il ciclo che scorrerà tutto l'elenco, partendo appunto dalla riga
sotto a quella letta "N", per cercare stringhe uguali al contenuto della
variabile "cosa", e sommare i valori nella colonna A, "riga", alla
variabile "tot".
-
flag = 1
- la variabile "flag" , che impostiamo a 1 e che verrà incrementato di 1 se
il ciclo While troverà stringhe uguali a "cosa", ci serve come marcatore
interno per determinare la condizione dell'azione da seguire (lo vediamo più
avanti) alla fine del ciclo While..Wend.
-
While Cells(riga,
2) <> "" - iniziamo il primo ciclo While che "girerà" fino
a che troverà celle, nella riga "riga", colonna B, diverse da vuoto. Per far
"girare", cioè avanzare un ciclo While,
si usa incrementare l'indice di riga di 1 unità prima di ogni Wend. Quando
si verificherà una condizione diversa da quella impostata dal ciclo (quindi
si troverà una cella vuota) il ciclo While termina automaticamente.
-
If Cells(riga, 6)
= "*" Then GoTo 10 - iniziato il ciclo, si
controlla subito che la riga non sia già stata letta, controllando la
colonna 6 se contiene un asterisco, in
quel caso passiamo all'indicatore di "riga codice" 10, saltando le
istruzioni sottostanti:
-
Trova = Trim(Cells(riga,
2) & Cells(riga, 3) & Cells(riga, 4) & Cells(riga, 5)) - ora usiamo la
variabile "Trova" come contenitore della stringa dei valori letti nelle
colonne B,C,D,E, ma nella riga "riga" letta nel ciclo While. (concatenati
come per la var. "Cosa").
-
If riga > uro Then
GoTo 20 - quindi controlliamo che il numero di
"riga" non sia superiore all'ultima riga occupata, altrimenti saltiamo alla
riga indice 20, saltando l'incremento di "riga"
ed uscendo dal primo ciclo While.
-
If Cosa = Trova Then - ora
si controlla se le due variabili "Cosa" e "Trova" sono uguali, se sono
uguali....
-
tot = tot + Cells(riga, 1) - si
incrementa il totalizzatore "tot" con il valore quantità letto, stessa riga
di "Trova", colonna A
-
Cells(riga, 6) = "*"
- si inserisce nella colonna F, stessa riga, il marcatore asterisco per
segnalare la riga come già letta.
-
flag = flag + 1
- si incrementa il valore della variabile "flag" di una unità.
-
End If -
fine della condizione If riga...
-
10: -
indice di riga codice
-
riga = riga + 1 - solo a questo
punto del ciclo While incrementiamo di 1 il valore di "riga", in modo che
con il sottostante Wend il ciclo legga la riga successiva.
-
Wend -
istruzione che serve a rinviare il ciclo al controllo While.
-
20: -
indice di riga codice; quando siamo qui è perchè è terminato il ciclo While,
e/o abbiamo superato il limite inferiore di tabella
-
If flag >= 1 Then
- se siamo arrivati a questa istruzione è perchè è terminato il primo ciclo
While..Wend, quindi sono state lette tutte le righe, e quindi se ci saranno
stati "doppioni", in marcatore "flag" sarà maggiore di 1
Ci fermiamo un
attimo: ciò che vorremo ottenere con la "condizione" "flag"
dipende da ciò che vogliamo, mi spiego: nell'elenco che avremo
scorso potranno esserci elementi riportati una sola volta, di cui non
servirebbe quindi fare un "totale" in quanto unico valore "quantità"; se
vorremo ricostruire una tabella con comunque tutti gli elementi letti,
totali per i doppioni ma anche elementi singoli, dovremo impostare la
condizione If flag >= 1, ciè se il marcatore "flag"
è uguale o maggiore di 1, se invece vogliamo costruire una tabella con il
riepilogo dei soli elementi presenti più volte, dovremo impostare la
condizione If flag > 1, che escluderà quindi
tutto ciò che è stato letto una sola volta. proseguiamo:
-
ri = 1 -
visto che vorremo costruire una nuova tabella, dovremo cercarci una zona
dove inserire i valori letti precedentemente; per questo cerchiamo una prima
riga libera dove incollare i dati; nell'esempio seguito, uso la colonna 8,
la H, come colonna di cui controllare le celle, e a partire dalla colonna G
fino alla K
ho predisposto le stesse intestazioni di colonna; con la variabile "ri" si
imposta la riga iniziale che userà il secondo ciclo While..Wend che cercherà
la prima cella libera.
-
While Cells(ri, 8) <> "" - fino a
che la riga "ri", colonna 8, sarà diversa da vuoto (quindi conterrà valori)
-
ri = ri + 1 - si
incrementa il numero di riga "ri" di una unità
-
Wend
- trovata la cella ri, 8 libera, termina il ciclo.
-
Cells(ri, 7) = tot
- è nella cella trovata libera, colonna 7 (la G), che depositeremo il valore
ottenuto da Tot, cioè il totale delle quantità lette per gli stessi elem.
-
For vai = 2 To 5
- quindi usiamo un ciclo interno For Next che gira 4 volte e che ha il
compito di leggere i valori che sono nelle colonne B,C,D,E, riga "N"
(non ci scordiamo che siamo ancora alla riga "N" letta dal primo ciclo For..Next
esterno), e dovremo depositare ogni singolo nome letto in altrettante
colonne della nuova tabella, riga "ri". Per questo aggiungiamo 6 ((tante sono le
colonne di differenza tra la B e la H, tra la C e la I, ecc.).
-
Cells(ri, vai + 6) = Cells(N, vai) -
è il contatore "vai" del ciclo che rappresenta il numero di colonna nella
sintassi Cells
-
Next - fine ciclo
-
End If - fine della condizione If
Flag..
-
tot = 0 - si imposta la variabile
totalizzatore "tot" a zero per il successivo ciclo
-
Trova = "" - si imposta a
vuota la variabile "Trova" per il successivo ciclo
-
Cosa = "" - si imposta
a vuota la variabile "Cosa" per il successivo ciclo
-
End If -
fine della prima condizione
If Cells(N, 6) <>
"*"....
-
Next - si passa alla riga "N"
successiva. il ciclo scorrerà fino a raggiungere la riga "uro"
Considerazioni:
sicuramente la routine è complessa, almeno per i neofiti del vba, ma
credo che le spiegazioni fornite mettano in grado di capirne il
funzionamento, e di poterla modificare per le proprie esigenze, specialmente
nella creazione della seconda tabella, sarà sufficiente modificare gli
indici di riga "ri" e/o di colonna in funzione dell'area che vorranno
destinarle.
un'ultima cosa: se
usate aggiornare con nuovi inserimenti la tabella di origine (Colonne A:E) e
vorrete rilanciare le istruzioni, converrà valutare se azzerare, cancellare
completamente i dati della seconda tabella per evitare accodamenti di dati;
verrebbero infatti inseriti sotto i dati presenti.
la routine
completa:
Option Compare Text
'da inserire in "Generale -
Dichiarazioni" del modulo
Sub TotProdotti()
uro = [A65536].End(xlUp).Row
Range("F:F").ClearContents
For N = 2 To uro
If Cells(N, 6) <> "*" Then
cosa = Trim(Cells(N, 2) & Cells(N, 3) & Cells(N, 4) & Cells(N, 5))
Cells(N, 6) = "*"
tot = Cells(N, 1)
riga = N + 1
flag = 1
While Cells(riga, 2) <> ""
If Cells(riga, 6) = "*" Then GoTo 10
trova = Trim(Cells(riga, 2) & Cells(riga, 3) & Cells(riga, 4) & Cells(riga,
5))
If riga > uro Then GoTo 20
If cosa = trova Then
tot = tot + Cells(riga, 1)
Cells(riga, 6) = "*"
flag = flag + 1
End If
10:
riga = riga + 1
Wend
20:
If flag >= 1 Then
ri = 1
While Cells(ri, 8) <> ""
ri = ri + 1
Wend
Cells(ri, 7) = tot
For vai = 2 To 5
Cells(ri, vai + 6) = Cells(N, vai)
Next
End If
tot = 0
trova = ""
cosa = ""
End If
Next
End Sub |
Conclusioni:
l'utilizzo dei cicli While...Wend, ove possibile, anche se usati interni ad
altri cicli, sono più veloci dei corrispondenti cicli For..Next usati per
svolgere le stesse operazioni; da verifiche fatte su una tabella come quella
sopra, lunga 500 righe, la routine con i cicli while è risultata 3
volte più veloce rispetto ad una stessa routine realizzata con tutti cicli
For..Next.
Buon lavoro.
prelevato sul sito
www.ennius.altervista.org
|