MySQL sélectionne 10 lignes aléatoires parmi 600K lignes rapidement

463

Comment puis-je écrire au mieux une requête qui sélectionne 10 lignes au hasard sur un total de 600 Ko?

Francisc
la source
15
Voici 8 techniques ; peut-être que l'un fonctionnera bien dans votre cas.
Rick James

Réponses:

386

Un excellent poste de traitement de plusieurs cas, du simple, aux lacunes, au non uniforme avec des lacunes.

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

Pour le cas le plus général, voici comment procéder:

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

Cela suppose que la distribution des identifiants est égale et qu'il peut y avoir des lacunes dans la liste des identifiants. Voir l'article pour des exemples plus avancés

Riedsio
la source
52
Oui, si vous avez des écarts potentiellement importants dans les identifiants, les chances que vos identifiants les plus bas soient choisis au hasard sont beaucoup plus faibles que vos identifiants élevés. En fait, la chance que le premier ID après le plus grand écart soit choisi est en fait la plus élevée. Par conséquent, ce n'est pas aléatoire par définition.
lukeocodes
6
Comment obtenez-vous 10 lignes aléatoires différentes? Devez-vous définir la limite à 10, puis répéter 10 fois avec mysqli_fetch_assoc($result)? Ou ces 10 résultats ne se distinguent-ils pas nécessairement?
Adam
12
Le hasard requiert une chance égale pour tout résultat, dans mon esprit. ;)
lukeocodes
4
L'article complet traite de problèmes tels que les distributions inégales et les résultats répétés.
Bradd Szonye
1
en particulier, si vous avez un écart au début de vos identifiants, le premier sera sélectionné (min / max-min) du temps. Dans ce cas, un simple ajustement est MAX () - MIN () * RAND + MIN (), ce qui n'est pas trop lent.
Code Abominator
343
SELECT column FROM table
ORDER BY RAND()
LIMIT 10

Pas la solution efficace mais fonctionne

Preetam Purbia
la source
139
ORDER BY RAND()est relativement lent
Mateusz Charytoniuk
7
Mateusz - preuve pls, SELECT words, transcription, translation, sound FROM vocabulary WHERE menu_id=$menuId ORDER BY RAND() LIMIT 10prend 0,0010, sans LIMIT 10 il a fallu 0,0012 (dans ce tableau 3500 mots).
Arthur Kushman
26
@zeusakm 3500 mots n'est pas tant que ça; le problème est qu'il explose au-delà d'un certain point car MySQL doit réellement trier TOUS les enregistrements après avoir lu chacun; une fois que cette opération frappe le disque dur, vous pouvez sentir la différence.
Ja͢ck
16
Je ne veux pas me répéter, mais encore une fois, c'est une analyse complète de la table. Sur une grande table, cela prend beaucoup de temps et de mémoire et peut entraîner la création et le fonctionnement d'une table temporaire sur le disque, ce qui est très lent.
mat
10
Lorsque j'ai interviewé Facebook en 2010, ils m'ont demandé comment sélectionner un enregistrement aléatoire dans un énorme fichier de taille inconnue, en une seule lecture. Une fois que vous avez proposé une idée, il est facile de la généraliser pour sélectionner plusieurs enregistrements. Alors oui, trier tout le fichier est ridicule. En même temps, c'est très pratique. Je viens d'utiliser cette approche pour choisir 10 lignes aléatoires dans une table avec 1 000 000 + lignes. Bien sûr, j'ai dû attendre un peu; mais je voulais juste avoir une idée de ce à quoi ressemblent les lignes typiques de ce tableau ...
osa
27

Requête simple qui a d' excellentes performances et fonctionne avec des lacunes :

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

Cette requête sur une table de 200 Ko prend 0,08 s et la version normale (SELECT * FROM tbl ORDER BY RAND () LIMIT 10) prend 0,35 s sur ma machine.

Ceci est rapide car la phase de tri utilise uniquement la colonne ID indexée. Vous pouvez voir ce comportement dans l'explication:

SÉLECTIONNEZ * À PARTIR DE L'ORDRE TBL PAR RAND () LIMITE 10: Explication simple

SELECT * FROM tbl AS t1 JOIN (SELECT id FROM tbl ORDER BY RAND () LIMIT 10) as t2 ON t1.id = t2.id entrez la description de l'image ici

Version pondérée : https://stackoverflow.com/a/41577458/893432

Ali
la source
1
Désolé, j'ai testé! performances lentes sur 600k enregistrements.
Dylan B
@DylanB J'ai mis à jour la réponse avec un test.
Ali
17

Je reçois des requêtes rapides (environ 0,5 seconde) avec un processeur lent , sélectionnant 10 lignes aléatoires dans une base de données MySQL de 400 Ko sans mise en cache de 2 Go. Voir ici mon code: sélection rapide de lignes aléatoires dans 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);
?>
extraits de code
la source
11
Compte tenu de ma table de plus de 14 millions d'enregistrements, c'est aussi lent queORDER BY RAND()
Fabrizio
5
@snippetsofcode Dans votre cas - 400k de lignes, vous pouvez utiliser le simple "ORDER BY rand ()". Votre astuce avec 3 requêtes est inutile. Vous pouvez le réécrire comme "SELECT id, url FROM pages WHERE id IN (SELECT id FROM pages ORDER BY rand () LIMIT 10)"
Roman Podlinov
4
Votre technique fait toujours un scan de table. Utilisez FLUSH STATUS; SELECT ...; SHOW SESSION STATUS LIKE 'Handler%';pour le voir.
Rick James
4
Essayez également d'exécuter cette requête dans une page Web à 200 requêtes / s. La concurrence vous tuera.
Marki555
@RomanPodlinov a l'avantage de cela par rapport à la plaine, ORDER BY RAND()car il trie uniquement les identifiants (pas les lignes complètes), donc la table temporaire est plus petite, mais doit toujours les trier tous.
Marki555
16

Sa requête très simple et sur une seule ligne.

SELECT * FROM Table_Name ORDER BY RAND() LIMIT 0,10;
Muhammad Azeem
la source
21
FYI, order by rand()est très lent si la table est grande
evilReiko
6
Parfois le SLOW est accepté si je veux le garder SIMPLE
L'indexation doit être appliquée sur la table si elle est importante.
Muhammad Azeem
1
L'indexation n'aidera pas ici. Les index sont utiles pour des choses très spécifiques, et cette requête n'en fait pas partie.
Andrew
13

Du livre:

Choisissez une ligne aléatoire en utilisant un décalage

Une autre technique encore qui évite les problèmes rencontrés dans les alternatives précédentes consiste à compter les lignes de l'ensemble de données et à renvoyer un nombre aléatoire entre 0 et le nombre. Utilisez ensuite ce nombre comme décalage lors de l'interrogation de l'ensemble de données

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

Utilisez cette solution lorsque vous ne pouvez pas assumer des valeurs de clé contiguës et que vous devez vous assurer que chaque ligne a une chance égale d'être sélectionnée.

zloctb
la source
1
pour les très grandes tables, SELECT count(*)devient lent.
Hans Z
7

Comment sélectionner des lignes aléatoires dans une table:

D'ici: Sélectionnez des lignes aléatoires dans MySQL

Une amélioration rapide par rapport au "scan de table" consiste à utiliser l'index pour récupérer des identifiants aléatoires.

SELECT *
FROM random, (
        SELECT id AS sid
        FROM random
        ORDER BY RAND( )
        LIMIT 10
    ) tmp
WHERE random.id = tmp.sid;
user1931858
la source
1
Cela aide certains pour MyISAM, mais pas pour InnoDB (en supposant que id est le cluster PRIMARY KEY).
Rick James
7

Eh bien, si vous n'avez pas de lacunes dans vos clés et qu'elles sont toutes numériques, vous pouvez calculer des nombres aléatoires et sélectionner ces lignes. mais ce ne sera probablement pas le cas.

Une solution serait donc la suivante:

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

ce qui garantira essentiellement que vous obtenez un nombre aléatoire dans la plage de vos clés, puis vous sélectionnez le meilleur suivant qui est le plus grand. vous devez le faire 10 fois.

cependant, ce n'est PAS vraiment aléatoire, car vos clés ne seront probablement pas distribuées également.

C'est vraiment un gros problème et pas facile à résoudre en remplissant toutes les exigences, rand () de MySQL est le meilleur que vous pouvez obtenir si vous voulez vraiment 10 lignes aléatoires.

Il existe cependant une autre solution qui est rapide mais qui a aussi un compromis en matière de hasard, mais qui peut vous convenir mieux. Lisez à ce sujet ici: Comment puis-je optimiser la fonction ORDER BY RAND () de MySQL?

La question est de savoir à quel point vous en avez besoin.

Pouvez-vous expliquer un peu plus afin que je puisse vous donner une bonne solution.

Par exemple, une entreprise avec laquelle je travaillais avait une solution où ils avaient besoin d'une rapidité absolue extrêmement rapide. Ils ont fini par pré-remplir la base de données avec des valeurs aléatoires qui ont été sélectionnées en ordre décroissant et réglées à nouveau sur différentes valeurs aléatoires par la suite.

Si vous ne mettez presque jamais à jour, vous pouvez également remplir un identifiant d'incrémentation afin que vous n'ayez pas d'espace et que vous puissiez simplement calculer des clés aléatoires avant de sélectionner ... Cela dépend du cas d'utilisation!

Le Surrican
la source
Salut Joe. Dans ce cas particulier, les clés ne doivent pas manquer d'espaces, mais avec le temps, cela peut changer. Et pendant que votre réponse fonctionne, elle générera les 10 lignes aléatoires (à condition que j'écrive la limite 10) qui sont consécutives et je voulais plus d'aléatoire pour ainsi dire. :) Merci.
Francisc
Si vous en avez besoin de 10, utilisez une sorte d'union pour générer 10 lignes uniques.
johno
ce que j'ai dit. vous devez l'exécuter 10 fois. le combiner avec l'union est une façon de le mettre dans une seule requête. voir mon addenda il y a 2 minutes.
Le Surrican du
1
@TheSurrican, Cette solution a l'air cool mais est très imparfaite . Essayez d'insérer un seul très grand Idet toutes vos requêtes aléatoires vous le rendront Id.
Pacerier
1
FLOOR(RAND()*MAX(id))est biaisé pour renvoyer des identifiants plus importants.
Rick James
3

J'avais besoin d'une requête pour renvoyer un grand nombre de lignes aléatoires à partir d'une table assez grande. C'est ce que j'ai trouvé. Obtenez d'abord l'ID d'enregistrement maximal:

SELECT MAX(id) FROM table_name;

Remplacez ensuite cette valeur par:

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

Où max est l'ID d'enregistrement maximum dans la table et n est le nombre de lignes que vous souhaitez dans votre jeu de résultats. L'hypothèse est qu'il n'y a pas de lacunes dans les identifiants d'enregistrement, bien que je doute que cela affecterait le résultat s'il y en avait (je ne l'ai pas essayé cependant). J'ai également créé cette procédure stockée pour être plus générique; transmettez le nom de la table et le nombre de lignes à renvoyer. J'exécute MySQL 5.5.38 sur Windows 2008, 32 Go, double 3GHz E5450, et sur une table avec 17361 264 lignes, il est assez cohérent à ~ 0,03 sec / ~ 11 sec pour renvoyer 1000000 de lignes. (les heures proviennent de MySQL Workbench 6.1; vous pouvez également utiliser CEIL au lieu de FLOOR dans la 2e instruction select selon vos préférences)

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

puis

CALL [schema name].random_rows([table name], n);
user2406626
la source
3

