通过关系查询 Active Record 以查找 Record,其中 none 个关系的值为真
Active Record query through relationship to find Record where none of its relationships have true for a value
Rails 6.0 与 Postrgres
我有一个关系模型,其中 Employee has_many
Jobs 和每个工作都有一个布尔值 active
。
如果员工拥有的每份工作都是 active: false
,那么该员工将不再受雇。因此,我想查询员工的每份工作都有 active: false
以确定哪些员工不再受雇。
我试过
class Employee < ApplicationRecord
scope :terminated, -> {
includes(:jobs).
where.not(jobs: {
active: true,
})
}
但这是在寻找 任何 工作但 active
不是 true
的员工。我想找到 每个 职位都有 active
false
的员工。 AR 可以原生做到这一点吗?
一种方法是加入作业 table 两次并使用别名:
SELECT "employees".* FROM "employees"
INNER JOIN "jobs"
ON "jobs"."employee_id" = "employees"."id"
INNER JOIN "jobs" "inactive_jobs"
ON "inactive_jobs"."employee_id" = "employees"."id"
AND "inactive_jobs"."active" = 0
GROUP BY "employees"."id"
HAVING COUNT("jobs"."id") = COUNT("inactive_jobs"."id") LIMIT ?
ActiveRecord 并没有真正直接的方法来使用别名进行连接,但是通过一些 Arel 技巧,您可以实现它。
class Employee < ApplicationRecord
has_many :jobs
def self.terminated
jobs = Job.arel_table
# Needed to generate a join with an alias
inactive_jobs = Job.arel_table.alias('inactive_jobs')
# joins all the jobs
joins(
:jobs
)
# joins jobs where active: false
.joins(
self.arel_table.join(
inactive_jobs,
Arel::Nodes::InnerJoin
).on(
inactive_jobs[:employee_id].eq(self.arel_table[:id])
.and(inactive_jobs[:active].eq(false))
).join_sources
)
# groups on employee id
.group(:id)
# Set a condition on the group that the jobs must equal the number of inactive jobs
.having(jobs[:id].count.eq(inactive_jobs[:id].count))
end
end
如果你添加一个反缓存你可以作弊并删除第二个连接:
class Job
belongs_to :employee, counter_cache: true
end
class Employee < ApplicationRecord
has_many :jobs
def self.terminated
joins(:jobs)
.where(jobs: { active: false })
.group(:id)
.having(arel_table[:jobs_count].eq(Job.arel_table[Arel.star].count))
end
end
此处您将缓存值与连接行数进行比较。
您不应将 scope
用于此类查询,因为如果没有匹配的记录,则 scope
returns 该模型的所有记录。
你可以为此写一个class方法:
class Employee < ApplicationRecord
has_many :jobs
def self.terminated
preload(:jobs)
.reject { |employee| employee.jobs.where(active: true).present? }
end
end
class Employee < ApplicationRecord
has_one :jobs
scope :terminated, ->{ joins(:jobs).merge(Job.inactive) }
end
class Job < ApplicationRecord
belongs_to :employee
scope :inactive, -> { where(active: false) }
end
Employee.terminated 应该 return 所有只拥有 active 为 false 的工作的员工。
Rails 6.0 与 Postrgres
我有一个关系模型,其中 Employee has_many
Jobs 和每个工作都有一个布尔值 active
。
如果员工拥有的每份工作都是 active: false
,那么该员工将不再受雇。因此,我想查询员工的每份工作都有 active: false
以确定哪些员工不再受雇。
我试过
class Employee < ApplicationRecord
scope :terminated, -> {
includes(:jobs).
where.not(jobs: {
active: true,
})
}
但这是在寻找 任何 工作但 active
不是 true
的员工。我想找到 每个 职位都有 active
false
的员工。 AR 可以原生做到这一点吗?
一种方法是加入作业 table 两次并使用别名:
SELECT "employees".* FROM "employees"
INNER JOIN "jobs"
ON "jobs"."employee_id" = "employees"."id"
INNER JOIN "jobs" "inactive_jobs"
ON "inactive_jobs"."employee_id" = "employees"."id"
AND "inactive_jobs"."active" = 0
GROUP BY "employees"."id"
HAVING COUNT("jobs"."id") = COUNT("inactive_jobs"."id") LIMIT ?
ActiveRecord 并没有真正直接的方法来使用别名进行连接,但是通过一些 Arel 技巧,您可以实现它。
class Employee < ApplicationRecord
has_many :jobs
def self.terminated
jobs = Job.arel_table
# Needed to generate a join with an alias
inactive_jobs = Job.arel_table.alias('inactive_jobs')
# joins all the jobs
joins(
:jobs
)
# joins jobs where active: false
.joins(
self.arel_table.join(
inactive_jobs,
Arel::Nodes::InnerJoin
).on(
inactive_jobs[:employee_id].eq(self.arel_table[:id])
.and(inactive_jobs[:active].eq(false))
).join_sources
)
# groups on employee id
.group(:id)
# Set a condition on the group that the jobs must equal the number of inactive jobs
.having(jobs[:id].count.eq(inactive_jobs[:id].count))
end
end
如果你添加一个反缓存你可以作弊并删除第二个连接:
class Job
belongs_to :employee, counter_cache: true
end
class Employee < ApplicationRecord
has_many :jobs
def self.terminated
joins(:jobs)
.where(jobs: { active: false })
.group(:id)
.having(arel_table[:jobs_count].eq(Job.arel_table[Arel.star].count))
end
end
此处您将缓存值与连接行数进行比较。
您不应将 scope
用于此类查询,因为如果没有匹配的记录,则 scope
returns 该模型的所有记录。
你可以为此写一个class方法:
class Employee < ApplicationRecord
has_many :jobs
def self.terminated
preload(:jobs)
.reject { |employee| employee.jobs.where(active: true).present? }
end
end
class Employee < ApplicationRecord
has_one :jobs
scope :terminated, ->{ joins(:jobs).merge(Job.inactive) }
end
class Job < ApplicationRecord
belongs_to :employee
scope :inactive, -> { where(active: false) }
end
Employee.terminated 应该 return 所有只拥有 active 为 false 的工作的员工。