2008-10-31 156 views
123

¿Cómo puedo calcular el número de días de trabajo entre dos fechas en SQL Server?Contabilizar días de trabajo entre dos fechas

de lunes a viernes y debe ser T-SQL.

+5

¿Se pueden definir días laborables? cualquier lunes a viernes? ¿Excluyendo vacaciones importantes? ¿Que pais? Debe hacerse en SQL? –

Respuesta

238

Para los días laborables, de lunes a viernes, puede hacerlo con un solo SELECT, así:

DECLARE @StartDate DATETIME 
DECLARE @EndDate DATETIME 
SET @StartDate = '2008/10/01' 
SET @EndDate = '2008/10/31' 


SELECT 
    (DATEDIFF(dd, @StartDate, @EndDate) + 1) 
    -(DATEDIFF(wk, @StartDate, @EndDate) * 2) 
    -(CASE WHEN DATENAME(dw, @StartDate) = 'Sunday' THEN 1 ELSE 0 END) 
    -(CASE WHEN DATENAME(dw, @EndDate) = 'Saturday' THEN 1 ELSE 0 END) 

Si desea incluir las vacaciones, usted tiene que trabajar hacia fuera un poco ...

+3

nice answer +1 :) –

+1

¡Me acabo de dar cuenta de que este código no funciona siempre! Intenté esto: SET @StartDate = '28 -mar-2011 ' SET @EndDate = '29 -mar-2011' la respuesta lo contó como 2 días – greektreat

+12

@greektreat Funciona bien. Es solo que tanto @StartDate como @EndDate están incluidos en el recuento. Si desea contar de lunes a martes como 1 día, simplemente elimine el "+1" después del primer DATEDIFF. Entonces también obtendrás Fri-> Sat = 0, Fri-> Sol = 0, Fri-> Mon = 1. –

27

En Calculating Work Days puede encontrar un buen artículo sobre este tema, pero como puede ver no está tan avanzado.

--Changing current database to the Master database allows function to be shared by everyone. 
USE MASTER 
GO 
--If the function already exists, drop it. 
IF EXISTS 
(
    SELECT * 
    FROM dbo.SYSOBJECTS 
    WHERE ID = OBJECT_ID(N'[dbo].[fn_WorkDays]') 
    AND XType IN (N'FN', N'IF', N'TF') 
) 
DROP FUNCTION [dbo].[fn_WorkDays] 
GO 
CREATE FUNCTION dbo.fn_WorkDays 
--Presets 
--Define the input parameters (OK if reversed by mistake). 
(
    @StartDate DATETIME, 
    @EndDate DATETIME = NULL [email protected] replaced by @StartDate when DEFAULTed 
) 

--Define the output data type. 
RETURNS INT 

AS 
--Calculate the RETURN of the function. 
BEGIN 
    --Declare local variables 
    --Temporarily holds @EndDate during date reversal. 
    DECLARE @Swap DATETIME 

    --If the Start Date is null, return a NULL and exit. 
    IF @StartDate IS NULL 
     RETURN NULL 

    --If the End Date is null, populate with Start Date value so will have two dates (required by DATEDIFF below). 
    IF @EndDate IS NULL 
     SELECT @EndDate = @StartDate 

    --Strip the time element from both dates (just to be safe) by converting to whole days and back to a date. 
    --Usually faster than CONVERT. 
    --0 is a date (01/01/1900 00:00:00.000) 
    SELECT @StartDate = DATEADD(dd,DATEDIFF(dd,0,@StartDate), 0), 
      @EndDate = DATEADD(dd,DATEDIFF(dd,0,@EndDate) , 0) 

    --If the inputs are in the wrong order, reverse them. 
    IF @StartDate > @EndDate 
     SELECT @Swap  = @EndDate, 
       @EndDate = @StartDate, 
       @StartDate = @Swap 

    --Calculate and return the number of workdays using the input parameters. 
    --This is the meat of the function. 
    --This is really just one formula with a couple of parts that are listed on separate lines for documentation purposes. 
    RETURN (
     SELECT 
     --Start with total number of days including weekends 
     (DATEDIFF(dd,@StartDate, @EndDate)+1) 
     --Subtact 2 days for each full weekend 
     -(DATEDIFF(wk,@StartDate, @EndDate)*2) 
     --If StartDate is a Sunday, Subtract 1 
     -(CASE WHEN DATENAME(dw, @StartDate) = 'Sunday' 
      THEN 1 
      ELSE 0 
     END) 
     --If EndDate is a Saturday, Subtract 1 
     -(CASE WHEN DATENAME(dw, @EndDate) = 'Saturday' 
      THEN 1 
      ELSE 0 
     END) 
     ) 
    END 
