เลือกแถวสุ่มจากตาราง sqlite


119

ฉันมีsqliteตารางที่มีสคีมาต่อไปนี้:

CREATE TABLE foo (bar VARCHAR)

ฉันใช้ตารางนี้เป็นที่จัดเก็บรายการสตริง

ฉันจะเลือกแถวสุ่มจากตารางนี้ได้อย่างไร?


คำตอบ:


213

ดูที่การเลือกแถวสุ่มจากตาราง SQLite

SELECT * FROM table ORDER BY RANDOM() LIMIT 1;

1
จะขยายโซลูชันนี้ไปสู่การเข้าร่วมได้อย่างไร เมื่อใช้SELECT a.foo FROM a JOIN b ON a.id = b.id WHERE b.bar = 2 ORDER BY RANDOM() LIMIT 1;ฉันจะได้แถวเดียวกันเสมอ
Helmut Grohne

เป็นไปได้ไหมที่จะสุ่มหมายเลข เช่น Book of the day ที่เพาะด้วย unix epoc สำหรับวันนี้ตอนเที่ยงดังนั้นมันจึงแสดงหนังสือเล่มเดียวกันทั้งวันแม้ว่าจะมีการเรียกใช้การสืบค้นหลายครั้งก็ตาม ใช่ฉันรู้ว่าการแคชมีประสิทธิภาพมากกว่าสำหรับกรณีการใช้งานนี้เป็นเพียงตัวอย่าง
danielson317

FWIW คำถามของฉันมีคำตอบที่นี่ และคำตอบคือคุณไม่สามารถสุ่มหมายเลข stackoverflow.com/questions/24256258/…
danielson317

31

วิธีแก้ปัญหาต่อไปนี้เร็วกว่าของ anktastic (จำนวน (*) มาก แต่ถ้าคุณแคชได้ความแตกต่างก็ไม่ควรใหญ่ขนาดนั้น) ซึ่งเร็วกว่า "order by random ()" มาก เมื่อคุณมีแถวจำนวนมากแม้ว่าจะมีความไม่สม่ำเสมอเล็กน้อย

หาก rowids ของคุณค่อนข้างแน่น (เช่นการลบเพียงเล็กน้อย) คุณสามารถทำสิ่งต่อไปนี้ได้ (โดยใช้(select max(rowid) from foo)+1แทนที่จะmax(rowid)+1ให้ประสิทธิภาพที่ดีขึ้นตามที่อธิบายไว้ในความคิดเห็น):

select * from foo where rowid = (abs(random()) % (select (select max(rowid) from foo)+1));

หากคุณมีรูบางครั้งคุณจะพยายามเลือก rowid ที่ไม่มีอยู่และการเลือกจะส่งคืนชุดผลลัพธ์ที่ว่างเปล่า หากไม่สามารถยอมรับได้คุณสามารถระบุค่าเริ่มต้นได้ดังนี้:

select * from foo where rowid = (abs(random()) % (select (select max(rowid) from foo)+1)) or rowid = (select max(rowid) from node) order by rowid limit 1;

โซลูชันที่สองนี้ไม่สมบูรณ์แบบ: การกระจายของความน่าจะเป็นจะสูงกว่าในแถวสุดท้าย (อันที่มี rowid สูงสุด) แต่ถ้าคุณใส่ข้อมูลลงในตารางบ่อยๆมันจะกลายเป็นเป้าหมายเคลื่อนที่และการกระจายของความน่าจะเป็นควรเป็น ดีกว่ามาก.

วิธีแก้ปัญหาอีกวิธีหนึ่งหากคุณมักจะเลือกสิ่งที่สุ่มจากตารางที่มีรูจำนวนมากคุณอาจต้องการสร้างตารางที่มีแถวของตารางเดิมที่เรียงลำดับแบบสุ่ม:

create table random_foo(foo_id);

จากนั้นคาบเวลาเติมตาราง random_foo อีกครั้ง

delete from random_foo;
insert into random_foo select id from foo;

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

แต่อีกวิธีหนึ่งที่ฉันเพิ่งพบในรายชื่ออีเมลคือการใส่ทริกเกอร์ในการลบเพื่อย้ายแถวที่มี rowid ใหญ่ที่สุดไปยังแถวที่ถูกลบในปัจจุบันเพื่อไม่ให้เหลือช่องว่าง

