Zufällige Zeilen durch SQLAlchemy erhalten

81

Wie wähle ich mit SQLAlchemy eine oder mehrere zufällige Zeilen aus einer Tabelle aus?

cnu
quelle

Antworten:

122

Dies ist ein sehr datenbankspezifisches Problem.

Ich weiß, dass PostgreSQL, SQLite, MySQL und Oracle die Möglichkeit haben, nach einer Zufallsfunktion zu ordnen, sodass Sie dies in SQLAlchemy verwenden können:

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

Als Nächstes müssen Sie die Abfrage durch die Anzahl der benötigten Datensätze begrenzen (z. B. mithilfe von .limit()).

Beachten Sie, dass zumindest in PostgreSQL die Auswahl von Zufallsdatensätzen schwerwiegende Leistungsprobleme aufweist. Hier ist ein guter Artikel darüber.

Łukasz
quelle
11
+1. Gleich wie Postgres funktioniert für SQLite: select.order_by(func.random()).limit(n)
Mechanical_Meat
Sie können order_by ('dbms_random.value') in Oracle verwenden.
Buttons840
11
Wenn Sie deklarative Modelle verwenden:session.query(MyModel).order_by(func.rand()).first
am
2
Danke @trinth, es hat funktioniert, als ich am Ende eine Paranthesis hinzugefügt habe:session.query(MyModel).order_by(func.rand()).first()
Kent Munthe Caspersen
3
Seit SQLAlchemy v0.4 func.random()handelt es sich um eine generische Funktion, die zur zufälligen Implementierung der Datenbank kompiliert wird.
RazerM
25

Wenn Sie den Orm verwenden und die Tabelle nicht groß ist (oder wenn die Anzahl der Zeilen zwischengespeichert ist) und Sie möchten, dass sie datenbankunabhängig ist, ist der wirklich einfache Ansatz.

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

Dies betrügt leicht, aber deshalb verwenden Sie einen Orm.

David Raznick
quelle
rand = random.randrange (0, session.query (Tabelle) .count ())
James Brady
Sie wählen und erstellen alle Objekte, bevor Sie eines von
Serge K.
Wie wäre es random.choice(session.query(Table))?
Solomon Ucko
23

Es gibt eine einfache Möglichkeit, eine zufällige Zeile abzurufen, die datenbankunabhängig ist. Verwenden Sie einfach .offset (). Sie müssen nicht alle Zeilen ziehen:

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

Wobei Tabelle Ihre Tabelle ist (oder Sie könnten dort eine Abfrage stellen). Wenn Sie einige Zeilen möchten, können Sie diese einfach mehrmals ausführen und sicherstellen, dass nicht jede Zeile mit der vorherigen identisch ist.

GuySoft
quelle
Update - bei ungefähr 10 Millionen Zeilen in MySQL wurde dies tatsächlich etwas langsam, ich denke, Sie könnten es optimieren.
GuySoft
1
Funktioniert gut für mich in einer Einstellung mit ~ 500.000 Zeilen.
Mario
1
Jetzt bis zu 11 Millionen Zeilen unter Oracle ... nicht mehr so ​​gut :-) Lineare Verschlechterung, aber trotzdem ... Ich muss etwas anderes finden.
Mario
2
@ Jayme: Sie könnten verwenden query.offset(random.randrange(rowCount)).limit(1).first().
JFS
1
@ Jayme auch, gibt es einen Grund, .limit(1)vorher zu verwenden .first()? Es scheint überflüssig. Vielleicht query.offset(random.randrange(row_count)).first()ist genug.
JFS
17

Hier sind vier verschiedene Varianten, geordnet von der langsamsten zur schnellsten. timeitErgebnisse unten:

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 Ergebnisse für 10.000 Läufe auf meinem Macbook gegen eine PostgreSQL-Tabelle mit 300 Zeilen:

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

Sie können leicht erkennen, dass die Verwendung func.random()weitaus schneller ist als die Rückgabe aller Ergebnisse an Python random.choice().

Zusätzlich ist , wie die Größe der Tabelle erhöht, die Leistung order_by_random()erheblich beeinträchtigt , da ein ORDER BYeinen vollständigen Tabellenscan gegenüber dem erfordert COUNTin optimized_random()kann einen Index verwenden.

