【问题标题】:Rails Active Record query: answered questions, ordered by answer date, not duplicatesRails Active Record 查询:已回答的问题,按回答日期排序,不重复
【发布时间】:2011-05-17 02:45:34
【问题描述】:

一个问题有很多答案。

很容易找到所有已回答的问题并按最新答案排序:

def self.answered
  joins(:answers).order('answers.created_at desc')
end

在控制器中,我会做 @answered = Question.answered

但是如果一个问题被回答了不止一次,这会返回重复的记录。

另一方面,很容易找到已回答的不同问题,但前提是我不尝试按其答案' created_at 日期对它们进行排序:

def self.answered
  joins(:answers).select("DISTINCT questions.title")
end

(我们在这里假设问题标题被验证为唯一,因此这会产生所有唯一记录)。

问题:

此查询不能按答案的“created_at”日期排序,因为我在 SQL 选择语句中只选择了问题的标题...

def self.answered
  joins(:answers).select("DISTINCT questions.title").order('answers.created_at desc')
end

导致此错误(我正在使用 Postgres):

PGError: ERROR:  for SELECT DISTINCT, ORDER BY expressions must appear in select list

我猜,这意味着它想在 select DISTINCT 语句中看到“answers.created_at”。

如果我想通过 q​​uestions.created_at 进行排序,以下内容似乎可行,但这不是我想要的:

def self.answered
  joins(:answers).select("DISTINCT(questions.title), questions.created_at").order('questions.created_at desc')
end

这就是我的基本 SQL 知识不足之处。如何选择不同的问题(只有那些有答案的问题)并按最新答案的 created_at 日期降序排列?

我想使用 Rails 3 Active Record 查询来编写它,但直接使用 SQL 就可以了。我希望有人可以提供帮助。

【问题讨论】:

    标签: sql ruby-on-rails activerecord join distinct


    【解决方案1】:

    如果我们谈论 MySQL,@Anton 的答案将是首选解决方案,但正如您所指出的,它会导致 PostgreSQL 中的错误,因为 PG 在其选择/分组功能方面更具限制性。

    这是我如何让它与 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
    

    或者,如果您想为问题选择整个 ActiveRecord 行,您可以按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
    

    【讨论】:

      【解决方案2】:

      为什么不使用 write as:

      def self.answered
        joins(:answers).order("answers.created_at DESC").group("questions.id")
      end
      

      这正是你想要的:)

      【讨论】:

      • 请注意:您可以将其拉入一个范围,以便您也可以将其用于一系列问题。
      • 我不明白为什么,但是 postgres 抱怨:PGError:错误:列“questions.title”必须出现在 GROUP BY 子句中或用于聚合函数第 1 行:选择“问题” .* FROM "questions" INNER JOIN ... ^ : SELECT "questions".* FROM "questions" INNER JOIN "answers" ON "answers"."question_id" = "questions"."id" GROUP BY questions.id ORDER BY answers.created_at DESC
      • 如果我将 questions.id 更改为 questions.title,它会抱怨相同的消息,但会询问 questions.id。无论我在视图中要求什么属性,它都会抱怨。即,在下面,q.title 可以是任何东西:
      • @SteveCotner 在尝试使用 PG 的上述解决方案时,我遇到了与您完全相同的问题。请参阅我的solution below,了解我是如何让它工作的。
      【解决方案3】:

      我可以为它编写 SQL 解决方案。它会如下所示:

      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
      

      希望对你有帮助!

      【讨论】:

        【解决方案4】:

        这最终可能会更加高效和可扩展,并且应该避免 psql 抱怨聚合函数的问题:

        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
        

        附:我的意思是可扩展的,您可以将其拆分为 Arel 结构,然后分解出诸如 with_answers 之类的作用域,并回答在幕后完成您的脏活,同时保持其余部分清洁并允许您稍后添加更多作用域。 :)

        更新:只是发布 Arel 方式:

        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')
        

        【讨论】:

        • 别在意最后一条评论;我误读了。但是,如果我这样写,它会抱怨“answer_count”列不存在: scope :answered, find_by_sql("select questions.*, count(answers.id) as answer_count from questions join answers on answers.question_id=questions .id 按answers.question_id 分组, answer_count > 0 order by answers.created_at desc")
        • 只是想知道:这一切是否只是一些 PostgreSQL 的小玩意儿,我不必使用 MySQL 来处理?
        • 到目前为止,我最好的折衷方案是让模型变得简单:scope :answered, joins(:answers).order('answers.created_at desc') 并使控制器有点限制:@ just_answered = Question.answered.uniq[0..9] 当然,我不喜欢这样,因为这意味着我不能再将作用域链接到 @just_answered
        • 您可以尝试不选择计数作为列,而只需使用 (count(answers.id)) > 0
        • 我正在使用 MySQL,这对我有用: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')
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-05-28
        • 1970-01-01
        相关资源
        最近更新 更多