Reputation: 505
A question has many answers.
It is easy to find all questions that have been answered and order them by newest answer:
def self.answered
joins(:answers).order('answers.created_at desc')
end
In the controller, I'd do @answered = Question.answered
But this returns duplicate records if a question has been answered more than once.
It's easy, on the other hand, to find distinct questions that have been answered, but only if I don't try to order them by their answers' created_at date:
def self.answered
joins(:answers).select("DISTINCT questions.title")
end
(Let's just assume here that question titles are validated to be unique, so this results in all unique records).
The problem:
This query can't be ordered by an answer's "created_at" date, because I've selected only the question's title in my SQL select statement...
def self.answered
joins(:answers).select("DISTINCT questions.title").order('answers.created_at desc')
end
results in this error (I'm using Postgres):
PGError: ERROR: for SELECT DISTINCT, ORDER BY expressions must appear in select list
What it means, I guess, is it wants to see "answers.created_at" in that select DISTINCT statement.
The following looks like it would work if I wanted to order by questions.created_at, but that's not what I want:
def self.answered
joins(:answers).select("DISTINCT(questions.title), questions.created_at").order('questions.created_at desc')
end
This is where my basic SQL knowledge conks out. How do I select distinct questions (only the ones with answers) and order them in descending order by their latest answer's created_at date?
I'd like to write it using Rails 3 Active Record queries, but straight SQL is fine. I hope someone can help.
Upvotes: 3
Views: 999
Reputation: 1572
@Anton's answer would be the preferred solution if we were talking about MySQL, but as you pointed out, it causes errors in PostgreSQL, since PG is a bit more restrictive in its select/grouping functions.
Here is how I was able to get it to work with PG:
def self.answered
scoped.select("questions.title").group("questions.title").joins(:answers).having('MAX(answers.created_at) IS NOT NULL').order('MAX(answers.created_at) DESC')
end
Or, if you want to select the entire ActiveRecord rows for the questions, you can select/group by every column in questions
:
def self.answered
cols = self.column_names.collect{ |c| "#{table_name}.#{c}" }.join(',')
scoped.select(cols).group(cols).joins(:answers).having('MAX(answers.created_at) IS NOT NULL').order('MAX(answers.created_at) DESC')
end
Upvotes: 1
Reputation: 1938
This might end up being a little bit more efficient and extensible and should avoid issues where psql gripes about aggregate functions:
select questions.*, count(answers.id) as answer_count from questions join answers on answers.question_id=questions.id group by answers.question_id having answer_count > 0 order by answers.created_at desc
P.S. by extensible I meant, you can split it into Arel structure, and then break out scopes such as with_answers and answered to do your dirty work behind the scenes while keeping the rest clean and allowing you to add on more scopes later. :)
UPDATE: just posting the Arel way:
scope :answered, select('questions.*, count(answers.id) as answer_count').joins('join answers on answers.question_id=questions.id').group('answers.question_id').having('answer_count > 0').order('answers.created_at desc')
Upvotes: 0
Reputation: 7309
I can write the SQL solution for it. It would go as below:
select q.id "Qn-ID", q.question, q.created_at "Qn-CreatedAt", a.id "Ans-ID", a.answer, a.created_at "Ans-CreatedAt" from questions q, answers a where a.question_id = q.id and a.id in (select max(id) answer_id from answers group by question_id) order by "Ans-CreatedAt" desc
Hope it helps!
Upvotes: 0
Reputation: 2483
Why not use write as:
def self.answered
joins(:answers).order("answers.created_at DESC").group("questions.id")
end
This will do exactly why you want :)
Upvotes: 1