Jeff Widman
quelle
Was ist mit der Probenahme? Wie was random.sample()tun? Was ist hier optimiert?
Hamidfzm
Öffnen Sie eine neue Frage und verlinken Sie sie, und ich werde versuchen, sie zu beantworten. Geben Sie nach Möglichkeit die zugrunde liegende Variante von SQL an, da dies auch die Antwort beeinflusst.
Jeff Widman
Verwendet das nicht flask-sqlalchemy?
MattSom
3

Einige SQL-DBMS, nämlich Microsoft SQL Server, DB2 und PostgreSQL, haben die SQL: 2003- TABLESAMPLEKlausel implementiert . SQLAlchemy wurde in Version 1.1 unterstützt . Es ermöglicht die Rückgabe einer Stichprobe einer Tabelle mit verschiedenen Stichprobenmethoden - der Standard verlangt SYSTEMund BERNOULLI, die einen gewünschten ungefähren Prozentsatz einer Tabelle zurückgeben.

In SQLAlchemy FromClause.tablesample()und tablesample()werden verwendet, um ein TableSampleKonstrukt herzustellen :

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

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

Bei Verwendung mit zugeordneten Klassen gibt TableSamplees ein kleines Problem : Das erzeugte Objekt muss mit einem Alias ​​versehen sein, um Modellobjekte abfragen zu können:

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

Da viele der Antworten Leistungsbenchmarks enthalten, werde ich auch hier einige einfache Tests einfügen. Wählen Sie anhand einer einfachen Tabelle in PostgreSQL mit etwa einer Million Zeilen und einer einzelnen Ganzzahlspalte (ca.) 1% Stichprobe aus:

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)

Bevor Sie sich beeilen, die SYSTEMStichprobenmethode zu verwenden, sollten Sie wissen, dass Seiten und nicht einzelne Tupel abgetastet werden. Daher ist sie möglicherweise nicht für kleine Tabellen geeignet und führt möglicherweise nicht zu zufälligen Ergebnissen, wenn die Tabelle gruppiert ist.

Ilja Everilä
quelle
0

Dies ist die Lösung, die ich benutze:

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
Hühnerfüße
quelle
1
Dies wäre auf großen Tischen unglaublich langsam. Sie würden jede einzelne Reihe greifen und sie dann in Scheiben schneiden.
Matthew
1
Wow ja, das ist nicht großartig. Wenn eine Abfrage zum Abrufen der Anzahl der Tabellendatensätze vorliegt, ist dies ein besserer Ansatz. Dies wurde in einer Web-App mit einer kleinen Datenbank durchgeführt, die nicht mehr mit dieser Firma zusammenarbeitet, daher kann ich nicht viel dagegen tun.
ChickenFeet
0

Dies ist meine Funktion, um zufällige Zeilen einer Tabelle auszuwählen:

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
quelle
-1

Verwenden Sie diese einfachste Methode in diesem Beispiel, um eine zufällige Frage aus der Datenbank auszuwählen: -

#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
quelle
1. Was ist, wenn die Datenbank eine Million Datensätze enthält? 2. Sollten wir alle bekommen und eine zufällige auswählen? Wird es nicht ein teurer Anruf sein?
Sourav Badami
1
Absolut wird ein teurer Anruf sein, aber er fragte nur nach der Zufallsmethode und nicht nach der Frage, wie eine zufällige Abfrage mit einem bestimmten Datenbereich oder mit einem bestimmten Schlüssel durchgeführt werden soll. Wenn ich also antwortete und überlegte, was Sie erwähnt haben, wird dies der Fall sein ganz anderes Thema sein. Ich habe versucht, so einfach wie möglich zu antworten, damit es klar ist und nur für genaue Anfragen. Leute antworten mit Tonnen von Zeilen, während es einfacher sein kann.
Anas
-2

Diese Lösung wählt eine einzelne zufällige Zeile aus

Diese Lösung erfordert, dass der Primärschlüssel den Namen id trägt. Wenn dies nicht bereits geschehen ist, sollte dies der Fall sein:

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
quelle
4
Dies schlägt fehl, wenn Sie eine Lücke in Ihrer ID haben.
Erickrf
-6

Je nachdem, welche Datenbank verwendet wird, gibt es verschiedene Möglichkeiten für SQL.

(Ich denke, SQLAlchemy kann all dies sowieso nutzen)

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

Orakel:

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

Ich kenne jedoch keinen Standardweg

Fire Lancer
quelle
6
Ja. Ich weiß, wie es in SQL geht (ich habe diese Antwort in beta.stackoverflow.com/questions/19412/… gepostet ), habe aber nach einer SQLAlchemy-spezifischen Lösung gesucht.
cnu