Rails: utilisation supérieure / inférieure à avec une instruction where

142

J'essaie de trouver tous les utilisateurs avec un identifiant supérieur à 200, mais j'ai des problèmes avec la syntaxe spécifique.

User.where(:id > 200) 

et

User.where("? > 200", :id) 

ont tous deux échoué.

Aucune suggestion?

Adam Templeton
la source

Réponses:

276

Essaye ça

User.where("id > ?", 200) 
RadBrad
la source
1
Découvrez également Squeel gem d'Ernie Miller
cpuguy83
7
Y a-t-il une raison de préférer utiliser ?, plutôt que d'intégrer le 200?
davetapley
20
il échappe automatiquement au 200 (s'il était possible pour l'utilisateur de saisir la valeur, cela évite la possibilité d'attaques par injection SQL)
user1051849
124

Je n'ai testé cela que dans Rails 4 mais il existe une manière intéressante d'utiliser une plage avec un wherehachage pour obtenir ce comportement.

User.where(id: 201..Float::INFINITY)

générera le SQL

SELECT `users`.* FROM `users`  WHERE (`users`.`id` >= 201)

La même chose peut être faite pour moins qu'avec -Float::INFINITY.

Je viens de publier une question similaire demandant de faire cela avec des dates ici sur SO .

>= contre >

Pour éviter que les gens n'aient à fouiller et à suivre la conversation des commentaires, voici les faits saillants.

La méthode ci-dessus génère uniquement une >=requête et non un fichier >. Il existe de nombreuses façons de gérer cette alternative.

Pour les nombres discrets

Vous pouvez utiliser une number_you_want + 1stratégie comme ci-dessus où je suis intéressé par les utilisateurs avec id > 200mais que vous recherchez en faitid >= 201 . C'est parfait pour les nombres entiers et les nombres où vous pouvez incrémenter d'une seule unité d'intérêt.

Si vous avez extrait le nombre dans une constante bien nommée, cela peut être le plus facile à lire et à comprendre en un coup d'œil.

Logique inversée

Nous pouvons utiliser le fait que x > y == !(x <= y)et utiliser la chaîne where not.

User.where.not(id: -Float::INFINITY..200)

qui génère le SQL

SELECT `users`.* FROM `users` WHERE (NOT (`users`.`id` <= 200))

Cela prend une seconde de plus à lire et à raisonner, mais fonctionnera pour les valeurs non discrètes ou les colonnes où vous ne pouvez pas utiliser le + 1 stratégie.

Table d'Arel

Si vous voulez avoir de la fantaisie, vous pouvez utiliser le Arel::Table.

User.where(User.arel_table[:id].gt(200))

générera le SQL

"SELECT `users`.* FROM `users` WHERE (`users`.`id` > 200)"

Les spécificités sont les suivantes:

User.arel_table              #=> an Arel::Table instance for the User model / users table
User.arel_table[:id]         #=> an Arel::Attributes::Attribute for the id column
User.arel_table[:id].gt(200) #=> an Arel::Nodes::GreaterThan which can be passed to `where`

Cette approche vous donnera le SQL exact qui vous intéresse, mais peu de gens utilisent directement la table Arel et peuvent la trouver désordonnée et / ou déroutante. Vous et votre équipe saurez ce qui est le mieux pour vous.

Prime

À partir de Rails 5, vous pouvez également le faire avec des dates!

User.where(created_at: 3.days.ago..DateTime::Infinity.new)

générera le SQL

SELECT `users`.* FROM `users` WHERE (`users`.`created_at` >= '2018-07-07 17:00:51')

Bonus double

Une fois Ruby 2.6 sorti (25 décembre 2018), vous pourrez utiliser la nouvelle syntaxe de plage infinie! Au lieu de cela, 201..Float::INFINITYvous pourrez simplement écrire 201... Plus d'informations dans ce billet de blog .

Aaron
la source
3
Pourquoi est-ce supérieur à la réponse acceptée, par curiosité?
mecampbellsoup
5
Superior est trompeur. En général, vous obtenez plus de flexibilité avec vos requêtes ARel si vous êtes capable d'utiliser la syntaxe de hachage sur des chaînes, c'est pourquoi beaucoup préféreraient cette solution. En fonction de votre projet / équipe / organisation, vous voudrez peut-être quelque chose de plus facile pour quelqu'un qui regarde le code pour déterminer quelle est la réponse acceptée.
Aaron
2
Je ne pense pas que vous puissiez faire cela en utilisant les wherematchers de base . Car >je suggère d'utiliser un >= (number_you_want + 1)pour plus de simplicité. Si vous voulez vraiment vous assurer qu'il ne s'agit que d'une >requête, vous pouvez accéder à la table ARel. Chaque classe qui hérite de ActiveRecorda une arel_tableméthode getter qui renvoie le Arel::Tablepour cette classe. Les colonnes de la table sont accessibles avec la []méthode comme User.arel_table[:id]. Cela renvoie un que Arel::Attributes::Attributevous pouvez appeler gtet transmettre 200. Cela peut ensuite être transmis à where. par exemple User.where(User.arel_table[:id].gt(200)).
Aaron
2
@bluehallu pouvez-vous donner un exemple? Ce qui suit ne fonctionne pas pour moi User.where(created_at: 3.days.ago..DateTime::Infinity.new).
Aaron le
1
Ah! C'est probablement un nouveau changement dans Rails 5 qui vous donne le comportement bénéfique. Dans Rails 4.2.5 (Ruby 2.2.2), vous obtiendrez une requête avec WHERE (users.created_at >= '2016-04-09 14:31:15' AND users.created_at < #<Date::Infinity:0x00>)(les graduations arrière autour des noms de table et de colonne omises pour le formatage des commentaires SO).
Aaron
22

Une meilleure utilisation consiste à créer une étendue dans le modèle utilisateur where(arel_table[:id].gt(id))

Mihai
la source
6

Si vous voulez une écriture plus intuitive, il existe un joyau appelé squeel qui vous permettra d'écrire votre instruction comme ceci:

User.where{id > 200}

Notez les caractères "accolades" {} et id sont qu'un texte.

Tout ce que vous avez à faire est d'ajouter squeel à votre Gemfile:

gem "squeel"

Cela peut vous faciliter la vie lors de l'écriture d'instructions SQL complexes dans Ruby.

Douglas
la source
11
Je recommande d'éviter d'utiliser squeel. Le long terme est difficile à maintenir et a parfois un comportement étrange. Il est également bogué avec certaines versions Active Record
John Owen Chili
J'utilise squeel depuis quelques années et j'en suis toujours satisfait. Mais peut-être vaut-il la peine d'essayer un autre ORM, comme la suite (<> squeel) par exemple, qui semble prometteuse de belles fonctionnalités pour remplacer ActiveRecord.
Douglas
5

Arel est votre ami.

User.where (User.arel_table [: id] .gt (200))

joegiralt
la source
4

Une autre possibilité sophistiquée est ...

User.where("id > :id", id: 100)

Cette fonctionnalité vous permet de créer des requêtes plus compréhensibles si vous souhaitez remplacer à plusieurs endroits, par exemple ...

User.where("id > :id OR number > :number AND employee_id = :employee", id: 100, number: 102, employee: 1205)

Cela a plus de sens que d'avoir beaucoup ?sur la requête ...

User.where("id > ? OR number > ? AND employee_id = ?", 100, 102, 1205)
Victor
la source
3

J'ai souvent ce problème avec les champs de date (où les opérateurs de comparaison sont très courants).

Pour approfondir la réponse de Mihai, qui, à mon avis, est une approche solide.

Aux modèles, vous pouvez ajouter des portées comme ceci:

scope :updated_at_less_than, -> (date_param) { 
  where(arel_table[:updated_at].lt(date_param)) }

... puis dans votre manette, ou partout où vous utilisez votre modèle:

result = MyModel.updated_at_less_than('01/01/2017')

... un exemple plus complexe avec des jointures ressemble à ceci:

result = MyParentModel.joins(:my_model).
  merge(MyModel.updated_at_less_than('01/01/2017'))

Un énorme avantage de cette approche est (a) qu'elle vous permet de composer vos requêtes à partir de différentes étendues et (b) évite les collisions d'alias lorsque vous rejoignez deux fois la même table car arel_table gérera cette partie de la génération de requête.

Brett Green
la source
2

Rails 6.1+

Rails 6.1 a ajouté une nouvelle `` syntaxe '' pour les opérateurs de comparaison dans les whereconditions, par exemple:

Post.where('id >': 9)
Post.where('id >=': 9)
Post.where('id <': 3)
Post.where('id <=': 3)

Ainsi, votre requête peut être réécrite comme suit:

User.where('id >': 200) 

Voici un lien vers PR où vous pouvez trouver plus d'exemples.

Marian13
la source
-3

Plus court:

User.where("id > 200")
Bengala
la source
8
Je suppose que cela doit être dynamique et que l'affiche veut éviter l'injection SQL en utilisant des requêtes paramétrées ( where("id > ?", 200)syntaxe). Cela n'atteint pas cela.
Matthew Hinea