215

I'm trying to find all Users with an id greater than 200, but I'm having some trouble with the specific syntax.

User.where(:id > 200) 

and

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

have both failed.

Any suggestions?

BinaryButterfly
  • 18,137
  • 13
  • 50
  • 91
Adam Templeton
  • 4,467
  • 7
  • 27
  • 39

10 Answers10

340

Try this

User.where("id > ?", 200) 
RadBrad
  • 7,234
  • 2
  • 24
  • 17
245

State of the Art

Ruby 2.7 introduced beginless ranges which makes specifying >, < and their inclusive cousins (>= and <=) even easier.

User.where(id: 200..).to_sql
  => "SELECT \"users\".* FROM \"users\" WHERE \"users\".\"id\" >= 200"
# There is no difference w/ a non-inclusive endless range (e.g. `200...`)

User.where(id: ..200).to_sql
  => "SELECT \"users\".* FROM \"users\" WHERE \"users\".\"id\" <= 200"
User.where(id: ...200).to_sql
  => "SELECT \"users\".* FROM \"users\" WHERE \"users\".\"id\" < 200"

This also works perfectly with timestamps!

User.where(created_at: 1.day.ago..).to_sql
  => "SELECT \"users\".* FROM \"users\" WHERE \"users\".\"created_at\" >= '2021-09-12 15:38:32.665061'"
User.where(created_at: ..1.day.ago).to_sql
  => "SELECT \"users\".* FROM \"users\" WHERE \"users\".\"created_at\" <= '2021-09-12 15:38:37.756870'"

Original Answer & Updates

I've only tested this in Rails 4 but there's an interesting way to use a range with a where hash to get this behavior.

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

will generate the SQL

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

The same can be done for less than with -Float::INFINITY.

I just posted a similar question asking about doing this with dates here on SO.

>= vs >

To avoid people having to dig through and follow the comments conversation here are the highlights.

The method above only generates a >= query and not a >. There are many ways to handle this alternative.

For discrete numbers

You can use a number_you_want + 1 strategy like above where I'm interested in Users with id > 200 but actually look for id >= 201. This is fine for integers and numbers where you can increment by a single unit of interest.

If you have the number extracted into a well named constant this may be the easiest to read and understand at a glance.

Inverted logic

We can use the fact that x > y == !(x <= y) and use the where not chain.

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

which generates the SQL

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

This takes an extra second to read and reason about but will work for non discrete values or columns where you can't use the + 1 strategy.

Arel table

If you want to get fancy you can make use of the Arel::Table.

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

will generate the SQL

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

