通过 SQLAlchemy 获取随机行

Posted

技术标签:

【中文标题】通过 SQLAlchemy 获取随机行【英文标题】:Getting random row through SQLAlchemy 【发布时间】:2010-09-08 19:35:13 【问题描述】:

如何使用 SQLAlchemy 从表中选择一个或多个随机行?

【问题讨论】:

【参考方案1】:

一些 SQL DBMS,即 Microsoft SQL Server、DB2 和 PostgreSQL 已经实现了 SQL:2003 TABLESAMPLE 子句。支持已添加到 SQLAlchemy in version 1.1。它允许使用不同的采样方法返回表格样本——标准要求SYSTEMBERNOULLI,它们返回所需的表格近似百分比。

在 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 采样方法之前,应该知道它采样页面,而不是单个元组,因此它可能不适合例如小表,并且可能不会产生随机结果,如果表是聚集的。


如果使用不允许将样本百分比/行数和种子作为参数传递的方言,以及不内联值的驱动程序,则将值作为文字 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)))

【讨论】:

有没有人能够使用 Azure SQL 数据库实现这一点?我正在尝试,但我得到 ``` ProgrammingError: (pyodbc.ProgrammingError) ('42000', '[42000] [Microsoft][ODBC Driver 13 for SQL Server][SQL Server]TABLESAMPLE 或 REPEATABLE 中不允许使用变量子句。(497)(SQLExecDirectW);[42000][Microsoft][ODBC Driver 13 for SQL Server][SQL Server]无法准备语句。(8180)')``` 听起来像是一个有趣的边缘案例,但实现并未涵盖;看起来不支持将百分比或种子作为参数传递。 Azure 方言需要在查询编译器中内联参数。我可以在今天晚些时候给出一个例子。 @robertfranklin 我希望添加的内容有助于使其正常工作。【参考方案2】:

使用这个最简单的方法 这个从数据库中选择一个随机问题的例子:-

#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())

【讨论】:

1.如果数据库中有一百万条记录怎么办? 2. 我们应该得到所有这些并随机选择一个吗?这不是一个昂贵的电话吗? 绝对会是一个昂贵的电话,但他只要求随机方法,而不是问“如何使用特定范围的数据或特定键进行随机查询”,所以如果我回答考虑到你提到的,那将是完全不同的话题。我试图尽可能简单地回答,以便清楚且仅用于准确询问。人们用大量的线条来回答,虽然它可以更简单。【参考方案3】:

这是我选择表格中随机行的函数:

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()

【讨论】:

【参考方案4】:

这是一个非常特定于数据库的问题。

我知道 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 中,选择随机记录具有严重的性能问题; here 是关于它的好文章。

【讨论】:

+1。与用于 SQLite 的 Postgres 相同:select.order_by(func.random()).limit(n) Oracle 中可以使用 order_by('dbms_random.value')。 如果您使用的是声明式模型:session.query(MyModel).order_by(func.rand()).first 感谢@trinth,当我在末尾添加括号时它起作用了:session.query(MyModel).order_by(func.rand()).first() 从 SQLAlchemy v0.4 开始,func.random() 是一个编译成数据库随机实现的通用函数。【参考方案5】:

这是我使用的解决方案:

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

【讨论】:

这在大桌子上会非常慢。您将抓取每一行,然后将其切片。 哇,是的,这不是很好。如果有查询来获取表记录计数,那将是一种更好的方法。这是在一个带有小型数据库的网络应用上完成的,不再与该公司合作,所以我对此无能为力。【参考方案6】:

这里有四种不同的变体,按从慢到快的顺序排列。 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 需要全表扫描,而optimized_random() 中的COUNT 可以使用索引。

【讨论】:

如何挑选样品?就像random.sample() 做什么?这里的优化方式是什么? 打开一个新问题并链接到它,我会尝试回答。如果可能,请指定 SQL 的基本风格,因为这也会影响答案。 这不是用flask-sqlalchemy吗?【参考方案7】:

此解决方案将选择一个随机行

此解决方案要求主键名为 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

【讨论】:

当您的 id 有空白时,此操作会失败。【参考方案8】:

有一种简单的方法可以拉取独立于数据库的随机行。 只需使用 .offset() 。无需拉所有行:

import random
query = DBSession.query(Table)
rowCount = int(query.count())
randomRow = query.offset(int(rowCount*random.random())).first()

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

【讨论】:

更新 - 在 mysql 中大约有 1000 万行时,这实际上开始变得有点慢,我想你可以优化它。 在 ~500k 行设置中对我来说效果很好。 现在在 Oracle 上达到 1100 万行......不再那么好 :-) 线性退化,但仍然......我必须找到别的东西。 @Jayme:你可以使用query.offset(random.randrange(rowCount)).limit(1).first() @Jayme 还有,有没有理由在.first() 之前使用.limit(1)?似乎是多余的。或许,query.offset(random.randrange(row_count)).first() 就够了。【参考方案9】:

如果您使用 orm 并且表不大(或者您缓存了它的行数)并且您希望它独立于数据库,那么真正简单的方法是。

import random
rand = random.randrange(0, session.query(Table).count()) 
row = session.query(Table)[rand]

这有点作弊,但这就是你使用 orm 的原因。

【讨论】:

rand = random.randrange(0, session.query(Table).count()) 在选择其中一个之前选择并创建所有对象 random.choice(session.query(Table)) 怎么样?【参考方案10】:

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

但是我不知道任何标准方式

【讨论】:

是的。我知道如何在 SQL 中执行此操作(我在 beta.***.com/questions/19412/… 中发布了该答案),但正在寻找 SQLAlchemy 特定的解决方案。

以上是关于通过 SQLAlchemy 获取随机行的主要内容,如果未能解决你的问题,请参考以下文章

VBA/宏根据多个条件复制随机行

如何从 PySpark DataFrame 中获取随机行?

生成新表时从表中获取随机行

读取随机行 com 文件

从mysql中的大表中快速选择随机行

在PostgreSQL中选择N个匹配条件的随机行