query expression self-join - f#

How can I change this query expression's join clause so that I don't have to wrap parent.ID in an option just to join on a candidate child.ParentID which might be None?
query { for parent in d.People do
join child in d.People on (Some parent.ID = child.ParentID)
exists (child.Birthdate <= parent.Birthdate) }
Thanks!

You might like this. There may be a more elegant syntax for this approach, and I expect in the end it is not going to perform much differently, if at all, but here it is:
query { for child in d.People do
where (child.ParentID.IsSome)
join parent in d.People on (child.ParentID.Value = parent.ID)
exists (child.Birthdate <= parent.Birthdate) }
I first came up with this, but I wondered whether it implies calling Value before filtering out the None values:
query { for parent in d.People do
join child in d.People on (parent.ID = child.ParentID.Value)
where (child.ParentID.IsSome)
exists (child.Birthdate <= parent.Birthdate) }
To keep parent first and also put the child filter before the join condition, you can do a subquery, but that seemed a worse solution, not better. If someone knows a way of doing that without the subquery, please add a comment.

Related

Rails: Collect records whose join tables appear in two queries

There are three models that matter here: Objective, Student, and Seminar. All are associated with has_and_belongs_to_many.
There is an ObjectiveStudent join model that includes columns "ready" and "points_all_time". There is an ObjectiveSeminar join model that includes column "priority".
I need to collect all of the objectives that are associated with a given student and also with a given seminar.
They need to also be marked with a "priority" above zero in the seminar. So I think I need this line:
obj_sems = ObjectiveSeminar.where(:seminar => given_seminar).where("priority > ?", 0)
Finally, they need to also be objectives where the student is ready, but has not scored above 7. So I think I need this line:
obj_studs = ObjectiveStudent.where(:user => given_student, :ready => true).where("points_all_time <= ?", 7)
Is there a way to gather all the objectives whose join table records appear in both of the above queries? Note that neither of the lists return objectives; they return objective_seminars, and objective_students, respectively. My end goal is to collect the objectives that meet all of the above criteria.
Or am I approaching this all wrong?
Bonus question: I would also love to sort the objectives by their priority in the given seminar. But I'm afraid that would add too much to the database load. What are your thoughts on this?
Thank you in advance for any insight.
In order to get Objectives you'll need to start your query from that.
In order to query with an AND condition the associated tables, you'll need inner joins with these tables.
Finally you'll need a distinct operator to only fetch each objective once.
The extended version of what (I think) you need is:
Objective.joins(objective_seminars: :seminar, objective_student: :student).
where(seminars: seminar_search_params, strudents: student_search_params).
where('objective_seminars.priority > 0').
where('objective_students.ready = 1 AND points_all_time <= 7').
order('objective_seminars.priority ASC').
distinct
Now for the database load it all depends on your indexes and the size of your tables.
The above query will translate to the following SQL (or something similar).
SELECT DISTINCT objectives.* FROM objectives
INNER JOIN objective_students ON objective_students.objective_id = objectives.id
INNER JOIN students ON students.id = objective_students.student_id
INNER JOIN objective_seminars ON objective_seminars.objective_id = objectives.id
INNER JOIN seminars ON seminars.id = objective_seminars.seminar_id
WHERE seminars_query AND
students_query AND
objective_seminars.priority > 0 AND
objective_students.ready = 1 AND points_all_time <= 7 AND
objective_seminars.priority ASC
So you'll need to add or extend your indexes so that all 5 tables queries can have an index helping out. The actual index implementation is up to you and depends on your application's specific (read - write load, tables size, cardinality etc)

Find only records which have all deleted associations

The question goes as RoR but I guess the question applies more on a query level.
I have a model
ModelA has_many ModelB
and I'm using paranoid gem in both of them.
I am trying to find out all the ModelA which have either no ModelB associated or all its ModelB have been deleted (therefore deleted_at is not NULL).
Based on this question, I was able to achieve the desired result, but reading the query it does not make much sense to me how it is working.
ModelA.joins('LEFT OUTER JOIN model_bs ON model_bs.model_a_id = model_as.id AND model_bs.deleted_at IS NULL')
.where('model_bs.model_a_id IS NULL')
.group(model_as.id)
As mentioned, reading this query it does not make sense to me because the joins condition I'm using is also being as null on the where clause afterwards.
Can someone please help me out getting the query properly set and if this is the right way to go, explain me how does the query breakdown into the right result.
Update:
As mentioned on a comment below, after some raw sql I managed to understand what was going on. Also, wrote my AR solution as to make it more clear (at least from my perspective)
Breaking it down:
This query represents a regular left outer join, which returns all models on the left independently if there is an association on the right.
ModelA.joins('LEFT OUTER JOIN model_bs ON model_bs.model_a_id = model_as.id')
Adding the extra condition in the join
AND model_bs.deleted_at IS NULL
will return the rows with ModelB attributes available if there is one associated, else will return one row with only the ModelA.
Applying over these rows the
.where('model_bs.model_a_id IS NULL') will keep only rows that have no associated models.
Note:
In this case, using the attribute model_a_id or any other attribute of model_b will work, but if going this way, I'd recommend using an attribute that if the association exists, its always going to be there. Otherwise you might end up with wrong results.
Finally, the AR that I've ended up by using to translate what I wanted:
ModelA.joins('LEFT OUTER JOIN model_bs ON model_bs.model_a_id = model_as.id AND model_bs.deleted_at IS NULL')
.group('model_as.id')
.having('count(model_bs.id) = 0')
Hey you can try this way i have tested it in mysql you can verify it with PG
ModelA.joins('LEFT OUTER JOIN model_bs ON model_bs.model_a_id = model_as.id')
.group('model_as.id')
.having('SUM(CASE WHEN model_bs.deleted_at IS NULL THEN 0 ELSE 1 END) AS OCT = 0')

