Nested query in squeel - ruby-on-rails

Short version: How do I write this query in squeel?
SELECT OneTable.*, my_count
FROM OneTable JOIN (
SELECT DISTINCT one_id, count(*) AS my_count
FROM AnotherTable
GROUP BY one_id
) counts
ON OneTable.id=counts.one_id
Long version: rocket_tag is a gem that adds simple tagging to models. It adds a method tagged_with. Supposing my model is User, with an id and name, I could invoke User.tagged_with ['admin','sales']. Internally it uses this squeel code:
select{count(~id).as(tags_count)}
.select("#{self.table_name}.*").
joins{tags}.
where{tags.name.in(my{tags_list})}.
group{~id}
Which generates this query:
SELECT count(users.id) AS tags_count, users.*
FROM users INNER JOIN taggings
ON taggings.taggable_id = users.id
AND taggings.taggable_type = 'User'
INNER JOIN tags
ON tags.id = taggings.tag_id
WHERE tags.name IN ('admin','sales')
GROUP BY users.id
Some RDBMSs are happy with this, but postgres complains:
ERROR: column "users.name" must appear in the GROUP BY
clause or be used in an aggregate function
I believe a more agreeable way to write the query would be:
SELECT users.*, tags_count FROM users INNER JOIN (
SELECT DISTINCT taggable_id, count(*) AS tags_count
FROM taggings INNER JOIN tags
ON tags.id = taggings.tag_id
WHERE tags.name IN ('admin','sales')
GROUP BY taggable_id
) tag_counts
ON users.id = tag_counts.taggable_id
Is there any way to express this using squeel?

I wouldn't know about Squeel, but the error you see could be fixed by upgrading PostgreSQL.
Some RDBMSs are happy with this, but postgres complains:
ERROR: column "users.name" must appear in the GROUP BY clause or be
used in an aggregate function
Starting with PostgreSQL 9.1, once you list a primary key in the GROUP BY you can skip additional columns for this table and still use them in the SELECT list. The release notes for version 9.1 tell us:
Allow non-GROUP BY columns in the query target list when the primary
key is specified in the GROUP BY clause
BTW, your alternative query can be simplified, an additional DISTINCT would be redundant.
SELECT o.*, c.my_count
FROM onetable o
JOIN (
SELECT one_id, count(*) AS my_count
FROM anothertable
GROUP BY one_id
) c ON o.id = counts.one_id

Related

Rails ActiveRecord subquery on Inner Join

I am struggling to use Rails' ActiveRecord query interface to replicate a query that has an inner join subquery. How would I replicate the following:
SELECT ass.name, COUNT(DISTINCT a.question_id) AS
answered_questions, tq.total_questions
FROM assessments AS ass
INNER JOIN (SELECT ass.id, COUNT(q.id) AS total_questions FROM
questions AS q INNER JOIN assessments AS ass ON ass.id=q.assessment_id
GROUP BY
ass.id) as tq ON tq.id=ass.id
INNER JOIN questions AS q ON q.assessment_id=ass.id
INNER JOIN answers AS a ON a.assessment_id=ass.id AND a.question_id=q.id
INNER JOIN org_assesments AS oa ON ass.id=oa.assessment_id
INNER JOIN users AS u ON oa.org_id=u.org_id AND
a.user_id=u.id
WHERE u.id=1
GROUP BY ass.name, tq.total_questions
ORDER BY ass.created_at DESC
LIMIT 10
I don't seem to be able to get this to work with the subquery using the query builder. Without the subquery I have this, which works and gives me the assessment title and number of questions answered:
Question.joins(:assessment => {:org_assessments => {:org => :users}}).joins(:answers)
.where(answers:{:user_id => params[:id]})
.distinct('answers.question_id').group(['assessments.name']).count()
How can I write this to include the subquery as in the original SQL above?
You may send the subquery as a string to the joins method:
subquery =
TotalQuestion.
joins(:assessments).
group('assessments.id').
select('assessments.id, COUNT(q.id) as total_questions').to_sql
Question.joins("(#{sub_query}) as tq on tq.id=ass.id")
And you can combine it with the other parts of the query:
Question.
joins(:assessment => {:org_assessments => {:org => :users}}).joins(:answers).
joins("(#{sub_query}) as tq on tq.id=ass.id").
where(answers:{:user_id => params[:id]}).
distinct('answers.question_id').group(['assessments.name']).count()

Nested SQL SELECT in Rails 4

