IALweb Homepage
Forum Home Forum Home > MS Office > Microsoft Office > Microsoft Access
  New Posts New Posts RSS Feed - Standardizzare Calcoli VBA
  FAQ FAQ  Forum Search   Events   Register Register  Login Login

 Topic   Topic Hot   Topic New   Topic Locked   Topic Sticky   Topic Hidden

Standardizzare Calcoli VBA

 Post Reply Post Reply
Author
Message
mjjfil View Drop Down
Utente Senior
Utente Senior


Joined: 07/Giu/2010
Status: Offline
Points: 236
Post Options Post Options   Thanks (0) Thanks(0)   Quote mjjfil Quote  Post ReplyReply Direct Link To This Post Topic: Standardizzare Calcoli VBA
    Posted: 01/Mag/2012 at 21:24
Ciao a Tutti,
apro questo 3D per porvi alcuni quesiti riguardo argomenti, in parte, già trattati da me e Luciano
in quest'altro 3D LINK.

In buona sostanza nel mio db ho creato un modulo nel quale ho inserito 4 funzioni e 1 Sub
Pubbliche che eseguono
calcoli in VBA (le prime 4) e una query di aggiornamento (la sub).
Queste poi le richiamo nel modulo della form interessata in questo modo:

-Richiamo in Form

Private Sub cmd_123_Click()
On Error Resume Next

If Me.anno = Year(Date) - 1 Then
    fore_cast.Value = Forecast(analisi.Value, dlkup, dlkup1, dlkup2)
    perc_forecast.Value = PercForeCast(fore_cast.Value)
    budget.Value = BudgetCalc(Me.fore_cast.Value, Me.delta.Value)
    perc_budget.Value = PercBudget(budget.Value)
End If

End Sub


-4 Funzioni Pubbliche nel Modulo

Option Compare Database

' Dichiarazione Variabili Pubbliche
Public varIdTipo, varGruppo As Long
Public SumBudgetRicaviPro, SommaForeCastRp As Double

' Prima Funzione di Calcolo
Public Function Forecast(varAnalisi, dlkup, dlkup1, dlkup2) As Double
On Error Resume Next

varIdTipo = Forms!MImporti!ce_tipo
varGruppo = Forms!MImporti!ce_gruppo

dlkup = DLookup("nz([valore],0)", "TTipoVoci", "[id_tipo]=" & 3)
dlkup1 = DLookup("nz([valore],0)", "TTipoVoci", "[id_tipo]=" & 1)
dlkup2 = DLookup("nz([valore],0)", "TTipoVoci", "[id_tipo]=" & 2)
Forecast = IIf([varGruppo] = 1, (Nz([varAnalisi], 0) * (1 + (dlkup) / 100) * (1 + (dlkup1) / 100)), _
IIf([varIdTipo] = 3, Nz([varAnalisi], 0) * (1 + (dlkup2) / 100), _
Nz([varAnalisi], 0) * (1 + (dlkup2) / 100)))

DoCmd.RunCommand acCmdSaveRecord
End Function

' Seconda Funzione di Calcolo
Public Function PercForeCast(varForecast) As Double
On Error Resume Next

SommaForeCastRp = Nz(DSum("[fore_cast]", "TImporti", "[anno]=year(date())-1 And [ce_gruppo]=1"), 0)
PercForeCast = IIf(Nz([SommaForeCastRp], 0) = 0, 0, Nz([varForecast], 0) / Nz([SommaForeCastRp], 0) * 100)

DoCmd.RunCommand acCmdSaveRecord
End Function

' Terza Funzione di Calcolo
Public Function BudgetCalc(varFore_Cast, varDelta) As Double
On Error Resume Next

varGruppo = Forms!MImporti!ce_gruppo

BudgetCalc = IIf([varGruppo] = 1, (IIf(Nz([varDelta], 0) > -1.01 And Nz([varDelta], 0) < 1, _
Nz([varFore_Cast], 0) + Nz([varDelta], 0) * Nz([varFore_Cast], 0), _
Nz([varFore_Cast], 0) + Nz([varDelta], 0))), (IIf(Nz([varDelta], 0) > -1.01 And Nz([varDelta], 0) < 1, _
Nz([varFore_Cast], 0) + Nz([varDelta], 0) * Nz([varFore_Cast], 0), Nz([varFore_Cast], 0) + Nz([varDelta], 0))))

