रेल 3: यादृच्छिक रिकॉर्ड प्राप्त करें


132

इसलिए, मुझे रेल 2 में एक यादृच्छिक रिकॉर्ड खोजने के लिए कई उदाहरण मिले हैं - पसंदीदा तरीका लगता है:

Thing.find :first, :offset => rand(Thing.count)

नौसिखिया के कुछ होने के नाते मुझे यकीन नहीं है कि रेल 3 में नए खोज वाक्यविन्यास का उपयोग करके इसका निर्माण कैसे किया जा सकता है।

तो, एक यादृच्छिक रिकॉर्ड खोजने के लिए "रेल 3 वे" क्या है?


1
डुप्लिकेट stackoverflow.com/questions/2752231/…
fl00r

9
^ ^ मैं विशेष रूप से रेल 3 इष्टतम तरीके की तलाश कर रहा हूं, सिवाय सवाल के पूरे उद्देश्य के।
एंड्रयू

रेल 3 विशिष्ट केवल क्वेरी श्रृंखला है :)
fl00r

जवाबों:


216
Thing.first(:order => "RANDOM()") # For MySQL :order => "RAND()", - thanx, @DanSingerman
# Rails 3
Thing.order("RANDOM()").first

या

Thing.first(:offset => rand(Thing.count))
# Rails 3
Thing.offset(rand(Thing.count)).first

दरअसल, रेल 3 में सभी उदाहरण काम करेंगे। लेकिन ऑर्डर RANDOMका उपयोग करना बड़ी तालिकाओं के लिए काफी धीमा है लेकिन अधिक वर्ग-शैली है

युपीडी। आप अनुक्रमित स्तंभ (PostgreSQL सिंटैक्स) पर निम्न चाल का उपयोग कर सकते हैं:

select * 
from my_table 
where id >= trunc(
  random() * (select max(id) from my_table) + 1
) 
order by id 
limit 1;

11
हालांकि आपका पहला उदाहरण MySQL में काम नहीं करेगा - MySQL का सिंटैक्स Thing.first (: order => "RAND ()") (SQL लिखने का एक खतरा है जो ActiveRecord सार का उपयोग करने के बजाय)
DanSermanerman

@ DanSingerman, हाँ यह डीबी विशिष्ट है RAND()या RANDOM()। धन्यवाद
fl00r

