简体   繁体   中英

SQLalchemy order_by hybrid_property resulting in "No such table"

My main goal is to make multiple filter options for a Car class. I can easily filter by distance and by the construction year, as they are just attributes of the class. The Car class is related to the Review class by a one-to-many relationship (One car can have many reviews). These reviews have a star rating from 0 - 5. I want to sort the Cars by the average rating of their reviews.

This is where I am at so far: The classes:

class Cars(db.Model):
    __tablename__= "Cars"
    
    year = db.Column(db.Integer)
    distance = db.Column(db.Integer)

    # Some other attributes

    reviews = db.relationship('Reviews', backref='car')
    

    @hybrid_property                  
    def score(self):
        if len(self.reviews) != 0:
            return sum(review.stars for review in self.reviews) / len(self.reviews)
        else:
            return None

    @score.expression
    def score(cls):
        return (select([func.count(Reviews.id)]).select_from(Reviews).where(Reviews.car_id == cls.id))
        

class Reviews(db.Model):
    __tablename__= "Reviews"
    __bind_key__ = 'Reviews'

    # Some other attributes
    
    stars = db.Column(db.Integer)
    
    car_id = db.Column(db.Integer, db.ForeignKey('Cars.id'))

This is my view function:

@mainRoutes.route("/cars/<int:page_num>")
def cars(page_num):
    cars = getPagination(Cars, page = page_num, sort=Cars.score) #Cars.score can be swapped to other columns  
    return render_template("cars.html", cars=cars)

I know that the score.expression function doesnt return the average, but instead the count. Thats just a WIP. For now, I just want to get this to work.

This is the getPagination function I wrote:

    def getPagination(model, sort, perPage = 16, page = 1, **filter):
        return model.query.filter(**filter).order_by(sort).paginate(per_page = perPage, page = page)

When I try to sort the cars by the hybrid_property score, I get the following error:

sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) no such table: Reviews
[SQL: SELECT "Cars".id AS "Cars_id", "Cars".model AS "Cars_model", "Cars".year AS "Cars_year", "Cars".distance AS "Cars_distance", "Cars".start_rent_period AS "Cars_start_rent_period", "Cars".end_rent_period AS "Cars_end_rent_period", "Cars".is_available AS "Cars_is_available" 
FROM "Cars" ORDER BY (SELECT count("Reviews".id) AS count_1 
FROM "Reviews" 
WHERE "Reviews".car_id = "Cars".id)
 LIMIT ? OFFSET ?]
[parameters: (16, 0)]
(Background on this error at: https://sqlalche.me/e/14/e3q8)

The error originates from line: return model.query.filter(**filter).order_by(sort).paginate(per_page = perPage, page = page) If you want the full traceback, I can supply that.

So my question is this: Why does Flask, or SQLalchemy, not recognize the Reviews table. Cause its def.netly there. I can display instances of it, I can read and write values into it and everywhere else in the code it works fine.

Other things I have tried: I took the code for the hybrid_property from Here And also tried it with the provided answer, but return select(Reviews.stars).where(Reviews.car_id==cls.id) also didnt work

I suspected, that maybe the problem is, that some cars dont have reviews (which some cars definitely dont have) and tried to account for that in this approach: reviews = select(case(exists().where(Reviews.car_id==cls.id), func.sum(Reviews.stars))) , however I am very new to SQLalchemy and dont even know if my idea here makes sense. It surely didnt fix my issue though.

Edit: The Cars database doesnt have a bind key, because its the "first" database. Here are the settings I use for my Flask project:

SQLALCHEMY_DATABASE_URI = 'sqlite:///../Databases/Cars.db'
SQLALCHEMY_BINDS = {
    'Reviews':          'sqlite:///../Databases/Reviews.db',
    }

The problem seems to be that while binds enable to connecting to different databases, they don't enable combining classes with different binds in a single query. For example, even a simple join will raise no such table: Reviews

Cars.query.options(orm.joinedload(Cars.reviews)).first().reviews

The usual way to handle this is by declaring a schema , at least on the "foreign" models:

class Reviews(db.Model):
    __tablename__ = 'Reviews'
    __bind_key__ = 'Reviews'
    __table_args__ = {'schema': 'reviews'}

Additionally we need to make SQLite aware of this; this can be done with an event listener (credit to Gabriel Costa for this answer ).

# This should be executed before dropping/creating tables
from sqlalchemy import event
from sqlalchemy.engine import Engine
...
@event.listens_for(Engine, "connect")
def connect(dbapi_connection, connection_record):
    dbapi_connection.execute('ATTACH "so71960014-reviews.db" AS "reviews"')

With these changes in place, the generated SQL qualifies the table name so that it is found:

SELECT "Cars".id AS "Cars_id", "Cars".year AS "Cars_year", "Cars".distance AS "Cars_distance" 
FROM "Cars" ORDER BY (SELECT count(reviews."Reviews".id) AS count_1 
FROM reviews."Reviews" 
WHERE reviews."Reviews".car_id = "Cars".id)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM