MySQL wählt schnell 10 zufällige Zeilen aus 600.000 Zeilen aus


463

Wie kann ich am besten eine Abfrage schreiben, die 10 Zeilen zufällig aus insgesamt 600.000 auswählt?


15
Hier sind 8 Techniken ; Vielleicht funktioniert man in Ihrem Fall gut.
Rick James

Antworten:


386

Ein großartiger Beitrag, der mehrere Fälle bearbeitet, von einfach über Lücken bis hin zu ungleichmäßigen Lücken.

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

Im allgemeinsten Fall gehen Sie folgendermaßen vor:

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

Dies setzt voraus, dass die Verteilung der IDs gleich ist und dass es Lücken in der ID-Liste geben kann. Weitere Informationen finden Sie im Artikel


52
Ja, wenn Sie potenziell große Lücken in den IDs haben, ist die Wahrscheinlichkeit, dass Ihre niedrigsten IDs zufällig ausgewählt werden, viel geringer als Ihre hohen IDs. Tatsächlich ist die Wahrscheinlichkeit, dass die erste ID nach der größten Lücke ausgewählt wird, tatsächlich die höchste. Daher ist dies per Definition nicht zufällig.
Lukecodes

6
Wie bekommt man 10 verschiedene zufällige Zeilen? Müssen Sie das Limit auf 10 setzen und dann 10 Mal mit wiederholen mysqli_fetch_assoc($result)? Oder sind diese 10 Ergebnisse nicht unbedingt unterscheidbar?
Adam

12
Zufällig erfordert meiner Meinung nach die gleiche Chance für jedes Ergebnis. ;)
lukeocodes

4
Der vollständige Artikel befasst sich mit Problemen wie ungleichen Verteilungen und wiederholten Ergebnissen.
Bradd Szonye

1
Insbesondere wenn Sie zu Beginn Ihrer IDs eine Lücke haben, wird die erste (min / max-min) der Zeit ausgewählt. In diesem Fall ist eine einfache Änderung MAX () - MIN () * RAND + MIN (), was nicht zu langsam ist.
Code Abominator

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

Nicht die effiziente Lösung, funktioniert aber


139
ORDER BY RAND()ist relativ langsam
Mateusz Charytoniuk

7
Mateusz - Proof pls, SELECT words, transcription, translation, sound FROM vocabulary WHERE menu_id=$menuId ORDER BY RAND() LIMIT 10dauert 0,0010, ohne LIMIT 10 dauerte es 0,0012 (in dieser Tabelle 3500 Wörter).
Arthur Kushman

26
@zeusakm 3500 Wörter sind nicht so viel; Das Problem ist, dass es über einen bestimmten Punkt hinaus explodiert, da MySQL nach dem Lesen jedes einzelnen Datensatzes ALLE Datensätze sortieren muss. Sobald diese Operation auf die Festplatte trifft, können Sie den Unterschied spüren.
Ja͢ck

16
Ich möchte mich nicht wiederholen, aber das ist wieder ein vollständiger Tabellenscan. Bei großen Tabellen ist dies sehr zeit- und speicherintensiv und kann dazu führen, dass eine temporäre Tabelle auf der Festplatte erstellt und ausgeführt wird, was sehr langsam ist.
Matt

10
Als ich 2010 ein Interview mit Facebook führte, fragten sie mich, wie ich in einer Lesung einen zufälligen Datensatz aus einer riesigen Datei unbekannter Größe auswählen könne. Sobald Sie eine Idee haben, können Sie diese leicht verallgemeinern, um mehrere Datensätze auszuwählen. Also ja, das Sortieren der gesamten Datei ist lächerlich. Gleichzeitig ist es sehr praktisch. Ich habe gerade diesen Ansatz verwendet, um 10 zufällige Zeilen aus einer Tabelle mit mehr als 1.000.000 Zeilen auszuwählen. Klar, ich musste ein bisschen warten; aber ich wollte nur eine Vorstellung davon bekommen, wie typische Zeilen in dieser Tabelle aussehen ...
osa