और यह समस्या पैदा नहीं करेगा अगर सूचकांक से आइटम गायब हैं? (यदि स्टैक के बीच में कुछ हटा दिया जाता है, तो क्या यह अनुरोध किया जाएगा एक मौका होगा?
विक्टर एस

@VictorS, नहीं, यह #offset नहीं होगा बस अगले उपलब्ध रिकॉर्ड के लिए जाता है। मैंने इसे रूबी 1.9.2 और रेल्स 3.1
SooDesuNe

1
@ जॉनमोर्लिनो, हां 0 ऑफसेट है, आईडी नहीं। ऑर्डर से तात्पर्य पहले आइटम से है।
fl00r

29

मैं एक परियोजना ( रेल 3.0.15, रूबी 1.9.3-p125-perf ) पर काम कर रहा हूं, जहां db लोकलहोस्ट में है और उपयोगकर्ता तालिका में 100K से अधिक रिकॉर्ड है

का उपयोग करते हुए

रैंड द्वारा आदेश ()

काफी धीमा है

User.order ( "रैंड (आईडी)")। पहले

हो जाता है

चुनें users। * usersरैंड (आईडी) सीमा 1 से आदेश

और प्रतिक्रिया देने के लिए 8 से 12 सेकंड लगते हैं !!

रेल लॉग:

उपयोगकर्ता लोड (11030.8ms) का चयन करें usersusersरैंड द्वारा आदेश से () सीमा 1

mysql के समझाने से

+----+-------------+-------+------+---------------+------+---------+------+--------+---------------------------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows   | Extra                           |
+----+-------------+-------+------+---------------+------+---------+------+--------+---------------------------------+
|  1 | SIMPLE      | users | ALL  | NULL          | NULL | NULL    | NULL | 110165 | Using temporary; Using filesort |
+----+-------------+-------+------+---------------+------+---------+------+--------+---------------------------------+

आप देख सकते हैं कि किसी भी इंडेक्स का उपयोग नहीं किया गया है ( संभव_की = NULL ), एक अस्थायी तालिका बनाई गई है और वांछित मान प्राप्त करने के लिए एक अतिरिक्त पास की आवश्यकता है ( अतिरिक्त = अस्थायी का उपयोग करके; फाइल का उपयोग करके )।

दूसरी ओर, क्वेरी को दो भागों में विभाजित करके और रूबी का उपयोग करके, प्रतिक्रिया समय में हमारे पास एक उचित सुधार है।

users = User.scoped.select(:id);nil
User.find( users.first( Random.rand( users.length )).last )

(; कंसोल उपयोग के लिए शून्य)

रेल लॉग:

उपयोगकर्ता लोड (25.2ms) usersउपयोगकर्ता लोड से आईडी का चयन करें (0.2ms) का चयन करें usersusersजहां से usersid= 106854 सीमा 1

और mysql की व्याख्या क्यों साबित होती है:

+----+-------------+-------+-------+---------------+--------------------------+---------+------+--------+-------------+
| id | select_type | table | type  | possible_keys | key                      | key_len | ref  | rows   | Extra       |
+----+-------------+-------+-------+---------------+--------------------------+---------+------+--------+-------------+
|  1 | SIMPLE      | users | index | NULL          | index_users_on_user_type | 2       | NULL | 110165 | Using index |
+----+-------------+-------+-------+---------------+--------------------------+---------+------+--------+-------------+

+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+
| id | select_type | table | type  | possible_keys | key     | key_len | ref   | rows | Extra |
+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+
|  1 | SIMPLE      | users | const | PRIMARY       | PRIMARY | 4       | const |    1 |       |
+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+

अब हम केवल अनुक्रमित और प्राथमिक कुंजी का उपयोग कर सकते हैं और लगभग 500 गुना तेजी से काम कर सकते हैं!

अपडेट करें:

जैसा कि टिप्पणियों में icantbecool द्वारा बताया गया है कि उपरोक्त समाधान में एक दोष है यदि तालिका में हटाए गए रिकॉर्ड हैं।

इसमें वर्कअराउंड हो सकता है

users_count = User.count
User.scoped.limit(1).offset(rand(users_count)).first

जो दो प्रश्नों का अनुवाद करता है

SELECT COUNT(*) FROM `users`
SELECT `users`.* FROM `users` LIMIT 1 OFFSET 148794

और लगभग 500ms में चलता है।


अपने अंतिम उदाहरण के लिए "अंतिम" के बाद ".id" जोड़ने से "आईडी के बिना मॉडल नहीं मिल सकता" त्रुटि से बच जाएगा। जैसे User.find (users.first (Random.rand (users.length))। last.id)
turing_machine

चेतावनी! MySQL में आपको प्रत्येक क्वेरी के लिए एक अलग यादृच्छिक क्रम नहीं दिया RAND(id)जाएगा । यदि प्रत्येक क्वेरी के लिए एक अलग क्रम चाहते हैं तो उपयोग करें । RAND()
जस्टिन टान्नर

यदि कोई रिकॉर्ड हटा दिया गया था, तो User.find (users.first (Random.rand (users.length))। last.id) काम नहीं करेगा। [1,2,4,5], और यह संभावित रूप से 3 की आईडी चुन सकता है, लेकिन एक सक्रिय रिकॉर्ड संबंध नहीं होगा।
icantbecool

इसके अलावा, उपयोगकर्ता = User.scoped.select (: id); nil को पदावनत नहीं किया जाता है। इसके स्थान पर इसका उपयोग करें: उपयोगकर्ता = User.where (nil) .select (: id)
icantbecool

मेरा मानना ​​है कि random.rand (users.length) को पहले एक बग के पैरामीटर के रूप में उपयोग किया जाता है। Random.rand 0. वापस कर सकता है। जब 0 को पहले पैरामीटर के रूप में उपयोग किया जाता है, तो सीमा शून्य पर सेट होती है और यह कोई रिकॉर्ड नहीं देता है। इसके बजाय किसी को क्या उपयोग करना चाहिए 1 + रैंडम (users.length) उपयोगकर्ताओं को मानते हुए ।length> 0.
SWoo

12

यदि Postgres का उपयोग कर रहे हैं

User.limit(5).order("RANDOM()")

अगर MySQL का उपयोग कर रहे हैं

User.limit(5).order("RAND()")

दोनों उदाहरणों में आप उपयोगकर्ता तालिका से यादृच्छिक रूप से 5 रिकॉर्ड का चयन कर रहे हैं। यहाँ कंसोल में प्रदर्शित वास्तविक एसक्यूएल क्वेरी है।

SELECT * FROM users ORDER BY RANDOM() LIMIT 5

11

मैंने ऐसा करने के लिए एक रेल 3 मणि बनाई जो बड़ी तालिकाओं पर बेहतर प्रदर्शन करती है और आपको संबंधों और परिमार्जन की अनुमति देती है:

https://github.com/spilliton/randumb

(संपादित करें): मेरे मणि का डिफ़ॉल्ट व्यवहार मूल रूप से ऊपर के रूप में एक ही दृष्टिकोण का उपयोग करता है, लेकिन आपके पास पुराने तरीके का उपयोग करने का विकल्प है जो आप चाहते हैं :)