rails fetch records with no has many relationship and ones with a condition

I have a HomeMaker and PerDateUnavailability models. HomeMaker has_many per_date_unavailabilities. I want all home makers who don't have a record in per_date_unavailabilities and home makers who have record but not when the per_date_unavailabilties.unavailable_date = somedate
I usually do the first part when I want HomeMakers without a PerDateUnavailability record using HomeMaker.includes(:per_date_unavailabilities).where(per_date_unavailabilities: {id: nil})
and the second part of the condition using HomeMaker.joins(:per_date_unavailabilities).where.not(per_date_unavailabilities: {unavailable_date: Date.today})
How do I mix these?
The sql you need is somewhat like this:
select a.*
from home_makers a left join per_date_unavailabilities b
on a.id = b.home_maker_id
where b.home_maker_id is NULL
or b.unavailable_date IS NOT ?;
Expressing the or clause is a little tough in ActiveRecord, and its best we don't fight it. (Edited after OP's comment)
HomeMaker.joins("LEFT JOIN per_date_unavailabilities on per_date_unavailabilities.home_maker_id = home_makers.id")
.where("per_date_unavailabilities.home_maker_id IS NULL OR per_date_unavailabilities.unavailable_date != ?", somedate)

JPQL join two entities with no direct relations

I have an issue: When I am trying to join two tables which do not have a foreign key or a direct entity relation through my java code within themselves. I am using the below JPQL query: -
SELECT p FROM P p, OM orgm WHERE p.o.id = orgm.o.id and p.u.id = orgm.u.id and orgm.ma = true and p.u.id = ? AND p.o.id IN (:oId);
But this turns to a MySQL query which has a "cross join" which obviously is expensive.
What I need is to make sure that a similar query gives me an inner join MySQL query between the two tables.
I am trying to make usage of the "WITH" clause but seems that it doesn't work with inner join.
Please revert what can be done in this scenario.
Thanks in advance.

Rails: Joining by one table OR another table

I'm looking for a more efficient way to write an ActiveRecord query. I want to get all instances of a model that either join one table or another table. Both is easy, but either is difficult.
Right now, I have the following two queries:
across_clues = Clue.joins(:across_cells)
down_clues = Clue.joins(:down_cells)
(Followed by the unsatisfactory clues = (across_clues + down_clues).uniq.sort_by{|clue| clue.id} )
I'm wondering how to write a single query that will give me the union of both of my queries. That way I can let Postgres do the heavy lifting and keep Rails from getting its hands dirty.
I know how to get the intersection of the two sets:
bad_clues = Clue.joins(:across_cells, :down_cells)
but I haven't seen a good way to get their union. Any help would be appreciated and loved!
(For posterity)
I used UNION DISTINCT according to shiva's answer, but just slightly modified it to be less hard-coded:
across_query = Clue.joins(:across_cells).to_sql
down_query = Clue.joins(:down_cells).to_sql
clues = Clue.find_by_sql("(#{across_query}) UNION DISTINCT (#{down_query})")
It works!
The key is you need to use find_by_sql and UNION DISTINCT
I am a MySQL guy so here is how I would do it
Clue.find_by_sql("(SELECT clue.* FROM clue
INNER JOIN across_cell ON across_cell.clue_id=clue.id)
UNION DISTINCT
(SELECT clue.* FROM clue
INNER JOIN down_cell ON down_cell.clue_id = clue.id)")
What about
across_clues = Clue.joins(:across_cells)
down_clues = Clue.joins(:down_cells)
Clue.where do
(id.in across_clues.select{id}) | (id.in down_clues.select{id})
end
with Squeel?

Resources