Як вибрати один або кілька випадкових рядків із таблиці за допомогою 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 вибір випадкового запису має серйозні проблеми з виконанням; ось хороша стаття про це.
session.query(MyModel).order_by(func.rand()).first
session.query(MyModel).order_by(func.rand()).first()
func.random()
є загальною функцією, яка компілюється до випадкової реалізації бази даних.
Якщо ви використовуєте orm і таблиця не велика (або у вас є кількість кешованих рядків), і ви хочете, щоб вона була незалежною від бази даних, насправді такий простий підхід.
import random
rand = random.randrange(0, session.query(Table).count())
row = session.query(Table)[rand]
Це трохи обман, але саме тому ви використовуєте орм.
random.choice(session.query(Table))
?
Існує простий спосіб витягнути випадковий рядок, незалежний від бази даних. Просто використовуйте .offset (). Не потрібно тягнути всі рядки:
import random
query = DBSession.query(Table)
rowCount = int(query.count())
randomRow = query.offset(int(rowCount*random.random())).first()
Де таблиця - це ваша таблиця (або ви можете туди поставити будь-який запит). Якщо вам потрібно кілька рядків, ви можете просто запустити це кілька разів і переконатися, що кожен рядок не ідентичний попередньому.
query.offset(random.randrange(rowCount)).limit(1).first()
.
.limit(1)
раніше .first()
? Це здається зайвим. Можливо, query.offset(random.randrange(row_count)).first()
досить.
Ось чотири різні варіанти, впорядковані від найповільніших до найшвидших. 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
результати для 10000 запусків на моєму 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()
може використовувати індекс.
random.sample()
робити? Що тут оптимізовано?
flask-sqlalchemy
?
Деякі СУБД SQL, а саме Microsoft SQL Server, DB2 та PostgreSQL , реалізували пропозицію SQL: 2003 TABLESAMPLE
. Підтримка була додана до SQLAlchemy у версії 1.1 . Це дозволяє повернути зразок таблиці, використовуючи різні методи вибірки - стандарт вимагає SYSTEM
і BERNOULLI
, які повертають бажаний приблизний відсоток таблиці.
У SQLAlchemy 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()
Використовуйте цей найпростіший метод у цьому прикладі для вибору випадкового запитання з бази даних: -
#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())
Це рішення вимагає, щоб первинний ключ називався ідентифікатором, він повинен бути, якщо його ще немає:
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
Oracle:
SELECT column FROM
(SELECT column FROM table
ORDER BY dbms_random.value)
WHERE rownum = 1
Однак я не знаю жодного стандартного способу
select.order_by(func.random()).limit(n)