6

पोस्ट किए गए जवाबों में से कई वास्तव में बड़ी तालिकाओं (1+ मिलियन पंक्तियों) पर अच्छा प्रदर्शन नहीं करेंगे। रैंडम ऑर्डर करने में कुछ सेकंड लगते हैं, और टेबल पर एक गिनती करने में भी काफी समय लगता है।

एक समाधान जो इस स्थिति में मेरे लिए अच्छी तरह से काम करता है वह RANDOM()एक ऐसी स्थिति के साथ उपयोग करना है:

Thing.where('RANDOM() >= 0.9').take

एक लाख से अधिक पंक्तियों वाली तालिका में, यह क्वेरी आम तौर पर 2ms से कम होती है।


आपके समाधान का एक अन्य लाभ उपयोग takeफ़ंक्शन है जो LIMIT(1)क्वेरी देता है लेकिन सरणी के बजाय एकल तत्व लौटाता है। इसलिए हमें आह्वान करने की आवश्यकता नहीं हैfirst
पिओट्र गलस

यह मुझे लगता है कि तालिका की शुरुआत में रिकॉर्ड्स में इस तरह से चयनित होने की संभावना अधिक होती है, जो शायद आप प्राप्त नहीं करना चाहते हैं।
Gorn

5

ये रहा

रेल रास्ता

#in your initializer
module ActiveRecord
  class Base
    def self.random
      if (c = count) != 0
        find(:first, :offset =>rand(c))
      end
    end
  end
end

प्रयोग

Model.random #returns single random object

या दूसरा विचार है

module ActiveRecord
  class Base
    def self.random
      order("RAND()")
    end
  end
end

उपयोग:

Model.random #returns shuffled collection

Couldn't find all Users with 'id': (first, {:offset=>1}) (found 0 results, but was looking for 2)
ब्रूनो

यदि कोई उपयोगकर्ता नहीं है और आप 2 प्राप्त करना चाहते हैं, तो आपको त्रुटियां मिलती हैं। सही बात।
टिम क्रॉश्चर

1
दूसरा दृष्टिकोण पोस्टग्रेज के साथ काम नहीं करेगा, लेकिन आप "RANDOM()"इसके बजाय उपयोग कर सकते हैं ...
डैनियल रिक्टर

4

यह मेरे लिए बहुत उपयोगी था लेकिन मुझे थोड़ा और लचीलेपन की आवश्यकता थी, इसलिए मैंने यही किया:

Case1: एक यादृच्छिक रिकॉर्ड स्रोत ढूँढना : trevor turk साइट
इसे Thing.rb मॉडल में जोड़ें

def self.random
    ids = connection.select_all("SELECT id FROM things")
    find(ids[rand(ids.length)]["id"].to_i) unless ids.blank?
end

फिर अपने कंट्रोलर में आप कुछ इस तरह से कॉल कर सकते हैं

@thing = Thing.random

Case2: कई रैंडम रिकॉर्ड्स (कोई दोहराता नहीं) स्रोत खोजना : याद नहीं रख सकता कि
मुझे 10 यादृच्छिक रिकॉर्ड्स को बिना किसी दोहराव के खोजने की आवश्यकता है, इसलिए यह वही है जो मैंने पाया है
आपके नियंत्रक में:

