MySQL chọn 10 hàng ngẫu nhiên từ 600K hàng nhanh


463

Làm cách nào tốt nhất tôi có thể viết một truy vấn chọn ngẫu nhiên 10 hàng trong tổng số 600k?


15
Đây là 8 kỹ thuật ; có lẽ một sẽ làm việc tốt trong trường hợp của bạn.
Rick James

Câu trả lời:


386

Một bài đăng tuyệt vời xử lý một số trường hợp, từ đơn giản, đến khoảng trống, không đồng nhất với các khoảng trống.

http://jan.kneschke.de/projects/mysql/order-by-rand/

Đối với hầu hết các trường hợp chung, đây là cách bạn làm điều đó:

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

Điều này cho rằng sự phân phối của id là bằng nhau và có thể có những khoảng trống trong danh sách id. Xem bài viết cho các ví dụ nâng cao hơn


52
Có, nếu bạn có những khoảng trống lớn trong ID thì khả năng ID thấp nhất của bạn được chọn ngẫu nhiên thấp hơn nhiều so với ID cao của bạn. Trong thực tế, cơ hội mà ID đầu tiên sau khi khoảng cách lớn nhất được chọn thực sự là cao nhất. Do đó, điều này không ngẫu nhiên theo định nghĩa.
lukeocodes

6
Làm thế nào để bạn có được 10 hàng ngẫu nhiên khác nhau? Bạn có phải đặt giới hạn thành 10 và sau đó lặp lại 10 lần với mysqli_fetch_assoc($result)? Hay là 10 kết quả không nhất thiết phải phân biệt?
Adam

12
Ngẫu nhiên đòi hỏi một cơ hội như nhau cho bất kỳ kết quả, trong tâm trí của tôi. ;)
lukeocodes

4
Toàn bộ bài viết đề cập đến các vấn đề như phân phối không đồng đều và kết quả lặp lại.
Bradd Szonye

1
cụ thể, nếu bạn có một khoảng trống khi bắt đầu ID của mình, lần đầu tiên bạn sẽ được chọn (tối thiểu / tối đa) thời gian. Trong trường hợp đó, một tinh chỉnh đơn giản là MAX () - MIN () * RAND + MIN (), không quá chậm.
Mã số Abominator

343
SELECT column FROM table
ORDER BY RAND()
LIMIT 10

Không phải là giải pháp hiệu quả nhưng hoạt động


139
ORDER BY RAND()tương đối chậm
Mateusz Charytoniuk

7
Mateusz - bằng chứng xin vui lòng, SELECT words, transcription, translation, sound FROM vocabulary WHERE menu_id=$menuId ORDER BY RAND() LIMIT 10mất 0,0010, không có GIỚI HẠN 10, nó mất 0,0012 (trong bảng đó 3500 từ).
Arthur Kushman

26
@zeusakm 3500 từ không nhiều; vấn đề là nó phát nổ qua một điểm nhất định vì MySQL phải thực sự sắp xếp TẤT CẢ các bản ghi sau khi đọc từng bản; Một khi hoạt động đó chạm vào đĩa cứng, bạn có thể cảm thấy sự khác biệt.
Ja͢ck

16
Tôi không muốn lặp lại nhưng một lần nữa, đó là quét toàn bộ bảng. Trên bàn lớn, nó rất tốn thời gian và bộ nhớ và có thể gây ra việc tạo & vận hành trên bảng tạm thời trên đĩa rất chậm.
matt

10
Khi tôi phỏng vấn Facebook trở lại vào năm 2010, họ đã hỏi tôi làm thế nào để chọn một bản ghi ngẫu nhiên từ một tệp khổng lồ có kích thước không xác định, trong một lần đọc. Một khi bạn đưa ra một ý tưởng, thật dễ dàng để khái quát hóa nó để chọn nhiều bản ghi. Vì vậy, có, sắp xếp toàn bộ tập tin là vô lý. Đồng thời, nó rất tiện dụng. Tôi chỉ sử dụng phương pháp này để chọn 10 hàng ngẫu nhiên từ một bảng có 1.000.000 + hàng. Chắc chắn, tôi đã phải chờ một chút; nhưng tôi chỉ muốn có một ý tưởng, những hàng tiêu biểu trong bảng này trông như thế nào ...
osa 15/12/13