27

Einfache Abfrage, die eine hervorragende Leistung aufweist und mit Lücken arbeitet :

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

Diese Abfrage für eine 200K-Tabelle dauert 0,08 Sekunden und die normale Version (SELECT * FROM tbl ORDER BY RAND () LIMIT 10) dauert 0,35 Sekunden auf meinem Computer .

Dies ist schnell, da in der Sortierphase nur die indizierte ID-Spalte verwendet wird. Sie können dieses Verhalten in der Erklärung sehen:

SELECT * FROM tbl ORDER BY RAND () LIMIT 10: Einfach erklären

SELECT * FROM tbl AS t1 JOIN (SELECT id FROM tbl ORDER BY RAND () LIMIT 10) als t2 ON t1.id = t2.id. Geben Sie hier die Bildbeschreibung ein

Gewichtete Version : https://stackoverflow.com/a/41577458/893432


1
Entschuldigung, ich habe getestet! langsame Leistung bei 600.000 Datensätzen.
Dylan B

@ DylanB Ich habe die Antwort mit einem Test aktualisiert.
Ali

17

Ich erhalte schnelle Abfragen (ca. 0,5 Sekunden) mit einer langsamen CPU und wähle 10 zufällige Zeilen in einer nicht zwischengespeicherten 2-GB-Größe der MySQL-Datenbank mit 400 KB aus. Siehe hier meinen Code: Schnelle Auswahl von zufälligen Zeilen in 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
Angesichts meiner über 14 Millionen Datensätze Tabelle ist dies so langsam wieORDER BY RAND()
Fabrizio

5
@snippetsofcode In Ihrem Fall - 400k Zeilen können Sie einfach "ORDER BY rand ()" verwenden. Ihr Trick mit 3 Abfragen ist nutzlos. Sie können es umschreiben wie "SELECT ID, URL FROM Seiten WHERE ID IN (SELECT ID FROM Seiten ORDER BY Rand () LIMIT 10)"
Roman Podlinov

4
Ihre Technik führt immer noch einen Tabellenscan durch. Verwenden Sie FLUSH STATUS; SELECT ...; SHOW SESSION STATUS LIKE 'Handler%';, um es zu sehen.
Rick James

4
Versuchen Sie auch, diese Abfrage auf einer Webseite mit 200 Anforderungen / s auszuführen. Parallelität wird dich töten.
Marki555

Der Vorteil von @RomanPodlinov gegenüber der Ebene ORDER BY RAND()besteht darin, dass nur die IDs (nicht die vollständigen Zeilen) sortiert werden, sodass die temporäre Tabelle kleiner ist, aber dennoch alle sortiert werden muss.
Marki555

16

Es ist eine sehr einfache und einzeilige Abfrage.

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

21
Zu order by rand()
Ihrer Information

6
Manchmal wird die LANGSAME akzeptiert, wenn ich sie

Die Indizierung sollte auf die Tabelle angewendet werden, wenn sie groß ist.
Muhammad Azeem

1
Indizierung hilft hier nicht weiter. Indizes sind für ganz bestimmte Dinge hilfreich, und diese Abfrage gehört nicht dazu.
Andrew

13

Aus dem Buch:

Wählen Sie eine zufällige Zeile mit einem Versatz

Eine weitere Technik, die Probleme in den vorhergehenden Alternativen vermeidet, besteht darin, die Zeilen im Datensatz zu zählen und eine Zufallszahl zwischen 0 und der Anzahl zurückzugeben. Verwenden Sie diese Nummer dann als Offset, wenn Sie den Datensatz abfragen

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

Verwenden Sie diese Lösung, wenn Sie keine zusammenhängenden Schlüsselwerte annehmen können und sicherstellen müssen, dass jede Zeile eine gleichmäßige Chance hat, ausgewählt zu werden.


