ActiveRecord在日期字段上按年,日或月查找


70

我有一个具有日期属性的ActiveRecord模型。是否可以利用该date属性按年,日和月查找:

Model.find_by_year(2012)
Model.find_by_month(12)
Model.find_by_day(1)

还是有可能find_by_date(2012-12-1)。

我希望可以避免创建Year,Month和Day属性。


我认为沟通不畅。我正在尝试做的是能够通过使用完整日期,仅年份,仅月份和仅日期来查找记录/对象。第一种情况很简单,后三种(使用月,日和年)给我带来了困难。
Mutuelinvestor 2012年

Answers:


171

假设您的“日期属性”是日期(而不是完整的时间戳记),那么简单的方法where将为您提供“按日期查找”:

Model.where(:date_column => date)

您不想要,find_by_date_column因为这样最多只能给出一个结果。

对于要使用extractSQL函数的年,月和日查询:

Model.where('extract(year  from date_column) = ?', desired_year)
Model.where('extract(month from date_column) = ?', desired_month)
Model.where('extract(day   from date_column) = ?', desired_day_of_month)

但是,如果您使用的是SQLite,则必须弄混,strftime因为它不知道是什么extract

Model.where("cast(strftime('%Y', date_column) as int) = ?", desired_year)
Model.where("cast(strftime('%m', date_column) as int) = ?", desired_month)
Model.where("cast(strftime('%d', date_column) as int) = ?", desired_day_of_month)

%m%d格式说明会在某些情况下,添加前导零,并能迷惑平等的测试,因此cast(... as int)迫使格式化字符串的数字。

ActiveRecord不会保护您免受数据库之间的所有差异的影响,因此,一旦您做任何不重要的事情,您要么必须构建自己的可移植层(难看但有时是必要的),将自己绑定到有限的一组数据库中(现实除非您发布的内容必须在任何数据库上运行),或者在Ruby中执行所有逻辑(对于任何非平凡的数据量来说都是疯狂的)。

在大型表上,年,月和月中的查询将非常慢。有些数据库允许您在函数结果上添加索引,但是ActiveRecord太愚蠢而无法理解它们,因此,如果尝试使用它们,将会造成很大的麻烦。因此,如果您发现这些查询太慢,则必须添加要避免的三个额外的列。

如果您将大量使用这些查询,则可以为其添加作用域,但是建议使用参数添加作用域的方法只是添加一个类方法:

使用类方法是接受范围参数的首选方法。这些方法仍然可以在关联对象上访问...

因此,您将拥有如下所示的类方法:

def self.by_year(year)
    where('extract(year from date_column) = ?', year)
end
# etc.

3
感谢您的好回答。看来您必须依靠数据库特定功能来做到这一点,而该功能使activerecord的优势之一丧失了。也许这就是为什么您看到如此频繁地按月,日和年划分日期的原因。再次感谢。
Mutuelinvestor 2012年

1
答案的质量总是给您留下深刻的印象
暂停

1
这确实很有帮助,尽管对于SQLite,我必须CAST将日期数字设置为整数而不是加0(由于某种原因,加0会给我错误的数字)。然后,我修改过的过滤器是: Model.where("CAST(strftime('%Y', date_column) as INT) = ?", desired_year) Model.where("CAST(strftime('%m', date_column) as INT) = ?", desired_month) Model.where("CAST(strftime('%d', date_column) as INT) = ?", desired_day_of_month)
Jay El-Kaake 2016年

@ JayEl-Kaake:无论如何,我认为显式cast(... as int)总比骗术好,我不确定为什么我一开始就没有使用cast
mu太短了

无需在PG提取方法,你可以使用TO_CHARILIKE在只有一条线,我贴了完整的例子,在这个线程结束
heriberto佩雷斯

32

数据库独立版本...

def self.by_year(year)
  dt = DateTime.new(year)
  boy = dt.beginning_of_year
  eoy = dt.end_of_year
  where("published_at >= ? and published_at <= ?", boy, eoy)
end

4
其他实现还有beginning_of_day/end_of_daybeginning_of_month/ end_of_month
d_rail

这是一个更好的答案,因为它将使用索引(如果有)。穆氏将进行表格扫描。
Sixty4Bit

11

在您的模型中:

scope :by_year, lambda { |year| where('extract(year from created_at) = ?', year) }

在您的控制器中:

@courses = Course.by_year(params[:year])


5

我认为最简单的方法是在范围内:

scope :date, lambda {|date| where('date_field > ? AND date_field < ?', DateTime.parse(date).beginning_of_day, DateTime.parse(date).end_of_day}

像这样使用它:

Model.date('2012-12-1').all

这将返回所有模型,这些模型在发送的日期的开始和结束之间具有date_field。


一天不是开放时间间隔,而是半开放时间间隔。
亩太短

2
'd >= ? and d < ?', dt.beginning_of_day, dt.tomorrow.beginning_of_day。使用<>错过天之间的界限。
亩太短

1
我不太确定这是Mutuelinvestor要求的,因为它没有涵盖用户要求从4月开始的所有帖子的情况,而忽略了“年份”部分
ITmeze 2012年



1

我喜欢BSB的回答,但范围很广

scope :by_year, (lambda do |year| 
  dt = DateTime.new(year.to_i, 1, 1)
  boy = dt.beginning_of_year
  eoy = dt.end_of_year
  where("quoted_on >= ? and quoted_on <= ?", boy, eoy)
end)

1

这将是另一个:

def self.by_year(year)
  where("published_at >= ? and published_at <= ?", "#{year}-01-01", "#{year}-12-31")
end

在SQLite中对我来说效果很好。


1

另一个简短的范围如下:

  class Leave
    scope :by_year, -> (year) {
      where(date:
                Date.new(year).beginning_of_year..Date.new(year).end_of_year)
    }
  end

调用范围:

Leave.by_year(2020)

0

不需要extract方法,这在postgresql中就像一个魅力:

text_value = "1997" # or text_value = '1997-05-19' or '1997-05' or '05', etc
sql_string = "TO_CHAR(date_of_birth::timestamp, 'YYYY-MM-DD') ILIKE '%#{text_value.strip}%'"
YourModel.where(sql_string)

0

在Postgresql v 11+(我已经测试过)中:以下方式有效

Model.where('extract(year  from date_column::date) = ?', desired_year)
Model.where('extract(month from date_column::date) = ?', desired_month)
Model.where('extract(day   from date_column::date) = ?', desired_day_of_month)


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.