Doctrine: Multiple (whereIn OR whereIn) query? - symfony1

I'm having trouble crafting a fairly simple query with Doctrine...
I have two arrays ($countries, $cities) and I need to check whether database record values would match any inside either. I'm looking for something like:
->whereIn('country', 'city', $countries, $cities)
... with 'country' being a WHERE IN for $countries and 'city' being a WHERE IN for $city.
I could separate the two out but the needed query has lots of other conditions so that's not possible. The resulting SQL I'm after would be:
SELECT ...
WHERE ...
AND ...
AND ...
AND ('country' IN (1,2,3) OR 'city' IN (7,8,9))
AND ...
AND ...;
One could therefore think of it also as a bracketing issue only. Anyone know if this is possible with Doctrine DQL? I've looked through the documentation but can't find any direction.
Thanks

After an hour of experimenting on this nonsense, here's the syntax to make it work.
$q->andWhere('country IN ? OR city IN ?', array(array(1, 2, 3), array(7, 8, 9)));

Why not use something like?
$countryIds=[1,2,3];
$cityIds=[7,8,9];
$q->whereIn('country',$countryIds)->andWhereIn('city',$cityIds);
Also, chain them together for context (most Doctrine methods return $this).
see http://www.symfony-project.org/doctrine/1_2/en/06-Working-With-Data

Related

Combining queries into an ActiveRecord_AssociationRelation

Hi I'm working on a project and I need to take result of two database queries and combine them into one ActiveRecord_AssociationRelation, at the moment I have:
results.where(pos_or_neg: "neg").order("value DESC") + (results.where(pos_or_neg: "pos").order("value ASC"))
However this returns an array which doesn't work as I need to do more processing afterwards. I've tried:
results.where(pos_or_neg: "neg").order("value DESC").merge(results.where(pos_or_neg: "pos").order("value ASC"))
but this only seems to return the half of the results.
Thanks
results.order("pos_or_neg ASC,case when pos_or_neg="neg" then value else -1*value end DESC")
I believe using merge is the equivalent of an AND query in SQL. What you are looking for is an OR query.
Since Rails 5 this is one of the Active Record query methods that you can use!
http://api.rubyonrails.org/classes/ActiveRecord/QueryMethods.html#method-i-or
Try to replace your .merge with .or and see if that works better

inserting variable into complex sql command

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.

Rails: select unique values from a column

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")

Map/Reduce on Scoped collection with Rails and Mongoid

I am trying to map/reduce a scope in a Rails 3.1 app using MongoDB with Mongoid.
The results seem odd, so I wonder if map_reduce can be applied on a prescoped collection for example like that:
current_user.tasks.for_year_and_month(year, month).collection.map_reduce(map, reduce, :out => "res")
for_year_and_month scopes tasks on a given month, but the results from map_reduce seem to include other tasks too. Now I wonder whether my map/reduce functions are wrong, or map/reduce can not be applied on pre-scoped collections.
If so, I had to do all the scope's work in my emit function what would make things even worse. I can't believe that.
Can anyone enlighten me?
Regards
Felix
The scope is ignored because you're working directly on collection.
You can pass the :query option to map_reduce and this will filter documents.
Eg:
Task.collection.map_reduce(map,reduce,out:{merge:'res'},query:{user_id: x, ...})
This is what you're after:
results = Task.collection.map_reduce(map, reduce, {query: {month: month, year: year}, out: "reduced_task_results"})
filtered_results = results.find({})
You can use the scope selector from Mongoid in the query option:
scoped = current_user.tasks.whatever
Task.map_reduce( ..., :query => scoped.selector)

Ruby/Rails Collection to Collection

I have a two tables joined with a join table - this is just pseudo code:
Library
Book
LibraryBooks
What I need to do is if i have the id of a library, i want to get all the libraries that all the books that this library has are in.
So if i have Library 1, and Library 1 has books A and B in them, and books A and B are in Libraries 1, 2, and 3, is there an elegant (one line) way todo this in rails?
I was thinking:
l = Library.find(1)
allLibraries = l.books.libraries
But that doesn't seem to work. Suggestions?
l = Library.find(:all, :include => :books)
l.books.map { |b| b.library_ids }.flatten.uniq
Note that map(&:library_ids) is slower than map { |b| b.library_ids } in Ruby 1.8.6, and faster in 1.9.0.
I should also mention that if you used :joins instead of include there, it would find the library and related books all in the same query speeding up the database time. :joins will only work however if a library has books.
Perhaps:
l.books.map {|b| b.libraries}
or
l.books.map {|b| b.libraries}.flatten.uniq
if you want it all in a flat array.
Of course, you should really define this as a method on Library, so as to uphold the noble cause of encapsulation.
If you want a one-dimensional array of libraries returned, with duplicates removed.
l.books.map{|b| b.libraries}.flatten.uniq
One problem with
l.books.map{|b| b.libraries}.flatten.uniq
is that it will generate one SQL call for each book in l. A better approach (assuming I understand your schema) might be:
LibraryBook.find(:all, :conditions => ['book_id IN (?)', l.book_ids]).map(&:library_id).uniq

Resources