Hauptsächlich habe ich zwei Arten von Zeitintervallen:
presence time
und absence time
absence time
Es kann sich um verschiedene Arten handeln (z. B. Pausen, Abwesenheiten, besondere Tage usw.), und Zeitintervalle können sich überschneiden und / oder überschneiden.
Es ist nicht sicher, dass in Rohdaten nur plausible Kombinationen von Intervallen existieren, z. Überlappende Anwesenheitsintervalle sind nicht sinnvoll, können aber existieren. Ich habe jetzt auf viele Arten versucht, die resultierenden Anwesenheitszeitintervalle zu identifizieren - für mich scheint das bequemste das folgende zu sein.
;with "timestamps"
as
(
select
"id" = row_number() over ( order by "empId", "timestamp", "opening", "type" )
, "empId"
, "timestamp"
, "type"
, "opening"
from
(
select "empId", "timestamp", "type", case when "types" = 'starttime' then 1 else -1 end as "opening" from
( select "empId", "starttime", "endtime", 1 as "type" from "worktime" ) as data
unpivot ( "timestamp" for "types" in ( "starttime", "endtime" ) ) as pvt
union all
select "empId", "timestamp", "type", case when "types" = 'starttime' then 1 else -1 end as "opening" from
( select "empId", "starttime", "endtime", 2 as "type" from "break" ) as data
unpivot ( "timestamp" for "types" in ( "starttime", "endtime" ) ) as pvt
union all
select "empId", "timestamp", "type", case when "types" = 'starttime' then 1 else -1 end as "opening" from
( select "empId", "starttime", "endtime", 3 as "type" from "absence" ) as data
unpivot ( "timestamp" for "types" in ( "starttime", "endtime" ) ) as pvt
) as data
)
select
T1."empId"
, "starttime" = T1."timestamp"
, "endtime" = T2."timestamp"
from
"timestamps" as T1
left join "timestamps" as T2
on T2."empId" = T1."empId"
and T2."id" = T1."id" + 1
left join "timestamps" as RS
on RS."empId" = T2."empId"
and RS."id" <= T1."id"
group by
T1."empId", T1."timestamp", T2."timestamp"
having
(sum( power( 2, RS."type" ) * RS."opening" ) = 2)
order by
T1."empId", T1."timestamp";
Einige Demo-Daten finden Sie unter SQL-Fiddle .
Die Rohdaten liegen in verschiedenen Tabellen in Form von "starttime" - "endtime"
oder vor "starttime" - "duration"
.
Die Idee war, eine geordnete Liste jedes Zeitstempels mit einer "bitmaskierten" fortlaufenden Summe offener Intervalle zu jedem Zeitpunkt zu erhalten, um die Anwesenheitszeit abzuschätzen.
Die Geige funktioniert und liefert geschätzte Ergebnisse, auch wenn die Startzeiten in unterschiedlichen Intervallen gleich sind. In diesem Beispiel werden keine Indizes verwendet.
Ist dies der richtige Weg, um eine fragliche Aufgabe zu erfüllen, oder gibt es dafür einen eleganteren Weg?
Falls für die Beantwortung relevant: Die Datenmenge beträgt bis zu mehreren zehntausend Datensätze pro Mitarbeiter und Tabelle. sql-2012 ist nicht verfügbar, um eine fortlaufende Summe der Vorgänger inline insgesamt zu berechnen.
bearbeiten:
Führen Sie die Abfrage gerade für eine größere Anzahl von Testdaten (1000, 10.000, 100.000, 1 Million) aus und sehen Sie, dass die Laufzeit exponentiell zunimmt. Offensichtlich eine Warnflagge, oder?
Ich habe die Abfrage geändert und die Aggregation der fortlaufenden Summe durch ein eigenartiges Update entfernt.
Ich habe eine Hilfstabelle hinzugefügt:
create table timestamps
(
"id" int
, "empId" int
, "timestamp" datetime
, "type" int
, "opening" int
, "rolSum" int
)
create nonclustered index "idx" on "timestamps" ( "rolSum" ) include ( "id", "empId", "timestamp" )
und ich habe die Berechnung der rollierenden Summe an diesen Ort verschoben:
declare @rolSum int = 0
update "timestamps" set @rolSum = "rolSum" = @rolSum + power( 2, "type" ) * "opening" from "timestamps"
Die Laufzeit verringerte sich auf 3 Sekunden in Bezug auf 1 Million Einträge in der "Arbeitszeit" -Tabelle.
Die Frage bleibt gleich : Was ist der effektivste Weg, um dies zu lösen?
[this]
. Ich mag das einfach besser als doppelte Anführungszeichen, denke ich.Antworten:
Ich kann Ihre Frage nach dem absolut besten Weg nicht beantworten. Aber ich kann einen anderen Weg zur Lösung des Problems anbieten , der vielleicht besser ist oder nicht. Es hat einen ziemlich flachen Ausführungsplan, und ich denke, es wird gut funktionieren. (Ich bin gespannt, also teile die Ergebnisse!)
Ich entschuldige mich dafür, dass ich meinen eigenen Syntaxstil anstelle Ihres verwendet habe - er hilft mir, Abfrage-Assistenten zu finden, wenn alles an seinem gewohnten Platz ausgerichtet ist.
Die Abfrage ist in einer SqlFiddle verfügbar . Ich habe EmpID 1 überlappt, nur um sicherzugehen, dass ich das abgedeckt habe. Wenn Sie schließlich feststellen, dass Überlappungen in Anwesenheitsdaten nicht auftreten können, können Sie die endgültige Abfrage und die
Dense_Rank
Berechnungen entfernen .Hinweis: Die Leistung dieser Abfrage würde verbessert. Sie haben die drei Tabellen kombiniert und eine Spalte hinzugefügt, um anzugeben, wie lange es gedauert hat: Arbeit, Pause oder Abwesenheit.
Und warum all die CTEs, fragst du? Weil jeder von dem gezwungen wird, was ich mit den Daten tun muss. Es gibt ein Aggregat, oder ich muss eine WHERE-Bedingung für eine Fensterfunktion setzen oder sie in einer Klausel verwenden, in der Fensterfunktionen nicht zulässig sind.
Jetzt werde ich nachsehen, ob ich mir keine andere Strategie ausdenken kann, um dies zu erreichen. :) :)
Zur Unterhaltung füge ich hier ein "Diagramm" hinzu, das ich zur Lösung des Problems erstellt habe:
Die drei Sätze von Strichen (durch Leerzeichen getrennt) repräsentieren in der Reihenfolge: Anwesenheitsdaten, Abwesenheitsdaten und das gewünschte Ergebnis.
quelle