1
für sehr große Tische SELECT count(*)wird langsam.
Hans Z

7

So wählen Sie zufällige Zeilen aus einer Tabelle aus:

Von hier aus: Wählen Sie zufällige Zeilen in MySQL aus

Eine schnelle Verbesserung gegenüber "Tabellenscan" besteht darin, den Index zum Abrufen zufälliger IDs zu verwenden.

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

1
Das hilft einigen für MyISAM, aber nicht für InnoDB (vorausgesetzt, id ist das Clustered PRIMARY KEY).
Rick James

7

Wenn Ihre Schlüssel keine Lücken aufweisen und alle numerisch sind, können Sie Zufallszahlen berechnen und diese Zeilen auswählen. Dies wird aber wahrscheinlich nicht der Fall sein.

Eine Lösung wäre also die folgende:

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

Dies stellt im Grunde sicher, dass Sie eine Zufallszahl im Bereich Ihrer Tasten erhalten und dann die nächstbeste auswählen, die größer ist. Sie müssen dies 10 Mal tun.

Dies ist jedoch NICHT wirklich zufällig, da Ihre Schlüssel höchstwahrscheinlich nicht gleichmäßig verteilt werden.

Es ist wirklich ein großes Problem und nicht einfach zu lösen, um alle Anforderungen zu erfüllen. Rand () von MySQL ist das Beste, was Sie bekommen können, wenn Sie wirklich 10 zufällige Zeilen wollen.

Es gibt jedoch eine andere Lösung, die schnell ist, aber auch einen Kompromiss in Bezug auf Zufälligkeit aufweist, aber möglicherweise besser zu Ihnen passt. Lesen Sie hier darüber: Wie kann ich die ORDER BY RAND () - Funktion von MySQL optimieren?

Die Frage ist, wie zufällig Sie es brauchen.

Können Sie uns etwas mehr erklären, damit ich Ihnen eine gute Lösung geben kann?

Zum Beispiel hatte ein Unternehmen, mit dem ich zusammengearbeitet habe, eine Lösung, bei der es extrem schnell um absolute Zufälligkeit ging. Am Ende wurde die Datenbank mit Zufallswerten gefüllt, die absteigend ausgewählt und anschließend wieder auf andere Zufallswerte gesetzt wurden.

Wenn Sie kaum jemals aktualisieren, können Sie auch eine inkrementelle ID eingeben, damit Sie keine Lücken haben und vor der Auswahl nur zufällige Schlüssel berechnen können ... Dies hängt vom Anwendungsfall ab!


Hallo Joe. In diesem speziellen Fall sollten die Schlüssel keine Lücken aufweisen, dies kann sich jedoch im Laufe der Zeit ändern. Und während Ihre Antwort funktioniert, werden die zufälligen 10 Zeilen (vorausgesetzt, ich schreibe Limit 10) aufeinanderfolgend generiert, und ich wollte sozusagen mehr Zufälligkeit. :) Vielen Dank.
Francisc

Wenn Sie 10 benötigen, verwenden Sie eine Art Vereinigung, um 10 eindeutige Zeilen zu generieren.
Johno

tahts was ich gesagt habe. Sie müssen das 10 Mal ausführen. Das Kombinieren mit Wition Union ist eine Möglichkeit, es in einer Abfrage zusammenzufassen. siehe meinen Nachtrag vor 2 Minuten.
The Surrican

1
@ TheSurrican, Diese Lösung sieht cool aus, ist aber sehr fehlerhaft . Versuchen Sie, nur eine sehr große einzufügen, Idund alle Ihre zufälligen Abfragen geben Ihnen diese zurück Id.
Pacerier

1
FLOOR(RAND()*MAX(id))ist voreingenommen in Richtung der Rückgabe größerer IDs.
Rick James

3

