Rake任务中Active Record查询报错?控制台正常的问题排查与解决
这个问题很容易被误导——你一开始以为是select语句的问题,但真正的元凶是对多字段查询的Relation调用count时的SQL生成逻辑差异。
错误原因分析
当你构建了指定多个字段的Active Record Relation:
attendees_with_sms_plans = Attendee.select('attendees.id, attendees.phone, attendees.local_phone').joins(:plans).where('plans.name = ?', "Yes! SMS!")
直接调用count方法时,Active Record会尝试把你select里的所有字段都传给PostgreSQL的COUNT函数,生成的SQL类似:
SELECT COUNT(attendees.id, attendees.phone, attendees.local_phone) FROM ...
但PostgreSQL的COUNT函数只支持单个字段或者*作为参数,没有接受多个参数的重载版本,所以就抛出了PG::UndefinedFunction错误。
至于为什么控制台里看起来正常?大概率是你在控制台测试时,要么没有对这个特定的多字段Relation调用count,要么是无意中先把Relation转换成了数组(比如执行了attendees_with_sms_plans.to_a),此时调用的是Ruby数组的count方法,而非Active Record的数据库层面count。
修复方案
你已经找到的to_a.count是有效的解决方案:
puts <<-TEXT Going to migrate #{attendees_with_sms_plans.to_a.count} attendees from "Yes! SMS!" plan to receive_sms: true attribute on Attendee Model TEXT
to_a会把Active Record Relation转换成内存中的Ruby数组,之后的count是Ruby数组的方法,直接统计数组元素数量,不会生成有问题的SQL。
如果你希望在数据库层面统计(数据量大时更高效,避免把所有数据加载到内存),可以改成只统计单个字段:
# 方案1:指定count的字段 attendees_with_sms_plans.count(:id) # 方案2:重新构建只select id的查询(如果后续不需要phone等字段的话) attendees_with_sms_plans = Attendee.select('attendees.id').joins(:plans).where('plans.name = ?', "Yes! SMS!") attendees_with_sms_plans.count
这两种方式生成的SQL都是SELECT COUNT(attendees.id) FROM ...,符合PostgreSQL的语法要求。
总结
核心区别在于:
- Active Record Relation的
count方法会生成数据库层面的统计SQL - Ruby数组的
count方法是在内存中统计元素数量
当你用select指定多个字段时,一定要注意调用count的方式,避免生成不合法的SQL。
内容的提问来源于stack exchange,提问作者Joel Cahalan

