查找关联计数大于零的所有记录
Posted
技术标签:
【中文标题】查找关联计数大于零的所有记录【英文标题】:Find all records which have a count of an association greater than zero 【发布时间】:2013-12-09 15:02:09 【问题描述】:我正在尝试做一些我认为很简单但似乎并非如此的事情。
我的项目模型有很多空缺。
class Project < ActiveRecord::Base
has_many :vacancies, :dependent => :destroy
end
我想获得至少有 1 个空缺的所有项目。 我尝试过这样的事情:
Project.joins(:vacancies).where('count(vacancies) > 0')
但它说
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)
.
【问题讨论】:
【参考方案1】:是的,vacancies
不是联接中的字段。我相信你想要:
Project.joins(:vacancies).group("projects.id").having("count(vacancies.id)>0")
【讨论】:
【参考方案2】:joins
默认使用内部连接,因此使用Project.joins(:vacancies)
实际上只会返回具有相关空缺的项目。
更新:
正如@mackskatz 在评论中指出的那样,如果没有group
子句,上面的代码将返回具有多个职位空缺的项目的重复项目。要删除重复项,请使用
Project.joins(:vacancies).group('projects.id')
更新:
正如@Tolsee 所指出的,您也可以使用distinct
。
Project.joins(:vacancies).distinct
举个例子
[10] pry(main)> Comment.distinct.pluck :article_id
=> [43, 34, 45, 55, 17, 19, 1, 3, 4, 18, 44, 5, 13, 22, 16, 6, 53]
[11] pry(main)> _.size
=> 17
[12] pry(main)> Article.joins(:comments).size
=> 45
[13] pry(main)> Article.joins(:comments).distinct.size
=> 17
[14] pry(main)> Article.joins(:comments).distinct.to_sql
=> "SELECT DISTINCT \"articles\".* FROM \"articles\" INNER JOIN \"comments\" ON \"comments\".\"article_id\" = \"articles\".\"id\""
【讨论】:
但是,如果不应用 group by 子句,这将为具有多个空缺的项目返回多个项目对象。 虽然不能生成高效的 SQL 语句。 这就是适合你的 Rails。如果您可以提供一个 sql 答案(并解释为什么这样做效率不高),那可能会更有帮助。 你觉得Project.joins(:vacancies).distinct
怎么样?
这是@Tolsee btw :D【参考方案3】:
错误告诉您,职位空缺基本上不是项目中的一列。
这应该可以工作
Project.joins(:vacancies).where('COUNT(vacancies.project_id) > 0')
【讨论】:
aggregate functions are not allowed in WHERE
【参考方案4】:
1) 获得至少有 1 个空缺的项目:
Project.joins(:vacancies).group('projects.id')
2) 获得超过 1 个职位空缺的项目:
Project.joins(:vacancies).group('projects.id').having('count(project_id) > 1')
3) 或者,如果Vacancy
模型设置了计数器缓存:
belongs_to :project, counter_cache: true
那么这也可以:
Project.where('vacancies_count > ?', 1)
vacancy
的变形规则可能需要为specified manually?
【讨论】:
不应该是Project.joins(:vacancies).group('projects.id').having('count(vacancies.id) > 1')
吗?查询空缺数量而不是项目ID
不,@KeithMattix,它不应该。但是,它可能是,如果它对您来说更好;这是一个偏好问题。可以使用连接表中保证在每一行中都有一个值的任何字段来完成计数。最有意义的候选人是projects.id
、project_id
和vacancies.id
。我选择计算project_id
,因为它是进行连接的字段;如果你愿意的话,连接的脊椎。它也提醒我这是一个连接表。【参考方案5】:
# 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')
【讨论】:
【参考方案6】:没有太多 Rails 魔法,你可以做到:
Project.where('(SELECT COUNT(*) FROM vacancies WHERE vacancies.project_id = projects.id) > 0')
这种类型的条件适用于所有 Rails 版本,因为大部分工作都直接在 DB 端完成。另外,链接.count
方法也可以很好地工作。我之前被Project.joins(:vacancies)
之类的查询所困扰。当然,有优点也有缺点,因为它与数据库无关。
【讨论】:
这比 join 和 group 方法慢得多,因为 'select count(*)..' 子查询将为每个项目执行。 @YasirAzgar join and group 方法比“exists”方法慢,因为它仍然会访问所有子行,即使有一百万个。【参考方案7】:在 Rails 4+ 中,您还可以使用 includes 或 eager_load 来获得相同的答案:
Project.includes(:vacancies).references(:vacancies).
where.not(vacancies: id: nil)
Project.eager_load(:vacancies).where.not(vacancies: id: nil)
【讨论】:
【参考方案8】:我认为有一个更简单的解决方案:
Project.joins(:vacancies).distinct
【讨论】:
也可以使用“distinct”,例如Project.joins(:vacancies).distinct 你是对的!最好使用#distinct 而不是#uniq。 #uniq 会将所有对象加载到内存中,但 #distinct 将在数据库端进行计算。【参考方案9】:结合group
或uniq
对has_many 表执行内部联接可能效率很低,在SQL 中这将更好地实现为使用EXISTS
和相关子查询的半联接。
这允许查询优化器探测空缺表以检查是否存在具有正确 project_id 的行。是否有一行或一百万具有该 project_id 并不重要。
这在 Rails 中并不那么简单,但可以通过以下方式实现:
Project.where(Vacancies.where("vacancies.project_id = projects.id").exists)
同样,查找所有没有空缺的项目:
Project.where.not(Vacancies.where("vacancies.project_id = projects.id").exists)
编辑:在最近的 Rails 版本中,您会收到一个弃用警告,告诉您不要依赖 exists
被委派给 arel。解决这个问题:
Project.where.not(Vacancies.where("vacancies.project_id = projects.id").arel.exists)
编辑:如果您对原始 SQL 不满意,请尝试:
Project.where.not(Vacancies.where(Vacancy.arel_table[:project_id].eq(Project.arel_table[:id])).arel.exists)
你可以通过添加类方法来隐藏arel_table
的使用来减少混乱,例如:
class Project
def self.id_column
arel_table[:id]
end
end
...所以...
Project.where.not(
Vacancies.where(
Vacancy.project_id_column.eq(Project.id_column)
).arel.exists
)
【讨论】:
这两个建议似乎不起作用...子查询Vacancy.where("vacancies.project_id = projects.id").exists?
产生true
或false
。 Project.where(true)
是 ArgumentError
。
Vacancy.where("vacancies.project_id = projects.id").exists?
不会执行——它会引发错误,因为查询中不存在projects
关系(并且上面的示例代码中也没有问号)。因此,将其分解为两个表达式是无效的并且不起作用。最近 Rails Project.where(Vacancies.where("vacancies.project_id = projects.id").exists)
提出了弃用警告......我会更新这个问题。【参考方案10】:
您还可以将EXISTS
与SELECT 1
一起使用,而不是从vacancies
表中选择所有列:
Project.where("EXISTS(SELECT 1 from vacancies where projects.id = vacancies.project_id)")
【讨论】:
以上是关于查找关联计数大于零的所有记录的主要内容,如果未能解决你的问题,请参考以下文章