J'ai une table dans postgres qui contient quelques millions de lignes. J'ai vérifié sur Internet et j'ai trouvé ce qui suit
SELECT myid FROM mytable ORDER BY RANDOM() LIMIT 1;
cela fonctionne, mais c'est vraiment lent ... existe-t-il un autre moyen de créer cette requête ou un moyen direct de sélectionner une ligne aléatoire sans lire tout le tableau? À propos, "myid" est un entier mais peut être un champ vide.
merci
Vous voudrez peut-être expérimenter avec OFFSET
, comme dans
SELECT myid FROM mytable OFFSET floor(random()*N) LIMIT 1;
Le N
est le nombre de lignes dans mytable
. Vous devrez peut-être d'abord faire une SELECT COUNT(*)
pour déterminer la valeur de N
.
Mise à jour (par Antony Hatchkins)
Vous devez utiliser floor
ici:
SELECT myid FROM mytable OFFSET floor(random()*N) LIMIT 1;
Considérons une table de 2 rangées; random()*N
génère 0 <= x < 2
et, par exemple, SELECT myid FROM mytable OFFSET 1.7 LIMIT 1;
renvoie 0 ligne en raison de l'arrondissement implicite au plus proche int.
PostgreSQL 9.5 a introduit une nouvelle approche pour une sélection d’échantillons beaucoup plus rapide: TABLESAMPLE
La syntaxe est
SELECT * FROM my_table TABLESAMPLE BERNOULLI(percentage);
SELECT * FROM my_table TABLESAMPLE SYSTEM(percentage);
Ce n'est pas la solution optimale si vous souhaitez uniquement sélectionner une ligne, car vous devez connaître le nombre de jours du tableau pour calculer le pourcentage exact.
Pour éviter un compte lent et utiliser TABLESAMPLE rapide pour les tables de 1 ligne à des milliards de lignes, vous pouvez effectuer les opérations suivantes:
SELECT * FROM my_table TABLESAMPLE SYSTEM(0.000001) LIMIT 1;
-- if you got no result:
SELECT * FROM my_table TABLESAMPLE SYSTEM(0.00001) LIMIT 1;
-- if you got no result:
SELECT * FROM my_table TABLESAMPLE SYSTEM(0.0001) LIMIT 1;
-- if you got no result:
SELECT * FROM my_table TABLESAMPLE SYSTEM(0.001) LIMIT 1;
...
Cela n’aura peut-être pas l’air si élégant, mais c’est probablement plus rapide que les autres réponses.
Pour décider si vous souhaitez utiliser BERNULLI ou SYSTEM, lisez la différence à propos de http://blog.2ndquadrant.com/tablesample-in-postgresql-9-5-2/
J'ai essayé cela avec une sous-requête et cela a bien fonctionné. Offset, du moins dans Postgresql v8.4.4, fonctionne correctement.
select * from mytable offset random() * (select count(*) from mytable) limit 1 ;
Vous devez utiliser floor
:
SELECT myid FROM mytable OFFSET floor(random()*N) LIMIT 1;
Vérifiez ce lien pour quelques options différentes. http://www.depesz.com/index.php/2007/09/16/my-ibly-on-getting-random-row/
Mise à jour: (A.Hatchkins)
Le résumé du (très) long article est le suivant.
L'auteur énumère quatre approches:
1) ORDER BY random() LIMIT 1;
- lent
2) ORDER BY id where id>=random()*N LIMIT 1
- non uniforme s'il y a des lacunes
3) colonne aléatoire - doit être mise à jour de temps en temps
4) custom agrégat aléatoire - méthode rusée, pourrait être lente: random () doit être généré N fois
et suggère d'améliorer la méthode n ° 2 en utilisant
5) ORDER BY id where id=random()*N LIMIT 1
avec les requêtes suivantes si le résultat est vide.
J'ai mis au point une solution très rapide sans TABLESAMPLE
. Beaucoup plus rapide que OFFSET random()*N LIMIT 1
. Il ne nécessite même pas le nombre de tables.
L'idée est de créer un index d'expression avec des données aléatoires mais prévisibles, par exemple md5(primary key)
.
Voici un test avec des exemples de données de 1 million de lignes:
create table randtest (id serial primary key, data int not null);
insert into randtest (data) select (random()*1000000)::int from generate_series(1,1000000);
create index randtest_md5_id_idx on randtest (md5(id::text));
explain analyze
select * from randtest where md5(id::text)>md5(random()::text)
order by md5(id::text) limit 1;
Résultat:
Limit (cost=0.42..0.68 rows=1 width=8) (actual time=6.219..6.220 rows=1 loops=1)
-> Index Scan using randtest_md5_id_idx on randtest (cost=0.42..84040.42 rows=333333 width=8) (actual time=6.217..6.217 rows=1 loops=1)
Filter: (md5((id)::text) > md5((random())::text))
Rows Removed by Filter: 1831
Total runtime: 6.245 ms
Cette requête peut parfois (avec une probabilité d'environ 1/Number_of_rows) renvoyer 0 ligne. Elle doit donc être vérifiée et réexécutée. De plus, les probabilités ne sont pas exactement les mêmes - certaines lignes sont plus probables que d'autres.
En comparaison:
explain analyze SELECT id FROM randtest OFFSET random()*1000000 LIMIT 1;
Les résultats varient beaucoup, mais peuvent être très mauvais:
Limit (cost=1442.50..1442.51 rows=1 width=4) (actual time=179.183..179.184 rows=1 loops=1)
-> Seq Scan on randtest (cost=0.00..14425.00 rows=1000000 width=4) (actual time=0.016..134.835 rows=915702 loops=1)
Total runtime: 179.211 ms
(3 rows)
Le moyen le plus simple et le plus rapide d'extraire une ligne aléatoire consiste à utiliser le tsm_system_rows
extension:
CREATE EXTENSION IF NOT EXISTS tsm_system_rows;
Ensuite, vous pouvez sélectionner le nombre exact de lignes que vous souhaitez:
SELECT myid FROM mytable TABLESAMPLE SYSTEM_ROWS(1);
Ceci est disponible avec PostgreSQL 9.5 et versions ultérieures.
Voir: https://www.postgresql.org/docs/current/static/tsm-system-rows.html