Toutes les meilleures réponses ont déjà été postées (principalement celles faisant référence au lien http://jan.kneschke.de/projects/mysql/order-by-rand/ ).

Je veux identifier une autre possibilité d'accélération - la mise en cache . Réfléchissez à la raison pour laquelle vous devez obtenir des lignes aléatoires. Vous souhaitez probablement afficher une publication aléatoire ou une annonce aléatoire sur un site Web. Si vous obtenez 100 req / s, est-il vraiment nécessaire que chaque visiteur reçoive des lignes aléatoires? Habituellement, il est tout à fait correct de mettre en cache ces X lignes aléatoires pendant 1 seconde (ou même 10 secondes). Peu importe si 100 visiteurs uniques dans la même 1 seconde reçoivent les mêmes messages aléatoires, car la seconde suivante, 100 autres visiteurs recevront un ensemble de messages différent.

Lorsque vous utilisez cette mise en cache, vous pouvez également utiliser une partie de la solution la plus lente pour obtenir les données aléatoires car elles ne seront récupérées de MySQL qu'une seule fois par seconde, indépendamment de votre demande.

Marki555
la source
3

J'ai amélioré la réponse de @Riedsio. C'est la requête la plus efficace que je puisse trouver sur une grande table uniformément distribuée avec des lacunes (testée sur l'obtention de 1000 lignes aléatoires à partir d'une table qui a> 2,6 milliards de lignes).

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

Permettez-moi de déballer ce qui se passe.

  1. @max := (SELECT MAX(id) FROM table)
    • Je calcule et enregistre le max. Pour les très grands tableaux, il y a une légère surcharge pour calculer MAX(id)chaque fois que vous avez besoin d'une ligne
  2. SELECT FLOOR(rand() * @max) + 1 as rand)
    • Obtient un identifiant aléatoire
  3. SELECT id FROM table INNER JOIN (...) on id > rand LIMIT 1
    • Cela comble les lacunes. Fondamentalement, si vous sélectionnez au hasard un nombre dans les espaces, il ne vous reste plus qu'à choisir l'id suivant. En supposant que les écarts sont uniformément répartis, cela ne devrait pas être un problème.

Faire l'union vous aide à tout ranger dans 1 requête afin que vous puissiez éviter de faire plusieurs requêtes. Il vous permet également d'économiser les frais généraux de calcul MAX(id). Selon votre application, cela peut avoir beaucoup ou très peu d'importance.

Notez que cela n'obtient que les identifiants et les obtient dans un ordre aléatoire. Si vous voulez faire quelque chose de plus avancé, je vous recommande de faire ceci:

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
Hans Z
la source
J'ai besoin de 30 dossiers au hasard, donc devrais - je changer LIMIT 1de LIMIT 30partout dans la requête
Hassaan
@Hassaan vous ne devriez pas, ce changement LIMIT 1vous LIMIT 30donnerait 30 enregistrements consécutifs à partir d'un point aléatoire dans le tableau. Vous devriez plutôt avoir 30 copies de la (SELECT id FROM ....partie au milieu.
Hans Z
J'ai essayé mais ne semble pas plus efficace que de Riedsiorépondre. J'ai essayé avec 500 accès par seconde à la page en utilisant PHP 7.0.22 et MariaDB sur centos 7, avec la Riedsioréponse j'ai obtenu plus de 500 réponses supplémentaires puis votre réponse.
Hassaan
1
La réponse de @Hassaan riedsio donne 1 ligne, celle-ci vous donne n lignes, ainsi que la réduction des frais généraux d'E / S pour les requêtes. Vous pourrez peut-être obtenir des lignes plus rapidement, mais avec plus de charge sur votre système.
Hans Z
3

J'ai utilisé ce http://jan.kneschke.de/projects/mysql/order-by-rand/ publié par Riedsio (j'ai utilisé le cas d'une procédure stockée qui renvoie une ou plusieurs valeurs aléatoires):

   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;

