J'ai une sqlite
table avec le schéma suivant:
CREATE TABLE foo (bar VARCHAR)
J'utilise cette table comme stockage pour une liste de chaînes.
Comment sélectionner une ligne aléatoire dans ce tableau?
J'ai une sqlite
table avec le schéma suivant:
CREATE TABLE foo (bar VARCHAR)
J'utilise cette table comme stockage pour une liste de chaînes.
Comment sélectionner une ligne aléatoire dans ce tableau?
Réponses:
Jetez un œil à Sélection d'une ligne aléatoire à partir d'une table SQLite
SELECT * FROM table ORDER BY RANDOM() LIMIT 1;
SELECT a.foo FROM a JOIN b ON a.id = b.id WHERE b.bar = 2 ORDER BY RANDOM() LIMIT 1;
j'obtiens toujours la même ligne.
Les solutions suivantes sont beaucoup plus rapides que celles d'anktastic (le count (*) coûte cher, mais si vous pouvez le mettre en cache, alors la différence ne devrait pas être si grande), qui lui-même est beaucoup plus rapide que le "order by random ()" lorsque vous avez un grand nombre de lignes, même si elles présentent quelques inconvénients.
Si vos rowids sont plutôt compacts (c'est-à-dire quelques suppressions), vous pouvez alors faire ce qui suit (utiliser (select max(rowid) from foo)+1
au lieu de max(rowid)+1
donne de meilleures performances, comme expliqué dans les commentaires):
select * from foo where rowid = (abs(random()) % (select (select max(rowid) from foo)+1));
Si vous avez des trous, vous essaierez parfois de sélectionner un rowid inexistant, et la sélection renverra un jeu de résultats vide. Si cela n'est pas acceptable, vous pouvez fournir une valeur par défaut comme celle-ci:
select * from foo where rowid = (abs(random()) % (select (select max(rowid) from foo)+1)) or rowid = (select max(rowid) from node) order by rowid limit 1;
Cette deuxième solution n'est pas parfaite: la distribution de probabilité est plus élevée sur la dernière ligne (celle avec le rowid le plus élevé), mais si vous ajoutez souvent des éléments à la table, elle deviendra une cible mobile et la distribution des probabilités devrait être beaucoup mieux.
Encore une autre solution, si vous sélectionnez souvent des éléments aléatoires dans une table avec beaucoup de trous, vous voudrez peut-être créer une table qui contient les lignes de la table d'origine triées dans un ordre aléatoire:
create table random_foo(foo_id);
Puis, périodiquement, remplissez à nouveau le tableau random_foo
delete from random_foo;
insert into random_foo select id from foo;
Et pour sélectionner une ligne aléatoire, vous pouvez utiliser ma première méthode (il n'y a pas de trous ici). Bien sûr, cette dernière méthode a quelques problèmes de concurrence, mais la reconstruction de random_foo est une opération de maintenance qui ne se produira probablement pas très souvent.
Pourtant, encore une autre façon, que j'ai récemment trouvée sur une liste de diffusion , consiste à mettre un déclencheur sur la suppression pour déplacer la ligne avec le plus grand rowid dans la ligne supprimée actuelle, de sorte qu'il ne reste aucun trou.
Enfin, notez que le comportement d'auto-incrémentation de la clé primaire rowid et d'un entier n'est pas identique (avec rowid, quand une nouvelle ligne est insérée, max (rowid) +1 est choisi, alors qu'il est plus élevé-valeur-jamais-vu + 1 pour une clé primaire), donc la dernière solution ne fonctionnera pas avec un auto-incrémentation dans random_foo, mais les autres méthodes le feront.
SELECT max(rowid) + 1
sera une requête lente - elle nécessite une analyse complète de la table. sqlite optimise uniquement la requête SELECT max(rowid)
. Ainsi, cette réponse serait améliorée par: select * from foo where rowid = (abs(random()) % (select (select max(rowid) from foo)+1));
Voir ceci pour plus d'informations: sqlite.1065341.n5.nabble.com
Vous devez mettre "ordre par RANDOM ()" sur votre requête.
Exemple:
select * from quest order by RANDOM();
Voyons un exemple complet
CREATE TABLE quest (
id INTEGER PRIMARY KEY AUTOINCREMENT,
quest TEXT NOT NULL,
resp_id INTEGER NOT NULL
);
Insertion de quelques valeurs:
insert into quest(quest, resp_id) values ('1024/4',6), ('256/2',12), ('128/1',24);
Une sélection par défaut:
select * from quest;
| id | quest | resp_id |
1 1024/4 6
2 256/2 12
3 128/1 24
--
Un choix aléatoire:
select * from quest order by RANDOM();
| id | quest | resp_id |
3 128/1 24
1 1024/4 6
2 256/2 12
--
* Chaque fois que vous sélectionnez, l'ordre sera différent.
Si vous souhaitez renvoyer une seule ligne
select * from quest order by RANDOM() LIMIT 1;
| id | quest | resp_id |
2 256/2 12
--
* Chaque fois que vous sélectionnez, le retour sera différent.
Qu'en est-il de:
SELECT COUNT(*) AS n FROM foo;
puis choisissez un nombre aléatoire m dans [0, n) et
SELECT * FROM foo LIMIT 1 OFFSET m;
Vous pouvez même enregistrer le premier nombre ( n ) quelque part et ne le mettre à jour que lorsque le nombre de bases de données change. De cette façon, vous n'avez pas à faire le SELECT COUNT à chaque fois.
OFFSET
semble augmenter en fonction de la taille du décalage - la ligne 2 est rapide, la ligne 2 millions prend un certain temps, même lorsque toutes les données dans le sont de taille fixe et il devrait pouvoir y chercher directement. Du moins, c'est à quoi cela ressemble dans SQLite 3.7.13.
SELECT bar
FROM foo
ORDER BY Random()
LIMIT 1
Voici une modification de la solution de @ ank:
SELECT *
FROM table
LIMIT 1
OFFSET ABS(RANDOM()) % MAX((SELECT COUNT(*) FROM table), 1)
Cette solution fonctionne également pour les indices avec des lacunes, car nous randomisons un offset dans une plage [0, count). MAX
est utilisé pour gérer un cas avec une table vide.
Voici des résultats de test simples sur une table avec 16k lignes:
sqlite> .timer on
sqlite> select count(*) from payment;
16049
Run Time: real 0.000 user 0.000140 sys 0.000117
sqlite> select payment_id from payment limit 1 offset abs(random()) % (select count(*) from payment);
14746
Run Time: real 0.002 user 0.000899 sys 0.000132
sqlite> select payment_id from payment limit 1 offset abs(random()) % (select count(*) from payment);
12486
Run Time: real 0.001 user 0.000952 sys 0.000103
sqlite> select payment_id from payment order by random() limit 1;
3134
Run Time: real 0.015 user 0.014022 sys 0.000309
sqlite> select payment_id from payment order by random() limit 1;
9407
Run Time: real 0.018 user 0.013757 sys 0.000208
J'ai proposé la solution suivante pour les grandes bases de données sqlite3 :
SELECT * FROM foo WHERE rowid = abs(random()) % (SELECT max(rowid) FROM foo) + 1;
La fonction abs (X) renvoie la valeur absolue de l'argument numérique X.
La fonction random () renvoie un entier pseudo-aléatoire entre -9223372036854775808 et +9223372036854775807.
L'opérateur% sort la valeur entière de son opérande gauche modulo son opérande droit.
Enfin, vous ajoutez +1 pour éviter que rowid soit égal à 0.