GO 

Si necesita utilizar un calendario personalizado, es posible que necesite agregar algunos controles y algunos parámetros. Con suerte, proporcionará un buen punto de partida.

+0

Gracias por incluir el enlace para entender cómo funciona esto. ¡La escritura en sqlservercentral fue genial! –

+0

Gracias por los comentarios en línea. Me ayudaron a ver rápidamente cómo funciona esto. – unnknown

4
DECLARE @TotalDays INT,@WorkDays INT 
DECLARE @ReducedDayswithEndDate INT 
DECLARE @WeekPart INT 
DECLARE @DatePart INT 

SET @TotalDays= DATEDIFF(day, @StartDate, @EndDate) +1 
SELECT @ReducedDayswithEndDate = CASE DATENAME(weekday, @EndDate) 
    WHEN 'Saturday' THEN 1 
    WHEN 'Sunday' THEN 2 
    ELSE 0 END 
SET @[email protected]@ReducedDayswithEndDate 
SET @[email protected]/7; 
SET @[email protected]%7; 
SET @WorkDays=(@WeekPart*5)[email protected] 

RETURN @WorkDays 
+0

Si publica código, XML o muestras de datos, ** por favor ** resalte esas líneas en el editor de texto y haga clic en el botón "muestras de código" ({}) en la barra de herramientas del editor para formatear y sintaxis ¡destaquelo! –

+0

Este es realmente muy bueno --- ¡gracias! – Dylon

+0

Excelente, sin necesidad de funciones de periferia o actualizaciones de la base de datos usando esto. Gracias. Love the saltire por cierto :-) –

1
DECLARE @StartDate datetime,@EndDate datetime 

select @StartDate='3/2/2010', @EndDate='3/7/2010' 

DECLARE @TotalDays INT,@WorkDays INT 

DECLARE @ReducedDayswithEndDate INT 

DECLARE @WeekPart INT 

DECLARE @DatePart INT 

SET @TotalDays= DATEDIFF(day, @StartDate, @EndDate) +1 

SELECT @ReducedDayswithEndDate = CASE DATENAME(weekday, @EndDate) 
    WHEN 'Saturday' THEN 1 
    WHEN 'Sunday' THEN 2 
    ELSE 0 END 

SET @[email protected]@ReducedDayswithEndDate 

SET @[email protected]/7; 

SET @[email protected]%7; 

SET @WorkDays=(@WeekPart*5)[email protected] 

SELECT @WorkDays 
+0

