Convertir una consulta SQL compleja a SQLAlchemy

Me quedo sin ideas. Busqué en Google más de un día y todavía no puedo encontrar ninguna respuesta útil a mi pregunta.

Lo que hice hasta ahora, traté de usar SQL en bruto pero sin suerte.

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

Cuando utilizo esta consulta de SQL sin formato, obtengo cero resultados, pero al ejecutar la misma consulta en mysql , devuelve los resultados correctos.

Además, me di cuenta de que al imprimir la consulta en el terminal, no maneja la cláusula HAVING() correctamente.

Mi consulta se ve así cuando se imprime:

 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 

¿Cómo convierto esta consulta SQL a 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; 

Mi mesa se ve así:

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

Cualquier ayuda es apreciada.

Su 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, sino que se evalúa como False . 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 por la cláusula HAVING, como se ve en la versión impresa:

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

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

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

No debe ajustar la expresión compleja del elemento de la lista de selección en un select() , que representa una instrucción SELECT. Etiqueta el text() como está:

 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 al hacer una función para la gran distancia de círculo , y con un poco de trabajo podría implementar un método híbrido en la 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 utiliza HAVING para eliminar filas no grupales no es portátil. Por ejemplo, en Postgresql, la presencia de la cláusula HAVING convierte una consulta en una consulta agrupada, incluso sin una cláusula GROUP BY. Podrías 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()