繁体   English   中英

多个外键上的SQLAlchemy Double Inner Join

[英]SQLAlchemy Double Inner Join on multiple foreign keys

请参阅底部的更新

我有三节课。 我们称它们为PostPostVersionTag (这是针对Web应用程序中的内部版本控制系统的,也许类似于StackOverflow,尽管我不确定它们的实现策略)。 我使用git中的术语来理解它。 这些是针对该问题的类的高度简化版本:

class Post(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    author_id = db.Column(db.Integer, db.ForeignKey("user.id"))
    author = db.relationship("User", backref="posts")
    head_id = db.Column(db.Integer, db.ForeignKey("post_version.id"))
    HEAD = db.relationship("PostVersion", foreign_keys=[head_id])
    added = db.Column(db.DateTime, default=datetime.utcnow)

class PostVersion(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    editor_id = db.Column(db.Integer, db.ForeignKey("user.id"))
    editor = db.relationship("User")
    previous_id = db.Column(db.Integer, db.ForeignKey("post_version.id"), default=None)
    previous = db.relationship("PostVersion")
    pointer_id = db.Column(db.Integer, db.ForeignKey("post.id"))
    pointer = db.relationship("Post", foreign_keys=[pointer_id])
    post = db.Column(db.Text)
    modified = db.Column(db.DateTime, default=datetime.utcnow)
    tag_1_id = db.Column(db.Integer, db.ForeignKey("tag.id"), default=None)
    tag_2_id = db.Column(db.Integer, db.ForeignKey("tag.id"), default=None)
    tag_3_id = db.Column(db.Integer, db.ForeignKey("tag.id"), default=None)
    tag_4_id = db.Column(db.Integer, db.ForeignKey("tag.id"), default=None)
    tag_5_id = db.Column(db.Integer, db.ForeignKey("tag.id"), default=None)
    tag_1 = db.relationship("Tag", foreign_keys=[tag_1_id])
    tag_2 = db.relationship("Tag", foreign_keys=[tag_2_id])
    tag_3 = db.relationship("Tag", foreign_keys=[tag_3_id])
    tag_4 = db.relationship("Tag", foreign_keys=[tag_4_id])
    tag_5 = db.relationship("Tag", foreign_keys=[tag_5_id])

class Tag(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    tag = db.Column(db.String(128))

为了发布新帖子,我创建了一个Post和一个Post.head_id指向的初始PostVersion 每次进行编辑时, PostVersion创建一个指向先前PostVersion的新PostVersion ,并将Post.head_id重置为指向新PostVersion 要将发布版本重置为较早的版本-到目前为止,我还没有做完,但是复制先前的版本或者只是将指针重置为先前的版本似乎都是微不足道的。

我的问题是这样的:我如何在PostTag之间写一个关系

  1. Post.tags将是当前PostVersion包含的所有标签的列表,并且
  2. Tag.posts是当前具有该特定标签的所有Post的列表吗?

第一个条件似乎很容易,一种简单的方法

def get_tags(self):
    t = []
    if self.HEAD.tag_1:
        t.append(self.HEAD.tag_1)
    if self.HEAD.tag_2:
        t.append(self.HEAD.tag_2)
    if self.HEAD.tag_3:
        t.append(self.HEAD.tag_3)
    if self.HEAD.tag_4:
        t.append(self.HEAD.tag_4)
    if self.HEAD.tag_5:
        t.append(self.HEAD.tag_5)
    return t

现在就可以了,但是第二个条件现在对我来说几乎是棘手的。 我目前使用的标签可憎的方法,我查询所有的PostVersion的使用的标签or_过滤器:

def get_posts(self):
    edits = PostVersion.query.filter(or_(
         PostVersion.tag_1_id==self.id,
         PostVersion.tag_2_id==self.id,
         PostVersion.tag_3_id==self.id,
         PostVersion.tag_4_id==self.id,
         PostVersion.tag_5_id==self.id,
         ).order_by(PostVersion.modified.desc()).all()
    posts = []
    for e in edits:
        if self in e.pointer.get_tags() and e.pointer not in posts:
            posts.append(e.pointer)
    return posts

这效率极低,我无法对结果进行分页。

我知道这将是从二级加盟Post ,以TagTagPost通过PostVersion ,但它必须是上或二级加盟,我不知道如何甚至开始写。

回顾我的代码,我开始想知道为什么其中一些关系需要定义foreign_keys参数,而另一些则不需要。 我认为这与定义它们的位置有关(是否紧随FK id列),并注意到有一个foreign_keys的列表,我在想这就是我定义它的方式。 但是我不确定如何实现这一目标。

我现在还在想是否可以通过配置良好的关系PostVersion上的pointer_id 但是,这与问题无关(尽管循环引用确实会引起头痛)。

作为参考,我正在使用Flask-SQLAlchemy,Flask-migrate和MariaDB。 我非常关注Miguel Grinberg的Flask Megatutorial

任何帮助或建议都是天赐之物。

UPDATE

我设计了下面的MySQL查询工作 ,现在我需要把它翻译成SQLAlchemy的:

SELECT
    post.id, tag.tag 
FROM
    post
INNER JOIN
    post_version
ON
    post.head_id=post_version.id
INNER JOIN 
    tag
ON 
    post_version.tag_1_id=tag.id OR
    post_version.tag_2_id=tag.id OR
    post_version.tag_3_id=tag.id OR
    post_version.tag_4_id=tag.id OR
    post_version.tag_5_id=tag.id OR
WHERE
    tag.tag="<tag name>";

您可以更改数据库设计,还是必须使应用程序在无法更改的数据库上工作? 如果是后者,我无能为力。 如果可以更改设计,则应这样进行:

  1. 将PostVersions的链接链替换为从Post到PostVersions的一对多关系。 您的“ Post”类最终将与与该Post相关的所有PostVersion实例具有“版本”关系。

  2. 使用附加的关联表将tag_id成员替换为多对多关系。

这两种方法在SQLAlchemy文档中都有很好的解释。 确保从最少的代码开始,在小型非Flask命令行程序中进行测试。 一旦掌握了基本功能,就可以将概念转移到更复杂的类中。 之后,再次问自己最初的问题。 答案将更加容易。

我自己解决了这个问题,实际上只包含了在主数据库中使用or_定义主数据库和辅助数据库的连接:

posts = db.relationship("Post", secondary="post_version",
    primaryjoin="or_(Tag.id==post_version.c.tag_1_id,"
    "Tag.id==post_version.c.tag_2_id,"
    "Tag.id==post_version.c.tag_3_id,"
    "Tag.id==post_version.c.tag_4_id,"
    "Tag.id==post_version.c.tag_5_id)",
    secondaryjoin="Annotation.head_id==post_version.c.id",
    lazy="dynamic")

如您所见,我混合使用表名和类名。 我将在尝试使答案更加规则时更新答案。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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