【问题标题】:ActiveRecord: sort of complicated query with nested associations and sortingActiveRecord:具有嵌套关联和排序的复杂查询
【发布时间】:2017-07-07 13:50:11
【问题描述】:

好吧,所以我不确定这是否不太具体......但我真的不知道如何构建这样的查询,无论是在 AR 中还是在 SQL 中。所以情况是这样的:

  • 我有一个User 模型。 User has_many Projects。反过来,Project 具有以下关联:
  • Project has_one BasicEvent
  • Projecthad_many AdditionalEvents
  • BasicEventAdditionalEvent 类建立在继承自 AR 模型类 Event 的基础上

现在,目标是:在我看来,我需要:

  • 访问所有Projects 计数每个User
  • 访问所有Events 个计数,其中happened_at 属性位于nil
  • Events 的计数对所有Users 进行排序,其中happened_at 属性位于nil

到目前为止,我已经进行了几次尝试,但并没有走得太远......我将不胜感激对于这个复杂的(至少从我的角度来看)查询的任何帮助。

【问题讨论】:

  • 你能详细说明你想要什么吗?我对每个用户和其他人的计数有点困惑
  • 不确定,但我会尝试。我想知道Projects 和User 的数量。并且每个User 也计算Events(但这两者之间没有直接关联,它通过Project 发生)。最后,我希望所有的Users 都按Events 的计数排序。我希望这会有所帮助。
  • 项目和事件之间有关联吗????并且应该有来自用户的关联,以有许多事件彻底的项目。来源是事件
  • ProjectEvent 之间存在关联,但如上所述,Event 会被进一步细分。不幸的是,UserEvent 之间根本没有任何关联。
  • 你有没有研究过counter_cache 在rails 上的使用。 This blog 涵盖了如何设置的基础知识。

标签: sql ruby-on-rails activerecord


【解决方案1】:

您可以从以下代码开始:

# -*- frozen-string-literal: true -*-
class UserStatService
  SELECT_CLAUSE = <<~SQL
    (SELECT count(1) FROM projects WHERE user_id=users.id) AS projects_count,
    (SELECT count(1) FROM events WHERE (happened_at IS NULL)
      AND (project_id IN (SELECT id FROM projects WHERE user_id=users.id)
    ) AS events_count,
    users.*
  SQL

  def call
    User
      .select( SELECT_CLAUSE )
      .order( 'events_count' )
  end
end

结果集中的每个User 实例都将具有projects_countevents_count 属性。

还建议使用任何类型的缓存(counter_cache 或手工制作)。因为预计查询不会很快。

【讨论】:

    【解决方案2】:

    每个用户的项目数:Project.group(:user_id).count

    事件计数,其中nil 中的happened_at 属性:Event.where(happened_at: nil).count

    用户按事件计数排序,其中nil 中的happened_at 属性:这是最棘手的,但也不是特别困难:

    User.
      select("users.*, (SELECT COUNT(*) FROM events WHERE user_id = users.id AND happened_at IS NULL) AS unhappened_events_count").
      order("unhappened_events_count DESC")
    

    这不是一个特别有效的查询,但它应该可以很好地处理几千条记录(很容易)——尤其是如果您在events 表中的user_idhappened_at 上设置索引。

    【讨论】:

      【解决方案3】:

      假设basic_eventsadditional_events 具有基本相同的列,解决此问题的一个很好的方法是将您的数据库/应用程序更改为使用single table inheritance

      假设您使用的是 Rails 5.x,您的模型将如下所示:

      class User < ApplicationRecord
        has_many :projects
        has_many :basic_events, through: :projects
        has_many :additional_events, through: :projects
      end
      
      class Project < ApplicationRecord
        belongs_to :user
        has_one :basic_event
        has_many :additional_events
      end
      
      class Event < ApplicationRecord
        belongs_to :project
      end
      
      class BasicEvent < Event
        belongs_to :project
      end
      
      class AdditionalEvent < Event
        belongs_to :project
      end
      

      这样,只涉及到三个表usersprojectsevents。您的events 表将有一个:type 列,您可以在其中指定它是basic 还是additional


      对于您的查询:

      每个用户访问所有项目的次数

      User
        .select("users.*, count(projects.id) AS projects_count")
        .joins(:projects)
        .group('users.id')
        .order('projects_count DESC')
      

      像这样使用 select 可以让您访问此活动记录关系中返回的每个 User 对象的 :projects_count 方法,因此如果您将此查询分配给名为 users_with_projects_count 的变量,您可以执行 users_with_projects_count.first.projects_count 并且它会返回与该用户关联的项目数。

      访问所有事件计数,其中发生的属性在 nil 中

      User
        .select("users.*, count(events.id) AS events_count")
        .joins(:events)
        .where('events.happened_at IS NULL')
        .group('users.id')
      

      您可以像上一个示例中的:projects_count 一样访问:events_count

      按事件计数对所有用户进行排序,其中发生的属性为 nil

      User
        .select("users.*, count(events.id) AS events_count")
        .joins(:events)
        .where('events.happened_at IS NULL')
        .group('users.id')
        .order('events_count DESC')
      

      您使用与上一个示例相同的查询,只需添加一个:order

      【讨论】:

        猜你喜欢
        • 2012-07-26
        • 2011-10-25
        • 2016-07-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多