Contar dias úteis entre duas datas

162

Como posso calcular o número de dias úteis entre duas datas no SQL Server?

Segunda a sexta-feira e deve ser T-SQL.

Ovidiu Pacurar
fonte
5
Você pode definir dias úteis? qualquer segunda a sexta-feira? Excluindo feriados importantes? Que país? Isso deve ser feito em SQL?
Dave K

Respostas:

300

Para dias úteis, de segunda a sexta-feira, você pode fazer isso com um único SELECT, assim:

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)

Se você deseja incluir férias, você deve trabalhar um pouco ...

CMS
fonte
3
Acabei de perceber que esse código nem sempre funciona! Eu tentei isso: SET @StartDate = '28 -mar-2011' SET @EndDate = '29 -mar-2011' a resposta contou-lo como 2 dias
greektreat
16
@greektreat Funciona bem. Só que @StartDate e @EndDate estão incluídos na contagem. Se você deseja que a segunda a terça-feira conte como 1 dia, remova o "+ 1" após o primeiro DATEDIFF. Então você também terá Fri-> Sat = 0, Fri-> Sun = 0, Fri-> Mon = 1.
Joe Daley
6
Como acompanhamento de @JoeDaley. Quando você remove o + 1 após o DATEDIFF para excluir a data de início da contagem, também é necessário ajustar a parte CASE. Acabei usando isso: + (CASE WHEN DATENAME (dw, @StartDate) = 'Saturday' THEN 1 ELSE 0 END) - (CASE WHEN DATENAME (dw, @EndDate) = 'Saturday' THEN 1 ELSE 0 END)
Sequenzia
7
A função de nome de dados depende da localidade. A solução mais robusta, mas também mais obscuro é substituir as duas últimas linhas 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 esclarecer o comentário de @ Sequenzia, você REMOVA inteiramente as declarações de caso sobre domingo, deixando apenas+(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

Em Calcular dias de trabalho, você pode encontrar um bom artigo sobre esse assunto, mas como pode ver, não é tão avançado.

--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

Se você precisar usar um calendário personalizado, poderá ser necessário adicionar algumas verificações e alguns parâmetros. Espero que isso forneça um bom ponto de partida.

Bogdan Maxim
fonte
Obrigado por incluir o link para entender como isso funciona. A gravação no sqlservercentral foi ótima!
31413 Chris Porter
20

Todo o crédito a Bogdan Maxim e Peter Mortensen. Esta é a postagem deles, acabei de adicionar feriados à função (Isso pressupõe que você tenha uma tabela "tblHolidays" com um campo de data e 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
fonte
2
Olá, Dan B. Apenas para informar que sua versão assume que a tabela tblHolidays não contém sábados e segundas-feiras, o que às vezes acontece. De qualquer forma, obrigado por compartilhar sua versão. Cheers
Julio Nobre
3
Julio - Sim - Minha versão pressupõe que sábado e domingo (não segunda-feira) são finais de semana e, portanto, não são dias "não comerciais". Mas se você estiver trabalhando nos fins de semana, acho que todo dia é um "dia útil" e você pode comentar a parte da cláusula de sábado e domingo e apenas adicionar todas as suas férias à tabela tblHolidays.
Dan B
1
Obrigado Dan. Eu incorporei isso em minha função, adicionando uma verificação para fins de semana, pois minha tabela DateDimensions inclui todas as datas, feriados, etc. Tomando sua função, eu apenas adicionei: e IsWeekend = 0 após o local [HolDate] entre StartDate e EndDate)
AlsoKnownAsJazz
Se a tabela Feriado contiver feriados nos finais de semana, você poderá alterar os critérios desta forma: WHERE HolDate BETWEEN @StartDate AND @EndDate AND DATEPART(dw, HolDate) BETWEEN 2 AND 6para contar apenas os feriados de segunda a sexta-feira.
Andre
7

Outra abordagem para calcular os dias úteis é usar um loop WHILE, que basicamente percorre um intervalo de datas e o incrementa em 1 sempre que os dias ocorrem entre segunda e sexta-feira. O script completo para calcular os dias úteis usando o loop WHILE é mostrado abaixo:

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

Embora a opção de loop WHILE seja mais limpa e use menos linhas de código, ela pode ser um gargalo de desempenho em seu ambiente, principalmente quando o período se estende por vários anos.

Você pode ver mais métodos sobre como calcular dias e horas de trabalho neste artigo: https://www.sqlshack.com/how-to-calculate-work-days-and-hours-in-sql-server/

AliceF
fonte
6

Minha versão da resposta aceita como uma função usando DATEPART, então não preciso fazer uma comparação de strings na linha com

DATENAME(dw, @StartDate) = 'Sunday'

Enfim, aqui está a minha função datada de negócios

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
fonte
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
fonte
Se você código postal, amostras de XML ou de dados, por favor destacar as linhas no editor de texto e clique no botão "código de amostras" ({}) na barra de ferramentas do editor de bem formato e sintaxe destacá-lo!
21411 Marc Marsh
Ótimo, não há necessidade de funções periféricas ou atualizações no banco de dados usando isso. Obrigado. Love the saltire btw :-)
Brian Scott
Super solução. Eu subbed em fórmulas para variáveis ​​a serem usadas em um universo webi para calcular os dias da semana (MF) entre as datas em 2 colunas da tabela assim ... (((((DATEDIFF (dia, tabela.col1, tabela.col2) +1) - ((CASE DATENAME (dia da semana, tabela.col2) QUANDO 'Sábado' ENTÃO 1 QUANDO 'Domingo' ENTÃO 2 MAIS 0 FINAL))) / 7) * 5) + (((DATEDIFF (dia, tabela.col1, tabela.col2) ) +1) - ((CASE DATENAME (dia da semana, tabela.col2) QUANDO 'Sábado' ENTÃO 1 QUANDO 'Domingo' ENTÃO 2 MAIS 0 FIM)))% 7)
Hilary
5