Si va a utilizar una función, podría ser mejor ir con una función basada en la tabla como en [la respuesta de Mário Meyrelles] (http://stackoverflow.com/questions/252519/ count-work-days-between-two-dates/21098589 # 21098589) –

6

Mi versión de la respuesta aceptada como una función utilizando DATEPART, así que no tengo que hacer una comparación de cadenas en la línea con

DATENAME(dw, @StartDate) = 'Sunday' 

De todos modos, aquí está mi función datediff negocio

SET ANSI_NULLS ON 
GO 
SET QUOTED_IDENTIFIER ON 
GO 

CREATE FUNCTION BDATEDIFF 
(
    @startdate as DATETIME, 
    @enddate as DATETIME 
) 
RETURNS INT 
AS 
BEGIN 
    DECLARE @res int 

SET @res = (DATEDIFF(dd, @startdate, @enddate) + 1) 
    -(DATEDIFF(wk, @startdate, @enddate) * 2) 
    -(CASE WHEN DATEPART(dw, @startdate) = 1 THEN 1 ELSE 0 END) 
    -(CASE WHEN DATEPART(dw, @enddate) = 7 THEN 1 ELSE 0 END) 

    RETURN @res 
END 
GO 
1
CREATE FUNCTION x 
(
    @StartDate DATETIME, 
    @EndDate DATETIME 
) 
RETURNS INT 
AS 
BEGIN 
    DECLARE @Teller INT 

    SET @StartDate = DATEADD(dd,1,@StartDate) 

    SET @Teller = 0 
    IF DATEDIFF(dd,@StartDate,@EndDate) <= 0 
    BEGIN 
     SET @Teller = 0 
    END 
    ELSE 
    BEGIN 
     WHILE 
      DATEDIFF(dd,@StartDate,@EndDate) >= 0 
     BEGIN 
      IF DATEPART(dw,@StartDate) < 6 
      BEGIN 
       SET @Teller = @Teller + 1 
      END 
      SET @StartDate = DATEADD(dd,1,@StartDate) 
     END 
    END 
    RETURN @Teller 
END 
+1

Sería mejor si explicaras el código que publicaste. –

5

(yo soy un pocos puntos por debajo de los privilegios que comentan)

Si decide renunciar a la 1 día en CMS's elegant solution, tenga en cuenta que si su fecha de inicio y fecha de finalización son el mismo fin de semana, obtendrá una respuesta negativa. Es decir, 2008/10/26 a 2008/10/26 devuelve -1.

mi solución simplista:

select @Result = (..CMS's answer..) 
if (@Result < 0) 
     select @Result = 0 
    RETURN @Result 

.. que también establece todos los mensajes erróneos con fecha de inicio después de la fecha final, a cero. Algo que puede o no estar buscando.

5

Por diferencia entre las fechas incluyendo los días festivos que se fue de esta manera:

1) Tabla con las fiestas:

CREATE TABLE [dbo].[Holiday](
[Id] [int] IDENTITY(1,1) NOT NULL, 
[Name] [nvarchar](50) NULL, 
[Date] [datetime] NOT NULL) 

2) tuve mi planificaciones Tabla así y quería llenar Work_Days columna que estaba vacío :

CREATE TABLE [dbo].[Plan_Phase](
[Id] [int] IDENTITY(1,1) NOT NULL, 
[Id_Plan] [int] NOT NULL, 
[Id_Phase] [int] NOT NULL, 
[Start_Date] [datetime] NULL, 
[End_Date] [datetime] NULL, 
[Work_Days] [int] NULL) 

3) por lo tanto, a fin de obtener "Work_Days" para llenar más tarde en mi columna sólo tenía que:

SELECT Start_Date, End_Date, 
(DATEDIFF(dd, Start_Date, End_Date) + 1) 
-(DATEDIFF(wk, Start_Date, End_Date) * 2) 
-(SELECT COUNT(*) From Holiday Where Date >= Start_Date AND Date <= End_Date) 
-(CASE WHEN DATENAME(dw, Start_Date) = 'Sunday' THEN 1 ELSE 0 END) 
-(CASE WHEN DATENAME(dw, End_Date) = 'Saturday' THEN 1 ELSE 0 END) 
-(CASE WHEN (SELECT COUNT(*) From Holiday Where Start_Date = Date) > 0 THEN 1 ELSE 0 END) 
-(CASE WHEN (SELECT COUNT(*) From Holiday Where End_Date = Date) > 0 THEN 1 ELSE 0 END) AS Work_Days 
from Plan_Phase 

Espero que pueda ayudar.

Saludos

+1

Con respecto a sus restas de vacaciones. ¿Qué sucede si la fecha de inicio es el 1 de enero y la fecha de finalización es el 31 de diciembre? Restarás solo 2, lo cual es incorrecto. Propongo usar DATEDIFF (día, fecha_inicio, fecha) y lo mismo para End_Date en lugar de todo 'SELECT COUNT (*) FROM Holiday ...'. –

1

Tomé los diversos ejemplos aquí, pero en mi situación particular tenemos un @PromisedDate para la entrega y una @ReceivedDate para la recepción real del artículo. Cuando se recibía un artículo antes del "PromisedDate", los cálculos no totalizaban correctamente a menos que ordenara que las fechas pasaran a la función por orden de calendario. No queriendo verificar las fechas cada vez, cambié la función para manejar esto por mí.

Create FUNCTION [dbo].[fnGetBusinessDays] 
(
@PromiseDate date, 
@ReceivedDate date 
) 
RETURNS integer 
AS 
BEGIN 
DECLARE @days integer 

