บันทึกแบบสุ่มใน ActiveRecord


151

ฉันต้องการรับการบันทึกแบบสุ่มจากตารางผ่าน ActiveRecord ฉันได้ตามตัวอย่างจากJamis บั๊กจาก 2006

อย่างไรก็ตามฉันได้พบวิธีอื่นผ่านการค้นหาของ Google (ไม่สามารถกำหนดแอตทริบิวต์ด้วยลิงก์เนื่องจากข้อ จำกัด ของผู้ใช้ใหม่):

 rand_id = rand(Model.count)
 rand_record = Model.first(:conditions => ["id >= ?", rand_id])

ฉันอยากรู้ว่าคนอื่น ๆ ที่นี่ทำได้อย่างไรหรือถ้าใครรู้ว่าจะมีประสิทธิภาพมากขึ้นอย่างไร


2
2 คะแนนที่อาจช่วยให้คำตอบ 1. รหัสของคุณมีการกระจายอย่างเท่าเทียมกันเป็นอย่างไร 2. จะต้องสุ่มแบบไหน? ดีพอที่จะสุ่มหรือสุ่มจริง?
Michael

พวกเขาเป็นรหัสลำดับที่สร้างขึ้นโดยอัตโนมัติจาก activerecord และมันก็ต้องดีพอ
jyunderwood

1
จากนั้นโซลูชันที่คุณเสนอนั้นใกล้เคียงกับอุดมคติ :) ฉันใช้ "SELECT MAX (id) FROM table_name" แทน COUNT (*) เพราะมันจะจัดการกับแถวที่ถูกลบออกไปได้ดีกว่ามิฉะนั้นส่วนที่เหลือก็ใช้ได้ กล่าวโดยย่อถ้า "ดีพอ" ก็โอเคคุณต้องมีวิธีการที่จะสรุปว่าการกระจายนั้นใกล้เคียงกับสิ่งที่คุณมีอยู่จริง ถ้ามันเหมือนกันและอย่างที่คุณพูดไว้แรนด์ก็ใช้งานได้ดี
Michael

1
สิ่งนี้จะไม่ทำงานเมื่อคุณลบแถว
Venkat D.

คำตอบ:


136

ฉันไม่พบวิธีที่เหมาะสมในการทำสิ่งนี้โดยไม่มีข้อความค้นหาอย่างน้อยสองข้อ

ต่อไปนี้จะใช้จำนวนที่สร้างแบบสุ่ม (ขึ้นอยู่กับจำนวนระเบียนปัจจุบัน) เป็นชดเชย

offset = rand(Model.count)

# Rails 4
rand_record = Model.offset(offset).first

# Rails 3
rand_record = Model.first(:offset => offset)

พูดตามตรงฉันเพิ่งใช้ ORDER BY RAND () หรือ RANDOM () (ขึ้นอยู่กับฐานข้อมูล) ไม่ใช่ปัญหาด้านประสิทธิภาพหากคุณไม่มีปัญหาด้านประสิทธิภาพ


2
รหัสModel.find(:offset => offset).firstจะผิดพลาด ฉันคิดว่าModel.first(:offset => offset)อาจทำงานได้ดีขึ้น
Harish Shetty

1
ใช่ฉันได้ทำงานร่วมกับ Rails 3 และยังคงสับสนเกี่ยวกับรูปแบบแบบสอบถามระหว่างรุ่นต่างๆ
Toby Hede

7
โปรดทราบว่าการใช้ออฟเซ็ตช้ามากกับชุดข้อมูลขนาดใหญ่เนื่องจากจำเป็นต้องใช้การสแกนดัชนี (หรือการสแกนตารางในกรณีที่ใช้ดัชนีกลุ่มคลัสเตอร์เช่น InnoDB) กล่าวอีกนัยหนึ่งก็คือการดำเนินการ O (N) แต่ "WHERE id> = # {rand_id} เรียงลำดับตามรหัส ASC LIMIT 1" คือ O (บันทึก N) ซึ่งเร็วกว่ามาก
kenn