27

Truy vấn đơn giản có hiệu suất tuyệt vời và hoạt động với các khoảng trống :

SELECT * FROM tbl AS t1 JOIN (SELECT id FROM tbl ORDER BY RAND() LIMIT 10) as t2 ON t1.id=t2.id

Truy vấn này trên bảng 200K mất 0,08 giây và phiên bản bình thường (CHỌN * TỪ Tbl ĐẶT HÀNG B RNG RAND () GIỚI HẠN 10) mất B RNG GIỚI 0,35 giây trên máy của tôi.

Điều này nhanh vì giai đoạn sắp xếp chỉ sử dụng cột ID được lập chỉ mục. Bạn có thể thấy hành vi này trong phần giải thích:

CHỌN * TỪ Tbl ĐẶT HÀNG B RNG RAND () GIỚI HẠN 10: Giải thích đơn giản

CHỌN * TỪ tbl NHƯ T1 THAM GIA (CHỌN id TỪ Tbl ĐẶT HÀNG B RNG RAND () GIỚI HẠN 10) là t2 TRÊN t1.id = t2.id nhập mô tả hình ảnh ở đây

Phiên bản có trọng số : https://stackoverflow.com/a/41577458/893432


1
Xin lỗi, tôi đã thử nghiệm! hiệu suất chậm trên hồ sơ 600k.
Dylan B

@DylanB Tôi đã cập nhật câu trả lời bằng một bài kiểm tra.
Ali

17

Tôi đang nhận được các truy vấn nhanh (khoảng 0,5 giây) với một cpu chậm , chọn 10 hàng ngẫu nhiên trong 400K đăng ký cơ sở dữ liệu MySQL kích thước 2Gb không được lưu trong bộ nhớ cache. Xem ở đây mã của tôi: Lựa chọn nhanh các hàng ngẫu nhiên trong 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);
?>

11
Với bảng hơn 14 triệu hồ sơ của tôi, việc này chậm nhưORDER BY RAND()
Fabrizio

5
@snippetsofcode Trong trường hợp của bạn - 400k hàng bạn có thể sử dụng "ORDER BY rand ()" đơn giản. Thủ thuật của bạn với 3 truy vấn là vô ích. Bạn có thể viết lại nó như "CHỌN id, url TỪ các trang WH id id IN (CHỌN id TỪ các trang ĐẶT HÀNG THEO rand ()
GIỚI

4
Kỹ thuật của bạn vẫn thực hiện quét bảng. Sử dụng FLUSH STATUS; SELECT ...; SHOW SESSION STATUS LIKE 'Handler%';để xem nó.
Rick James

4
Cũng cố gắng chạy truy vấn đó trong trang web 200 req / s. Đồng thời sẽ giết bạn.
Marki555

@RomanPodlinov lợi ích của điều này trên đồng bằng ORDER BY RAND()là nó chỉ sắp xếp các id (không phải hàng đầy đủ), vì vậy bảng tạm thời nhỏ hơn, nhưng vẫn phải sắp xếp tất cả chúng.
Marki555

16

Nó rất đơn giản và truy vấn dòng đơn.

SELECT * FROM Table_Name ORDER BY RAND() LIMIT 0,10;

21
FYI, order by rand()rất chậm nếu bàn lớn
evilReiko

6
Đôi khi SLOW được chấp nhận nếu tôi muốn giữ SIMPLE

Lập chỉ mục nên được áp dụng trên bảng nếu nó lớn.
Muhammad Azeem

1
Lập chỉ mục sẽ không giúp đỡ ở đây. Các chỉ mục rất hữu ích cho những thứ rất cụ thể và truy vấn này không phải là một trong số chúng.
Andrew

13

Từ cuốn sách:

Chọn một hàng ngẫu nhiên bằng cách sử dụng một offset

Một kỹ thuật khác để tránh các vấn đề được tìm thấy trong các lựa chọn thay thế trước đó là đếm các hàng trong tập dữ liệu và trả về một số ngẫu nhiên trong khoảng từ 0 đến số đếm. Sau đó sử dụng số này làm phần bù khi truy vấn tập dữ liệu

<?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();

