Obtener una fila aleatoria a través de SQLAlchemy

Respuestas:

122

Este es un problema muy específico de la base de datos.

Sé que PostgreSQL, SQLite, MySQL y Oracle tienen la capacidad de ordenar por una función aleatoria, por lo que puede usar esto en 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

A continuación, debe limitar la consulta por la cantidad de registros que necesita (por ejemplo, usar .limit()).

Tenga en cuenta que al menos en PostgreSQL, la selección de registros aleatorios tiene graves problemas de rendimiento; aquí hay un buen artículo al respecto.

Łukasz
fuente
11
+1. Igual que Postgres funciona para SQLite: select.order_by(func.random()).limit(n)
mechanical_meat
Puede utilizar order_by ('dbms_random.value') en Oracle.
Buttons840
11
Si está utilizando modelos declarativos:session.query(MyModel).order_by(func.rand()).first
trinth
2
Gracias @trinth, funcionó cuando agregué paranthesis al final:session.query(MyModel).order_by(func.rand()).first()
Kent Munthe Caspersen
3
Desde SQLAlchemy v0.4, func.random()es una función genérica que se compila a la implementación aleatoria de la base de datos.
RazerM
25

Si está utilizando el orm y la tabla no es grande (o tiene su cantidad de filas almacenadas en caché) y desea que sea independiente de la base de datos, el enfoque realmente simple es.

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

Esto es un poco de trampa, pero por eso usas un orm.

David Raznick
fuente
rand = random.randrange (0, session.query (Table) .count ())
James Brady
Usted elige y crea todos los objetos antes de elegir uno de
Serge K.
¿Qué tal random.choice(session.query(Table))?
Solomon Ucko
23

Hay una forma sencilla de extraer una fila aleatoria que ES independiente de la base de datos. Simplemente use .offset (). No es necesario tirar de todas las filas:

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

Donde Table es su tabla (o puede poner cualquier consulta allí). Si desea algunas filas, puede ejecutar esto varias veces y asegurarse de que cada fila no sea idéntica a la anterior.

GuySoft
fuente
Actualización: en alrededor de 10 millones de filas en mysql, esto en realidad comenzó a ponerse un poco lento, supongo que podría optimizarlo.
GuySoft
1
Funciona bien para mí en una configuración de ~ 500k filas.
Mario
1
Ahora con 11 millones de filas en Oracle ... ya no es tan bueno :-) Degradación lineal, pero aún así ... tengo que encontrar algo más.
Mario
2
@Jayme: te vendría bien query.offset(random.randrange(rowCount)).limit(1).first().
jfs
1
@Jayme también, ¿hay alguna razón para usar .limit(1)antes .first()? Parece redundante. Quizás query.offset(random.randrange(row_count)).first()sea ​​suficiente.
jfs
17

Aquí hay cuatro variaciones diferentes, ordenadas de la más lenta a la más rápida. timeitresultados en la 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 ejecuciones en mi Macbook contra una tabla de PostgreSQL con 300 filas:

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

Puede ver fácilmente que usar func.random()es mucho más rápido que devolver todos los resultados a Python random.choice().

Además, como el tamaño de la tabla aumenta, el rendimiento de order_by_random()degradará significativamente debido a una ORDER BYrequiere un escaneo completo de tabla frente a la COUNTen optimized_random()puede utilizar un índice.

Jeff Widman
fuente
¿Qué pasa con la recogida de muestras? Como que random.sample()hacer ¿Qué es la forma optimizada aquí?
hamidfzm
Abra una nueva pregunta y enlace a ella y intentaré responderla. Si es posible, especifique el sabor subyacente de SQL, ya que también influye en la respuesta.
Jeff Widman
¿No es esto usando flask-sqlalchemy?
MattSom
3

Algunos DBMS SQL, a saber, Microsoft SQL Server, DB2 y PostgreSQL han implementado la TABLESAMPLEcláusula SQL: 2003 . Se agregó soporte a SQLAlchemy en la versión 1.1 . Permite devolver una muestra de una tabla utilizando diferentes métodos de muestreo: el estándar requiere SYSTEMy BERNOULLI, que devuelven un porcentaje aproximado deseado de una tabla.

En SQLAlchemy FromClause.tablesample()y tablesample()se utilizan para producir una TableSampleconstrucción:

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

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

Hay un pequeño problema cuando se usa con clases mapeadas: el TableSampleobjeto producido debe tener un alias para poder usarlo para consultar objetos de modelo:

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

Dado que muchas de las respuestas contienen evaluaciones comparativas de rendimiento, también incluiré aquí algunas pruebas sencillas. Usando una tabla simple en PostgreSQL con aproximadamente un millón de filas y una sola columna entera, seleccione (aprox.) 1% de muestra:

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 apresurarse a usar SYSTEMel método de muestreo, uno debe saber que toma muestras de páginas , no de tuplas individuales, por lo que podría no ser adecuado para tablas pequeñas, por ejemplo, y puede que no produzca resultados aleatorios si la tabla está agrupada.

Ilja Everilä
fuente
0

Esta es la solución que 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
Patas de pollo
fuente
1
Esto sería increíblemente lento en mesas grandes. Estarías agarrando cada fila y luego cortándola.
Mateo
1
Vaya, sí, esto no es genial. Si hay una consulta para obtener el recuento de registros de la tabla, ese sería un mejor enfoque. Esto se hizo en una aplicación web con una base de datos pequeña, ya no funciona con esa empresa, por lo que no puedo hacer mucho al respecto.
ChickenFeet
0

Esta es mi función para seleccionar filas aleatorias de una tabla:

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()
Charles Wang
fuente
-1

Utilice este método más simple en este ejemplo para elegir una pregunta aleatoria de la base de datos: -

#first import the random module
import random

#then choose what ever Model you want inside random.choise() method
get_questions = random.choice(Question.query.all())
Anas
fuente
1. ¿Qué pasa si hay un millón de registros en la base de datos? 2. ¿Deberíamos obtenerlos todos y seleccionar uno al azar? ¿No será una llamada cara?
Sourav Badami
1
Absolutamente será una llamada costosa, pero preguntó solo por el método aleatorio, sin preguntar "cómo hacer una consulta aleatoria con un rango específico de datos o por una clave específica", así que si respondí y considerando lo que mencionaste, será ser un tema totalmente diferente. Traté de responder lo más simple que pude para que sea claro y solo para una consulta exacta. la gente responde con toneladas de líneas, aunque puede ser más simple.
Anas
-2

esta solución seleccionará una sola fila aleatoria

Esta solución requiere que la clave principal se llame id, debería serlo si aún no lo está:

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
med116
fuente
4
Esto falla cuando tienes un hueco en tu identificación.
erickrf
-6

Hay un par de formas a través de SQL, según la base de datos que se utilice.

(Creo que SQLAlchemy puede usar todos estos de todos modos)

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

Oráculo:

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

Sin embargo, no conozco ninguna forma estándar

Lancer de fuego
fuente
7
Si. Sé cómo hacerlo en SQL ( publiqué esa respuesta en beta.stackoverflow.com/questions/19412/… ) pero estaba buscando una solución específica de SQLAlchemy.
CNU