15
โปรดทราบว่าวิธีการชดเชยจะให้จุดข้อมูลเดียวที่พบแบบสุ่มเท่านั้น (ครั้งแรกหลังจากทั้งหมดจะยังคงเรียงลำดับตาม id) หากคุณต้องการหลายระเบียนที่สุ่มเลือกคุณต้องใช้วิธีนี้หลายครั้งหรือใช้วิธีการเรียงลำดับแบบสุ่มจากฐานข้อมูลของคุณเช่นThing.order("RANDOM()").limit(100)100 รายการที่สุ่มเลือก (โปรดทราบว่ามันRANDOM()อยู่ใน PostgreSQL และRAND()ใน MySQL ... ไม่สามารถพกพาได้อย่างที่คุณต้องการ)
Florian Pilz

3
ไม่ทำงานสำหรับฉัน on Rails 4. Model.offset(offset).firstใช้
mahemoff

206

ราง 6

ตามที่ระบุโดย Jason ในข้อคิดเห็นใน Rails 6 ไม่อนุญาตให้มีการขัดแย้งกับแอ็ตทริบิวต์ คุณต้องตัดค่าในArel.sql()คำสั่ง

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

ราง 5, 4

ในRails 4และ5โดยใช้PostgresqlหรือSQLiteให้ใช้RANDOM():

Model.order('RANDOM()').first

สันนิษฐานเดียวกันจะทำงานให้MySQLด้วยRAND()

Model.order('RAND()').first

นี่เร็วกว่าวิธีที่ประมาณ 2.5 เท่าในคำตอบที่ยอมรับได้

Caveat : สิ่งนี้ช้าสำหรับชุดข้อมูลขนาดใหญ่ที่มีเรคคอร์ดนับล้านรายการดังนั้นคุณอาจต้องการเพิ่มส่วนlimitคำสั่ง


4
"Random ()" ใช้งานได้ใน sqlite ดังนั้นสำหรับพวกเราที่ยังคงพัฒนา sqlite และเรียกใช้ postgres ในการผลิตโซลูชันของคุณทำงานได้ทั้งสองสภาพแวดล้อม
wuliwong

5
ฉันสร้างมาตรฐานสำหรับสิ่งนี้เทียบกับคำตอบที่ยอมรับได้ บน Postgresql 9.4 วิธีการของคำตอบนี้เร็วขึ้นเป็นสองเท่า
panmari

3
ดูเหมือนว่าจะไม่แนะนำให้ใช้บน mysql webtrenches.com/post.cfm/avoid-rand-in-mysql
Prakash Murthy

นี่เป็นวิธีแก้ปัญหาที่เร็วที่สุด
Sergio Belevskij

1
"อาร์กิวเมนต์ที่ไม่ใช่แอตทริบิวต์จะไม่ได้รับอนุญาตใน Rails 6.0 วิธีนี้ไม่ควรถูกเรียกด้วยค่าที่ผู้ใช้กำหนดเช่นพารามิเตอร์คำขอหรือคุณลักษณะของรูปแบบ
Trenton Tyler

73

โค้ดตัวอย่างของคุณจะเริ่มทำงานอย่างไม่ถูกต้องเมื่อมีการลบบันทึก

คุณน่าจะดีกว่าโดยใช้วิธีการสุ่มภายในฐานข้อมูลของคุณ สิ่งเหล่านี้แตกต่างกันไปขึ้นอยู่กับฐานข้อมูลที่คุณใช้ แต่: order => "RAND ()" ใช้งานได้กับ mysql และ: order => "RANDOM ()" ใช้งานได้สำหรับ postgres

Model.first(:order => "RANDOM()") # postgres example

7
ORDER BY RAND () สำหรับ MySQL สิ้นสุดในรันไทม์ที่น่ากลัวเมื่อข้อมูลเพิ่มขึ้น มันไม่สามารถรักษาได้ (ขึ้นอยู่กับข้อกำหนดของเวลา) แม้กระทั่งเริ่มต้นที่แถวนับพัน
Michael

Michael นำเสนอจุดที่ยอดเยี่ยม (นั่นเป็นความจริงสำหรับฐานข้อมูลอื่น ๆ ด้วย) โดยทั่วไปแล้วการเลือกแถวแบบสุ่มจากตารางขนาดใหญ่ไม่ใช่สิ่งที่คุณต้องการทำในการกระทำแบบไดนามิก การแคชคือเพื่อนของคุณ การทบทวนสิ่งที่คุณพยายามทำให้สำเร็จอาจไม่ใช่ความคิดที่ไม่ดีเช่นกัน
semanticart

