Mysql
 sql >> Base de données >  >> RDS >> Mysql

Convertir une requête SQL complexe en SQLAlchemy

Votre HAVING est géré correctement, mais vous lui passez la mauvaise expression. Il semble que vous utilisiez Python 2, car la comparaison relationnelle entre une chaîne et un entier

'distance' < 25

ne lève pas d'exception, mais est évalué à False Au lieu. En d'autres termes, votre requête est égale à

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

ce qui explique pourquoi vous n'obtenez aucun résultat :toutes les lignes sont explicitement filtrées par la clause HAVING, comme on le voit dans la version imprimée :

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

Une solution consiste à utiliser une construction appropriée, telle que column() , pour produire l'expression :

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

Vous ne devez pas envelopper l'expression d'élément de liste de sélection complexe dans un select() , qui représente une instruction SELECT. Soit étiqueter le text() tel quel :

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

ou construisez l'expression en utilisant le modèle :

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

Vous pouvez améliorer la lisibilité de la construction de votre requête en créant une fonction pour la distance du grand cercle , et avec un peu de travail, vous pourriez implémenter un méthode hybride sur 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()

Notez que la façon dont vous utilisez HAVING pour éliminer les lignes non groupées n'est pas portable. Par exemple dans Postgresql la présence de clause HAVING transforme une requête en une requête groupée, même sans clause GROUP BY. Vous pouvez utiliser une sous-requête à la place :

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