如何在 SQLAlchemy 的过滤条件中传递模型参数

Posted

技术标签:

【中文标题】如何在 SQLAlchemy 的过滤条件中传递模型参数【英文标题】:How to pass a model parameter in a filter condition in SQLAlchemy 【发布时间】:2020-03-23 19:12:48 【问题描述】:

我正在使用 SQLAlchemy 来处理我目前正在开发的 Flask webapp 的数据库。作为网站的一项功能,我希望用户能够以该指定位置的位置和半径作为参数搜索其他用户。所以很自然,我对 SQLAlchemy 不太了解,所以我尝试了这个表达式:

profiles = User.query.filter(sqrt(abs(User.latitude - location.latitude)**2 + abs(User.longitude - location.longitude)**2) <= radius)

说明:我想返回所有位于指定位置半径范围内的用户。

但是,它给了我这个错误:

Traceback (most recent call last):
 File "c:\users\frederik\appdata\local\programs\python\python37\lib\site-packages\flask\app.py", line 2309, in __call__
   return self.wsgi_app(environ, start_response)
 File "c:\users\frederik\appdata\local\programs\python\python37\lib\site-packages\flask\app.py", line 2295, in wsgi_app
   response = self.handle_exception(e)
 File "c:\users\frederik\appdata\local\programs\python\python37\lib\site-packages\flask\app.py", line 1741, in handle_exception
   reraise(exc_type, exc_value, tb)
 File "c:\users\frederik\appdata\local\programs\python\python37\lib\site-packages\flask\_compat.py", line 35, in reraise
   raise value
 File "c:\users\frederik\appdata\local\programs\python\python37\lib\site-packages\flask\app.py", line 2292, in wsgi_app
   response = self.full_dispatch_request()
 File "c:\users\frederik\appdata\local\programs\python\python37\lib\site-packages\flask\app.py", line 1815, in full_dispatch_request
   rv = self.handle_user_exception(e)
 File "c:\users\frederik\appdata\local\programs\python\python37\lib\site-packages\flask\app.py", line 1718, in handle_user_exception
   reraise(exc_type, exc_value, tb)
 File "c:\users\frederik\appdata\local\programs\python\python37\lib\site-packages\flask\_compat.py", line 35, in reraise
   raise value
 File "c:\users\frederik\appdata\local\programs\python\python37\lib\site-packages\flask\app.py", line 1813, in full_dispatch_request
   rv = self.dispatch_request()
 File "c:\users\frederik\appdata\local\programs\python\python37\lib\site-packages\flask\app.py", line 1799, in dispatch_request
   return self.view_functions[rule.endpoint](**req.view_args)
 File "C:\Users\Frederik\Desktop\start\app\routes.py", line 136, in explore
   profiles = User.query.filter_by(sqrt(abs(User.latitude - location.latitude)**2 + abs(User.longitude - location.longitude)**2) <= radius)
TypeError: bad operand type for abs(): 'BinaryExpression'

我如何能够将方程中的模型参数作为条件传递给这样的查询搜索?

编辑

根据 frost-nzcr4 的建议,我尝试使用 @hybrid_method 装饰器,因为这似乎是最适合我的情况。

