Il blog di Gianni Giaccaglini

Blog su VBA e VSTO
Gianni Giaccaglini

My Links

News

NB - V. anche gli ARTICOLI (in fondo a questa barra)
Solo quesiti validi a: giannigiac@tin.it
Il mio Best seller su VBA
(v. www.hoepli.it)


Il mio ultimo libro su Open XML
(v. www.FAG.it):



La mia nipotina ELISA

Foto con dedica a ME di
Bill Gates giovanissimo
nei mitici anni 80!

Categorie Post

Categorie Articoli

Archivio

Immagini

Blog Stats

Calcoli tradotti al volo in valori (ricetta originale!)

QUESTA "RICETTA" NASCE DALLA RICHIESTA FATTAMI DA UNO SVILUPPATORE DI MODELLI EXCEL CON VBA, SPECIALIZZATO IN PROBLEMI DI STUDI DENTISTICI:

 

Paolo Bortolini

Sito: www.studiobortolini.com

Blog: http://managementodontoiatria.wordpress.com/

 

A onor del vero, dopo avergliela escogitata e proposta, mi ha de visu mostrato che nel frattempo anch'egli era suppergiù pervenuto ad un'idea stostanzialmente consimile... (succede, non s'inventa nulla a questo Excel-mondo).

Calcoli tradotti al volo in valori (ricetta originale!)

Si abbia una tabella del tipo seguente - con dati fasulli, colonne e righe omesse –sulla cui parte destra (da colonna H in avanti) si vogliono riportare formule anche complesse copiate in basso su tutti i record.

 

A

B

C ... F

G

H

I

J

1

Data

Nominativo

ALTRI CAMPI...

Classe

Costi

Volumi

Resa

2

01/12/2009

Rossi A.

. . .

AB

   219,38

58,50

  182,89

3

01/12/2009

Bianchi G.

. . .

BB

   219,38

87,00

  211,39

4

01/12/2009

Verdi F.

. . .

CFS

     43,88

3,80

    47,69

5

01/12/2009

Mauri R.

. . .

MM

     87,75

3,84

    91,56

6

01/12/2009

Andreini M.

. . .

FR

     26,33

10,00

    36,34

7

01/12/2009

Rossi A.

. . .

OO

     65,81

3,90

    69,62

8

01/12/2009

Bianchi G.

. . .

PM

     87,75

3,75

    68,44

9

01/12/2009

Verdi F.

. . .

RD

       8,78

3,81

    12,59

10

01/12/2009

Mauri R.

. . .

RD

     13,16

4, 66

    16,97

11

01/12/2009

Andreini M.

. . .

ED

   153,57

8,85

 € 102,43

12

01/12/2009

Rossi A.

. . .

RD

     21,94

3,75

    24,25

ALTRE RIGHE...

12499

01/12/2009

Zucchi L.

. . .

AX

   219,38

82,50

   221,89

12500

01/12/2009

Brambilla P.

. . .

BB

   219,38

58,00

   182,00

 

Il problema che subito si presenta deriva dal fatto che le migliaia di righe in ballo sono troppe, con due conseguenze poco simpatiche: le formule appesantiscono il file e, peggio ancora, rallentano paurosamente l’esecuzione.

Nota. Per la cronaca, nel modello che mi è stato commissionato diverse formulacce, per soprammercato “matriciali”, erano del tipo
 =SE(Y2<=Dataqmax;SOMMA(SE(DataC<=Dataqmax;SE(DataC>=Dataqmax-Orizzonte;SE(ClienteC=0;SE(CatC=AA2;SE(PC=AB2;Consumo;))))))/AQ2;0).

Prima soluzione (inadeguata)

Partendo dal normale intervallo della bozza affidatami dal committente, ovvero non da una tabella “canonica” (ListObject in VBA), ho dapprima pensato a una macro che, partendo da una prima riga contenente essa soltanto le formulacce da colonna H in poi, le ricopiasse in basso per tutte le righe sottostanti da 2 a 12500 (o giù di lì). Peggio che andar di notte, ovviamente: si registravano tempi biblici di ricalcolo.

Poi mi sono detto che salvare i risultati in termini di VALORI poteva comunque essere utile, in vista di ulteriori elaborazioni con altri programmi come Access o applicazioni sviluppate in Visual Studio. Ed ecco allora le macro VBA con le quali ho tentato di concretizzare l’idea. Si esamini anzitutto la seguente, seguita dalla sua brava Sub di prova.

Sub IncollaSpeciale(Scarto As Integer)

  Dim RigaFrm As Range, RigaDest As Range

  Set RigaFrm = Range("RigaFormule")

  Set RigaDest = RigaFrm.Offset(Scarto)

  RigaFrm.Copy RigaDest

  RigaDest.Copy

  RigaDest.PasteSpecial Paste:=xlPasteValues

  Application.CutCopyMode = False

End Sub

 

Sub ProvaIncollaSpeciale()

   IncollaSpeciale 3

End Sub

 

La IncollaSpeciale applica appunto all’intervallo preliminarmente denominato “RigaFormule” (nella figura precedente potrebbe avere coordinate quali H1:M1 o simili) contenenti le fatidiche formule a riga 2 da H in poi) il comando omonimo Copia seguito da Incolla Speciale con opzione Valori. Tali operazioni si traducono in VBA in queste successive istruzioni: a) proprietà Copy applicato a RigaForm che registra il Range(“RigaFormule”) assegnandole come destinazione una RigaDest shiftata secondo l’argomento Scarto; b) a tale RigaDest è di nuovo applicato Copy; c) quindi, sempre RigaDest, è assoggettato a PasteSpecial con opzione Paste:=xlPasteValue che, come qui si desidera,incolla i valori e non le formule; d) chiude un (inevitabile) Application.CutCopyMode = False che elimina la dannata casella ruotante che altrimenti continuerebbe a girare attorno all’origine (o alla destinazione? Verificate voi stessi...).

