Comment prendre un échantillon aléatoire simple efficace en SQL ? La base de données en question fonctionne sous MySQL ; ma table compte au moins 200 000 lignes, et je veux un échantillon aléatoire simple d'environ 10 000.
La réponse "évidente" est de :
SELECT * FROM table ORDER BY RAND() LIMIT 10000
Pour les grandes tables, c'est trop lent : il faut appeler RAND()
pour chaque ligne (ce qui le place déjà à O(n)), et les trie, ce qui le rend O(n lg n) au mieux. Existe-t-il un moyen de faire cela plus rapidement que O(n) ?
Note : Comme Andrew Mao le souligne dans les commentaires, si vous utilisez cette approche sur SQL Server, vous devez utiliser la fonction T-SQL NEWID()
parce que RAND() peut retourner la même valeur pour tous les rangs .
EDIT : 5 ANS PLUS TARD
J'ai rencontré ce problème à nouveau avec une table plus grande, et j'ai fini par utiliser une version de la solution de @ignorant, avec deux modifications :
- Échantillonner les rangs de 2 à 5 fois la taille de l'échantillon souhaité, afin de réduire les coûts.
ORDER BY RAND()
- Sauvegarder le résultat de
RAND()
à une colonne indexée à chaque insertion/mise à jour. (Si votre ensemble de données n'est pas très chargé en mises à jour, vous devrez peut-être trouver un autre moyen de garder cette colonne fraîche).
Pour prendre un échantillon de 1000 articles d'une table, je compte les lignes et échantillonne le résultat jusqu'à, en moyenne, 10 000 lignes avec la colonne frozen_rand :
SELECT COUNT(*) FROM table; -- Use this to determine rand_low and rand_high
SELECT *
FROM table
WHERE frozen_rand BETWEEN %(rand_low)s AND %(rand_high)s
ORDER BY RAND() LIMIT 1000
(Mon implémentation réelle implique plus de travail pour s'assurer que je ne sous-échantillonne pas, et pour envelopper manuellement rand_high, mais l'idée de base est "réduire aléatoirement votre N à quelques milliers").
Bien que cela implique quelques sacrifices, cela me permet d'échantillonner la base de données à l'aide d'un balayage d'index, jusqu'à ce qu'elle soit suffisamment petite pour que je puisse l'utiliser. ORDER BY RAND()
encore.
4 votes
Cela ne fonctionne même pas dans le serveur SQL parce que
RAND()
renvoie la même valeur à chaque appel suivant.1 votes
Bon point -- Je vais ajouter une note indiquant que les utilisateurs de SQL Server devraient utiliser ORDER BY NEWID() à la place.
0 votes
Elle reste terriblement inefficace car elle doit trier toutes les données. Une technique d'échantillonnage aléatoire pour un certain pourcentage est préférable, mais même après avoir lu un tas de messages ici, je n'ai pas trouvé de solution acceptable qui soit suffisamment aléatoire.
0 votes
Si vous lisez la question, je demande spécifiquement parce que ORDER BY RAND() est O(n lg n).
0 votes
La réponse de muposat ci-dessous est excellente si vous n'êtes pas trop obsédé par le caractère statistiquement aléatoire de RAND().