(我通过阅读文档得出了这个结论:https://docs.sqlalchemy.org/en/13/orm/extensions/hybrid.html

因此,我将它添加到我的模型中:

@hybrid_method
def is_nearby(self, latitude, longitude, radius):
    return sqrt((self.latitude - latitude)**2 + (self.longitude - longitude)**2) <= radius

(我还删除了表达式中的abs() 函数,因为平方已经返回一个完全正值..)

这是我的路线而不是旧的表达方式:

profiles = User.query.filter(User.is_nearby(latitude=location.latitude, longitude=location.longitude, radius=radius))

(仅供参考,然后我保存了我的代码,更新了我的数据库并重新启动了烧瓶服务器)

我再次尝试查询搜索,然后... 我收到了这个错误:

Traceback (most recent call last):
  File "c:\users\frederik\appdata\local\programs\python\python37\lib\site-packages\flask\app.py", line 2309, in __call__
    return self.wsgi_app(environ, start_response)
  File "c:\users\frederik\appdata\local\programs\python\python37\lib\site-packages\flask\app.py", line 2295, in wsgi_app
    response = self.handle_exception(e)
  File "c:\users\frederik\appdata\local\programs\python\python37\lib\site-packages\flask\app.py", line 1741, in handle_exception
    reraise(exc_type, exc_value, tb)
  File "c:\users\frederik\appdata\local\programs\python\python37\lib\site-packages\flask\_compat.py", line 35, in reraise
    raise value
  File "c:\users\frederik\appdata\local\programs\python\python37\lib\site-packages\flask\app.py", line 2292, in wsgi_app
    response = self.full_dispatch_request()
  File "c:\users\frederik\appdata\local\programs\python\python37\lib\site-packages\flask\app.py", line 1815, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "c:\users\frederik\appdata\local\programs\python\python37\lib\site-packages\flask\app.py", line 1718, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "c:\users\frederik\appdata\local\programs\python\python37\lib\site-packages\flask\_compat.py", line 35, in reraise
    raise value
  File "c:\users\frederik\appdata\local\programs\python\python37\lib\site-packages\flask\app.py", line 1813, in full_dispatch_request
    rv = self.dispatch_request()
  File "c:\users\frederik\appdata\local\programs\python\python37\lib\site-packages\flask\app.py", line 1799, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "C:\Users\Frederik\Desktop\start\app\routes.py", line 134, in explore
    profiles = User.query.filter(User.is_nearby(latitude=location.latitude, longitude=location.longitude, radius=radius))
  File "C:\Users\Frederik\Desktop\start\app\models.py", line 64, in is_nearby
    return sqrt((self.latitude - latitude)**2 + (self.longitude - longitude)**2) <= radius
TypeError: unsupported operand type(s) for ** or pow(): 'BinaryExpression' and 'int'

位置变量不包含 sqlalchemy 模型。这是来自the Geopy Python library 的对象。然而,它的依赖 .latitude.longitude 都只是浮点数。例如,它可以这样定义:

location = geolocator.geocode("NYC, New York, USA")

这是我的用户模型,它有两个与此问题相关的依赖项:

class User(UserMixin, db.Model):
    ...
    latitude = db.Column(db.Float, index=True)
    longitude = db.Column(db.Float, index=True)
    ...

为什么模型的这些属性不能应用于特定的操作数ie。在查询搜索中的条件内平方?在我的情况下,我该如何解决?

PS

非常感谢您对 frost-nzcr4 的评论。这意味着很多!

编辑 2

好的,所以现在根据 frost-nzcr4 的另一个建议,我将 is_nearby 方法更改为: (这里func是指from sqlalchemy import func

@hybrid_method
def is_nearby(self, latitude, longitude, radius):
    return func.sqrt(func.pow(self.latitude - latitude, 2) + func.pow(self.longitude - longitude, 2)) <= radius

另外,查询搜索语句现在写成这样:

profiles = User.query.filter(User.is_nearby(latitude=location.latitude, longitude=location.longitude, radius=radius)).all()

而且它有效!它摆脱了以前的错误..但随后又出现了另一个..:

Traceback (most recent call last):
  File "c:\users\frederik\appdata\local\programs\python\python37\lib\site-packages\sqlalchemy\engine\base.py", line 1244, in _execute_context
    cursor, statement, parameters, context
  File "c:\users\frederik\appdata\local\programs\python\python37\lib\site-packages\sqlalchemy\engine\default.py", line 552, in do_execute
    cursor.execute(statement, parameters)
sqlite3.OperationalError: no such function: sqrt

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "c:\users\frederik\appdata\local\programs\python\python37\lib\site-packages\flask\app.py", line 2309, in __call__
    return self.wsgi_app(environ, start_response)
  File "c:\users\frederik\appdata\local\programs\python\python37\lib\site-packages\flask\app.py", line 2295, in wsgi_app
    response = self.handle_exception(e)
  File "c:\users\frederik\appdata\local\programs\python\python37\lib\site-packages\flask\app.py", line 1741, in handle_exception
    reraise(exc_type, exc_value, tb)
  File "c:\users\frederik\appdata\local\programs\python\python37\lib\site-packages\flask\_compat.py", line 35, in reraise
    raise value
  File "c:\users\frederik\appdata\local\programs\python\python37\lib\site-packages\flask\app.py", line 2292, in wsgi_app
    response = self.full_dispatch_request()
  File "c:\users\frederik\appdata\local\programs\python\python37\lib\site-packages\flask\app.py", line 1815, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "c:\users\frederik\appdata\local\programs\python\python37\lib\site-packages\flask\app.py", line 1718, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "c:\users\frederik\appdata\local\programs\python\python37\lib\site-packages\flask\_compat.py", line 35, in reraise
    raise value
  File "c:\users\frederik\appdata\local\programs\python\python37\lib\site-packages\flask\app.py", line 1813, in full_dispatch_request
    rv = self.dispatch_request()
  File "c:\users\frederik\appdata\local\programs\python\python37\lib\site-packages\flask\app.py", line 1799, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "C:\Users\Frederik\Desktop\start\app\routes.py", line 135, in explore
    profiles = User.query.filter(User.is_nearby(latitude=location.latitude, longitude=location.longitude, radius=radius)).all()
  File "c:\users\frederik\appdata\local\programs\python\python37\lib\site-packages\sqlalchemy\orm\query.py", line 3161, in all
    return list(self)
  File "c:\users\frederik\appdata\local\programs\python\python37\lib\site-packages\sqlalchemy\orm\query.py", line 3317, in __iter__
    return self._execute_and_instances(context)
  File "c:\users\frederik\appdata\local\programs\python\python37\lib\site-packages\sqlalchemy\orm\query.py", line 3342, in _execute_and_instances
    result = conn.execute(querycontext.statement, self._params)
  File "c:\users\frederik\appdata\local\programs\python\python37\lib\site-packages\sqlalchemy\engine\base.py", line 988, in execute
    return meth(self, multiparams, params)
  File "c:\users\frederik\appdata\local\programs\python\python37\lib\site-packages\sqlalchemy\sql\elements.py", line 287, in _execute_on_connection
    return connection._execute_clauseelement(self, multiparams, params)
  File "c:\users\frederik\appdata\local\programs\python\python37\lib\site-packages\sqlalchemy\engine\base.py", line 1107, in _execute_clauseelement
    distilled_params,
  File "c:\users\frederik\appdata\local\programs\python\python37\lib\site-packages\sqlalchemy\engine\base.py", line 1248, in _execute_context
    e, statement, parameters, cursor, context
  File "c:\users\frederik\appdata\local\programs\python\python37\lib\site-packages\sqlalchemy\engine\base.py", line 1466, in _handle_dbapi_exception
    util.raise_from_cause(sqlalchemy_exception, exc_info)
  File "c:\users\frederik\appdata\local\programs\python\python37\lib\site-packages\sqlalchemy\util\compat.py", line 383, in raise_from_cause
    reraise(type(exception), exception, tb=exc_tb, cause=cause)
  File "c:\users\frederik\appdata\local\programs\python\python37\lib\site-packages\sqlalchemy\util\compat.py", line 128, in reraise
    raise value.with_traceback(tb)
  File "c:\users\frederik\appdata\local\programs\python\python37\lib\site-packages\sqlalchemy\engine\base.py", line 1244, in _execute_context
    cursor, statement, parameters, context
  File "c:\users\frederik\appdata\local\programs\python\python37\lib\site-packages\sqlalchemy\engine\default.py", line 552, in do_execute
    cursor.execute(statement, parameters)
sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) no such function: sqrt
[SQL: SELECT user.id AS user_id, user.name AS user_name, user.location AS user_location, user.latitude AS user_latitude, user.longitude AS user_longitude, user.username AS user_username, user.email AS user_email, user.password_hash AS user_password_hash
FROM user
WHERE sqrt(pow(user.latitude - ?, ?) + pow(user.longitude - ?, ?)) <= ?]
[parameters: (55.8125143, 2, 12.4687513, 2, '10')]
(Background on this error at: http://sqlalche.me/e/e3q8)

sqlalchemy.func 内部似乎没有相当于 Math 模块的平方根函数。

如果有,那它叫什么?如果没有,那我该怎么办?

【问题讨论】:

检查装饰器@hybrid_property并使用sqlaclhemy.func中的所有函数 在此处描述您的location 模型。 你试过sqlalchemy.func.pow(self.latitude - latitude, 2)吗? 再次感谢您!我认为平方现在有效,但sqlalchemy.func 似乎不包含与数学模块的sqrt() 等效的函数。这个问题现在更新为我得到的错误。 (我希望这是最后一步!) sqlalchemy.func 不包含任何函数,它是一种用于生成 SQL 函数表达式(后来编译为实际 SQL 的数据结构)的工厂。您可以使用它来生成几乎任何函数表达式,例如func.xyzzy()。问题是 SQLite 没有提供开箱即用的数学函数,但很容易注册这些函数:***.com/questions/7595050/… 和 ***.com/questions/53061014/… 【参考方案1】:

答案有点复杂:

    通过@hybrid_method/@hybrid_property描述逻辑。 将sqlalchemy.func 用于powsqrt 等。 确保您的数据库后端已实现这些功能。

在您的情况下,sqlite3.OperationalError: no such function: sqrt 请参阅https://***.com/a/12731982/5274713。在最坏的情况下,您必须使用纯 SQL 表达式,如 https://***.com/a/2002448/5274713

【讨论】:

哟,非常感谢您帮助我!通过浏览您的链接,我注意到为了避免下载对sqrt()pow() 的支持,我可以像这样编写is_nearby 方法的返回语句:return (self.latitude - latitude)*(self.latitude - latitude)+(self.longitude - longitude)*(self.longitude - longitude) &lt;= radius*radius 再想一想,我可能需要实现类似the Haversine formula 的东西,但这是另一个问题;)

以上是关于如何在 SQLAlchemy 的过滤条件中传递模型参数的主要内容,如果未能解决你的问题,请参考以下文章

如何在 sqlalchemy 查询过滤器中使用用户定义的 python 函数?

嵌套的一对多关系 sqlalchemy 过滤

sqlalchemy filter 方法常用过滤条件

您如何设置 sqlalchemy 关系ihp,以便子查询负载将根据属性过滤我们的结果

在 sqlalchemy python 中构建动态过滤器

SQLAlchemy Flask 过滤器查询以组合来自两个模型的结果