1
การสั่งซื้อ RAND () ใน mysql บนโต๊ะที่มีแถวประมาณหนึ่งล้านแถวเป็น slooooooooooooooooooooooow
Subimage

24
ไม่ทำงานอีกต่อไป ใช้Model.order("RANDOM()").firstแทน
phil pirozhkov

ช้าและเฉพาะฐานข้อมูล ActiveRecord ควรทำงานอย่างราบรื่นระหว่างฐานข้อมูลดังนั้นคุณไม่ควรใช้วิธีนี้
Dex

29

การเปรียบเทียบทั้งสองวิธีใน MySQL 5.1.49, Ruby 1.9.2p180 บนตารางผลิตภัณฑ์ที่มีการบันทึก + 5 ล้านรายการ:

def random1
  rand_id = rand(Product.count)
  rand_record = Product.first(:conditions => [ "id >= ?", rand_id])
end

def random2
  if (c = Product.count) != 0
    Product.find(:first, :offset =>rand(c))
  end
end

n = 10
Benchmark.bm(7) do |x|
  x.report("next id:") { n.times {|i| random1 } }
  x.report("offset:")  { n.times {|i| random2 } }
end


             user     system      total        real
next id:  0.040000   0.000000   0.040000 (  0.225149)
offset :  0.020000   0.000000   0.020000 ( 35.234383)

ออฟเซ็ตใน MySQL นั้นช้ากว่ามาก

แก้ไข ฉันยังลอง

Product.first(:order => "RAND()")

แต่ฉันต้องฆ่ามันหลังจาก ~ 60 วินาที MySQL คือ "การคัดลอกไปยังตาราง tmp บนดิสก์" นั่นจะไม่ทำงาน


1
สำหรับผู้ที่กำลังมองหาการทดสอบเพิ่มเติมว่าต้องใช้วิธีการแบบสุ่มจริงนานแค่ไหน: ฉันลองThing.order("RANDOM()").firstบนตารางที่มีรายการ 250k - การค้นหาจะเสร็จภายในครึ่งวินาที (PostgreSQL 9.0, REE 1.8.7, 2 x 2.66 GHz คอร์) ที่เร็วพอสำหรับฉันเนื่องจากฉันกำลังทำการ "ล้างข้อมูล" แบบครั้งเดียว
Florian Pilz

6
วิธีแรนด์ของรูบี้คืนค่าน้อยกว่าจำนวนที่ระบุดังนั้นคุณจะต้องการrand_id = rand(Product.count) + 1หรือคุณจะไม่ได้รับข้อมูลล่าสุด
Ritchie

4
หมายเหตุrandom1จะไม่ทำงานหากคุณลบแถวในตาราง (จำนวนจะน้อยกว่ารหัสสูงสุดและคุณจะไม่สามารถเลือกแถวที่มีรหัสสูงได้)
นิโคลัส

การใช้random2สามารถปรับปรุงได้โดย#orderใช้คอลัมน์ที่จัดทำดัชนี
Carson Reinke

18

ไม่จำเป็นต้องยากขนาดนั้น

ids = Model.pluck(:id)
random_model = Model.find(ids.sample)

pluckส่งคืนอาร์เรย์ของ id ทั้งหมดในตาราง sampleวิธีการในอาร์เรย์ที่แสดงรหัสสุ่มจากอาร์เรย์

สิ่งนี้ควรทำงานได้ดีโดยมีความน่าจะเป็นที่เท่ากันในการเลือกและรองรับตารางที่มีแถวที่ถูกลบ คุณสามารถผสมกับข้อ จำกัด ได้

User.where(favorite_day: "Friday").pluck(:id)

และเลือกผู้ใช้แบบสุ่มที่ชอบวันศุกร์มากกว่าแค่ผู้ใช้คนอื่น


8
นี่สะอาดและใช้งานได้กับโต๊ะเล็ก ๆ หรือการใช้ครั้งเดียวเพียงแค่สังเกตว่ามันจะไม่ขยาย บนตาราง 3M การถอน ID ใช้เวลาประมาณ 15 วินาทีสำหรับฉันบน MariaDB
mahemoff

2
นั่นเป็นจุดที่ดี คุณพบวิธีการแก้ปัญหาทางเลือกที่เร็วขึ้นในขณะที่ยังคงคุณภาพเดิมหรือไม่
Niels B.

โซลูชันออฟเซ็ตที่ยอมรับไม่รักษาคุณภาพเดียวกันหรือไม่
mahemoff