I'm looking for a way to generate the following SQL in Rails (to make it a scope), so that I could chain it with further scopes (e.g. Article.published.most_comments):
SELECT *, cs.count
FROM articles, (
SELECT article_id, count(*)
FROM comments
GROUP BY comments.article_id
) cs
WHERE articles.id = cs.article_id
ORDER BY cs.count DESC;
I've tried something along the lines of Article.joins(:comments).select('*').group('comments.article_id'), but that doesn't generate the desired SQL:
SELECT * FROM "articles"
INNER JOIN "comments" ON "comments"."article_id" = "articles"."id"
GROUP BY comments.article_id
(PSQL): PG::GroupingError: ERROR: column "articles.id"
must appear in the GROUP BY clause or be used in
an aggregate function
And there doesn't appear to be a .from method in which I could specify the nested SQL SELECT.
Actually, there's a .from method:
scope :most_comments, -> {
Article.select('*, cs.count').from(
'articles, (
SELECT article_id, count(*)
FROM comments
GROUP BY comments.article_id
) cs'
)
.where('articles.id = cs.article_id')
.order('cs.count DESC')
}
Not sure if this is the best way but it works...

How to write inner join for Rails Active Record query

I want Rails Active Record query for the in case of below sql query
Any help is appreciated
SELECT parent_id FROM categories INNER JOIN categories_coaches ON categories.id = categories_coaches.category_id
Category.select(:parent_id).joins(:category_coaches)

Using multiple column names in where with RoR and ActiveRecord

I want to produce the following sql using active record.
WHERE (column_name1, column_name1) IN (SELECT ....)
I don't know how to do this is active record.
I've tried these so far
where('column_name1, column_name2' => {})
where([:column_name1, :column_name2] => {})
This is the full query I'd like to create
SELECT a, Count(1)
FROM table
WHERE ( a, b ) IN (SELECT a,
Max(b)
FROM table
GROUP BY a)
GROUP BY a
HAVING Count(1) > 1)
I've already written a scope for the subquery
Thanks in advance.
WHERE (column_name1, column_name1) IN (SELECT ....) is not a valid construct in sql; so it can't be done in active record either.
The valid way of accomplishing the same in SQL would be:
WHERE column_name1 IN (select ....) OR column_name2 IN (select ...)
The same query can be used directly in the active record:
where("column_name1 IN (select ...) OR column_name2 IN (select...)")
Avoiding duplication:
selected_values = select ...
where("column_name IN ? OR column_name2 in ?", selected_values, selected_values)
So I decided to use an inner join to gain the same functionality. Here is my solution.
select(:column1, 'Count(1)').
joins("INNER JOIN (#{subquery.to_sql}) AS table2 ON
table1.column1=table2.column1
AND table1.column2=table2.column2")

How do I get Rails ActiveRecord to generate optimized SQL?

Let's say that I have 4 models which are related in the following ways:
Schedule has foreign key to Project
Schedule has foreign key to User
Project has foreign key to Client
In my Schedule#index view I want the most optimized SQL so that I can display links to the Schedule's associated Project, Client, and User. So, I should not pull all of the columns for the Project, Client, and User; only their IDs and Name.
If I were to manually write the SQL it might look like this:
select
s.id,
s.schedule_name,
s.schedule_type,
s.project_id,
p.name project_name,
p.client_id client_id,
c.name client_name,
s.user_id,
u.login user_login,
s.created_at,
s.updated_at,
s.data_count
from
Users u inner join
Clients c inner join
Schedules s inner join
Projects p
on p.id = s.project_id
on c.id = p.client_id
on u.id = s.user_id
order by
s.created_at desc
My question is: What would the ActiveRecord code look like to get Rails 3 to generate that SQL? For example, somthing like:
#schedules = Schedule. # ?
I already have the associations setup in the models (i.e. has_many / belongs_to).
I think this will build (or at least help) you get what you're looking for:
Schedule.select("schedules.id, schedules.schedule_name, projects.name as project_name").joins(:user, :project=>:client).order("schedules.created_at DESC")
should yield:
SELECT schedules.id, schedules.schedule_name, projects.name as project_name FROM `schedules` INNER JOIN `users` ON `users`.`id` = `schedules`.`user_id` INNER JOIN `projects` ON `projects`.`id` = `schedules`.`project_id` INNER JOIN `clients` ON `clients`.`id` = `projects`.`client_id`
The main problem I see in your approach is that you're looking for schedule objects but basing your initial "FROM" clause on "User" and your associations given are also on Schedule, so I built this solution based on the plain assumption that you want schedules!
I also didn't include all of your selects to save some typing, but you get the idea. You will simply have to add each one qualified with its full table name.

Resources