查找计数大于零的所有记录

JPH*_*rta 91 sql activerecord ruby-on-rails ruby-on-rails-3

我正在尝试做一些我认为很简单的事情,但似乎并不是这样.

我有一个有很多职位空缺的项目模型.

class Project < ActiveRecord::Base

  has_many :vacancies, :dependent => :destroy

end
Run Code Online (Sandbox Code Playgroud)

我想得到所有至少有1个空缺的项目.我试过这样的事情:

Project.joins(:vacancies).where('count(vacancies) > 0')
Run Code Online (Sandbox Code Playgroud)

但它说

SQLite3::SQLException: no such column: vacancies: SELECT "projects".* FROM "projects" INNER JOIN "vacancies" ON "vacancies"."project_id" = "projects"."id" WHERE ("projects"."deleted_at" IS NULL) AND (count(vacancies) > 0).

Art*_*rta 154

1)获得至少有1个空缺的项目:

Project.joins(:vacancies).group('projects.id')
Run Code Online (Sandbox Code Playgroud)

2)获得超过1个空缺的项目:

Project.joins(:vacancies).group('projects.id').having('count(project_id) > 1')
Run Code Online (Sandbox Code Playgroud)

3)或者,如果Vacancy模型设置计数器缓存:

belongs_to :project, counter_cache: true
Run Code Online (Sandbox Code Playgroud)

那么这也会奏效:

Project.where('vacancies_count > ?', 1)
Run Code Online (Sandbox Code Playgroud)

vacancy可能需要手动指定变形规则?

  • 不,@KeithMattix,**不应该**不是。然而,如果它对你来说更好读的话,它**可以**;这是一个偏好问题。可以使用连接表中保证每行都有值的任何字段来完成计数。最有意义的候选者是“projects.id”、“project_id”和“vacancies.id”。我选择计算“project_id”,因为它是进行连接的字段;如果愿意的话,可以是连接的脊柱。它还提醒我这是一个连接表。 (3认同)
  • 难道不是`Project.joins(:vacancies).group('projects.id')。having('count(vacancies.id)&gt; 1')`吗?查询空缺数量而不是项目ID (2认同)

jvn*_*ill 57

joins默认情况下使用内连接,因此使用Project.joins(:vacancies)will实际上只返回具有关联空位的项目.

更新:

正如@mackskatz在评论中指出的那样,如果没有group条款,上面的代码将为具有多个空缺的项目返回重复项目.要删除重复项,请使用

Project.joins(:vacancies).group('projects.id')
Run Code Online (Sandbox Code Playgroud)


Pet*_*vin 31

是的,vacancies不是加入中的一个字段.我相信你想:

Project.joins(:vacancies).group("projects.id").having("count(vacancies.id)>0")
Run Code Online (Sandbox Code Playgroud)


Dor*_*ian 15

# None
Project.joins(:vacancies).group('projects.id').having('count(vacancies) = 0')
# Any
Project.joins(:vacancies).group('projects.id').having('count(vacancies) > 0')
# One
Project.joins(:vacancies).group('projects.id').having('count(vacancies) = 1')
# More than 1
Project.joins(:vacancies).group('projects.id').having('count(vacancies) > 1')
Run Code Online (Sandbox Code Playgroud)


Dav*_*dge 8

对has_many 表执行内部连接并结合a grouporuniq可能非常低效,并且在SQL 中这将更好地实现为EXISTS与相关子查询一起使用的半连接。

这允许查询优化器探测空缺表以检查是否存在具有正确 project_id 的行。无论是一行还是一百万具有该 project_id 都没有关系。

这在 Rails 中没有那么简单,但可以通过以下方式实现:

Project.where(Vacancies.where("vacancies.project_id = projects.id").exists)
Run Code Online (Sandbox Code Playgroud)

同样,找到所有没有空缺的项目:

Project.where.not(Vacancies.where("vacancies.project_id = projects.id").exists)
Run Code Online (Sandbox Code Playgroud)

编辑:在最近的 Rails 版本中,您会收到一个弃用警告,告诉您不要依赖exists被委派给 arel。解决这个问题:

Project.where.not(Vacancies.where("vacancies.project_id = projects.id").arel.exists)
Run Code Online (Sandbox Code Playgroud)

编辑:如果您对原始 SQL 感到不舒服,请尝试:

Project.where.not(Vacancies.where(Vacancy.arel_table[:project_id].eq(Project.arel_table[:id])).arel.exists)
Run Code Online (Sandbox Code Playgroud)

您可以通过添加类方法来隐藏 的使用,从而减少混乱arel_table,例如:

class Project
  def self.id_column
    arel_table[:id]
  end
end
Run Code Online (Sandbox Code Playgroud)

... 所以 ...

Project.where.not(
  Vacancies.where(
    Vacancy.project_id_column.eq(Project.id_column)
  ).arel.exists
)
Run Code Online (Sandbox Code Playgroud)


kon*_*yak 5

在Rails 4+中,您还可以使用includeseager_load来获得相同的答案:

Project.includes(:vacancies).references(:vacancies).
        where.not(vacancies: {id: nil})

Project.eager_load(:vacancies).where.not(vacancies: {id: nil})
Run Code Online (Sandbox Code Playgroud)


Yur*_*ich 5

我认为有一个更简单的解决方案:

Project.joins(:vacancies).distinct
Run Code Online (Sandbox Code Playgroud)