到目前为止,从数据库获取随机记录的"common"方法是:
# Postgress
Model.order("RANDOM()").first
# MySQL
Model.order("RAND()").first
但是,在Rails 5.2中执行此操作时,它会显示以下弃用警告:
DEPRECATION WARNING:使用非属性参数调用的危险查询方法(其参数用作原始SQL的方法):“RANDOM()”。 Rails 6.0中不允许使用非属性参数。不应使用用户提供的值调用此方法,例如请求参数或模型属性。可以通过将它们包装在Arel.sql()中来传递已知安全值。
我对Arel并不熟悉,所以我不确定解决这个问题的正确方法是什么。
答案 0 :(得分:31)
如果您想继续使用order by random()
,只需将其包含在Arel.sql
中,就像弃用警告提示一样,将其声明为安全:
Model.order(Arel.sql('random()')).first
有很多方法可以选择随机行,它们都有优点和缺点,但有些时候你绝对必须在order by
中使用一段SQL(例如当你需要时the order to match a Ruby array并且必须得到一个大的case when ... end
表达式到数据库)所以使用Arel.sql
来解决这个"属性"限制是我们都需要了解的工具。
已编辑:示例代码缺少右括号。
答案 1 :(得分:4)
我是这个解决方案的粉丝:
Model.offset(rand(Model.count)).first
答案 2 :(得分:2)
有许多记录,而且删除的记录不多,这可能更有效。在我的情况下,我必须使用.unscoped
,因为默认范围使用连接。如果您的模型没有使用此类默认范围,则可以在出现的任何位置省略.unscoped
。
Patient.unscoped.count #=> 134049
class Patient
def self.random
return nil unless Patient.unscoped.any?
until @patient do
@patient = Patient.unscoped.find rand(Patient.unscoped.last.id)
end
@patient
end
end
#Compare with other solutions offered here in my use case
puts Benchmark.measure{10.times{Patient.unscoped.order(Arel.sql('RANDOM()')).first }}
#=>0.010000 0.000000 0.010000 ( 1.222340)
Patient.unscoped.order(Arel.sql('RANDOM()')).first
Patient Load (121.1ms) SELECT "patients".* FROM "patients" ORDER BY RANDOM() LIMIT 1
puts Benchmark.measure {10.times {Patient.unscoped.offset(rand(Patient.unscoped.count)).first }}
#=>0.020000 0.000000 0.020000 ( 0.318977)
Patient.unscoped.offset(rand(Patient.unscoped.count)).first
(11.7ms) SELECT COUNT(*) FROM "patients"
Patient Load (33.4ms) SELECT "patients".* FROM "patients" ORDER BY "patients"."id" ASC LIMIT 1 OFFSET 106284
puts Benchmark.measure{10.times{Patient.random}}
#=>0.010000 0.000000 0.010000 ( 0.148306)
Patient.random
(14.8ms) SELECT COUNT(*) FROM "patients"
#also
Patient.unscoped.find rand(Patient.unscoped.last.id)
Patient Load (0.3ms) SELECT "patients".* FROM "patients" ORDER BY "patients"."id" DESC LIMIT 1
Patient Load (0.4ms) SELECT "patients".* FROM "patients" WHERE "patients"."id" = $1 LIMIT 1 [["id", 4511]]
之所以这样,是因为我们正在使用rand()
来获取随机ID并在该单条记录上进行查找。但是,删除的行数(跳过的id)越大,while循环执行多次的可能性就越大。它可能有点矫枉过正,但是如果你永远不会删除行,可能值得性能提高62%,甚至更高。测试它是否对您的用例更好。