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

时间:2019-07-24 08:13:15

标签: ruby-on-rails postgresql between

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

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包含范围边界(docs):

  

BETWEEN谓词简化了范围测试:

a BETWEEN x AND y
     

等同于

a >= x AND a <= y
     

请注意,BETWEEN将端点值视为包含在范围内。

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

  • 我应该将this_execution_time中的时间更改为9:29:59吗?
  • 还是需要考虑更细微的事情(例如毫秒)?

[编辑] 我使用rails 5.2.3pg 1.1.4

3 个答案:

答案 0 :(得分:0)

您可以查询类似:

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)

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

答案 1 :(得分:0)

是的,如果您将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)

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

答案 2 :(得分:0)

我对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谓词,因为“运行日期包含在d1和d2范围内,包括d1但不包括d2”。您想要的是将该谓词包含在您的位置中,以代替介于谓词之间。您可以将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;