Factor over tijden berekenen met mysql

Overzicht Reageren

Sponsored by: Vacatures door Monsterboard

Peter K

Peter K

18/11/2014 08:28:14
Quote Anchor link
Beste PHPhulp,

Ik heb de volgende database:

Database gegevens
Tabel a:
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
9
10
11
CREATE TABLE `a` (
  `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
  `tijd_in` TIME NULL,
  `tijd_uit` TIME NULL,
  PRIMARY KEY (`id`),
  UNIQUE INDEX `id_UNIQUE` (`id` ASC));

INSERT INTO `a` (`tijd_in`, `tijd_uit`) VALUES ('18:00:00', '20:00:00');
INSERT INTO `a` (`tijd_in`, `tijd_uit`) VALUES ('17:00:00', '21:00:00');
INSERT INTO `a` (`tijd_in`, `tijd_uit`) VALUES ('17:00:00', '19:00:00');
INSERT INTO `a` (`tijd_in`, `tijd_uit`) VALUES ('19:00:00', '21:00:00');


Tabel b:
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
9
10
CREATE TABLE `b` (
  `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
  `factor` DECIMAL(3,2) NOT NULL,
  `tijd_in` TIME NULL,
  `tijd_uit` TIME NULL,
  PRIMARY KEY (`id`, `factor`),
  UNIQUE INDEX `id_UNIQUE` (`id` ASC));

INSERT INTO `b` (`factor`, `tijd_in`, `tijd_uit`) VALUES ('1.50', '00:00:00', '04:00:00');
INSERT INTO `b` (`factor`, `tijd_in`, `tijd_uit`) VALUES ('1.25', '18:00:00', '20:00:00');


In tabel a staan diverse tijden waarover conform tabel b een bepaalde factor overheen moet.


Voorbeeld:
Regel 1 van tabel a:
18:00:00 valt onder de factor van tabel b met id = 2.
Over de tijd 20:00:00 - 18:00:00 = 2 uur moet dus een factor van 1.25.
2 * 1.25 = 2.5 als eindresultaat.

Regel 2 van tabel a:
17:00:00 valt ook onder de factor van tabel b met id = 2
* Over de tijd van 17:00:00 t/m 18:00:00 wordt geen factor berekend
* Over de tijd van 18:00:00 t/m 20:00:00 wordt wel een factor berekend
* Over de tijd van 20:00:00 t/m 21:00:00 wordt weer geen factor berekend

Zo zijn er dus diverse opties mogelijk.
In tabel b staat dus de tijd wanneer zo'n factor berekend moet worden over tabel a heen.

