Pergunta

Como seleciono uma (ou algumas) linhas aleatórias de uma tabela usando SQLAlchemy?

Foi útil?

Solução

Este é um problema específico do banco de dados.

Eu sei que PostgreSQL, SQLite, MySQL e Oracle têm a capacidade de ordenar por uma função aleatória, então você pode usar isso no 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

Em seguida, você precisa limitar a consulta pelo número de registros necessários (por exemplo, usando .limit()).

Tenha em mente que, pelo menos no PostgreSQL, a seleção de registros aleatórios apresenta graves problemas de desempenho; aqui é um bom artigo sobre isso.

Outras dicas

Se você estiver usando o orm e a tabela não for grande (ou você tiver a quantidade de linhas armazenadas em cache) e quiser que ela seja independente do banco de dados, a abordagem realmente simples é.

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

Isso é um pouco trapaceiro, mas é por isso que você usa um formulário.

Existe uma maneira simples de extrair uma linha aleatória que seja independente do banco de dados.Basta usar .offset() .Não há necessidade de puxar todas as linhas:

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

Onde Table é a sua tabela (ou você pode colocar qualquer consulta lá).Se você quiser algumas linhas, basta executar isso várias vezes e certificar-se de que cada linha não seja idêntica à anterior.

Aqui estão quatro variações diferentes, ordenadas da mais lenta para a mais rápida. timeit resultados na parte inferior:

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 resultados para 10.000 execuções no meu Macbook em uma tabela PostgreSQL com 300 linhas:

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

Você pode ver isso facilmente usando func.random() é muito mais rápido do que retornar todos os resultados para o Python random.choice().

Além disso, à medida que o tamanho da tabela aumenta, o desempenho do order_by_random() degradará significativamente porque um ORDER BY requer uma varredura completa da tabela versus o COUNT em optimized_random() pode usar um índice.

Alguns DBMS SQL, nomeadamente Microsoft SQL Server, DB2 e PostgreSQL implementaram o SQL:2003 TABLESAMPLE cláusula.Suporte foi adicionado ao SQLAlchemy na versão 1.1.Permite retornar uma amostra de uma tabela usando diferentes métodos de amostragem – a norma exige SYSTEM e BERNOULLI, que retorna uma porcentagem aproximada desejada de uma tabela.

Em SQLAlchemy FromClause.tablesample() e tablesample() são usados ​​para produzir TableSample construir:

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

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

Há um pequeno problema quando usado com classes mapeadas:o produzido TableSample object deve ter um alias para ser usado para consultar objetos de modelo:

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

Como muitas das respostas contêm benchmarks de desempenho, incluirei aqui também alguns testes simples.Usando uma tabela simples no PostgreSQL com cerca de um milhão de linhas e uma única coluna inteira, selecione (aprox.) amostra 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)

Antes de correr para usar SYSTEM método de amostragem, deve-se saber que ele amostra Páginas, e não tuplas individuais, portanto pode não ser adequado para tabelas pequenas, por exemplo.

Esta é a solução que eu uso:

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

Esta é minha função para selecionar linha(s) aleatória(s) de uma tabela:

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()

esta solução selecionará uma única linha aleatória

Esta solução requer que a chave primária seja chamada id, deveria ser, se ainda não for:

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

Existem algumas maneiras de usar o SQL, dependendo de qual banco de dados está sendo usado.

(Acho que SQLAlchemy pode usar tudo isso de qualquer maneira)

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()

IBMDB2:

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

Oráculo:

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

No entanto, não conheço nenhuma maneira padrão

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top