PostgreSQL
 sql >> Datenbank >  >> RDS >> PostgreSQL

Wie erhalten Sie eine dynamische 12-Tage-Ansicht in Postgresql?

Dies kann mit einem CTE gelöst werden:

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

Einige Erklärungen:

  • Der innere CTE beginnt damit, dass er zum vorherigen Montag zurückarbeitet und ein current_date kompensiert das auf einen Wochenendtag fällt.
  • Der rekursive Begriff fügt dann Zeilen hinzu, indem er volle Wochen zurückgeht (back_day - 7 für das Kalenderdatum und go_back - 5 für die Werktage) bis go_back = 0 .
  • Der äußere CTE gibt den back_day zurück Datum, wobei go_back = 0 . Dies ist daher eine skalare Abfrage und Sie können sie als Unterabfrage in einem Filterausdruck verwenden.

Sie können die Anzahl der zurückblickenden Geschäftstage ändern, indem Sie einfach die Zahlen 12 ändern und 7 im anfänglichen SELECT im inneren CTE. Denken Sie jedoch daran, dass der Wert so sein sollte, dass er auf den vorherigen Montag zurückgeht, oder die Abfrage schlägt aufgrund des gleichen anfänglichen SELECT fehl des inneren CTE.

Eine viel flexiblere (und wahrscheinlich schnellere*) Lösung ist die Verwendung der folgenden Funktion:

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;

Diese Funktion kann ein beliebiges Datum zur Berechnung und eine beliebige Anzahl von Tagen in die Zukunft nehmen (positiver Wert von diff ) oder der Vergangenheit (negativer Wert von diff ), einschließlich Diffs innerhalb der aktuellen Woche. Und da es das Datum des Geschäftstages als Skalar zurückgibt, ist die Verwendung in Ihrer Abfrage sehr einfach:

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

Abgesehen davon können Sie auch Felder aus Ihrer Tabelle übergeben und verrückte Sachen machen wie:

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

d.h. ein Selbstbeitritt nach einer bestimmten Anzahl von Geschäftstagen.

Beachten Sie, dass diese Funktion von einer Werktagswoche von Montag bis Freitag ausgeht.

* Diese Funktion führt nur einfache Arithmetik mit skalaren Werten durch. Das CTE muss alle Arten von Strukturen einrichten, um die Iteration und die resultierenden Datensätze zu unterstützen.