简体   繁体   中英

sqlalchemy subquery and join not ordering relationship correctly

I have two tables models and images. These tables have a one to many relationship one Model has many Images. My image sqlalchemy model has an attribute that is a query_expression this is set in the subquery and I want to use it to order both the model results and the images of the models.

using the subquery orders the models but does not order the images of the models. insted the images are ordered by id.

subquery does not work quite how I expected it to, because it doesn't seem use the Images from the subquery it appears gets new Images from the database, this does make sense to me but is not what I want it to do. I want it to use the images from the subquery so they have the weight set I think this would fix sorting issue as well. If this is not possible I am also happy to just have the images related to the models sorted correctly matching the subquery

I have tried to solve this problem in many different ways, This code is the closest i've gotten so far

from flask import Flask
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///:memory:"
db = SQLAlchemy(app)


class Image(db.Model):
    __tablename__ = 'images'

    id = db.Column(db.Integer, primary_key=True)
    url = db.Column(db.Text)
    model_id = db.Column(db.ForeignKey('models.id'))

    weight = db.query_expression()

    model = db.relationship('Model', primaryjoin='Image.model_id == Model.id', backref="images")

    def __repr__(self):
        return f"<Image {self.id} {self.model_id} {self.weight}>"  # <Image id model_id weight>


class Model(db.Model):
    __tablename__ = 'models'

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.Text)
    url = db.Column(db.Text)


db.create_all()

test_data = [(496, [2457, 2458, 2459, 2460, 2461, 2462, 2463, 2464, 2465]),
             (719, [3669, 3670, 3671, 3672, 3673, 3674, 3675, 5122]),
             (720, [3676, 3677, 3678, 3679, 3680, 3681, 3682, 3683, 3684]), (721, [3685, 3686, 3687, 3688, 3689]),
             (957, [4921, 4922, 4923, 4924, 4925, 4926, 4927, 4928])]
for model_id, image_ids in test_data:
    model = Model(id=model_id)
    for image_id in image_ids:
        model.images.append(Image(id=image_id, model_id=model_id))
    db.session.add(model)
db.session.commit()


@app.route('/')
def hello_world():
    image_ids = [5122, 3669, 2457, 3670, 2460, 3677, 2459, 3685, 3676, 2458, 4921, 4923, 3687,
                 4922, 3686, 3671, 4924, 4927, 2461, 3679, 3672, 2465, 3678, 3688, 3682, 3680,
                 4928, 3689, 4925, 3684, 2464, 4926, 3683, 2462, 3675, 3673, 3681, 2463, 3674]
    model_ids = [719, 496, 720, 721, 957]
    image_weights = [0.023905573, 0.82817817, 0.9049727, 0.9182812, 0.9498839, 0.9507337, 0.9895415, 1.0062627,
                     1.006611, 1.0194101, 1.0212375, 1.0346948, 1.053909, 1.095496, 1.0977578, 1.0993682, 1.1059334,
                     1.1750572, 1.1773994, 1.1993531, 1.1995214, 1.2016991, 1.2049828, 1.2159566, 1.2252866, 1.2271863,
                     1.245359, 1.248407, 1.2530898, 1.2567475, 1.2585162, 1.2642251, 1.2729594, 1.2750005, 1.2892585,
                     1.2958231, 1.296642, 1.3050407, 1.310439]

    case_statement = db.case([(uid == Image.id, weight) for uid, weight in zip(image_ids, image_weights)], else_=None) \
        .label("weight")
    images = db.session.query(Image).options(db.with_expression(Image.weight, case_statement)).order_by(
        "weight").filter(db.column("weight").isnot(None))
    images_subquery = images.subquery("images")
    models = db.session.query(Model).join(images_subquery)
    for model in models:
        print(model, model.images)
    return 'Hello World!'


if __name__ == '__main__':
    app.run()

The output when going to the / route is

<Model 719> [<Image 3669 719 None>, <Image 3670 719 None>, <Image 3671 719 None>, <Image 3672 719 None>, <Image 3673 719 None>, <Image 3674 719 None>, <Image 3675 719 None>, <Image 5122 719 None>]
<Model 496> [<Image 2457 496 None>, <Image 2458 496 None>, <Image 2459 496 None>, <Image 2460 496 None>, <Image 2461 496 None>, <Image 2462 496 None>, <Image 2463 496 None>, <Image 2464 496 None>, <Image 2465 496 None>]
<Model 720> [<Image 3676 720 None>, <Image 3677 720 None>, <Image 3678 720 None>, <Image 3679 720 None>, <Image 3680 720 None>, <Image 3681 720 None>, <Image 3682 720 None>, <Image 3683 720 None>, <Image 3684 720 None>]
<Model 721> [<Image 3685 721 None>, <Image 3686 721 None>, <Image 3687 721 None>, <Image 3688 721 None>, <Image 3689 721 None>]
<Model 957> [<Image 4921 957 None>, <Image 4922 957 None>, <Image 4923 957 None>, <Image 4924 957 None>, <Image 4925 957 None>, <Image 4926 957 None>, <Image 4927 957 None>, <Image 4928 957 None>]

as you can see the Models are ordered but the Images are ordered by id and there weight is None I want them to be ordered by their weight and hopefully have it set correctly like this

<Model 719> [<Image 5122 719 0.023905573>, <Image 3669 719 0.82817817>, <Image 3670 719 0.9182812>, <Image 3671 719 1.0993682>, <Image 3672 719 1.1995214>, <Image 3675 719 1.2892585>, <Image 3673 719 1.2958231>, <Image 3674 719 1.310439>]
<Model 496> [<Image 2457 496 0.9049727>, <Image 2460 496 0.9498839>,<Image 2459 496 0.9895415>, <Image 2458 496 1.0194101>, <Image 2461 496 1.1773994>, <Image 2465 496 1.2016991>, <Image 2464 496 1.2585162>, <Image 2462 496 1.2750005>, <Image 2463 496 1.3050407>]
<Model 720> [<Image 3677 720 0.9507337>, <Image 3676 720 1.006611>, <Image 3679 720 1.1993531>, <Image 3678 720 1.2049828>, <Image 3682 720 1.2252866>, <Image 3680 720 1.2271863>, <Image 3684 720 1.2567475>, <Image 3683 720 1.2729594>, <Image 3681 720 1.296642>]
<Model 721> [<Image 3685 721 1.0062627>, <Image 3687 721 1.053909>, <Image 3686 721 1.0977578>, <Image 3688 721 1.2159566>, <Image 3689 721 1.248407>]
<Model 957> [<Image 4921 957 1.0212375>, <Image 4923 957 1.0346948>, <Image 4922 957 1.095496>, <Image 4924 957 1.1059334>, <Image 4927 957 1.1750572>, <Image 4928 957 1.245359>, <Image 4925 957 1.2530898>, <Image 4926 957 1.2642251>]

Any help is greatly appreciated

After lots of searching through the sqlalchemy docs I found contains_eager . It tells SQLAlchemy that the a given relationship should be loaded from the columns in the query.

models = Model.query.join(Image).order_by(case_statement).options(db.contains_eager(Model.images)).filter(Model.id.in_(model_ids))

To make my project work I decided to remove the with_expression and the query_expression and instead just used the case statement in a order_by()

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