SQLAlchemy を使用してテーブルから 1 つまたは複数のランダムな行を選択するにはどうすればよいですか?
10 に答える
これは、データベース固有の問題です。
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]
これは少しごまかしていますが、それがオームを使用する理由です。
データベースに依存しないランダムな行をプルする簡単な方法があります。.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
私の Macbook で 300 行の PostgreSQL テーブルに対して 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:2003TABLESAMPLE
句を実装しています。バージョン 1.1 でSQLAlchemy にサポートが追加されました。これにより、さまざまなサンプリング方法を使用してテーブルのサンプルを返すことができます。標準では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
をサンプリングすることを知っておく必要があります。そのため、たとえば小さなテーブルには適していない可能性があり、テーブルがクラスター化されている場合はランダムな結果として生成されない可能性があります。
サンプルのパーセンテージ/行数とシードをパラメーターとして渡すことを許可しないダイアレクトと、値をインライン化しないドライバーを使用する場合、値が static の場合はリテラル SQL テキストとして値を渡すか、カスタムを使用してインライン化します。 SQLA コンパイラ拡張機能:
from sqlalchemy.ext.compiler import compiles
from sqlalchemy.sql import TableSample
@compiles(TableSample)
def visit_tablesample(tablesample, self, asfrom=False, **kw):
""" Compile `TableSample` with values inlined.
"""
kw_literal_binds = {**kw, "literal_binds": True}
text = "%s TABLESAMPLE %s" % (
self.visit_alias(tablesample, asfrom=True, **kw),
tablesample._get_method()._compiler_dispatch(self, **kw_literal_binds),
)
if tablesample.seed is not None:
text += " REPEATABLE (%s)" % (
tablesample.seed._compiler_dispatch(self, **kw_literal_binds)
)
return text
from sqlalchemy import table, literal, text
# Static percentage
print(table("tbl").tablesample(text("5 PERCENT")))
# Compiler inlined values
print(table("tbl").tablesample(5, seed=literal(42)))
このソリューションは、単一のランダムな行を選択します
このソリューションでは、プライマリ キーの名前が 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
しかし、私は標準的な方法を知りません