ไม่มันไม่สนับสนุนเงื่อนไขและไม่มีความน่าจะเป็นที่เท่ากันของการเลือกสำหรับตารางที่มีระเบียนที่ถูกลบ
Niels B.

1
ลองนึกถึงถ้าคุณใช้ข้อ จำกัด เมื่อทั้งการนับและการเลือกด้วยการชดเชยเทคนิคควรใช้งานได้ ฉันนึกภาพนำไปใช้กับการนับเท่านั้น
Niels B.

15

มันไม่ได้แนะนำว่าให้คุณใช้วิธีการแก้ปัญหานี้ แต่ถ้าด้วยเหตุผลบางอย่างที่คุณจริงๆต้องการที่จะสุ่มเลือกบันทึกในขณะที่เพียงทำให้การสืบค้นฐานข้อมูลหนึ่งที่คุณสามารถใช้sampleวิธีการจากArray ชั้นทับทิมซึ่งจะช่วยให้คุณสามารถเลือกไอเทมสุ่ม จากอาร์เรย์

Model.all.sample

วิธีนี้ต้องใช้แบบสอบถามฐานข้อมูลเท่านั้น แต่ช้ากว่าทางเลือกModel.offset(rand(Model.count)).firstที่ต้องการแบบสอบถามฐานข้อมูลสองรายการอย่างมีนัยสำคัญ


99
อย่าทำอย่างนี้. เคย.
Zabba

5
หากคุณมี 100k แถวในฐานข้อมูลของคุณทั้งหมดเหล่านี้จะต้องโหลดลงในหน่วยความจำ
Venkat D.

3
แน่นอนว่ามันไม่แนะนำสำหรับโค้ดเรียลไทม์ที่ใช้ในการผลิต แต่ฉันชอบวิธีนี้มันชัดเจนมากที่จะใช้สำหรับสถานการณ์พิเศษเช่นการสร้างฐานข้อมูลด้วยค่าปลอม
fguillen

13
ได้โปรด - อย่าพูดไม่เคย นี่เป็นโซลูชันที่ยอดเยี่ยมสำหรับการดีบักเวลาพัฒนาหากตารางมีขนาดเล็ก (และถ้าคุณใช้ตัวอย่างการดีบักอาจเป็นกรณีการใช้งาน)
mahemoff

ฉันใช้สำหรับการเพาะและเป็นสิ่งที่ดีสำหรับฉัน นอกจากนี้ Model.all.sample (n) ก็ทำงานด้วยเช่นกัน :)
Arnaldo Ignacio Gaspar Véjar

13

ฉันสร้างพลอย 3 เม็ดเพื่อจัดการสิ่งนี้:

https://github.com/spilliton/randumb

อนุญาตให้คุณทำสิ่งนี้:

Model.where(:column => "value").random(10)

7
ในเอกสารของอัญมณีพวกเขาอธิบายว่า"randumb เพียงแค่เพิ่มเพิ่มเติมORDER BY RANDOM()(หรือRAND()สำหรับ mysql) ในแบบสอบถามของคุณ" - ดังนั้นความคิดเห็นเกี่ยวกับประสิทธิภาพที่ไม่ดีที่กล่าวถึงในความคิดเห็นต่อคำตอบโดย @semanticart จะมีผลเมื่อใช้อัญมณีนี้ด้วย แต่อย่างน้อยก็เป็น DB อิสระ
นิโคลัส

8

ฉันใช้สิ่งนี้บ่อยๆจากคอนโซลฉันขยาย ActiveRecord ใน initializer - ตัวอย่าง Rails 4:

class ActiveRecord::Base
  def self.random
    self.limit(1).offset(rand(self.count)).first
  end
end

ฉันสามารถโทรFoo.randomเพื่อนำกลับมาบันทึกแบบสุ่ม


1
คุณต้องการlimit(1)ไหม ActiveRecord#firstควรฉลาดพอที่จะทำเช่นนั้นได้
tokland

6

หนึ่งแบบสอบถามใน Postgres:

User.order('RANDOM()').limit(3).to_sql # Postgres example
=> "SELECT "users".* FROM "users" ORDER BY RANDOM() LIMIT 3"

การใช้ออฟเซตสองเคียวรี:

offset = rand(User.count) # returns an integer between 0 and (User.count - 1)
Model.offset(offset).limit(1)

