ActiveRecord中的随机记录> = 5.2

到目前为止,从数据库获取随机记录的“常见”方法是:

# Postgress Model.order("RANDOM()").first # MySQL Model.order("RAND()").first 

但是,在Rails 5.2中执行此操作时,它会显示以下Deprecation警告:

DEPRECATION WARNING:使用非属性参数调用的危险查询方法(其参数用作原始SQL的方法):“RANDOM()”。 Rails 6.0中不允许使用非属性参数。 不应使用用户提供的值调用此方法,例如请求参数或模型属性。 可以通过将它们包装在Arel.sql()中来传递已知安全值。

我对Arel并不熟悉,所以我不确定解决这个问题的正确方法是什么。

如果你想order by random()继续使用order by random()那么只需将它包装在Arel.sql就可以将它声明为安全,就像弃用警告建议的那样:

 Model.order(Arel.sql('random()')).first 

有很多方法可以选择一个随机行,它们都有优点和缺点,但有些时候你绝对必须按order by使用一段SQL(例如当你需要命令匹配一个Ruby数组时 ,必须得到一个大case when ... end表达式到数据库)所以使用Arel.sql来解决这个“仅属性”限制是我们都需要了解的工具。

编辑:示例代码缺少右括号。

我是这个解决方案的粉丝:

 Model.offset(rand(Model.count)).first 

对于许多记录,并且删除的记录不多,这可能更有效。 在我的情况下,我必须使用.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%甚至更高。 测试你的用例是否更好。

Interesting Posts