Dans l'ensemble, j'ai deux types d'intervalles de temps:
presence time
et absence time
absence time
peuvent être de différents types (par exemple, pauses, absences, jour spécial, etc.) et les intervalles de temps peuvent se chevaucher et / ou se recouper.
Il n'est pas certain que seules des combinaisons plausibles d'intervalles existent dans les données brutes, par exemple. les intervalles de présence qui se chevauchent n'ont pas de sens, mais peuvent exister. J'ai essayé d'identifier les intervalles de temps de présence résultants de plusieurs façons maintenant - pour moi, le plus confortable semble être celui qui suit.
;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";
voir SQL-Fiddle pour quelques données de démonstration.
Les données brutes existent dans différentes tables sous forme de "starttime" - "endtime"
ou "starttime" - "duration"
.
L'idée était d'obtenir une liste ordonnée de chaque horodatage avec une somme continue "bitmaskée" d'intervalles ouverts à chaque fois pour estimer le temps de présence.
Le violon fonctionne et donne des résultats estimés, même si les heures de début de différents intervalles sont égales. Aucun indice n'est utilisé dans cet exemple.
Est-ce la bonne façon de réaliser la tâche remise en question ou existe-t-il une manière plus élégante pour cela?
Si cela est pertinent pour répondre: la quantité de données peut atteindre plusieurs dizaines de milliers de jeux de données par employé et par table. sql-2012 n'est pas disponible pour calculer une somme cumulée de prédécesseurs en ligne dans l'ensemble.
Éditer:
Vient d'exécuter la requête sur une plus grande quantité de données de test (1000, 10 000, 100 000, 1 million) et peut voir que le temps d'exécution augmente de façon exponentielle. De toute évidence, un drapeau d'avertissement, non?
J'ai changé la requête et supprimé l'agrégation de la somme mobile par une mise à jour excentrique.
J'ai ajouté une table auxiliaire:
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" )
et j'ai déplacé le calcul de la somme mobile à cet endroit:
declare @rolSum int = 0
update "timestamps" set @rolSum = "rolSum" = @rolSum + power( 2, "type" ) * "opening" from "timestamps"
Le temps d'exécution a diminué à 3 secondes pour 1 million d'entrées dans la table "worktime".
La question reste la même : quel est le moyen le plus efficace de résoudre ce problème?
[this]
. J'aime mieux que les guillemets doubles, je suppose.Réponses:
Je ne peux pas répondre à votre question sur la meilleure façon. Mais je peux proposer une manière différente de résoudre le problème, qui peut être meilleure ou non. Son plan d'exécution est relativement plat et je pense qu'il fonctionnera bien. (J'ai hâte de savoir alors partagez les résultats!)
Je m'excuse d'avoir utilisé mon propre style de syntaxe au lieu du vôtre - cela aide la requête de sorcellerie à venir lorsque tout s'aligne à sa place habituelle.
La requête est disponible dans un SqlFiddle . J'ai ajouté un chevauchement pour EmpID 1 juste pour être sûr d'avoir couvert. Si vous constatez finalement que des chevauchements ne peuvent pas se produire dans les données de présence, vous pouvez supprimer la requête finale et les
Dense_Rank
calculs.Remarque: les performances de cette requête seraient améliorées si vous combiniez les trois tables et ajoutiez une colonne pour indiquer le type d'heure: travail, pause ou absence.
Et pourquoi tous les CTE, demandez-vous? Parce que chacun est forcé par ce que je dois faire pour les données. Il y a un agrégat, ou j'ai besoin de mettre une condition WHERE sur une fonction de fenêtrage ou de l'utiliser dans une clause où les fonctions de fenêtrage ne sont pas autorisées.
Maintenant, je vais aller voir si je ne peux pas imaginer une autre stratégie pour y parvenir. :)
Pour le plaisir, j'inclus ici un "diagramme" que j'ai fait pour aider à résoudre le problème:
Les trois ensembles de tirets (séparés par des espaces) représentent, dans l'ordre: les données de présence, les données d'absence et le résultat souhaité.
la source