在PostgreSQL中查询日期范围,不包括周末。

huangapple go评论69阅读模式
英文:

Query date ranges excluding weekends in PostgreSQL

问题

我有以下的PostgreSQL表:

     id   |        date_slot       
    ------+-------------------------
     1    | [2023-02-08,2023-02-15)
     2    | [2023-02-20,2023-02-26)
     3    | [2023-02-27,2023-03-29)

我想要查询包含这些范围但排除周末的行。

例如,我做的查询返回以下结果,但不排除周末。
```lang-sql
SELECT * FROM table where '2023-02-11'::date <@ date_slot;
 id   |        date_slot       
------+-------------------------
 1    | [2023-02-08,2023-02-15)

2023-02-11是一个周末,所以它不应该返回结果。我该如何做到呢?


<details>
<summary>英文:</summary>

I have the following postgresql table;

     id   |        date_slot        
    ------+-------------------------
     1    | [2023-02-08,2023-02-15)
     2    | [2023-02-20,2023-02-26)
     3    | [2023-02-27,2023-03-29)

I want to make a query that return rows contained in these ranges but exclude weekends 

for example the query I made return the following but does not exclude the weekends. 
```lang-sql
SELECT * FROM table where &#39;2023-02-11&#39;::date &lt;@ date_slot;
 id   |        date_slot        
------+-------------------------
 1    | [2023-02-08,2023-02-15)

2023-02-11 is a weekend so it must not return a result. How can I do that?

答案1

得分: 1

选择仅包括工作日的 daterange(不包括周末):

您可以使用 extract() 来检查日期范围的第一天是一周中的哪一天,通过 upper()-lower() 来确定日期范围的长度,然后判断它是否会跨越周末:在线演示

select * 
from test_table 
where '2023-02-11'::date <@ date_slot
and extract(isodow from lower(date_slot)
                        + (not lower_inc(date_slot))::int)
  +(  (upper(date_slot) - (not upper_inc(date_slot))::int)
     -(lower(date_slot) + (not lower_inc(date_slot))::int) ) < 6 ;

处理范围的上限和下限包容性不同的情况可以使用 lower_inc()upper_inc() 来处理 - 当将它们的布尔结果转换为整数时,它们会根据范围是否包含日期来添加或减去一天。

如果日期范围的起始日期是周末或者从一周的其他任何工作日延续得太久,那么该范围将涵盖周末:

  • 如果从星期一开始(isodow=1),持续4天
  • 如果从星期二开始(isodow=2),持续3天
  • 如果从星期三开始(isodow=3),持续2天
  • 如果从星期四开始(isodow=4),持续1天
  • 如果从星期五开始(isodow=5),持续0天

这意味着日期范围的起始日期的 isodow 和范围长度的总和不能超过5,以避免范围与周末重叠。

您还可以使用 generate_series() 枚举包含在这些日期范围内的日期,并查看它们是否包括星期六(dow6)或星期日(dow0isodow7):

select * 
from test_table 
where '2023-02-11'::date <@ date_slot
and not exists (
  select true
  from generate_series(
          lower(date_slot) + (not lower_inc(date_slot))::int,
          upper(date_slot) - (not upper_inc(date_slot))::int,
          '1 day'::interval) as alias(d)
  where extract(isodow from d) in (6,7) );

根据仅包括工作日的 date 选择记录:

第一个评论 中的方法是正确的:

select * 
from table_with_dateranges dr, 
     table_with_dates d
where d.date <@ dr.date_slot
and extract(isodow from d.date) not in (6,7);
英文:

Selecting workday-only dateranges (without weekends):

You can check what day of the week it is on the first day in the range using extract() and knowing its length from upper()-lower(), determine if it'll cross a weekend: online demo

select * 
from test_table 
where &#39;2023-02-11&#39;::date &lt;@ date_slot
and extract(isodow from lower(date_slot)
                        + (not lower_inc(date_slot))::int)
  +(  (upper(date_slot) - (not upper_inc(date_slot))::int)
     -(lower(date_slot) + (not lower_inc(date_slot))::int) ) &lt; 6 ;

Cases where your ranges have varying upper and lower bound inclusivity are handled by lower_inc() and upper_inc() - their boolean result, when cast to int, just adds or subtracts a day to account for whether it's included by the range or not.

The range is on or includes a weekend if it starts on a weekend day or continues for too long from any other day of the week:

  • 4 days, if it starts on a Monday (isodow=1)
  • 3 days, if it starts on a Tuesday (isodow=2)
  • 2 days, if it starts on a Wednesday (isodow=3)
  • 1 days, if it starts on a Thursday (isodow=4)
  • 0 days, if it starts on a Friday (isodow=5)

This means the isodow of range start date and the range length cannot sum up to more than 5 for the range not to overlap a weekend.


You can also enumerate the dates contained by these ranges using generate_series() and see if they include a Saturday (day 6) or a Sunday (0 as dow, 7 as isodow):

select * 
from test_table 
where &#39;2023-02-11&#39;::date &lt;@ date_slot
and not exists (
  select true
  from generate_series(
          lower(date_slot) + (not lower_inc(date_slot))::int,
          upper(date_slot) - (not upper_inc(date_slot))::int,
          &#39;1 day&#39;::interval) as alias(d)
  where extract(isodow from d) in (6,7) );

Selecting records based on workday-only dates:

First comment got it right

select * 
from table_with_dateranges dr, 
     table_with_dates d
where d.date &lt;@ dr.date_slot
and extract(isodow from d.date) not in (6,7);

huangapple
  • 本文由 发表于 2023年2月8日 18:57:17
  • 转载请务必保留本文链接:https://go.coder-hub.com/75384771.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定