Flask-SQLAlchemy过滤与父模型的多对多关系

Dou*_*ler 8 python sqlalchemy flask-sqlalchemy

我有一个Parent模型,通过外键将一对不同类型的项目用作父项.我在父模型上也有很多关系.我试图基于查询多对多模型来获取子模型.

这是父模型

class MediaItem(db.Model):
    __tablename__ = "media_item"
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String, unique=True)
    tags = db.relationship('Tags', secondary=tags_joiner, backref='media_items')
    videos = db.relationship('Video', backref='Parent', lazy='dynamic')
    audios = db.relationship('Audio', backref='Parent', lazy='dynamic')
    pictures = db.relationship('Picture', backref='Parent', lazy='dynamic')
    codes = db.relationship('Code', backref='Parent', lazy='dynamic')
Run Code Online (Sandbox Code Playgroud)

和多对多的关系

class Tags(db.Model):
    __tablename__ = 'tags'
    id = db.Column(db.Integer, primary_key=True)
    tag = db.Column(db.String, unique=True, nullable=False)


tags_joiner = db.Table('tags_joiner',
                       db.Column('tag_id', db.Integer, db.ForeignKey('tags.id')),
                       db.Column('mediaitem_id', db.Integer, db.ForeignKey('media_item.id')),
                       db.PrimaryKeyConstraint('tag_id', 'mediaitem_id'))
Run Code Online (Sandbox Code Playgroud)

最后,有一个儿童模型的例子

class Video(db.Model):
    __tablename__ = 'video'
    id = db.Column(db.Integer, primary_key=True)
    parent_id = db.Column(db.Integer, db.ForeignKey('media_item.id'))
    file_name = db.Column(db.String, unique=True)
Run Code Online (Sandbox Code Playgroud)

MediaItem模型中定义的关系证明了其他几种类型的子模型.

我希望通过标签对子模型进行过滤.也就是说,给定特定标记,返回与该标记关联的所有子模型.

Video.query.join(media_tags).filter_by(MediaItem.tags.any(Tags.tag.in_(tag)))
Run Code Online (Sandbox Code Playgroud)

返回它不知道如何连接三个表,(找不到要加入的FROM子句.尝试加入,但得到:找不到'media_item'和'tags'之间的任何外键关系.)

我的方法是什么?

van*_*van 7

版本1:下面的查询应该返回所需的结果:

tag = 'my_filter_tag'
q = (
    db.session
    .query(Video)
    .filter(Video.Parent.has(MediaItem.tags.any(Tags.tag == tag)))
)
Run Code Online (Sandbox Code Playgroud)

它可能不是最优的,因为它产生SQL了两个嵌套EXISTS子句,但绝对是非常可读的sqlalchemy查询.以下是为以下内容生成的查询sqlite:

SELECT  video.id AS video_id, video.parent_id AS video_parent_id, video.file_name AS video_file_name
FROM    video
WHERE EXISTS (
    SELECT  1
    FROM    media_item
    WHERE   media_item.id = video.parent_id
        AND (
        EXISTS (
            SELECT 1
            FROM    tags_joiner, tags
            WHERE   media_item.id = tags_joiner.mediaitem_id
                AND tags.id = tags_joiner.tag_id
                AND tags.tag = :tag_1
            )
        )
    )
Run Code Online (Sandbox Code Playgroud)

版本2:稍微优化的查询将是加入media_item,但仍然exists在标记上执行:

q = (
    db.session
    .query(Video)
    .join(MediaItem, Video.Parent)
    .filter(MediaItem.tags.any(Tags.tag == tag))
)
Run Code Online (Sandbox Code Playgroud)

这将产生SQL如下:

SELECT  video.id AS video_id, video.parent_id AS video_parent_id, video.file_name AS video_file_name
FROM    video
JOIN    media_item
    ON  media_item.id = video.parent_id
WHERE   EXISTS (
    SELECT  1
    FROM    tags_joiner, tags
    WHERE   media_item.id = tags_joiner.mediaitem_id
        AND tags.id = tags_joiner.tag_id
        AND tags.tag = :tag_1
    )
Run Code Online (Sandbox Code Playgroud)

你也可以加入进一步的tags_joinertags实现的结果.但是这消除了一些灵活性:如果你想执行并OR检查多个标签,结果可能会返回多Video行,而保持查询EXISTS将处理这一点.


请注意,您的代码有一个media_tags,但不清楚它是什么.