สุดท้ายโปรดทราบว่าลักษณะการทำงานของ rowid และการเพิ่มอัตโนมัติของคีย์หลักจำนวนเต็มไม่เหมือนกัน (ด้วย rowid เมื่อมีการแทรกแถวใหม่จะมีการเลือก max (rowid) +1 ในขณะที่ค่านั้นเป็นค่าที่สูงที่สุดเท่าที่เคยเห็นมา + 1 สำหรับ คีย์หลัก) ดังนั้นโซลูชันสุดท้ายจะไม่ทำงานกับการเพิ่มอัตโนมัติใน random_foo แต่วิธีอื่น ๆ จะ


เหมือนที่ฉันเพิ่งเห็นในรายชื่ออีเมลแทนที่จะใช้วิธีการสำรอง (วิธีที่ 2) คุณสามารถใช้ rowid> = [random] แทน = แต่จริงๆแล้วมันช้ามากเมื่อเทียบกับวิธีที่ 2
Suzanne Dupéron

3
นี่คือคำตอบที่ดี อย่างไรก็ตามมันมีปัญหาอย่างหนึ่ง SELECT max(rowid) + 1จะเป็นการค้นหาที่ช้า - ต้องมีการสแกนแบบเต็มตาราง SQLite SELECT max(rowid)เพียงเพิ่มประสิทธิภาพการค้นหา ดังนั้นคำตอบนี้จะได้รับการปรับปรุงโดย: select * from foo where rowid = (abs(random()) % (select (select max(rowid) from foo)+1)); ดูข้อมูลเพิ่มเติมได้ที่sqlite.1065341.n5.nabble.com/…
dasl

19

คุณต้องใส่"order by RANDOM ()"ในข้อความค้นหาของคุณ

ตัวอย่าง:

select * from quest order by RANDOM();

มาดูตัวอย่างที่สมบูรณ์

  1. สร้างตาราง:
CREATE TABLE  quest  (
    id  INTEGER PRIMARY KEY AUTOINCREMENT,
    quest TEXT NOT NULL,
    resp_id INTEGER NOT NULL
);

การแทรกค่าบางอย่าง:

insert into quest(quest, resp_id) values ('1024/4',6), ('256/2',12), ('128/1',24);

เลือกเริ่มต้น:

select * from quest;

| id |   quest  | resp_id |
   1     1024/4       6
   2     256/2       12
   3     128/1       24
--

เลือกแบบสุ่ม:

select * from quest order by RANDOM();
| id |   quest  | resp_id |
   3     128/1       24
   1     1024/4       6
   2     256/2       12
--
* แต่ละครั้งที่คุณเลือกลำดับจะแตกต่างกัน

หากคุณต้องการกลับเพียงแถวเดียว

select * from quest order by RANDOM() LIMIT 1;
| id |   quest  | resp_id |
   2     256/2       12
--
* แต่ละครั้งที่คุณเลือกผลตอบแทนจะแตกต่างกัน


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

2
ฉันชอบวิธีนี้เนื่องจากช่วยให้ฉันค้นหา n บรรทัดได้ ในกรณีของฉันฉันต้องการตัวอย่างสุ่ม 100 ตัวอย่างจากฐานข้อมูล - ORDER BY RANDOM () รวมกับ LIMIT 100 ทำเช่นนั้น
mnr

17

สิ่งที่เกี่ยวกับ:

SELECT COUNT(*) AS n FROM foo;

จากนั้นเลือกตัวเลขสุ่มmใน [0, n) และ

SELECT * FROM foo LIMIT 1 OFFSET m;

คุณสามารถบันทึกหมายเลขแรก ( n ) ไว้ที่ไหนสักแห่งและอัปเดตเมื่อจำนวนฐานข้อมูลเปลี่ยนแปลงเท่านั้น ด้วยวิธีนี้คุณไม่จำเป็นต้องทำการ SELECT COUNT ทุกครั้ง


1
เป็นวิธีที่รวดเร็วดี มันไม่ได้สรุปได้ดีนักในการเลือกมากกว่า 1 แถว แต่ OP ขอเพียง 1 ดังนั้นฉันเดาว่าไม่เป็นไร
Ken Williams

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

@KenWilliams ฐานข้อมูลทั้งหมดมีปัญหาเดียวกันกับ `` OFFSET '' เป็นวิธีที่ไม่มีประสิทธิภาพมากในการสืบค้นฐานข้อมูลเพราะต้องอ่านว่าหลายแถวแม้ว่าจะส่งคืนเพียง 1
Jonathan Allen