Dans l'article, il résout le problème des lacunes dans les identifiants provoquant des résultats pas si aléatoires en maintenant une table (en utilisant des déclencheurs, etc ... voir l'article); Je résout le problème en ajoutant une autre colonne à la table, remplie de nombres contigus, à partir de 1 ( modifier: cette colonne est ajoutée à la table temporaire créée par la sous-requête lors de l'exécution, n'affecte pas votre table permanente):

   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;

Dans l'article, je peux voir qu'il s'est donné beaucoup de mal pour optimiser le code; Je n'ai aucune idée si / à quel point mes changements ont un impact sur les performances mais fonctionnent très bien pour moi.

bogdan
la source
"Je n'ai aucune idée si / à quel point mes changements ont un impact sur les performances" - beaucoup. Car @no_gaps_idaucun index ne peut être utilisé, donc si vous recherchez EXPLAINvotre requête, vous avez Using filesortet Using where(sans index) pour les sous-requêtes, contrairement à la requête d'origine.
Fabian Schmengler du
2

Voici un changeur de jeu qui peut être utile pour beaucoup;

J'ai une table avec 200k lignes, avec des identifiants séquentiels , j'avais besoin de choisir N lignes aléatoires, alors j'ai choisi de générer des valeurs aléatoires basées sur le plus grand ID de la table, j'ai créé ce script pour savoir quelle est l'opération la plus rapide:

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

Les résultats sont:

  • Nombre: 36.8418693542479ms
  • Max: 0.241041183472ms
  • Commande: 0.216960906982ms

Sur la base de ces résultats, l'ordre desc est l'opération la plus rapide pour obtenir l'id max,
voici ma réponse à la question:

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: Pour obtenir 10 lignes aléatoires d'une table de 200k, cela m'a pris 1,78 ms (y compris toutes les opérations du côté php)

António Almeida
la source
3
Je vous suggère d'augmenter LIMITlégèrement - vous pouvez obtenir des doublons.
Rick James
2

C'est super rapide et 100% aléatoire même si vous avez des lacunes.

  1. Comptez le nombre xde lignes dont vous disposezSELECT COUNT(*) as rows FROM TABLE
  2. Choisissez 10 nombres aléatoires distincts a_1,a_2,...,a_10entre 0 etx
  3. Interrogez vos lignes comme ceci: SELECT * FROM TABLE LIMIT 1 offset a_ipour i = 1, ..., 10

J'ai trouvé ce hack dans le livre SQL Antipatterns de Bill Karwin .

Adam
la source
Je pensais à la même solution, dites-moi s'il vous plaît, est-ce plus rapide que la méthode des autres?
G.Adnane
@ G.Adnane ce n'est pas plus rapide ou plus lent que la réponse acceptée, mais la réponse acceptée suppose une distribution égale des identifiants. Je ne peux imaginer aucun scénario où cela puisse être garanti. Cette solution est en O (1) où la solution SELECT column FROM table ORDER BY RAND() LIMIT 10est en O (nlog (n)). Alors oui, c'est la solution à jeun et elle fonctionne pour toute distribution d'ID.
Adam
non, car dans le lien posté pour la solution acceptée, il y a d'autres méthodes, je veux savoir si cette solution est plus rapide que les autres, autrement, on peut essayer d'en trouver une autre, c'est pourquoi je demande, en tout cas, +1 pour ta réponse. J'utilisais le samething
G. Adnane
il y a un cas où vous voulez obtenir x nombre de lignes mais le décalage va à la fin du tableau qui renverra <x lignes ou seulement 1 ligne. je n'ai pas vu votre réponse avant de poster la mienne mais je l'ai expliqué plus clairement ici stackoverflow.com/a/59981772/10387008
ZOLDIK
@ZOLDIK, il semble que vous choisissiez les 10 premières lignes après le décalage x. Je dirais que ce n'est pas une génération aléatoire de 10 lignes. Dans ma réponse, vous devez exécuter la requête à l'étape trois 10 fois, c'est-à-dire que l'on n'obtient qu'une ligne par exécution et ne vous inquiétez pas si le décalage est à la fin du tableau.
Adam
1