Sử dụng giải pháp này khi bạn không thể giả sử các giá trị khóa liền kề và bạn cần đảm bảo mỗi hàng có cơ hội được chọn.


1
cho các bảng rất lớn, SELECT count(*)trở nên chậm.
Hans Z

7

Cách chọn các hàng ngẫu nhiên từ một bảng:

Từ đây: Chọn các hàng ngẫu nhiên trong MySQL

Một cải tiến nhanh chóng so với "quét bảng" là sử dụng chỉ mục để nhận id ngẫu nhiên.

SELECT *
FROM random, (
        SELECT id AS sid
        FROM random
        ORDER BY RAND( )
        LIMIT 10
    ) tmp
WHERE random.id = tmp.sid;

1
Điều đó giúp một số cho MyISAM, nhưng không phải cho InnoDB (giả sử id là cụm PRIMARY KEY).
Rick James

7

Chà, nếu bạn không có khoảng trống trong các phím của mình và chúng đều là số, bạn có thể tính các số ngẫu nhiên và chọn các dòng đó. nhưng điều này có thể sẽ không xảy ra

Vì vậy, một giải pháp sẽ là như sau:

SELECT * FROM table WHERE key >= FLOOR(RAND()*MAX(id)) LIMIT 1

về cơ bản sẽ đảm bảo rằng bạn nhận được một số ngẫu nhiên trong phạm vi các khóa của mình và sau đó bạn chọn số tốt nhất tiếp theo lớn hơn. bạn phải làm điều này 10 lần

tuy nhiên điều này KHÔNG thực sự ngẫu nhiên vì các khóa của bạn rất có thể sẽ không được phân phối đều.

Đây thực sự là một vấn đề lớn và không dễ để giải quyết đáp ứng tất cả các yêu cầu, rand () của MySQL là điều tốt nhất bạn có thể nhận được nếu bạn thực sự muốn 10 hàng ngẫu nhiên.

Tuy nhiên, có một giải pháp khác nhanh nhưng cũng có sự đánh đổi khi nói đến sự ngẫu nhiên, nhưng có thể phù hợp với bạn hơn. Đọc về nó ở đây: Làm thế nào tôi có thể tối ưu hóa chức năng ORDER BY RAND () của MySQL?

Câu hỏi là ngẫu nhiên như thế nào bạn cần nó được.

Bạn có thể giải thích thêm một chút để tôi có thể cung cấp cho bạn một giải pháp tốt.

Ví dụ, một công ty tôi làm việc cùng có một giải pháp mà họ cần sự ngẫu nhiên tuyệt đối cực kỳ nhanh. Họ đã kết thúc với việc điền trước cơ sở dữ liệu với các giá trị ngẫu nhiên được chọn giảm dần và được đặt thành các giá trị ngẫu nhiên khác nhau sau đó một lần nữa.

Nếu bạn hầu như không bao giờ cập nhật, bạn cũng có thể điền vào một id tăng dần để bạn không có khoảng trống và chỉ có thể tính toán các khóa ngẫu nhiên trước khi chọn ... Nó phụ thuộc vào trường hợp sử dụng!


Chào joe. Trong trường hợp cụ thể này, các phím không nên thiếu các khoảng trống, nhưng theo thời gian, điều này có thể thay đổi. Và trong khi câu trả lời của bạn hoạt động, nó sẽ tạo ra 10 hàng ngẫu nhiên (với điều kiện tôi viết giới hạn 10) liên tiếp và tôi muốn có nhiều sự ngẫu nhiên hơn để nói. :) Cảm ơn bạn.
Francisc

Nếu bạn cần 10, hãy sử dụng một số loại kết hợp để tạo 10 hàng duy nhất.
johno

những gì tôi nói bạn cần phải thực hiện 10 lần. kết hợp nó với nhau là một cách để đưa nó vào một truy vấn. xem phụ lục của tôi 2 phút trước.
Surrican

1
@TheSurrican, Giải pháp này có vẻ mát mẻ nhưng rất thiếu sót . Hãy thử chèn chỉ một cái rất lớn Idtất cả các truy vấn ngẫu nhiên của bạn sẽ trả về cho bạn cái đó Id.
Pacerier

1
FLOOR(RAND()*MAX(id))thiên về việc trả lại id lớn hơn.
Rick James

