如何编写查询同时包含Ruby与HTML语言的多对多ActiveRecord查询语句?
解决同时关联Ruby和HTML的项目查询问题
你遇到的问题很典型——第一个查询之所以返回空数组,是因为你试图让同一条language记录同时满足name = 'Ruby'和name = 'HTML',这显然不可能嘛!咱们换个思路,要找的是同时关联了两种语言的项目,得从“项目关联的语言集合包含这两个”这个角度入手。
下面给你几个更简洁的实现方式:
方式1:使用分组 + HAVING(推荐)
通过分组统计每个项目关联的目标语言数量,筛选出数量等于2的项目:
Project.joins(:languages) .where(languages: { name: ['Ruby', 'HTML'] }) .group('projects.id') .having('COUNT(DISTINCT languages.id) = 2')
这个方法效率不错,只需要一次查询就能搞定,而且逻辑清晰——先筛选出关联了Ruby或HTML的项目,再分组后只保留同时关联了两种的(因为COUNT的结果是2)。
方式2:使用多个JOIN
通过两次关联project_languages和languages,分别匹配不同的语言:
Project.joins(project_languages: :language) .joins('INNER JOIN project_languages pl2 ON pl2.project_id = projects.id') .joins('INNER JOIN languages l2 ON l2.id = pl2.language_id') .where(languages: { name: 'Ruby' }, l2: { name: 'HTML' }) .distinct
这里注意要加distinct避免重复的项目结果,因为两次join可能会让同一个项目出现多次。
方式3:使用EXISTS子查询
用两个EXISTS子查询分别检查项目是否关联了Ruby和HTML:
Project.where( 'EXISTS(SELECT 1 FROM project_languages pl1 JOIN languages l1 ON pl1.language_id = l1.id WHERE pl1.project_id = projects.id AND l1.name = ?)', 'Ruby' ).where( 'EXISTS(SELECT 1 FROM project_languages pl2 JOIN languages l2 ON pl2.language_id = l2.id WHERE pl2.project_id = projects.id AND l2.name = ?)', 'HTML' )
这种写法语义非常直观,就是明确要求项目同时满足两个存在条件,对于复杂的多条件筛选很有用。
你之前用&的方式虽然能得到结果,但本质上是执行了两次查询然后在内存中取交集,当数据量较大时性能会不如上面这些单查询的方法。
内容的提问来源于stack exchange,提问作者yeniv
相关产品推荐
相关产品推荐