1
ไม่จำเป็นต้องใช้ -1, rand นับได้ถึง NUM - 1
anemaria20

ขอขอบคุณเปลี่ยน: +1:
Thomas Klemm

5

การอ่านสิ่งเหล่านี้ไม่ได้ให้ความมั่นใจกับฉันมากนักว่าสิ่งเหล่านี้จะทำงานได้ดีที่สุดในสถานการณ์เฉพาะของฉันกับ Rails 5 และ MySQL / Maria 5.5 ดังนั้นฉันจึงทดสอบคำตอบบางส่วนใน ~ 65000 บันทึกและมีสองวิธี:

  1. RAND () ที่มี a limitเป็นผู้ชนะที่ชัดเจน
  2. อย่าใช้+plucksample
def random1
  Model.find(rand((Model.last.id + 1)))
end

def random2
  Model.order("RAND()").limit(1)
end

def random3
  Model.pluck(:id).sample
end

n = 100
Benchmark.bm(7) do |x|
  x.report("find:")    { n.times {|i| random1 } }
  x.report("order:")   { n.times {|i| random2 } }
  x.report("pluck:")   { n.times {|i| random3 } }
end

              user     system      total        real
find:     0.090000   0.000000   0.090000 (  0.127585)
order:    0.000000   0.000000   0.000000 (  0.002095)
pluck:    6.150000   0.000000   6.150000 (  8.292074)

คำตอบนี้สังเคราะห์ตรวจสอบและอัปเดตคำตอบของโมฮาเหม็ดรวมถึงความคิดเห็นของนามิหวางในแบบเดียวกันและความคิดเห็นของฟลอเรียนปิลซ์ในคำตอบที่ยอมรับ - โปรดส่งคะแนนโหวตให้พวกเขา!


3

คุณสามารถใช้Arrayเมธอดsampleเมธอดsampleส่งคืนออบเจกต์แบบสุ่มจากอาเรย์เพื่อใช้งานคุณเพียงแค่ต้องเอ็กซีคิวต์ในActiveRecordเคียวรีแบบง่ายๆที่ส่งคืนคอลเลกชันเช่น:

User.all.sample

จะส่งคืนสิ่งนี้:

#<User id: 25, name: "John Doe", email: "admin@example.info", created_at: "2018-04-16 19:31:12", updated_at: "2018-04-16 19:31:12">

ฉันจะไม่แนะนำให้ทำงานกับวิธีอาร์เรย์ในขณะที่ใช้ AR วิธีนี้ใช้เวลาเกือบ 8 เท่าในเวลาที่order('rand()').limit(1)ทำหน้าที่ "เดียวกัน" (กับระเบียน ~ 10K)
Sebastian Palma

3

ขอแนะนำอัญมณีนี้สำหรับการบันทึกแบบสุ่มซึ่งออกแบบมาเป็นพิเศษสำหรับตารางที่มีแถวข้อมูลจำนวนมาก:

https://github.com/haopingfan/quick_random_records

คำตอบอื่น ๆ ทั้งหมดทำงานได้ไม่ดีกับฐานข้อมูลขนาดใหญ่ยกเว้น gem นี้:

  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 คนเท่านั้น ยิ่งคุณมีบันทึกมากเท่าใดความแตกต่างของประสิทธิภาพก็จะยิ่งมากขึ้นเท่านั้น


2

หากคุณต้องการเลือกผลลัพธ์แบบสุ่มภายในขอบเขตที่ระบุ :

scope :male_names, -> { where(sex: 'm') }
number_of_results = 10

rand = Names.male_names.pluck(:id).sample(number_of_results)
Names.where(id: rand)

1

sampleวิธีทับทิมสำหรับการสุ่มหยิบจากรายการคือ ต้องการสร้างประสิทธิภาพsampleสำหรับ ActiveRecord และจากคำตอบก่อนหน้านี้ฉันใช้:

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 }

นี่จะเป็นข้อความค้นหาหนึ่งรายการหากขนาดของแบบจำลองนั้นถูกแคชไว้แล้ว


1

Rails 4.2 และ Oracle :

สำหรับ oracle คุณสามารถกำหนดขอบเขตในรุ่นของคุณได้เช่น:

scope :random_order, -> {order('DBMS_RANDOM.RANDOM')}

หรือ

scope :random_order, -> {order('DBMS_RANDOM.VALUE')}

