Problema con calcolo giorni lavorativi

di il
13 risposte

Problema con calcolo giorni lavorativi

Ho scaricato l'esempio postato da Alex (CalcoloWorkingDays) e l'ho adattato alle mie esigenze. I campi data non sono piu caricati da una form ma pescati direttamente da una query

Funziona tutto bene fino alla parte di istruzione dove viene richiamata una tabella aggiuntiva che contiene i periodi di apertura e chiusura personalizzati a quel punto il programma va in loop, eliminando questa parte di programma:

Case Else
Dim rs As DAO.Recordset
Dim sSQL As String
sSQL = "SELECT * FROM Giorni_Festivi " & _
"WHERE Format$(DataFesta,'ddmm')='" & Format$(myDate, "ddmm") & "'"
Set rs = DBEngine(0)(0).OpenRecordset(sSQL, dbOpenDynaset, dbReadOnly)
' Se il rs è vuoto significa che la data non è inserita come FESTIVITA'
Festivo = Not rs.EOF
rs.Close
Set rs = Nothing

Il programma funziona benissimo.

non riesco a capire dove sia il problema, qualcuno ha avuto un problema simile?

13 Risposte

  • Re: Problema con calcolo giorni lavorativi

    Guarda avevo trovato un baco in effetti mi pareva fosse nell'assegnazione della variabile Festivo...
    Purtroppo sono impossibilitato tutta settimana in quanto via per lavoro ed aimé senza pc....
    Se nessuno nel frattempo ti risponde nel week end vedo di darti dettagli...
    Altrimenti se hai urgenza in questo demo trovi la routine funzionante... nel senso che avevo sanato un bug proprio relativo alla tua segnalazione:
    **
  • Re: Problema con calcolo giorni lavorativi

    Ciao Alex,

    grazie mille, veramente interessante l'agenda appuntamenti, provo a studiarmi anche il ribbon personalizzato.

    Ho provato il nuovo codice che mi hai inviato ma il problema persiste, non riesco a capire come risolverlo, ho provato a ridurre il nimero di righe nella query (90) e riesco a far girare il tutto, ma con molte righe il tempo di esecuzione diventa infinito.

    Non capisco pero perche funziona tutto bene se escludo il richiamo ai giorni in tabella mentre il tempo aumenta in maniera cosi esponenziale quando fa anche questo controllo.

    A questo punto il problema piu che di codice è di velocita di esecuzione ma non capisco perche

    Il codice attualmente usato è questo:
    Public Function GetWorkDaysNum(DateStart As Date, DateStop As Date, Optional WorkDays As Integer = 6) As Long
    Dim dtStart As Date
    Dim dtStop As Date
    Dim dtAct As Date
    Dim lngTot As Long
    Dim lngTotW As Long
    Dim x As Long
    Dim blFest As Boolean
    Dim conta

    dtStart = DateStart
    dtStop = DateStop
    dtAct = DateStart

    lngTot = DateDiff("d", dtStart, dtStop) + 1
    Do Until dtAct > dtStop
    blFest = False
    ' Controllo se il G è all'interno della Settimana LUNGA/CORTA
    ' quindi se è SABATO o DOMENICA.
    If Weekday(dtAct, vbMonday) > WorkDays Then blFest = True

    ' Nel caso non sia già SABATO/DOMENICA controllo che non sia
    ' una FESTIVITA'.
    If blFest = False Then blFest = Festivo(dtAct)
    x = x - blFest
    dtAct = DateAdd("d", 1, dtAct)
    Loop

    lngTotW = lngTot - x
    GetWorkDaysNum = lngTotW

    End Function


    ' VERIFICA SE LA DATA E' UN GIORNO FESTIVO IN ITALIA
    Function Festivo(ByVal mydate As Date, Optional domenica As Boolean = True, Optional sabato As Boolean = False) As Boolean
    If sabato = True Then
    Festivo = Weekday(mydate, vbMonday) = 6
    If Festivo Then: Exit Function
    End If

    If domenica = True Then
    Festivo = Weekday(mydate, vbMonday) = 7
    If Festivo Then: Exit Function
    End If

    Festivo = (Easter(year(mydate)) = mydate)
    If Festivo Then Exit Function

    Festivo = (Easter(year(mydate)) + 1 = mydate)
    If Festivo Then Exit Function

    ' Controllo le Festività RICORRENTI
    Dim rs As DAO.Recordset
    Dim sSQL As String
    sSQL = "SELECT * FROM T_FL WHERE Giorno=" & Day(mydate) & " AND Mese=" & Month(mydate) & " AND Attivo=-1"
    Set rs = DBEngine(0)(0).OpenRecordset(sSQL, dbOpenDynaset, dbReadOnly)
    ' Se il rs è vuoto significa che la data non è inserita come FESTIVITA'
    If rs.EOF = False Then Festivo = True
    rs.Close
    Set rs = Nothing
    If Festivo Then Exit Function

    ' Controllo le Festività ONE-SHOT
    sSQL = "SELECT * FROM T_FL WHERE Giorno=" & Day(mydate) & " AND Mese=" & Month(mydate) & " AND Anno=" & year(mydate) & " AND Attivo=-1"
    Set rs = DBEngine(0)(0).OpenRecordset(sSQL, dbOpenDynaset, dbReadOnly)
    ' Se il rs è vuoto significa che la data non è inserita come FESTIVITA'
    If rs.EOF = False Then Festivo = True
    rs.Close
    Set rs = Nothing
    End Function

    ' CALCOLO DELLA PASQUA
    ' Questa funzione non è mia ma è recuperata dal WEB
    Public Function Easter(year As Integer) As Date
    Dim d As Integer
    d = (((255 - 11 * (year Mod 19)) - 21) Mod 30) + 21
    Easter = DateSerial(year, 3, 1) + d + (d > 48) + 6 - ((year + year \ 4 + d + (d > 48) + 1) Mod 7)
    End Function
  • Re: Problema con calcolo giorni lavorativi

    @Alex ha scritto:


    ... tutta settimana ... senza pc....
    Ti siamo tutti vicini in questo momento, sappiamo quanto è difficile e non lo auguriamo a nessuno. Ma succede.

    bean_bandit ha scritto:


    ...
    Ho provato il nuovo codice che mi hai inviato ma il problema persiste, non riesco a capire come risolverlo, ho provato a ridurre il nimero di righe nella query (90) e riesco a far girare il tutto, ma con molte righe il tempo di esecuzione diventa infinito.

    Non capisco pero perche funziona tutto bene se escludo il richiamo ai giorni in tabella mentre il tempo aumenta in maniera cosi esponenziale quando fa anche questo controllo.
    ...
    Potresti indicare come inserisci la funzione nella query? (in sostanza pubblica la query)
    Per quello che può valere, ho fatto una prova super rapita su una query (senza criteri di "filtro") con più di 20000 record e il risultato è immediato. Si tratta di calcoli di giorni lavorativi molto brevi (massimo 7) però credo che sia comunque indicativo del fatto che c'è qualcos'altro che non va, non il codice.
    Quante "festività" hai inserito nella tabella T_FL?
    Mmmm... i problemi cominciano ad iniziare se devo ordinare per "giorni lavorativi", cioè il risultato della funzione (ed è anche comprensibile, direi normale). Se disattivo l'ulteriore verifica (se rientra cioè in una delle festività della tabella T_FL) il tempo di esecuzione si riduce di molto, sempre però mantenendo un criterio di ordinamento sul campo dei giorni lavorativi.
    E' questa la tua situazione o almeno simile?
  • Re: Problema con calcolo giorni lavorativi

    La query è questa
    TRANSFORM Sum(IIf((([Creazione Gantt Query].[Data Inzio])-1)<([Calendario].[data_calendario]) And (([Creazione Gantt Query].[Data Fine prevista])+1)>([Calendario].[data_calendario]) And Weekday([Calendario].[Data_calendario],1)<>2,(([Ore previste])/(GetWorkDaysNum([Data Inzio],[Data Fine prevista],6))),Null)) AS valore
    SELECT [Creazione Gantt Query].Attività
    FROM [Creazione Gantt Query], Calendario
    GROUP BY [Creazione Gantt Query].Attività
    PIVOT Calendario.Data_calendario;

    credo che il casino derivi da come tiro fuori le date dato che si tratta di una tabella con dentro delle date e non collegata in alcun modo a nulla.

    Cattura.JPG
    Cattura.JPG

    il risultato che mi da pero è quello che mi serve, non so se si puo fare in altro magari inserendo un istruzione tipo "includi intervallo di date da\a"
    (ho provato con
    Espr1: Day([Creazione Gantt Query]![Data Inzio]) & "/" & Month([Creazione Gantt Query]![Data Inzio]) & "/" & Year([Creazione Gantt Query]![Data Inzio])
    ma non funziona)
    direttamente nella query evitando la tabella calendario:

    Cattura2.JPG
    Cattura2.JPG

    Quante "festività" hai inserito nella tabella T_FL?

    11, quelle "standard" nazionali.
    Mmmm... i problemi cominciano ad iniziare se devo ordinare per "giorni lavorativi"
    Cosa intendi con ordinare? nel mio caso uso il calcolo dei giorni lavorativi per dividere il numero delle ore totali e sapere quante ore lavoro mi servono al giorno
  • Re: Problema con calcolo giorni lavorativi

    bean_bandit ha scritto:


    ...
    Mmmm... i problemi cominciano ad iniziare se devo ordinare per "giorni lavorativi"
    Cosa intendi con ordinare? nel mio caso uso il calcolo dei giorni lavorativi per dividere il numero delle ore totali e sapere quante ore lavoro mi servono al giorno
    Solo dopo aver visto che si tratta di una query a campi incrociati ho intuito vagamente qualcosa. Pensando che si trattasse di una normale query di selezione mi interessava sapere se sul campo (perché ipotizzavo che ce ne fosse uno) "giorni lavorativi" ci fosse qualche operazione di ordinamento.
    Seppur in una situazione diversa, la lentezza penso che derivi dallo stesso motivo cioè la necessità che ha il programma di eseguire la funzione all'apertura della query per tutti i record e non solo per quelli che visualizza e che ha "caricato in memoria".
    Mi spiego: all'inizio ho detto che su oltre 20000 record l'apertura della query era immediata. Vero. Però ha eseguito la funzione meno di 200 volte, cioè per i record che gli interessavano in quel momento. Se comincio a scorrere l'elenco, lo scorrimento non è così fluido, proprio perché man mano che carica nuovi record dalla tabella deve eseguire la funzione. E si tratta comunque di un rallentamento "comprensibile" (non per l'utente ma per chi sa cosa ci sta dietro).
    Nel (mio) caso dell'ordinamento e, presumo, della (tua) query a campi incrociati c'è la necessità di elaborare tutto fin da subito ed ecco la lentezza.
    Questo è, nel mio piccolo, quello che sono riuscito a capire. Quindi non ho né soluzioni né suggerimenti di altro tipo.
  • Re: Problema con calcolo giorni lavorativi

    Philcattivocarattere ha scritto:


    Questa è, nel mio piccolo, quello che sono riuscito a capire. Quindi non ho né soluzioni né suggerimenti di altro tipo.
    Ti ringrazio lo stesso spero qualcuno abbia qualche dritta da darmi in merito!
  • Re: Problema con calcolo giorni lavorativi

    Fai una Prova e realizza la query standard con il calcolo, magari cercando, se puoi, di restringere i records con il filtro in questa query delegando poi alla Cross query il solo compito di rappresentazione.
    Se però devi fare un Report, anche se dubito avendo una Cross query, converrebbe provare a delegare il calcolo all'evento Format....
  • Re: Problema con calcolo giorni lavorativi

    @Alex ha scritto:


    Fai una Prova e realizza la query standard con il calcolo, magari cercando, se puoi, di restringere i records con il filtro in questa query delegando poi alla Cross query il solo compito di rappresentazione.
    Se però devi fare un Report, anche se dubito avendo una Cross query, converrebbe provare a delegare il calcolo all'evento Format....
    In realta... devo fare entrambe le cose, sto usando la cross query per fare il calcolo ore\giorni lavorativi.
    Ho usato questo sistema perche vado a prendere l'intestazione colonne da una tabella che ho chiamato calendario e che posso facilmente aggiornare di mese in mese.

    Nella cross query faccio il calcolo ore\giorni lavorativi cosi da ottenere quante ore mi servono per ogni attivita ed ogni commessa al giorno lasciando la domenica come giorno di riposo (dovrei poi aggiungere una seconda variabile nel calcolo e cioe che i sabati valgono la meta, ma lo faro dopo)

    Da questa cross query con la formattazione condizionale mediante maschera ricavo un diagramma di gantt che mi da la schedulazione visiva delle attivita.

    Da una seconda cross query sviluppata sulla base della prima ho la somma di ore per attivita, cosi da ottenere un numero che rappresenta il carico totale di ore in ogni giorno per quella particolare attivita
    Questa cross query viene poi letta da un report basato sull DB Northwind_GRAPH che avevi condiviso tempo fa e che ho modificato (come output grafico ho cmq qualcosa di molto simile)

    In serata cerco di provare con la query semplice, se hai suggerimenti su come impostare le attivita che sto facendo sono sempre ben accetti
  • Re: Problema con calcolo giorni lavorativi

    Ciao Alex,

    nei giorni scorsi ho fatto diverse prove compresa quella che mi hai suggerito:
    Ho creato una query di selezione e lanciata da li la funzione GetWorkDaysNum scritta in questo modo:
    Giorni: GetWorkDaysNum([Data Inzio];[Data Fine prevista];6)
    la query è questa:
    SELECT [10 Commesse].[Nome progetto], [10 Commesse].ODV, [20 Macchine].PosizioneODV, [20 Macchine].[Macchina in Fornitura], [20 Macchine].Modello, [20 Macchine].ID, [30 Attivita macchine].[N° Operazione], [30 Attivita macchine].Attività, [30 Attivita macchine].[Data Inzio], [30 Attivita macchine].[Data Fine prevista], [30 Attivita macchine].[Ore previste], [10 Commesse].Stato, [30 Attivita macchine].Codice_Somma, Colori_Ore.Priorita, GetWorkDaysNum([Data Inzio],[Data Fine prevista],6) AS Giorni
    FROM [10 Commesse] LEFT JOIN ([20 Macchine] LEFT JOIN ([30 Attivita macchine] LEFT JOIN Colori_Ore ON [30 Attivita macchine].Codice_Somma = Colori_Ore.Desc_Attivita) ON [20 Macchine].ID = [30 Attivita macchine].Id_macchina_riferimento) ON [10 Commesse].ID = [20 Macchine].Progetto
    WHERE ((([30 Attivita macchine].[Data Inzio]) Is Not Null) AND (([10 Commesse].Stato)="In corso") AND (([30 Attivita macchine].Codice_Somma) Is Not Null) AND ((Colori_Ore.Priorita) Is Not Null))
    ORDER BY Colori_Ore.Priorita;


    il codice che richiama è questo:
    Public Function GetWorkDaysNum(DateStart As Date, DateStop As Date, Optional WorkDays As Integer = 6) As Long
        Dim dtStart As Date
        Dim dtStop  As Date
        Dim dtAct   As Date
        Dim lngTot  As Long
        Dim lngTotW As Long
        Dim x       As Long
        Dim blFest  As Boolean
        Dim conta
       
        dtStart = DateStart
        dtStop = DateStop
        dtAct = DateStart
        
        lngTot = DateDiff("d", dtStart, dtStop) + 1
        Do Until dtAct > dtStop
            blFest = False
            ' Controllo se il G è all'interno della Settimana LUNGA/CORTA
            ' quindi se è SABATO o DOMENICA.
            If Weekday(dtAct, vbMonday) > WorkDays Then blFest = True
            
            ' Nel caso non sia già SABATO/DOMENICA controllo che non sia
            ' una FESTIVITA'.
            If blFest = False Then blFest = Festivo2(dtAct)
            x = x - blFest
            dtAct = DateAdd("d", 1, dtAct)
       Loop
       
        lngTotW = lngTot - x
        GetWorkDaysNum = lngTotW
        
    End Function
    Function Festivo2(ByVal mydate As Date, Optional domenica As Boolean = True, Optional sabato As Boolean = False) As Boolean
    Dim Festivo
        If sabato = True Then
            Festivo = Weekday(mydate, vbMonday) = 6
            If Festivo Then: Exit Function
        End If
    
        If domenica = True Then
            Festivo = Weekday(mydate, vbMonday) = 7
            If Festivo Then: Exit Function
        End If
        
        Festivo = (Easter(year(mydate)) = mydate)
        If Festivo Then Exit Function
        
        Festivo = (Easter(year(mydate)) + 1 = mydate)
        If Festivo Then Exit Function
        
        ' Controllo le Festività RICORRENTI
        Dim rs         As DAO.Recordset
        Dim sSQL       As String
        sSQL = "SELECT * FROM T_FL WHERE Giorno=" & Day(mydate) & " AND Mese=" & Month(mydate) & " AND Attivo=-1"
        Set rs = DBEngine(0)(0).OpenRecordset(sSQL, dbOpenDynaset, dbReadOnly)
        ' Se il rs è vuoto significa che la data non è inserita come FESTIVITA'
        If rs.EOF = False Then Festivo = True
        rs.Close
        Set rs = Nothing
        If Festivo Then Exit Function
        
        ' Controllo le Festività ONE-SHOT
        sSQL = "SELECT * FROM T_FL WHERE Giorno=" & Day(mydate) & " AND Mese=" & Month(mydate) & " AND Anno=" & year(mydate) & " AND Attivo=-1"
        Set rs = DBEngine(0)(0).OpenRecordset(sSQL, dbOpenDynaset, dbReadOnly)
        ' Se il rs è vuoto significa che la data non è inserita come FESTIVITA'
        If rs.EOF = False Then Festivo = True
        rs.Close
        Set rs = Nothing
    End Function
    ' CALCOLO DELLA PASQUA
    ' Questa funzione non è mia ma è recuperata dal WEB
    Public Function Easter(year As Integer) As Date
       Dim d As Integer
       d = (((255 - 11 * (year Mod 19)) - 21) Mod 30) + 21
       Easter = DateSerial(year, 3, 1) + d + (d > 48) + 6 - ((year + year \ 4 + d + (d > 48) + 1) Mod 7)
    End Function
    lanciandolo cosi funziona ma la query diventa lentissima anche in visualizzazione, se la scorri "rallenta" come se ricalcolasse qualcosa inoltre cliccando sulle varie celle ha dei ritardi a selezionarle nell'ordine del mezzo secondo.

    Infine dal 13/05/16 al 21/06/16 la formula mi calcola 34 giorni, non dovrebbero essere 33?

    Richiamo bene la funzione? ho fatto qualche casino copiando il codice? onestamente non so piu cosa provare, c'è un qualche errore stupido che non vedo?

    Edit: avevo provato anche una query piu semplice, stesso problema di rallentamento
  • Re: Problema con calcolo giorni lavorativi

    Pretendere sia un fulmine una query così è decisamente poco sensato con tutti quei JOIN sai quante volte viene ripetuo il Calcolo... se poi ci metti eventuali errori di Indicizzazione ne incrementi la poca funzionalità.

    Ti suggerivo di LIMITARE alla sola Query di Calcolo giorni lavorativi per comprendere dove il processo si rallenta... quando poi unisci le Singole Query... anche nei JOIN.
    Sai che i JOIN devono essere fatti con Campi INDICIZZATI... ecc...!

    Altro non riesco a dirti.
  • Re: Problema con calcolo giorni lavorativi

    Ho scritto la query che uso (effettivamente posso fare il calcolo e poi unirli) ma ho provato anche una query molto piu semplice:
    SELECT [30 Attivita macchine].[Data Inzio], [30 Attivita macchine].[Data Fine prevista], GetWorkDaysNum([Data Inzio],[Data Fine prevista],6) AS Giorni
    FROM [30 Attivita macchine]
    WHERE ((([30 Attivita macchine].[Data Inzio]) Is Not Null) AND (([30 Attivita macchine].Codice_Somma) Is Not Null));
    Stessa storia pero, query su una tabella singola, i record sono 180 ma con con gli "is not null" diventano 40 (senza ci vuole molto piu tempo). Cosi la query impiega circa 8 secondi ad essere calcolata...

    Edit:
    Ho creato un nuovo DB perche temevo ci potesse essere qualcosa che desse fastidio, copiato ed incollato il codice e creata tabella nuova con solo data inizio e fine, query per calcolare i giorni, stesso problema.
  • Re: Problema con calcolo giorni lavorativi

    Aggiungo anche che il rallentamento sparisce se escludo la verifica dei giorni festivi da tabella T_FL
  • Re: Problema con calcolo giorni lavorativi

    Credo sia ovvio, e ti dirò di più, maggiore è il Differenziale tra DataInizio e DataFine, maggiore è il tempo di Esecuzione... perchè, come puoi osservare nella Funzione, ad ogni passata esegue un Ciclo da DataInizo e DataFine di 1gg alla volta...

    Una soluzione potrebbe essere ottimizzare il tutto e riscrivere la Funzione facendo 2 processi in serie.

    Il primo deve determinare la DataMinima e la DataMassima dell'intervallo della Query Finale(e questo è semplice con una Query Raggruppata in MIN e MAX), e riempie una Tabella Aggiuntiva con i GIORNI FESTIVI all'interno del Periodo ricavati con la funzione GetWorkDaysNum(...) usata solo 1 volta estemporanea esclusivamente in VBA.

    Quindi il calcolo dei giorni lavorativi nella Query non verrebbe più fatto dalla Funzione GetWorkDaysNum(...) ma dato il GiornoInizio ed il GiornoFine di ogni Record si può calcolare il DateDiff(numero giorni complessivo) al quale Sottrarre il Numero di GG che, all'interno della Tabella(sopra) rientrano in queste date(Query Raggruppata con COUNT(*) e WHERE sui Campi Data della Query Madre).

    Con la speranza di essermi fatto capire come concetto esposto, sono convinto che avresti un sorprendente aumento di velocità.

    Ovviamente ora lascio a te l'implementazione, che ritengo molto interessante.
Devi accedere o registrarti per scrivere nel forum
13 risposte