from this example that I stole from the net:
Company company = context.Companies
.Include("Employee.Employee_Car")
.Include("Employee.Employee_Country")
.FirstOrDefault(c => c.Id == companyID);
if I want a where clause in the include of Employee_Car for example, how do I do that? Say I just want it to retrieve blue cars.
Thanks
Short answer is you can't do it just through using the include. You will need to do a bit of joining.
So taking this tip post and the SO answer you could do something like along these lines. (Note not exactly your return type but the closest)
var companyBlueCars = from company in context.Companies
where company.Id == companyID
select new
{
company,
blueCars = from employee in company.Employees
where employee.Employee_Car.Colour == "blue"
select employee.Employee_Car
};
(I did make a couple guesses about the data structure but the idea is there.)
You will not do that because include doesn't support filtering or sorting. You must execute two separate queries to load companies and cars with their filters.
Related
I have a Company model that has many Disclosures. The Disclosure has columns named title, pdf and pdf_sha256.
class Company < ActiveRecord::Base
has_many :disclosures
end
class Disclosure < ActiveRecord::Base
belongs_to :company
end
I want to make it unique by pdf_sha256 and if pdf_sha256 is nil that should be treated as unique.
If it is an Array, I'll write like this.
companies_with_sha256 = company.disclosures.where.not(pdf_sha256: nil).group_by(&:pdf_sha256).map do |key,values|
values.max_by{|v| v.title.length}
end
companies_without_sha256 = company.disclosures.where(pdf_sha256: nil)
companies = companies_with_sha256 + companeis_without_sha256
How can I get the same result by using ActiveRecord query?
It is possible to do it in one query by first getting a different id for each different pdf_sha256 as a subquery, then in the query getting the elements within that set of ids by passing the subquery as follows:
def unique_disclosures_by_pdf_sha256(company)
subquery = company.disclosures.select('MIN(id) as id').group(:pdf_sha256)
company.disclosures.where(id: subquery)
.or(company.disclosures.where(pdf_sha256: nil))
end
The great thing about this is that ActiveRecord is lazy loaded, so the first subquery will not be run and will be merged to the second main query to create a single query in the database. It will then retrieve all the disclosures unique by pdf_sha256 plus all the ones that have pdf_sha256 set to nil.
In case you are curious, given a company, the resulting query will be something like:
SELECT "disclosures".* FROM "disclosures"
WHERE (
"disclosures"."company_id" = $1 AND "disclosures"."id" IN (
SELECT MAX(id) as id FROM "disclosures" WHERE "disclosures"."company_id" = $2 GROUP BY "disclosures"."pdf_sha256"
)
OR "disclosures"."company_id" = $3 AND "disclosures"."pdf_sha256" IS NULL
)
The great thing about this solution is that the returned value is an ActiveRecord query, so it won't be loaded until you actually need. You can also use it to keep chaining queries. Example, you can select only the id instead of the whole model and limit the number of results returned by the database:
unique_disclosures_by_pdf_sha256(company).select(:id).limit(10).each { |d| puts d }
You can achieve this by using uniq method
Company.first.disclosures.to_a.uniq(&:pdf_sha256)
This will return you the disclosures records uniq by cloumn "pdf_sha256"
Hope this helps you! Cheers
Assuming you are using Rails 5 you could chain a .or command to merge both your queries.
pdf_sha256_unique_disclosures = company.disclosures.where(pdf_sha256: nil).or(company.disclosures.where.not(pdf_sha256: nil))
Then you can proceed with your group_by logic.
However, in the example above i'm not exactly sure what is the objective but I am curious to better understand how you would use the resulting companies variable.
If you wanted to have a hash of unique pdf_sha256 keys including nil, and its resultant unique disclosure document you could try the following:
sorted_disclosures = company.disclosures.group_by(&:pdf_sha256).each_with_object({}) do |entries, hash|
hash[entries[0]] = entries[1].max_by{|v| v.title.length}
end
This should give you a resultant hash like structure similar to the group_by where your keys are all your unique pdf_sha256 and the value would be the longest named disclosure that match that pdf_sha256.
Why not:
ids = Disclosure.select(:id, :pdf_sha256).distinct.map(&:id)
Disclosure.find(ids)
The id sill be distinct either way since it's the primary key, so all you have to do is map the ids and find the Disclosures by id.
If you need a relation with distinct pdf_sha256, where you require no explicit conditions, you can use group for that -
scope :unique_pdf_sha256, -> { where.not(pdf_sha256: nil).group(:pdf_sha256) }
scope :nil_pdf_sha256, -> { where(pdf_sha256: nil) }
You could have used or, but the relation passed to it must be structurally compatible. So even if you get same type of relations in these two scopes, you cannot use it with or.
Edit: To make it structurally compatible with each other you can see #AlexSantos 's answer
Model.select(:rating)
Result of this is an array 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)
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']
Say I'm modeling Students, Lessons, and Teachers. Given a single student enrolled in many lessons, how would I find all of their teachers of classes that are level 102? For that matter, how would I find all of their lessons' teachers? Right now, I have this:
s = Mongoid::Student.find_by(name: 'Billy')
l = s.lessons.where(level: 102)
t = l.map { |lesson| lesson.teachers }.flatten
Is there a way to do turn the second two lines into one query?
Each collection requires at least one query, there's no way to access more than one collection in one query (i.e. no JOINs) so that's the best you can do. However, this:
t = l.map { |lesson| lesson.teachers }.flatten
is doing l.length queries to get the teachers per lesson. You can clean that up by collecting all the teacher IDs from the lessons:
teacher_ids = l.map(&:teacher_ids).flatten.uniq # Or use `inject` or ...
and then grab the teachers based on those IDs:
t = Teacher.find(teacher_ids)
# or
t = Teacher.where(:id.in => teacher_ids).to_a
If all those queries don't work for you then you're stuck with denormalizing something so that you have everything you need embedded in a single collection; this would of course mean that you'd have to maintain the copies as things change and periodically sanity check the copies for consistency problems.
i've seen in grails doc something about join fetch :
"This works well for single-ended associations, but you need to be
careful with one-to-manys. Queries will work as you'd expect right up
to the moment you add a limit to the number of results you want. At
that point, you will likely end up with fewer results than you were
expecting. The reason for this is quite technical but ultimately the
problem arises from GORM using a left outer join."
i don't see why a left outer join can raise a problem in a none single-end association like one-to-many for example.
can you give me an example ?
if i take the example given by Joshua
Class Person {
String name
static hasMany = [numbers:PhoneNumber]
static mapping = {
nubmers fetch : 'join'
}
}
Class PhoneNumber{
static belongsTo = [owner : Person]
}
//for testing
def person = Person.get(1)
result in:
select name,... from
person left outer join phone_number
on person.id = phone_number.owner_id
where person.id=1;
Can you give me a query(in gorm) which can show me the problem ?
thanks
If I understand your question correctly then it's easiest to explain with a simple example. Let's say we have a domain class Person with many PhoneNumber classes associated with it. Thus a LEFT JOIN to fetch this data will result in a SQL result like this:
PersonId, Name, PhoneNumberId, TelNumber
-----------------------------------------
1, Joe, 1, 123-123-1234
1, Joe, 2, 222-222-2222
1, Joe, 3, 333-333-3333
2, Jane, 4, 000-000-000
This would be four records returned from the datasource, but only two instances of the Person domain with their associated phone numbers. However, if you limit this query to a maximum of 2 records you will in fact only get one instance of the Person domain (Joe in this example) and his first two phone numbers.
Hope that helps you understand the underlying issue.
P.S. This is an entirely theoretical example, the actual query results from GORM would have different column names and such due to Hibernate.
Update
An example GORM based query is quite simple to demonstrate:
def people = Person.list([max: 2])
You might expect the above to give you two person instances, but in reality because of the left join you will only end up with one instance. Turning on SQL logging will show you the actual query being executed and you will notice it's using a LEFT join.
I have some Events, People. There is a many-to-many relationship between them so there is a PersonEvent connecting Events to People.
Event has a date and type
PersonEvent has an event_id and a person_id
Person has a name
I'm trying to build a search form that allows the user to search by the type of an Event, and then returns a list of People who attended a Event of that type in the past and the last date they attended such an Event. This should be in the controller.
The only solution I can think of involves nested loops and will probably run very slowly. I'm definitely looping through a lot of things I don't need to be.
For each person in Person.all
For each personevent in PersonEvent.all
Add the personevent to an array if the person_event.event.type is correct
Now, loop through the array and find the event with the latest date. That's the date of the last Event attendance.
Can anyone suggest a better algorithm?
In RoR, it would be:
Person.joins(:events).where(events: { type: params[:type] })
Rails joins will create an INNER JOIN, which will discard people who don't have an associated event that meets the criteria in where.
You don't explain how your keeping the date of attendance information, so I'll leave that bit up to you.
As you have the associations already set up you should be able to do something like:
f = Person.joins(:events)
f = f.where(:events => { :type => "the_type_you_are_searching_for" })
f = f.group('people.id')
f = f.select('people.*, max(events.date) as last_event_date')
people = f.all # though you probably want to paginate really
I've done it line by line to make it easier to read in here but often you'd see the where, group and select chained together one after the other on the same line.
You need the group otherwise you'll get people returned multiple times if they have been to multiple events.
The custom select is to include the last_event_date in the results.
Why not just write a custom SQL query? It would look something like this:
SELECT * FROM person_events
INNER JOIN people ON people.id = person_events.person_id
INNER JOIN events ON events.id = person_events.event_id AND events.type = 'EventType'
I am not sure if I am going about this the best way, but I will try to explain what I am trying to do.
I have the following domain classes
class User {
static hasMany = [goals: Goal]
}
So each User has a list of Goal objects. I want to be able to take an instance of User and return 5 Users with the highest number of matching Goal objects (with the instance) in their goals list.
Can someone kindly explain how I might go about doing this?
The easiest and most efficient way to achieve this is using plain SQL. Assuming you have these tables
users [id]
goals [id, description]
user_goals [user_id, goal_id]
You can have the following query to do what you need:
set #userId=123;
select user_id, count(*) as matched from user_goals
where user_id!=#userId
and goal_id in (select ug.goal_id from user_goals ug where ug.user_id=#userId)
group by user_id order by matched desc limit 5;
This takes a user id and returns a list of other users with matching goals, sorted by the number of matches. Wrap it up in a GoalService and you're done!
class GoalService {
def findUsersWithSimilarGoals(user) {
// ...
}
}
It may also be possible to do this with criteria or HQL, but with queries like this it's usually easier to use SQL.
If you're looking for a simple match, perhaps the easiest way would be to do a findAll for each Goal and then count the number of results that each other User appears in:
Map user2Count = [:]
for (goal in myUser.goals){
for (u in User.findAllByGoal(goal)){
def count = user2Count.containsKey(u) ? user2Count.get(u) : 0
count++
user2Count.put(u, count)
}
}
// get the top 5 users
def topUsers = user2Count.entrySet().sort({ it.value }).reverse()[0..5]
This may be too slow, depending on your needs, but it is simple. If many users share the same goals then you could cache the results of findAllByGoal.