ActiveRecord Поиск по году, дню или месяцу в поле даты

У меня есть модель ActiveRecord с атрибутом даты. Можно ли использовать этот атрибут даты для поиска по году, дню и месяцу:

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

или просто можно найти_по_дате(2012-12-1).

Я надеялся, что смогу избежать создания атрибутов Год, Месяц и День.


person Mutuelinvestor    schedule 08.03.2012    source источник
comment
Я не думаю, что хорошо общался. Я пытаюсь найти записи/объекты, используя полную дату, только год, только месяц и только день. Первый случай прост, последние три (с использованием месяца, дня и года) вызывают у меня затруднения.   -  person Mutuelinvestor    schedule 09.03.2012


Ответы (14)


Предполагая, что ваш «атрибут даты» является датой (а не полной отметкой времени), тогда простой 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 слишком глуп, чтобы понять их, поэтому, если вы попытаетесь их использовать, возникнет большой беспорядок; поэтому, если вы обнаружите, что эти запросы слишком медленные, вам придется добавить три дополнительных столбца, которых вы пытаетесь избежать.

Если вы собираетесь часто использовать эти запросы, вы можете добавить для них области действия, но рекомендуемый способ добавить область действия с аргументом — просто добавить метод класса:

Использование метода класса является предпочтительным способом принятия аргументов для областей. Эти методы по-прежнему будут доступны для объектов ассоциации...

Таким образом, у вас будут методы класса, которые выглядят так:

def self.by_year(year)
    where('extract(year from date_column) = ?', year)
end
# etc.
person mu is too short    schedule 08.03.2012
comment
Спасибо за отличный ответ. Кажется, что для этого вам нужно полагаться на специфическую функцию базы данных, которая побеждает одно из преимуществ activerecord. Возможно, именно поэтому вы так часто видите даты с разбивкой по месяцам, дням и годам. Еще раз спасибо. - person Mutuelinvestor; 09.03.2012
comment
всегда впечатлен качеством ваших ответов - person apneadiving; 09.03.2012
comment
Это было действительно полезно, хотя для 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) - person Jay El-Kaake; 05.04.2016
comment
@JayEl-Kaake: Я думаю, что явное cast(... as int) в любом случае лучше, чем обман, я не уверен, почему я не использовал cast в первую очередь. - person mu is too short; 08.04.2016
comment
Нет необходимости в методе извлечения в pg, вы можете использовать TO_CHAR с ILIKE только в одной строке, я разместил полный пример в конце этой темы. - person heriberto perez; 07.06.2017
comment
но я использую sqlite3, как это сделать - person giri dharan; 20.12.2018
comment
@giridharan Есть Однако, если вы используете SQLite, вам придется возиться с strftime, поскольку он не знает, какой раздел extract уже существует. - person mu is too short; 20.12.2018

Независимая от базы данных версия...

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
person BSB    schedule 27.03.2013
comment
Также есть beginning_of_day/end_of_day и beginning_of_month/end_of_month для других реализаций. - person d_rail; 26.08.2013
comment
Это гораздо лучший ответ, поскольку он будет использовать индекс, если он доступен. Му просканирует стол. - person Sixty4Bit; 23.01.2015

В вашей модели:

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

В вашем контроллере:

@courses = Course.by_year(params[:year])
person Lane    schedule 30.03.2013

Для MySQL попробуйте это

Model.where("MONTH(date_column) = 12")
Model.where("YEAR(date_column) = 2012")
Model.where("DAY(date_column) = 24")
person Beena Shetty    schedule 26.10.2012

Я думаю, что самый простой способ сделать это - область видимости:

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 между началом и концом дня для даты, которую вы отправляете.

person Veraticus    schedule 08.03.2012
comment
День — это не открытый интервал, это полуоткрытый интервал. - person mu is too short; 09.03.2012
comment
'd >= ? and d < ?', dt.beginning_of_day, dt.tomorrow.beginning_of_day. Использование < и > пропускает границу между днями. - person mu is too short; 09.03.2012
comment
я не совсем уверен, что это то, о чем просил Mutuelinvestor, поскольку он не распространяется на случай, когда пользователь запрашивает все сообщения за апрель, игнорируя часть «год» - person ITmeze; 26.03.2012

Простая реализация всего за год

app/models/your_model.rb

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

Использование

Model.created_in( 1984 )
person Joshua Pinter    schedule 12.12.2014

Попробуй это:

Model.where("MONTH(date_column) = ? and DAY(date_column) = ?", DateTime.now.month, DateTime.now.day)
person monteirobrena    schedule 02.05.2014
comment
@NarenP Я так не думаю. В PostgreSQL нет функции MONTH. Попробуйте извлечь или date_part. wiki.postgresql.org/wiki/МЕСЯЦ()_эквивалент postgresqltutorial.com/postgresql-date_part - person monteirobrena; 01.10.2018

Мне нравится ответ 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)
person idrinkpabst    schedule 11.05.2013

Это будет другой:

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

У меня очень хорошо работает в SQLite.

person Tintin81    schedule 20.09.2013

Еще один короткий объем, как показано ниже:

  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)
person Shiko    schedule 13.11.2018

Я обычно использую:

Model.where('extract(month from birthday_column) = ? and extract(day from birthday_column) = ?', Time.now.month, Time.now.day)
person Leandro Castro    schedule 16.06.2020

Нет необходимости в методе 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)
person heriberto perez    schedule 07.06.2017

В 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)
person Ravistm    schedule 16.01.2020

Если вы ищете только дату, просто выполните:

Model.where(date: Date.today.beginning_of_day..Date.today.end_of_day)
person Angelo Richardson    schedule 12.11.2020