Pobieranie losowego wiersza przez SQLAlchemy

Odpowiedzi:

124

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.

Łukasz
źródło
12
+1. Tak samo jak Postgres działa dla SQLite: select.order_by(func.random()).limit(n)
Mechanical_meat
Możesz użyć order_by ('dbms_random.value') w Oracle.
Przyciski840
11
Jeśli używasz modeli deklaratywnych:session.query(MyModel).order_by(func.rand()).first
trinth
2
Dzięki @trinth, zadziałało, kiedy dodałem parantezę na końcu:session.query(MyModel).order_by(func.rand()).first()
Kent Munthe Caspersen,
3
Od wersji SQLAlchemy v0.4 func.random()jest funkcją ogólną, która kompiluje się do losowej implementacji bazy danych.
RazerM
25

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.

David Raznick
źródło
rand = random.randrange (0, session.query (Table) .count ())
James Brady
Wybierasz i tworzysz wszystkie obiekty, zanim wybierzesz jeden z
Serge K.
A co powiesz random.choice(session.query(Table))?
Solomon Ucko
23

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.

GuySoft
źródło
Aktualizacja - przy około 10 milionach wierszy w mysql zaczęło się to trochę spowalniać. Myślę, że można to zoptymalizować.
GuySoft
1
U mnie działa dobrze w ustawieniu ~ 500 tys. Wierszy.
Mario,
1
Teraz przy 11 milionach wierszy na Oracle… już nie tak dobrze :-) Degradacja liniowa, ale mimo to… muszę znaleźć coś innego.
Mario,
2
@Jayme: możesz użyć query.offset(random.randrange(rowCount)).limit(1).first().
jfs
1
@Jayme też, czy istnieje powód, aby używać .limit(1)wcześniej .first()? Wydaje się zbędne. Być może query.offset(random.randrange(row_count)).first()wystarczy.
jfs
17

Oto cztery różne warianty, uporządkowane od najwolniejszej do najszybszej. timeitwyniki 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 BYwymaga pełnego skanowania tabeli, a program COUNTin optimized_random()może używać indeksu.

Jeff Widman
źródło
A co z pobieraniem próbek? Jak co random.sample()zrobić? Co jest tutaj zoptymalizowane?
hamidfzm
Otwórz nowe pytanie i link do niego, a ja spróbuję odpowiedzieć. Jeśli to możliwe, określ podstawową odmianę języka SQL, ponieważ ma to również wpływ na odpowiedź.
Jeff Widman
Czy to nie używa flask-sqlalchemy?
MattSom
3

Niektóre SQL DBMS, a mianowicie Microsoft SQL Server, DB2 i PostgreSQL , zaimplementowały TABLESAMPLEklauzulę 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 SYSTEMi BERNOULLI, które zwracają żądany przybliżony procent tabeli.

W SQLAlchemy FromClause.tablesample()i tablesample()są używane do tworzenia TableSamplekonstrukcji:

# 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 TableSampleobiekt 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 SYSTEMmetody 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.

Ilja Everilä
źródło
0

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
ChickenFeet
źródło
1
Byłoby to niezwykle powolne przy dużych stołach. Chwyciłbyś każdy pojedynczy rząd, a następnie go pokroił.
Matthew
1
Wow tak, to nie jest świetne. Jeśli istnieje zapytanie, aby uzyskać liczbę rekordów tabeli, byłoby to lepsze podejście. Zrobiono to w aplikacji internetowej z małą bazą danych, która nie współpracuje już z tą firmą, więc niewiele mogę z tym zrobić.
ChickenFeet
0

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()
Charles Wang
źródło
-1

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())
Anas
źródło
1. A jeśli baza danych zawiera milion rekordów? 2. Czy powinniśmy zdobyć je wszystkie i wybrać losowo? Czy to nie będzie kosztowne połączenie?
Sourav Badami
1
Absolutnie będzie to kosztowne połączenie, ale zapytał tylko o metodę losową, nie pytając „jak wykonać losowe zapytanie z określonym zakresem danych lub określonym kluczem”, więc jeśli odpowiem i rozważę to, o czym wspomniałeś, to być zupełnie innym tematem. Starałem się odpowiedzieć tak prosto, jak tylko mogłem, aby było jasne i tylko do dokładnego zapytania. ludzie odpowiadają tonami wierszy, chociaż może to być prostsze.
Anas
-2

to rozwiązanie wybierze jeden losowy wiersz

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
med116
źródło
4
To się nie udaje, gdy masz lukę w identyfikatorze.
erickrf
-6

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

Ognisty Lancer
źródło
7
Tak. Wiem, jak to zrobić w SQL (opublikowałem tę odpowiedź na beta.stackoverflow.com/questions/19412/… ), ale szukałem rozwiązania specyficznego dla SQLAlchemy.
cnu