Si vous n'avez qu'une seule demande de lecture

Combinez la réponse de @redsio avec une table temporaire (600K n'est pas tant que ça):

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;

Et puis prenez une version de @redsios Answer:

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;

Si la table est grande, vous pouvez tamiser la première partie:

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

Si vous avez plusieurs demandes de lecture

  1. Version: vous pouvez conserver la table tmp_randorderpersistante, appelez-la datatable_idlist. Recréez ce tableau à certains intervalles (jour, heure), car il obtiendra également des trous. Si votre table devient vraiment grande, vous pouvez également remplir des trous

    sélectionnez l.data_id dans son ensemble dans datatable_idlist l jointure gauche datatable dt on dt.id = l.data_id où dt.id est nul;

  2. Version: donnez à votre jeu de données une colonne random_sortorder directement dans datatable ou dans une table supplémentaire persistante datatable_sortorder. Indexez cette colonne. Générez une valeur aléatoire dans votre application (je l'appellerai $rand).

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

Cette solution discrimine les «lignes de bord» avec le plus haut et le plus bas random_sortorder, afin de les réorganiser à intervalles (une fois par jour).

flaschenpost
la source
1

Une autre solution simple serait de classer les lignes et d'en récupérer une au hasard et avec cette solution, vous n'aurez pas besoin d'avoir de colonne basée sur 'Id' dans le tableau.

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;

Vous pouvez modifier la valeur limite selon votre besoin d'accéder à autant de lignes que vous le souhaitez, mais il s'agit principalement de valeurs consécutives.

Cependant, si vous ne voulez pas de valeurs aléatoires consécutives, vous pouvez extraire un échantillon plus grand et le sélectionner de manière aléatoire. quelque chose comme ...

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;
sactiw
la source
1

Une façon que je trouve assez bonne s'il y a un identifiant généré automatiquement est d'utiliser l'opérateur modulo '%'. Par exemple, si vous avez besoin de 10 000 enregistrements aléatoires sur 70 000, vous pouvez simplifier cela en disant que vous avez besoin d'une ligne sur 7. Cela peut être simplifié dans cette requête:

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

Si le résultat de la division des lignes cibles par le total disponible n'est pas un entier, vous aurez quelques lignes supplémentaires par rapport à ce que vous avez demandé, vous devez donc ajouter une clause LIMIT pour vous aider à réduire le jeu de résultats comme ceci:

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

Cela nécessite une analyse complète, mais c'est plus rapide que ORDER BY RAND, et à mon avis plus simple à comprendre que les autres options mentionnées dans ce fil. De plus, si le système qui écrit dans la base de données crée des ensembles de lignes par lots, vous pourriez ne pas obtenir un résultat aussi aléatoire que prévu.

Nicolas Cohen
la source
2
Maintenant que je le pense, si vous avez besoin de lignes aléatoires à chaque fois que vous l'appelez, cela est inutile. Je ne pensais qu'à la nécessité d'obtenir des lignes aléatoires d'un ensemble pour faire des recherches. Je pense toujours que modulo est une bonne chose pour aider dans l'autre cas. Vous pouvez utiliser modulo comme filtre de premier passage pour réduire le coût d'une opération ORDER BY RAND.
Nicolas Cohen
1

J'ai parcouru toutes les réponses, et je pense que personne ne mentionne cette possibilité du tout, et je ne sais pas pourquoi.

Si vous voulez une simplicité et une vitesse maximales, à moindre coût, alors il me semble logique de stocker un nombre aléatoire contre chaque ligne de la base de données. Créez simplement une colonne supplémentaire random_numberet définissez sa valeur par défaut sur RAND(). Créez un index sur cette colonne.

Ensuite, lorsque vous souhaitez récupérer une ligne, générez un nombre aléatoire dans votre code (PHP, Perl, peu importe) et comparez-le à la colonne.

SELECT FROM tbl WHERE random_number >= :random LIMIT 1

Je suppose que même si c'est très soigné pour une seule rangée, pour dix rangées comme l'OP vous a demandé de l'appeler dix fois (ou de proposer un ajustement intelligent qui m'échappe immédiatement)

Codemonkey
la source
Il s'agit en fait d'une approche très agréable et efficace. Le seul inconvénient est le fait que vous avez échangé de l'espace contre de la vitesse, ce qui semble être une bonne affaire à mon avis.
Tochukwu Nkemdilim
Merci. J'ai eu un scénario où la table principale à partir de laquelle je voulais une ligne aléatoire avait 5 millions de lignes et pas mal de jointures, et après avoir essayé la plupart des approches dans cette question, c'était le truc sur lequel je me suis installé. Une colonne supplémentaire a été un compromis très valable, pour moi.
Codemonkey
0

Les éléments suivants doivent être rapides, impartiaux et indépendants de la colonne id. Cependant, cela ne garantit pas que le nombre de lignes renvoyées correspondra au nombre de lignes demandées.

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

Explication: en supposant que vous vouliez 10 lignes sur 100, chaque ligne a 1/10 de probabilité d'être sélectionné, ce qui pourrait être réalisé par WHERE RAND() < 0.1. Cette approche ne garantit pas 10 lignes; mais si la requête est exécutée suffisamment de fois, le nombre moyen de lignes par exécution sera d'environ 10 et chaque ligne du tableau sera sélectionnée de manière égale.

Salman A
la source
0

Vous pouvez facilement utiliser un décalage aléatoire avec une limite

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

Vous pouvez également appliquer une clause where comme so

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;

Testé sur 600 000 lignes (700 Mo), l'exécution des requêtes de table a pris environ 0,016 s de disque dur

--EDIT--
   Le décalage peut prendre une valeur proche de la fin de la table, ce qui entraînera l'instruction select renvoyant moins de lignes (ou peut-être seulement 1 ligne), pour éviter cela, nous pouvons vérifier à offsetnouveau après l'avoir déclaré, comme si

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;
ZOLDIK
la source
-1

J'utilise cette requête:

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

temps de requête: 0,016 s

josejavierfm
la source
Avoir des PK comme 1,2,9,15. par la requête ci-dessus, vous obtiendrez des lignes comme 4, 7, 14, 11 qui sont insuffisantes!
Junaid Atari
-2

Voici comment je le fais:

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

Je l'aime car il ne nécessite pas d'autres tables, il est simple à écrire et très rapide à exécuter.

Bernardo Siu
la source
5
C'est l'analyse complète de la table et il n'utilise aucun index. Pour les grandes tables et les environnements occupés, c'est grand non non.
mat
-2

Utilisez la requête simple ci-dessous pour obtenir des données aléatoires à partir d'une table.

SELECT user_firstname ,
COUNT(DISTINCT usr_fk_id) cnt
FROM userdetails 
GROUP BY usr_fk_id 
ORDER BY cnt ASC  
LIMIT 10
MANOJ
la source
Si vous souhaitez utiliser n'importe quelle instruction de jointure et quel filtre vous pouvez utiliser.
MANOJ
3
De quelle partie de la requête vous obtenez l'aléatoire?
Marki555
-4

Je suppose que c'est la meilleure façon possible ..

SELECT id, id * RAND( ) AS random_no, first_name, last_name
FROM user
ORDER BY random_no
Ritesh Patadiya
la source
8
Enfer non, c'est l'une des pires façons d'obtenir des lignes aléatoires de la table. C'est l'analyse complète de la table + le tri de fichiers + la table tmp = de mauvaises performances.
mat
1
Outre les performances, il est également loin d'être parfaitement aléatoire; vous triez par le produit de l'id et d'un nombre aléatoire, plutôt que simplement par un nombre aléatoire, ce qui signifie que les lignes avec des identifiants inférieurs seront biaisées pour apparaître plus tôt dans votre ensemble de résultats.
Mark Amery