sql >> Databasteknik >  >> RDS >> PostgreSQL

Hur får man en dynamisk vy över 12 arbetsdagar i Postgresql?

Detta kan lösas med en 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);

Någon förklaring:

  • Den inre CTE börjar med att arbeta tillbaka till föregående måndag och kompensera för ett current_date som infaller på en helgdag.
  • Den rekursiva termen lägger sedan till rader genom att gå tillbaka hela veckor (back_day - 7 för kalenderdatum och go_back - 5 för arbetsdagarna) tills go_back = 0 .
  • Den yttre CTE returnerar back_day datum där go_back = 0 . Detta är därför en skalär fråga och du kan använda den som en underfråga i ett filteruttryck.

Du kan ändra antalet arbetsdagar för att se tillbaka genom att helt enkelt ändra siffrorna 12 och 7 i den initiala SELECT i den inre CTE. Tänk dock på att värdet bör vara sådant att det går tillbaka till föregående måndag annars misslyckas frågan på grund av samma initiala SELECT av den inre CTE.

En mycket mer flexibel (och förmodligen snabbare*) lösning är att använda följande 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;

Denna funktion kan ta vilket datum som helst att beräkna från och vilket antal dagar som helst i framtiden (positivt värde på diff ) eller det förflutna (negativt värde för diff ), inklusive skillnader inom den aktuella veckan. Och eftersom det returnerar arbetsdagsdatumet som en skalär, är användningen i din fråga mycket enkel:

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

Förutom det kan du också skicka in fält från ditt bord och göra läckra saker som:

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.v.s. en självanslutning vid ett visst antal arbetsdagars separation.

Observera att denna funktion förutsätter en måndag-fredag ​​arbetsdag i veckan.

* Denna funktion gör endast enkel aritmetik på skalära värden. CTE måste sätta upp alla slags strukturer för att stödja iterationen och de resulterande postuppsättningarna.




  1. Får du använda siffror som tabellnamn i MySQL?

  2. förvirring med att lägga till 2 tidsvärden

  3. mysql_num_fields():det angivna argumentet är inte en giltig MySQL-resultatresurs

  4. PostgreSQL-kodningsproblem när en fråga körs från kommandoraden