web-dev-qa-db-fra.com

ActiveRecord Rechercher par année, jour ou mois dans un champ Date

J'ai un modèle ActiveRecord qui a un attribut de date. Est-il possible d'utiliser cet attribut de date pour rechercher par année, jour et mois:

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

ou est-il simplement possible de trouver_par_date (2012-12-1).

J'espérais pouvoir éviter de créer des attributs Année, Mois et Jour.

64
Mutuelinvestor

En supposant que votre "attribut de date" est une date (plutôt qu'un horodatage complet), un simple where vous donnera votre "recherche par date":

Model.where(:date_column => date)

Vous ne voulez pas find_by_date_column Car cela donnera au plus un résultat.

Pour les requêtes sur l'année, le mois et le jour, vous souhaitez utiliser la fonction SQL extract:

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)

Cependant, si vous utilisez SQLite, vous devrez jouer avec strftime car il ne sait pas ce qu'est 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)

Les spécificateurs de format %m Et %d Ajouteront des zéros non significatifs dans certains cas et cela peut confondre les tests d'égalité, d'où la fonction cast(... as int) pour forcer les chaînes formatées en nombres.

ActiveRecord ne vous protégera pas de toutes les différences entre les bases de données, donc dès que vous faites quelque chose de non trivial, vous devez soit créer votre propre couche de portabilité (laide mais parfois nécessaire), vous lier à un ensemble limité de bases de données (réaliste sauf si vous publiez quelque chose qui doit s'exécuter sur n'importe quelle base de données), ou faites toute votre logique dans Ruby (fou pour toute quantité de données non triviale).

Les requêtes sur l'année, le mois et le jour du mois seront assez lentes sur les grandes tables. Certaines bases de données vous permettent d'ajouter des index sur les résultats des fonctions, mais ActiveRecord est trop stupide pour les comprendre, donc cela fera un gros gâchis si vous essayez de les utiliser; donc, si vous trouvez que ces requêtes sont trop lentes, vous devrez ajouter les trois colonnes supplémentaires que vous essayez d'éviter.

Si vous utilisez beaucoup ces requêtes, vous pouvez leur ajouter des étendues, mais la méthode recommandée pour ajouter une étendue avec un argument consiste simplement à ajouter une méthode de classe:

L'utilisation d'une méthode de classe est le moyen préféré d'accepter des arguments pour les étendues. Ces méthodes seront toujours accessibles sur les objets d'association ...

Vous auriez donc des méthodes de classe qui ressemblent à ceci:

def self.by_year(year)
    where('extract(year from date_column) = ?', year)
end
# etc.
155
mu is too short

Version indépendante de la base de données ...

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
29
BSB

Pour MySQL, essayez ceci

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

Dans votre modèle:

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

Dans votre contrôleur:

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

Je pense que la façon la plus simple de le faire est une portée:

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

Utilisez-le comme ceci:

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

Cela renverra tous les modèles avec un champ date_ entre le début et la fin de la journée pour la date que vous envoyez.

5
Veraticus

Essaye ça:

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

Mise en œuvre simple pour juste l'année

app/models/your_model.rb

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

tilisation

Model.created_in(1984)
2
Joshua Pinter

Ce serait un autre:

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

Fonctionne assez bien pour moi dans SQLite.

1
Tintin81

J'aime la réponse de BSB mais comme une portée

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
idrinkpabst

Une autre portée courte comme ci-dessous:

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

Appelez la portée:

Leave.by_year(2020)
0
Shiko

Pas besoin de la méthode extract, cela fonctionne comme un charme dans 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
heriberto perez