(Eu tenho alguns pontos a menos de comentar os privilégios)

Se você decidir renunciar ao dia +1 na solução elegante do CMS , observe que, se sua data de início e data de término forem no mesmo fim de semana, você receberá uma resposta negativa. Ou seja, 2008/10/26 a 2008/10/26 retorna -1.

minha solução bastante simplista:

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

.. que também define todas as postagens incorretas com data de início após a data de término como zero. Algo que você pode ou não estar procurando.

phareim
fonte
5

Para a diferença entre datas, incluindo feriados, eu fui assim:

1) Mesa com feriados:

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

2) Eu tinha minha tabela de planejamentos assim e queria preencher a coluna Work_Days que estava vazia:

    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) Então, para fazer com que "Work_Days" preencha posteriormente minha coluna, bastava:

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 eu possa ajudar.

Felicidades

joaopintocruz
fonte
1
Em relação às subtrações de feriados. E se a data de início for 1º de janeiro e a data final for 31 de dezembro? Você subtrairá apenas 2 - o que está errado. Proponho usar DATEDIFF (dia, Data_Inicial, Data) e o mesmo para End_Date em vez de todo 'SELECT COUNT (*) FROM Holiday ...'.
Illia Ratkevych
4

Aqui está uma versão que funciona bem (eu acho). A tabela Holiday contém colunas Holiday_date que contêm feriados observados pela sua 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 
user2733766
fonte
Essas datas de feriado também podem cair nos finais de semana. E para alguns, as férias no domingo serão substituídas pela próxima segunda-feira.
Irawan Soetomo
3

Sei que essa é uma pergunta antiga, mas precisava de uma fórmula para dias úteis, excluindo a data de início, pois tenho vários itens e preciso que os dias se acumulem corretamente.

Nenhuma das respostas não iterativas funcionou para mim.

Eu usei uma definição como

Número de vezes que passa meia-noite a segunda-feira, terça-feira, quarta-feira, quinta-feira e sexta-feira

(outros podem contar da meia-noite ao sábado em vez de segunda-feira)

Acabei com 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
fonte
1
Aquele fez isso por mim, mas eu tive que fazer uma pequena alteração. Não estava explicando quando @StartDateé um sábado ou sexta-feira. Aqui está a minha versão: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 retorna 0, sábado a segunda-feira retorna 1, sexta-feira a sábado retorna 0. Todos são consistentes com minha definição. Seu código não irá acumular corretamente (por exemplo, retornar 6 para sexta-feira a sexta-feira, mas 5 para segunda-feira a segunda-feira)
adrianm
3

Essa é basicamente a resposta do CMS sem depender de uma configuração de idioma específica. E, como estamos filmando para genéricos, isso significa que deve funcionar para todas as @@datefirstconfigurações também.

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, ...) sempre usa um limite de sábado a domingo por semanas, para que a expressão seja determinística e não precise ser modificada (desde que nossa definição de dias da semana seja consistentemente de segunda a sexta-feira.) A numeração dos dias varia de acordo com o @@datefirst configuração e as cálculos modificados lidam com essa correção com a pequena complicação de alguma aritmética modular.

Uma maneira mais limpa de lidar com a coisa de sábado / domingo é traduzir as datas anteriores à extração do valor do dia da semana. Após a mudança, os valores voltarão à linha com uma numeração fixa (e provavelmente mais familiar) que começa com 1 no domingo e termina com 7 no 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

