ฉันจะเขียนแบบสอบถามที่ดีที่สุดที่เลือก 10 แถวสุ่มจากทั้งหมด 600k ได้อย่างไร
ฉันจะเขียนแบบสอบถามที่ดีที่สุดที่เลือก 10 แถวสุ่มจากทั้งหมด 600k ได้อย่างไร
คำตอบ:
โพสต์ที่ยอดเยี่ยมในการจัดการหลายกรณีจากง่ายไปยังช่องว่างไปจนถึงไม่สม่ำเสมอกับช่องว่าง
http://jan.kneschke.de/projects/mysql/order-by-rand/
สำหรับกรณีทั่วไปส่วนใหญ่นี่คือวิธีที่คุณทำ:
SELECT name
FROM random AS r1 JOIN
(SELECT CEIL(RAND() *
(SELECT MAX(id)
FROM random)) AS id)
AS r2
WHERE r1.id >= r2.id
ORDER BY r1.id ASC
LIMIT 1
นี่สมมติว่าการแจกแจงของ id นั้นเท่ากันและสามารถมีช่องว่างในรายการ id ได้ ดูบทความสำหรับตัวอย่างขั้นสูงเพิ่มเติม
mysqli_fetch_assoc($result)
หรือไม่ หรือว่า 10 ผลลัพธ์ไม่จำเป็นต้องแยกแยะความแตกต่าง?
SELECT column FROM table
ORDER BY RAND()
LIMIT 10
ไม่ใช่วิธีที่มีประสิทธิภาพ แต่ใช้ได้ผล
ORDER BY RAND()
ค่อนข้างช้า
SELECT words, transcription, translation, sound FROM vocabulary WHERE menu_id=$menuId ORDER BY RAND() LIMIT 10
ใช้เวลา 0.0010 โดยไม่ จำกัด 10 เอา 0.0012 (ในตารางที่ 3500 คำ)
แบบสอบถามง่ายๆที่มีประสิทธิภาพดีเยี่ยมและใช้งานได้กับช่องว่าง :
SELECT * FROM tbl AS t1 JOIN (SELECT id FROM tbl ORDER BY RAND() LIMIT 10) as t2 ON t1.id=t2.id
ข้อความค้นหาในตาราง 200K นี้ใช้เวลา0.08 วินาทีและรุ่นปกติ (SELECT * จาก tbl ORDER BY RAND () LIMIT 10) ใช้เวลา0.35 วินาทีในเครื่องของฉัน
สิ่งนี้เร็วเพราะเฟสการเรียงลำดับใช้คอลัมน์ ID ที่จัดทำดัชนี คุณสามารถดูพฤติกรรมนี้ได้ในคำอธิบาย:
เลือก * จากคำสั่ง tbl ตามแรนด์ () จำกัด 10:
SELECT * จาก tbl AS t1 เข้าร่วม (เลือก id จาก tbl เรียงลำดับตามแรนด์ () จำกัด 10) เป็น t2 บน t1.id = t2.id
เวอร์ชันถ่วงน้ำหนัก : https://stackoverflow.com/a/41577458/893432
ฉันได้รับข้อความค้นหาอย่างรวดเร็ว (ประมาณ 0.5 วินาที) ด้วยซีพียูช้าเลือก 10 แถวแบบสุ่มใน 400K ลงทะเบียนฐานข้อมูล MySQL ขนาดแคช 2Gb ไม่ลงทะเบียน ดูที่นี่รหัสของฉัน: เลือกแถวสุ่มใน MySQL ได้อย่างรวดเร็ว
<?php
$time= microtime_float();
$sql='SELECT COUNT(*) FROM pages';
$rquery= BD_Ejecutar($sql);
list($num_records)=mysql_fetch_row($rquery);
mysql_free_result($rquery);
$sql="SELECT id FROM pages WHERE RAND()*$num_records<20
ORDER BY RAND() LIMIT 0,10";
$rquery= BD_Ejecutar($sql);
while(list($id)=mysql_fetch_row($rquery)){
if($id_in) $id_in.=",$id";
else $id_in="$id";
}
mysql_free_result($rquery);
$sql="SELECT id,url FROM pages WHERE id IN($id_in)";
$rquery= BD_Ejecutar($sql);
while(list($id,$url)=mysql_fetch_row($rquery)){
logger("$id, $url",1);
}
mysql_free_result($rquery);
$time= microtime_float()-$time;
logger("num_records=$num_records",1);
logger("$id_in",1);
logger("Time elapsed: <b>$time segundos</b>",1);
?>
ORDER BY RAND()
FLUSH STATUS; SELECT ...; SHOW SESSION STATUS LIKE 'Handler%';
เพื่อดูมัน
ORDER BY RAND()
คือมันเรียงลำดับรหัส (ไม่ใช่แถวเต็ม) ดังนั้นตาราง temp จึงมีขนาดเล็กลง แต่ยังต้องเรียงลำดับทั้งหมด
มันง่ายมากและแบบสอบถามบรรทัดเดียว
SELECT * FROM Table_Name ORDER BY RAND() LIMIT 0,10;
order by rand()
ช้ามากถ้าตารางมีขนาดใหญ่
จากหนังสือ:
เลือกแถวสุ่มโดยใช้ออฟเซ็ต
เทคนิคอื่นที่หลีกเลี่ยงปัญหาที่พบในทางเลือกก่อนหน้านี้คือการนับแถวในชุดข้อมูลและส่งกลับตัวเลขสุ่มระหว่าง 0 ถึงจำนวน จากนั้นใช้หมายเลขนี้เป็นออฟเซ็ตเมื่อทำการสอบถามชุดข้อมูล
<?php
$rand = "SELECT ROUND(RAND() * (SELECT COUNT(*) FROM Bugs))";
$offset = $pdo->query($rand)->fetch(PDO::FETCH_ASSOC);
$sql = "SELECT * FROM Bugs LIMIT 1 OFFSET :offset";
$stmt = $pdo->prepare($sql);
$stmt->execute( $offset );
$rand_bug = $stmt->fetch();
ใช้โซลูชันนี้เมื่อคุณไม่สามารถถือว่าค่าคีย์ที่อยู่ติดกันและคุณต้องตรวจสอบให้แน่ใจว่าแต่ละแถวมีโอกาสเลือกเท่ากัน
SELECT count(*)
ช้า
วิธีเลือกสุ่มแถวจากตาราง:
จากที่นี่: เลือกแถวสุ่มใน MySQL
การปรับปรุงอย่างรวดเร็วผ่าน "การสแกนตาราง" คือการใช้ดัชนีเพื่อรับรหัสสุ่ม
SELECT *
FROM random, (
SELECT id AS sid
FROM random
ORDER BY RAND( )
LIMIT 10
) tmp
WHERE random.id = tmp.sid;
PRIMARY KEY
)
ถ้าคุณไม่มีช่องว่างในคีย์และเป็นตัวเลขทั้งหมดคุณสามารถคำนวณตัวเลขสุ่มและเลือกบรรทัดเหล่านั้นได้ แต่นี่อาจจะไม่ใช่กรณี
ดังนั้นทางออกหนึ่งจะเป็นดังต่อไปนี้:
SELECT * FROM table WHERE key >= FLOOR(RAND()*MAX(id)) LIMIT 1
ซึ่งโดยทั่วไปจะให้แน่ใจว่าคุณได้รับตัวเลขสุ่มในช่วงของคีย์ของคุณและจากนั้นคุณเลือกที่ดีที่สุดถัดไปซึ่งมากกว่า คุณต้องทำเช่นนี้ 10 ครั้ง
อย่างไรก็ตามนี่ไม่ใช่การสุ่มอย่างแท้จริงเพราะกุญแจของคุณมักจะไม่ได้รับการแจกจ่ายอย่างเท่าเทียมกัน
มันเป็นปัญหาใหญ่จริง ๆ และไม่ใช่เรื่องง่ายที่จะแก้ปัญหาให้ตรงตามข้อกำหนดทั้งหมดแรนด์ของ MySQL () นั้นดีที่สุดที่คุณจะได้รับถ้าคุณต้องการสุ่ม 10 แถว
มีวิธีแก้ไขปัญหาอื่นที่รวดเร็ว แต่ยังมีการแลกเปลี่ยนเมื่อมันมาถึงการสุ่ม แต่อาจเหมาะกับคุณดีกว่า อ่านเกี่ยวกับที่นี่: ฉันจะเพิ่มประสิทธิภาพฟังก์ชั่น ORDER BY RAND () ของ MySQL ได้อย่างไร?
คำถามคือวิธีการสุ่มที่คุณต้องการให้เป็น
คุณช่วยอธิบายเพิ่มเติมอีกหน่อยได้ไหมเพื่อให้ฉันได้คำตอบที่ดี
ตัวอย่างเช่น บริษัท ที่ฉันทำงานด้วยมีวิธีแก้ปัญหาที่พวกเขาต้องการการสุ่มแบบเร็วมากอย่างรวดเร็ว พวกเขาลงเอยด้วยการเติมฐานข้อมูลล่วงหน้าด้วยค่าสุ่มที่เลือกจากมากไปน้อยและตั้งค่าเป็นค่าสุ่มต่าง ๆ หลังจากนั้นอีกครั้ง
หากคุณไม่ได้อัปเดตคุณสามารถใส่รหัสที่เพิ่มขึ้นเพื่อที่คุณจะได้ไม่มีช่องว่างและสามารถคำนวณปุ่มสุ่มก่อนที่จะเลือก ... ขึ้นอยู่กับกรณีการใช้งาน!
Id
และแบบสอบถามแบบสุ่มทั้งหมดของคุณจะส่งคืนข้อความId
นั้น
FLOOR(RAND()*MAX(id))
มีอคติต่อการส่งคืนรหัสที่ใหญ่กว่า
ฉันต้องการแบบสอบถามเพื่อส่งคืนแถวสุ่มจำนวนมากจากตารางที่ค่อนข้างใหญ่ นี่คือสิ่งที่ฉันมาด้วย อันดับแรกรับรหัสบันทึกสูงสุด:
SELECT MAX(id) FROM table_name;
จากนั้นแทนที่ค่านั้นเป็น:
SELECT * FROM table_name WHERE id > FLOOR(RAND() * max) LIMIT n;
โดยที่ max คือ id บันทึกสูงสุดในตารางและ n คือจำนวนแถวที่คุณต้องการในชุดผลลัพธ์ ข้อสันนิษฐานคือว่าไม่มีช่องว่างใน id ของบันทึกแม้ว่าฉันสงสัยว่ามันจะส่งผลกระทบต่อผลถ้ามี (ยังไม่ได้ลองแม้ว่า) ฉันยังสร้างกระบวนงานที่เก็บไว้นี้ให้เป็นแบบทั่วไปมากขึ้น ผ่านในชื่อตารางและจำนวนแถวที่จะส่งคืน ฉันใช้ MySQL 5.5.38 ใน Windows 2008, 32GB, 3GHz คู่ E5450 และบนโต๊ะที่มี 17,361,264 แถวมันค่อนข้างสอดคล้องกันที่ ~ .03 วินาที / ~ 11 วินาทีเพื่อกลับ 1,000,000 แถว (เวลามาจาก MySQL Workbench 6.1 คุณสามารถใช้ CEIL แทน FLOOR ในคำสั่ง select ที่ 2 ขึ้นอยู่กับความต้องการของคุณ)
DELIMITER $$
USE [schema name] $$
DROP PROCEDURE IF EXISTS `random_rows` $$
CREATE PROCEDURE `random_rows`(IN tab_name VARCHAR(64), IN num_rows INT)
BEGIN
SET @t = CONCAT('SET @max=(SELECT MAX(id) FROM ',tab_name,')');
PREPARE stmt FROM @t;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @t = CONCAT(
'SELECT * FROM ',
tab_name,
' WHERE id>FLOOR(RAND()*@max) LIMIT ',
num_rows);
PREPARE stmt FROM @t;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END
$$
แล้วก็
CALL [schema name].random_rows([table name], n);
คำตอบที่ดีที่สุดทั้งหมดได้รับการโพสต์แล้ว (ส่วนใหญ่อ้างอิงจากลิงค์http://jan.kneschke.de/projects/mysql/order-by-rand/ )
ฉันต้องการที่จะระบุความเป็นไปได้อีกความเร็วขึ้น - แคช ลองคิดดูว่าทำไมคุณต้องสุ่มแถว อาจเป็นไปได้ว่าคุณต้องการแสดงโพสต์แบบสุ่มหรือโฆษณาแบบสุ่มบนเว็บไซต์ หากคุณได้รับ 100 req / s จำเป็นหรือไม่ที่ผู้เข้าชมแต่ละคนจะได้รับแถวสุ่ม โดยปกติแล้วการแคชแถว X แบบสุ่มเหล่านี้เป็นเวลา 1 วินาที (หรือแม้แต่ 10 วินาที) ไม่สำคัญว่าผู้เข้าชมที่ไม่ซ้ำ 100 คนใน 1 วินาทีเดียวกันจะได้รับโพสต์แบบสุ่มหรือไม่เพราะผู้เข้าชม 100 คนต่อมาจะได้รับโพสต์ชุดอื่น
เมื่อใช้แคชนี้คุณสามารถใช้วิธีการช้าลงบางส่วนในการรับข้อมูลแบบสุ่มเนื่องจากจะถูกดึงจาก MySQL เพียงหนึ่งครั้งต่อวินาทีโดยไม่คำนึงถึงความต้องการของคุณ
ฉันปรับปรุงคำตอบที่ @Riedsio ได้ นี่เป็นแบบสอบถามที่มีประสิทธิภาพที่สุดที่ฉันสามารถหาได้ในตารางที่มีขนาดใหญ่และกระจายอย่างสม่ำเสมอโดยมีช่องว่าง (ทดสอบกับการรับ 1,000 แถวสุ่มจากตารางที่มี> 2.6B แถว)
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max := (SELECT MAX(id) FROM table)) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1)
ให้ฉันแกะสิ่งที่เกิดขึ้น
@max := (SELECT MAX(id) FROM table)
MAX(id)
ทุกครั้งที่คุณต้องการแถวSELECT FLOOR(rand() * @max) + 1 as rand)
SELECT id FROM table INNER JOIN (...) on id > rand LIMIT 1
การทำสหภาพช่วยให้คุณพอดีทุกอย่างเป็น 1 แบบสอบถามเพื่อให้คุณสามารถหลีกเลี่ยงการทำหลายแบบสอบถาม MAX(id)
นอกจากนี้ยังช่วยให้คุณประหยัดค่าใช้จ่ายในการคำนวณ สิ่งนี้อาจมีความสำคัญไม่มากก็น้อย
โปรดทราบว่านี่จะได้รับรหัสและได้รับพวกเขาในลำดับสุ่ม ถ้าคุณต้องการที่จะทำอะไรขั้นสูงเพิ่มเติมฉันขอแนะนำให้คุณทำสิ่งนี้:
SELECT t.id, t.name -- etc, etc
FROM table t
INNER JOIN (
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max := (SELECT MAX(id) FROM table)) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1)
) x ON x.id = t.id
ORDER BY t.id
LIMIT 1
ไปLIMIT 30
ทุกที่ในแบบสอบถาม
LIMIT 1
จะเปลี่ยนให้LIMIT 30
คุณได้รับ 30 ระเบียนในแถวจากจุดสุ่มในตาราง คุณควรมี(SELECT id FROM ....
ส่วนที่อยู่ตรงกลาง30 สำเนา
Riedsio
ตอบ ฉันได้ลองกับการเข้าชม 500 ครั้งต่อวินาทีไปที่หน้าโดยใช้ PHP 7.0.22 และ MariaDB บน centos 7 ด้วยRiedsio
คำตอบฉันได้รับการตอบกลับที่ประสบความสำเร็จมากกว่า 500+ ครั้งจากคำตอบของคุณ
ฉันใช้http://jan.kneschke.de/projects/mysql/order-by-rand/นี้โพสต์โดย Riedsio (ฉันใช้กรณีของกระบวนงานที่เก็บไว้ซึ่งส่งกลับค่าสุ่มหนึ่งค่าขึ้นไป):
DROP TEMPORARY TABLE IF EXISTS rands;
CREATE TEMPORARY TABLE rands ( rand_id INT );
loop_me: LOOP
IF cnt < 1 THEN
LEAVE loop_me;
END IF;
INSERT INTO rands
SELECT r1.id
FROM random AS r1 JOIN
(SELECT (RAND() *
(SELECT MAX(id)
FROM random)) AS id)
AS r2
WHERE r1.id >= r2.id
ORDER BY r1.id ASC
LIMIT 1;
SET cnt = cnt - 1;
END LOOP loop_me;
ในบทความเขาแก้ปัญหาช่องว่างในรหัสทำให้ไม่ได้ผลลัพธ์แบบสุ่มโดยการบำรุงรักษาตาราง (ใช้ทริกเกอร์ ฯลฯ ... ดูบทความ); ฉันกำลังแก้ปัญหาโดยการเพิ่มคอลัมน์อื่นลงในตารางซึ่งมีจำนวนต่อเนื่องกันเริ่มต้นที่ 1 ( แก้ไข:คอลัมน์นี้ถูกเพิ่มในตารางชั่วคราวที่สร้างโดยเคียวรีย่อยที่รันไทม์ไม่ส่งผลกระทบต่อตารางถาวรของคุณ):
DROP TEMPORARY TABLE IF EXISTS rands;
CREATE TEMPORARY TABLE rands ( rand_id INT );
loop_me: LOOP
IF cnt < 1 THEN
LEAVE loop_me;
END IF;
SET @no_gaps_id := 0;
INSERT INTO rands
SELECT r1.id
FROM (SELECT id, @no_gaps_id := @no_gaps_id + 1 AS no_gaps_id FROM random) AS r1 JOIN
(SELECT (RAND() *
(SELECT COUNT(*)
FROM random)) AS id)
AS r2
WHERE r1.no_gaps_id >= r2.id
ORDER BY r1.no_gaps_id ASC
LIMIT 1;
SET cnt = cnt - 1;
END LOOP loop_me;
ในบทความฉันสามารถเห็นเขาไปยาวมากเพื่อเพิ่มประสิทธิภาพรหัส; ฉันไม่มีไอเดียถ้า / การเปลี่ยนแปลงของฉันส่งผลกระทบต่อประสิทธิภาพการทำงาน แต่ทำงานได้ดีมากสำหรับฉัน
@no_gaps_id
ดัชนีที่ไม่สามารถใช้ได้ดังนั้นหากคุณดูที่EXPLAIN
การสืบค้นของคุณคุณมีUsing filesort
และUsing where
(ไม่มีดัชนี) สำหรับเคียวรีย่อยซึ่งตรงกันข้ามกับเคียวรีดั้งเดิม
นี่คือตัวเปลี่ยนเกมที่อาจเป็นประโยชน์สำหรับคนส่วนใหญ่
ฉันมีตารางที่มี 200k แถวโดยมี id ของลำดับฉันจำเป็นต้องเลือกแถวสุ่มNแถวดังนั้นฉันเลือกที่จะสร้างค่าแบบสุ่มโดยใช้ ID ที่ใหญ่ที่สุดในตารางฉันสร้างสคริปต์นี้ขึ้นมาเพื่อหาว่าเป็นการดำเนินการที่เร็วที่สุด:
logTime();
query("SELECT COUNT(id) FROM tbl");
logTime();
query("SELECT MAX(id) FROM tbl");
logTime();
query("SELECT id FROM tbl ORDER BY id DESC LIMIT 1");
logTime();
ผลลัพธ์ที่ได้คือ:
36.8418693542479
ms0.241041183472
ms0.216960906982
msจากผลลัพธ์นี้ order เรียงเป็นการดำเนินการที่เร็วที่สุดเพื่อรับ max id
นี่คือคำตอบสำหรับคำถามของฉัน:
SELECT GROUP_CONCAT(n SEPARATOR ',') g FROM (
SELECT FLOOR(RAND() * (
SELECT id FROM tbl ORDER BY id DESC LIMIT 1
)) n FROM tbl LIMIT 10) a
...
SELECT * FROM tbl WHERE id IN ($result);
FYI: หากต้องการรับ 10 แถวสุ่มจากตาราง 200k ฉันใช้เวลา 1.78 ms (รวมถึงการดำเนินการทั้งหมดในด้าน php)
LIMIT
เล็กน้อย - คุณสามารถได้รับซ้ำ
นี่คือเร็วสุดและสุ่ม 100% แม้ว่าคุณจะมีช่องว่าง
x
แถวที่คุณมีSELECT COUNT(*) as rows FROM TABLE
a_1,a_2,...,a_10
ระหว่าง 0 ถึงx
SELECT * FROM TABLE LIMIT 1 offset a_i
สำหรับ i = 1, ... , 10ผมพบว่าสับนี้ในหนังสือSQL Antipatternsจากบิล Karwin
SELECT column FROM table ORDER BY RAND() LIMIT 10
อยู่ใน O (nlog (n)) ใช่แล้วนี่คือทางออกที่รวดเร็วและใช้ได้กับการแจกแจงรหัสต่างๆ
x
แถวแรกหลังจากการชดเชย ฉันจะยืนยันว่านี่ไม่ใช่รุ่นสุ่ม 10 แถว ในคำตอบของฉันคุณต้องดำเนินการแบบสอบถามในขั้นตอนที่สาม 10 ครั้งคือหนึ่งได้รับเพียงหนึ่งแถวต่อการดำเนินการและไม่ต้องกังวลหากออฟเซตอยู่ท้ายตาราง
รวมคำตอบของ @redsio เข้ากับ temp-table (600K ไม่มาก):
DROP TEMPORARY TABLE IF EXISTS tmp_randorder;
CREATE TABLE tmp_randorder (id int(11) not null auto_increment primary key, data_id int(11));
INSERT INTO tmp_randorder (data_id) select id from datatable;
จากนั้นให้ใช้ @redsios เวอร์ชันหนึ่งคำตอบ:
SELECT dt.*
FROM
(SELECT (RAND() *
(SELECT MAX(id)
FROM tmp_randorder)) AS id)
AS rnd
INNER JOIN tmp_randorder rndo on rndo.id between rnd.id - 10 and rnd.id + 10
INNER JOIN datatable AS dt on dt.id = rndo.data_id
ORDER BY abs(rndo.id - rnd.id)
LIMIT 1;
หากตารางมีขนาดใหญ่คุณสามารถกรองในส่วนแรก:
INSERT INTO tmp_randorder (data_id) select id from datatable where rand() < 0.01;
เวอร์ชัน: คุณสามารถเก็บตารางไว้tmp_randorder
ถาวรเรียกว่า datatable_idlist สร้างตารางนั้นใหม่ในบางช่วงเวลา (วันชั่วโมง) เนื่องจากจะได้รับรู หากโต๊ะของคุณใหญ่จริง ๆ คุณสามารถเติมได้
เลือก l.data_id ทั้งหมดจาก datatable_idlist l ซ้ายเข้าร่วม datatable dt บน dt.id = l.data_id โดยที่ dt.id เป็นโมฆะ
เวอร์ชัน: ให้ชุดข้อมูลคอลัมน์ random_sortorder ทั้งโดยตรงใน DataTable datatable_sortorder
หรือในตารางพิเศษถาวร ดัชนีคอลัมน์นั้น สร้างค่าสุ่มในใบสมัครของคุณ (ฉันจะเรียกมันว่า$rand
)
select l.*
from datatable l
order by abs(random_sortorder - $rand) desc
limit 1;
วิธีการแก้ปัญหานี้จำแนก 'แถวของขอบ' ด้วยการสุ่มเรียงลำดับสูงสุดและต่ำสุดดังนั้นจัดเรียงใหม่ตามช่วงเวลา (วันละครั้ง)
อีกวิธีง่ายๆคือการจัดอันดับแถวและดึงหนึ่งในนั้นแบบสุ่มและด้วยโซลูชันนี้คุณไม่จำเป็นต้องมีคอลัมน์ 'Id' ใด ๆ ในตาราง
SELECT d.* FROM (
SELECT t.*, @rownum := @rownum + 1 AS rank
FROM mytable AS t,
(SELECT @rownum := 0) AS r,
(SELECT @cnt := (SELECT RAND() * (SELECT COUNT(*) FROM mytable))) AS n
) d WHERE rank >= @cnt LIMIT 10;
คุณสามารถเปลี่ยนค่า จำกัด ตามที่คุณต้องการเข้าถึงได้มากเท่าที่คุณต้องการ แต่ส่วนใหญ่จะเป็นค่าติดต่อกัน
อย่างไรก็ตามหากคุณไม่ต้องการค่าสุ่มต่อเนื่องคุณสามารถดึงตัวอย่างที่ใหญ่กว่าและเลือกแบบสุ่มได้ สิ่งที่ต้องการ ...
SELECT * FROM (
SELECT d.* FROM (
SELECT c.*, @rownum := @rownum + 1 AS rank
FROM buildbrain.`commits` AS c,
(SELECT @rownum := 0) AS r,
(SELECT @cnt := (SELECT RAND() * (SELECT COUNT(*) FROM buildbrain.`commits`))) AS rnd
) d
WHERE rank >= @cnt LIMIT 10000
) t ORDER BY RAND() LIMIT 10;
วิธีหนึ่งที่ฉันพบว่าค่อนข้างดีถ้ามีรหัสที่สร้างอัตโนมัติคือใช้ตัวดำเนินการโมดูโล '%' ตัวอย่างเช่นถ้าคุณต้องการ 10,000 เรคคอร์ดสุ่มออก 70,000 คุณสามารถทำให้มันง่ายขึ้นโดยบอกว่าคุณต้องการ 1 จากทุก 7 แถว สิ่งนี้สามารถทำให้ง่ายขึ้นในแบบสอบถามนี้:
SELECT * FROM
table
WHERE
id %
FLOOR(
(SELECT count(1) FROM table)
/ 10000
) = 0;
หากผลลัพธ์ของการแบ่งแถวเป้าหมายตามผลรวมที่มีให้ไม่ใช่จำนวนเต็มคุณจะมีแถวพิเศษมากกว่าที่คุณขอดังนั้นคุณควรเพิ่มส่วนคำสั่ง LIMIT เพื่อช่วยคุณตัดแต่งชุดผลลัพธ์ดังนี้:
SELECT * FROM
table
WHERE
id %
FLOOR(
(SELECT count(1) FROM table)
/ 10000
) = 0
LIMIT 10000;
สิ่งนี้ต้องใช้การสแกนแบบเต็ม แต่เร็วกว่า ORDER BY RAND และในความคิดของฉันง่ายต่อการเข้าใจมากกว่าตัวเลือกอื่น ๆ ที่กล่าวถึงในหัวข้อนี้ นอกจากนี้หากระบบที่เขียนไปยัง DB สร้างชุดของแถวเป็นชุดคุณอาจไม่ได้รับผลลัพธ์แบบสุ่มตามที่คุณคาดหวัง
หากคุณต้องการหนึ่งระเบียนสุ่ม (ไม่ว่าจะมีช่องว่างระหว่างรหัส):
PREPARE stmt FROM 'SELECT * FROM `table_name` LIMIT 1 OFFSET ?';
SET @count = (SELECT
FLOOR(RAND() * COUNT(*))
FROM `table_name`);
EXECUTE stmt USING @count;
ฉันได้ดูคำตอบทั้งหมดแล้วและฉันไม่คิดว่าจะมีใครพูดถึงความเป็นไปได้ทั้งหมดและฉันไม่แน่ใจว่าทำไม
หากคุณต้องการความเรียบง่ายและความเร็วสูงสุดด้วยค่าใช้จ่ายเล็กน้อยสำหรับฉันแล้วดูเหมือนว่าคุณควรเก็บตัวเลขสุ่มไว้กับแต่ละแถวในฐานข้อมูล เพียงแค่สร้างคอลัมน์พิเศษrandom_number
, RAND()
และการตั้งค่าเริ่มต้นของการ สร้างดัชนีในคอลัมน์นี้
จากนั้นเมื่อคุณต้องการเรียกแถวสร้างตัวเลขสุ่มในรหัสของคุณ (PHP, Perl, อะไรก็ตาม) และเปรียบเทียบกับคอลัมน์
SELECT FROM tbl WHERE random_number >= :random LIMIT 1
ฉันเดาว่าถึงแม้มันจะเป็นระเบียบสำหรับแถวเดียว แต่สำหรับสิบแถวอย่าง OP ขอให้คุณเรียกมันว่าสิบครั้งแยกกัน
ต่อไปนี้ควรเป็นไปอย่างรวดเร็วไม่ลำเอียงและเป็นอิสระจากคอลัมน์ id อย่างไรก็ตามไม่รับประกันว่าจำนวนแถวที่ส่งคืนจะตรงกับจำนวนแถวที่ร้องขอ
SELECT *
FROM t
WHERE RAND() < (SELECT 10 / COUNT(*) FROM t)
คำอธิบาย: สมมติว่าคุณต้องการ 10 แถวจาก 100 แล้วแต่ละแถวมี 1/10 WHERE RAND() < 0.1
น่าจะเป็นของการเลือกซึ่งอาจทำได้โดย วิธีการนี้ไม่รับประกัน 10 แถว; แต่ถ้าแบบสอบถามรันครั้งเพียงพอจำนวนแถวเฉลี่ยต่อการดำเนินการจะอยู่ที่ประมาณ 10 และแต่ละแถวในตารางจะถูกเลือกอย่างเท่าเทียมกัน
PREPARE stm from 'select * from table limit 10 offset ?';
SET @total = (select count(*) from table);
SET @_offset = FLOOR(RAND() * @total);
EXECUTE stm using @_offset;
นอกจากนี้คุณยังสามารถใช้ประโยคที่เป็นเช่นนี้
PREPARE stm from 'select * from table where available=true limit 10 offset ?';
SET @total = (select count(*) from table where available=true);
SET @_offset = FLOOR(RAND() * @total);
EXECUTE stm using @_offset;
การทดสอบบน 600,000 แถว (700MB) ดำเนินการแบบสอบถามตารางเอา ~ 0.016sec ฮาร์ดดิสก์ไดรฟ์
--EDIT--
ชดเชยอาจใช้ค่าที่ใกล้ถึงจุดสิ้นสุดของตารางซึ่งจะส่งผลให้คำสั่งเลือกกลับมาแถวน้อย (หรืออาจเพียง 1 แถว) เพื่อหลีกเลี่ยงปัญหานี้เราสามารถตรวจสอบoffset
อีกครั้งหลังจากประกาศแล้วเช่นนั้น
SET @rows_count = 10;
PREPARE stm from "select * from table where available=true limit ? offset ?";
SET @total = (select count(*) from table where available=true);
SET @_offset = FLOOR(RAND() * @total);
SET @_offset = (SELECT IF(@total-@_offset<@rows_count,@_offset-@rows_count,@_offset));
SET @_offset = (SELECT IF(@_offset<0,0,@_offset));
EXECUTE stm using @rows_count,@_offset;
ฉันใช้แบบสอบถามนี้:
select floor(RAND() * (SELECT MAX(key) FROM table)) from table limit 10
เวลาสอบถาม: 0.016s
นี่คือวิธีที่ฉันทำ:
select *
from table_with_600k_rows
where rand() < 10/600000
limit 10
ฉันชอบเพราะไม่จำเป็นต้องใช้ตารางอื่น ๆ มันง่ายต่อการเขียนและสามารถดำเนินการได้อย่างรวดเร็ว
ฉันเดาว่านี่เป็นวิธีที่ดีที่สุดที่เป็นไปได้ ..
SELECT id, id * RAND( ) AS random_no, first_name, last_name
FROM user
ORDER BY random_no