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
ORDERBY 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
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.
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 ORDERBY 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:
SELECT * FROM tbl AS t1 JOIN (SELECT id FROM tbl ORDER BY RAND () LIMIT 10) as t2 ON t1.id = t2.id
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);?>
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.
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
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.
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.
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.
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!
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)
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.
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 FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max :=(SELECT MAX(id)FROMtable))+1as rand) r on id > rand LIMIT 1)UNION(SELECT id FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max)+1as rand) r on id > rand LIMIT 1)UNION(SELECT id FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max)+1as rand) r on id > rand LIMIT 1)UNION(SELECT id FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max)+1as rand) r on id > rand LIMIT 1)UNION(SELECT id FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max)+1as rand) r on id > rand LIMIT 1)UNION(SELECT id FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max)+1as rand) r on id > rand LIMIT 1)UNION(SELECT id FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max)+1as rand) r on id > rand LIMIT 1)UNION(SELECT id FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max)+1as rand) r on id > rand LIMIT 1)UNION(SELECT id FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max)+1as rand) r on id > rand LIMIT 1)UNION(SELECT id FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max)+1as rand) r on id > rand LIMIT 1)
Permettez-moi de déballer ce qui se passe.
@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
SELECT FLOOR(rand() * @max) + 1 as rand)
Obtient un identifiant aléatoire
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, etcFROMtable t
INNERJOIN((SELECT id FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max :=(SELECT MAX(id)FROMtable))+1as rand) r on id > rand LIMIT 1)UNION(SELECT id FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max)+1as rand) r on id > rand LIMIT 1)UNION(SELECT id FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max)+1as rand) r on id > rand LIMIT 1)UNION(SELECT id FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max)+1as rand) r on id > rand LIMIT 1)UNION(SELECT id FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max)+1as rand) r on id > rand LIMIT 1)UNION(SELECT id FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max)+1as rand) r on id > rand LIMIT 1)UNION(SELECT id FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max)+1as rand) r on id > rand LIMIT 1)UNION(SELECT id FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max)+1as rand) r on id > rand LIMIT 1)UNION(SELECT id FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max)+1as rand) r on id > rand LIMIT 1)UNION(SELECT id FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max)+1as rand) r on id > rand LIMIT 1)) x ON x.id = t.id
ORDERBY t.id
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.
DROP TEMPORARY TABLEIFEXISTS rands;CREATE TEMPORARY TABLE rands ( rand_id INT );
loop_me: LOOP
IF cnt <1THEN
LEAVE loop_me;ENDIF;INSERTINTO 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
ORDERBY 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 TABLEIFEXISTS rands;CREATE TEMPORARY TABLE rands ( rand_id INT );
loop_me: LOOP
IF cnt <1THEN
LEAVE loop_me;ENDIF;SET@no_gaps_id :=0;INSERTINTO rands
SELECT r1.id
FROM(SELECT id,@no_gaps_id :=@no_gaps_id +1AS 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
ORDERBY 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.
"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 ORDERBY 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)
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 TABLEIFEXISTS tmp_randorder;CREATETABLE tmp_randorder (id int(11)notnull auto_increment primarykey, data_id int(11));INSERTINTO 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
INNERJOIN tmp_randorder rndo on rndo.id between rnd.id -10and rnd.id +10INNERJOIN datatable AS dt on dt.id = rndo.data_id
ORDERBY abs(rndo.id - rnd.id)
LIMIT 1;
Si la table est grande, vous pouvez tamiser la première partie:
INSERTINTO tmp_randorder (data_id)select id from datatable where rand()<0.01;
Si vous avez plusieurs demandes de lecture
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;
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
orderby 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).
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 +1AS 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 +1AS 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 ORDERBY RAND() LIMIT 10;
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*FROMtableWHERE
id %
FLOOR((SELECT count(1)FROMtable)/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*FROMtableWHERE
id %
FLOOR((SELECT count(1)FROMtable)/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.
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
Si vous voulez un enregistrement aléatoire (peu importe s'il y a des lacunes entre les identifiants):
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)
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()<(SELECT10/ 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.
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(*)fromtablewhere 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
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.
Réponses:
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:
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
la source
mysqli_fetch_assoc($result)
? Ou ces 10 résultats ne se distinguent-ils pas nécessairement?Pas la solution efficace mais fonctionne
la source
ORDER BY RAND()
est relativement lentSELECT words, transcription, translation, sound FROM vocabulary WHERE menu_id=$menuId ORDER BY RAND() LIMIT 10
prend 0,0010, sans LIMIT 10 il a fallu 0,0012 (dans ce tableau 3500 mots).Requête simple qui a d' excellentes performances et fonctionne avec des lacunes :
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:
SELECT * FROM tbl AS t1 JOIN (SELECT id FROM tbl ORDER BY RAND () LIMIT 10) as t2 ON t1.id = t2.id
Version pondérée : https://stackoverflow.com/a/41577458/893432
la source
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
la source
ORDER BY RAND()
FLUSH STATUS; SELECT ...; SHOW SESSION STATUS LIKE 'Handler%';
pour le voir.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.Sa requête très simple et sur une seule ligne.
la source
order by rand()
est très lent si la table est grandeDu 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
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.
la source
SELECT count(*)
devient lent.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.
la source
PRIMARY KEY
).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:
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!
la source
Id
et toutes vos requêtes aléatoires vous le rendrontId
.FLOOR(RAND()*MAX(id))
est biaisé pour renvoyer des identifiants plus importants.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:
Remplacez ensuite cette valeur par:
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)
puis
la source
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.
la source
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).
Permettez-moi de déballer ce qui se passe.
@max := (SELECT MAX(id) FROM table)
MAX(id)
chaque fois que vous avez besoin d'une ligneSELECT FLOOR(rand() * @max) + 1 as rand)
SELECT id FROM table INNER JOIN (...) on id > rand LIMIT 1
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:
la source
LIMIT 1
deLIMIT 30
partout dans la requêteLIMIT 1
vousLIMIT 30
donnerait 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.Riedsio
répondre. J'ai essayé avec 500 accès par seconde à la page en utilisant PHP 7.0.22 et MariaDB sur centos 7, avec laRiedsio
réponse j'ai obtenu plus de 500 réponses supplémentaires puis votre réponse.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):
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):
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.
la source
@no_gaps_id
aucun index ne peut être utilisé, donc si vous recherchezEXPLAIN
votre requête, vous avezUsing filesort
etUsing where
(sans index) pour les sous-requêtes, contrairement à la requête d'origine.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:
Les résultats sont:
36.8418693542479
ms0.241041183472
ms0.216960906982
msSur 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:
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)
la source
LIMIT
légèrement - vous pouvez obtenir des doublons.C'est super rapide et 100% aléatoire même si vous avez des lacunes.
x
de lignes dont vous disposezSELECT COUNT(*) as rows FROM TABLE
a_1,a_2,...,a_10
entre 0 etx
SELECT * FROM TABLE LIMIT 1 offset a_i
pour i = 1, ..., 10J'ai trouvé ce hack dans le livre SQL Antipatterns de Bill Karwin .
la source
SELECT column FROM table ORDER BY RAND() LIMIT 10
est en O (nlog (n)). Alors oui, c'est la solution à jeun et elle fonctionne pour toute distribution d'ID.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.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):
Et puis prenez une version de @redsios Answer:
Si la table est grande, vous pouvez tamiser la première partie:
Si vous avez plusieurs demandes de lecture
Version: vous pouvez conserver la table
tmp_randorder
persistante, 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 troussé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;
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
).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).
la source
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.
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 ...
la source
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:
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:
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.
la source
Si vous voulez un enregistrement aléatoire (peu importe s'il y a des lacunes entre les identifiants):
Source: https://www.warpconduit.net/2011/03/23/selecting-a-random-record-using-mysql-benchmark-results/#comment-1266
la source
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_number
et définissez sa valeur par défaut surRAND()
. 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)
la source
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.
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.la source
Vous pouvez facilement utiliser un décalage aléatoire avec une limite
Vous pouvez également appliquer une clause where comme so
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 à
offset
nouveau après l'avoir déclaré, comme sila source
J'utilise cette requête:
temps de requête: 0,016 s
la source
Voici comment je le fais:
Je l'aime car il ne nécessite pas d'autres tables, il est simple à écrire et très rapide à exécuter.
la source
Utilisez la requête simple ci-dessous pour obtenir des données aléatoires à partir d'une table.
la source
Je suppose que c'est la meilleure façon possible ..
la source