Question

Comment sélectionner une (ou des) ligne(s) aléatoire(s) dans une table à l'aide de SQLAlchemy ?

Était-ce utile?

La solution

Il s'agit en grande partie d'un problème spécifique à la base de données.

Je sais que PostgreSQL, SQLite, MySQL et Oracle ont la possibilité de trier par une fonction aléatoire, vous pouvez donc l'utiliser dans SQLAlchemy :

from  sqlalchemy.sql.expression import func, select

select.order_by(func.random()) # for PostgreSQL, SQLite

select.order_by(func.rand()) # for MySQL

select.order_by('dbms_random.value') # For Oracle

Ensuite, vous devez limiter la requête au nombre d'enregistrements dont vous avez besoin (par exemple en utilisant .limit()).

Gardez à l'esprit qu'au moins dans PostgreSQL, la sélection d'un enregistrement aléatoire pose de graves problèmes de performances ; ici c'est un bon article à ce sujet.

Autres conseils

Si vous utilisez l'orm et que la table n'est pas grande (ou si son nombre de lignes est mis en cache) et que vous souhaitez qu'elle soit indépendante de la base de données, l'approche très simple est la suivante.

import random
rand = random.randrange(0, session.query(Table).count()) 
row = session.query(Table)[rand]

C'est un peu de la triche mais c'est pourquoi vous utilisez un orm.

Il existe un moyen simple d’extraire une ligne aléatoire indépendante de la base de données.Utilisez simplement .offset() .Pas besoin d'extraire toutes les lignes :

import random
query = DBSession.query(Table)
rowCount = int(query.count())
randomRow = query.offset(int(rowCount*random.random())).first()

Où Table est votre table (ou vous pouvez y poser n'importe quelle requête).Si vous voulez quelques lignes, vous pouvez simplement l'exécuter plusieurs fois et vous assurer que chaque ligne n'est pas identique à la précédente.

Voici quatre variantes différentes, classées de la plus lente à la plus rapide. timeit résultats en bas :

from sqlalchemy.sql import func
from sqlalchemy.orm import load_only

def simple_random():
    return random.choice(model_name.query.all())

def load_only_random():
    return random.choice(model_name.query.options(load_only('id')).all())

def order_by_random():
    return model_name.query.order_by(func.random()).first()

def optimized_random():
    return model_name.query.options(load_only('id')).offset(
            func.floor(
                func.random() *
                db.session.query(func.count(model_name.id))
            )
        ).limit(1).all()

timeit résultats pour 10 000 exécutions sur mon Macbook par rapport à une table PostgreSQL de 300 lignes :

simple_random(): 
    90.09954111799925
load_only_random():
    65.94714171699889
order_by_random():
    23.17819356000109
optimized_random():
    19.87806927999918

Vous pouvez facilement le constater en utilisant func.random() est beaucoup plus rapide que de renvoyer tous les résultats à Python random.choice().

De plus, à mesure que la taille de la table augmente, les performances de order_by_random() se dégradera considérablement parce qu'un ORDER BY nécessite une analyse complète de la table par rapport au COUNT dans optimized_random() peut utiliser un index.

Certains SGBD SQL, à savoir Microsoft SQL Server, DB2 et PostgreSQL ont implémenté le SQL:2003 TABLESAMPLE clause.Le support a été ajouté à SQLAlchemy dans la version 1.1.Il permet de renvoyer un échantillon d’un tableau en utilisant différentes méthodes d’échantillonnage – la norme exige SYSTEM et BERNOULLI, qui renvoient un pourcentage approximatif souhaité d'une table.

Dans SQLAlchimie FromClause.tablesample() et tablesample() sont utilisés pour produire un TableSample construction:

# Approx. 1%, using SYSTEM method
sample1 = mytable.tablesample(1)

# Approx. 1%, using BERNOULLI method
sample2 = mytable.tablesample(func.bernoulli(1))

Il y a un léger piège lorsqu'il est utilisé avec des classes mappées :le produit TableSample L'objet doit avoir un alias pour pouvoir être utilisé pour interroger les objets du modèle :

sample = aliased(MyModel, tablesample(MyModel, 1))
res = session.query(sample).all()

Étant donné que de nombreuses réponses contiennent des références de performances, j'inclurai également ici quelques tests simples.À l'aide d'un simple tableau dans PostgreSQL avec environ un million de lignes et une seule colonne entière, sélectionnez (environ) un échantillon de 1 % :

In [24]: %%timeit
    ...: foo.select().\
    ...:     order_by(func.random()).\
    ...:     limit(select([func.round(func.count() * 0.01)]).
    ...:           select_from(foo).
    ...:           as_scalar()).\
    ...:     execute().\
    ...:     fetchall()
    ...: 
307 ms ± 5.72 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

In [25]: %timeit foo.tablesample(1).select().execute().fetchall()
6.36 ms ± 188 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

In [26]: %timeit foo.tablesample(func.bernoulli(1)).select().execute().fetchall()
19.8 ms ± 381 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

Avant de vous précipiter pour utiliser SYSTEM méthode d'échantillonnage il faut savoir qu'elle échantillonne pages, et non des tuples individuels, il pourrait donc ne pas convenir aux petites tables, par exemple.

Voici la solution que j'utilise :

from random import randint

rows_query = session.query(Table)                # get all rows
if rows_query.count() > 0:                       # make sure there's at least 1 row
    rand_index = randint(0,rows_query.count()-1) # get random index to rows 
    rand_row   = rows_query.all()[rand_index]    # use random index to get random row

Voici ma fonction pour sélectionner des lignes aléatoires d'un tableau :

from sqlalchemy.sql.expression import func

def random_find_rows(sample_num):
    if not sample_num:
        return []

    session = DBSession()
    return session.query(Table).order_by(func.random()).limit(sample_num).all()

cette solution sélectionnera une seule ligne aléatoire

Cette solution nécessite que la clé primaire soit nommée id, elle devrait l'être si ce n'est pas déjà fait :

import random
max_model_id = YourModel.query.order_by(YourModel.id.desc())[0].id
random_id = random.randrange(0,max_model_id)
random_row = YourModel.query.get(random_id)
print random_row

Il existe plusieurs méthodes via SQL, en fonction de la base de données utilisée.

(Je pense que SQLAlchemy peut utiliser tout cela de toute façon)

mysql :

SELECT colum FROM table
ORDER BY RAND()
LIMIT 1

PostgreSQL :

SELECT column FROM table
ORDER BY RANDOM()
LIMIT 1

MSSQL :

SELECT TOP 1 column FROM table
ORDER BY NEWID()

IBM DB2 :

SELECT column, RAND() as IDX
FROM table
ORDER BY IDX FETCH FIRST 1 ROWS ONLY

Oracle:

SELECT column FROM
(SELECT column FROM table
ORDER BY dbms_random.value)
WHERE rownum = 1

Cependant, je ne connais aucune méthode standard

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top