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 はテーブルです (または、そこに任意のクエリを置くこともできます)。いくつかの行が必要な場合は、これを複数回実行し、各行が前の行と同一でないことを確認します。
以下に 4 つの異なるバリエーションを、最も遅いものから最も速いものへと並べています。 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
300 行の PostgreSQL テーブルに対して Macbook で 10,000 回実行した結果:
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 DBMS、つまり 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()
回答の多くにはパフォーマンスのベンチマークが含まれているため、ここではいくつかの簡単なテストも含めます。約 100 万行と 1 つの整数列を含む 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
ただし、標準的な方法はわかりません