SQLAlchemy中有.in_的反向功能吗?

时间:2019-03-27 15:11:51

标签: python sqlalchemy flask-sqlalchemy

我正在尝试通过“标签”来组织我的博客帖子,这只是一串用逗号分隔的单词。我希望能够选择一个标签,该标签仅显示在标签字符串中某处具有该标签的帖子。

例如: 帖子1 -标签:“世界政治,技术”

Post2 -tags:“技术”

选择“世界政治”-我只想要Post1。选择“技术”,我想要Post1和Post2。

我正在尝试使用.in_过滤器功能,但到目前为止,它什么也不会选择

@app.route('/index/<tag>')
def taggedindex(tag):
    posts = Post.query.filter(Post.tags.in_(tag)).all()

如果我使用

posts = Post.query.filter(tag == tag).all()

很明显,它将只选择直接匹配。

2 个答案:

答案 0 :(得分:2)

您的当前模型不在1st normal form中,因为Post.tags不是原子的,或者换句话说,它不包含其域的单个值。这使得对其进行查询更具挑战性。例如,@ blakebjorn提供的解决方案遭受误报。假设您有带有标签"nice, boat""ice, cool"的帖子1和2,并且正在查找带有标签"ice"的帖子。谓词tags LIKE '%ice%'也将与nice匹配,因此您同时获得帖子1和2:

In [4]: session.add_all([Post(tags="nice,boat"), Post(tags="ice,cool")])

In [5]: session.commit()

In [6]: session.query(Post).filter(Post.tags.like("%ice%")).all()
Out[6]: [<__main__.Post at 0x7f83b27e5b70>, <__main__.Post at 0x7f83b27e5be0>]

正确的解决方案是将标签拆分为单个标签。为了避免重复Post中的其他字段,您必须将标记拆分到它们自己的表中:

class Tag(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.Unicode, unique=True)

因为一个帖子可以有很多标签,并且一个标签可以与很多帖子相关,所以您需要一个关联表来将两者连接起来,在SQLAlchemy中也称为“辅助”表:

post_tag = db.Table(
    "post_tag",
    db.Column("post_id", db.ForeignKey("post.id"), primary_key=True),
    db.Column("tag_id", db.ForeignKey("tag.id"), primary_key=True)
)

然后您可能希望将此模型中的关系映射为many to many relationship

class Post(db.Model):
    ...
    tags = db.relationship("Tag", secondary="post_tag")

该关系可用于查询带有某些标签的帖子:

In [15]: session.query(Post).filter(Post.tags.any(name="ice")).all()
Out[15]: [<__main__.Post at 0x7fb45d48a518>]

In [24]: session.query(Post).filter(Post.tags.any(Tag.name.in_(["boat", "ice"]))).all()
Out[24]: [<__main__.Post at 0x7fb45d3d0470>, <__main__.Post at 0x7fb45d48a518>]

使用association proxy,您可以隐藏标签不仅是字符串,而且本身就是模型的事实:

class Post(db.Model):
    ...
    tag_objects = db.relationship("Tag", secondary="post_tag")
    tags = db.association_proxy("tag_objects", "name",
                                creator=lambda name: Tag(name=name))

关联代理也support basic queries

In [22]: session.query(Post).filter(Post.tags.any(Tag.name == "ice")).all()
Out[22]: [<__main__.Post at 0x7fb45d48a518>]

和(有点不直观,因为它代理标量属性):

In [23]: session.query(Post).filter(Post.tags == "ice").all()
Out[23]: [<__main__.Post at 0x7fb45d48a518>]

请注意,我们使Tag.name唯一,因此,如果使用现有标签名,则插入将失败。例如,可以使用"unique object" pattern来解决。

答案 1 :(得分:0)

使用通配符使用sql like运算符

@app.route('/index/<tag>')
def taggedindex(tag):
    posts = Post.query.filter(Post.tags.like(f'%{tag}%')).all()
    # case insensitive
    # posts = Post.query.filter(Post.tags.ilike(f'%{tag}%')).all()