3

Tôi cần một truy vấn để trả về một số lượng lớn các hàng ngẫu nhiên từ một bảng khá lớn. Đây là những gì tôi đã đưa ra. Đầu tiên nhận id hồ sơ tối đa:

SELECT MAX(id) FROM table_name;

Sau đó thay thế giá trị đó thành:

SELECT * FROM table_name WHERE id > FLOOR(RAND() * max) LIMIT n;

Trong đó max là id bản ghi tối đa trong bảng và n là số lượng hàng bạn muốn trong tập kết quả của bạn. Giả định là không có lỗ hổng nào trong id hồ sơ mặc dù tôi nghi ngờ nó sẽ ảnh hưởng đến kết quả nếu có (mặc dù chưa thử). Tôi cũng đã tạo thủ tục lưu trữ này để chung chung hơn; vượt qua trong tên bảng và số lượng hàng được trả lại. Tôi đang chạy MySQL 5.5,38 trên Windows 2008, 32GB, 3G54 E5450 kép và trên một bảng có 17.361.264 hàng, nó khá nhất quán ở ~ .03 giây / ~ 11 giây để trả về 1.000.000 hàng. (thời gian là từ MySQL Workbench 6.1; bạn cũng có thể sử dụng CEIL thay vì FLOOR trong câu lệnh chọn thứ 2 tùy theo sở thích của bạn)

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
$$

sau đó

CALL [schema name].random_rows([table name], n);

3

Tất cả các câu trả lời hay nhất đã được đăng (chủ yếu là những câu trả lời liên kết http://jan.kneschke.de/projects/mysql/order-by-rand/ ).

Tôi muốn xác định một khả năng tăng tốc khác - bộ nhớ đệm . Hãy nghĩ về lý do tại sao bạn cần nhận được các hàng ngẫu nhiên. Có lẽ bạn muốn hiển thị một số bài đăng ngẫu nhiên hoặc quảng cáo ngẫu nhiên trên một trang web. Nếu bạn đang nhận được 100 req / s, có thực sự cần thiết rằng mỗi khách truy cập có được các hàng ngẫu nhiên không? Thông thường sẽ hoàn toàn ổn khi lưu trữ các hàng X ngẫu nhiên này trong 1 giây (hoặc thậm chí 10 giây). Sẽ không có vấn đề gì nếu 100 khách truy cập duy nhất trong cùng 1 giây nhận được cùng một bài đăng ngẫu nhiên, bởi vì trong 100 giây tiếp theo, 100 khách truy cập khác sẽ nhận được các nhóm bài đăng khác nhau.

Khi sử dụng bộ đệm này, bạn cũng có thể sử dụng một số giải pháp chậm hơn để nhận dữ liệu ngẫu nhiên vì nó sẽ được tìm nạp từ MySQL chỉ một lần mỗi giây bất kể req / s của bạn.


3

Tôi đã cải thiện câu trả lời @Riedsio. Đây là truy vấn hiệu quả nhất mà tôi có thể tìm thấy trên một bảng lớn, được phân phối đồng đều với các khoảng trống (được thử nghiệm khi nhận 1000 hàng ngẫu nhiên từ một bảng có> 2,6B hàng).

(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)

Hãy để tôi giải nén những gì đang xảy ra.

  1. @max := (SELECT MAX(id) FROM table)
    • Tôi đang tính toán và tiết kiệm tối đa. Đối với các bảng rất lớn, có một chi phí nhỏ để tính toán MAX(id)mỗi khi bạn cần một hàng
  2. SELECT FLOOR(rand() * @max) + 1 as rand)
    • Nhận một id ngẫu nhiên
  3. SELECT id FROM table INNER JOIN (...) on id > rand LIMIT 1
    • Điều này lấp đầy trong những khoảng trống. Về cơ bản nếu bạn chọn ngẫu nhiên một số trong các khoảng trống, nó sẽ chỉ chọn id tiếp theo. Giả sử các khoảng trống được phân phối đồng đều, đây không phải là một vấn đề.

Thực hiện liên kết giúp bạn điều chỉnh mọi thứ thành 1 truy vấn để bạn có thể tránh thực hiện nhiều truy vấn. Nó cũng cho phép bạn tiết kiệm chi phí tính toán MAX(id). Tùy thuộc vào ứng dụng của bạn, điều này có thể quan trọng rất nhiều hoặc rất ít.

