Cuenta los días de trabajo entre dos fechas

162

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

De lunes a viernes y debe ser T-SQL.

Ovidiu Pacurar
fuente
55
¿Puedes definir días de trabajo? cualquier lunes a viernes? ¿Excluyendo los feriados importantes? ¿Que pais? ¿Debe hacerse en SQL?
Dave K

Respuestas:

300

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

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 vacaciones, debe resolverlo un poco ...

CMS
fuente
3
¡Me acabo de dar cuenta de que este código no funciona siempre! probé esto: SET @StartDate = '28 -mar-2011 'SET @EndDate = '29 -mar-2011' la respuesta lo contó como 2 días
greektreat
16
@greektreat Funciona bien. Es solo que tanto @StartDate como @EndDate están incluidos en el conteo. Si desea que el lunes a martes cuente como 1 día, simplemente elimine el "+ 1" después del primer DATEDIFF. Entonces también obtendrás Fri-> Sat = 0, Fri-> Sun = 0, Fri-> Mon = 1.
Joe Daley el
66
Como seguimiento de @JoeDaley. Cuando elimina el + 1 después del DATEDIFF para excluir la fecha de inicio del recuento, también debe ajustar la parte CASO de esto. Terminé usando esto: + (CASO CUANDO DATENAME (dw, @StartDate) = 'Saturday' THEN 1 ELSE 0 END) - (CASE WHEN DATENAME (dw, @EndDate) = 'Saturday' THEN 1 ELSE 0 END)
Sequenzia
77
La función datename depende de la configuración regional. Una solución más robusta pero también más oscura es reemplazar las dos últimas líneas por:-(case datepart(dw, @StartDate)+@@datefirst when 8 then 1 else 0 end) -(case datepart(dw, @EndDate)+@@datefirst when 7 then 1 when 14 then 1 else 0 end)
Torben Klein
2
Para aclarar el comentario de @ Sequenzia, que eliminaría las declaraciones de casos sobre Domingo por completo, dejando sólo+(CASE WHEN DATENAME(dw, @StartDate) = 'Saturday' THEN 1 ELSE 0 END) - (CASE WHEN DATENAME(dw, @EndDate) = 'Saturday' THEN 1 ELSE 0 END)
Andy Raddatz
32

En Cálculo de días de trabajo puede encontrar un buen artículo sobre este tema, pero como puede ver, no es 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 --@EndDate 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 deba agregar algunas comprobaciones y algunos parámetros. Esperemos que proporcione un buen punto de partida.

Bogdan Maxim
fuente
Gracias por incluir el enlace para entender cómo funciona esto. La escritura en sqlservercentral fue genial!
Chris Porter
20

Todo el crédito a Bogdan Maxim y Peter Mortensen. Esta es su publicación, acabo de agregar días festivos 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 --@EndDate 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)
*/
Dan B
fuente
2
Hola, Dan B. Solo para hacerle saber que su versión asume que la tabla tblHolidays no contiene los sábados y los lunes, lo que, a veces, sucede. De todos modos, gracias por compartir tu versión. Saludos
Julio Nobre
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 son días "no hábiles". Pero si está trabajando los fines de semana, supongo que todos los días es un "día de trabajo" y puede comentar la parte de la cláusula los sábados y domingos y simplemente agregar todas sus vacaciones a la tabla tblHolidays.
Dan B
1
Gracias Dan. Lo incorporé a mi función, agregando un cheque para los fines de semana ya que mi tabla DateDimensions incluye todas las fechas, días festivos, etc. Tomando su función, acabo de agregar: y IsWeekend = 0 después de [HolDate] entre StartDate y EndDate)
AlsoKnownAsJazz
Si la tabla de días festivos contiene feriados los fines de semana, puede modificar los criterios de esta manera: WHERE HolDate BETWEEN @StartDate AND @EndDate AND DATEPART(dw, HolDate) BETWEEN 2 AND 6para contar solo los feriados de lunes a viernes.
Andre
7

Otro enfoque para calcular los días hábiles es usar un ciclo WHILE que básicamente itera a través de un rango de fechas y lo incrementa en 1 cada vez que se encuentran días dentro de lunes a viernes. La secuencia de comandos completa para calcular los días hábiles utilizando el ciclo 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 WHILE loop es más limpia y usa menos líneas de código, tiene el potencial de ser un cuello de botella de rendimiento en su entorno, particularmente cuando su rango de fechas se extiende a lo largo de varios años.