SELECT @days = 
    Case when @PromiseDate > @ReceivedDate Then 
     DATEDIFF(d,@PromiseDate,@ReceivedDate) + 
     ABS(DATEDIFF(wk,@PromiseDate,@ReceivedDate)) * 2 + 
     CASE 
      WHEN DATENAME(dw, @PromiseDate) <> 'Saturday' AND DATENAME(dw, @ReceivedDate) = 'Saturday' THEN 1 
      WHEN DATENAME(dw, @PromiseDate) = 'Saturday' AND DATENAME(dw, @ReceivedDate) <> 'Saturday' THEN -1 
      ELSE 0 
     END + 
     (Select COUNT(*) FROM CompanyHolidays 
      WHERE HolidayDate BETWEEN @ReceivedDate AND @PromiseDate 
      AND DATENAME(dw, HolidayDate) <> 'Saturday' AND DATENAME(dw, HolidayDate) <> 'Sunday') 
    Else 
     DATEDIFF(d,@PromiseDate,@ReceivedDate) - 
     ABS(DATEDIFF(wk,@PromiseDate,@ReceivedDate)) * 2 - 
      CASE 
       WHEN DATENAME(dw, @PromiseDate) <> 'Saturday' AND DATENAME(dw, @ReceivedDate) = 'Saturday' THEN 1 
       WHEN DATENAME(dw, @PromiseDate) = 'Saturday' AND DATENAME(dw, @ReceivedDate) <> 'Saturday' THEN -1 
       ELSE 0 
      END - 
     (Select COUNT(*) FROM CompanyHolidays 
      WHERE HolidayDate BETWEEN @PromiseDate and @ReceivedDate 
      AND DATENAME(dw, HolidayDate) <> 'Saturday' AND DATENAME(dw, HolidayDate) <> 'Sunday') 
    End 


RETURN (@days) 

END 
4

Aquí hay una versión que funciona bien (creo). La tabla de días festivos contiene las columnas de fechas de vacaciones que contienen las vacaciones que su empresa observa.

DECLARE @RAWDAYS INT 

    SELECT @RAWDAYS = DATEDIFF(day, @StartDate, @EndDate)--+1 
        -(2 * DATEDIFF(week, @StartDate, @EndDate)) 
        + CASE WHEN DATENAME(dw, @StartDate) = 'Saturday' THEN 1 ELSE 0 END 
        - CASE WHEN DATENAME(dw, @EndDate) = 'Saturday' THEN 1 ELSE 0 END 

    SELECT @RAWDAYS - COUNT(*) 
    FROM HOLIDAY NumberOfBusinessDays 
    WHERE [Holiday_Date] BETWEEN @StartDate+1 AND @EndDate 
+0

Esas fechas de vacaciones también pueden caer los fines de semana. Y para algunos, las vacaciones del domingo serán reemplazadas por el próximo lunes. –

14

