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.
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")
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))
Not sure of this is helpful but this what worked for me in Rails 4
Foo.where.not(bar: nil)
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)
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
Related
I am in a situation to filter the records based on some conditions(conditions are in the form of scopes). in user model
scope :has_email, -> { where('email IS NOT NULL') }
scope :email_contains, (email) -> { where("email ILIKE :email'", email: email)}
If I want both conditions to be combined with 'AND' operator, We can do something like,
User.has_email.email_contains
The query generated would be
SELECT "user".* FROM "user" WHERE (email ILIKE '%gmail.com%') AND (email IS NOT NULL)
How can I proceed if I need scopes to be combined with OR operators? I found that rails 5 added support to or method(https://blog.bigbinary.com/2016/05/30/rails-5-adds-or-support-in-active-record.html), But this won't work if we use includes or joins
Eg: User.has_email.or(User.some_scope).or(User.joins(:event).temp)
How do I join scopes with OR?
The bit you are missing is that a join is forcing the association to exist. To prevent that, you use left_joins:
User.left_joins(:event).where(event: {foo: bar})
Still it won't solve the issue because the .or method will work (by documentation) only on structurally equivalent relations.
You can actually overcome it by going one step lower, to Arel:
rels = [User.foo, User.bar(baz), User.joins(:event).temp]
cond = rels.map { |rel| rel.where_values.reduce(&:and) }.reduce(&:or)
User.left_joins(:event).where(cond)
The where_values property is an array of Arel::Nodes::Node instances, all of which are normally and-ed to get your query. You have to and them by hand, and then or the results.
If something does not work as expected, check the output of cond.to_sql in case you have missed something.
Can someone briefly explain to me the difference in use between the methods uniq and distinct?
I've seen both used in similar context, but the difference isnt quite clear to me.
Rails queries acts like arrays, thus .uniq produces the same result as .distinct, but
.distinct is sql query method
.uniq is array method
Note: In Rails 5+ Relation#uniq is deprecated and recommended to use Relation#distinct instead.
See http://edgeguides.rubyonrails.org/5_0_release_notes.html#active-record-deprecations
Hint:
Using .includes before calling .uniq/.distinct can slow or speed up your app, because
uniq won't spawn additional sql query
distinct will do
But both results will be the same
Example:
users = User.includes(:posts)
puts users
# First sql query for includes
users.uniq
# No sql query! (here you speed up you app)
users.distinct
# Second distinct sql query! (here you slow down your app)
This can be useful to make performant application
Hint:
Same works for
.size vs .count;
present? vs .exists?
map vs pluck
Rails 5.1 has removed the uniq method from Activerecord Relation and added distinct method...
If you use uniq with query it will just convert the Activerecord Relaction to Array class...
You can not have Query chain if you added uniq there....(i.e you can not do User.active.uniq.subscribed it will throw error undefined method subscribed for Array )
If your DB is large and you want to fetch only required distinct entries its good to use distinct method with Activerecord Relation query...
From the documentation:
uniq(value = true)
Alias for ActiveRecord::QueryMethods#distinct
Its not exactly answer your question, but what I know is:
If we consider ActiveRecord context then uniq is just an alias for distinct. And both work as removing duplicates on query result set(which you can say up to one level).
And at array context uniq is so powerful that it removes duplicates even if the elements are nested. for example
arr = [["first"], ["second"], ["first"]]
and if we do
arr.uniq
answer will be : [["first"], ["second"]]
So even if elements are blocks it will go in deep and removes duplicates.
Hope it helps you in some ways.
Ransack allows me to build conditions with an attribute, a predicate and a value. I haven't been able to find any documentation on how to compare one attribute to another however. For instance, how could I create a condition for:
WHERE column_a < column_b
I've been using Ransack for quite a while, but I don't see any possibility to do what you are looking for. What you want is a "case -> when" statement, which can be produced in Rails or as SQL with ActiveRecord.
Ransack gives you the ability to create a custom SQL command, by defining attribute, predicate and value, which then translates into WHERE Statement you already mentioned. I don't see any possibility to tell Ransack directly to filter for what you want. However:
What you could is create a scope like:
scope :column_b_gt_columnb_a, -> { where('column_b > column_a') }
And then you can build your search like this:
Object({ column_b_gt_columnb_a: true })
Probably not really what you were looking, but I think that's the best you gonna get...
And if you want to do it with Rails you would do to compare values or use said where statement I used above.
Records.each do |i|
case i.variable_a
when i.variable_b
# do something when it's the same
when i.variable_a > i.variable_b
# do something when it's greater
end
end
For an example of an SQL statement look here
How do I compare two columns for equality in SQL Server?
Hope this helps a bit!
I have a set-up with multiple contests and objects. They are tied together with a has_many :through arrangement with contest_objs. contest_objs also has votes so I can have several contests including several objects. I have a complex SQL setup to calculate the current ranking. However, I need to specify the contest in the SQL select statement for the ranking. I am having difficulty doing this. This is what I got so far:
#objects = #contest.objects.select('"contest_objs"."votes" AS v, name, "objects"."id" AS id,
(SELECT COUNT(DISTINCT "oi"."object_id")
FROM contest_objs oi
WHERE ("oi"."votes") > ("contest_objs"."votes"))+1 AS vrank')
Is there any way in the selection of vrank to specify that WHERE also includes "oi"."contest_id" = #contest.id ?
Since #contest.id is an integer and does not present any risk of an SQL Injection, you could do the following using string interpolation :
Model.select("..... WHERE id = #{#contest.id}")
Another possible solution would be to build your subquery using ActiveRecord, and then call .to_sql in order to get the generated SQL, and insert it in your main query.
Use sanitize_sql_array:
sanitize_sql_array('select ? from foo', 'bar')
If you're outside a model, because the method is protected you have to do this:
ActiveRecord::Base.send(:sanitize_sql_array, ['select ? from foo', 'bar'])
http://apidock.com/rails/ActiveRecord/Sanitization/ClassMethods/sanitize_sql_array
You can insert variables into sql commands like this:
Model.select("...... WHERE id = ?", #contest.id)
Rails will escape the values for you.
Edit:
This does not work as stated by Intrepidd in the comments, use string interpolation like he suggested in his answer. That is safe for integer parameters.
If you find yourself inserting several strings in a query, you could consider using find_by_sql, which gives you the above mentioned ? replacement, but you can't use it with chaining, so rewriting the whole query would be needed.
I already have a working solution, but I would really like to know why this doesn't work:
ratings = Model.select(:rating).uniq
ratings.each { |r| puts r.rating }
It selects, but don't print unique values, it prints all values, including the duplicates. And it's in the documentation: http://guides.rubyonrails.org/active_record_querying.html#selecting-specific-fields
Model.select(:rating)
The result of this is a collection of Model objects. Not plain ratings. And from uniq's point of view, they are completely different. You can use this:
Model.select(:rating).map(&:rating).uniq
or this (most efficient):
Model.uniq.pluck(:rating)
Rails 5+
Model.distinct.pluck(:rating)
Update
Apparently, as of rails 5.0.0.1, it works only on "top level" queries, like above. Doesn't work on collection proxies ("has_many" relations, for example).
Address.distinct.pluck(:city) # => ['Moscow']
user.addresses.distinct.pluck(:city) # => ['Moscow', 'Moscow', 'Moscow']
In this case, deduplicate after the query
user.addresses.pluck(:city).uniq # => ['Moscow']
If you're going to use Model.select, then you might as well just use DISTINCT, as it will return only the unique values. This is better because it means it returns less rows and should be slightly faster than returning a number of rows and then telling Rails to pick the unique values.
Model.select('DISTINCT rating')
Of course, this is provided your database understands the DISTINCT keyword, and most should.
This works too.
Model.pluck("DISTINCT rating")
If you want to also select extra fields:
Model.select('DISTINCT ON (models.ratings) models.ratings, models.id').map { |m| [m.id, m.ratings] }
Model.uniq.pluck(:rating)
# SELECT DISTINCT "models"."rating" FROM "models"
This has the advantages of not using sql strings and not instantiating models
Model.select(:rating).uniq
This code works as 'DISTINCT' (not as Array#uniq) since rails 3.2
Model.select(:rating).distinct
Another way to collect uniq columns with sql:
Model.group(:rating).pluck(:rating)
If I am going right to way then :
Current query
Model.select(:rating)
is returning array of object and you have written query
Model.select(:rating).uniq
uniq is applied on array of object and each object have unique id. uniq is performing its job correctly because each object in array is uniq.
There are many way to select distinct rating :
Model.select('distinct rating').map(&:rating)
or
Model.select('distinct rating').collect(&:rating)
or
Model.select(:rating).map(&:rating).uniq
or
Model.select(:name).collect(&:rating).uniq
One more thing, first and second query : find distinct data by SQL query.
These queries will considered "london" and "london " same means it will neglect to space, that's why it will select 'london' one time in your query result.
Third and forth query:
find data by SQL query and for distinct data applied ruby uniq mehtod.
these queries will considered "london" and "london " different, that's why it will select 'london' and 'london ' both in your query result.
please prefer to attached image for more understanding and have a look on "Toured / Awaiting RFP".
If anyone is looking for the same with Mongoid, that is
Model.distinct(:rating)
Some answers don't take into account the OP wants a array of values
Other answers don't work well if your Model has thousands of records
That said, I think a good answer is:
Model.uniq.select(:ratings).map(&:ratings)
=> "SELECT DISTINCT ratings FROM `models` "
Because, first you generate a array of Model (with diminished size because of the select), then you extract the only attribute those selected models have (ratings)
You can use the following Gem: active_record_distinct_on
Model.distinct_on(:rating)
Yields the following query:
SELECT DISTINCT ON ( "models"."rating" ) "models".* FROM "models"
In my scenario, I wanted a list of distinct names after ordering them by their creation date, applying offset and limit. Basically a combination of ORDER BY, DISTINCT ON
All you need to do is put DISTINCT ON inside the pluck method, like follow
Model.order("name, created_at DESC").offset(0).limit(10).pluck("DISTINCT ON (name) name")
This would return back an array of distinct names.
Model.pluck("DISTINCT column_name")