Puede ver más métodos sobre cómo calcular días y horas de trabajo en este artículo: https://www.sqlshack.com/how-to-calculate-work-days-and-hours-in-sql-server/

AliceF
fuente
6

Mi versión de la respuesta aceptada como una función usando DATEPART, por lo 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 dataiff de negocios

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
Carter Cole
fuente
5
 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 @TotalDays=@TotalDays-@ReducedDayswithEndDate
 SET @WeekPart=@TotalDays/7;
 SET @DatePart=@TotalDays%7;
 SET @WorkDays=(@WeekPart*5)+@DatePart

 RETURN @WorkDays
Muthuvel
fuente
Si el código de poste, XML o datos de muestras, por favor, destacar aquellas líneas en el editor de texto y haga clic en el botón "muestras de código" ({}) en la barra de herramientas de editor de formato y la sintaxis muy bien resáltala!
marc_s
Genial, sin necesidad de funciones periféricas o actualizaciones de la base de datos usando esto. Gracias. Love the saltire por cierto :-)
Brian Scott
Súper solución. Subtitulé en fórmulas las variables para usar en un universo webi para calcular los días de la semana (MF) entre las fechas en 2 columnas de la tabla de esta manera ... (((((DATEDIFF (day, table.col1, table.col2) +1) - ((CASO DATENAME (día de la semana, table.col2) CUANDO 'Sábado' ENTONCES 1 CUANDO 'Domingo' ENTONCES 2 OTRO 0 FIN))) / 7) * 5) + (((DATEDIFF (día, table.col1, table.col2 ) +1) - ((CASO DATENAME (día de la semana, table.col2) CUANDO 'Sábado' ENTONCES 1 CUANDO 'Domingo' ENTONCES 2 ELSE 0 FIN)))% 7)
Hilary
5

(Me faltan algunos puntos para comentar los privilegios)

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

mi solución bastante simplista:

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

.. que también establece todas las publicaciones erróneas con fecha de inicio después de la fecha de finalización en cero. Algo que puede o no estar buscando.

phareim
fuente
5

Por la diferencia entre las fechas, incluidas las vacaciones, fui de esta manera:

1) Mesa con vacaciones:

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

2) Tenía mi tabla de planificación como esta y quería llenar la columna Work_Days que estaba vacía:

    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) Entonces, para que "Work_Days" complete más tarde mi columna solo 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.

Salud

joaopintocruz
fuente
1
En cuanto a sus sustracciones de vacaciones. ¿Qué pasa si la fecha de inicio es el 1 de enero y la fecha de finalización es el 31 de diciembre? Restará solo 2, lo cual está mal. Propongo usar DATEDIFF (día, fecha_inicio, fecha) y lo mismo para fecha_final en lugar de todo 'SELECCIONAR CUENTA (*) DE vacaciones ...'.
Illia Ratkevych
4

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

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 
usuario2733766
fuente
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.
Irawan Soetomo
3

Sé que esta es una pregunta antigua, pero necesitaba una fórmula para los días de trabajo, excluyendo la fecha de inicio, ya que tengo varios elementos y necesito que los días se acumulen correctamente.

Ninguna de las respuestas no iterativas funcionó para mí.

Usé una definición como

Número de veces que pasa de medianoche a lunes, martes, miércoles, jueves y viernes

(otros pueden contar de medianoche a sábado en lugar de lunes)

Terminé con esta fórmula

SELECT DATEDIFF(day, @StartDate, @EndDate) /* all midnights passed */
     - DATEDIFF(week, @StartDate, @EndDate) /* remove sunday midnights */
     - DATEDIFF(week, DATEADD(day, 1, @StartDate), DATEADD(day, 1, @EndDate)) /* remove saturday midnights */
adrianm
fuente
1
Ese lo hizo por mí, pero tuve que hacer un pequeño cambio. No contaba cuándo @StartDatees un sábado o viernes. Aquí está mi versión:DATEDIFF(day, @StartDate, @EndDate) - DATEDIFF(week, @StartDate, @EndDate) - DATEDIFF(week, DATEADD(day, 1, @StartDate), DATEADD(day, 1, @EndDate)) - (CASE WHEN DATEPART(WEEKDAY, @StartDate) IN (1, 7) THEN 1 ELSE 0 END) + 1
caiosm1005
@ caiosm1005, sábado a domingo devuelve 0, sábado a lunes devuelve 1, viernes a sábado devuelve 0. Todos son consistentes con mi definición. Su código no se acumulará correctamente (por ejemplo, devuelva 6 de viernes a viernes pero 5 de lunes a lunes)
adrianm
3