แล้วสำหรับตัวอย่างเรียกมันว่า:

Model.random_order.take(10)

หรือ

Model.random_order.limit(5)

แน่นอนคุณสามารถสั่งซื้อโดยไม่มีขอบเขตเช่น:

Model.all.order('DBMS_RANDOM.RANDOM') # or DBMS_RANDOM.VALUE respectively

คุณสามารถทำได้ด้วย postgres กับorder('random()'และ MySQL ด้วยorder('rand()')เช่นกัน นี่เป็นคำตอบที่ดีที่สุดอย่างแน่นอน
jrochkind

1

สำหรับฐานข้อมูล MySQL ลองใช้: Model.order ("RAND ()") ก่อน


สิ่งนี้ใช้ไม่ได้กับ mysql .. คุณควร incloude เป็นอย่างน้อยสิ่งที่เครื่อง DB ควรใช้กับมัน
Arnold Roa

ขออภัยมีการพิมพ์ผิด แก้ไขแล้ว ควรทำงานกับ mysql (เท่านั้น)
Vadim Eremeev

1

หากคุณใช้ PostgreSQL 9.5+ คุณสามารถใช้ประโยชน์จากTABLESAMPLEการเลือกบันทึกแบบสุ่ม

วิธีการสุ่มตัวอย่างเริ่มต้นที่สอง ( SYSTEMและBERNOULLI) ต้องการให้คุณระบุจำนวนแถวที่จะส่งกลับเป็นเปอร์เซ็นต์ของจำนวนแถวทั้งหมดในตาราง

-- Fetch 10% of the rows in the customers table.
SELECT * FROM customers TABLESAMPLE BERNOULLI(10);

สิ่งนี้ต้องทราบจำนวนของระเบียนในตารางเพื่อเลือกเปอร์เซ็นต์ที่เหมาะสมซึ่งอาจไม่สามารถค้นหาได้อย่างรวดเร็ว โชคดีที่มีtsm_system_rowsโมดูลที่ให้คุณระบุจำนวนแถวที่จะส่งกลับโดยตรง

CREATE EXTENSION tsm_system_rows;

-- Fetch a single row from the customers table.
SELECT * FROM customers TABLESAMPLE SYSTEM_ROWS(1);

หากต้องการใช้สิ่งนี้ภายใน ActiveRecord ก่อนอื่นให้เปิดใช้งานส่วนขยายภายในการโยกย้าย:

class EnableTsmSystemRowsExtension < ActiveRecord::Migration[5.0]
  def change
    enable_extension "tsm_system_rows"
  end
end

จากนั้นปรับเปลี่ยนfromส่วนของแบบสอบถาม:

customer = Customer.from("customers TABLESAMPLE SYSTEM_ROWS(1)").first

ฉันไม่ทราบว่าSYSTEM_ROWSวิธีการสุ่มตัวอย่างจะสุ่มทั้งหมดหรือเพียงแค่คืนแถวแรกจากหน้าสุ่ม

ส่วนใหญ่ของข้อมูลนี้ถูกนำมาจากบล็อกโพสต์ 2ndQuadrant เขียนโดย Gulcin Yildirim


1

หลังจากเห็นคำตอบมากมายฉันตัดสินใจที่จะเปรียบเทียบพวกเขาทั้งหมดในฐานข้อมูล PostgreSQL (9.6.3) ของฉัน ฉันใช้ตารางขนาดเล็กกว่า 100,000 ตารางและกำจัด Model.order ("RANDOM ()") ครั้งแรกเนื่องจากมันมีขนาดสองคำสั่งช้ากว่า

การใช้ตารางที่มี 2,500,000 รายการที่มี 10 คอลัมน์มือที่ชนะเลิศคือวิธีถอนออกเร็วกว่านักวิ่งเกือบ 8 เท่า (ชดเชยฉันวิ่งแค่นี้บนเซิร์ฟเวอร์ในพื้นที่เพื่อให้ตัวเลขนั้นอาจสูงเกินจริง วิธีการคือสิ่งที่ฉันจะใช้ท้ายที่สุดนอกจากนี้ยังเป็นที่น่าสังเกตว่านี่อาจทำให้เกิดปัญหาคือคุณถอนมากกว่า 1 ผลในแต่ละครั้งเนื่องจากแต่ละอันจะไม่ซ้ำกันหรือที่สุ่มน้อย

Pluck ชนะการทำงาน 100 ครั้งในตารางแถวที่ 25,000,000 ของฉัน Edit: จริง ๆ แล้วเวลานี้รวมถึงการถอนในวงถ้าฉันเอามันออกมามันจะวิ่งเร็วเท่าการทำซ้ำง่าย ๆ บน id อย่างไรก็ตาม; ใช้ RAM ในปริมาณที่พอสมควร

RandomModel                 user     system      total        real
Model.find_by(id: i)       0.050000   0.010000   0.060000 (  0.059878)
Model.offset(rand(offset)) 0.030000   0.000000   0.030000 ( 55.282410)
Model.find(ids.sample)     6.450000   0.050000   6.500000 (  7.902458)

นี่คือข้อมูลกำลังทำงาน 2,000 ครั้งในตารางแถวละ 100,000 แถวของฉันเพื่อแยกแยะแบบสุ่ม

RandomModel       user     system      total        real
find_by:iterate  0.010000   0.000000   0.010000 (  0.006973)
offset           0.000000   0.000000   0.000000 (  0.132614)
"RANDOM()"       0.000000   0.000000   0.000000 ( 24.645371)
pluck            0.110000   0.020000   0.130000 (  0.175932)

1

คำถามที่เก่ามาก แต่ด้วย:

rand_record = Model.all.shuffle

คุณได้อาร์เรย์ของเรคคอร์ดเรียงตามลำดับแบบสุ่ม ไม่จำเป็นต้องมีอัญมณีหรือสคริปต์

หากคุณต้องการบันทึกหนึ่งรายการ:

rand_record = Model.all.shuffle.first

1
ไม่ใช่ตัวเลือกที่ดีที่สุดเนื่องจากจะโหลดระเบียนทั้งหมดลงในหน่วยความจำ นอกจากนี้shuffle.first==.sample
Andrew Rozhenko

0

ฉันใหม่เอี่ยมถึง RoR แต่ฉันได้รับสิ่งนี้เพื่อฉัน:

 def random
    @cards = Card.all.sort_by { rand }
 end

มันมาจาก:

วิธีการสุ่มเรียงลำดับ (ช่วงชิง) อาร์เรย์ใน Ruby?


4
สิ่งที่ไม่ดีเกี่ยวกับมันคือมันจะโหลดการ์ดทั้งหมดจากฐานข้อมูล มันมีประสิทธิภาพมากกว่าที่จะทำในฐานข้อมูล
Anton Kuzmin

array.shuffleนอกจากนี้คุณยังสามารถสับเปลี่ยนกับอาร์เรย์ อย่างไรก็ตามระวังเช่นเดียวกับที่Card.allจะโหลดบันทึกการ์ดทั้งหมดลงในหน่วยความจำซึ่งจะทำให้ประสิทธิภาพของวัตถุที่เราพูดถึงมากขึ้น
โทมัสไคลม์


0

ฉันลองตัวอย่างของ Sam บนแอพของฉันโดยใช้ราง 4.2.8 ของเบนช์มาร์ก (ฉันใส่ 1.Category.count สำหรับการสุ่มเพราะถ้าการสุ่มใช้ 0 มันจะทำให้เกิดข้อผิดพลาด (ActiveRecord :: RecordNotFound: ไม่พบ หมวดหมู่ที่มี 'id' = 0)) และของฉันคือ:

 def random1
2.4.1 :071?>   Category.find(rand(1..Category.count))
2.4.1 :072?>   end
 => :random1
2.4.1 :073 > def random2
2.4.1 :074?>    Category.offset(rand(1..Category.count))
2.4.1 :075?>   end
 => :random2
2.4.1 :076 > def random3
2.4.1 :077?>   Category.offset(rand(1..Category.count)).limit(rand(1..3))
2.4.1 :078?>   end
 => :random3
2.4.1 :079 > def random4
2.4.1 :080?>    Category.pluck(rand(1..Category.count))
2.4.1 :081?>
2.4.1 :082 >     end
 => :random4
2.4.1 :083 > n = 100
 => 100
2.4.1 :084 > Benchmark.bm(7) do |x|
2.4.1 :085 >     x.report("find") { n.times {|i| random1 } }
2.4.1 :086?>   x.report("offset") { n.times {|i| random2 } }
2.4.1 :087?>   x.report("offset_limit") { n.times {|i| random3 } }
2.4.1 :088?>   x.report("pluck") { n.times {|i| random4 } }
2.4.1 :089?>   end

                  user      system      total     real
find            0.070000   0.010000   0.080000 (0.118553)
offset          0.040000   0.010000   0.050000 (0.059276)
offset_limit    0.050000   0.000000   0.050000 (0.060849)
pluck           0.070000   0.020000   0.090000 (0.099065)

0

.order('RANDOM()').limit(limit)ดูเรียบร้อย แต่จะช้าสำหรับตารางขนาดใหญ่เนื่องจากต้องการดึงข้อมูลและเรียงลำดับแถวทั้งหมดแม้ว่าlimitจะเป็น 1 (ภายในฐานข้อมูล แต่ไม่อยู่ใน Rails) ฉันไม่แน่ใจเกี่ยวกับ MySQL แต่สิ่งนี้เกิดขึ้นใน Postgres คำอธิบายเพิ่มเติมในที่นี่และที่นี่

ทางออกหนึ่งสำหรับตารางที่มีขนาดใหญ่เป็น.from("products TABLESAMPLE SYSTEM(0.5)")ที่หมายถึง0.5 0.5%อย่างไรก็ตามฉันพบว่าวิธีนี้ยังช้าถ้าคุณมีWHEREเงื่อนไขที่กรองแถวจำนวนมากออก ฉันเดาว่าเป็นเพราะTABLESAMPLE SYSTEM(0.5)ดึงข้อมูลทุกแถวก่อนWHEREนำไปใช้กับเงื่อนไข

โซลูชันอื่นสำหรับตารางขนาดใหญ่ (แต่ไม่สุ่มมาก) คือ:

products_scope.limit(sample_size).sample(limit)

ที่sample_sizeสามารถ100( แต่ไม่ใหญ่เกินไปมิฉะนั้นมันช้าและสิ้นเปลืองหน่วยความจำมาก) และสามารถlimit 1โปรดทราบว่าถึงแม้ว่านี่จะเร็ว แต่ก็ไม่ได้สุ่มแบบสุ่ม แต่เป็นการสุ่มภายในsample_sizeระเบียนเท่านั้น

PS: ผลลัพธ์มาตรฐานในคำตอบข้างต้นไม่น่าเชื่อถือ (อย่างน้อยใน Postgres) เนื่องจากบางคำสั่ง DB ที่รันในครั้งที่ 2 สามารถเร็วกว่าการรันในครั้งแรกอย่างมีนัยสำคัญเนื่องจากแคช DB และน่าเสียดายที่ไม่มีวิธีง่ายๆในการปิดใช้งานแคชใน Postgres เพื่อให้การวัดประสิทธิภาพเหล่านี้เชื่อถือได้


0

นอกเหนือจากการใช้งานRANDOM()แล้วคุณยังสามารถโยนมันลงในขอบเขต:

class Thing
  scope :random, -> (limit = 1) {
    order('RANDOM()').
    limit(limit)
  }
end

หรือถ้าคุณไม่นึกว่ามันเป็นเพียงแค่โยนมันลงในวิธีการเรียน ตอนนี้ทำงานพร้อมกับThing.randomThing.random(n)


0

ขึ้นอยู่กับความหมายของ "สุ่ม" และสิ่งที่คุณต้องการจะทำจริงtakeอาจเพียงพอ

โดย "ความหมาย" ของการสุ่มฉันหมายถึง:

  • คุณหมายถึงให้องค์ประกอบใด ๆ ที่ฉันไม่สนใจตำแหน่งหรือไม่ ก็เพียงพอแล้ว
  • ตอนนี้ถ้าคุณหมายถึง "ให้องค์ประกอบใด ๆ กับความน่าจะเป็นธรรมที่การทดลองซ้ำ ๆ จะให้องค์ประกอบที่แตกต่างจากชุด" จากนั้นให้บังคับ "โชค" ด้วยวิธีการใด ๆ ที่กล่าวถึงในคำตอบอื่น ๆ

ตัวอย่างเช่นสำหรับการทดสอบข้อมูลตัวอย่างจะได้รับการสร้างขึ้นแบบสุ่ม anyways จึงเป็นมากกว่าเพียงพอและจะซื่อสัตย์แม้takefirst

https://guides.rubyonrails.org/active_record_querying.html#take

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.