thing_ids = Thing.find( :all, :select => 'id' ).map( &:id )
@things = Thing.find( (1..10).map { thing_ids.delete_at( thing_ids.size * rand ) } )

इससे 10 यादृच्छिक रिकॉर्ड मिलेंगे, हालांकि यह ध्यान देने योग्य है कि यदि डेटाबेस विशेष रूप से बड़े (लाखों रिकॉर्ड) हैं, तो यह आदर्श नहीं होगा, और प्रदर्शन में बाधा आएगी। कुछ हजार रिकॉर्ड तक अच्छा प्रदर्शन करेगा जो मेरे लिए पर्याप्त था।


4

किसी सूची से आइटम को बेतरतीब ढंग से चुनने के लिए रूबी विधि है samplesampleActiveRecord के लिए एक कुशल बनाना चाहते हैं , और पिछले उत्तरों के आधार पर, मैंने उपयोग किया:

module ActiveRecord
  class Base
    def self.sample
      offset(rand(size)).first
    end
  end
end

मैंने इसे इसमें डाल दिया lib/ext/sample.rbऔर फिर इसे इसमें लोड किया config/initializers/monkey_patches.rb:

Dir[Rails.root.join('lib/ext/*.rb')].each { |file| require file }

दरअसल, #countDB के लिए एक कॉल करेंगे a COUNT। यदि रिकॉर्ड पहले से लोड है, तो यह एक बुरा विचार हो सकता है। एक रिफ्लेक्टर #sizeइसके बजाय उपयोग करना होगा क्योंकि यह तय करेगा कि #countइसका उपयोग किया जाना चाहिए, या, यदि रिकॉर्ड पहले से लोड है, तो उपयोग करने के लिए #length
BenMorganIO

आपकी प्रतिक्रिया के आधार पर स्विच किया countगया size। अधिक जानकारी: dev.mensfeld.pl/2014/09/…
Dan Kohn

3

रेल 5 में काम करता है और डीबी अज्ञेयवादी है:

यह आपके नियंत्रक में है:

@quotes = Quote.offset(rand(Quote.count - 3)).limit(3)

बेशक, आप इसे एक चिंता में डाल सकते हैं जैसा कि यहां दिखाया गया है

एप्लिकेशन / मॉडल / चिंताओं / randomable.rb

module Randomable
  extend ActiveSupport::Concern

  class_methods do
    def random(the_count = 1)
      records = offset(rand(count - the_count)).limit(the_count)
      the_count == 1 ? records.first : records
    end
  end
end

फिर...

एप्लिकेशन / मॉडल / book.rb

class Book < ActiveRecord::Base
  include Randomable
end

तो आप बस का उपयोग करके कर सकते हैं:

Books.random

या

Books.random(3)

यह हमेशा बाद के रिकॉर्ड लेता है, जिसे कम से कम दस्तावेज होने की आवश्यकता होती है (जैसा कि यह नहीं हो सकता है कि उपयोगकर्ता क्या चाहता है)।
जूल

2

आप ActiveRecord में नमूना () का उपयोग कर सकते हैं

उदाहरण के लिए

def get_random_things_for_home_page
  find(:all).sample(5)
end

स्रोत: http://thinkingeek.com/2011/07/04/easily-select-random-records-rails/


33
यह उपयोग करने के लिए एक बहुत ही खराब क्वेरी है यदि आपके पास बड़ी मात्रा में रिकॉर्ड हैं, क्योंकि DB सभी रिकॉर्ड का चयन करेगा, तो रेल उस से पांच रिकॉर्ड उठाएगी - बड़े पैमाने पर बेकार।
डेवस्टेफेन्स

5
sampleActiveRecord में नहीं है, नमूना ऐरे में है। api.rubyonrails.org/classes/Array.html#method-i-sample
Frans

3
यह एक यादृच्छिक रिकॉर्ड प्राप्त करने का एक महंगा तरीका है, खासकर एक बड़ी तालिका से। रेल आपके टेबल से मेमोरी में हर रिकॉर्ड के लिए एक ऑब्जेक्ट लोड करेगी। यदि आपको प्रमाण की आवश्यकता है, तो 'रेल कंसोल' चलाएं, 'SomeModelFromYourApp.find (: all) .sample (5)' आज़माएं और उत्पादित SQL को देखें।
एलियट साइक्स