DoCmd.RunCommand acCmdSaveRecord
End Function

' Quarta Funzione Di Calcolo
Public Function PercBudget(varBudget) As Double
On Error Resume Next

varGruppo = Forms!MImporti!ce_gruppo

SumBudgetRicaviPro = Nz(DSum("[budget]", "TImporti", "[anno]=year(date())-1 And [ce_gruppo]=1"), 0)
PercBudget = IIf(Nz([SumBudgetRicaviPro], 0) = 0, 0, Nz([varBudget], 0) / Nz([SumBudgetRicaviPro], 0) * 100)

DoCmd.RunCommand acCmdSaveRecord
End Function

' Sub Pubblica che contiene query di aggiornamento
Public Sub QryAggiornaBudget()
On Error GoTo Errore
Dim strSQL As String
Application.SetOption "Confirm Action Queries", False
SommaForeCastRp = Nz(DSum("[fore_cast]", "QXMImporti", "[anno]=year(date())-1 And [ce_gruppo]=1"), 0)
SumBudgetRicaviPro = Nz(DSum("[budget]", "QXMImporti", "[anno]=year(date())-1 And [ce_gruppo]=1"), 0)

strSQL = "UPDATE TImporti INNER JOIN QXAggiornamentoBudget ON TImporti.id_importo = QXAggiornamentoBudget.id_importo _
SET TImporti.perc_forecast =IIf(" & Str(Nz([SommaForeCastRp], 0)) & " = 0, 0, [QXAggiornamentoBudget]![fore_cast] / " & Str(Nz([SommaForeCastRp], 0)) & " * 100), _
TImporti.perc_budget =IIf(" & Str(Nz([SumBudgetRicaviPro], 0)) & " = 0, 0, [QXAggiornamentoBudget]![budget] / " & Str(Nz([SumBudgetRicaviPro], 0)) & " * 100) _
WHERE (((TImporti.anno)=year(date())-1) AND ((TImporti.id_importo)<[forms]![MImporti]![id_importo]))"

DoCmd.RunSQL strSQL

DoCmd.RunCommand acCmdSaveRecord
Application.SetOption "Confirm Action Queries", True
Exit Sub
Errore:
Resume Next
End Sub


Ora:
prima di fare così come mi aveva consigliato Luciano, eseguivo i calcoli direttamente dalla form.
Però, così facendo, se l'utente voleva modificare alcune "parti dei calcoli", x es. il campo [valore] (vedi codice seguente)
e doveva farlo da una Form, diversa da quella in cui eseguivo i calcoli, cosa facevo io:

Nell'evento "su dopo aggiornamento" del controllo (origine-[valore]), che l'utente intendeva modificare, facevo aprire e chiudere
la form che eseguiva i calcoli.
Poi con un banalissimo [For...Next + "Call cmd_123_Click],
facevo ciclare tutti i record.
Così si aggiornavano i risultati dei calcoli nella tabella a fronte del dato ([valore]) modificato.


' Parte del calcolo
dlkup = DLookup("nz([valore],0)", "TTipoVoci", "[id_tipo]=" & 3)

'CALCOLO NELLA 1° FUNZIONE PUBBLICA
Forecast = IIf([varGruppo] = 1, (Nz([varAnalisi], 0) * (1 + (dlkup) / 100) * (1 + (dlkup1) / 100)), _
IIf([varIdTipo] = 3, Nz([varAnalisi], 0) * (1 + (dlkup2) / 100), _
Nz([varAnalisi], 0) * (1 + (dlkup2) / 100)))


Adesso la questione è la seguente:
Siccome Luciano mi aveva insegnato che i calcoli "standardizzati" potevano essere richiamati in qualsiasi
punto dell'applicazione, io vorrei operare come segue:

Se l'utente modifica il campo [valore], tramite l'apposita form, vorrei che funzioni pubbliche e relativi calcoli fossero rieseguiti, onde avere
nel report finale dei dati corretti (senza dover ciclare tutta la form che richiama le [public functions]).

Aggiungo che la form che richiama i calcoli (Funzioni Pubbliche), funziona alla grande e i risultati
sono esatti...! :-)

