【问题标题】:Complex Rails Query复杂的 Rails 查询
【发布时间】:2013-05-22 14:01:09
【问题描述】:

我的模型结构如下:

  1. 用户通过 ResponseSets 进行了许多调查(用户可以通过为每次尝试创建 ResponseSets 多次参加同一个调查)。
  2. ResponseSet 有很多响应
  3. Response 属于 Questions、Answers 和 ResponseSets。

我需要一个 Rails 查询来查找:

  1. 有多少用户在他们最后的 ResponseSet(意味着最后一次尝试)中为与答案对应的给定调查选择了给定答案。

  2. 有多少用户在他们最后的 ResponseSet(意味着最后一次尝试)中为与问题对应的给定调查选择了给定问题。

【问题讨论】:

  • response_set has_one user ?
  • 更好,response_set belongs_to user ?
  • 我已经解决了一个非常相似的问题,期待您得到一个很好的答案。问题不在于提出查询,而是获得合理的效率。获取每个用户和每个问题的最后一个响应需要进行索引扫描,无论您如何对其进行切片。我想你必须做我所做的:使用回调来维护一个LastResponseSetByUser 表,这样一个简单的连接就可以得到你需要的东西。如果您没有得到更好的结果,我将提供详细信息。
  • 您将使用什么属性来确定“最后一个”响应集 - created_atupdated_atid
  • @Gene - 如果可以,请。从早上开始真的很绝望......

标签: ruby-on-rails postgresql activerecord ruby-on-rails-3.2


【解决方案1】:

由于您使用的是 PostgreSQL,我建议在子查询中使用窗口函数来简化逻辑。子查询将收集给定查询的所有 response_set。外部查询过滤到该调查的每个用户的最后一个 ResponseSet(基于窗口函数生成的 rowNum),并确保它包含与给定答案相关的响应:

select_sql <<-SELECT
  response_sets.*,
  ROW_NUMBER() OVER (PARTITION BY response_sets.user_id ORDER BY response_sets.id DESC) as rowNum
SELECT

subquery = survey.response_sets.select(select_sql).to_sql

ResponseSet.joins(:responses).from(Arel.sql("(#{subquery}) response_sets")).
            where(responses: {answer_id: answer.id}).
            where("rowNum = 1").count

请注意,这假定给定答案每个 ResponseSet 只能使用一次。如果不是这种情况,您可以通过将.count 替换为.count(:distinct =&gt; :user_id) 来进一步完善它

【讨论】:

  • 感谢您的帮助。一个给定的答案可能在许多响应集中出现重复,因为用户可以多次参加调查并始终给出相同的答案。但是在 response_set 中,用户只能使用一次答案。您能否在不编辑此条件的情况下也添加其他条件。
  • 如果单个 ResponseSet 只能与一个答案关联一次,那么这应该适合您。仅包含每个用户的“最新”响应集,然后每个匹配答案的一条记录包含在计数中。更新以演示如何执行 COUNT(DISTINCT)。
  • 谢谢你会试一试,让你知道。非常感谢!
  • 会像魅力一样工作,但是当 ResponseSets 表变大时,请仔细观察性能......
【解决方案2】:

正如我在评论中提到的,您需要一个子查询或 Postgresql WITH 子句来查找最新响应,如果有许多响应集,即使使用索引也会变得相当昂贵。

另一方面,如果您有一个仅包含最新响应的表,则可以使用直接嵌套连接。如果我正确理解了您的架构,那么这些很接近:

User.count(:joins => { :survey => {:latest_response_set => { :response => :answer }}}, 
           :conditions => ['answers.id = ?', answer_id])

User.count(:joins => { :survey => {:latest_response_set => { :response => :question}}}, 
           :conditions => ['questions.id = ? and surveys.id = ?', question_id, survey_id])

您可以使用after_save 回调更新最新的响应表。这是相对安全的,因为回调被包装在事务中。

【讨论】:

  • 非常感谢。我一定会试一试,然后回复你。欣赏帮助的心态:)
  • 回调是相对安全的——但并非完全如此。在并发请求到来之前,它们运行良好,这可能比您想象的更常见。根据输入数据的方式,这对您来说可能不是问题。
  • @PinnyM 你能解释一下它们是如何不安全的吗?通过包装在 ACID 事务中(由 Rails 规范保证),保存(INSERT 或 UPDATE)和回调中的所有 SQL 都必须自动完成,否则会发生回滚并且没有完成。不安全会发生在哪里?
  • 插入 response_sets 后,两个事务之间可能会出现竞争条件,即哪个请求将最后更新“latest_response_set_id”缓存。无论该字段最后的哪个更新都将长期存在,无论该 response_set 是否实际上是“最新的”(此实现中的更高 id)。这是通过应用程序级回调维护缓存的固有限制 - 不是特定于 ActiveRecord。有一些解决方案,但无论它们如何实施,它们都会带来某种形式的混乱。
  • @PinnyM 这对我来说没有意义,好像它是真的一样,同时发生两个简单的保存也会发生同样的情况。在保存期间,id 缓存更新必须是带有插入的单个事务,不是吗?谢谢。
猜你喜欢
  • 2017-12-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多