Ed ecco la macro centrale che utilizza la copia in basso dei valori, lasciando in vita la riga 2 di formule, secondo un certo numero di record NumRec come argomento.

Sub CopiaDati(NumeroRec As Integer)

  Dim Inizio As Date

  Inizio = Now()

  SvuotaRigheMenoUna

  Application.ScreenUpdating = False

  Dim i As Integer

  For i = 1 To NumeroRec

    IncollaSpeciale i

  Next

  MsgBox "Inizio: " & Inizio & vbLf & "Fine: " & Now()

End Sub

 

A questo punto non voglio tediare gli esperti cui mi rivolgo con commenti che ora ritengo superflui. Costoro potranno divertirsi a sfruttare CopiaDati passandole un NumRec pari alle righe meno una del nostro elenco, nonché a creare una macro SvuotaRigheMenoUna che, appunto, ripulisce tutte le celle che stanno sotto la riga 2 a partire da colonna H (o quel che l’è).

La pensata precedente può essere interessante per situazioni intermedie, diciamo con non più di 200-300 righe, ma purtroppo delude in gran parte le speranze. Infatti se garantisce un minor appesantimento di formule l’esecuzione del codice presenta riduzioni di tempo piuttosto scarse, come indicano - provare per credere – le istruzioni iniziale Inizio = Now e finale MsgBox ecc. che segnalano circa 10 minuti già con un migliaio di righe.

Nota. L’applicazione che mi era stata data “in cura” prevedeva l’inserimento di record di dettaglio quotidianamente, anche più di uno al giorno, per uno e persino più ANNI! Le aspettative di certi utenti sono a volte utopistiche...

Altro tentativo fallito

