【问题标题】:Rails 5.1 (postgresql): efficient DB query to select parent records and filtered children recordsRails 5.1 (postgresql):高效的数据库查询来选择父记录和过滤子记录
【发布时间】:2018-02-22 21:17:17
【问题描述】:

我正在开发一个任务管理系统,其中project 有许多taskstask 可以分配给users

我想添加一个“我的任务”页面,在下面显示所有项目以及当前用户的任务。

像这样,每个任务都分配给current_user

Project #1
- task 1
- task 2
Project #2
- task 1
- task 2
Project #3
- task 1
- task 2
- task 3

我想用伪 ActiveRecord 代码实现的目标:

@projects_with_tasks = current_user.projects.includes(:tasks).where(tasks: { user_id: current_user.id })

然后我想遍历每个项目,列出分配给 current_user 的任务:

<% @projects.each do |project| %>
  <%= project.title %>
  <ul>
    <% project.tasks.each do |task| %>
      <li><%= task.title %> - <%= task.due_date %></li>
    <% end %>
  </ul>
<% end %>

看起来很简单,但是当我调用 project.tasks 时,它会返回并加载项目的所有任务,而不仅仅是 current_user 的任务。

有没有办法有效地获取项目和过滤的任务列表?

我目前最好的解决方案是先获取所有项目,然后对其进行迭代,并进行单独的数据库查询以检索所有过滤后的任务。但是,如果有人参与了 20 多个项目(可能在我的用例中),那么这就是 21 多个查询(1 个用于所有项目,1 个用于任务)。不要介意某些用户将拥有 50 个项目的情况......

我更喜欢将所有内容都保存在 ActiveRecord 中,但我也知道这可能是创建带有一些 SQL 的查询对象的情况。

【问题讨论】:

    标签: sql postgresql optimization ruby-on-rails-5


    【解决方案1】:

    如果你的模型是这样定义的:

    # app/models/user.rb
    class User < ActiveRecord::Base
      has_many :tasks
    end
    
    # app/models/task.rb
    class Task < ActiveRecord::Base
      belongs_to :project
      belongs_to :user
    end
    
    # app/models/project.rb
    class Project < ActiveRecord::Base
      has_many :tasks
    end
    

    您可以使用此查询查询具有过滤任务的项目:

    @projects = Project.includes(:tasks).joins(:tasks).where(Task.table_name => { user_id: current_user.id })
    

    .includes(:tasks) - 急切地加载任务

    .joins(:tasks) - 执行 INNER JOIN 而不是 LEFT OUTER JOIN

    .where(Task.table_name =&gt; { user_id: current_user.id }) - 按用户过滤任务

    您还可以为您的 Task 模型添加范围:

    scope :for_user, ->(user) { where(user: user) }
    

    之后,查询可以这样写:

    @projects = Project.includes(:tasks).joins(:tasks).merge(Task.for_user(current_user))
    

    输出将是相同的。

    注意:

    我猜你不会这样做,但仍然值得一提的是,在迭代加载了上述任何查询的项目时,你应该避免调用 project.tasks.reload。它将强制重新加载tasks 关联而不由用户过滤。

    【讨论】:

      【解决方案2】:

      我假设您的 User.rb 文件中有 has_many :tasks 之类的内容,Task.rb 中有 belongs_to :project 之类的内容

      然后您可以简单地获取current_user.tasks.includes(:project),然后从那里获取唯一的项目列表。

      【讨论】:

        猜你喜欢
        • 2021-06-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-08-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多