Получение случайной строки через SQLAlchemy
-
09-06-2019 - |
Вопрос
Как выбрать (или некоторые) случайные строки из таблицы с помощью SQLAlchemy?
Решение
Это во многом проблема, специфичная для базы данных.
Я знаю, что PostgreSQL, SQLite, MySQL и Oracle имеют возможность упорядочивания по случайной функции, поэтому вы можете использовать это в 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
Далее вам необходимо ограничить запрос количеством необходимых записей (например, используя .limit()
).
Имейте в виду, что, по крайней мере в PostgreSQL, выбор случайной записи приводит к серьезным проблемам с производительностью; здесь хорошая статья об этом.
Другие советы
Если вы используете orm, а таблица небольшая (или у вас есть кэшированное количество строк), и вы хотите, чтобы она была независимой от базы данных, то это действительно простой подход.
import random
rand = random.randrange(0, session.query(Table).count())
row = session.query(Table)[rand]
Это немного обман, но именно поэтому вы используете orm.
Существует простой способ получить случайную строку, независимую от базы данных.Просто используйте .offset() .Не нужно тянуть все строки:
import random
query = DBSession.query(Table)
rowCount = int(query.count())
randomRow = query.offset(int(rowCount*random.random())).first()
Где Table — ваша таблица (или вы можете поместить туда любой запрос).Если вам нужно несколько строк, вы можете просто запустить это несколько раз и убедиться, что каждая строка не идентична предыдущей.
Вот четыре различных варианта, упорядоченных от самого медленного к самому быстрому. timeit
результаты внизу:
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
результаты 10 000 запусков на моем Macbook таблицы PostgreSQL с 300 строками:
simple_random():
90.09954111799925
load_only_random():
65.94714171699889
order_by_random():
23.17819356000109
optimized_random():
19.87806927999918
Вы можете легко увидеть это, используя func.random()
гораздо быстрее, чем возвращать все результаты в Python random.choice()
.
Кроме того, по мере увеличения размера таблицы производительность order_by_random()
значительно ухудшится, поскольку ORDER BY
требует полного сканирования таблицы по сравнению с COUNT
в optimized_random()
можно использовать индекс.
Некоторые СУБД SQL, а именно Microsoft SQL Server, DB2 и PostgreSQL внедрили SQL:2003 TABLESAMPLE
пункт.В SQLAlchemy добавлена поддержка. в версии 1.1.Это позволяет вернуть выборку таблицы, используя разные методы выборки – стандарт требует SYSTEM
и BERNOULLI
, которые возвращают желаемый приблизительный процент таблицы.
В SQLАлхимии FromClause.tablesample()
и tablesample()
используются для производства TableSample
конструкция:
# Approx. 1%, using SYSTEM method
sample1 = mytable.tablesample(1)
# Approx. 1%, using BERNOULLI method
sample2 = mytable.tablesample(func.bernoulli(1))
При использовании сопоставленных классов возникает небольшая ошибка:произведенный TableSample
объект должен иметь псевдоним, чтобы его можно было использовать для запроса объектов модели:
sample = aliased(MyModel, tablesample(MyModel, 1))
res = session.query(sample).all()
Поскольку многие ответы содержат тесты производительности, я также включу сюда несколько простых тестов.Используя простую таблицу в PostgreSQL, содержащую около миллиона строк и один целочисленный столбец, выберите (приблизительно) 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)
Прежде чем торопиться использовать SYSTEM
метод выборки, следует знать, что он осуществляет выборку страницы, а не отдельные кортежи, поэтому может не подходить, например, для небольших таблиц.
Это решение, которое я использую:
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
Это моя функция для выбора случайных строк таблицы:
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()
это решение выберет одну случайную строку
Это решение требует, чтобы первичный ключ имел имя id, так и должно быть, если его еще нет:
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
Есть несколько способов использования SQL, в зависимости от того, какая база данных используется.
(Я думаю, что SQLAlchemy в любом случае может использовать все это)
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
Оракул:
SELECT column FROM
(SELECT column FROM table
ORDER BY dbms_random.value)
WHERE rownum = 1
Однако я не знаю ни одного стандартного способа