Estou precisando obter um registro aleatório de uma tabela via ActiveRecord. Eu segui o exemplo de Jamis Buck de 2006 .
No entanto, também deparei com outra maneira através de uma pesquisa no Google (não é possível atribuir um link devido a novas restrições de usuário):
rand_id = rand(Model.count)
rand_record = Model.first(:conditions => ["id >= ?", rand_id])
Estou curioso para saber como outras pessoas o fizeram ou se alguém sabe de que maneira seria mais eficiente.
ruby-on-rails
random
rails-activerecord
jyunderwood
fonte
fonte
Respostas:
Não encontrei a maneira ideal de fazer isso sem pelo menos duas consultas.
A seguir, utiliza-se um número gerado aleatoriamente (até a contagem atual de registros) como deslocamento .
Para ser sincero, acabei de usar ORDER BY RAND () ou RANDOM () (dependendo do banco de dados). Não é um problema de desempenho se você não tiver um problema de desempenho.
fonte
Model.find(:offset => offset).first
gerará erro. eu acho queModel.first(:offset => offset)
pode ter um desempenho melhor.Thing.order("RANDOM()").limit(100)
para 100 entradas selecionadas aleatoriamente. (Esteja ciente de que estáRANDOM()
no PostgreSQL eRAND()
no MySQL ... não tão portátil quanto você gostaria que fosse.)Model.offset(offset).first
.Trilhos 6
Conforme afirma Jason nos comentários, no Rails 6, argumentos não relacionados a atributos não são permitidos. Você deve agrupar o valor em uma
Arel.sql()
instruçãoTrilhos 5, 4
Nos Rails 4 e 5 , usando Postgresql ou SQLite , usando
RANDOM()
:Presumivelmente, o mesmo funcionaria para o MySQL com
RAND()
Isso é cerca de 2,5 vezes mais rápido que a abordagem na resposta aceita .
Advertência : isso é lento para grandes conjuntos de dados com milhões de registros, portanto, você pode querer adicionar uma
limit
cláusula.fonte
Seu código de exemplo começará a se comportar de maneira imprecisa quando os registros forem excluídos (favorecerá injustamente itens com IDs mais baixos)
Você provavelmente está melhor usando os métodos aleatórios no seu banco de dados. Isso varia dependendo do banco de dados que você está usando, mas: order => "RAND ()" funciona no mysql e: order => "RANDOM ()" funciona no postgres
fonte
Model.order("RANDOM()").first
vez disso.Comparando esses dois métodos no MySQL 5.1.49, Ruby 1.9.2p180 em uma tabela de produtos com + 5 milhões de registros:
O deslocamento no MySQL parece ser muito mais lento.
EDIT Eu também tentei
Mas eu tive que matá-lo depois de 60 segundos. O MySQL estava "Copiando para a tabela tmp no disco". Isso não vai funcionar.
fonte
Thing.order("RANDOM()").first
em uma tabela com entradas de 250k - a consulta terminou em meio segundo. (PostgreSQL 9.0, REE 1.8.7, núcleos de 2 x 2,66 GHz) Isso é rápido o suficiente para mim, pois estou fazendo uma "limpeza" única.rand_id = rand(Product.count) + 1
ou nunca obtenha o último registro.random1
não funcionará se você excluir uma linha da tabela. (A contagem será menor que o ID máximo e você nunca poderá selecionar linhas com IDs altos).random2
pode ser aprimorado#order
usando uma coluna indexada.Não precisa ser tão difícil.
pluck
retorna uma matriz de todos os IDs na tabela. Osample
método na matriz retorna um ID aleatório da matriz.Isso deve ter um bom desempenho, com igual probabilidade de seleção e suporte para tabelas com linhas excluídas. Você pode até misturá-lo com restrições.
E, assim, escolha um usuário aleatório que goste de sextas-feiras, em vez de qualquer usuário.
fonte
Não é recomendável usar essa solução, mas se, por algum motivo, você realmente deseja selecionar aleatoriamente um registro enquanto faz apenas uma consulta ao banco de dados, você pode usar o
sample
método da classe Ruby Array , que permite selecionar um item aleatório de uma matriz.Esse método requer apenas consulta ao banco de dados, mas é significativamente mais lento que alternativas, como as
Model.offset(rand(Model.count)).first
que requerem duas consultas ao banco de dados, embora a última ainda seja a preferida.fonte
Eu fiz uma jóia de trilhos 3 para lidar com isso:
https://github.com/spilliton/randumb
Ele permite que você faça coisas assim:
fonte
ORDER BY RANDOM()
(ouRAND()
mysql) à sua consulta". - portanto, os comentários sobre o mau desempenho mencionados nos comentários à resposta por @semanticart também se aplicam ao usar esta gema. Mas pelo menos é independente de DB.Eu uso isso tantas vezes no console, estendo o ActiveRecord em um inicializador - exemplo do Rails 4:
Posso ligar
Foo.random
para trazer de volta um registro aleatório.fonte
limit(1)
?ActiveRecord#first
deve ser inteligente o suficiente para fazer isso.Uma consulta no Postgres:
Usando um deslocamento, duas consultas:
fonte
Ler tudo isso não me deu muita confiança sobre qual deles funcionaria melhor em minha situação particular com o Rails 5 e o MySQL / Maria 5.5. Então, testei algumas das respostas em ~ 65000 registros e tenho duas alternativas:
limit
é um vencedor claro.pluck
+sample
.Esta resposta sintetiza, valida e atualiza a resposta de Mohamed , bem como o comentário de Nami WANG sobre o mesmo e o comentário de Florian Pilz sobre a resposta aceita - envie votos a eles!
fonte
Você pode usar o
Array
métodosample
, o métodosample
retorna um objeto aleatório de uma matriz. Para usá-lo, basta executar em umaActiveRecord
consulta simples que retorna uma coleção, por exemplo:retornará algo como isto:
fonte
order('rand()').limit(1)
que o trabalho "é o mesmo" (com ~ 10 mil registros).Recomenda vivamente esta gema para registros aleatórios, projetados especialmente para tabelas com muitas linhas de dados:
https://github.com/haopingfan/quick_random_records
Todas as outras respostas apresentam um desempenho ruim com um banco de dados grande, exceto esta gema:
4.6ms
totalmente.User.order('RAND()').limit(10)
custo733.0ms
.offset
abordagem de resposta aceita custa245.4ms
totalmente.User.all.sample(10)
custo da abordagem573.4ms
.Nota: Minha tabela possui apenas 120.000 usuários. Quanto mais registros você tiver, mais enorme será a diferença de desempenho.
fonte
Se você precisar selecionar alguns resultados aleatórios dentro do escopo especificado :
fonte
O método Ruby para escolher aleatoriamente um item de uma lista é
sample
. Querendo criar um eficientesample
para o ActiveRecord, e com base nas respostas anteriores, usei:Eu coloquei isso
lib/ext/sample.rb
e carreguei com isso emconfig/initializers/monkey_patches.rb
:Essa será uma consulta se o tamanho do modelo já estiver armazenado em cache e duas caso contrário.
fonte
Rails 4.2 e Oracle :
Para a oracle, você pode definir um escopo no seu modelo da seguinte maneira:
ou
E então, para uma amostra, chame assim:
ou
é claro que você também pode fazer um pedido sem um escopo como este:
fonte
order('random()'
e MySQLorder('rand()')
. Esta é definitivamente a melhor resposta.Para o banco de dados MySQL, tente: Model.order ("RAND ()"). First
fonte
Se você estiver usando o PostgreSQL 9.5+, poderá tirar proveito de
TABLESAMPLE
para selecionar um registro aleatório.Os dois métodos de amostragem padrão (
SYSTEM
eBERNOULLI
) exigem que você especifique o número de linhas a serem retornadas como uma porcentagem do número total de linhas na tabela.Isso requer conhecer a quantidade de registros na tabela para selecionar a porcentagem apropriada, o que pode não ser fácil de encontrar rapidamente. Felizmente, existe o
tsm_system_rows
módulo que permite especificar o número de linhas a serem retornadas diretamente.Para usar isso no ActiveRecord, primeiro habilite a extensão dentro de uma migração:
Em seguida, modifique a
from
cláusula da consulta:Não sei se o
SYSTEM_ROWS
método de amostragem será inteiramente aleatório ou se ele retorna a primeira linha de uma página aleatória.A maioria dessas informações foi retirada de um post do 2ndQuadrant escrito por Gulcin Yildirim .
fonte
Depois de ver tantas respostas, decidi compará-las todas no meu banco de dados PostgreSQL (9.6.3). Eu uso uma tabela menor de 100.000 e me livrei da Model.order ("RANDOM ()"). Primeiro, pois ela já era duas ordens de magnitude mais lenta.
Usando uma tabela com 2.500.000 entradas com 10 colunas, o vencedor foi o método de arranque quase 8 vezes mais rápido que o vice-campeão (deslocamento. Eu apenas o executei em um servidor local para que o número possa ser inflado, mas é maior o suficiente para que o arranque É o método que eu vou acabar usando.Também vale a pena notar que isso pode causar problemas: você obtém mais de um resultado por vez, já que cada um deles será único, menos aleatório.
O Pluck ganha rodando 100 vezes na minha tabela de 25.000.000 de linhas. Contudo; é preciso uma quantidade razoável de RAM.
Aqui estão os dados em execução 2000 vezes na minha tabela de 100.000 linhas para descartar aleatoriamente
fonte
Pergunta muito antiga, mas com:
Você tem uma matriz de registro, classificar por ordem aleatória. Não há necessidade de gemas ou scripts.
Se você deseja um registro:
fonte
shuffle.first
==.sample
Sou novato no RoR, mas consegui que isso funcionasse para mim:
Veio de:
Como ordenar (embaralhar) aleatoriamente uma matriz em Ruby?
fonte
array.shuffle
. De qualquer forma, cuidado, poisCard.all
carregará todos os registros do cartão na memória, o que torna mais ineficiente quanto mais objetos estamos falando.Que tal fazer:
Para mim é muito claro
fonte
Eu tento este exemplo do Sam no meu aplicativo usando os trilhos 4.2.8 do Benchmark (coloquei 1..Category.count aleatoriamente, porque se o aleatório receber um 0, ele produzirá um erro (ActiveRecord :: RecordNotFound: Não foi possível encontrar Categoria com 'id' = 0)) e a mina era:
fonte
.order('RANDOM()').limit(limit)
parece elegante, mas é lento para tabelas grandes porque precisa buscar e classificar todas as linhas, mesmo quelimit
seja 1 (internamente no banco de dados, mas não no Rails). Não tenho certeza sobre o MySQL, mas isso acontece no Postgres. Mais explicações aqui e aqui .Uma solução para tabelas grandes é
.from("products TABLESAMPLE SYSTEM(0.5)")
onde0.5
significa0.5%
. No entanto, acho que essa solução ainda é lenta se você tiverWHERE
condições que filtram muitas linhas. Eu acho que é porqueTABLESAMPLE SYSTEM(0.5)
busca todas as linhas antes que asWHERE
condições se apliquem.Outra solução para tabelas grandes (mas não muito aleatória) é:
onde
sample_size
pode estar100
(mas não muito grande, caso contrário, é lento e consome muita memória) elimit
pode estar1
. Observe que, embora isso seja rápido, mas não seja realmente aleatório, é aleatóriosample_size
apenas nos registros.PS: Os resultados de benchmark nas respostas acima não são confiáveis (pelo menos no Postgres) porque algumas consultas ao banco de dados em execução no 2º tempo podem ser significativamente mais rápidas do que na 1ª vez, graças ao cache do banco de dados. E, infelizmente, não há uma maneira fácil de desativar o cache no Postgres para tornar esses benchmarks confiáveis.
fonte
Junto com o uso
RANDOM()
, você também pode colocar isso em um escopo:Ou, se você não gosta disso como escopo, jogue-o em um método de classe. Agora
Thing.random
funciona junto comThing.random(n)
.fonte
Dependendo do significado de "aleatório" e do que você realmente deseja fazer,
take
pode ser suficiente.Com o "significado" de aleatório, quero dizer:
Por exemplo, para testes, os dados de amostra poderiam ter sido criados aleatoriamente de qualquer maneira, portanto,
take
é mais do que suficiente e, para ser sincero, atéfirst
.https://guides.rubyonrails.org/active_record_querying.html#take
fonte