Esta es básicamente la respuesta de CMS sin depender de una configuración de idioma particular. Y dado que estamos buscando genéricos, eso significa que también debería funcionar para todas las @@datefirstconfiguraciones.

datediff(day, <start>, <end>) + 1 - 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 usa un límite de sábado a domingo durante semanas, por lo que la expresión es determinista y no necesita ser modificada (siempre y cuando nuestra definición de días de la semana sea consistentemente de lunes a viernes). La numeración de los días varía según el @@datefirst configuración y Los cálculos modificados manejan esta corrección con la pequeña complicación de alguna aritmética modular.

Una forma más limpia de lidiar con lo del sábado / domingo es traducir las fechas antes de extraer el valor de un 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>) + 1 - 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 solución al menos hasta 2002 y un artículo de Itzik Ben-Gan. ( https://technet.microsoft.com/en-us/library/aa175781(v=sql.80).aspx ) Aunque necesitaba un pequeño ajuste ya que los datetipos más nuevos no permiten la aritmética de fechas, de lo contrario es idéntico.

EDITAR: agregué de nuevo el +1que de alguna manera se había dejado. También vale la pena señalar que este método siempre cuenta los días de inicio y finalización. También supone que la fecha de finalización es igual o posterior a la fecha de inicio.

shawnt00
fuente
Tenga en cuenta que esto devolverá resultados incorrectos para muchas fechas los fines de semana para que no sumen p (Fri-> Mon debería ser igual que Fri-> Sat + Sat-> Sun + Sun-> Mon). Fri-> Sat debería ser 0 (correcto), Sat-> Sun debería ser 0 (incorrecto -1), Sun-> Mon debería ser 1 (incorrecto 0). Otros errores que siguen son Sat-> Sat = -1, Sun-> Sun = -1, Sun-> Sat = 4
adrianm el
@adrianm Creo que había corregido los problemas. En realidad, el problema era que siempre estaba apagado por uno porque de alguna manera había dejado caer esa parte por accidente.
shawnt00
Gracias por la actualización. Pensé que su fórmula excluía la fecha de inicio, que es lo que necesitaba. Lo resolví yo mismo y lo agregué como otra respuesta.
adrianm
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 tiene eso, puede usar 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

Ambos deben ser rápidos y elimina la ambigüedad / complejidad. La primera opción es la mejor, pero si no tiene una tabla de calendario, siempre puede crear una tabla de números con un CTE.

Brian
fuente
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 @TotalDays=@TotalDays-@ReducedDayswithEndDate

SET @WeekPart=@TotalDays/7;

SET @DatePart=@TotalDays%7;

SET @WorkDays=(@WeekPart*5)+@DatePart

SELECT @WorkDays
Muthuvel
fuente
Si va a usar una función, podría ser mejor usar una función basada en tablas como en la respuesta de Mário Meyrelles
James Jenkins
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
bel
fuente
1

Tomé los diversos ejemplos aquí, pero en mi situación particular tenemos un @PromisedDate para la entrega y un @ReceivedDate para la recepción real del artículo. Cuando se recibió un artículo antes del "PromisedDate", los cálculos no totalizaban correctamente a menos que ordenara las fechas pasadas 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
RobertD
fuente
1

Si necesita agregar días hábiles a una fecha determinada, puede crear una función que dependa de una tabla de calendario, que se describe 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
Mário Meyrelles
fuente
+1 Terminé usando una solución similar aquí
James Jenkins, el
1

Al igual que con DATEDIFF, no considero que la fecha de finalización sea parte del intervalo. El número de (por ejemplo) domingos entre @StartDate y @EndDate es el número de domingos entre un lunes "inicial" y el @EndDate menos el número de domingos entre este lunes "inicial" y el @StartDate. Sabiendo esto, podemos calcular el número de días laborables de la siguiente manera:

DECLARE @StartDate DATETIME
DECLARE @EndDate DATETIME
SET @StartDate = '2018/01/01'
SET @EndDate = '2019/01/01'

SELECT DATEDIFF(Day, @StartDate, @EndDate) -- Total Days
  - (DATEDIFF(Day, 0, @EndDate)/7 - DATEDIFF(Day, 0, @StartDate)/7) -- Sundays
  - (DATEDIFF(Day, -1, @EndDate)/7 - DATEDIFF(Day, -1, @StartDate)/7) -- Saturdays

¡Atentamente!

Wolfgang Kais
fuente
¡Perfecto! Esto es lo que estaba buscando. ¡Gracias especiales!
Fantasma
0

Eso funciona para mí, en mi país los sábados y domingos son días no laborables.

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
usuario3424126
fuente
0

Crear función 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
Igor Krupitsky
fuente
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

Final

pix1985
fuente
0

Encontré el siguiente TSQL como una solución bastante elegante (no tengo permisos para ejecutar funciones). Encontré los DATEDIFFignoraDATEFIRST y quería que mi primer día de la semana fuera lunes. También quería que el primer día hábil se estableciera en cero y si cae un fin de semana, el lunes será un cero. Esto puede ayudar a alguien que tiene un requisito ligeramente diferente :)

