【问题标题】:Convert a complex SQL query to SQLAlchemy将复杂的 SQL 查询转换为 SQLAlchemy
【发布时间】:2018-06-25 17:35:10
【问题描述】:

我的想法已经用完了。我现在用谷歌搜索了一天以上,但我仍然找不到任何有用的答案。

到目前为止,我尝试使用原始 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()

使用此原始 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

如何将此 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;

我的桌子是这样的:

+--------------+----------------+------+-----+---------+-------+
| 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    |       |
+--------------+----------------+------+-----+---------+-------+

感谢任何帮助。

【问题讨论】:

    标签: python mysql sqlalchemy geospatial


    【解决方案1】:

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

    'distance' < 25
    

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

    locations = db.session.query(...).having(False).all()
    

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

    ...
    HAVING false = 1  -- remove all rows
    

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

    locations = db.session.query(...).having(column('distance') < 25).all()
    

    您不应将复杂的选择列表项表达式包装在代表 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')
    

    或使用模型构建表达式:

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

    您可以通过为great-circle distance 创建一个函数来提高查询构造的可读性,并且通过一些工作您可以在Location 上实现hybrid method

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

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

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

    【讨论】:

      猜你喜欢
      • 2017-04-25
      • 1970-01-01
      • 1970-01-01
      • 2019-03-20
      • 2015-01-31
      • 2020-12-29
      • 1970-01-01
      • 1970-01-01
      • 2021-06-07
      相关资源
      最近更新 更多