Aggiungo pure che i calcoli eseguiti, vengono memorizzati nei campi della tabella.
Penso che ciò NON infranga la 4° regola di "Normalizzazione DataBase".
Questo perchè un campo calcolato è un campo che contiene ed esegue un calcolo.
Infatti i campi delle tabelle in cui vengono memorizzati i risultati non sono calcolati e le tabelle non sono altro che contenitori sterili.
Il calcolo lo esegue solo il VBA.

Si potrebbe quasi dire che "non è del tutto giusto" eseguire i calcoli nelle query di selezione,
in quanto queste ultime servono (teoricamente) solo a interrogare le tabelle (o anche query) e dovrebbero essere utilizzate per lo scopo
per il quale sono state concepite (interrogare dati di Tabelle o query).

Mi scuso se mi sono dilungato così tanto e ringrazio chiunque vorrà aiutarmi... ;-)

Abbraccio
Ciao
Fil

P.S.
Ditemi pure se devo spiegarmi meglio...!
Back to Top
willy55 View Drop Down
Veterano
Veterano
Avatar
Esperto di Access

Joined: 03/Ago/2011
Location: Italy
Status: Offline
Points: 1230
Post Options Post Options   Thanks (0) Thanks(0)   Quote willy55 Quote  Post ReplyReply Direct Link To This Post Posted: 03/Mag/2012 at 14:49
Per modificare un valore da un qualsiasi punto dell'applicativo devi realizzare una serie di funzioni in grado di operare indipendentemente dalla maschera (tutti i dati devono essere parametri della funzione stessa) ove si imputano i dati e quindi che effettui esclusivamente il calcolo e restituisca il valore.
Dopodichè si può valutare se effettuare la memorizzazione del calcolo in quanto, se scaturisce da dati presenti nel db e conosciuta la logica, è sempre possibile ottenerne il risultato (appunto fornito dalla funzione sopracitata).
Ciò significa rivedere il tuo codice.
Buon lavoro.
Willy
Back to Top
mjjfil View Drop Down
Utente Senior
Utente Senior


Joined: 07/Giu/2010
Status: Offline
Points: 236
Post Options Post Options   Thanks (0) Thanks(0)   Quote mjjfil Quote  Post ReplyReply Direct Link To This Post Posted: 04/Mag/2012 at 12:52
Ciao Willy55 e Grazie,

