Obtendo número de certos dias-de-semana (fim de semana) de intervalo no PostgreSQL
-
09-09-2019 - |
Pergunta
Dado 2 timestamps no Postgres, como você calcular a diferença de tempo sem contar toda sábados e domingos?
ou
Como você contar o número de sábados e domingos em um determinado intervalo de tempo?
Solução
A função a seguir está retornando o número de dias completos de fim de semana entre duas datas. Como você precisa dias completos, você pode lançar a data e hora para datas antes de chamar a função. Ele retorna 0 no caso da primeira data não é estritamente antes do segundo.
CREATE FUNCTION count_full_weekend_days(date, date)
RETURNS int AS
$BODY$
SELECT
($1 < $2)::int
*
(
(($2 - $1) / 7) * 2
+
(EXTRACT(dow FROM $1)<6 AND EXTRACT(dow FROM $2)>0 AND EXTRACT(dow FROM $1)>EXTRACT(dow FROM $2))::int * 2
+
(EXTRACT(dow FROM $1)=6 AND EXTRACT(dow FROM $2)>0)::int
+
(EXTRACT(dow FROM $2)=0 AND EXTRACT(dow FROM $1)<6)::int
);
$BODY$
LANGUAGE 'SQL' IMMUTABLE STRICT;
Exemplos:
SELECT COUNT_FULL_WEEKEND_DAYS('2009-04-10', '2009-04-20');
# returns 4
SELECT COUNT_FULL_WEEKEND_DAYS('2009-04-11', '2009-04-20');
# returns 3 (11th is Saturday, so it shouldn't be counted as full day)
SELECT COUNT_FULL_WEEKEND_DAYS('2009-04-12', '2009-04-20');
# returns 2 (12th is Sunday, so it shouldn't be counted as full day)
SELECT COUNT_FULL_WEEKEND_DAYS('2009-04-13', '2009-04-20');
# returns 2
Para obter o número de dias, exceto dias completos de fim de semana, basta subtrair o número de dias a partir da função acima:
SELECT
'2009-04-20'::date
-
'2009-04-13'::date
-
COUNT_FULL_WEEKEND_DAYS('2009-04-13', '2009-04-20');
Outras dicas
Você pode achar isso realmente útil:
CREATE OR REPLACE FUNCTION working_days(date, date) RETURNS INT AS
$$
SELECT COUNT(days)::INT
FROM generate_series($1, $2, '1 day') AS days
WHERE EXTRACT(DOW FROM days) NOT IN(0, 6);
$$
LANGUAGE 'sql' IMMUTABLE STRICT;
(dias / 7) * 2 + número de Sáb / Dom nos últimos dias (% 7) dias.
Isso deve responder à segunda parte da sua pergunta:
create or replace function is_weekend_day(date) returns boolean
strict immutable language 'sql'
as $$ select case extract(dow from $1) when 0 then true when 6 then true else false end $$;
create or replace function count_weekend_days(start_date date, end_date date) returns int
strict immutable language 'sql'
as $$
select cast(sum(case when is_weekend_day($1 + ofs) then 1 else 0 end) as int)
from generate_series(0, $2 - $1) ofs
$$;
Fazendo um count_non_weekend_days
correspondente é simples depois disso.
A melhor solução para isso é usar uma tabela de calendário. Isso é incrivelmente útil e você pode fazer todo tipo de coisas interessantes -. Incluindo contagem do número de dias úteis entre duas datas ou contagem do número de feriados entre duas datas
Esta tabela é normalmente preenchido com antecedência - digamos por 20 anos com a data para férias bem conhecidos adequadamente marcadas. Se feriados mudar, você manter a mesa de vez em quando para marcar os dias como feriados. Mais informações aqui e aqui . Isto usa MS SQL Server, mas é facilmente portado bem.
Você pode encontrar este blogpost útil: http://www.depesz.com/index.php/2007/12/27/how-many-1sts-of-any-month-were -sundays desde-1901/01/01 /
Isto irá contar o número de um determinado dia entre duas datas:
-- 0 Sunday
-- 1 Monday
-- 2 Tuesday
-- 3 Wednesday
-- 4 Thursday
-- 5 Friday
-- 6 Saturday
WITH rng AS (
SELECT
'march 3 2013'::date AS start,
'march 3 2014'::date AS end,
0 AS day -- Sunday
)
SELECT count(1)
FROM rng, generate_series(0, (extract(epoch from age(rng.end, rng.start)) / (60*60*24))::int) AS n
WHERE extract(dow from rng.start + (n * '1 day'::interval)) = rng.day
Eu sugiro que você criar uma função para uso sempre que quiser e escrever menos; )
Este código acima irá criar uma função SQL que contagem e retornar a quantidade de dias de fim de semana (Sáb, Dom). Do jeito que você terá mais flexibilidade para usar esta função.
CREATE OR REPLACE FUNCTION <YourSchemaNameOptional>.count_full_weekend_days(date, date)
RETURNS bigint AS
$BODY$
select COUNT(MySerie.*) as Qtde
from (select CURRENT_DATE + i as Date, EXTRACT(DOW FROM CURRENT_DATE + i) as DiaDate
from generate_series(date ($1) - CURRENT_DATE, date ($2) - CURRENT_DATE ) i) as MySerie
WHERE MySerie.DiaDate in (6,0);
$BODY$
LANGUAGE 'SQL' IMMUTABLE STRICT;
Depois disso, você pode usar a função para retornar apenas o número de dias de semana em um intervalo. Aqui está o exemplo de uso:
SELECT <YourSchemaNameOptional>.count_full_weekend_days('2017-09-11', '2017-09-25') as days; --> RES: 4
Esta select deve retornar quatro porque o primeiro eo segundo dia é segunda-feira, e temos 2 sábados e domingos 2 entre eles.
Agora, para retornar apenas dias úteis (sem fins de semana), como você deseja , basta fazer uma subtração, como no exemplo abaixo:
SELECT (date '2017-09-25' - date '2017-09-11' ) - <YourSchemaName>.count_full_weekend_days('2017-09-11', '2017-09-25'); --> RES: 14 - 4 = 10