Comment étendre cette solution à une jointure? Lors de l'utilisation, 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.
Helmut Grohne
Est-il possible de semer le nombre aléatoire. Par exemple, le livre du jour avec unix epoc pour aujourd'hui à midi, il montre donc le même livre toute la journée même si la requête est exécutée plusieurs fois. Oui, je sais que la mise en cache est plus efficace pour ce cas d'utilisation, juste un exemple.
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)+1au lieu de max(rowid)+1donne 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:
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:
createtable random_foo(foo_id);
Puis, périodiquement, remplissez à nouveau le tableau random_foo
deletefrom random_foo;insertinto 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.
Comme je viens de le voir sur une liste de diffusion, au lieu d'avoir la méthode de secours (méthode 2), vous pouvez simplement utiliser rowid> = [random] au lieu de =, mais c'est en fait extrêmement lent par rapport à la méthode 2.
Suzanne Dupéron
3
C'est une excellente réponse; cependant, il a un problème. SELECT max(rowid) + 1sera 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
...
19
Vous devez mettre "ordre par RANDOM ()" sur votre requête.
Exemple:
select*from quest orderby RANDOM();
Voyons un exemple complet
Créez un tableau:
CREATETABLE quest (
id INTEGER PRIMARYKEY AUTOINCREMENT,
quest TEXT NOTNULL,
resp_id INTEGER NOTNULL);
Bien que les réponses basées uniquement sur le code ne soient pas interdites, veuillez comprendre qu'il s'agit d'une communauté de questions-réponses, plutôt que d'une communauté de type crowdsourcing, et que, généralement, si l'OP comprenait le code affiché comme une réponse, il serait venu avec une solution similaire par lui-même, et n'aurait pas publié de question en premier lieu. En tant que tel, veuillez fournir un contexte à votre réponse et / ou à votre code en expliquant comment et / ou pourquoi cela fonctionne.
XenoRo
2
Je préfère cette solution, car elle me permet de rechercher n lignes. Dans mon cas, j'avais besoin de 100 échantillons aléatoires de la base de données - ORDER BY RANDOM () combiné avec LIMIT 100 fait exactement cela.
mnr
17
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.
C'est une bonne méthode rapide. Cela ne se généralise pas très bien à la sélection de plus d'une ligne, mais l'OP n'en a demandé qu'une, donc je suppose que c'est bien.
Ken Williams
Une chose curieuse à noter est que le temps nécessaire pour trouver le OFFSETsemble 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.
Ken Williams
@KenWilliams Presque toutes les bases de données ont le même problème avec `` OFFSET ''. C'est une manière très inefficace d'interroger une base de données car elle a besoin de lire autant de lignes même si elle ne retournera que 1.
Jonathan Allen
1
Notez que je parlais cependant de / taille fixe / enregistrements - il devrait être facile de scanner directement le bon octet dans les données ( sans lire autant de lignes), mais ils devraient implémenter l'optimisation explicitement.
Ken Williams
@KenWilliams: il n'y a pas d'enregistrements de taille fixe dans SQLite, il est typé dynamiquement et les données ne doivent pas nécessairement correspondre aux affinités déclarées ( sqlite.org/fileformat2.html#section_2_1 ). Tout est stocké dans des pages b-tree, donc dans tous les cas, il doit faire au moins une recherche b-tree vers la feuille. Pour ce faire efficacement, il aurait besoin de stocker la taille du sous-arbre avec chaque pointeur enfant. Ce serait trop de frais généraux pour peu d'avantages, car vous ne pourrez toujours pas optimiser l'OFFSET pour les jointures, l'ordre par, etc. (et sans ORDER BY, la commande n'est pas définie.)
Cette solution fonctionne également pour les indices avec des lacunes, car nous randomisons un offset dans une plage [0, count). MAXest 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.000user0.000140 sys 0.000117
sqlite>select payment_id from payment limit 1 offset abs(random())%(select count(*)from payment);14746
Run Time: real 0.002user0.000899 sys 0.000132
sqlite>select payment_id from payment limit 1 offset abs(random())%(select count(*)from payment);12486
Run Time: real 0.001user0.000952 sys 0.000103
sqlite>select payment_id from payment orderby random() limit 1;3134
Run Time: real 0.015user0.014022 sys 0.000309
sqlite>select payment_id from payment orderby random() limit 1;9407
Run Time: real 0.018user0.013757 sys 0.000208
Bien essayé mais je ne pense pas que cela fonctionnera. Que faire si une ligne avec rowId = 5 a été supprimée, mais que les rowIds 1,2,3,4,6,7,8,9,10 existent toujours? Ensuite, si le rowId aléatoire choisi est 5, cette requête ne retournera rien.
Réponses:
Jetez un œil à Sélection d'une ligne aléatoire à partir d'une table SQLite
la source
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 demax(rowid)+1
donne de meilleures performances, comme expliqué dans les commentaires):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:
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:
Puis, périodiquement, remplissez à nouveau le tableau random_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.
la source
SELECT max(rowid) + 1
sera une requête lente - elle nécessite une analyse complète de la table. sqlite optimise uniquement la requêteSELECT 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.comVous devez mettre "ordre par RANDOM ()" sur votre requête.
Exemple:
Voyons un exemple complet
Insertion de quelques valeurs:
Une sélection par défaut:
Un choix aléatoire:
* Chaque fois que vous sélectionnez, l'ordre sera différent.Si vous souhaitez renvoyer une seule ligne
* Chaque fois que vous sélectionnez, le retour sera différent.la source
Qu'en est-il de:
puis choisissez un nombre aléatoire m dans [0, n) et
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.
la source
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.la source
Voici une modification de la solution de @ ank:
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:
la source
J'ai proposé la solution suivante pour les grandes bases de données sqlite3 :
Enfin, vous ajoutez +1 pour éviter que rowid soit égal à 0.
la source