Successivamente ho sperimentato la variante consistente nel tradurre il fatidico intervallo in una Tabella , mediante l’apposito comando (in Excel 2007 posto nella scheda omonima) che automaticamente affibbia il nome default Tabella1. La cosa è in ogni caso interessante per due motivi:

1)      offre gratuitamente filtri automatici sui vari campi e funzionalità di filtro avanzato nonché l’automatico arruolamento come nuovo record di tale Tabella1 ogni volta che si aggiunge qualcosa sotto l’ultima riga precedente;

2)      riporta, sempre automaticamente, le formule inserite (da H in poi, sempre per stare al nostro caso) in tutti i record vecchi o nuovi.

Ma purtroppo anche in questo caso i tempi di ricalcolo si rivelano altresì solo leggermente più contenuti: confidavo in un qualche sistema di ottimizzazione offerto da Excel con le tabelle, alias ListObject. Aspettativa ingenua (miracoli non se ne fanno, neanche in casa Microsoft), almeno con le molte e complesse formule della nostra riga da H in poi e i tempi gli appesantimenti del file restano inaccettabili.

Eureka! Ovvero un uovo di Colombo

Per giunta il mio incontentabile committente pretendeva che venisse esportato su un diverso foglio l’intervallo di righe relative a un certo criterio (principalmente quelle compresi fra due date fissate dall’utente finale). Di qui il secondo uovo di Colombo, più complicato perché richiede codice VBA, ma comunque brillante.

Visto che i vari record sono immessi giorno per giorno l’aggiornamento di ciascuno secondo le formule di destra si può affidare a una macro che le calcola inserendone i valori.

L’idea appena detta si concretizza con un record ausiliario posto su colonne lontane da quelle del database. Si tratta di una cosa del genere, che ripropone a parte un tipico record della nostra Tabella1:

 

Y

Z

AA

AB

AC

AD

AE

AF

AG

eccetera

1

01/12/2009

Rossi M.

Xxx

zzz

Ecc.

Ecc.

Ecc.

Formula 1

Formula 2

eccetera

 

Si fa presente che le prime 7 celle costituiscono campi di input, mentre quelle accanto, da colonna AF in avanti contengono le dannate formule. Orbene, prima di esaminare la prima basilare macro si sappia che agli intervalli delle une e delle altre sono stati affibbiati i nomi QuestoRecord e, rispettivamente, QuesteFormule. Eloquenti, direi.

Ed ecco la macro in parola, seguita da una SvuotaRighe a sua volta richiamata dalla prima con evidente finalità:

Sub AggiornaValori()

  If ActiveCell.Column <> 8 Then

    MsgBox "Cella attiva DEVE essere in colonna H!", vbCritical

    Exit Sub

  End If

  Dim SetteCelle As Range

  With ActiveCell

    Set SetteCelle = Range(.Offset(0, -1), .Offset(0, -7))

  End With

  SetteCelle.Copy Range("QuestoRecord")

  Range("QuesteFormule").Copy

  ActiveCell.PasteSpecial Paste:=xlPasteValues

  Application.CutCopyMode = False

End Sub

 

Sub SvuotaRighe()

  Dim PrimaCella As Range, UltimaCella As Range

  With Range("RigaFormule")

    Set PrimaCella = .Cells(1, 1)

    Set UltimaCella = .Cells(.Count)(1, 1)

  End With

  If IsEmpty(PrimaCella) Then Exit Sub

  If IsEmpty(PrimaCella.Offset(1, 0)) Then

    Range(PrimaCella, UltimaCella).Clear

  Else

    Range(PrimaCella.End(xlDown), UltimaCella).Clear

  End If

End Sub

 

Commenti. Tacitiani e limitati alla Sub AggiornaValori (la seconda è affidata all’esegesi autogestita). La variabile SetteCelle serve a registrare l’intervallo delle prime celle dei campi di input (il valore 7 deriva dal caso trattato e va allineato a situazioni differenti). La macro s’incarica poi di copiare il Range SetteCelle nel Range QuestoRecord , con conseguente calcolo delle specifiche formule nella zona QuesteFormule, che a loro volta vengono incollate col PasteSpecial di soli valori nella cella attiva.