seguendo le tue indicazioni ho modificato le funzioni, nel modulo, come segue (ne mostro solo una).
Spero di aver fatto dei passi avanti (e non indietro :P).
In debug mi esegue tutta la funzione e il risultato del calcolo quantomeno c'è.
Tuttavia non viene memorizzato nella tabella.   :(

ecco il codice:

Option Compare Database

Public OrigineDati As Recordset
Public varIdTipo, varGruppo As Long
Public SumBudgetRicaviPro, SommaForeCastRp As Double

Public Function Forecast(varAnalisi, dlkup, dlkup1, dlkup2) As Double
'On Error Resume Next

Set OrigineDati = CurrentDb.OpenRecordset("QXMImporti")

varIdTipo = OrigineDati.Fields("ce_tipo").Value
varGruppo = OrigineDati.Fields("ce_gruppo").Value
varAnalisi = OrigineDati.Fields("analisi").Value

dlkup = DLookup("nz([valore],0)", "TTipoVoci", "[id_tipo]=" & 3)
dlkup1 = DLookup("nz([valore],0)", "TTipoVoci", "[id_tipo]=" & 1)
dlkup2 = DLookup("nz([valore],0)", "TTipoVoci", "[id_tipo]=" & 2)
Forecast = IIf([varGruppo] = 1, (Nz([varAnalisi], 0) * (1 + (dlkup) / 100) * (1 + (dlkup1) / 100)), _
IIf([varIdTipo] = 3, Nz([varAnalisi], 0) * (1 + (dlkup2) / 100), _
Nz([varAnalisi], 0) * (1 + (dlkup2) / 100)))

Forecast = OrigineDati.Fields("fore_cast").Value

DoCmd.RunCommand acCmdSaveRecord
End Function


Ho controllato pure i nomi dei campi e sono giusti
mi valorizza tutto, ma a questa riga non memorizza il valore nella tabella e a da errore runtime:
"Utilizzo non valido di null".
Forecast = OrigineDati.Fields("fore_cast").Value

E per richiamare la funzione pubblica del modulo nella form uso il seguente [for...next]:

conta = RecordsetClone.RecordCount
For conta1 = 1 To conta - 1
     Call Forecast(varAnalisi, dlkup, dlkup1, dlkup2)
            DoCmd.GoToRecord , , acNext
Next conta1


Attendo tue notizie e ti ringrazio in anticipo per la pazienza e l'aiuto.

Abbraccio
Ciao
Fil
Back to Top
willy55 View Drop Down
Veterano
Veterano
Avatar
Esperto di Access

Joined: 03/Ago/2011
Location: Italy
Status: Offline
Points: 1230
Post Options Post Options   Thanks (0) Thanks(0)   Quote willy55 Quote  Post ReplyReply Direct Link To This Post Posted: 05/Mag/2012 at 22:19

La tua funzione Forecast non si capisce il senso dei parametri impiegati (varAnalisi, dlkup, dlkup1, dlkup2).
Dall'esempio all'interno della funzione agisci con i campi del Recordset QXMImporti ma se devi ciclare per
tutti i record è necessario o che operi con una condizione where oppure la OpenRecordset sia esterna
alla funzione stessa (oppure viene passato il recordset e aggiornato il dato).
Fai attenzione che se agisci con il recordset devi utilizzare le appropriate istruzioni per aggiungere/aggiornare i dati. Inoltre guarda come viene restituito il valore della funzione (Forecast) e come tu lo utilizzi.
Insomma rivedi la logica e acquisisci una maggiore padronanza dell'impiego dei recordset

Esempi di cicli con recordset con aggiunta/modifica dati
http://allenbrowne.com/ser-29.html
http://bytes.com/topic/access/answers/206600-using-excel-vba-update-modify-access-records
http://bytes.com/topic/access/answers/458412-add-update-records-vba



Edited by willy55 - 05/Mag/2012 at 22:19
Willy
Back to Top
mjjfil View Drop Down
Utente Senior
Utente Senior


Joined: 07/Giu/2010
Status: Offline
Points: 236
Post Options Post Options   Thanks (0) Thanks(0)   Quote mjjfil Quote  Post ReplyReply Direct Link To This Post Posted: 06/Mag/2012 at 21:35
Ciao Willy e grazie,

sarò sincero: non mi ci raccapezzo più... :(
Non è che potresti postare un esempio di una funzione pubblica che esegue un calcolo con dati presenti
in campi di varie tabelle, che li memorizza nella tabella interessata
e che tale Funzione sia richiamabile da qualsiasi punto dell'applicazione?

Attendo tue notizie.

Grazie in anticipo

Abbraccio
Fil
Back to Top
willy55 View Drop Down
Veterano
Veterano
Avatar
Esperto di Access

Joined: 03/Ago/2011
Location: Italy
Status: Offline
Points: 1230
Post Options Post Options   Thanks (0) Thanks(0)   Quote willy55 Quote  Post ReplyReply Direct Link To This Post Posted: 07/Mag/2012 at 22:11

Rispondo alla tua

Originally posted by mjjfil mjjfil wrote:


... potresti postare un esempio di una funzione pubblica che esegue un calcolo con dati presenti in campi di varie tabelle, che li memorizza nella tabella interessata

Per memorizzare un dato da un recordset devi utilizzare il metodo "Update" (con "Edit" prima se utilizzi DAO) e AddNew se devi aggiungere un nuovo record.
Per operare con varie tabelle la funzione deve acquisire come parametro sia la tabella che i campi interessati (che potrebbero avere nomi diversi fra le tabelle).
Riprendendo l'esempio della funzione Forecast da te presentata, da un esame sommario, i calcoli sembrano essere sempre legati in modo fisso alla dLookup  sulla tabella/query "TTipoVoci" al campo "Valore" e ai criteri per "id_tipo" e gli altri elementi (variabili) che entrano in gioco sono i campi: "ce_tipo", "ce_gruppo", "analisi" del recordset "QXMImporti".
Ipotizzando che il calcolo debba essere effettuato con un ciclo per l'intero insieme dei dati e oltre al recordset vi siano solo i tre campi succitati interessati alla elaborazione questi ("ce_tipo", "ce_gruppo", "analisi") devono essere forniti quali parametri.
Pertanto, una pseudo codifica è la seguente:


Dim dbs As Database, rst As Recordset
Set dbs = DBEngine.Workspaces(0).Databases(0)
Set rst = dbs.OpenRecordset("QXMImporti", DB_OPEN_DYNASET)  ' Apre un dynaset.
Do While Not rst.EOF  ' Cicla per tutti i record presenti
   rst.Edit           ' Consente la modifica.
   rst("TuoNomeCampoCalcolato") = Forecast(rst, "ce_tipo", "ce_gruppo", "analisi")
            ' richiama la funzione Forecast 
            ' con il recordset e tre campi (con il loro nome)
            ' viene restituito il calcolo da memorizzare nel campo della tabella (TuoNomeCampoCalcolato)
   rst.Update         ' Salva le modifiche.
   rst.MoveNext       ' si sposta al nuovo record
Loop
rst.Close    ' Chiude il recordset.

E la funzione di calcolo dovrebbe essere similare a questa:


Public Function Forecast(rst As Recordset, ByVal fldTipo As String, ByVal fldGruppo As String, ByVal fldAnalisi As String) As Double

varIdTipo = rst.Fields(fldTipo).Value
varGruppo = rst.Fields(fldGruppo).Value
varAnalisi = rst.Fields(fldAnalisi).Value
dlkup =  DLookup("nz([valore],0)", "TTipoVoci", "[id_tipo]=" & 3)
dlkup1 = DLookup("nz([valore],0)", "TTipoVoci", "[id_tipo]=" & 1)
dlkup2 = DLookup("nz([valore],0)", "TTipoVoci", "[id_tipo]=" & 2)
varCalcolo = IIf([varGruppo] = 1, (Nz([varAnalisi], 0) * (1 + (dlkup)  / 100) * (1 + (dlkup1) / 100)), _
             IIf([varIdTipo] = 3,  Nz([varAnalisi], 0) * (1 + (dlkup2) / 100),  _
                                   Nz([varAnalisi], 0) * (1 + (dlkup2) / 100)))
Forecast = varCalcolo    ' Viene restituito il valore calcolato
End Function

Credo che il codice schematico possa farti comprendere il concetto, dopodichè devi adeguare la funzione nel numero e nel tipo di parametri di tuo interesse.

Willy
Back to Top
mjjfil View Drop Down
Utente Senior
Utente Senior


Joined: 07/Giu/2010
Status: Offline
Points: 236
Post Options Post Options   Thanks (0) Thanks(0)   Quote mjjfil Quote  Post ReplyReply Direct Link To This Post Posted: 08/Mag/2012 at 06:50
Buongiorno a tutti,
grazie a willy55 sono riuscito al "97%" nel mio intento...
l'unica cosa che mi manca è assegnare la funzione del calcolo al "campo calcolato" della tabella nel [do...loop] solo se i dati memorizzati nella tabella e più precisamente nei records dei campi calcolati sono differenti dal calcolo che viene eseguito.

mi spiego meglio:
se il risultato del calcolo rieffettuato dalla funzione è <> da null o è <> da quello già memorizzato:
ricalcolarlo.
Altrimenti:
uscire dal ciclo [do...loop].

io avevo tentato una cosa del genere ma, non funziona.

Private Sub Form_Open(Cancel As Integer)
'On Error GoTo Errore
Dim dbs As Database, rst As Recordset
Set dbs = DBEngine.Workspaces(0).Databases(0)

Set rst = dbs.OpenRecordset("MiaQuery", dbOpenDynaset)
Do While Not rst.EOF ' Cicla per tutti i record presenti
   
   If rst("fore_cast") = Forecast(rst, "ce_tipo", "ce_gruppo", "analisi") Or rst("perc_forecast") = PercForecast(rst, "fore_cast") _
   Or rst("budget") = BudgetCalc(rst, "delta", "ce_gruppo", "fore_cast") _
   Or rst("perc_budget") = PercBudget(rst, "ce_gruppo", "budget") Then Exit Do
   
   rst.Edit           ' Consente la modifica.
   
   rst("fore_cast") = Forecast(rst, "ce_tipo", "ce_gruppo", "analisi")
   rst("perc_forecast") = PercForecast(rst, "fore_cast")
   rst("budget") = BudgetCalc(rst, "delta", "ce_gruppo", "fore_cast")
   rst("perc_budget") = PercBudget(rst, "ce_gruppo", "budget")
   rst.Update        ' Salva le modifiche.
   rst.MoveNext
Loop
rst.Close    ' Chiude il recordset.

Exit Sub
Errore:
Resume Next
End Sub


Questa richiesta di aiuto non è rivolta solo a willy55, ma a chiunque abbia voglia di mettersi in gioco e aiutare...

...ovviamente nessuno deve sentirsi obbligato... ;-D

Super-Abbraccio
Ciao e Buona Giornata
Fil
Back to Top
willy55 View Drop Down
Veterano
Veterano
Avatar
Esperto di Access

Joined: 03/Ago/2011
Location: Italy
Status: Offline
Points: 1230
Post Options Post Options   Thanks (0) Thanks(0)   Quote willy55 Quote  Post ReplyReply Direct Link To This Post Posted: 09/Mag/2012 at 22:29

Riprendo il tuo esempio in quanto ipotizzo che la logica non sia corretta date le condizioni logiche in OR con cui vai ad effettuare il controllo del ciclo (in base alla tua descrizione che dovrebbe soddisfare la funzione).
In particolare nella condizione IF con le condizioni in OR (probabilmente avresti dovuto impiegare la condizione in AND) ovvero esce dal ciclo se tutte le condizioni sono uguali. Ho modificato il codice considerando che le varie funzioni determinano un tempo di elaborazione e quindi è più conveniente (avendo determinato già il valore) impiegare direttamente l'assegnazione. Infine è da valutare se tale codice deve essere posto alla apertura della maschera in quanto il ciclo verrà effettuato per tutti i record (con tempi significativi se questi sono di un certo numero) per cui si dovrebbe valutare se la funzione è da porre come utilità e gestire il calcolo ad ogni variazione di uno dei dati che entrano nel processo.  


Private Sub Form_Open(Cancel As Integer)
'On Error GoTo Errore
Dim dbs As Database, rst As Recordset
Set dbs = DBEngine.Workspaces(0).Databases(0)

Set rst = dbs.OpenRecordset("MiaQuery", dbOpenDynaset)
Do While Not rst.EOF  ' Cicla per tutti i record presenti
  
   varForecast = Forecast(rst, "ce_tipo", "ce_gruppo", "analisi")
   varPercForeCast = PercForecast(rst, "fore_cast") _
   varBudget =  BudgetCalc(rst, "delta", "ce_gruppo", "fore_cast")
   varPercBudget = PercBudget(rst, "ce_gruppo", "budget")

   ' Nel caso in cui vi sia un elemento diverso li aggiorna tutti
   If varForecast <> rst("fore_cast") OR varPercForeCast <> rst("perc_forecast") OR _
      varBudget <> rst("budget") OR varPercBudget <> rst("perc_budget") Then
  
      rst.Edit           ' Consente la modifica.
  
      rst("fore_cast") = varForecast   ' Aggiorna i vari dati
      rst("perc_forecast") = varPercForeCast
      rst("budget") = varBudget
      rst("perc_budget") = varPercBudget

      rst.Update         ' Salva le modifiche.

   Endif
   rst.MoveNext
Loop
rst.Close    ' Chiude il recordset.

Exit Sub
Errore:
Resume Next
End Sub

Concludendo rivedi la logica e fai ora le tue valutazioni del caso.

Edited by willy55 - 09/Mag/2012 at 22:32
Willy
Back to Top
 Post Reply Post Reply
  Share Topic   

Forum Jump Forum Permissions View Drop Down

Forum Software by Web Wiz Forums® version 10.11
Copyright ©2001-2012 Web Wiz Ltd.

This page was generated in 0,188 seconds.