PostgreSQL
 sql >> Base de données >  >> RDS >> PostgreSQL

Comment obtenir une vue dynamique sur 12 jours ouvrés dans Postgresql ?

Cela peut être résolu avec un 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);

Quelques explications :

  • Le CTE interne commence par remonter au lundi précédent, en compensant une current_date qui tombe un jour de week-end.
  • Le terme récursif ajoute ensuite des lignes en remontant des semaines entières (back_day - 7 pour la date du calendrier et go_back - 5 pour les jours ouvrables) jusqu'à go_back = 0 .
  • Le CTE externe renvoie le back_day date où go_back = 0 . Il s'agit donc d'une requête scalaire et vous pouvez l'utiliser comme sous-requête dans une expression de filtre.

Vous pouvez modifier le nombre de jours ouvrables à regarder en arrière en changeant simplement les chiffres 12 et 7 dans le SELECT initial dans le CTE intérieur. Gardez à l'esprit, cependant, que la valeur doit être telle qu'elle remonte au lundi précédent ou la requête échouera, en raison du même SELECT initial du CTE intérieur.

Une solution beaucoup plus flexible (et probablement plus rapide*) consiste à utiliser la fonction suivante :

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;

Cette fonction peut prendre n'importe quelle date à partir de laquelle calculer et n'importe quel nombre de jours dans le futur (valeur positive de diff ) ou le passé (valeur négative de diff ), y compris les différences de la semaine en cours. Et comme il renvoie la date du jour ouvrable sous forme de scalaire, l'utilisation dans votre requête est très simple :

SELECT * 
FROM table
WHERE analysis_date >= business_days_diff(current_date, -12);

En dehors de cela, vous pouvez également passer des champs depuis votre table et faire des choses géniales comme :

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

c'est-à-dire une auto-jointure sur un certain nombre de jours ouvrables de séparation.

Notez que cette fonction suppose une semaine de jour ouvrable du lundi au vendredi.

* Cette fonction ne fait que de l'arithmétique simple sur des valeurs scalaires. Le CTE doit mettre en place toutes sortes de structures pour prendre en charge l'itération et les ensembles d'enregistrements qui en résultent.