2010-07-21 7 views
3

J'ai besoin de calculer le nombre de "minutes actives" pour un événement dans une base de données. L'heure de début est bien connue.Calculer la différence de temps (seulement les heures de travail) en minutes entre deux dates

La complication est que ces minutes actives ne devraient être comptés au cours d'une journée de travail - du lundi au vendredi 9 heures-à-18:30, hors week-ends et (connue) liste des jours de vacances

Le début ou de « courant » peut être en dehors des heures de travail, mais seulement les heures de travail sont comptées.

Il s'agit de SQL Server 2005, vous pouvez donc utiliser T-SQL ou un assembly géré.

Répondre

4

Si vous voulez le faire SQL pur est ici une approche

CREATE TABLE working_hours (start DATETIME, end DATETIME); 

Maintenant remplir les heures de travail table avec des périodes dénombrables, ~ 250 lignes par an.

Si vous avez un événement (@event_start, @event_end) qui commencera en dehors des heures et à la fin en dehors des heures puis requête simple

SELECT SUM(end-start) as duration 
FROM working_hours 
WHERE start >= @event_start AND end <= @event_end 

suffit.

Si d'autre part le début de l'événement et/ou se termine pendant les heures de travail de la requête est plus compliquée

SELECT SUM(duration) 
FROM 
(
    SELECT SUM(end-start) as duration 
    FROM working_hours 
    WHERE start >= @event_start AND end <= @event_end 
UNION ALL 
    SELECT [email protected]_start 
    FROM working_hours 
    WHERE @event_start between start AND end 
UNION ALL 
    SELECT @event_end - start 
    FROM working_hours 
    WHERE @event_end between start AND end 
) AS u 

Notes:

  • ci-dessus est question non testé, en fonction de votre SGBDR peut avoir besoin de fonctions date/heure pour agréger et soustraire datetime (et en fonction des fonctions utilisées, la requête ci-dessus peut fonctionner avec une précision temporelle quelconque).
  • la requête peut être réécrite pour ne pas utiliser l'UNION ALL.
  • la table working_hours peut être utilisé pour d'autres choses dans le système et permet une flexibilité maximale

EDIT: En MSSQL vous pouvez utiliser DATEDIFF (mi, début, fin) pour obtenir le nombre de minutes pour chaque soustraction au dessus.

+1

+1 Si les calculs peuvent s'étendre sur plusieurs jours ouvrables (et supposer que les heures de travail et les vacances sont relativement stables), j'ai trouvé utile d'ajouter une colonne pour les heures de travail julianisées. Ainsi, plutôt que d'UNIONing les trois ensembles de résultats faisant alors le SUM, vous feriez le SUM pour chacun puis les additionneriez, excepté maintenant le calcul pour le premier de vos trois resultsets UNIONed est simplement un cas de soustraire deux valeurs (plutôt que sommer la soustraction pour * toutes * les rangées intermédiaires) car elles ont été sommées à l'avance (stockées non calculées). – onedaywhen

+0

brillant ... plus de la moitié d'une solution de code managé - mais c'est beaucoup plus agréable! –

0

À l'échelle mondiale, vous aurez besoin:

  1. Une façon de capturer la fin du temps de l'événement (éventuellement par voie de notification, ou quel que soit l'événement a commencé en premier lieu), et une table d'enregistrer cette heure de début et de fin.
  2. Une table auxiliaire contenant toutes les périodes (début et fin) à compter. (Et alors vous aurez besoin du code de soutien pour garder ce tableau à jour dans le futur)
  3. Une procédure stockée qui:
    • itérer sur cette table d'aide et trouver les périodes « actives »
    • calculer les minutes dans chaque période active.