Lưu ý rằng điều này chỉ nhận được id và nhận chúng theo thứ tự ngẫu nhiên. Nếu bạn muốn làm bất cứ điều gì cao cấp hơn, tôi khuyên bạn nên làm điều này:

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

Tôi cần 30 hồ sơ ngẫu nhiên, vì vậy tôi nên thay đổi LIMIT 1để LIMIT 30ở khắp mọi nơi trong truy vấn
Hassaan

@Hassaan bạn không nên, rằng việc thay đổi LIMIT 1để LIMIT 30có thể giúp bạn có được 30 bản ghi trong một hàng từ một điểm ngẫu nhiên trong bảng. Thay vào đó, bạn nên có 30 bản sao của (SELECT id FROM ....phần ở giữa.
Hans Z

Tôi đã thử nhưng có vẻ không hiệu quả hơn thì Riedsiotrả lời. Tôi đã thử với 500 lần truy cập mỗi giây vào trang bằng PHP 7.0.22 và MariaDB trên centos 7, với Riedsiocâu trả lời tôi đã nhận được hơn 500 phản hồi thành công sau đó là câu trả lời của bạn.
Hassaan

1
Câu trả lời của @Hassaan riedsio cho 1 hàng, câu này cung cấp cho bạn n hàng, cũng như cắt giảm chi phí I / O để truy vấn. Bạn có thể có được các hàng nhanh hơn, nhưng tải nhiều hơn trên hệ thống của bạn.
Hans Z

3

Tôi đã sử dụng http://jan.kneschke.de/projects/mysql/order-by-rand/ được đăng bởi Riedsio (tôi đã sử dụng trường hợp của một thủ tục được lưu trữ trả về một hoặc nhiều giá trị ngẫu nhiên):

   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;

Trong bài viết, ông giải quyết vấn đề về khoảng trống trong id gây ra kết quả không ngẫu nhiên bằng cách duy trì bảng (sử dụng trình kích hoạt, v.v ... xem bài viết); Tôi đang giải quyết vấn đề bằng cách thêm một cột khác vào bảng, được điền với các số liền kề, bắt đầu từ 1 ( chỉnh sửa: cột này được thêm vào bảng tạm thời được tạo bởi truy vấn con khi chạy, không ảnh hưởng đến bảng cố định của bạn):

   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;

Trong bài viết tôi có thể thấy anh ấy đã đi rất lâu để tối ưu hóa mã; Tôi không có ý tưởng nếu / bao nhiêu thay đổi của tôi ảnh hưởng đến hiệu suất nhưng hoạt động rất tốt cho tôi.


"Tôi không có ý tưởng nếu / bao nhiêu thay đổi của tôi ảnh hưởng đến hiệu suất" - khá nhiều. Đối với @no_gaps_idkhông có chỉ mục nào có thể được sử dụng, vì vậy nếu bạn xem xét EXPLAINtruy vấn của mình, bạn có Using filesortUsing where(không có chỉ mục) cho các truy vấn con, trái ngược với truy vấn ban đầu.
Fabian Schmengler

2

Đây là một thay đổi trò chơi có thể hữu ích cho nhiều người;

Tôi có một bảng có 200 nghìn hàng, với id tuần tự , tôi cần chọn N hàng ngẫu nhiên, vì vậy tôi chọn tạo các giá trị ngẫu nhiên dựa trên ID lớn nhất trong bảng, tôi đã tạo tập lệnh này để tìm ra hoạt động nhanh nhất:

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();

Kết quả là:

  • Đếm: 36.8418693542479ms
  • Tối đa: 0.241041183472ms
  • Đặt hàng: 0.216960906982ms

Dựa trên kết quả này, order desc là thao tác nhanh nhất để có được id tối đa,
đây là câu trả lời của tôi cho câu hỏi:

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: Để có được 10 hàng ngẫu nhiên từ một bảng 200 nghìn, tôi đã mất 1,78 ms (bao gồm tất cả các hoạt động ở phía php)


3
Đề nghị bạn tăng LIMITmột chút - bạn có thể nhận được các bản sao.
Rick James

2

Điều này là siêu nhanh và ngẫu nhiên 100% ngay cả khi bạn có khoảng trống.

  1. Đếm số x hàng mà bạn có sẵnSELECT COUNT(*) as rows FROM TABLE
  2. Chọn 10 số ngẫu nhiên khác nhau a_1,a_2,...,a_10 trong khoảng từ 0 đếnx
  3. Truy vấn các hàng của bạn như thế này: SELECT * FROM TABLE LIMIT 1 offset a_ifor i = 1, ..., 10

Tôi tìm thấy bản hack này trong cuốn sách SQL Antipotypes từ Bill Karwin .


Tôi đã suy nghĩ về cùng một giải pháp, xin vui lòng cho tôi biết, nó có nhanh hơn phương pháp khác không?
G. Adnane

@ G.Anane không nhanh hơn hay chậm hơn câu trả lời được chấp nhận, nhưng câu trả lời được chấp nhận giả định phân phối id bằng nhau. Tôi không thể tưởng tượng bất kỳ kịch bản nào mà điều này có thể được đảm bảo. Giải pháp này nằm trong O (1) trong đó giải pháp SELECT column FROM table ORDER BY RAND() LIMIT 10nằm trong O (nlog (n)). Vì vậy, có, đây là giải pháp nhanh và nó hoạt động cho bất kỳ phân phối id.
Adam

không, bởi vì trong liên kết được đăng cho giải pháp được chấp nhận, có các phương pháp khác, tôi muốn biết liệu giải pháp này có nhanh hơn không, các cách khác, chúng tôi có thể cố gắng tìm một giải pháp khác, đó là lý do tại sao tôi hỏi, bằng mọi cách, +1 cho câu trả lời của bạn. Tôi đã sử dụng phương pháp lấy mẫu
G. Adnane

có một trường hợp khi bạn muốn lấy x số hàng nhưng phần bù đi đến cuối bảng sẽ trả về <x hàng hoặc chỉ 1 hàng. Tôi đã không thấy câu trả lời của bạn trước khi tôi đăng bài của mình nhưng tôi đã nói rõ hơn ở đây stackoverflow.com/a/59981772/10387008
ZOLDIK

@ZOLDIK có vẻ như bạn chọn 10 hàng đầu tiên sau khi bù x. Tôi cho rằng đây không phải là thế hệ ngẫu nhiên gồm 10 hàng. Trong câu trả lời của tôi, bạn phải thực hiện truy vấn trong bước ba 10 lần, tức là chỉ có một hàng cho mỗi lần thực hiện và không phải lo lắng nếu phần bù nằm ở cuối bảng.
Adam

1

Nếu bạn chỉ có một Yêu cầu Đọc

Kết hợp câu trả lời của @redsio với bảng tạm thời (600K là không nhiều):

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;

Và sau đó lấy phiên bản của @redsios Trả lời:

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;

Nếu bàn lớn, bạn có thể rây ở phần đầu tiên:

INSERT INTO tmp_randorder (data_id) select id from datatable where rand() < 0.01;

Nếu bạn có nhiều yêu cầu đọc

  1. Phiên bản: Bạn có thể giữ bảng tmp_randorder liên tục, gọi nó là datitable_idlist. Tái tạo bảng đó trong các khoảng thời gian nhất định (ngày, giờ), vì nó cũng sẽ có lỗ. Nếu bàn của bạn trở nên thật to, bạn cũng có thể đổ đầy lỗ

    chọn l.data_id làm toàn bộ từ datitable_idlist l trái tham gia dữ liệu dt trên dt.id = l.data_id trong đó dt.id là null;

  2. Phiên bản: Cung cấp cho Dataset của bạn một cột Random_sortorder trực tiếp trong dữ liệu hoặc trong một bảng bổ sung liên tục datatable_sortorder. Chỉ mục cột đó. Tạo giá trị ngẫu nhiên trong ứng dụng của bạn (tôi sẽ gọi nó $rand).

    select l.*
    from datatable l 
    order by abs(random_sortorder - $rand) desc 
    limit 1;

Giải pháp này phân biệt các 'hàng cạnh' với Random_sortorder cao nhất và thấp nhất, do đó sắp xếp lại chúng theo các khoảng (một lần một ngày).


1

Một giải pháp đơn giản khác sẽ là xếp hạng các hàng và tìm nạp một trong số chúng một cách ngẫu nhiên và với giải pháp này, bạn sẽ không cần có bất kỳ cột 'Id' nào trong bảng.

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;

Bạn có thể thay đổi giá trị giới hạn theo nhu cầu của bạn để truy cập nhiều hàng như bạn muốn nhưng đó chủ yếu sẽ là các giá trị liên tiếp.

Tuy nhiên, nếu bạn không muốn các giá trị ngẫu nhiên liên tiếp thì bạn có thể tìm nạp một mẫu lớn hơn và chọn ngẫu nhiên từ nó. cái gì đó như ...

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;

1

Một cách mà tôi thấy khá tốt nếu có id được tạo tự động là sử dụng toán tử modulo '%'. Ví dụ: nếu bạn cần 10.000 bản ghi ngẫu nhiên trong số 70.000, bạn có thể đơn giản hóa việc này bằng cách nói rằng bạn cần 1 trên 7 hàng. Điều này có thể được đơn giản hóa trong truy vấn này:

SELECT * FROM 
    table 
WHERE 
    id % 
    FLOOR(
        (SELECT count(1) FROM table) 
        / 10000
    ) = 0;

Nếu kết quả của việc chia các hàng mục tiêu cho tổng số có sẵn không phải là một số nguyên, bạn sẽ có thêm một số hàng so với những gì bạn yêu cầu, vì vậy bạn nên thêm một mệnh đề LIMIT để giúp bạn cắt tập kết quả như sau:

SELECT * FROM 
    table 
WHERE 
    id % 
    FLOOR(
        (SELECT count(1) FROM table) 
        / 10000
    ) = 0
LIMIT 10000;

Điều này không yêu cầu quét toàn bộ, nhưng nó nhanh hơn ORDER BY RAND và theo tôi thì đơn giản hơn để hiểu hơn các tùy chọn khác được đề cập trong chủ đề này. Ngoài ra, nếu hệ thống ghi vào DB tạo ra các tập hợp theo lô, bạn có thể không nhận được kết quả ngẫu nhiên như bạn mong đợi.


2
Bây giờ tôi nghĩ vậy, nếu bạn cần các hàng ngẫu nhiên mỗi khi bạn gọi nó, điều này là vô ích. Tôi chỉ nghĩ về sự cần thiết phải lấy các hàng ngẫu nhiên từ một bộ để thực hiện một số nghiên cứu. Tôi vẫn nghĩ modulo là một điều tốt để giúp đỡ trong trường hợp khác. Bạn có thể sử dụng modulo làm bộ lọc thông qua đầu tiên để giảm chi phí cho thao tác ORDER BY RAND.
Nicolas Cohen


1

Tôi đã xem qua tất cả các câu trả lời, và tôi không nghĩ có ai đề cập đến khả năng này cả, và tôi không chắc tại sao.

Nếu bạn muốn sự đơn giản và tốc độ tối đa, với chi phí nhỏ, thì đối với tôi, việc lưu trữ một số ngẫu nhiên đối với mỗi hàng trong DB là điều hợp lý. Chỉ cần tạo một cột thêm random_numbervà đặt mặc định thành RAND(). Tạo một chỉ mục trên cột này.

Sau đó, khi bạn muốn lấy một hàng, hãy tạo một số ngẫu nhiên trong mã của bạn (PHP, Perl, bất cứ thứ gì) và so sánh nó với cột.

SELECT FROM tbl WHERE random_number >= :random LIMIT 1

Tôi đoán mặc dù nó rất gọn gàng cho một hàng, trong mười hàng như OP yêu cầu bạn phải gọi nó mười lần riêng biệt (hoặc đưa ra một tinh chỉnh thông minh thoát khỏi tôi ngay lập tức)


Đây thực sự là một cách tiếp cận rất tốt đẹp và hiệu quả. Hạn chế duy nhất là thực tế là bạn đã trao đổi không gian cho tốc độ, có vẻ như là một thỏa thuận công bằng theo ý kiến ​​của tôi.
Tochukwu Nkemdilim

Cảm ơn. Tôi đã có một kịch bản trong đó bảng chính mà tôi muốn có một hàng ngẫu nhiên có 5 triệu hàng và khá nhiều liên kết, và sau khi thử hầu hết các cách tiếp cận trong câu hỏi này, đây là loại bùn mà tôi đã giải quyết. Đối với tôi, một cột thêm là một sự đánh đổi rất đáng giá.
Codemonkey

0

Sau đây phải nhanh, không thiên vị và độc lập với cột id. Tuy nhiên, điều đó không đảm bảo rằng số lượng hàng được trả về sẽ khớp với số lượng hàng được yêu cầu.

SELECT *
FROM t
WHERE RAND() < (SELECT 10 / COUNT(*) FROM t)

Giải thích: giả sử bạn muốn 10 hàng trong số 100 thì mỗi hàng có xác suất 1/10 để được CHỌN có thể đạt được WHERE RAND() < 0.1. Cách tiếp cận này không đảm bảo 10 hàng; nhưng nếu truy vấn được chạy đủ số lần, số lượng hàng trung bình cho mỗi lần thực hiện sẽ vào khoảng 10 và mỗi hàng trong bảng sẽ được chọn đều.


0

Bạn có thể dễ dàng sử dụng một phần bù ngẫu nhiên có giới hạn

PREPARE stm from 'select * from table limit 10 offset ?';
SET @total = (select count(*) from table);
SET @_offset = FLOOR(RAND() * @total);
EXECUTE stm using @_offset;

Bạn cũng có thể áp dụng mệnh đề where như vậy

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;

Đã thử nghiệm trên 600.000 hàng (700 MB) thực thi truy vấn bảng mất ~ 0,016 giây ổ đĩa cứng

--EDIT--
   Phần bù có thể lấy một giá trị gần cuối bảng, điều này sẽ dẫn đến câu lệnh chọn trả về ít hàng hơn (hoặc có thể chỉ 1 hàng), để tránh điều này, chúng ta có thể kiểm tra offsetlại sau khi khai báo, như vậy

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;

-1

Tôi sử dụng truy vấn này:

select floor(RAND() * (SELECT MAX(key) FROM table)) from table limit 10

thời gian truy vấn: 0,016s


Có PK như 1,2,9,15. bằng cách truy vấn trên, bạn sẽ nhận được các hàng như 4, 7, 14, 11 không đủ!
Atari

-2

Đây là cách tôi làm điều đó:

select * 
from table_with_600k_rows
where rand() < 10/600000
limit 10

Tôi thích nó bởi vì không yêu cầu các bảng khác, nó đơn giản để viết và nó rất nhanh để thực hiện.


5
Đó là quét toàn bộ bảng và nó không sử dụng bất kỳ chỉ mục nào. Đối với bàn lớn và môi trường bận rộn đó là không lớn.
matt

-2

Sử dụng truy vấn đơn giản dưới đây để lấy dữ liệu ngẫu nhiên từ một bảng.

SELECT user_firstname ,
COUNT(DISTINCT usr_fk_id) cnt
FROM userdetails 
GROUP BY usr_fk_id 
ORDER BY cnt ASC  
LIMIT 10

Nếu bạn muốn sử dụng bất kỳ câu lệnh nối nào và nơi bạn có thể sử dụng bộ lọc.
MANOJ

3
Từ phần nào của truy vấn bạn có được tính ngẫu nhiên?
Marki555

-4

Tôi đoán đây là cách tốt nhất có thể ..

SELECT id, id * RAND( ) AS random_no, first_name, last_name
FROM user
ORDER BY random_no

8
Chết tiệt, đó là một trong những cách tồi tệ nhất để lấy các hàng ngẫu nhiên từ bàn. Đó là quét toàn bộ bảng + tập tin + bảng tmp = hiệu suất kém.
matt

1
Bên cạnh hiệu suất, nó cũng không hoàn toàn ngẫu nhiên; bạn đang đặt hàng theo sản phẩm của id và một số ngẫu nhiên, thay vì chỉ đơn giản là sắp xếp theo một số ngẫu nhiên, điều đó có nghĩa là các hàng có id thấp hơn sẽ bị sai lệch khi xuất hiện sớm hơn trong tập kết quả của bạn.
Đánh dấu Amery
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.