Rails where condition using NOT NIL
Asked Answered
C

5

399

Using the rails 3 style how would I write the opposite of:

Foo.includes(:bar).where(:bars=>{:id=>nil})

I want to find where id is NOT nil. I tried:

Foo.includes(:bar).where(:bars=>{:id=>!nil}).to_sql

But that returns:

=> "SELECT     \"foos\".* FROM       \"foos\"  WHERE  (\"bars\".\"id\" = 1)"

That's definitely not what I need, and almost seems like a bug in ARel.

Chavarria answered 23/11, 2010 at 2:56 Comment(1)
!nil evaluates to true in Ruby, and ARel translates true to 1 in a SQL query. So the generated query is in fact what you asked for - this was not an ARel bug.Vladamir
P
567

Rails 4+

ActiveRecord 4.0 and above adds where.not so you can do this:

Foo.includes(:bar).where.not('bars.id' => nil)
Foo.includes(:bar).where.not(bars: { id: nil })

When working with scopes between tables, I prefer to leverage merge so that I can use existing scopes more easily.

Foo.includes(:bar).merge(Bar.where.not(id: nil))

Also, since includes does not always choose a join strategy, you should use references here as well, otherwise you may end up with invalid SQL.

Foo.includes(:bar)
   .references(:bar)
   .merge(Bar.where.not(id: nil))

Rails 3

The canonical way to do this with Rails 3:

Foo.includes(:bar).where("bars.id IS NOT NULL")
Planking answered 23/11, 2010 at 3:51 Comment(11)
Last one here isn't working for me, do we need an extra gem or plugin for this? I get: rails undefined method 'not_eq' for :confirmed_at:Symbol..Merozoite
@Tim Yes, the MetaWhere gem I linked above.Planking
@oreoshake MetaWhere/Squeel are well worth having, this is just a tiny facet. But of course a general case is good to know.Planking
Hi Adam/everyone, do you need both squeel and metawhere to do that nice "ruby only" stuff or is one of them enough? What's the difference, really?Classless
And another thing: I have not managed to do this with sqlite3. sqlite3 wants to see field_name != 'NULL'.Classless
@Classless MetaWhere was created for Rails 3.0, Squeel replaced it for Rails >= 3.1Planking
I personally try to avoid EVER having raw SQL in my code, so I use Squeel and also the new Arel syntax as explained by Ryan Bigg belowAsch
@jaydel the problem with Squeel is that it needs constant attention to deal with breaking changes, and it doesn't always have an owner. I'm transitioning more toward lightweight Arel helpers and query objects instead.Planking
@AdamLassek You can really say "it doesn't always have an owner" to most gems in the ruby world. I think that most of us decide to either trust a gem (to a degree) or accept the risk that you might have to fork and fix things you need.Asch
@AdamLassek thank you Adam. What if there are multiple where conditions? would appending the rest of them .where( other conditions here) not hamper performance?Worldshaking
@BKSpurgeon Chaining where conditions is simply building an AST, it doesn't hit the database until you hit a terminal method like each or to_a. Building the query isn't a performance concern; what you're requesting from the database is.Planking
S
255

It's not a bug in ARel, it's a bug in your logic.

What you want here is:

Foo.includes(:bar).where(Bar.arel_table[:id].not_eq(nil))
Statutory answered 23/11, 2010 at 3:53 Comment(7)
I'm curious what the logic is then for turning !nil into '1'Chavarria
At a guess, !nil returns true, which is a boolean. :id => true gets you id = 1 in SQLese.Bourke
This is a good way to avoid writing raw sql fragments. The syntax isn't as concise as Squeel though.Ethmoid
I have not managed to do this with sqlite3. sqlite3 wants to see field_name != 'NULL'.Classless
@Bourke Unless you're using postgres, in which case you get id = 't' :)Isreal
The problem with this is that it != null always tests true in SQL even if it IS null. Yes, the rails folks have worked around this but using the not_eq syntax just encourages confusion when you eventually do need to do the conversion to SQL.Alternative
Worth noting that this works in Rails 3 (unlike several answers).Expressway
P
48

Not sure of this is helpful but this what worked for me in Rails 4

Foo.where.not(bar: nil)
Proulx answered 11/11, 2016 at 12:3 Comment(1)
This is the best answer here. -- 2017 robots.thoughtbot.com/activerecords-wherenotSchlesien
S
38

For Rails4:

So, what you're wanting is an inner join, so you really should just use the joins predicate:

  Foo.joins(:bar)

  Select * from Foo Inner Join Bars ...

But, for the record, if you want a "NOT NULL" condition simply use the not predicate:

Foo.includes(:bar).where.not(bars: {id: nil})

Select * from Foo Left Outer Join Bars on .. WHERE bars.id IS NOT NULL

Note that this syntax reports a deprecation (it talks about a string SQL snippet, but I guess the hash condition is changed to string in the parser?), so be sure to add the references to the end:

Foo.includes(:bar).where.not(bars: {id: nil}).references(:bar)

DEPRECATION WARNING: It looks like you are eager loading table(s) (one of: ....) that are referenced in a string SQL snippet. For example:

Post.includes(:comments).where("comments.title = 'foo'")

Currently, Active Record recognizes the table in the string, and knows to JOIN the comments table to the query, rather than loading comments in a separate query. However, doing this without writing a full-blown SQL parser is inherently flawed. Since we don't want to write an SQL parser, we are removing this functionality. From now on, you must explicitly tell Active Record when you are referencing a table from a string:

Post.includes(:comments).where("comments.title = 'foo'").references(:comments)
Shiftless answered 6/10, 2013 at 18:36 Comment(1)
The references call helped me!Enfield
M
23

With Rails 4 it's easy:

 Foo.includes(:bar).where.not(bars: {id: nil})

See also: http://guides.rubyonrails.org/active_record_querying.html#not-conditions

Meet answered 8/8, 2014 at 6:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.