sql >> Base de Datos >  >> RDS >> Mysql

Convierta una consulta SQL compleja a SQLAlchemy

Tu HAVING se maneja correctamente, pero le está pasando la expresión incorrecta. Parece que estás usando Python 2, ya que la comparación relacional entre una cadena y un entero

'distance' < 25

no genera una excepción, pero se evalúa como False en cambio. En otras palabras, su consulta es igual a

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

lo que explica por qué obtiene cero resultados:todas las filas se filtran explícitamente mediante la cláusula HAVING, como se ve en la versión impresa:

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

Una solución es utilizar una construcción adecuada, como column() , para producir la expresión:

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

No debe envolver la expresión de elemento de lista de selección compleja en una select() , que representa una instrucción SELECT. Etiquete el text() como es:

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

o construye la expresión usando el modelo:

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

Podría mejorar la legibilidad de la construcción de su consulta creando una función para la gran distancia del círculo , y con un poco de trabajo podría implementar un método híbrido en 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()

Tenga en cuenta que la forma en que usa HAVING para eliminar filas que no son de grupo no es portátil. Por ejemplo, en Postgresql la presencia de cláusula HAVING convierte una consulta en una consulta agrupada, incluso sin una cláusula GROUP BY. Podría usar una subconsulta en su lugar:

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