(Notez que cela suppose l'événement peut durer plusieurs jours: est-ce vraiment possible?)

Une autre méthode serait d'avoir une horloge à l'intérieur de l'événement, qui vérifie chaque fois que l'événement doit être compté à ce moment-là, et incrémente (en secondes ou en minutes) chaque fois qu'il se découvre actif pendant la période concernée. Cela nécessiterait toujours la table d'assistance et serait moins auditable (vraisemblablement).

+0

Si, par itérer, vous voulez dire utiliser des curseurs (ou un autre algorithme basé sur les lignes) alors je ne suis pas d'accord - ce n'est pas nécessaire (voir ma réponse). – Unreason

1

Je suis venu ici à la recherche d'une réponse à une question très similaire - j'avais besoin d'obtenir les minutes entre 2 dates excluant les fins de semaine et excluant les heures en dehors de 08h30 et 18h00. Après un peu de piratage, je pense que je l'ai trié. Voici comment je l'ai fait. pensées sont les bienvenus - qui sait, peut-être un jour je vais inscrire sur ce site :)

create function WorkingMinutesBetweenDates(@dteStart datetime, @dteEnd datetime) 
returns int 
as 
begin 

declare @minutes int 
set @minutes = 0 

while @dteEnd>[email protected] 
    begin 

     if datename(weekday,@dteStart) <>'Saturday' and datename(weekday,@dteStart)<>'Sunday' 
      and (datepart(hour,@dteStart) >=8 and datepart(minute,@dteStart)>=30) 
      and (datepart(hour,@dteStart) <=17) 

      begin 
       set @minutes = @minutes + 1 
      end  

     set @dteStart = dateadd(minute,1,@dteStart) 
    end 

return @minutes 
end 
go 
+0

Je pense que ce sera très lent car c'est une boucle. L'approche basée sur les ensembles ci-dessus est probablement la meilleure; et si vous ne voulez pas utiliser une table, une approche sqlclr serait beaucoup plus rapide. J'ai une implémentation de sqlclr qui fonctionne et qui est très rapide mais nous nous dirigeons vers l'approche basée sur les ensembles pour obtenir plus de définissabilité –

1

je commencé à travailler avec ce que Déraison affiché et était un bon début. J'ai testé c'est SQL Server et trouvé pas tout le temps était capturé. Je pense que le problème était principalement lorsque l'événement a commencé et s'est terminé le même jour. Cette solution semble fonctionner assez bien pour moi

CREATE TABLE [dbo].[working_hours](
[wh_id] [int] IDENTITY(1,1) NOT FOR REPLICATION NOT NULL, 
[wh_starttime] [datetime] NULL, 
[wh_endtime] [datetime] NULL, 
PRIMARY KEY CLUSTERED 
(
[wh_id] ASC 
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF,   ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] 
) ON [PRIMARY] 

GO

CREATE FUNCTION [dbo].[udFWorkingMinutes] 
(
@startdate DATETIME 
,@enddate DATETIME 
) 
RETURNS INT 
AS 
BEGIN 


DECLARE @WorkingHours INT 
SET @WorkingHours = 
(SELECT 
CASE WHEN COALESCE(SUM(duration),0) < 0 THEN 0 ELSE SUM(Duration) 
END AS Minutes 
FROM 
(
    --All whole days 
    SELECT ISNULL(DATEDIFF(mi,wh_starttime,wh_endtime),0) AS Duration 
    FROM working_hours 
    WHERE wh_starttime >= @startdate AND wh_endtime <= @enddate 
    UNION ALL 
    --All partial days where event start after office hours and finish after office hours 
    SELECT ISNULL(DATEDIFF(mi,@startdate,wh_endtime),0) AS Duration 
    FROM working_hours 
    WHERE @startdate > wh_starttime AND @enddate >= wh_endtime 
    AND (CAST(wh_starttime AS DATE) = CAST(@startdate AS DATE)) 
    AND @startdate < wh_endtime 
    UNION ALL 
    --All partial days where event starts before office hours and ends before day end 
    SELECT ISNULL(DATEDIFF(mi,wh_starttime,@enddate),0) AS Duration 
    FROM working_hours 
    WHERE @enddate < wh_endtime 
    AND @enddate >= wh_starttime 
    AND @startdate <= wh_starttime 
    AND (CAST(wh_endtime AS DATE) = CAST(@enddate AS DATE)) 
    UNION ALL 
    --Get partial day where intraday event 
    SELECT ISNULL(DATEDIFF(mi,@startdate,@enddate),0) AS Duration 
    FROM working_hours 
    WHERE @startdate > wh_starttime AND @enddate < wh_endtime 
    AND (CAST(@startdate AS DATE)= CAST(wh_starttime AS DATE)) 
    AND (CAST(@enddate AS DATE)= CAST(wh_endtime AS DATE)) 
) AS u) 

RETURN @WorkingHours 
END 
GO 

Alls qui reste à faire est de remplir la table des heures de travail avec quelque chose comme

;WITH cte AS (
SELECT CASE WHEN DATEPART(Day,'2014-01-01 9:00:00 AM') = 1 THEN '2014-01-01 9:00:00 AM' 
ELSE DATEADD(Day,DATEDIFF(Day,0,'2014-01-01 9:00:00 AM')+1,0) END AS  myStartDate, 
CASE WHEN DATEPART(Day,'2014-01-01 5:00:00 PM') = 1 THEN '2014-01-01 5:00:00 PM' 
ELSE DATEADD(Day,DATEDIFF(Day,0,'2014-01-01 5:00:00 PM')+1,0) END AS myEndDate 
UNION ALL 
SELECT DATEADD(Day,1,myStartDate), DATEADD(Day,1,myEndDate) 
FROM cte 
WHERE DATEADD(Day,1,myStartDate) <= '2015-01-01' 
) 
INSERT INTO working_hours 
SELECT myStartDate, myEndDate 
FROM cte 
OPTION (MAXRECURSION 0) 

delete from working_hours where datename(dw,wh_starttime) IN ('Saturday', 'Sunday') 

--delete public holidays 

delete from working_hours where CAST(wh_starttime AS DATE) = '2014-01-01' 

Mon premier post ! Sois miséricordieux.

1

En utilisant un excellent point de départ de la déraison, voici une implémentation TSQL pour SQL Server 2012.

Cette première SQL alimente une table avec nos journées de travail et les heures hors week-ends et jours fériés:

declare @dteStart date 
declare @dteEnd date 
declare @dtStart smalldatetime 
declare @dtEnd smalldatetime 
Select @dteStart = '2016-01-01' 
Select @dteEnd = '2016-12-31' 

CREATE TABLE working_hours (starttime SMALLDATETIME, endtime SMALLDATETIME); 

while @dteStart <= @dteEnd 
BEGIN 
    IF datename(WEEKDAY, @dteStart) <> 'Saturday' 
    AND DATENAME(WEEKDAY, @dteStart) <> 'Sunday' 
    AND @dteStart not in ('2016-01-01' --New Years 
          ,'2016-01-18' --MLK Jr 
          ,'2016-02-15' --President's Day 
          ,'2016-05-30' --Memorial Day 
          ,'2016-07-04' --Fourth of July 
          ,'2016-09-05' --Labor Day 
          ,'2016-11-11' --Veteran's Day 
          ,'2016-11-24' --Thanksgiving 
          ,'2016-11-25' --Day after Thanksgiving 
          ,'2016-12-26' --Christmas 
         ) 
     BEGIN 
     select @dtStart = SMALLDATETIMEFROMPARTS(year(@dteStart),month(@dteStart),day(@dteStart),8,0) --8:00am 
     select @dtEnd = SMALLDATETIMEFROMPARTS(year(@dteStart),month(@dteStart),day(@dteStart),17,0) --5:00pm 
     insert into working_hours values (@dtStart,@dtEnd) 
     END 
    Select @dteStart = DATEADD(day,1,@dteStart) 
END 

Maintenant ici est la logique qui a travaillé pour retourner le procès-verbal INT:

declare @event_start datetime2 
declare @event_end datetime2 
select @event_start = '2016-01-04 8:00' 
select @event_end = '2016-01-06 16:59' 

SELECT SUM(duration) as minutes 
FROM 
(
    SELECT DATEDIFF(mi,@event_start,@event_end) as duration 
    FROM working_hours 
    WHERE @event_start >= starttime 
    AND @event_start <= endtime 
    AND @event_end <= endtime 
UNION ALL 
    SELECT DATEDIFF(mi,@event_start,endtime) 
    FROM working_hours 
    WHERE @event_start >= starttime 
    AND @event_start <= endtime 
    AND @event_end > endtime 
UNION ALL 
    SELECT DATEDIFF(mi,starttime,@event_end) 
    FROM working_hours 
    WHERE @event_end >= starttime 
    AND @event_end <= endtime 
    AND @event_start < starttime 
UNION ALL 
    SELECT SUM(DATEDIFF(mi,starttime,endtime)) 
    FROM working_hours 
    WHERE starttime > @event_start 
    AND endtime < @event_end 
) AS u 

Ce retourne correctement 1 minute timide de trois jours de travail 9 heures

Questions connexes