通过SQLAlchemy获取随机行

cnu*_*cnu 72 python sql database sqlalchemy

如何使用SQLAlchemy从表中选择(或某些)随机行?

Łuk*_*asz 103

这是一个特定于数据库的问题.

我知道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
Run Code Online (Sandbox Code Playgroud)

接下来,您需要根据所需的记录数限制查询(例如使用.limit()).

请记住,至少在PostgreSQL中,选择随机记录存在严重的性能问题; 是关于它的好文章.

  • +1.与Postgres一样适用于SQLite:`select.order_by(func.random()).limit(n)` (9认同)
  • 如果您使用声明性模型:`session.query(MyModel).order_by(func.rand()).first` (9认同)
  • 从 SQLAlchemy v0.4 开始,`func.random()` 是一个通用函数,可以编译为数据库的随机实现。 (4认同)
  • 功能模块在哪里?SA有没有文件? (3认同)
  • 谢谢@trinth,当我在最后添加括号时它起作用了:`session.query(MyModel).order_by(func.rand()).first()` (2认同)

Dav*_*ick 24

如果你正在使用orm并且表不是很大(或者你有缓存的行数),并且你希望它与数据库无关,那么真正简单的方法就是.

import random
rand = random.randrange(0, session.query(Table).count()) 
row = session.query(Table)[rand]
Run Code Online (Sandbox Code Playgroud)

这是作弊,但这就是你使用orm的原因.


Guy*_*oft 18

有一种简单的方法来提取IS数据库独立的随机行.只需使用.offset().无需拉出所有行:

import random
query = DBSession.query(Table)
rowCount = int(query.count())
randomRow = query.offset(int(rowCount*random.random())).first()
Run Code Online (Sandbox Code Playgroud)

Table是你的表(或者你可以在那里放任何查询).如果你想要几行,那么你可以多次运行它,并确保每一行与前一行不同.

  • @Jayme:你可以使用`query.offset(random.randrange(rowCount)).limit(1).first()`。 (2认同)

Jef*_*man 13

这里有四种不同的变化,从最慢到最快排序.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()
Run Code Online (Sandbox Code Playgroud)

timeit 我的Macbook上针对具有300行的PostgreSQL表进行10,000次运行的结果:

simple_random(): 
    90.09954111799925
load_only_random():
    65.94714171699889
order_by_random():
    23.17819356000109
optimized_random():
    19.87806927999918
Run Code Online (Sandbox Code Playgroud)

您可以很容易地看到使用func.random()远比将所有结果返回到Python更快random.choice().

此外,随着表的大小增加,性能order_by_random()将显着降低,因为ORDER BY需要全表扫描而不是COUNTin optimized_random()可以使用索引.


Ilj*_*ilä 8

一些 SQL DBMS,即 Microsoft SQL Server、DB2 和PostgreSQL已经实现了 SQL:2003TABLESAMPLE子句。在 1.1 版中添加了对 SQLAlchemy 的支持。SYSTEM它允许使用标准要求的不同采样方法 \xe2\x80\x93和来返回表的样本BERNOULLI,这会返回表的所需近似百分比。

\n

在 SQLAlchemy 中FromClause.tablesample()tablesample()用于生成TableSample构造:

\n
# Approx. 1%, using SYSTEM method\nsample1 = mytable.tablesample(1)\n\n# Approx. 1%, using BERNOULLI method\nsample2 = mytable.tablesample(func.bernoulli(1))\n
Run Code Online (Sandbox Code Playgroud)\n

与映射类一起使用时有一个轻微的问题:生成的TableSample对象必须具有别名才能用于查询模型对象:

\n
sample = aliased(MyModel, tablesample(MyModel, 1))\nres = session.query(sample).all()\n
Run Code Online (Sandbox Code Playgroud)\n
\n

由于许多答案都包含性能基准,因此我还将在此处包含一些简单的测试。使用 PostgreSQL 中的一个简单表,其中包含大约一百万行和一个整数列,选择(大约)1% 的样本:

\n
In [24]: %%timeit\n    ...: foo.select().\\\n    ...:     order_by(func.random()).\\\n    ...:     limit(select([func.round(func.count() * 0.01)]).\n    ...:           select_from(foo).\n    ...:           as_scalar()).\\\n    ...:     execute().\\\n    ...:     fetchall()\n    ...: \n307 ms \xc2\xb1 5.72 ms per loop (mean \xc2\xb1 std. dev. of 7 runs, 1 loop each)\n\nIn [25]: %timeit foo.tablesample(1).select().execute().fetchall()\n6.36 ms \xc2\xb1 188 \xc2\xb5s per loop (mean \xc2\xb1 std. dev. of 7 runs, 100 loops each)\n\nIn [26]: %timeit foo.tablesample(func.bernoulli(1)).select().execute().fetchall()\n19.8 ms \xc2\xb1 381 \xc2\xb5s per loop (mean \xc2\xb1 std. dev. of 7 runs, 10 loops each)\n
Run Code Online (Sandbox Code Playgroud)\n

在急于使用SYSTEM采样方法之前,我们应该知道它对页面进行采样,而不是对单个元组进行采样,因此它可能不适合例如小型表,并且如果表是聚集的,则可能不会产生随机结果。

\n
\n

如果使用不允许将样本百分比/行数和种子作为参数传递的方言,以及不允许内联值的驱动程序,则如果这些值是 static ,则将这些值作为文字 SQL 文本传递,或者使用自定义的内联它们SQLA 编译器扩展:

\n
from sqlalchemy.ext.compiler import compiles\nfrom sqlalchemy.sql import TableSample\n\n@compiles(TableSample)\ndef visit_tablesample(tablesample, self, asfrom=False, **kw):\n    """ Compile `TableSample` with values inlined.\n    """\n    kw_literal_binds = {**kw, "literal_binds": True}\n    text = "%s TABLESAMPLE %s" % (\n        self.visit_alias(tablesample, asfrom=True, **kw),\n        tablesample._get_method()._compiler_dispatch(self, **kw_literal_binds),\n    )\n\n    if tablesample.seed is not None:\n        text += " REPEATABLE (%s)" % (\n            tablesample.seed._compiler_dispatch(self, **kw_literal_binds)\n        )\n\n    return text\n\nfrom sqlalchemy import table, literal, text\n\n# Static percentage\nprint(table("tbl").tablesample(text("5 PERCENT")))\n# Compiler inlined values\nprint(table("tbl").tablesample(5, seed=literal(42)))\n
Run Code Online (Sandbox Code Playgroud)\n