Rastreei essa forma da solução em pelo menos 2002 e em um artigo da Itzik Ben-Gan. ( https://technet.microsoft.com/en-us/library/aa175781(v=sql.80).aspx ) Apesar de precisar de um pequeno ajuste desde que o mais recentedate tipos não permitem aritmética de datas, é idêntico.

EDIT: Eu adicionei de volta o +1que de alguma forma foi deixado de fora. Também é importante notar que esse método sempre conta os dias de início e fim. Ele também pressupõe que a data de término seja posterior ou posterior à data de início.

shawnt00
fonte
Observe que isso retornará resultados incorretos para muitas datas nos fins de semana, para que não aumentem (Fri-> Seg deve ser o mesmo que Fri-> Sáb + Sáb-> Sol + Sol-> Seg). Fri-> Sáb deve ser 0 (correto), Sáb-> Dom deve ser 0 (errado -1), Dom-> Seg deve ser 1 (0 errado). Outros erros a seguir são Sat-> Sat = -1, Sun-> Sun = -1, Sun-> Sat = 4
adrianm
@adrianm Acho que corrigi os problemas. Na verdade, o problema era que ele sempre era desligado por uma, porque, de alguma forma, eu deixei essa parte por acidente.
precisa saber é o seguinte
Obrigado pela atualização. Eu pensei que sua fórmula estava excluindo a data de início, que é o que eu precisava. Resolvi e adicionei como outra resposta.
Adrianm 5/07
2

Usando uma tabela de datas:

    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;

Se você não possui, pode usar uma tabela 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 devem ser rápidos e eliminam a ambiguidade / complexidade. A primeira opção é a melhor, mas se você não tiver uma tabela de calendário, poderá sempre criar uma tabela de números com um CTE.

Brian
fonte
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
fonte
Se você estiver indo para usar uma função, que poderia ser melhor para ir com uma função baseada em tabela como na resposta por 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
fonte
1

Peguei os vários exemplos aqui, mas em minha situação particular, temos um @PromisedDate para entrega e um @ReceivedDate para o recebimento real do item. Quando um item foi recebido antes do "PromisedDate", os cálculos não estavam totalizando corretamente, a menos que eu pedisse as datas passadas para a função por ordem de calendário. Não querendo verificar as datas todas as vezes, mudei a função para lidar com isso para mim.

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
fonte
1

Se você precisar adicionar dias úteis a uma determinada data, poderá criar uma função que depende de uma tabela de calendário, descrita abaixo:

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
fonte
+1 Acabei usando uma solução semelhante aqui
James Jenkins
1

Assim como no DATEDIFF, não considero a data final como parte do intervalo. O número de (por exemplo) domingos entre @StartDate e @EndDate é o número de domingos entre uma segunda-feira "inicial" e o @EndDate menos o número de domingos entre essa segunda-feira "inicial" e o @StartDate. Sabendo disso, podemos calcular o número de dias úteis da seguinte maneira:

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

Cumprimentos!

Wolfgang Kais
fonte
Perfeito! Era isso que eu estava procurando. Agradecimentos especiais!
Phantom
0

Isso está funcionando para mim, no meu país, no sábado e domingo, são dias não úteis.

Para mim, é importante o tempo de @StartDate e @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
user3424126
fonte
0

Crie uma função 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

Você pode chamar a função como:

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

Ou como:

select dbo.fn_WorkDays(StartDate, EndDate) 
from table1
Igor Krupitsky
fonte
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

Fim

pix1985
fonte
0

Eu achei o TSQL abaixo uma solução bastante elegante (não tenho permissões para executar funções). Eu encontrei os DATEDIFFignoraDATEFIRST e queria que meu primeiro dia da semana fosse segunda-feira. Eu também queria que o primeiro dia útil fosse definido como zero e, se cair em um fim de semana, a segunda-feira será zero. Isso pode ajudar alguém que tem um requisito ligeiramente diferente :)

Não lida com feriados

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*/ 
Linha de base9
fonte
0

Uma abordagem é 'percorrer as datas' do início ao fim em conjunto com uma expressão de caso que verifica se o dia não é um sábado ou um domingo e sinalizá-lo (1 para dia da semana, 0 para final de semana). E, no final, basta somar sinalizadores (seria igual à contagem de 1 sinalizadores, pois o outro sinalizador é 0) para fornecer o número de dias da semana.

Você pode usar uma função de utilitário do tipo GetNums (startNumber, endNumber), que gera uma série de números para 'loop' da data de início até a data de término. Consulte http://tsql.solidq.com/SourceCodes/GetNums.txt para obter uma implementação. A lógica também pode ser estendida para atender a feriados (digamos se você tiver uma tabela de feriados)

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
fonte
0

Peguei emprestadas algumas idéias de outras pessoas para criar minha solução. Eu uso o código embutido para ignorar fins de semana e feriados federais dos EUA. No meu ambiente, EndDate pode ser nulo, mas nunca precederá 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
fonte