ActiveRecord left outer join with and clause - sql

I am using Rails 3 with ActiveRecord and I cannot get it to generate the query I want without inserting almost plain SQL in it.
I simply want to execute this SQL query (actually the query is a little more complex but it's really this part that I cannot get right).
SELECT DISTINCT users.*, possible_dates.*
FROM users
LEFT OUTER JOIN possible_dates
ON possible_dates.user_id = users.id
AND possible_dates.event_id = MY_EVENT_ID;
which I managed to using
User.includes(:possible_dates)
.joins("LEFT OUTER JOIN possible_dates
ON possible_dates.user_id = users.id
AND possible_dates.event_id = #{ActiveRecord::Base.sanitize(self.id)}"
)
.uniq
but I feel that I am missing something to simply add the AND condition of the left join using ActiveRecord query methods.
I also tried to do something like this
User.includes(:possible_dates)
.where('possible_dates.event_id' => self.id)
.uniq
but this yields (as expected), a query with a WHERE clause at the end and not the AND clause I want on the join.
By the way, self in the two snippets above is an instance of my Event class.
Thank you for your help.

(1 year later...)
After looking around, I found out that the best was probably to use arel tables.
For the above example, the following code would work.
ut = User.arel_table
pt = PossibleDate.arel_table
et = Event.arel_table
User.joins(
ut.join(pt, Arel::Nodes::OuterJoin)
.on(pt[:user_id].eq(u[:id])
.and(pt[:event_id].eq(1))
).join_sql
).includes(:possible_dates).uniq
the 1 in eq(1) should be replaced by the correct event id.

Related

Why does Django QuerySet produce a query with 2 inner joins with AND clauses, instead or a single inner join with OR, when chaining filters?

I'm following the Django docs on making queries, and this example showed up:
Blog.objects.filter(entry__headline__contains='Lennon').filter(entry__pub_date__year=2008)
I was hoping that the corresponding SQL query to this involved only one inner join and a OR clause, since the corresponding results are entries that either satisfy one or both of the conditions.
Nevertheless, this is what an inspection to the queryset's query returned:
>>> qs = Blog.objects.filter(entry__headline__contains='Lennon').filter(entry__pub_date__year=2008)
>>> print(qs.query)
SELECT "blog_blog"."id", "blog_blog"."name", "blog_blog"."tagline"
FROM "blog_blog"
INNER JOIN "blog_entry" ON ("blog_blog"."id" = "blog_entry"."blog_id")
INNER JOIN "blog_entry" T3 ON ("blog_blog"."id" = T3."blog_id")
WHERE ("blog_entry"."headline" LIKE %Lennon% ESCAPE '\'
AND T3."pub_date" BETWEEN 2008-01-01 AND 2008-12-31)
You can see that it makes TWO inner joins.
Wouldn't the result be the same as:
SELECT "blog_blog"."id", "blog_blog"."name", "blog_blog"."tagline"
FROM "blog_blog"
INNER JOIN "blog_entry" ON ("blog_blog"."id" = "blog_entry"."blog_id")
WHERE ("blog_entry"."headline" LIKE %Lennon% ESCAPE '\' OR "blog_entry"."pub_date" BETWEEN 2008-01-01 AND 2008-12-31)
?
And this query is faster.
Well, after some research I've learned a few things.
First, from this answer, the way to implement an OR query is:
Blog.objects.filter(Q(entry__headline__contains='Lennon') | Q(entry__pub_date__year=2008))
And, from this fiddling:
https://www.db-fiddle.com/f/f8SGzTLeyr7DNZUaCx9HVL/0
I realized that the two queries in the OP don't get the same results: in the 2 inner joins, there is a cartesian product of results (2 "Lennon" * 2 "2008"), while in the second one there are 3 results (and it's indeed faster).
I assume, it is because you do filter() twice.
Try
qs = Blog.objects.filter(entry__headline__contains='Hello', entry__pub_date__year=2008)

Rails LEFT OUTER JOIN to load multiple models

I have a model called Entry that has_many :entry_states. EntryState has an attribute called :read.
I want to get all entries whether or not there is an entry_state so I think a LEFT OUTER JOIN is the best way to go see Left outer join on Coding Horror
However I also want to include an attribute of EntryState in the result. So I have a query that looks like:
entries = Entry.select('entries.*, entry_states.read').
joins("LEFT OUTER JOIN entry_states ON entries.id = entry_states.entry_id AND entry_states.user_id = #{user_id}")
The problem with this is the entry_states object is not available after this.
I can get the read state like:
entries.first.read
>> t
Which gives me the raw column value, but this is a boolean column so it would be great to get back true or false
What would be even better is if I could get the whole EntryState object. I've tried using .includes(:entry_state), but I have not found a way to include the outer join condition of AND entry_states.user_id = #{user_id}

Joining tables, counting, and group to return a Model

So I've got a SQL query I'd like to duplicate in rails:
select g.*
from gamebox_favorites f
inner join gameboxes g on f.gamebox_id = g.id
group by f.gamebox_id
order by count(f.gamebox_id) desc;
I've been reading over the rails Active Record Query Interface site, but can't quite seem to put this together. I'd like the query to return a collection of Gamebox records, sorted by the number of 'favorites' a gamebox has. What is the cleanest way to do this in rails?
I believe this will work (works on a similarly structured database locally), though I'm not sure I have the proper models in the proper spots for what you're trying to do, so you might need to move a coule things around:
Gamebox.joins(:gamebox_favorites).
group('"gamebox_favorites"."gamebox_id"').
order('count("gamebox_favorites"."gamebox_id")')
On the console, this should compile to (in the case of PostgreSQL on the back end):
SELECT "gameboxes".* FROM "gamebox_favorites"
INNER JOIN "gamebox_favorites"
ON "gamebox_favorites"."gamebox_id" = "gamebox"."id"
GROUP BY "gamebox_favorites"."gamebox_id"
ORDER BY count("gamebox_favorites"."gamebox_id")
...and I'm guessing that you don't want do just wrap it in a find_by_sql call, such as:
Gamebox.find_by_sql("select g.* from gamebox_favorites f
inner join gameboxes g
on f.gamebox_id = g.id
group by f.gamebox_id
order by count(f.gamebox_id) desc")

Nhibernate join filtering

I have a question about joins in NHIBERNATE. We had an issue with our sql query that was generated but nhibernate. Our db developer optimized the raw sql so it works as we need, but we need to change the nhibernate code to make generated sql look like optimized.
the part of the original part of the query is:
FROM PERSON_VISIT this_
inner join PERSON_Basic per2_
on this_.PERSON_ID = per2_.PERSON_ID
left outer join PERSONC_QUESTIONS perint10_
on per2_.PERSON_ID = perint10_.PERSON_ID
left outer join TELEPHONE_QUESTIONS intaudit13_
on perint10_.PP_QUESTIONS_ID = intaudit13_.PP_QUESTIONS_ID
inner join C_QUESTIONS intdef14_
on perint10_.QUESTION_ID = intdef14_.QUESTION_ID
and perint10_.QUESTIONS_CODE = intdef14_.QUESTIONS_CODE
and perint10_.QUESTION_ID = intdef14_.QUESTION_ID
The optimized one is :
FROM PERSON_VISIT this_
inner join PERSON_Basic per2_
on this_.PERSON_ID = per2_.PERSON_ID
left outer join PERSONC_QUESTIONS perint10_
on per2_.PERSON_ID = perint10_.PERSON_ID
left outer join TELEPHONE_QUESTIONS intaudit13_
on perint10_.PP_QUESTIONS_ID = intaudit13_.PP_QUESTIONS_ID
left outer join C_QUESTIONS intdef14_
on perint10_.QUESTION_ID = intdef14_.QUESTION_ID
and perint10_.QUESTIONS_CODE = intdef14_.QUESTIONS_CODE
and perint10_.QUESTION_ID = intdef14_.QUESTION_ID
and intdef14_.DISCIPLINE_CODE = this_.DISCIPLINE_CODE
To change query from inner join to left outer join is easy, i changed only one line of code:
.CreateAlias("PersonInt.QuestionEntity", "IntDef", JoinType.LeftOuterJoin)
But how I can add
and intdef14_.DISCIPLINE_CODE = this_.DISCIPLINE_CODE
using nhibernate code?
There is an option to add reference from PERSON_VISIT definition to C_QUESTIONS, but the problem is that
PERSON_VISIT is used everywhere and I don't want this change to possibly break other queries, I just wnat to add only one line of code to add, how I can do that? Is there any way to have access to the raw join to change it? Or some other way to add this
and intdef14_.DISCIPLINE_CODE = this_.DISCIPLINE_CODE
To the query?
I know that somebody will say that we can add a restriction to the query through criteria.Add, but it is not an option cause db developer optimized our query taking this restriction from WHERE clause to the join.
How I can do that quickly without changing the models definitions? Just changing only this one query without changing the whole model?
It is possible using HQL and the Criteria API's.
This question gives you the answer: Adding conditionals to outer joins with nhibernate
Something like this may solve your issue.
.CreateAlias("PersonInt.QuestionEntity", "IntDef", JoinType.LeftOuterJoin,
Restrictions.EqProperty("DISCIPLINE_CODE", "IntDef.DISCIPLINE_CODE"))
Thanks for answers. We use 2.0 version of NHibernate in our project so we didn't have a chance to use new methods of .CreateAlias with restrictions.
I have fixed an issue using Interceptors:
public class SqlInterceptor : EmptyInterceptor, IInterceptor
{
SqlString IInterceptor.OnPrepareStatement(SqlString sql)
{
//manipulating with the sql
return sql;
}
}
than
var factory = Session.SessionFactory;
var session = factory.OpenSession(new SqlInterceptor());
And use my query without a change.

sql query question inner join

LEFT JOIN PatientClinics AB ON PPhy.PatientID = AB.PatientID
JOIN Clinics CL ON CL.ID = AB.ClinicID
AND COUNT(AB.ClinicID) = 1
I get error using Count(AB.ClinicID) = 1 (ClinicID has duplicate values in the table and
I want to use only 1 value of each duplicate value of ClinicId to produce result)
What mistake am I making?
I've never seen a COUNT() being used in a JOIN before. Maybe you should use:
HAVING COUNT(AB.ClinicID) = 1
instead.
Count() can't be used as a join/filter predicate. It can be used in the HAVING clause however. You should include the entire query in order to get a better example of how to rewrite it.
maybe investigate the HAVING clause instead of using COUNT where you put it.
hard to help without the full query.