您如何在 Postgresql 中获得动态的 12 个工作日视图?

How do you get a dynamic 12 business day view in Postgresql?

这是我目前拥有的代码,它提供了我过去 12 天的数据

SELECT * 
FROM table
WHERE analysis_date >= current_date - interval '12' day;

analysis_date 是 table 中的日期列。我明白为什么这不起作用,因为它没有计算工作日。我怎样才能重写这个,以便我得到最近 12 个工作日的时间间隔?

我尝试在线搜索并找到

extract(dow from (date))

但我找不到需要工作日间隔的示例。任何帮助,将不胜感激。

这可以用 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);

一些解释:

  • 内部 CTE 从回到上周一开始,补偿 current_date 周末的一天。
  • 递归项然后通过返回整周(back_day - 7 用于日历日期,go_back - 5 用于工作日)添加行,直到 go_back = 0.
  • 外部 CTE returns back_day 日期 go_back = 0。因此,这是一个标量查询,您可以将其用作过滤器表达式中的子查询。

您只需更改内部 CTE 中初始 SELECT 中的数字 127,即可更改回顾的工作日数。但请记住,由于内部 CTE 的初始 SELECT 相同,该值应该可以追溯到前一个星期一,否则查询将失败。

更灵活(并且可能更快*)的解决方案是使用以下函数:

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;

此函数可以使用任何日期来计算未来(diff 的正值)或过去(diff 的负值)的任意天数,包括当前一周。由于它 returns 工作日日期作为标量,因此在您的查询中使用非常简单:

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

除此之外,您还可以传入 table 中的字段并执行一些时髦的操作,例如:

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

即一定数量的工作日分离的自连接。

请注意,此函数假定工作日为周一至周五。

* 此函数仅对标量值进行简单的算术运算。 CTE 必须设置各种结构来支持迭代和生成的记录集。