The specifics are as follows:

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`

This approach will get you the exact SQL you're interested in however not many people use the Arel table directly and can find it messy and/or confusing. You and your team will know what's best for you.

Bonus

Starting in Rails 5 you can also do this with dates!

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

will generate the SQL

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

Double Bonus

Once Ruby 2.6 is released (December 25, 2018) you'll be able to use the new infinite range syntax! Instead of 201..Float::INFINITY you'll be able to just write 201... More info in this blog post.

Aaron
  • 13,349
  • 11
  • 66
  • 105
  • 2
    This answer is the best for Rails 4 IMO. Been using it for quite a while and works perfectly. – Nacho L. Sep 02 '14 at 08:06
  • 4
    Why is this superior to the accepted answer, out of curiosity? – mecampbellsoup Jan 27 '15 at 19:18
  • 6
    Superior is misleading. In general you achieve more flexibility with your ARel queries if you're able to use the hash syntax over strings which is why many would prefer this solution. Depending on your project/team/organization you may want something that is easier for someone glancing at the code to figure out, which the accepted answer is. – Aaron Jan 28 '15 at 17:48
  • Strings also eat memory vs immutable literals (symbols, fixnums, etc.). Of course it takes a really big project to really make a difference – Dr.Strangelove May 13 '15 at 20:27
  • This only generates `>=` query. I even tests `201...Float::INFINITY` but it also generate `>=` query. Is there any way to do this and get a `>` query? – ArashM Jun 06 '15 at 08:31
  • 2
    I don't believe you can do that using the basic `where` matchers. For `>` I suggest using a `>= (number_you_want + 1)` for simplicity. If you really want to ensure it is just a `>` query you can access the ARel table. Every class that inherits from `ActiveRecord` has an `arel_table` getter method which returns the `Arel::Table` for that class. Columns on the table are accessed with the `[]` method like `User.arel_table[:id]`. This returns an `Arel::Attributes::Attribute` you can call `gt` on and pass in `200`. This can be then passed to `where`. eg `User.where(User.arel_table[:id].gt(200))`. – Aaron Jun 08 '15 at 17:43
  • This solution may work, but it makes for ugly reading. Other developers may be confused by this. IMO, the use of strings in where conditions is something the Rails project could improve upon. – Winston Kotzan Oct 09 '15 at 22:24
  • I know that Rails 5 is getting an `or` method for simplifying conditionals which is awesome. While going to ARel is always an option for comparisons my previous comment explains why some would prefer a somewhat uglier syntax for chaining flexibility. Although most projects may never get complex enough where either would matter so it's all about what works best for you, your project, and team. – Aaron Oct 11 '15 at 18:57
  • If you want to do this with times: Time.current...DateTime::Infinity.new – bluehallu Apr 04 '16 at 17:12
  • 2
    @bluehallu can you provide an example? The following is not working for me `User.where(created_at: 3.days.ago..DateTime::Infinity.new)`. – Aaron Apr 04 '16 at 17:52
  • Shame I can't edit the comment anymore. I missed parenthesis around the times between the ranges, correct way would be: where(time: (Date.today.beginning_of_day)...(Date.today.end_of_day)) – bluehallu Apr 04 '16 at 21:25
  • @bluehallu the parens don't matter in your updated statement as the end of the range `Date.today.end_of_day` is treated differently than `DateTime::Infinity.new`. `Date.today.end_of_day` will generate a range for the timestamp to be between as opposed to a `>=` condition as intended. I can also confirm that adding parens around the original suggestion as such `User.where(created_at: (3.days.ago)...(DateTime::Infinity.new))` also does not work. – Aaron Apr 05 '16 at 20:44
  • I'm running the code I quoted above in rails 5 ruby 2.2.4 without problems. What error are you getting? – bluehallu Apr 11 '16 at 10:09
  • 1
    Ah! It's likely a new change in Rails 5 that's giving you the beneficial behavior. In Rails 4.2.5 (Ruby 2.2.2) you'll get a query with `WHERE (users.created_at >= '2016-04-09 14:31:15' AND users.created_at < #)` (back ticks around table and column names omitted for SO comment formatting). – Aaron Apr 12 '16 at 14:33
  • WRT to `>` specifically this can be accomplished in what I feel is less readable but still expressible via hash. `where.not(id: -Float::INFINITY..200)` generates `WHERE (NOT (\`users\`.\`id\` <= 200))` and `x > 200 == !(x <= 200)`. This would be necessary if had to use the hash syntax for a field that wasn't an integer. – Aaron Dec 21 '17 at 17:59
  • why `...200` returning `<` meanwhile `200...` returning `>=` any reasons or explanation for that? – buncis Oct 14 '22 at 15:40
  • Ruby's [range literals](https://ruby-doc.org/core-3.1.2/doc/syntax/literals_rdoc.html#label-Range+Literals) can be expressed with double or triple dots to indicate if the ending value is included or not. `...200` does not include the end value and therefore would be expressed as `< 200` in SQL whereas an endless range doesn't have an end value to include or exclude. – Aaron Oct 15 '22 at 18:56
26

A better usage is to create a scope in the user model where(arel_table[:id].gt(id))

Mihai
  • 1,254
  • 2
  • 15
  • 27
23

Update

Rails core team decided to revert this change for a while, in order to discuss it in more detail. See this comment and this PR for more info.

I am leaving my answer only for educational purposes.


new 'syntax' for comparison in Rails 6.1 (Reverted)

Rails 6.1 added a new 'syntax' for comparison operators in where conditions, for example:

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

So your query can be rewritten as follows:

User.where('id >': 200) 

Here is a link to PR where you can find more examples.

Marian13
  • 7,740
  • 2
  • 47
  • 51
  • It's not quite clear whether this pull request will be accepted. According to (https://github.com/rails/rails/pull/39613#issuecomment-659553274)[this comment] the core team needs to discuss the issue first. – Fabian Winkler Oct 13 '20 at 15:41
  • @Fabian Winkler, thanks for your observation. I am monitoring this pull request. If something will change, I'll immediately update this answer. – Marian13 Oct 17 '20 at 00:02
  • Rails 7.03 and this doesnt seem to work. - `where(end_at: ...Date.today).to_sql` - will have to do – Brent Greeff Aug 02 '22 at 19:49
11

Arel is your friend:

User.where(User.arel_table[:id].gt(200))
Viktor
  • 2,623
  • 3
  • 19
  • 28
joegiralt
  • 310
  • 3
  • 5
7

Another fancy possibility is...

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

This feature allows you to create more comprehensible queries if you want to replace in multiple places, for example...

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

This has more meaning than having a lot of ? on the query...

User.where("id > ? OR number > ? AND employee_id = ?", 100, 102, 1205)
Victor
  • 1,904
  • 18
  • 18
5

If you want a more intuitive writing, it exist a gem called squeel that will let you write your instruction like this:

User.where{id > 200}

Notice the 'brace' characters { } and id being just a text.

All you have to do is to add squeel to your Gemfile:

gem "squeel"

This might ease your life a lot when writing complex SQL statement in Ruby.

chamini2
  • 2,820
  • 2
  • 24
  • 37
Douglas
  • 5,229
  • 3
  • 43
  • 54
  • 12
    I recommend avoid using squeel. Long term is difficult to maintain and sometimes has odd behavior. Also it is buggy with certain Active Record versions – John Owen Chile Jul 26 '16 at 16:05
  • I've been using squeel for some years and still happy with it. But Maybe it worth to try another ORM, like sequel (<> squeel) for instance, which seems promising nice features to replace ActiveRecord. – Douglas Jul 29 '16 at 07:19
4

I often have this problem with date fields (where comparison operators are very common).

To elaborate further on Mihai's answer, which I believe is a solid approach.

To the models you can add scopes like this:

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

... and then in your controller, or wherever you are using your model:

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

... a more complex example with joins looks like this:

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

A huge advantage of this approach is (a) it lets you compose your queries from different scopes and (b) avoids alias collisions when you join to the same table twice since arel_table will handle that part of the query generation.

Brett Green
  • 3,535
  • 1
  • 22
  • 29
3

For Ruby 2.6 can accept ranges like:

# => 2.6
User.where(id: 201..)
# < 2.6
User.where(id: 201..Float::INFINITY)
Mosaaleb
  • 1,028
  • 1
  • 9
  • 23
-3

Shorter:

User.where("id > 200")
Miguel Peniche
  • 981
  • 10
  • 20
  • 8
    I assume this has to be dynamic, and that the poster wants to avoid SQL injection by using parameterized queries (`where("id > ?", 200)` syntax). This doesn't achieve that. – Matthew Hinea Sep 05 '17 at 17:25