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

时间:2012-03-08 20:40:30

标签: ruby-on-rails ruby-on-rails-3 activerecord rails-activerecord

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

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

或者只是可以find_by_date(2012-12-1)。

我希望我可以避免创建年,月和日属性。

14 个答案:

答案 0 :(得分:155)

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

Model.where(:date_column => date)

您不希望find_by_date_column,因为它最多会产生一个结果。

对于年,月和日查询,您需要使用extract SQL函数:

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太愚蠢而无法理解它们,所以如果你试图使用它们会很麻烦;所以,如果你发现这些查询太慢,那么你必须添加你想要避免的三个额外列。

如果你要经常使用这些查询,那么可以为它们添加范围,但recommended way to add a scope with an argument只是添加一个类方法:

  

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

所以你的类方法看起来像这样:

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

答案 1 :(得分:28)

数据库独立版......

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

答案 2 :(得分:10)

对于MySQL试试这个

Model.where("MONTH(date_column) = 12")
Model.where("YEAR(date_column) = 2012")
Model.where("DAY(date_column) = 24")

答案 3 :(得分:10)

在你的模特中:

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

在您的控制器中:

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

答案 4 :(得分: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。

答案 5 :(得分:3)

试试这个:

Model.where("MONTH(date_column) = ? and DAY(date_column) = ?", DateTime.now.month, DateTime.now.day)

答案 6 :(得分:2)

年度简单实施

应用/模型/ your_model.rb

scope :created_in, ->(year) { where( "YEAR( created_at ) = ?", year ) }

<强>用法

Model.created_in(1984)

答案 7 :(得分: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)

答案 8 :(得分:1)

这将是另一个:

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

在SQLite中对我很有用。

答案 9 :(得分: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)

答案 10 :(得分:0)

另一个简短的范围如下:

for

调用范围:

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

答案 11 :(得分: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)

答案 12 :(得分:0)

我通常使用:

Model.where('extract(month from birthday_column) = ? and extract(day from birthday_column) = ?', Time.now.month, Time.now.day)

答案 13 :(得分:0)

如果您只是在寻找日期,只需执行以下操作:

Model.where(date: Date.today.beginning_of_day..Date.today.end_of_day)