PostgreSQL
 sql >> Base de Dados >  >> RDS >> PostgreSQL

Como você obtém uma visualização dinâmica de 12 dias úteis no Postgresql?


Isso pode ser resolvido com um CTE:
WITH business_days_back AS (
  WITH RECURSIVE bd(back_day, go_back) AS (
    -- Go back to the previous Monday, allowing for current_date in the weekend
    SELECT CASE extract(dow from current_date)
             WHEN 0 THEN current_date - 6
             WHEN 6 THEN current_date - 5
             ELSE current_date - extract(dow from current_date)::int + 1
           END,
           CASE extract(dow from current_date)
             WHEN 0 THEN 7
             WHEN 6 THEN 7
             ELSE 12 - extract(dow from current_date)::int + 1
           END
    UNION
    -- Go back by the week until go_back = 0
    SELECT CASE
         WHEN go_back >= 5 THEN back_day - 7
         WHEN go_back > 0 THEN back_day - 2 - go_back
       END,
       CASE
         WHEN go_back >= 5 THEN go_back - 5
         WHEN go_back > 0 THEN 0
       END
    FROM bd
  )
  SELECT back_day FROM bd WHERE go_back = 0
)
SELECT * FROM my_table WHERE analysis_date >= (SELECT * FROM business_days_back);

Alguma explicação:
  • O CTE interno começa voltando para a segunda-feira anterior, compensando uma current_date que cai em um dia de fim de semana.
  • O termo recursivo adiciona linhas voltando semanas inteiras (back_day - 7 para a data do calendário e go_back - 5 para os dias úteis) até go_back = 0 .
  • O CTE externo retorna o back_day data em que go_back = 0 . Portanto, esta é uma consulta escalar e você pode usá-la como uma subconsulta em uma expressão de filtro.

Você pode alterar o número de dias úteis para olhar para trás simplesmente alterando os números 12 e 7 no SELECT inicial no CTE interno. Tenha em mente, porém, que o valor deve ser tal que volte para a segunda-feira anterior ou a consulta falhará, devido ao mesmo SELECT inicial do CTE interno.

Uma solução muito mais flexível (e provavelmente mais rápida*) é usar a seguinte função:
CREATE FUNCTION business_days_diff(from_date date, diff int) RETURNS date AS $$
-- This function assumes Mon-Fri business days
DECLARE
  start_dow int;
  calc_date date;
  curr_diff int;
  weekend   int;
BEGIN
  -- If no diff requested, return the from_date. This may be a non-business day.
  IF diff = 0 THEN
    RETURN from_date;
  END IF;

  start_dow := extract(dow from from_date)::int;
  calc_date := from_date;

  IF diff < 0 THEN -- working backwards
    weekend := -2;
    IF start_dow = 0 THEN -- Fudge initial Sunday to the previous Saturday
      calc_date := calc_date - 1;
      start_dow := 6;
    END IF;
    IF start_dow + diff >= 1 THEN -- Stay in this week
      RETURN calc_date + diff;
    ELSE                             -- Work back to Monday
      calc_date := calc_date - start_dow + 1;
      curr_diff := diff + start_dow - 1;
    END IF;
  ELSE -- Working forwards
    weekend := 2;
    IF start_dow = 6 THEN -- Fudge initial Saturday to the following Sunday
      calc_date := calc_date + 1;
      start_dow := 0;
    END IF;
    IF start_dow + diff <= 5 THEN -- Stay in this week
      RETURN calc_date + diff;
    ELSE                             -- Work forwards to Friday
      calc_date := calc_date + 5 - start_dow;
      curr_diff := diff - 5 + start_dow;
    END IF;
  END IF;

  -- Move backwards or forwards by full weeks
  calc_date := calc_date + (curr_diff / 5) * 7;

  -- Process any remaining days, include weekend
  IF curr_diff % 5 != 0 THEN
    RETURN calc_date + curr_diff % 5 + weekend;
  ELSE
    RETURN calc_date;
  END IF;
END; $$ LANGUAGE plpgsql STRICT IMMUTABLE;

Esta função pode tomar qualquer data para calcular e qualquer número de dias no futuro (valor positivo de diff ) ou o passado (valor negativo de diff ), incluindo diferenças na semana atual. E como ele retorna a data do dia útil como escalar, o uso na sua consulta é bem direto:
SELECT * 
FROM table
WHERE analysis_date >= business_days_diff(current_date, -12);

Além disso, você também pode passar campos da sua tabela e fazer coisas divertidas como:
SELECT t1.some_value - t2.some_value AS value_diff
FROM table t1
JOIN table t2 ON t2.analysis_date = business_days_diff(t1.analysis_date, -12);

ou seja, uma auto-junção em um certo número de dias úteis de separação.

Observe que esta função assume uma semana de dias úteis de segunda a sexta-feira.

* Esta função faz apenas aritmética simples em valores escalares. O CTE precisa configurar todos os tipos de estruturas para suportar a iteração e os conjuntos de registros resultantes.