如何用单条Rails查询获取关联指定模型列表的全部Constraint?
解决多态关联下的全包含Constraint查询问题
这个场景我之前也碰到过,多态关联加上这种「所有关联项都属于指定集合」的查询,确实没法直接用普通的where搞定。你原来的实现虽然能得到结果,但每个Constraint都要查一遍它的Vars,数据量大的时候会有严重的N+1问题,用单条SQL查询完全可以解决。
核心思路
我们需要筛选出所有关联的Var都属于给定模型列表的Constraint,换句话说:
- Constraint不能存在任何一个Var不在目标模型集合里
- 同时(可选)确保Constraint至少有一个Var关联(如果允许空Var的Constraint可以跳过这一步)
实现代码
首先把传入的models转换成model_type和model_id的配对集合,然后用SQL的NOT EXISTS或者分组统计来实现:
方法一:用NOT EXISTS排除不符合条件的Constraint
这种方式性能通常更好,因为数据库可以快速排除那些有违规Var的Constraint:
def constraints_for_models_list(models) # 把模型转换成(model_type, model_id)的配对数组 model_entries = models.map { |model| [model.class.name, model.id] } target_types = model_entries.map(&:first) target_ids = model_entries.map(&:second) Constraint.where(active: true) # 排除掉存在至少一个Var不在目标模型列表里的Constraint .where.not( id: CnsVar.joins(:var) .where.not(vars: { model_type: target_types, model_id: target_ids }) .distinct .select(:constraint_id) ) # 确保Constraint至少有一个Var在目标列表里(如果允许无Var的Constraint可去掉这部分) .joins(:vars) .where(vars: { model_type: target_types, model_id: target_ids }) .distinct end
方法二:用分组统计验证全包含
通过对比Constraint的总Var数和它在目标列表里的Var数是否相等,来判断是否所有Var都符合条件:
def constraints_for_models_list(models) model_entries = models.map { |model| [model.class.name, model.id] } target_types = model_entries.map(&:first) target_ids = model_entries.map(&:second) Constraint.joins(:vars) .where(active: true) .where(vars: { model_type: target_types, model_id: target_ids }) .group('constraints.id') .having( 'COUNT(DISTINCT vars.id) = ( SELECT COUNT(DISTINCT vars.id) FROM vars JOIN cns_vars ON vars.id = cns_vars.var_id WHERE cns_vars.constraint_id = constraints.id )' ) end
代码解释
- 方法一:先通过子查询找出所有「存在违规Var」的Constraint ID,然后在主查询中排除这些ID,剩下的就是所有Var都符合条件的Constraint。最后加上
joins和where是为了过滤掉没有任何Var关联的Constraint(如果你的业务允许空Var的Constraint,可以去掉这部分)。 - 方法二:先筛选出至少有一个Var在目标列表里的Constraint,然后分组后对比该Constraint的总Var数和目标列表内的Var数,只有当两者相等时,说明所有Var都在目标列表里。
对比原实现的优势
- 从原来的O(N)次查询变成1次查询,性能提升明显,尤其是当Constraint和Var数量较多时
- 利用数据库的查询优化器,比Ruby端的内存过滤效率更高
- 代码更简洁,符合Rails的ActiveRecord查询风格
内容的提问来源于stack exchange,提问作者LiKaZ
相关产品推荐
相关产品推荐

