Rails:如何通过单次高效查询判断各分类是否有成员
最快实现方案(无需计数器缓存)
方案1:LEFT JOIN + GROUP BY 直接查询
这是性能最优的方案之一,全程在数据库层面完成计算,只返回需要的结果,完全避免Ruby层的冗余处理。
对应的原生SQL:
SELECT categories.name, CASE WHEN COUNT(members.id) > 0 THEN TRUE ELSE FALSE END AS has_members FROM categories LEFT JOIN members ON categories.id = members.category_id GROUP BY categories.id, categories.name;
在Rails里用Active Record调用的写法:
Category.select( "categories.name", "CASE WHEN COUNT(members.id) > 0 THEN TRUE ELSE FALSE END AS has_members" ).left_joins(:members).group("categories.id, categories.name")
执行后每个Category实例会自带has_members属性,直接取值就能用。
方案2:EXISTS子查询
如果你的分类表数据量极大,且大部分分类没有关联成员,用EXISTS子查询会更高效——因为它找到第一个匹配的成员就会停止扫描,不用遍历全表。
原生SQL写法:
SELECT categories.name, EXISTS(SELECT 1 FROM members WHERE members.category_id = categories.id) AS has_members FROM categories;
对应的Rails AR写法(两种方式):
方式一:直接写SQL片段
Category.select( :name, Arel.sql("EXISTS(SELECT 1 FROM members WHERE members.category_id = categories.id) AS has_members") )
方式二:用ARel构建,避免硬写SQL
member_table = Member.arel_table exists_subquery = member_table.project(Arel.sql('1')).where(member_table[:category_id].eq(Category.arel_table[:id])).exists Category.select(:name, exists_subquery.as('has_members'))
为什么之前的方案慢?
eager_load和includes会把所有分类关联的成员数据全查出来,再在Ruby里逐个用any?判断。数据量大的时候,不仅数据库返回的结果集超大,Ruby遍历处理也会吃掉大量内存和CPU,自然速度慢。而上面的方案都是让数据库只返回你需要的name和has_members,开销极小。
内容的提问来源于stack exchange,提问作者Kalsan
相关产品推荐
相关产品推荐

