Necesito obtener un registro aleatorio de una tabla a través de ActiveRecord. He seguido el ejemplo de Jamis Buck de 2006 .
Sin embargo, también he encontrado otra forma a través de una búsqueda en Google (no se puede atribuir con un enlace debido a nuevas restricciones de usuario):
rand_id = rand(Model.count)
rand_record = Model.first(:conditions => ["id >= ?", rand_id])
Tengo curiosidad por saber cómo lo han hecho otros aquí o si alguien sabe de qué manera sería más eficiente.
ruby-on-rails
random
rails-activerecord
jyunderwood
fuente
fuente
Respuestas:
No he encontrado una manera ideal de hacer esto sin al menos dos consultas.
Lo siguiente utiliza un número generado aleatoriamente (hasta el recuento de registros actual) como compensación .
Para ser honesto, acabo de usar ORDER BY RAND () o RANDOM () (dependiendo de la base de datos). No es un problema de rendimiento si no tiene un problema de rendimiento.
fuente
Model.find(:offset => offset).first
arrojará un error. Creo queModel.first(:offset => offset)
podría funcionar mejor.Thing.order("RANDOM()").limit(100)
para 100 entradas seleccionadas al azar. (Tenga en cuenta que estáRANDOM()
en PostgreSQL yRAND()
en MySQL ... no es tan portátil como desee).Model.offset(offset).first
.Carriles 6
Como dijo Jason en los comentarios, en Rails 6, los argumentos sin atributos no están permitidos. Debe ajustar el valor en una
Arel.sql()
declaración.Carriles 5, 4
En los rieles 4 y 5 , usando Postgresql o SQLite , usando
RANDOM()
:Presumiblemente lo mismo funcionaría para MySQL con
RAND()
Esto es aproximadamente 2.5 veces más rápido que el enfoque en la respuesta aceptada .
Advertencia : esto es lento para grandes conjuntos de datos con millones de registros, por lo que es posible que desee agregar una
limit
cláusula.fuente
Su código de ejemplo comenzará a comportarse incorrectamente una vez que se eliminen los registros (favorecerá injustamente los elementos con identificadores inferiores)
Probablemente sea mejor usar los métodos aleatorios dentro de su base de datos. Estos varían según la base de datos que esté utilizando, pero: order => "RAND ()" funciona para mysql y: order => "RANDOM ()" funciona para postgres
fuente
Model.order("RANDOM()").first
lugar.Comparando estos dos métodos en MySQL 5.1.49, Ruby 1.9.2p180 en una tabla de productos con + 5 millones de registros:
La compensación en MySQL parece ser mucho más lenta.
EDITAR También probé
Pero tuve que matarlo después de ~ 60 segundos. MySQL fue "Copiar a la tabla tmp en el disco". Eso no va a funcionar.
fuente
Thing.order("RANDOM()").first
en una tabla con 250,000 entradas; la consulta terminó en menos de medio segundo. (PostgreSQL 9.0, REE 1.8.7, 2 x 2.66 GHz núcleos) Eso es lo suficientemente rápido para mí, ya que estoy haciendo una "limpieza" única.rand_id = rand(Product.count) + 1
o nunca obtendrá el último registro.random1
no funcionará si alguna vez elimina una fila en la tabla. (El recuento será inferior al ID máximo y nunca podrá seleccionar filas con ID altos).random2
se puede mejorar#order
usando una columna indexada.No tiene que ser tan difícil.
pluck
devuelve una matriz de todos los id en la tabla. lossample
método en la matriz devuelve una identificación aleatoria de la matriz.Esto debería funcionar bien, con la misma probabilidad de selección y soporte para tablas con filas eliminadas. Incluso puedes mezclarlo con restricciones.
Y, por lo tanto, elija un usuario aleatorio al que le gusten los viernes en lugar de cualquier usuario.
fuente
No se recomienda que use esta solución, pero si por alguna razón realmente desea seleccionar un registro al azar mientras realiza una sola consulta de base de datos, puede usar el
sample
método de la clase Ruby Array , que le permite seleccionar un elemento aleatorio de una matriz.Este método requiere solo una consulta a la base de datos, pero es significativamente más lento que las alternativas como las
Model.offset(rand(Model.count)).first
que requieren dos consultas a la base de datos, aunque esta última sigue siendo preferida.fuente
Hice una gema de rieles 3 para manejar esto:
https://github.com/spilliton/randumb
Te permite hacer cosas como esta:
fuente
ORDER BY RANDOM()
(oRAND()
para mysql) a su consulta". - por lo tanto, los comentarios sobre el mal desempeño mencionados en los comentarios a la respuesta de @semanticart también se aplican al usar esta gema. Pero al menos es DB independiente.Lo uso tan a menudo desde la consola que extiendo ActiveRecord en un inicializador - Ejemplo de Rails 4:
Entonces puedo llamar
Foo.random
para recuperar un registro aleatorio.fuente
limit(1)
?ActiveRecord#first
debería ser lo suficientemente inteligente como para hacer eso.Una consulta en Postgres:
Usando un desplazamiento, dos consultas:
fuente
Leer todo esto no me dio mucha confianza sobre cuál funcionaría mejor en mi situación particular con Rails 5 y MySQL / Maria 5.5. Así que probé algunas de las respuestas en ~ 65000 registros, y tengo dos conclusiones:
limit
es un claro ganador.pluck
+sample
.Esta respuesta sintetiza, valida y actualiza la respuesta de Mohamed , así como el comentario de Nami WANG sobre la misma y el comentario de Florian Pilz sobre la respuesta aceptada. ¡Por favor envíeles votos!
fuente
Puede usar el
Array
métodosample
, el métodosample
devuelve un objeto aleatorio de una matriz, para usarlo solo necesita ejecutar en unaActiveRecord
consulta simple que devuelva una colección, por ejemplo:devolverá algo como esto:
fuente
order('rand()').limit(1)
hace "el mismo" trabajo (con ~ 10K registros).Recomiendo esta gema para registros aleatorios, que está especialmente diseñada para tablas con muchas filas de datos:
https://github.com/haopingfan/quick_random_records
Todas las demás respuestas funcionan mal con una base de datos grande, excepto esta gema:
4.6ms
totalmente.User.order('RAND()').limit(10)
costo733.0ms
.offset
enfoque de respuesta aceptado cuesta245.4ms
totalmente.User.all.sample(10)
costo de aproximación573.4ms
.Nota: Mi mesa solo tiene 120,000 usuarios. Cuantos más registros tenga, más enorme será la diferencia de rendimiento.
fuente
Si necesita seleccionar algunos resultados aleatorios dentro del alcance especificado :
fuente
El método Ruby para elegir aleatoriamente un elemento de una lista es
sample
. Queriendo crear un eficientesample
para ActiveRecord, y en base a las respuestas anteriores, usé:Puse esto
lib/ext/sample.rb
y luego lo cargué con esto enconfig/initializers/monkey_patches.rb
:Esta será una consulta si el tamaño del modelo ya está en caché y dos en caso contrario.
fuente
Rails 4.2 y Oracle :
Para Oracle, puede establecer un alcance en su modelo de la siguiente manera:
o
Y luego, para una muestra, llámalo así:
o
Por supuesto, también puede realizar un pedido sin un alcance como este:
fuente
order('random()'
y MySQL conorder('rand()')
. Esta es definitivamente la mejor respuesta.Para la base de datos MySQL, pruebe: Model.order ("RAND ()"). First
fuente
Si está utilizando PostgreSQL 9.5+, puede aprovechar
TABLESAMPLE
para seleccionar un registro aleatorio.Los dos métodos de muestreo predeterminados (
SYSTEM
yBERNOULLI
) requieren que especifique el número de filas a devolver como un porcentaje del número total de filas en la tabla.Esto requiere conocer la cantidad de registros en la tabla para seleccionar el porcentaje apropiado, que puede no ser fácil de encontrar rápidamente. Afortunadamente, existe el
tsm_system_rows
módulo que le permite especificar el número de filas para devolver directamente.Para usar esto dentro de ActiveRecord, primero habilite la extensión dentro de una migración:
Luego modifique la
from
cláusula de la consulta:No se si el
SYSTEM_ROWS
método de muestreo será completamente aleatorio o si solo devuelve la primera fila de una página aleatoria.La mayor parte de esta información fue tomada de una publicación de blog de 2ndQuadrant escrita por Gulcin Yildirim .
fuente
Después de ver tantas respuestas, decidí compararlas todas en mi base de datos PostgreSQL (9.6.3). Utilicé una tabla de 100.000 más pequeña y me deshice del Model.order ("RANDOM ()"). Primero, ya que era dos órdenes de magnitud más lento.
Usando una tabla con 2,500,000 entradas con 10 columnas, el ganador fue que el método de selección fue casi 8 veces más rápido que el finalista (desplazamiento. Solo ejecuté esto en un servidor local para que ese número pueda estar inflado, pero es lo suficientemente grande como para que el complemento es lo que terminaré usando. También vale la pena señalar que esto podría causar problemas si seleccionas más de 1 resultado a la vez, ya que cada uno de ellos será único o menos aleatorio.
Pluck gana corriendo 100 veces en mi tabla de 25,000,000 filas Editar: en realidad esta vez incluye el pluck en el bucle si lo elimino, funciona casi tan rápido como una simple iteración en la identificación. Sin embargo; ocupa una buena cantidad de RAM.
Aquí están los datos que se ejecutan 2000 veces en mi tabla de 100,000 filas para descartar al azar
fuente
Pregunta muy antigua pero con:
Tienes una matriz de registro, ordenada por orden aleatorio. No necesita gemas ni scripts.
Si quieres un registro:
fuente
shuffle.first
==.sample
Soy nuevo en RoR pero conseguí que esto funcione para mí:
Vino de:
¿Cómo ordenar aleatoriamente (codificar) una matriz en Ruby?
fuente
array.shuffle
. De todos modos, tenga cuidado, yaCard.all
que cargará todos los registros de la tarjeta en la memoria, lo que se vuelve más ineficiente a medida que más objetos estamos hablando.Qué hay que hacer:
Para mi esta muy claro
fuente
Intento esto del ejemplo de Sam en mi aplicación usando rails 4.2.8 de Benchmark (pongo 1..Category.count para random, porque si el random toma un 0 producirá un error (ActiveRecord :: RecordNotFound: No se pudo encontrar Categoría con 'id' = 0)) y la mina fue:
fuente
.order('RANDOM()').limit(limit)
se ve ordenado pero es lento para tablas grandes porque necesita buscar y ordenar todas las filas incluso silimit
es 1 (internamente en la base de datos pero no en Rails). No estoy seguro acerca de MySQL pero esto sucede en Postgres. Más explicaciones aquí y aquí .Una solución para tablas grandes es
.from("products TABLESAMPLE SYSTEM(0.5)")
donde0.5
significa0.5%
. Sin embargo, creo que esta solución sigue siendo lenta si tieneWHERE
condiciones que filtran muchas filas. Supongo que es porqueTABLESAMPLE SYSTEM(0.5)
buscar todas las filas antesWHERE
que se apliquen las condiciones.Otra solución para tablas grandes (pero no muy aleatorias) es:
dónde
sample_size
puede estar100
(pero no demasiado grande, de lo contrario es lento y consume mucha memoria), ylimit
puede ser1
. Tenga en cuenta que aunque esto es rápido pero no es realmente aleatorio, es aleatoriosample_size
solo dentro de los registros.PD: Los resultados de referencia en las respuestas anteriores no son confiables (al menos en Postgres) porque algunas consultas de DB que se ejecutan en la segunda vez pueden ser significativamente más rápidas que en la primera, gracias a la caché de DB. Y desafortunadamente no hay una manera fácil de deshabilitar el caché en Postgres para hacer que estos puntos de referencia sean confiables.
fuente
Junto con el uso
RANDOM()
, también puede incluir esto en un ámbito:O, si no le gusta eso como un alcance, simplemente tírelo a un método de clase. Ahora
Thing.random
funciona junto conThing.random(n)
.fuente
Dependiendo del significado de "aleatorio" y de lo que realmente quieres hacer,
take
podría ser suficiente.Por "significado" de aleatorio quiero decir:
Por ejemplo, para las pruebas, los datos de muestra podrían haberse creado aleatoriamente de todos modos, por lo que
take
es más que suficiente y, para ser sincero, inclusofirst
.https://guides.rubyonrails.org/active_record_querying.html#take
fuente