Jak wybrać jeden lub więcej losowych wierszy z tabeli za pomocą SQLAlchemy?
Odpowiedzi:
Jest to w dużej mierze kwestia specyficzna dla bazy danych.
Wiem, że PostgreSQL, SQLite, MySQL i Oracle mają możliwość zamawiania według losowej funkcji, więc możesz użyć tego w 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
Następnie musisz ograniczyć zapytanie o liczbę potrzebnych rekordów (na przykład używając .limit()
).
Pamiętaj, że przynajmniej w PostgreSQL wybranie losowego rekordu wiąże się z poważnymi problemami z wydajnością; tutaj jest dobry artykuł na ten temat.
session.query(MyModel).order_by(func.rand()).first
session.query(MyModel).order_by(func.rand()).first()
func.random()
jest funkcją ogólną, która kompiluje się do losowej implementacji bazy danych.
Jeśli używasz orm i tabela nie jest duża (lub masz buforowaną ilość wierszy) i chcesz, aby była niezależna od bazy danych, to naprawdę proste podejście jest takie.
import random
rand = random.randrange(0, session.query(Table).count())
row = session.query(Table)[rand]
To trochę oszustwo, ale dlatego używasz orm.
random.choice(session.query(Table))
?
Istnieje prosty sposób na pobranie losowego wiersza niezależnego od bazy danych. Po prostu użyj .offset (). Nie ma potrzeby ciągnięcia wszystkich rzędów:
import random
query = DBSession.query(Table)
rowCount = int(query.count())
randomRow = query.offset(int(rowCount*random.random())).first()
Gdzie Table to twoja tabela (lub możesz umieścić tam dowolne zapytanie). Jeśli potrzebujesz kilku wierszy, możesz po prostu uruchomić to wiele razy i upewnić się, że każdy wiersz nie jest identyczny z poprzednim.
query.offset(random.randrange(rowCount)).limit(1).first()
.
.limit(1)
wcześniej .first()
? Wydaje się zbędne. Być może query.offset(random.randrange(row_count)).first()
wystarczy.
Oto cztery różne warianty, uporządkowane od najwolniejszej do najszybszej. timeit
wyniki na dole:
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
wyniki dla 10000 uruchomień na moim Macbooku dla tabeli PostgreSQL z 300 wierszami:
simple_random():
90.09954111799925
load_only_random():
65.94714171699889
order_by_random():
23.17819356000109
optimized_random():
19.87806927999918
Możesz łatwo zauważyć, że używanie func.random()
jest znacznie szybsze niż zwracanie wszystkich wyników do Pythona random.choice()
.
Ponadto wraz ze wzrostem rozmiaru tabeli wydajność order_by_random()
znacznie się obniży, ponieważ program ORDER BY
wymaga pełnego skanowania tabeli, a program COUNT
in optimized_random()
może używać indeksu.
random.sample()
zrobić? Co jest tutaj zoptymalizowane?
flask-sqlalchemy
?
Niektóre SQL DBMS, a mianowicie Microsoft SQL Server, DB2 i PostgreSQL , zaimplementowały TABLESAMPLE
klauzulę SQL: 2003 . Wsparcie zostało dodane do SQLAlchemy w wersji 1.1 . Umożliwia zwrócenie próbki tabeli przy użyciu różnych metod próbkowania - norma wymaga SYSTEM
i BERNOULLI
, które zwracają żądany przybliżony procent tabeli.
W SQLAlchemy FromClause.tablesample()
i tablesample()
są używane do tworzenia TableSample
konstrukcji:
# Approx. 1%, using SYSTEM method
sample1 = mytable.tablesample(1)
# Approx. 1%, using BERNOULLI method
sample2 = mytable.tablesample(func.bernoulli(1))
W przypadku używania z mapowanymi klasami występuje drobna pułapka: utworzony TableSample
obiekt musi być aliasowany, aby można go było użyć do zapytań o obiekty modelu:
sample = aliased(MyModel, tablesample(MyModel, 1))
res = session.query(sample).all()
Ponieważ wiele odpowiedzi zawiera testy porównawcze wydajności, zamieszczę tutaj również kilka prostych testów. Używając prostej tabeli w PostgreSQL z około milionem wierszy i jedną kolumną z liczbą całkowitą, wybierz (w przybliżeniu) 1% próbki:
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)
Przed pospiesznym użyciem SYSTEM
metody próbkowania należy wiedzieć, że próbkuje ona strony , a nie pojedyncze krotki, więc może nie nadawać się na przykład do małych tabel i może nie dawać losowych wyników, jeśli tabela jest skupiona.
Oto rozwiązanie, którego używam:
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
Oto moja funkcja do wybierania losowych wierszy tabeli:
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()
Skorzystaj z tej najprostszej metody z tego przykładu, aby wybrać losowe pytanie z bazy danych: -
#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())
To rozwiązanie wymaga, aby klucz podstawowy miał nazwę id, tak powinno być, jeśli jeszcze nie jest:
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
Istnieje kilka sposobów korzystania z SQL, w zależności od używanej bazy danych.
(Myślę, że SQLAlchemy i tak może użyć tych wszystkich)
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
Wyrocznia:
SELECT column FROM
(SELECT column FROM table
ORDER BY dbms_random.value)
WHERE rownum = 1
Jednak nie znam żadnego standardowego sposobu
select.order_by(func.random()).limit(n)