代码之家  ›  专栏  ›  技术社区  ›  Daniel

弃用警告:危险的查询方法:ActiveRecord中的随机记录>=5.2

  •  32
  • Daniel  · 技术社区  · 7 年前

    到目前为止 "common" 从数据库中获取随机记录的方法有:

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

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

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

    我对阿雷尔不是很熟悉,所以我不知道怎样才能解决这个问题。

    3 回复  |  直到 6 年前
        1
  •  51
  •   mu is too short    4 年前

    如果要继续使用 order by random() 那就把它包起来,声明它是安全的 Arel.sql 如弃用警告所示:

    Model.order(Arel.sql('random()')).first # PostgreSQL
    Model.order(Arel.sql('rand()')).first   # MySQL
    

    选择随机行的方法有很多,它们都有优点和缺点,但有时您必须在 order by (如需要时 the order to match a Ruby array 而且必须得到一个大的 case when ... end 表达式下至数据库)因此使用 阿雷尔。sql 要绕过“仅属性”限制,我们都需要了解这一工具。

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

        2
  •  5
  •   Anthony L    7 年前

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

    Model.offset(rand(Model.count)).first
    
        3
  •  3
  •   lacostenycoder    6 年前

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