I'm trying to get all the clients that have doctors associated BUT none of them has started their first session (one client has_many doctors and can have first sessions with each of them).
So far I have:
#clients = Client.joins(:doctors).where('doctors.first_session IS NULL').order('clients.id DESC')
But this doesn't work when a client has for example 2 doctors. the first doctor.first_session = null but the second one is not. This case will return the client and it don't want it to.
Any ideas?
This is one of those cases where in order to find records that don't meet a certain condition, you do it by finding all records except those that meet the condition. In SQL this is done with a subquery in the WHERE clause.
For cases like this, the squeel gem is extremely helpful, because it encapsulates the SQL complexity. This is how I would do it (with squeel):
scope :visited_doctor, joins(:doctors).where { doctors.first_visit != nil }
scope :not_visited_doctor, where { id.not_in(Patient.visited_doctor.select(:id)) }
Note that you can do this without squeel, but you'll have to get your hands (and your code) dirty with SQL.
This will work, but may be a little less efficient since it does some of the work in ruby instead of all in the db.
clients = Client.order('clients.id DESC').include(:doctors).select do |client|
client.doctors.all? {|doctor| doctor.first_session.nil? }
end
Logically, that should fetch all the clients, and then in ruby, the select will evaluate the condition in the block and those clients that return true will be assigned to clients.
The condition block will return true only if all of that client's doctors have a nil first_session.
Hope that helps. There's probably a more efficient way to do this using subselects, but the syntax for that is likely to depend on which database you're using.
Well, I found a solution that involved two queries.
avoid_ids_results = Doctors.select('client_id')
.where("first_session IS NOT NULL")
.map(&:client_id).join(', ')
#clients = Clients.
joins(:doctors).
where('clients.id NOT IN (' + avoid_ids_results + ')').
order('clients.id DESC')
Thank you all!
You could create a method in your Client model which returns true if any first_session on a client's doctors is true, something like...
def has_first?
self.doctors.each do |doctor|
return true if !doctor.first_session.nil?
end
return false
end
This is pseudocode and may need to be tweaked first
Related
I want to efficiently get the results using '.includes()'.
I need the includes Table to retrieve the records that are NOT nil. I need to keep the included: true in the query.
Using postgres.
Role has many tasks.
Tasks belongs to a Role.
The following works but gets the wrong records from the Roles table.
Task.includes(:role).where(included: true, roles: {doer: nil})
The next part is the IDEA of what I want...
but obviously syntax is wrong in the where clause's "roles" value.
Task.includes(:role).where(
included: true,
roles: {
doer != nil #=> where 'doer' is not 'nil'
})
I'm looking for an efficient query which is why i did the includes.
I don't know how to get this without multiple where queries.
If you understand the question but think it could be asked better to be more clear, let me know. I couldn't any clues for this answer anywhere unless multiple where statments are used.
I prefer to avoid strings when possible, so I'd use the following:
Task.includes(:role).where(included: true).where.not(roles: { doer: nil })
If you have specifically set the included column on the tasks table to have NULL FALSE, you could also condense the where calls into a single call, although this will still only launch one query:
Task.includes(:role).where.not(included: false, roles: { doer: nil })
Personally, I'd like to see this cleaned-up a bit with some scopes, providing that these calls are commonplace. Something like:
scope :with_doer, -> { includes(:role).where.not(roles: { doer: nil }) }
so the resulting code would be more readable:
Task.with_doer.where(included: true)
You could obviously extend this pattern to the included: true bit as well.
Note that ActiveRecord queries are built up and then kicked to the database with a "kicker" which Rails kind of sneakily and hackily does through #inspect or #to_a most of the time. So, you don't need to worry about needing to condense the wherecalls into a single call.
What about something like:
Task.includes(:roles).where.not('roles.doer' => nil)
This is a rails 4-and-up convention, for rails 3 it would be something like:
Task.includes(:roles).where("roles.doer IS NOT NULL")
And you don't need the included attribute, it can be removed from the model for these purposes.
Since you do seem to need included (oops)
Task.includes(:roles).where('tasks.included' => true, 'roles.doer' => !nil)
Aaah.. how i love postgres.. When you're asking for efficiency, I think this fails, but I'm returning correct results. If you benchmark the options, this is correct (I think) but slow.
Task.joins('LEFT OUTER JOIN "roles" ON roles.user_id = user_id').where("tasks.included IS true AND roles.doer IS NOT NULL")
Write SQL statements like this?
Task.includes(:role).where("included = 'true' AND roles.doer NOT 'nil'")
I have a rails app with a Customer and a ShippingAddress model. It is implemented with a one-to-many relationship so that a ShippingAddress can have multiple customers.
I am successfully able to query across these two models and several others with an include statement, but as I tried to update the query to find all of the customers that does not have a shipping_address got 0 results, even though I am able to se from my DB-admin tool that I have multiple customers where the value of the shipping_address_id is nil.
These queries works, but does not give me customers without addresses:
Customer.includes(:orders, :shipping_address).where('customers.name LIKE ? or customers.mail LIKE ?', searchstring, searchstring)
Customer.where('customers.shipping_address_id = ?',2)
These attempts to adapt the above to give me customers without addreses doesn't:
Customer.includes(:orders, :shipping_address).where('shipping_address = ?', nil)
Customer.includes(:orders, :shipping_address).where('shipping_address = NULL')
# At the very least I should be able to get the right result by referencing the fk directly, but no?
Customer.where('customers.shipping_address_id = ?',nil)
What am I missing here?
The NULL value can be surprising until you get used to it. Conceptually, NULL means “a missing unknown value” and it is treated somewhat differently from other values.
You cannot compare null using equal to for this you must use IS NULL. So update your queries to
Customer.includes(:orders, :shipping_address).where('customers.shipping_address_id IS NULL')
Or rails way of doing this is
Customer.where(shipping_address_id: nil).includes(:orders, :shipping_address)
You could also just do:
#customers_without_shipping_ids = Customer.where('shipping_address_id IS NULL').all
Please have a try with these queries
Customer.all(:conditions => ['shipping_address_id IS NULL'])
and
Customer.includes(:orders, :shipping_address).where('shipping_address_id IS NULL')
I have a class method on User, that returns applies a complicated select / join / order / limit to User, and returns the relation. It also applies a where(:admin => true) clause. Is it possible to remove this one particular where statement, if I have that relation object with me?
Something like
User.complex_stuff.without_where(:admin => true)
I know this is an old question, but since rails 4 now you can do this
User.complex_stuff.unscope(where: :admin)
This will remove the where admin part of the query, if you want to unscope the whole where part unconditinoally
User.complex_stuff.unscope(:where)
ps: thanks to #Samuel for pointing out my mistake
I haven't found a way to do this. The best solution is probably to restructure your existing complex_stuff method.
First, create a new method complex_stuff_without_admin that does everything complex_stuff does except for adding the where(:admin => true). Then rewrite the complex_stuff method to call User.complex_stuff_without_admin.where(:admin => true).
Basically, just approach it from the opposite side. Add where needed, rather than taking away where not needed.
This is an old question and this doesn't answer the question per say but rewhere is a thing that exists.
From the documentation:
Allows you to change a previously set where condition for a given attribute, instead of appending to that condition.
So something like:
Person.where(name: "John Smith", status: "live").rewhere(name: "DickieBoy")
Will output:
SELECT `people`.* FROM `people` WHERE `people`.`name` = 'DickieBoy' AND `people`.`status` = 'live';
The key point being that the name column has been overwritten, but the status column has stayed.
You could do something like this (where_values holds each where query; you'd have to tweak the SQL to match the exact output of :admin => true on your system). Keep in mind this will only work if you haven't actually executed the query yet (i.e. you haven't called .all on it, or used its results in a view):
#users = User.complex_stuff
#users.where_values.delete_if { |query| query.to_sql == "\"users\".\"admin\" = 't'" }
However, I'd strongly recommend using Emily's answer of restructuring the complex_stuff method instead.
I needed to do this (Remove a 'where' clause from an ActiveRecord::Relation which was being created by a scope) while joining two scopes, and did it like this: self.scope(from,to).values[:joins].
I wanted to join values from the two scopes that made up the 'joined_scope' without the 'where' clauses, so that I could add altered 'where' clauses separately (altered to use 'OR' instead of 'AND').
For me, this went in the joined scope, like so:
scope :joined_scope, -> (from, to) {
joins(self.first_scope(from,to).values[:joins])
.joins(self.other_scope(from,to).values[:joins])
.where(first_scope(from,to).ast.cores.last.wheres.inject{|ws, w| (ws &&= ws.and(w)) || w}
.or(other_scope(from,to).ast.cores.last.wheres.last))
}
Hope that helps someone
Following Problem:
I need something like an empty scope. Which means that this scope is emtpy, but responds to all methods a scope usually responds to.
I'm currently using a little dirty hack. I simply supply "1=0" as conditions. I find this realy ugly, since it hits the database. Simply returning an empty array won't work, since the result must respond to the scoped methods.
Is there a better existing solution for this or will I need to code this myself?
Maybe some example code could help explain what i need:
class User < ActiveRecord::Base
named_scope :admins, :conditions => {:admin => true }
named_scope :none_dirty, :conditions => "1=0" # this scope is always empty
def none_broken
[]
end
def self.sum_score # okay, a bit simple, but a method like this should work!
total = 0
self.all.each do |user|
total += user.score
end
return total
end
end
User.admin.sum_score # the score i want to know
User.none_drity.sum_score # works, but hits the db
User.none_broken.sum_score # ...error, since it doesn't respond to sum_score
Rails 4 introduces the none scope.
It is to be used in instances where you have a method which returns a relation, but there is a condition in which you do not want the database to be queried.
If you want a scope to return an unaltered scope use all:
No longer will a call to Model.all execute a query immediately and return an array of records. In Rails 4, calls to Model.all is equivalent to now deprecated Model.scoped. This means that more relations can be chained to Model.all and the result will be lazily evaluated.
User.where('false')
returns an ActiveRecord::Relation with zero elements, that is a chain-able scope that won't hit the database until you actually try to access one of its elements. This is similar to PhilT's solution with ('1=0') but a little more elegant.
Sorry User.scoped is not what you want. As commented this returns everything. Should have paid more attention to the question.
I've seen where('1 = 0') suggested before and Rails should probably cache it as well.
Also, where('1 = 0') won't hit the database until you do .all, .each, or one of the calculations methods.
I thing you need User.scoped({})
How about User.where(id: nil) ?
Or User.where(_id: nil) for mongoid.
The thing you are looking for does not exist. You could implement something like this by monky patching the find method. Yet, this would be an overkill, so I recomend keeping this unless it's performance critical.
Looking at your example code indicates you may not know about aggregated queries in SQL which are exposed as calculations methods in Rails:
User.sum(:score) will give you the sum of all users' scores
Take a look at Rails Guides for more info:
http://guides.rubyonrails.org/active_record_querying.html#sum
In my posts model, I have a named scope:
named_scope :random, :order => "Random()"
I'd like to give users the ability to get posts in a random order by sending a GET request with params[:scope] = 'random'.
Short of eval("Post.#{params[:scope]}"), how can I do this?
I would suggest my very awesome acts_as_filter plugin designed for user-driven filtering of results via named_scopes.
http://github.com/tobyhede/acts_as_filter/tree/master
Eval is fine to use - but make sure you validate against accepted/expected values (I often just plug some values into an array and test accepted_values.include?(parameter))
eval is a pretty bad idea. However, #send is perfect for this - it's inherently safer, and faster than eval (as I understand it).
Product.send(params[:scope])
That should do it :)
I came across it in a search. searchlogic is perfect for this.
I would stay away from eval since you're dealing with data that comes from the user. Maybe just use a simple case statement? This way you'll be able to validate what the data they're giving you.
For the example you give, I'd be explicit, and chain scopes together to build the query you want:
scope = Post
scope = scope.random if params[:scope] == 'random'
#posts = scope.find(:all, ...) # or paginate or whatever you need to do
If params[:scope] isn't 'random', this is the same as calling Post.find(), otherwise it's doing Post.random.find()
From one of the other answers, it looks like find_by_filter would do pretty much the same thing for you.
Using this pattern, you can also combine multiple scopes into the query if you needed to support things that weren't mutually exclusive
e.g.
scope = scope.only_monsters if params[:just_monsters] == 1
scope = scope.limit(params[:limit].to_i) unless params[:limit].to_i.zero?
So GETting /posts?scope=random&just_monsters=1&limit=5 will give you:
Post.random.just_monsters.limit(5).find(:all, ...)