Huidige situatie:
Voor elke factor wordt er nu handmatig vergeleken in de query. Dit moet geautomatiseerd worden.
Dit is een voorbeeld van hoe een deel wordt gedaan nu. Dit gebeurd voor alle mogelijke opties en toeslagen.
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
9
10
11
CASE
    WHEN
        (
            tijd_in <= '00:00:00' AND tijd_uit > '00:00:00' AND tijd_uit < '6:00:00'
        )
    THEN
        (
            ROUND((TIME_TO_SEC(TIMEDIFF(TIME('06:00:00'), tijd_uit))/3600),2)
        )
    ELSE
        (.........


Conclusie:
Bovenstaande situatie zou ik graag willen realiseren. Er is al een bestaande situatie, maar wil deze vernieuwen om performance winst te boeken. Het kan totaal om duizenden/tienduizenden regels gaan waarover dit berekend moet worden.

Hoe kan ik deze situatie het beste aanpakken? Alvast bedankt voor je hulp.
Gewijzigd op 18/11/2014 08:38:14 door Peter K
 
PHP hulp

PHP hulp

16/11/2024 18:56:26
 
Ward van der Put
Moderator

Ward van der Put

18/11/2014 08:56:58
Quote Anchor link
>> Er is al een bestaande situatie, maar wil deze vernieuwen om performance winst te boeken.

Dan voor die performance eerst even vier andere dingen:

1. Op de primaire sleutels hoef je geen UNIQUE INDEX te zetten.

2. In de tweede tabel b met de factoren per tijdvak mag niets nullable zijn. Je hebt anders namelijk een onbruikbaar en dus ongeldig tijdvak.

3. In de tweede tabel kun je de primaire sleutel verkleinen van een INT (4 bytes) naar een SMALLINT (2 bytes) of zelfs een TINYINT (1 byte). Zo veel tijdvakken zijn er namelijk niet.

4. Sla het berekende resultaat op in een extra tabel c met een primaire sleutel die refereert naar de primaire sleutel van tabel a. Met andere woorden: bouw een cache, zodat je alleen nog nieuwe tijdvakken hoeft door te rekenen.
Gewijzigd op 18/11/2014 08:57:28 door Ward van der Put
 
Peter K

Peter K

18/11/2014 09:07:21
Quote Anchor link
@ Ward,

Bedankt voor je snelle reactie. Bovenstaand was een voorbeeld snel aangemaakt.

Punten 1, 2 en 3 zijn in de huidige situatie wel in orde. Excuses hiervoor.

Punt 4:
Ik wil werken zonder een extra tabel waar hard data in staat. Zodra tabel a namelijk veranderd moet alles realtime kloppen. Na een update in tabel a wil ik dus niet tabel c weer moeten herberekenen.
 
Ward van der Put
Moderator

Ward van der Put

18/11/2014 09:26:54
Quote Anchor link
Maar tabel a verandert zelden helemaal (of waarschijnlijker: nooit). Meestal zal er een INSERT worden uitgevoerd voor nieuwe tijdregistraties en incidenteel een UPDATE voor een correctie. Dat betekent dat je het herberekenen kunt beperken tot records die (nog) niet voorkomen in tabel c met de uitkomsten.

Wat je al hebt berekend, hoef je niet steeds realtime opnieuw te berekenen: dáár zit de performancewinst als je tienduizenden registraties hebt en er dagelijks maar enkele honderden bijkomen.
 
Ger van Steenderen
Tutorial mod

Ger van Steenderen

18/11/2014 17:30:07
Quote Anchor link
Hoe nauwkeurig moet het zijn? Want nu staan in het voorbeeld netjes hele uren, maar in de berekening wordt afgerond naar 2 cijfers achter de komma.

Wat ik opmaak uit de OP is dat een tijdsregistratie meerdere factoren kan bevatten, dus dan moet er een lookup table komen voor minimaal ieder uur van de dag.

En heb je de query al wel eens zonder de berekening uitgevoerd?
 
Peter K

Peter K

19/11/2014 14:12:50
Quote Anchor link
Bedankt voor de reacties.

Het idee van het systeem is dat er tijden ingevuld worden in tabel a door de gebruiker. Deze kunnen ten alle tijden gewijzigd worden. (ook in het verleden nog aanpasbaar) Tijdens het aanpassen wordt er een nieuwe regel aangemaakt met de gewijzigde tijden en de huidige regel wordt op niet actief gezet.

Uiteindelijk wil ik een eindtotaal kunnen opmaken van de uren + eventuele toeslagen. Dit over bijvoorbeeld een bereik van bijvoorbeeld 10 dagen t/m 1 jaar.

De nauwkeurigheid maakt niet zo heel veel uit. Laat ons uitgaan van 2 decimalen.
Het gaat nu nog niet perse om de snelheid, maar om het verhaal hoe de query tot stand kan komen.

Zelf heb ik dit nu gevonden als een optie:

Overlappende uren vinden
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
TIMEDIFF((LEAST(a.tijd_uit,b.tijd_uit)),(GREATEST(a.tijd_in,b.tijd_in))) as overlappende_uren


Vermenigvuldigd met factor
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
SEC_TO_TIME((TIME_TO_SEC(TIMEDIFF((LEAST(a.tijd_uit,b.tijd_uit)),(GREATEST(a.tijd_in,b.tijd_in))))*b.factor) - TIME_TO_SEC(TIMEDIFF((LEAST(a.tijd_uit,b.tijd_uit)),(GREATEST(a.tijd_in,b.tijd_in))))) as toeslag_uren


Kan dit wellicht simpeler of beter/effecitiever?
 
Eddy E

Eddy E

19/11/2014 14:27:26
Quote Anchor link
Maakt het voor TIMEDIFF uit welke tijd er eerst komt? Er is toch ook een datum bekend?
 
Peter K

Peter K

19/11/2014 14:46:46
Quote Anchor link
Dat durf ik niet te zeggen of het uitmaakt. Bij mij is het om te bepalen of er dus gedeeltelijke overlap is of gehele of geen overlap.

Een datum is niet bekend in dit geval.
 
Ger van Steenderen
Tutorial mod

Ger van Steenderen

19/11/2014 16:27:16
Quote Anchor link
Het maakt net zo veel uit als de volgorde in een aftreksom ......

Maar in openingspost stel je dat de performance wilt verbeteren.

Denk je nu echt dat het introduceren van een extra tabel, en het vervangen van CASE door greater/least daar aan toe bijdraagt?
 
Peter K

Peter K

20/11/2014 10:27:03
Quote Anchor link
Bedankt voor je reactie Ger.

Ik wil inderdaad performance verbeteren, maar daarnaast ook uitbreiden.

Het gaat niet om het vervangen van 1 case in dit geval maar om het vervangen van meerdere cases. Gezien het aantal cases ook variabel moet worden zal ik dus gaan moeten werken met een extra tabel dacht ik? De gebruiker moet namelijk zelf kunnen kiezen welke toeslag en wanneer deze berekend wordt. Vandaar dat ik dus met het idee van de extra tabel gekomen ben.

De case die er nu in staat is ook nog vele malen groter en onlogisch opgebouwd. Als ik deze netjes opsplits als in de beginpost bij huidige situatie, loopt deze op tot 50 regels +/-. Dit kan ik nu vervangen met een LEFT JOIN van de toeslag en vervolgens dit als stuk in de query:

Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
SEC_TO_TIME((TIME_TO_SEC(TIMEDIFF((LEAST(a.tijd_uit,b.tijd_uit)),(GREATEST(a.tijd_in,b.tijd_in))))*b.factor) - TIME_TO_SEC(TIMEDIFF((LEAST(a.tijd_uit,b.tijd_uit)),(GREATEST(a.tijd_in,b.tijd_in))))) as toeslag_uren


Daarnaast is het nu ook nog zo dat het stuk script in PHP geloopt wordt. Dit wil ik nu ook door mysql gaan doen door het gebruik van 'SUM' en 'WITH ROLLUP'.

Maar wat je aangeeft, is een extra tabel erg slecht voor de performance dan vergeleken met een heleboel cases? Is het niet sneller om gebruik te maken van kleine tabellen met kleine queries?

Alvast bedankt voor jullie input,
 
Ward van der Put
Moderator

Ward van der Put

20/11/2014 14:17:40
Quote Anchor link
Je moet de business case helder hebben voordat je aan andere cases gaat sleutelen.

Bijvoorbeeld:

>> De gebruiker moet namelijk zelf kunnen kiezen welke toeslag en wanneer deze berekend wordt.

Dat kun je op meerdere manieren lezen. De meeste complexe: elke gebruiker kan voor zijn eigen urenregistratie én vervolgens per klant/project andere toeslagen/kortingen rekenen. Dan krijg je er één, twee of drie sleutels bij (gebruiker, klant en/of project).

Verder denk ik dat je best hier en daar wat keuzen mag maken. Bijvoorbeeld: die factoren per tijdvak worden in de regel gebruikt om overuren te factureren/vergoeden met een toeslag. Op een gegeven moment komt daaraan echter een eind: je gaat niet facturen van 3 jaar geleden of loonstroken van 5 jaar geleden herzien. Die data hoef je dus ook niet meer realtime door te rekenen.

Alle mogelijkheden openlaten en niets uitsluiten is soms lastig te automatiseren.
 



Overzicht Reageren

 
 

Om de gebruiksvriendelijkheid van onze website en diensten te optimaliseren maken wij gebruik van cookies. Deze cookies gebruiken wij voor functionaliteiten, analytische gegevens en marketing doeleinden. U vindt meer informatie in onze privacy statement.