如何避免在 Rails 中使用 PostgreSQL 的 BETWEEN 时间戳重复?

How to avoid duplicates using PostgreSQL's BETWEEN for timestamps in Rails?

我的 Rails 应用程序中有一个如下所示的查询。本质上,我想获取在昨天 9:30am 和今天 9:30am 之间创建的记录。我打算在每天运行一次的任务中使用此查询。

last_execution_time = Time.zone.parse("#{Time.zone.yesterday.strftime('%Y-%m-%d')} 09:30:00}")
this_execution_time = Time.zone.parse("#{Time.zone.today.strftime('%Y-%m-%d')} 09:30:00}")

new_cat_records = Cat.where(created_at: last_execution_time..this_execution_time)

但是,我担心恰好在 9:30:00 上午创建的记录会发生什么情况。如果我今天和明天 运行 这个查询,它会被包括在内吗? 我知道 PostgreSQL 的 BETWEEN 包括 运行ge 边界 (docs):

The BETWEEN predicate simplifies range tests:

a BETWEEN x AND y

is equivalent to

a >= x AND a <= y

Notice that BETWEEN treats the endpoint values as included in the range.

如果上面的代码可能会导致重复,我该如何避免呢?


[编辑] 我使用 rails 5.2.3pg 1.1.4.

您可以这样查询:

Cat.where("created_at >= ? AND created_at < ?", last_execution_time, this_execution_time)

Cat.where("created_at > ? AND created_at <= ?", last_execution_time, this_execution_time)

不过我不确定这是否会影响性能。

是的,如果将 between 与 09:30:00 一起使用,边界条件会出现小问题。09:30:00

你可以用毫秒的方式改变this_execution_time:

this_execution_time = Time.zone.parse("#{Time.zone.today.strftime('%Y-%m-%d')} 09:29:59.999999}")

或者你可以使用 Arel 或 clean sql 编写正确的条件:

Cat.where(Cat.arel_table[:created_at].gteq(last_execution_time).and(Cat.arel_table[:created_at].lt(this_execution_time)))
Cat.where("created_at >= ? AND created_at < ?", last_execution_time, this_execution_time)

但是如果你写一些边界条件测试并在那里检查就更好了。

我知道的不多Rails,读过一些书,仅此而已,但我确实知道一点 Postgres - 也许它会有所帮助。 Postgres 有一个 intervals 的概念,它允许设置像 BETWEEN 这样的结构,但也允许定义是否包含端点。在这种情况下,包括 start_time 并排除 end_time。下面创建这样一个间隔:

with date_period as 
     ( select current_date + interval '9:30:00' d1
            , current_date + interval '1 day' + interval '9:30:00' d2
     ) 
   , op_dates as 
     ( SELECT generate_series(current_date, current_date+interval '2 day', interval '.5 hours') run_dt)
select  run_dt
  from op_dates
     , date_period
 where 1=1
   and run_dt <@ tsrange(d1, d2, '[)');

将where子句中的AND谓词读作"run date is contained within the range d1 and d2, include d1 but exclude d2"。您想要的是将该谓词包含到您的 where 中,而不是 between 谓词。您可以将 tsrange 函数更改为 (d1, d2, '(]')。这将排除范围 (d1) 的开头但包括范围 (d2)

的结尾

为了进行比较,我将包含具有相同生成数据的 BETWEEN 查询;

with date_period as 
     ( select current_date + interval '9:30:00' d1
            , current_date + interval '1 day' + interval '9:30:00' d2
     ) 
   , op_dates as 
     ( SELECT generate_series(current_date, current_date+interval '2 day', interval '.5 hours') run_dt)
select run_dt
  from op_dates
     , date_period
 where 1=1
   and run_dt between d1 and d2;