Ich brauchte eine Abfrage, um eine große Anzahl zufälliger Zeilen aus einer ziemlich großen Tabelle zurückzugeben. Das habe ich mir ausgedacht. Erhalten Sie zuerst die maximale Datensatz-ID:

SELECT MAX(id) FROM table_name;

Ersetzen Sie diesen Wert dann durch:

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

Dabei ist max die maximale Datensatz-ID in der Tabelle und n die Anzahl der Zeilen, die Sie in Ihrer Ergebnismenge haben möchten. Die Annahme ist, dass es keine Lücken in den Datensatz-IDs gibt, obwohl ich bezweifle, dass dies das Ergebnis beeinflussen würde, wenn es solche gäbe (habe es aber nicht versucht). Ich habe diese gespeicherte Prozedur auch allgemeiner erstellt. Übergeben Sie den Tabellennamen und die Anzahl der zurückzugebenden Zeilen. Ich verwende MySQL 5.5.38 unter Windows 2008, 32 GB, Dual 3 GHz E5450 und in einer Tabelle mit 17.361.264 Zeilen ist es mit ~ 0,03 Sekunden / ~ 11 Sekunden ziemlich konsistent, um 1.000.000 Zeilen zurückzugeben. (Die Zeiten stammen aus MySQL Workbench 6.1. Sie können in der zweiten select-Anweisung auch CEIL anstelle von FLOOR verwenden, je nach Ihren Vorlieben.)

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

dann

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

3

Die besten Antworten wurden bereits veröffentlicht (hauptsächlich diejenigen, die auf den Link http://jan.kneschke.de/projects/mysql/order-by-rand/ verweisen ).

Ich möchte eine andere Möglichkeit der Beschleunigung aufzeigen - das Caching . Überlegen Sie, warum Sie zufällige Zeilen benötigen. Wahrscheinlich möchten Sie einen zufälligen Beitrag oder eine zufällige Anzeige auf einer Website anzeigen. Wenn Sie 100 Anforderungen / s erhalten, ist es wirklich erforderlich, dass jeder Besucher zufällige Zeilen erhält? Normalerweise ist es völlig in Ordnung, diese X zufälligen Zeilen 1 Sekunde (oder sogar 10 Sekunden) zwischenzuspeichern. Es spielt keine Rolle, ob 100 eindeutige Besucher in derselben Sekunde dieselben zufälligen Beiträge erhalten, da in der nächsten Sekunde weitere 100 Besucher unterschiedliche Beiträge erhalten.

Wenn Sie dieses Caching verwenden, können Sie auch einige der langsameren Lösungen zum Abrufen der Zufallsdaten verwenden, da diese unabhängig von Ihren Anforderungen nur einmal pro Sekunde von MySQL abgerufen werden.


3

Ich habe die Antwort von @Riedsio verbessert. Dies ist die effizienteste Abfrage, die ich für eine große, gleichmäßig verteilte Tabelle mit Lücken finden kann (getestet, um 1000 zufällige Zeilen aus einer Tabelle mit> 2,6B Zeilen zu erhalten).

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

Lassen Sie mich auspacken, was los ist.

  1. @max := (SELECT MAX(id) FROM table)
    • Ich berechne und speichere die max. Bei sehr großen Tabellen entsteht ein geringer Aufwand für die Berechnung MAX(id)jedes Mal, wenn Sie eine Zeile benötigen
  2. SELECT FLOOR(rand() * @max) + 1 as rand)
    • Ruft eine zufällige ID ab
  3. SELECT id FROM table INNER JOIN (...) on id > rand LIMIT 1
    • Dies füllt die Lücken. Wenn Sie zufällig eine Zahl in den Lücken auswählen, wird grundsätzlich nur die nächste ID ausgewählt. Unter der Annahme, dass die Lücken gleichmäßig verteilt sind, sollte dies kein Problem sein.

