将复杂的 SQL 查询转换为 SQLAlchemy

web*_*ial 4 python mysql sqlalchemy geospatial

我的想法用完了。我现在用谷歌搜索超过一天,但我仍然找不到任何有用的答案来解决我的问题。

到目前为止我所做的,我尝试使用原始 SQL 但没有运气。

locations = db.session.query(Location, select([text('( 6371 * acos( cos( radians("53.6209798282177") ) * cos( radians( lat ) ) * cos( radians( lng ) - radians("13.96948162900808") ) + sin( radians("53.6209798282177") ) * sin( radians( lat ) ) ) )')]).label('distance')).having('distance' < 25).all()
Run Code Online (Sandbox Code Playgroud)

使用此原始 SQL 查询时mysql,返回的结果为零,但在其中运行相同的查询时,返回正确的结果。

此外,我还发现,在将查询打印到终端时,它无法HAVING()正确处理该子句。

我的查询在打印时如下所示:

SELECT location.id AS location_id, location.created_date AS location_created_date, location.zip AS location_zip, location.user_id AS location_user_id, location.lat AS location_lat, location.lng AS location_lng, location.city AS location_city 
FROM location 
HAVING false = 1
Run Code Online (Sandbox Code Playgroud)

如何将此 SQL 查询转换为 SQLAlchemy

SELECT *, ( 6371 * acos( cos( radians(53.6209798282177) ) * cos( radians( lat ) ) * cos( radians( lng ) - radians(11.96948162900808) ) + sin( radians(53.6209798282177) ) * sin( radians( lat ) ) ) ) AS distance FROM location HAVING distance < 25 ORDER BY distance;
Run Code Online (Sandbox Code Playgroud)

我的桌子看起来像这样:

+--------------+----------------+------+-----+---------+-------+
| Field        | Type           | Null | Key | Default | Extra |
+--------------+----------------+------+-----+---------+-------+
| id           | varchar(50)    | NO   | PRI | NULL    |       |
| created_date | datetime       | YES  |     | NULL    |       |
| zip          | varchar(5)     | NO   | UNI | NULL    |       |
| user_id      | varchar(50)    | NO   |     | NULL    |       |
| lat          | decimal(15,13) | NO   |     | NULL    |       |
| lng          | decimal(15,13) | NO   |     | NULL    |       |
| city         | text           | NO   |     | NULL    |       |
+--------------+----------------+------+-----+---------+-------+
Run Code Online (Sandbox Code Playgroud)

任何帮助表示赞赏。

Ilj*_*ilä 7

HAVING的处理正确,但是您传递了错误的表达式。似乎您正在使用 Python 2,因为字符串和整数之间的关系比较

'distance' < 25
Run Code Online (Sandbox Code Playgroud)

不会引发异常,而是评估为False。换句话说,您的查询等于

locations = db.session.query(...).having(False).all()
Run Code Online (Sandbox Code Playgroud)

这解释了为什么你得到零结果:所有行都被 HAVING 子句显式过滤掉,如打印版本所示:

'distance' < 25
Run Code Online (Sandbox Code Playgroud)

一种解决方案是使用合适的构造(例如column())来生成表达式:

locations = db.session.query(...).having(column('distance') < 25).all()
Run Code Online (Sandbox Code Playgroud)

您不应将复杂的选择列表项表达式包装在select()代表 SELECT 语句的 中。要么按text()原样标记:

text('( 6371 * acos( cos( radians("53.6209798282177") ) * '
     'cos( radians( lat ) ) * cos( radians( lng ) - radians("13.96948162900808") ) + '
     'sin( radians("53.6209798282177") ) * sin( radians( lat ) ) ) ) '
     'AS distance')
Run Code Online (Sandbox Code Playgroud)

或使用模型构建表达式:

(6371 *
 func.acos(func.cos(func.radians(53.6209798282177)) *
           func.cos(func.radians(Location.lat)) *
           func.cos(func.radians(Location.lng) - func.radians(13.96948162900808)) +
           func.sin(func.radians(53.6209798282177)) *
           func.sin(func.radians(Location.lat)))).label('distance')
Run Code Online (Sandbox Code Playgroud)

你可以通过使功能为提高查询建设的可读性大圆距离,并与工作的一点点,你可以实现一个混合方法Location

import math

def gc_distance(lat1, lng1, lat2, lng2, math=math):
    ang = math.acos(math.cos(math.radians(lat1)) *
                    math.cos(math.radians(lat2)) *
                    math.cos(math.radians(lng2) -
                             math.radians(lng1)) +
                    math.sin(math.radians(lat1)) *
                    math.sin(math.radians(lat2)))

    return 6371 * ang

class Location(db.Model):
    ...
    @hybrid_method
    def distance(self, lat, lng):
        return gc_distance(lat, lng, self.lat, self.lng)

    @distance.expression
    def distance(cls, lat, lng):
        return gc_distance(lat, lng, cls.lat, cls.lng, math=func)

locations = db.session.query(
        Location,
        Location.distance(53.6209798282177,
                          13.96948162900808).label('distance')).\
    having(column('distance') < 25).\
    order_by('distance').\
    all()
Run Code Online (Sandbox Code Playgroud)

请注意,您使用 HAVING 消除非组行的方式不可移植。例如,在 Postgresql 中,即使没有 GROUP BY 子句,HAVING 子句的存在也会将查询转换为分组查询。您可以改用子查询:

stmt = db.session.query(
        Location,
        Location.distance(53.6209798282177,
                          13.96948162900808).label('distance')).\
    subquery()

location_alias = db.aliased(Location, stmt)

locations = db.session.query(location_alias).\
    filter(stmt.c.distance < 25).\
    order_by(stmt.c.distance).\
    all()        
Run Code Online (Sandbox Code Playgroud)