Nota. Quel che va sottolineato, chi non l’avesse ancora inteso, è che di questa manovra in due tempi non si può fare a meno, perché inserendo anche temporaneamente le formule accanto alla cella attiva le avrebbe generalizzate sull’intera colonna H, creando il rallenti che si voleva evitare...

Qualcuno obietterà che l’utente potrebbe dimenticare di lanciare AggiornaValori. Eccolo accontentato con una macro che li aggiorna tutti, circostanza nella quale il distrattone potrebbe andarsi a prendere un caffè, una tantum si spera!

Sub AggiornaTuttiValori()

  Dim Inizio As Date

  Inizio = Now()

  Load UserForm1

  UserForm1.Show

  Dim CampoCosti As Range, i As Integer

  Dim SetteCelle As Range

  Set CampoCosti = _

  Foglio1.ListObjects("Tabella1").ListColumns("COSTI GENERALI").Range

    On Error Resume Next ' Patch dovuta a causa dell'orribile caso #DIV/0!

    For i = 2 To CampoCosti.Cells.Count

    If CampoCosti.Cells(i) = "" Then

      With CampoCosti.Cells(i)

        Set SetteCelle = Range(.Offset(0, -1), .Offset(0, -7))

      End With

      SetteCelle.Copy Range("QuestoRecord")

      Range("QuesteFormule").Copy

      CampoCosti.Cells(i).PasteSpecial Paste:=xlPasteValues

      Application.CutCopyMode = False

    End If

  Next

  UserForm1.Hide

  Unload UserForm1

  Range("A3").Select

  MsgBox "Inizio: " & Inizio & vbLf & "Fine: " & Now()

End Sub

 

Anche qui mi astengo da commenti, in modo pilatesco quanto pigro. Preciso anzi che ho preparato una versione che, per limitare il predetto coffee break, esclude i record già sistemati. La lascio per esercizio. Piuttosto si esamini quest’altra macro, relativa all’evento DoubleClick del Worksheet ove risiede la tabella:

Private Sub Worksheet_BeforeDoubleClick(ByVal Target As Range, Cancel As Boolean)

    If Target.Column = 8 Then AggiornaValori

    Cancel = True

End Sub

 

Come a tutti è chiaro, essa agisce ogni volta che si dà una doppia cliccata su una cella di colonna H (quella del campo contenente la prima formulaccia).

L’evento risolutore. Alla fine, riflettendo, ci si rende conto che le macro precedenti meno AggiornaValori e la sua ancella SvuotaRighe sono pleonastiche o quasi. Questo perché l’evento che sistema tutto o quasi (*) è l’evento Change sempre del foglio della Tabella1:

Private Sub Worksheet_Change(ByVal Target As Range)

  If Intersect(Target, Range("Tabella1")) Is Nothing _

    Or Target.Column = 8 Then Exit Sub

  Range("H" & Target.Row).Select

  AggiornaValori

End Sub

 

Qui si fanno solo due osservazioni: a) una tabella canonica ha la virtù che se si digita qualcosa in una riga subito sotto questa viene automaticamente inserita come nuovo record; b) può essere importante che i valori da colonna H in poi vengano aggiornati (come avverrebbe con formule) qualora l’utente modifichi in seguito qualche dato di input. A buon intenditor...

Nota (*) Contro i superdistratti ci si potrebbe affidare agli eventi Open del modulo ThisWorkbook e/o agli Activate e Deactivate del Worksheet di Tabella1, facendoli richiamare la macro AggiornaTuttiValori. Ma è un troppa-grazia: la microinformatica non presuppone un utente consapevole piuttosto che un robot?

?>

?>

?>

posted on giovedì 18 marzo 2010 11.38