Durch die Vereinigung können Sie alles in eine Abfrage einpassen, sodass Sie nicht mehrere Abfragen ausführen müssen. Sie können damit auch den Aufwand für die Berechnung sparen MAX(id). Abhängig von Ihrer Anwendung kann dies sehr oder sehr wenig bedeuten.

Beachten Sie, dass dadurch nur die IDs und in zufälliger Reihenfolge abgerufen werden. Wenn Sie etwas Fortgeschritteneres tun möchten, empfehle ich Ihnen Folgendes:

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

Ich brauche 30 zufällige Datensätze, also sollte ich ändern , LIMIT 1um LIMIT 30in Abfrage überall
Hassaan

@ Hassaan sollten Sie nicht, dass das Ändern LIMIT 1zu LIMIT 3030 Datensätze in einer Reihe von einem zufälligen Punkt in der Tabelle erhalten würde. Sie sollten stattdessen 30 Kopien des (SELECT id FROM ....Teils in der Mitte haben.
Hans Z

Ich habe es versucht, aber es scheint nicht effizienter zu sein als zu Riedsioantworten. Ich habe mit 500 pro Sekunde Treffer auf die Seite mit PHP 7.0.22 und MariaDB auf Centos 7 versucht. Mit der RiedsioAntwort habe ich mehr als 500 zusätzliche erfolgreiche Antworten erhalten als Ihre Antwort.
Hassaan

1
Die Antwort von @Hassaan riedsio ergibt 1 Zeile, diese gibt Ihnen n Zeilen und reduziert den E / A-Overhead für die Abfrage. Möglicherweise können Sie Zeilen schneller abrufen, aber Ihr System wird stärker belastet.
Hans Z

3

Ich habe diese http://jan.kneschke.de/projects/mysql/order-by-rand/ von Riedsio verwendet (ich habe den Fall einer gespeicherten Prozedur verwendet, die einen oder mehrere zufällige Werte zurückgibt):

   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;

In dem Artikel löst er das Problem von Lücken in IDs, die nicht so zufällige Ergebnisse verursachen, indem er eine Tabelle verwaltet (unter Verwendung von Triggern usw. ... siehe Artikel); Ich löse das Problem, indem ich der Tabelle eine weitere Spalte hinzufüge, die mit zusammenhängenden Zahlen gefüllt ist, beginnend mit 1 (hinzufüge Bearbeiten: Diese Spalte wird der temporären Tabelle hinzugefügt, die zur Laufzeit von der Unterabfrage erstellt wurde, wirkt sich nicht auf Ihre permanente Tabelle aus):

   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;

In dem Artikel kann ich sehen, dass er große Anstrengungen unternommen hat, um den Code zu optimieren. Ich habe keine Ahnung, ob / wie sehr sich meine Änderungen auf die Leistung auswirken, aber es funktioniert sehr gut für mich.


"Ich habe keine Ahnung, ob / wie sehr sich meine Änderungen auf die Leistung auswirken" - ziemlich viel. Für die @no_gaps_idkein Index verwendet werden kann, so dass , wenn man sich anschaut , EXPLAINfür Ihre Anfrage, Sie haben Using filesortund Using where(ohne Index) für die Unterabfragen, im Gegensatz zu der ursprünglichen Abfrage.
Fabian Schmengler

2

Hier ist ein Game Changer, der für viele hilfreich sein kann.

Ich habe eine Tabelle mit 200.000 Zeilen und sequentiellen IDs. Ich musste N zufällige Zeilen auswählen. Daher habe ich mich dafür entschieden, zufällige Werte basierend auf der größten ID in der Tabelle zu generieren. Ich habe dieses Skript erstellt, um herauszufinden, welche Operation die schnellste ist:

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

Die Ergebnisse sind:

  • Anzahl: 36.8418693542479ms
  • Max: 0.241041183472ms
  • Reihenfolge: 0.216960906982ms

Basierend auf diesen Ergebnissen ist order desc die schnellste Operation, um die maximale ID zu erhalten.
Hier ist meine Antwort auf die Frage:

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

Zu Ihrer Information: Um 10 zufällige Zeilen aus einer 200k-Tabelle zu erhalten, habe ich 1,78 ms gebraucht (einschließlich aller Operationen auf der PHP-Seite).


3
Schlagen Sie vor, dass Sie die LIMITgeringfügig erhöhen - Sie können Duplikate erhalten.
Rick James

2

Dies ist super schnell und 100% zufällig, auch wenn Sie Lücken haben.

  1. Zählen Sie die Anzahl xder verfügbaren ZeilenSELECT COUNT(*) as rows FROM TABLE
  2. Wählen Sie 10 verschiedene Zufallszahlen a_1,a_2,...,a_10zwischen 0 undx
  3. Fragen Sie Ihre Zeilen folgendermaßen ab: SELECT * FROM TABLE LIMIT 1 offset a_ifür i = 1, ..., 10

Ich habe diesen Hack in dem Buch SQL Antipatterns von Bill Karwin gefunden .


Ich habe über die gleiche Lösung nachgedacht, bitte sagen Sie mir, ist sie schneller als die andere Methode?
G. Adnane

@ G.Adnane ist nicht schneller oder langsamer als die akzeptierte Antwort, aber die akzeptierte Antwort setzt eine gleichmäßige Verteilung der IDs voraus. Ich kann mir kein Szenario vorstellen, in dem dies garantiert werden kann. Diese Lösung befindet sich in O (1), wobei sich die Lösung SELECT column FROM table ORDER BY RAND() LIMIT 10in O (nlog (n)) befindet. Ja, dies ist die schnellste Lösung und funktioniert für jede Verteilung von IDs.
Adam

Nein, da es in dem Link, der für die akzeptierte Lösung gepostet wurde, andere Methoden gibt. Ich möchte wissen, ob diese Lösung schneller ist als die anderen. Auf andere Weise können wir versuchen, eine andere zu finden. Deshalb frage ich auf jeden Fall +1 für deine Antwort. Ich benutzte das gleiche
G. Adnane

Es gibt einen Fall, in dem Sie x Zeilen erhalten möchten, der Versatz jedoch bis zum Ende der Tabelle reicht, wodurch <x Zeilen oder nur 1 Zeile zurückgegeben werden. Ich habe Ihre Antwort nicht gesehen, bevor ich meine gepostet habe, aber ich habe sie hier klarer gemacht. stackoverflow.com/a/59981772/10387008
ZOLDIK

@ZOLDIK es scheint, dass Sie die ersten 10 Zeilen nach dem Versatz auswählen x. Ich würde argumentieren, dass dies keine zufällige Generation von 10 Zeilen ist. In meiner Antwort müssen Sie die Abfrage in Schritt drei zehnmal ausführen, dh man erhält nur eine Zeile pro Ausführung und muss sich keine Sorgen machen, wenn der Offset am Ende der Tabelle steht.
Adam

1

Wenn Sie nur eine Leseanforderung haben

Kombinieren Sie die Antwort von @redsio mit einer temporären Tabelle (600K sind nicht so viel):

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;

Und dann nimm eine Version von @redsios Antwort:

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;

Wenn der Tisch groß ist, können Sie den ersten Teil sieben:

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

Wenn Sie viele Leseanfragen haben

  1. Version: Sie könnten die Tabelle behalten tmp_randorder persistent halten und als datatable_idlist bezeichnen. Erstellen Sie diese Tabelle in bestimmten Intervallen (Tag, Stunde) neu, da sie auch Löcher bekommt. Wenn Ihr Tisch wirklich groß wird, können Sie auch Löcher nachfüllen

    Wählen Sie l.data_id als Ganzes aus der Liste datatable_id aus. l left join datatable dt on dt.id = l.data_id wobei dt.id null ist.

  2. Version: Geben Sie Ihrem Dataset eine random_sortorder-Spalte entweder direkt in datatable oder in einer persistenten zusätzlichen Tabelle datatable_sortorder. Indizieren Sie diese Spalte. Generieren Sie einen Zufallswert in Ihrer Anwendung (ich werde es nennen $rand).

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

Diese Lösung unterscheidet die 'Kantenzeilen' mit der höchsten und der niedrigsten random_sortorder, also ordnen Sie sie in Intervallen (einmal am Tag) neu an.


1

Eine andere einfache Lösung wäre, die Zeilen zu ordnen und eine davon zufällig abzurufen. Mit dieser Lösung müssen Sie keine 'Id'-basierte Spalte in der Tabelle haben.

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;

Sie können den Grenzwert nach Bedarf ändern, um auf so viele Zeilen zuzugreifen, wie Sie möchten. Dies sind jedoch meistens aufeinanderfolgende Werte.

Wenn Sie jedoch keine aufeinanderfolgenden Zufallswerte möchten, können Sie eine größere Stichprobe abrufen und zufällig auswählen. etwas wie ...

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

Eine Möglichkeit, die ich ziemlich gut finde, wenn es eine automatisch generierte ID gibt, ist die Verwendung des Modulo-Operators '%'. Wenn Sie beispielsweise 10.000 zufällige Datensätze von 70.000 benötigen, können Sie dies vereinfachen, indem Sie sagen, dass Sie 1 von 7 Zeilen benötigen. Dies kann in dieser Abfrage vereinfacht werden:

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

Wenn das Ergebnis der Division der Zielzeilen durch die verfügbare Gesamtzahl keine Ganzzahl ist, stehen Ihnen einige zusätzliche Zeilen zur Verfügung, als Sie angefordert haben. Fügen Sie daher eine LIMIT-Klausel hinzu, um die Ergebnismenge wie folgt zu kürzen:

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

Dies erfordert einen vollständigen Scan, ist jedoch schneller als ORDER BY RAND und meiner Meinung nach einfacher zu verstehen als andere in diesem Thread erwähnte Optionen. Auch wenn das System, das in die Datenbank schreibt, Sätze von Zeilen in Stapeln erstellt, erhalten Sie möglicherweise kein so zufälliges Ergebnis, wie Sie es erwartet haben.


2
Nun, da ich denke, wenn Sie jedes Mal, wenn Sie es aufrufen, zufällige Zeilen benötigen, ist dies nutzlos. Ich habe nur über die Notwendigkeit nachgedacht, zufällige Zeilen aus einem Satz zu erhalten, um Nachforschungen anzustellen. Ich denke immer noch, dass Modulo eine gute Sache ist, um im anderen Fall zu helfen. Sie können Modulo als First-Pass-Filter verwenden, um die Kosten einer ORDER BY RAND-Operation zu senken.
Nicolas Cohen


1

Ich habe alle Antworten durchgesehen, und ich glaube, niemand erwähnt diese Möglichkeit überhaupt, und ich bin mir nicht sicher, warum.

Wenn Sie äußerste Einfachheit und Geschwindigkeit zu geringen Kosten wünschen, erscheint es mir sinnvoll, eine Zufallszahl für jede Zeile in der Datenbank zu speichern. Erstellen Sie einfach eine zusätzliche Spalte random_numberund setzen Sie die Standardeinstellung auf RAND(). Erstellen Sie einen Index für diese Spalte.

Wenn Sie dann eine Zeile abrufen möchten, generieren Sie eine Zufallszahl in Ihrem Code (PHP, Perl, was auch immer) und vergleichen Sie diese mit der Spalte.

SELECT FROM tbl WHERE random_number >= :random LIMIT 1

Ich denke, obwohl es für eine einzelne Zeile sehr ordentlich ist, müssten Sie es für zehn Zeilen wie das OP zehn Mal einzeln aufrufen (oder sich eine clevere Optimierung einfallen lassen, die mir sofort entgeht).


Dies ist eigentlich ein sehr schöner und effizienter Ansatz. Der einzige Nachteil ist die Tatsache, dass Sie Platz gegen Geschwindigkeit eingetauscht haben, was meiner Meinung nach ein faires Geschäft ist.
Tochukwu Nkemdilim

Vielen Dank. Ich hatte ein Szenario, in dem die Haupttabelle, aus der ich eine zufällige Zeile haben wollte, 5 Millionen Zeilen und ziemlich viele Verknüpfungen hatte, und nachdem ich die meisten Ansätze in dieser Frage ausprobiert hatte, war dies der Kludge, auf den ich mich festgelegt hatte. Eine zusätzliche Kolumne war für mich ein sehr lohnender Kompromiss.
Codemonkey

0

Das Folgende sollte schnell, unvoreingenommen und unabhängig von der ID-Spalte sein. Es kann jedoch nicht garantiert werden, dass die Anzahl der zurückgegebenen Zeilen mit der Anzahl der angeforderten Zeilen übereinstimmt.

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

Erläuterung: Angenommen, Sie möchten 10 von 100 Zeilen, dann hat jede Zeile eine Wahrscheinlichkeit von 1/10, ausgewählt zu werden, was durch erreicht werden könnte WHERE RAND() < 0.1. Dieser Ansatz garantiert nicht 10 Zeilen; Wenn die Abfrage jedoch genügend oft ausgeführt wird, beträgt die durchschnittliche Anzahl der Zeilen pro Ausführung etwa 10, und jede Zeile in der Tabelle wird gleichmäßig ausgewählt.


0

Sie können leicht einen zufälligen Versatz mit einem Limit verwenden

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

Sie können auch eine where-Klausel wie diese anwenden

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;

Die Ausführung der Tabellenabfrage mit 600.000 Zeilen (700 MB) dauerte ~ 0,016 Sekunden. Festplattenlaufwerk

--EDIT--
   Der Offset kann einen Wert nahe dem Ende der Tabelle annehmen, was dazu führt, dass die select-Anweisung weniger Zeilen (oder möglicherweise nur 1) zurückgibt row), um dies zu vermeiden, können wir das offseterneut überprüfen , nachdem wir es deklariert haben

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