No maneja feriados bancarios

SET DATEFIRST 1
SELECT
,(DATEDIFF(DD,  [StartDate], [EndDate]))        
-(DATEDIFF(wk,  [StartDate], [EndDate]))        
-(DATEDIFF(wk, DATEADD(dd,-@@DATEFIRST,[StartDate]), DATEADD(dd,-@@DATEFIRST,[EndDate]))) AS [WorkingDays] 
FROM /*Your Table*/ 
Línea de base9
fuente
0

Un enfoque es 'recorrer las fechas' de principio a fin junto con una expresión de caso que verifica si el día no es sábado o domingo y lo marca (1 para el día de la semana, 0 para el fin de semana). Y al final solo suma banderas (sería igual a la cuenta de 1 banderas ya que la otra bandera es 0) para darte la cantidad de días de la semana.

Puede usar un tipo de función de utilidad GetNums (startNumber, endNumber) que genera una serie de números para 'bucles' 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)
umbersar
fuente
0

Tomé prestadas algunas ideas de otros para crear mi solución. Utilizo el código en línea para ignorar los fines de semana y feriados federales de los EE. UU. En mi entorno, EndDate puede ser nulo, pero nunca precederá a StartDate.

CREATE FUNCTION dbo.ufn_CalculateBusinessDays(
@StartDate DATE,
@EndDate DATE = NULL)

RETURNS INT
AS

BEGIN
DECLARE @TotalBusinessDays INT = 0;
DECLARE @TestDate DATE = @StartDate;


IF @EndDate IS NULL
    RETURN NULL;

WHILE @TestDate < @EndDate
BEGIN
    DECLARE @Month INT = DATEPART(MM, @TestDate);
    DECLARE @Day INT = DATEPART(DD, @TestDate);
    DECLARE @DayOfWeek INT = DATEPART(WEEKDAY, @TestDate) - 1; --Monday = 1, Tuesday = 2, etc.
    DECLARE @DayOccurrence INT = (@Day - 1) / 7 + 1; --Nth day of month (3rd Monday, for example)

    --Increment business day counter if not a weekend or holiday
    SELECT @TotalBusinessDays += (
        SELECT CASE
            --Saturday OR Sunday
            WHEN @DayOfWeek IN (6,7) THEN 0
            --New Year's Day
            WHEN @Month = 1 AND @Day = 1 THEN 0
            --MLK Jr. Day
            WHEN @Month = 1 AND @DayOfWeek = 1 AND @DayOccurrence = 3 THEN 0
            --G. Washington's Birthday
            WHEN @Month = 2 AND @DayOfWeek = 1 AND @DayOccurrence = 3 THEN 0
            --Memorial Day
            WHEN @Month = 5 AND @DayOfWeek = 1 AND @Day BETWEEN 25 AND 31 THEN 0
            --Independence Day
            WHEN @Month = 7 AND @Day = 4 THEN 0
            --Labor Day
            WHEN @Month = 9 AND @DayOfWeek = 1 AND @DayOccurrence = 1 THEN 0
            --Columbus Day
            WHEN @Month = 10 AND @DayOfWeek = 1 AND @DayOccurrence = 2 THEN 0
            --Veterans Day
            WHEN @Month = 11 AND @Day = 11 THEN 0
            --Thanksgiving
            WHEN @Month = 11 AND @DayOfWeek = 4 AND @DayOccurrence = 4 THEN 0
            --Christmas
            WHEN @Month = 12 AND @Day = 25 THEN 0
            ELSE 1
            END AS Result);

    SET @TestDate = DATEADD(dd, 1, @TestDate);
END

RETURN @TotalBusinessDays;
END
Gary
fuente