Je veux une sélection aléatoire de lignes dans PostgreSQL, j'ai essayé ceci:
select * from table where random() < 0.01;
Mais certains autres recommandent ceci:
select * from table order by random() limit 1000;
J'ai une très grande table avec 500 millions de lignes, je veux que ce soit rapide.
Quelle approche est la meilleure? Quelles sont les différences? Quelle est la meilleure façon de sélectionner des lignes aléatoires?
sql
performance
postgresql
random
nanounanue
la source
la source
Réponses:
Compte tenu de vos spécifications (plus d'informations supplémentaires dans les commentaires),
La requête ci-dessous n'a pas besoin d'une analyse séquentielle de la grande table, seulement une analyse d'index.
Tout d'abord, obtenez des estimations pour la requête principale:
La seule partie éventuellement chère est la
count(*)
(pour les tables énormes). Compte tenu des spécifications ci-dessus, vous n'en avez pas besoin. Une estimation fera très bien l'affaire, disponible presque gratuitement ( explication détaillée ici ):Tant qu'elle
ct
n'est pas beaucoup plus petite queid_span
, la requête surpassera les autres approches.Générez des nombres aléatoires dans l'
id
espace. Vous avez "quelques lacunes", alors ajoutez 10% (assez pour couvrir facilement les blancs) au nombre de lignes à récupérer.Chacun
id
peut être sélectionné plusieurs fois par hasard (bien que très peu probable avec un grand espace d'identification), alors regroupez les numéros générés (ou utilisezDISTINCT
).Rejoignez le
id
s à la grande table. Cela devrait être très rapide avec l'index en place.Enfin, coupez les surplus
id
qui n'ont pas été mangés par les dupes et les lacunes. Chaque ligne a une chance tout à fait égale d'être sélectionnée.Version courte
Vous pouvez simplifier cette requête. Le CTE dans la requête ci-dessus est uniquement à des fins éducatives:
Affiner avec rCTE
Surtout si vous n'êtes pas sûr des écarts et des estimations.
Nous pouvons travailler avec un surplus plus petit dans la requête de base. S'il y a trop de lacunes et que nous ne trouvons pas suffisamment de lignes dans la première itération, le rCTE continue à itérer avec le terme récursif. Nous avons encore besoin de relativement peu lacunes dans l'espace ID ou la récursivité peut se tarir avant que la limite soit atteinte - ou nous devons commencer avec un tampon suffisamment grand qui défie le but d'optimiser les performances.
Les doublons sont éliminés par le
UNION
dans le rCTE.L'extérieur
LIMIT
fait arrêter le CTE dès que nous avons suffisamment de lignes.Cette requête est soigneusement rédigée pour utiliser l'index disponible, générer des lignes réellement aléatoires et ne pas s'arrêter jusqu'à ce que nous atteignions la limite (à moins que la récursivité ne sèche). Il y a un certain nombre d'écueils ici si vous voulez le réécrire.
Envelopper dans la fonction
Pour une utilisation répétée avec des paramètres variables:
Appel:
Vous pouvez même faire en sorte que ce générique fonctionne pour n'importe quelle table: prenez le nom de la colonne PK et de la table comme type polymorphe et utilisez
EXECUTE
... Mais cela dépasse le cadre de cette question. Voir:Alternative possible
SI vos besoins permettent des ensembles identiques pour les appels répétés (et nous parlons d'appels répétés), je considérerais une vue matérialisée . Exécutez la requête ci-dessus une fois et écrivez le résultat dans une table. Les utilisateurs obtiennent une sélection quasi aléatoire à la vitesse de l'éclair. Rafraîchissez votre choix aléatoire à intervalles ou événements de votre choix.
Postgres 9.5 présente
TABLESAMPLE SYSTEM (n)
Où
n
est un pourcentage. Le manuel:Accentuation sur moi. C'est très rapide , mais le résultat n'est pas exactement aléatoire . Le manuel à nouveau:
Le nombre de lignes renvoyées peut varier énormément. Pour notre exemple, pour obtenir environ 1000 lignes:
En relation:
Ou installez le module supplémentaire tsm_system_rows pour obtenir exactement le nombre de lignes demandées (s'il y en a assez) et autorisez la syntaxe la plus pratique:
Voir la réponse d' Evan pour plus de détails.
Mais ce n'est toujours pas exactement aléatoire.
la source
JOIN bigtbl t
qui est l'abréviation deJOIN bigtbl AS t
.t
est un alias de table pourbigtbl
. Son but est de raccourcir la syntaxe mais cela ne serait pas nécessaire dans ce cas particulier. J'ai simplifié la requête dans ma réponse et ajouté une version simple.Vous pouvez examiner et comparer le plan d'exécution des deux en utilisant
Un test rapide sur un grand tableau 1 montre que le
ORDER BY
premier trie le tableau complet puis sélectionne les 1000 premiers éléments. Le tri d'une grande table lit non seulement cette table mais implique également la lecture et l'écriture de fichiers temporaires. Lawhere random() < 0.1
seule analyse le tableau complet une fois.Pour les grandes tables, cela peut ne pas être ce que vous voulez, car même une analyse complète de la table peut prendre trop de temps.
Une troisième proposition serait
Celui-ci arrête l'analyse de la table dès que 1000 lignes ont été trouvées et revient donc plus tôt. Bien sûr, cela réduit un peu le caractère aléatoire, mais c'est peut-être suffisant dans votre cas.
Edit: Outre ces considérations, vous pouvez consulter les questions déjà posées à ce sujet. L'utilisation de la requête
[postgresql] random
renvoie plusieurs hits.Et un article lié de depez décrivant plusieurs autres approches:
1 "grand" comme dans "le tableau complet ne rentrera pas dans la mémoire".
la source
random() < 0.02
, puis mélanger cette liste, alorslimit 1000
! Le tri sera moins cher sur quelques milliers de lignes (lol).ordre postgresql par random (), sélectionnez les lignes dans un ordre aléatoire:
ordre postgresql par random () avec un distinct:
ordre postgresql par limite aléatoire d'une ligne:
la source
select your_columns from your_table ORDER BY random() limit 1
prendre ~ 2 minutes pour exécuter sur 45milÀ partir de PostgreSQL 9.5, il existe une nouvelle syntaxe dédiée à l'obtention d'éléments aléatoires à partir d'une table:
Cet exemple vous donnera 5% d'éléments de
mytable
.Voir plus d'explications sur ce billet de blog: http://www.postgresql.org/docs/current/static/sql-select.html
la source
TABLESAMPLE SYSTEM_ROWS(400)
pour obtenir un échantillon de 400 lignes aléatoires. Vous devez activer l' extension intégréetsm_system_rows
pour utiliser cette instruction.Celui avec le ORDER BY va être le plus lent.
select * from table where random() < 0.01;
va enregistrement par enregistrement, et décide de le filtrer au hasard ou non. Cela va êtreO(N)
dû au fait qu'il n'a besoin de vérifier chaque enregistrement qu'une seule fois.select * from table order by random() limit 1000;
va trier la table entière, puis choisir les 1000 premiers. En dehors de toute magie vaudou dans les coulisses, l'ordre estO(N * log N)
.L'inconvénient de celui-
random() < 0.01
ci est que vous obtiendrez un nombre variable d'enregistrements de sortie.Remarque, il existe une meilleure façon de mélanger un ensemble de données que de trier par hasard: le Fisher-Yates Shuffle , qui s'exécute
O(N)
. Cependant, implémenter le shuffle dans SQL semble être tout un défi.la source
Voici une décision qui fonctionne pour moi. Je suppose que c'est très simple à comprendre et à exécuter.
la source
ORDER BY random()
ce qui fonctionne mais peut ne pas être efficace lorsque vous travaillez avec une grande table.Si vous savez combien de lignes vous souhaitez, consultez
tsm_system_rows
.tsm_system_rows
Installez d'abord l'extension
Ensuite, votre requête,
la source
SYSTEM
méthode intégrée .tsm_system_rows
ettsm_system_time
. Pour autant que je puisse voir, ils sont pratiquement inutiles pour quoi que ce soit, mais une sélection absolument minimale de lignes aléatoires. Je vous serais reconnaissant de bien vouloir jeter un rapide coup d'œil et de commenter la validité ou non de mon analyse.Si vous ne voulez qu'une seule ligne, vous pouvez utiliser un
offset
dérivé calculé à partir decount
.la source
Une variante de la vue matérialisée «Alternative possible» décrite par Erwin Brandstetter est possible.
Supposons, par exemple, que vous ne souhaitiez pas de doublons dans les valeurs aléatoires renvoyées. Vous devrez donc définir une valeur booléenne sur la table primaire contenant votre ensemble de valeurs (non aléatoire).
En supposant que c'est la table d'entrée:
Remplissez le
ID_VALUES
tableau selon vos besoins. Ensuite, comme décrit par Erwin, créez une vue matérialisée qui randomise laID_VALUES
table une fois:Notez que la vue matérialisée ne contient pas la colonne utilisée, car celle-ci deviendra rapidement obsolète. La vue ne doit pas non plus contenir d'autres colonnes qui peuvent se trouver
id_values
tableau.Afin d'obtenir (et « consommer ») des valeurs aléatoires, un UPDATE utiliser REVIENT sur
id_values
, la sélectionid_values
deid_values_randomized
avec une jointure, et appliquer les critères souhaités pour obtenir que des possibilités pertinentes. Par exemple:Modifiez
LIMIT
si nécessaire - si vous n'avez besoin que d'une seule valeur aléatoire à la fois, passezLIMIT
à1
.Avec les index appropriés
id_values
, je pense que la MISE À JOUR-RETOUR devrait s'exécuter très rapidement avec peu de charge. Il renvoie des valeurs aléatoires avec un aller-retour de base de données. Les critères pour les lignes "éligibles" peuvent être aussi complexes que requis. De nouvelles lignes peuvent être ajoutées à laid_values
table à tout moment et elles deviendront accessibles à l'application dès que la vue matérialisée sera actualisée (ce qui peut probablement être exécuté à un moment hors pointe). La création et l'actualisation de la vue matérialisée seront lentes, mais elles ne doivent être exécutées que lorsque de nouveaux identifiants sont ajoutés à laid_values
table.la source
Une leçon de mon expérience:
offset floor(random() * N) limit 1
n'est pas plus rapide queorder by random() limit 1
.Je pensais que l'
offset
approche serait plus rapide car elle devrait faire gagner du temps au tri dans Postgres. Il s'avère que ce n'était pas le cas.la source
Ajoutez une colonne appelée
r
avec typeserial
. Indexr
.Supposons que nous ayons 200 000 lignes, nous allons générer un nombre aléatoire
n
, où 0 <n
<<= 200 000.Sélectionnez les lignes avec
r > n
, triez-lesASC
et sélectionnez la plus petite.Code:
Le code est explicite. La sous-requête au milieu est utilisée pour estimer rapidement le nombre de lignes de table à partir de https://stackoverflow.com/a/7945274/1271094 .
Au niveau de l'application, vous devez exécuter à nouveau l'instruction si
n
> le nombre de lignes ou vous devez sélectionner plusieurs lignes.la source
Je sais que je suis un peu en retard à la fête, mais je viens de trouver cet outil génial appelé pg_sample :
J'ai essayé cela avec une base de données de 350 millions de lignes et c'était vraiment rapide, je ne sais pas sur le caractère aléatoire .
la source