Ich benutze diese Abfrage:

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

Abfragezeit: 0,016 s


Mit PKs wie 1,2,9,15. Durch die obige Abfrage erhalten Sie Zeilen wie 4, 7, 14, 11, die nicht ausreichen!
Junaid Atari

-2

So mache ich es:

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

Ich mag es, weil keine anderen Tabellen erforderlich sind, es einfach zu schreiben ist und sehr schnell ausgeführt werden kann.


5
Das ist ein vollständiger Tabellenscan und es werden keine Indizes verwendet. Für große Tische und geschäftige Umgebungen, die groß sind, nein, nein.
Matt

-2

Verwenden Sie die folgende einfache Abfrage, um zufällige Daten aus einer Tabelle abzurufen.

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

Wenn Sie eine Join-Anweisung verwenden möchten und wo Filter verwendet werden können.
MANOJ

3
Aus welchem ​​Teil der Abfrage erhalten Sie die Zufälligkeit?
Marki555

-4

Ich denke, das ist der bestmögliche Weg.

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

8
Zur Hölle, nein, das ist eine der schlechtesten Möglichkeiten, zufällige Zeilen aus der Tabelle zu erhalten. Das ist vollständiger Tabellenscan + Dateisortierung + tmp-Tabelle = schlechte Leistung.
Matt

1
Neben der Leistung ist es auch alles andere als zufällig. Sie bestellen nach dem Produkt aus der ID und einer Zufallszahl, anstatt nur nach einer Zufallszahl zu bestellen. Dies bedeutet, dass Zeilen mit niedrigeren IDs dazu neigen, früher in Ihrer Ergebnismenge zu erscheinen.
Mark Amery
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.