todo el crédito a Bogdan Maxim & Peter Mortensen. Esta es su puesto, me acaba de agregar días de fiesta a la función (esto supone que tiene una tabla "tblHolidays" con un campo de fecha y hora "HolDate".

--Changing current database to the Master database allows function to be shared by everyone. 
USE MASTER 
GO 
--If the function already exists, drop it. 
IF EXISTS 
(
    SELECT * 
    FROM dbo.SYSOBJECTS 
    WHERE ID = OBJECT_ID(N'[dbo].[fn_WorkDays]') 
    AND XType IN (N'FN', N'IF', N'TF') 
) 

DROP FUNCTION [dbo].[fn_WorkDays] 
GO 
CREATE FUNCTION dbo.fn_WorkDays 
--Presets 
--Define the input parameters (OK if reversed by mistake). 
(
    @StartDate DATETIME, 
    @EndDate DATETIME = NULL [email protected] replaced by @StartDate when DEFAULTed 
) 

--Define the output data type. 
RETURNS INT 

AS 
--Calculate the RETURN of the function. 
BEGIN 
    --Declare local variables 
    --Temporarily holds @EndDate during date reversal. 
    DECLARE @Swap DATETIME 

    --If the Start Date is null, return a NULL and exit. 
    IF @StartDate IS NULL 
     RETURN NULL 

    --If the End Date is null, populate with Start Date value so will have two dates (required by DATEDIFF below). 
    IF @EndDate IS NULL 
     SELECT @EndDate = @StartDate 

    --Strip the time element from both dates (just to be safe) by converting to whole days and back to a date. 
    --Usually faster than CONVERT. 
    --0 is a date (01/01/1900 00:00:00.000) 
    SELECT @StartDate = DATEADD(dd,DATEDIFF(dd,0,@StartDate), 0), 
      @EndDate = DATEADD(dd,DATEDIFF(dd,0,@EndDate) , 0) 

    --If the inputs are in the wrong order, reverse them. 
    IF @StartDate > @EndDate 
     SELECT @Swap  = @EndDate, 
       @EndDate = @StartDate, 
       @StartDate = @Swap 

    --Calculate and return the number of workdays using the input parameters. 
    --This is the meat of the function. 
    --This is really just one formula with a couple of parts that are listed on separate lines for documentation purposes. 
    RETURN (
     SELECT 
     --Start with total number of days including weekends 
     (DATEDIFF(dd,@StartDate, @EndDate)+1) 
     --Subtact 2 days for each full weekend 
     -(DATEDIFF(wk,@StartDate, @EndDate)*2) 
     --If StartDate is a Sunday, Subtract 1 
     -(CASE WHEN DATENAME(dw, @StartDate) = 'Sunday' 
      THEN 1 
      ELSE 0 
     END) 
     --If EndDate is a Saturday, Subtract 1 
     -(CASE WHEN DATENAME(dw, @EndDate) = 'Saturday' 
      THEN 1 
      ELSE 0 
     END) 
     --Subtract all holidays 
     -(Select Count(*) from [DB04\DB04].[Gateway].[dbo].[tblHolidays] 
      where [HolDate] between @StartDate and @EndDate) 
     ) 
    END 
GO 
-- Test Script 
/* 
declare @EndDate datetime= dateadd(m,2,getdate()) 
print @EndDate 
select [Master].[dbo].[fn_WorkDays] (getdate(), @EndDate) 
*/ 
+1

Hola Dan B. Solo para hacerle saber que su versión asume que la tabla tblHolidays no contiene los sábados ni los lunes, lo que a veces ocurre. De todos modos, gracias por compartir tu versión. Cheers –

+3

Julio - Sí - Mi versión asume que los sábados y domingos (no los lunes) son fines de semana, y por lo tanto no es un día "no comercial". Pero si trabajas los fines de semana, supongo que todos los días es un "día de trabajo" y puedes comentar la parte de la cláusula del sábado y domingo y simplemente agregar todas tus vacaciones a la mesa de tblHolidays. –

1

Si es necesario agregar días de trabajo a una fecha determinada, puede crear una función que depende de una tabla de calendario, se describen a continuación:

CREATE TABLE Calendar 
(
    dt SMALLDATETIME PRIMARY KEY, 
    IsWorkDay BIT 
); 

--fill the rows with normal days, weekends and holidays. 


create function AddWorkingDays (@initialDate smalldatetime, @numberOfDays int) 
    returns smalldatetime as 

    begin 
     declare @result smalldatetime 
     set @result = 
     (
      select t.dt from 
      (
       select dt, ROW_NUMBER() over (order by dt) as daysAhead from calendar 
       where dt > @initialDate 
       and IsWorkDay = 1 
       ) t 
      where t.daysAhead = @numberOfDays 
     ) 

     return @result 
    end 
+0

+1 Terminé usando [una solución similar aquí] (http://stackoverflow.com/questions/32277191/sql-subquery-based-on-row-values-with-unrelated-table/32333303#32333303) –

2

Usando una tabla de fechas:

DECLARE 
     @StartDate date = '2014-01-01', 
     @EndDate date = '2014-01-31'; 
    SELECT 
     COUNT(*) As NumberOfWeekDays 
    FROM dbo.Calendar 
    WHERE CalendarDate BETWEEN @StartDate AND @EndDate 
     AND IsWorkDay = 1; 

Si no tienes eso, se puede utilizar una tabla de números:

DECLARE 
    @StartDate datetime = '2014-01-01', 
    @EndDate datetime = '2014-01-31'; 
    SELECT 
    SUM(CASE WHEN DATEPART(dw, DATEADD(dd, Number-1, @StartDate)) BETWEEN 2 AND 6 THEN 1 ELSE 0 END) As NumberOfWeekDays 
    FROM dbo.Numbers 
    WHERE Number <= DATEDIFF(dd, @StartDate, @EndDate) + 1 -- Number table starts at 1, we want a 0 base 

Deben ser rápidos y eliminar la ambigüedad/complejidad. La primera opción es la mejor, pero si no tienes una tabla de calendario, siempre puedes crear una tabla de números con un CTE.

0

Eso me funciona, en mi país los sábados y domingos son días inhábiles.

Para mí es importante el tiempo de @StartDate y @EndDate.

CREATE FUNCTION [dbo].[fnGetCountWorkingBusinessDays] 
(
    @StartDate as DATETIME, 
    @EndDate as DATETIME 
) 
RETURNS INT 
AS 
BEGIN 
    DECLARE @res int 

SET @StartDate = CASE 
    WHEN DATENAME(dw, @StartDate) = 'Saturday' THEN DATEADD(dd, 2, DATEDIFF(dd, 0, @StartDate)) 
    WHEN DATENAME(dw, @StartDate) = 'Sunday' THEN DATEADD(dd, 1, DATEDIFF(dd, 0, @StartDate)) 
    ELSE @StartDate END 

SET @EndDate = CASE 
    WHEN DATENAME(dw, @EndDate) = 'Saturday' THEN DATEADD(dd, 0, DATEDIFF(dd, 0, @EndDate)) 
    WHEN DATENAME(dw, @EndDate) = 'Sunday' THEN DATEADD(dd, -1, DATEDIFF(dd, 0, @EndDate)) 
    ELSE @EndDate END 


SET @res = 
    (DATEDIFF(hour, @StartDate, @EndDate)/24) 
    - (DATEDIFF(wk, @StartDate, @EndDate) * 2) 

SET @res = CASE WHEN @res < 0 THEN 0 ELSE @res END 

    RETURN @res 
END 

GO 
0
función

Crear como:

CREATE FUNCTION dbo.fn_WorkDays(@StartDate DATETIME, @EndDate DATETIME= NULL) 
RETURNS INT 
AS 
BEGIN 
     DECLARE @Days int 
     SET @Days = 0 

     IF @EndDate = NULL 
       SET @EndDate = EOMONTH(@StartDate) --last date of the month 

     WHILE DATEDIFF(dd,@StartDate,@EndDate) >= 0 
     BEGIN 
       IF DATENAME(dw, @StartDate) <> 'Saturday' 
        and DATENAME(dw, @StartDate) <> 'Sunday' 
        and Not ((Day(@StartDate) = 1 And Month(@StartDate) = 1)) --New Year's Day. 
        and Not ((Day(@StartDate) = 4 And Month(@StartDate) = 7)) --Independence Day. 
       BEGIN 
        SET @Days = @Days + 1 
       END 

       SET @StartDate = DATEADD(dd,1,@StartDate) 
     END 

     RETURN @Days 
END 

puede llamar a la función como:

select dbo.fn_WorkDays('1/1/2016', '9/25/2016') 

O como:

select dbo.fn_WorkDays(StartDate, EndDate) 
from table1 
1

Esta es básicamente la respuesta de CMS sin la dependencia en una configuración de idioma particular. Y dado que estamos buscando genéricos, eso significa que también debería funcionar para todos los ajustes de @@datefirst.

datediff(day, <start>, <end>) - datediff(week, <start>, <end>) * 2 
    /* if start is a Sunday, adjust by -1 */ 
    + case when datepart(weekday, <start>) = 8 - @@datefirst then -1 else 0 end 
    /* if end is a Saturday, adjust by -1 */ 
    + case when datepart(weekday, <end>) = (13 - @@datefirst) % 7 + 1 then -1 else 0 end 

datediff(week, ...) siempre utiliza un límite de sábado a domingo para semana, de modo que la expresión es determinista y no necesita ser modificado (siempre y cuando nuestra definición de lunes a viernes es siempre de lunes a viernes.) Día de numeración varía de acuerdo con la configuración @@datefirst y los cálculos modificados manejan esta corrección con la pequeña complicación de alguna aritmética modular.

Una manera más limpia de lidiar con el sábado/domingo es traducir las fechas antes de extraer un valor del día de la semana. Después del cambio, los valores volverán a estar en línea con una numeración fija (y probablemente más familiar) que comienza con 1 el domingo y termina con 7 el sábado.

datediff(day, <start>, <end>) - datediff(week, <start>, <end>) * 2 
    + case when datepart(weekday, dateadd(day, @@datefirst, <start>)) = 1 then -1 else 0 end, 
    + case when datepart(weekday, dateadd(day, @@datefirst, <end>)) = 7 then -1 else 0 end 

He rastreado esta forma de la solución remonta por lo menos tan lejos como 2002 y un artículo Itzik Ben-Gan. (https://technet.microsoft.com/en-us/library/aa175781(v=sql.80).aspx) Aunque necesitó una pequeña modificación ya que los nuevos tipos date no permiten la aritmética de fecha, de lo contrario es idéntica.

0
Create Function dbo.DateDiff_WeekDays 
(
@StartDate DateTime, 
@EndDate DateTime 
) 
Returns Int 
As 

Begin 

Declare @Result Int = 0 

While @StartDate <= @EndDate 
Begin 
    If DateName(DW, @StartDate) not in ('Saturday','Sunday') 
     Begin 
      Set @Result = @Result +1 
     End 
     Set @StartDate = DateAdd(Day, +1, @StartDate) 
End 

Return @Result 

Fin

5

Otro enfoque para el cálculo de días de trabajo es utilizar un bucle while que básicamente itera a través de un rango de fechas y de incrementarlo en 1 cada vez que se encuentran día a estar dentro de lunes - viernes. El guión completo para el cálculo de días de trabajo utilizando el bucle while se muestra a continuación:

CREATE FUNCTION [dbo].[fn_GetTotalWorkingDaysUsingLoop] 
(@DateFrom DATE, 
@DateTo   DATE 
) 
RETURNS INT 
AS 
     BEGIN 
         DECLARE @TotWorkingDays INT= 0; 
         WHILE @DateFrom <= @DateTo 
             BEGIN 
                 IF DATENAME(WEEKDAY, @DateFrom) IN('Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday') 
                     BEGIN 
                         SET @TotWorkingDays = @TotWorkingDays + 1; 
                 END; 
                 SET @DateFrom = DATEADD(DAY, 1, @DateFrom); 
             END; 
         RETURN @TotWorkingDays; 
     END; 
GO 

Aunque la opción de bucle WHILE es más limpio y utiliza menos líneas de código, que tiene el potencial de ser un cuello de botella en su entorno particular cuando su rango de fechas abarca varios años.

se puede ver más métodos en la forma de calcular los días de trabajo y las horas en este artículo: https://www.sqlshack.com/how-to-calculate-work-days-and-hours-in-sql-server/

0

me encontré con el siguiente TSQL una solución bastante elegante (no tengo permisos para ejecutar funciones). Encontré que el DATEDIFF ignora DATEFIRST y quería que mi primer día de la semana fuera un lunes. También quería que el primer día de trabajo se estableciera en cero y si se trata de un fin de semana el lunes será cero. Esto puede ayudar a alguien que tiene un requisito ligeramente diferente :)

No maneja festivos

SET DATEFIRST 1 
SELECT 
,(DATEDIFF(DD, [StartDate], [EndDate]))   
-(DATEDIFF(wk, [StartDate], [EndDate]))   
-(DATEDIFF(wk, DATEADD(dd,[email protected]@DATEFIRST,[StartDate]), DATEADD(dd,[email protected]@DATEFIRST,[EndDate]))) AS [WorkingDays] 
FROM /*Your Table*/ 
0

Un enfoque consiste en 'caminar' las fechas de principio a fin en conjunción con una expresión case que comprueba si el día no es un sábado o un domingo y marcarlo (1 para el día de la semana, 0 para el fin de semana). Y al final solo suma banderas (sería igual al conteo de 1-banderas como la otra bandera es 0) para darte el número de días de la semana.

Puede utilizar un tipo de función de utilidad GetNums (número de inicio, número final) que genera una serie de números para 'bucle' desde la fecha de inicio hasta la fecha de finalización. Consulte http://tsql.solidq.com/SourceCodes/GetNums.txt para una implementación. La lógica también se puede ampliar para atender las vacaciones (por ejemplo, si tiene una tabla de vacaciones)

declare @date1 as datetime = '19900101' 
declare @date2 as datetime = '19900120' 

select sum(case when DATENAME(DW,currentDate) not in ('Saturday', 'Sunday') then 1 else 0 end) as noOfWorkDays 
from dbo.GetNums(0,DATEDIFF(day,@date1, @date2)-1) as Num 
cross apply (select DATEADD(day,n,@date1)) as Dates(currentDate) 
Cuestiones relacionadas