Information ausblenden
Willkommen im Forum für alle Datenbanken! Registriere Dich kostenlos und diskutiere über DBs wie Mysql, MariaDB, Oracle, Sql-Server, Postgres, Access uvm

Zeitspanne nur einmalig für eine Summe werten

Dieses Thema im Forum "MySQL und MariaDB" wurde erstellt von Nenaro, 16 Oktober 2015.

  1. Nenaro

    Nenaro Neuer Benutzer

    Guten Morgen zusammen!

    Aktuell plagt mich ein Problem, dessen Lösung ich noch nicht gefunden habe.
    Im Grund geht es um eine Art Zeiterfassung, in der parallel mehrere Zeiten an einem Tag zu einem Nutzer eingetragen sein können. Die vergangenen Zeitspannen dieser Einträge dürfen aber in Summe nicht doppelt gewertet werden.

    Ein Beispiel:
    1. Eintrag: 16.10.2015 08:00 bis 12:00
    2. Eintrag: 16.10.2015 09:00 bis 13:00
    3. Eintrag: 16.10.2015 15:00 bis 17:00

    Nun muss ich eine Summe der Dauer des 16.10.2015 ziehen, in der keine Zeitabschnitte doppelt gerechnet werden dürfen. Im Beispiel oben darf die Summe nicht 10 Stunden ergeben sondern 7 Stunden.

    Die Felder in denen die Zeitangaben eingetragen werden sind DATETIME Format.

    Ich wäre über Ideen und Gedankenanstöße sehr dankbar.
    Beste Grüße, Nenaro
     
  2. ukulele

    ukulele Datenbank-Guru

    Also zunächst käme mir nur ein Weg in den Sinn: Alle Tageszeiten in Intervalle aufbröseln und Intervalle, die von deinen Zeitspannen abgedreckt werden, addieren. Das ist nicht sehr elegant und vor allem mit MySQL ein Krampf. Vieleicht fällt mir noch was ein.
     
  3. Nenaro

    Nenaro Neuer Benutzer

    Diesen Ansatz bin ich schon angegangen. Da ein minütlicher Intervall sein muss habe ich 1440 Abschnitte pro Nutzer und Tag.
    Insgesamt wird der ganze Vorgang mitunter sehr träge bis ich dann Werte erhalte. Auf einen Nutzer und einen Monat gesehen geht das noch recht schnell, aber sobald ich ein Ergebnis für 3 Monate und 5 Nutzer möchte kommt die Kaffeemaschine ins Spiel (;
     
  4. akretschmer

    akretschmer Datenbank-Guru

    Mit MySQL hast Du da bei der Wahl der DB in der Tat recht tief ins Klo gegriffen. Ich zeige das mal mit PostgreSQL und seinen Möglichkieten, hier Range-Typen und Window-Funktionen.

    Du hast:

    Code:
    privat=*# select * from arbeitszeiten ;
     mitarbeiter_id |  von_bis
    ----------------+-----------------------------------------------
      1 | ["2015-10-16 08:00:00","2015-10-16 12:00:00")
      1 | ["2015-10-16 09:00:00","2015-10-16 13:00:00")
      1 | ["2015-10-16 15:00:00","2015-10-16 17:00:00")
    (3 rows)
    
    Und nun

    Code:
    privat=*# select *, von_bis * lag(von_bis) over (partition by mitarbeiter_id order by lower(von_bis)) as vorgaenger, upper(von_bis)-lower(von_bis) as stunden, coalesce(upper(von_bis * lag(von_bis) over (partition by mitarbeiter_id order by lower(von_bis))) - lower(von_bis * lag(von_bis) over (partition by mitarbeiter_id order by lower(von_bis)))) from arbeitszeiten ;
     mitarbeiter_id |  von_bis  |  vorgaenger  | stunden  | coalesce
    ----------------+-----------------------------------------------+-----------------------------------------------+----------+----------
      1 | ["2015-10-16 08:00:00","2015-10-16 12:00:00") |  | 04:00:00 |
      1 | ["2015-10-16 09:00:00","2015-10-16 13:00:00") | ["2015-10-16 09:00:00","2015-10-16 12:00:00") | 04:00:00 | 03:00:00
      1 | ["2015-10-16 15:00:00","2015-10-16 17:00:00") | empty  | 02:00:00 |
    (3 rows)
    
    In vorgaenger hast die vorhergenden Arbeitszeit bzw. das was sich diese mit der gegenwärtigen überlappt, das macht hier der * - Operator. Er vergleicht zwei Ranges und gibt das zurück, was beide gemeinsam haben. In Stunden die aktuellen Stunden für die Zeile, in der coalesce-Spalte das, was aus der vorher berechneten Überlappung an Stunden abzuziehen ist.

    Hier noch mal etwas besser:

    Code:
    privat=*# select *, von_bis * lag(von_bis) over (partition by mitarbeiter_id order by lower(von_bis)) as vorgaenger, upper(von_bis)-lower(von_bis) as stunden, coalesce(upper(von_bis * lag(von_bis) over (partition by mitarbeiter_id order by lower(von_bis))) - lower(von_bis * lag(von_bis) over (partition by mitarbeiter_id order by lower(von_bis))),'0minutes'::interval) as abzug from arbeitszeiten ;
     mitarbeiter_id |  von_bis  |  vorgaenger  | stunden  |  abzug
    ----------------+-----------------------------------------------+-----------------------------------------------+----------+----------
      1 | ["2015-10-16 08:00:00","2015-10-16 12:00:00") |  | 04:00:00 | 00:00:00
      1 | ["2015-10-16 09:00:00","2015-10-16 13:00:00") | ["2015-10-16 09:00:00","2015-10-16 12:00:00") | 04:00:00 | 03:00:00
      1 | ["2015-10-16 15:00:00","2015-10-16 17:00:00") | empty  | 02:00:00 | 00:00:00
    (3 rows)
    
    Und nun summiert:

    Code:
    privat=*# select sum(stunden-abzug) from (select *, von_bis * lag(von_bis) over (partition by mitarbeiter_id order by lower(von_bis)) as vorgaenger, upper(von_bis)-lower(von_bis) as stunden, coalesce(upper(von_bis * lag(von_bis) over (partition by mitarbeiter_id order by lower(von_bis))) - lower(von_bis * lag(von_bis) over (partition by mitarbeiter_id order by lower(von_bis))),'0minutes'::interval) as abzug from arbeitszeiten ) bar;
      sum
    ----------
     07:00:00
    (1 row)
    
    Fragen? Fragen!
     
  5. Nenaro

    Nenaro Neuer Benutzer

    Traumhafte Lösung akretschmer! Nur ist die Wahl der DB systembedingt auf MySQL bereits seit einigen Jahren festgelegt, weil darin die Informationen von unterschiedlichen Systemen zusammengetragen werden.
     
  6. akretschmer

    akretschmer Datenbank-Guru

    Danke ;-)

    Nun ja, ist halt jetzt mehr oder weniger Dein Problem. MySQL kann nur sehr, sehr wenig im Vergleich zu 'richtigen' Datenbanken. Das galt übrigens schon vor einigen Jahren, das ist (meiner Meinung nach) keine wirkliche Entschuldigung. Egal. Das ist natürlich Eure Entscheidung, was (auf lange Sicht) teurer ist: ein Schnitt und Migration auf z.B. PG oder weitere viele Jahre rumwurschteln ...

    PS.: eigentlich sollten sich die Zeiten ja nicht überschneiden dürfen, oder? Das könnte man in PG mit einem Constraint lösen.

    Code:
    privat=*# create table arbeitszeiten(mitarbeiter_id int, von_bis tsrange, exclude using gist(mitarbeiter_id with =, von_bis with &&));
    CREATE TABLE
    privat=*# insert into arbeitszeiten values (1, '[2015-10-16 08:00:00,2015-10-16 12:00:00)');
    INSERT 0 1
    privat=*# insert into arbeitszeiten values (1, '[2015-10-16 15:00:00,2015-10-16 17:00:00)');
    INSERT 0 1
    privat=*# insert into arbeitszeiten values (1, '[2015-10-16 09:00:00,2015-10-16 13:00:00)');
    ERROR:  conflicting key value violates exclusion constraint "arbeitszeiten_mitarbeiter_id_von_bis_excl"
    DETAIL:  Key (mitarbeiter_id, von_bis)=(1, ["2015-10-16 09:00:00","2015-10-16 13:00:00")) conflicts with existing key (mitarbeiter_id, von_bis)=(1, ["2015-10-16 08:00:00","2015-10-16 12:00:00")).
    privat=*#
    
    MySQL kann keine Check-Constraint und erst recht nicht solche doch recht komplexen Constraints.

    Das mal nur so als 'Blick übern Tellerrand', was abseits von MySQL so Stand der Technik ist.
     
  7. ukulele

    ukulele Datenbank-Guru

    In MSSQL würde ich mich an einen Lösungsversuch mit CTE wagen, das geht aber in MySQL nicht. Ich kann dir eigentlich nur zum Holzhammer raten: Berechne es gar nicht Live. Schreibe dir einen Trigger der bei Eingabe eines neuen Zeitraums auf Überschneidungen prüft und entweder die Daten direkt anpasst, die Eingabe verbietet oder zumindest eine weitere Tabelle mit Anpassungen füllt. Das ist nicht elegant, sollte aber robust genug sein.
     
  8. Nenaro

    Nenaro Neuer Benutzer

    Danke ukulele. Glaube ich werde so in der Richtung die Lösung "erwurschteln". Und sollte ein Wert in der Vergangenheit aus irgendeinem Grund durch ein angeschlossenes System verändert werden, dann muss eine Art Plausibilitätsprüfung danach extern ausgelöst werden.
    Live-Werte reichen im Verlauf des aktuellen Tages. Historische Werte dürfen einen Stand von Zeit x haben.

    Für eine generelle Umstellung auf z.B. PG fehlt aktuell leider wirklich die Zeit - und wenn erstmal alles läuft, dann wird nicht der Bedarf gesehen es zu ändern.
    Glaube darin liegt die Berechtigung des Seins dieser Systeme (;

    Danke euch für die Antworten und dem "Blick übern Tellerrand"
    Nenaro
     
  9. akretschmer

    akretschmer Datenbank-Guru

    ACK. Meine Range-Typen hier sind nur das Sahnehäubchen, der Kern der Lösung ist die lag()-Funktion.
     
  10. akretschmer

    akretschmer Datenbank-Guru

    Das ist halt das Problem: man zementiert die schlechte Lösung. Je länger man damit lebt, umso teurer wird irgendwann das Erwachen...
     
    Nenaro gefällt das.
Die Seite wird geladen...

Diese Seite empfehlen

  1. Diese Seite verwendet Cookies. Wenn du dich weiterhin auf dieser Seite aufhältst, akzeptierst du unseren Einsatz von Cookies.
    Information ausblenden