我有一个名为Project的模型,而Project有许多任务
任务可以具有3个不同的状态(整数)。
我想获得状态为1、2和3的具有相关任务计数的项目列表。
我能得到的最好的是在Project上有一个方法
def open_tasks
self.tasks.where(:status => 1).count
end
但是这将为每个计数生成另一个SQL,并且在加载100个项目时性能非常差。
是否有一种方法可以在一条SQL语句中实现?
我可以想到几种方法来做到这一点...
(这不是单个sql语句,而是两个,尽管仍然很有效)...Task.where(status: 1).group(:project_id).count
将为您提供一个哈希值,其中键是项目ID,值是任务计数。然后,您可以将其与项目列表结合在一起。
您可以使用ActiveRecord counter_cache在项目记录中保存打开任务数的值。 ActiveRecord将自动为您更新。我相信您需要像这样向项目模型添加关联:
# app/models/project.rb
# needs to include a column called open_task_count
class Project < ActiveRecord::Base
has_many :open_tasks, class_name: Task, -> { where status: 1 }
end
class Task < ActiveRecord::Base
belongs_to :project, counter_cache: true
end
Project.select(
'projects.*',
'(SELECT COUNT(tasks.*) FROM tasks WHERE tasks.project_id = projects.id AND tasks.status = 0) AS status_0_count',
'(SELECT COUNT(tasks.*) FROM tasks WHERE tasks.project_id = projects.id AND tasks.status = 1) AS status_1_count'
).left_joins(:tasks)
尽管有许多更优雅的方式(例如横向联接和CTE),但子查询在大多数DB上起作用。如果状态为ActiveRecord::Enum,则可以通过遍历枚举映射来构造子查询:
class Project < ApplicationRecord
has_many :tasks
def self.with_task_counts
# constucts an array of SQL strings
statuses = Task.statuses.map do |key, int|
sql = Task.select('COUNT(*)')
.where('tasks.project_id = projects.id')
.where(status: key)
.to_sql
"(#{sql}) AS #{key}_tasks_count"
end
select(
'projects.*',
*statuses # * turns the array into a list of args
).left_joins(:tasks)
end
end
在Rails 4中,您仍然可以通过使用SQL字符串进行LEFT OUTER JOIN:
class Project
def self.left_joins_tasks(*args)
deprecator = ActiveSupport::Deprecation.new("5.0", "MyApp")
deprecator.deprecation_warning("left_joins_tasks is deprecated, use `.left_joins(:tasks)` instead")
joins('LEFT OUTER JOIN tasks ON tasks.project_id = projects.id')
end
end
使用.joins
也可以,但是提供了INNER连接,因此可以过滤掉没有任务的行。您也可以使用.includes
。