1
โปรดทราบว่าฉันกำลังพูดถึง / ขนาดคงที่ / บันทึก - มันควรจะง่ายในการสแกนโดยตรงไปยังไบต์ที่ถูกต้องในข้อมูล ( ไม่ใช่การอ่านหลายแถว) แต่พวกเขาจะต้องใช้การเพิ่มประสิทธิภาพอย่างชัดเจน
เคนวิลเลียมส์

@KenWilliams: ไม่มีระเบียนขนาดคงที่ใน SQLite มีการพิมพ์แบบไดนามิกและข้อมูลไม่จำเป็นต้องตรงกับความสัมพันธ์ที่ประกาศไว้ ( sqlite.org/fileformat2.html#section_2_1 ) ทุกอย่างจะถูกเก็บไว้ในหน้า b-tree ดังนั้นไม่ว่าจะด้วยวิธีใดก็ตามอย่างน้อยก็ต้องทำการค้นหา b-tree ไปทางใบไม้ เพื่อให้บรรลุผลสำเร็จอย่างมีประสิทธิภาพคุณจะต้องจัดเก็บขนาดของทรีย่อยพร้อมกับตัวชี้ลูกแต่ละตัว มันจะเป็นค่าใช้จ่ายที่มากเกินไปสำหรับผลประโยชน์เพียงเล็กน้อยเนื่องจากคุณยังไม่สามารถเพิ่มประสิทธิภาพ OFFSET สำหรับการรวมการสั่งซื้อโดย ฯลฯ ... (และไม่ได้กำหนด ORDER BY คำสั่งซื้อ)
Yakov Galka

13
SELECT   bar
FROM     foo
ORDER BY Random()
LIMIT    1

11
เนื่องจากจะเลือกเนื้อหาทั้งตารางก่อนสิ่งนี้จะไม่ใช้เวลานานสำหรับตารางขนาดใหญ่หรือไม่?
Alex_coder

1
คุณไม่สามารถ จำกัด ขอบเขตโดยใช้เงื่อนไข "WHERE" ได้หรือไม่?
jldupont

11

นี่คือการปรับเปลี่ยนโซลูชันของ @ ank:

SELECT * 
FROM table
LIMIT 1 
OFFSET ABS(RANDOM()) % MAX((SELECT COUNT(*) FROM table), 1)

โซลูชันนี้ใช้ได้กับดัชนีที่มีช่องว่างเช่นกันเนื่องจากเราสุ่มค่าชดเชยในช่วง [0, count) MAXใช้สำหรับจัดการเคสที่มีโต๊ะว่าง

นี่คือผลการทดสอบอย่างง่ายบนตารางที่มีแถว 16k:

sqlite> .timer on
sqlite> select count(*) from payment;
16049
Run Time: real 0.000 user 0.000140 sys 0.000117

sqlite> select payment_id from payment limit 1 offset abs(random()) % (select count(*) from payment);
14746
Run Time: real 0.002 user 0.000899 sys 0.000132
sqlite> select payment_id from payment limit 1 offset abs(random()) % (select count(*) from payment);
12486
Run Time: real 0.001 user 0.000952 sys 0.000103

sqlite> select payment_id from payment order by random() limit 1;
3134
Run Time: real 0.015 user 0.014022 sys 0.000309
sqlite> select payment_id from payment order by random() limit 1;
9407
Run Time: real 0.018 user 0.013757 sys 0.000208

4

ฉันคิดวิธีแก้ปัญหาต่อไปนี้สำหรับฐานข้อมูล sqlite3 ขนาดใหญ่ :

SELECT * FROM foo WHERE rowid = abs(random()) % (SELECT max(rowid) FROM foo) + 1; 

ฟังก์ชัน abs (X) ส่งกลับค่าสัมบูรณ์ของอาร์กิวเมนต์ตัวเลข X

ฟังก์ชัน random () ส่งคืนจำนวนเต็มสุ่มหลอกระหว่าง -9223372036854775808 และ +9223372036854775807

% ตัวดำเนินการจะส่งออกค่าจำนวนเต็มของตัวถูกดำเนินการด้านซ้ายของโมดูโลตัวถูกดำเนินการด้านขวา

สุดท้ายคุณเพิ่ม +1 เพื่อป้องกันไม่ให้ rowid เท่ากับ 0


1
ลองดี แต่ฉันไม่คิดว่าจะได้ผล จะเกิดอะไรขึ้นถ้าแถวที่มี rowId = 5 ถูกลบ แต่ rowIds 1,2,3,4,6,7,8,9,10 ยังคงอยู่ จากนั้นหากสุ่ม rowId ที่เลือกคือ 5 คิวรีนี้จะไม่ส่งคืน
Calicoder
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.