1
मेरा उत्तर देखें, जो इस महंगे उत्तर को कई यादृच्छिक रिकॉर्ड प्राप्त करने के लिए एक सुव्यवस्थित सुंदरता में बदल देता है।
आर्कोलाई


1

रैंडम रेकॉर्ड्स के लिए इस रत्न की जोरदार अनुशंसा करें, जो विशेष रूप से बहुत सारी डेटा पंक्तियों वाली तालिका के लिए डिज़ाइन किया गया है:

https://github.com/haopingfan/quick_random_records

इस मणि को छोड़कर अन्य सभी उत्तर बड़े डेटाबेस के साथ खराब प्रदर्शन करते हैं:

  1. quick_random_records केवल 4.6msपूरी तरह से लागत ।

यहाँ छवि विवरण दर्ज करें

  1. स्वीकृत उत्तर User.order('RAND()').limit(10)लागत 733.0ms

यहाँ छवि विवरण दर्ज करें

  1. offsetदृष्टिकोण की लागत 245.4msपूरी तरह से।

यहाँ छवि विवरण दर्ज करें

  1. User.all.sample(10)दृष्टिकोण लागत 573.4ms

यहाँ छवि विवरण दर्ज करें

नोट: मेरी तालिका में केवल 120,000 उपयोगकर्ता हैं। आपके पास जितने अधिक रिकॉर्ड होंगे, प्रदर्शन का अंतर उतना ही अधिक होगा।


अपडेट करें:

550,000 पंक्तियों के साथ तालिका पर प्रदर्शन करें

  1. Model.where(id: Model.pluck(:id).sample(10)) लागत 1384.0ms

यहाँ छवि विवरण दर्ज करें

  1. gem: quick_random_recordsकेवल 6.4msपूरी तरह से लागत

यहाँ छवि विवरण दर्ज करें


-2

तालिका से कई यादृच्छिक रिकॉर्ड प्राप्त करने का एक बहुत आसान तरीका है। यह 2 सस्ते प्रश्न बनाता है।

Model.where(id: Model.pluck(:id).sample(3))

आप "3" को अपने इच्छित रैंडम रिकॉर्ड की संख्या में बदल सकते हैं।


1
नहीं, Model.pluck (: id) .sample (3) भाग सस्ता नहीं है। यह तालिका में प्रत्येक तत्व के लिए आईडी फ़ील्ड को पढ़ेगा।
मैक्सिमिलियानो गुज़मैन 19

क्या कोई तेज़ डेटाबेस-अज्ञेयवादी तरीका है?
आर्कोलिये

-5

मैं सिर्फ इस मुद्दे पर भागता रहा कि एक छोटा अनुप्रयोग विकसित किया जाए जहां मैं अपने DB से एक यादृच्छिक प्रश्न का चयन करना चाहता था। मैंनें इस्तेमाल किया:

@question1 = Question.where(:lesson_id => params[:lesson_id]).shuffle[1]

और यह मेरे लिए अच्छा काम कर रहा है। मैं इस पर नहीं बोल सकता कि बड़े DB के लिए प्रदर्शन कैसा है क्योंकि यह एक छोटा अनुप्रयोग है।


हाँ, यह केवल आपके सभी रिकॉर्ड प्राप्त कर रहा है और उन पर माणिक सरणी विधियों का उपयोग कर रहा है। खामी यह है कि इसका मतलब है कि आपके सभी रिकॉर्ड्स को मेमोरी में लोड करना, फिर उन्हें बेतरतीब ढंग से फिर से लोड करना, फिर रिजेक्टेड एरे में दूसरे आइटम को हथियाना। यदि आप एक बड़े डेटासेट के साथ काम कर रहे हैं तो यह निश्चित रूप से एक मेमोरी हॉग हो सकता है। माइनर एक तरफ, पहले तत्व को क्यों नहीं पकड़ा? (यानी। shuffle[0])
एंड्रयू

फेरबदल करना चाहिए [0]
मार्सेलो ऑस्ट्रिया
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.