SQLAlchemy Double Inner Join на нескольких внешних ключах - PullRequest
0 голосов
/ 04 октября 2018

Пожалуйста, смотрите обновление внизу

У меня есть три класса.Давайте назовем их Post, PostVersion и Tag.(Это для внутренней системы контроля версий в веб-приложении, возможно, похожей на 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))

Чтобы создать новый пост, я создаю и пост, и начальный PostVersion, на который указывает Post.head_id.Каждый раз, когда выполняется редактирование, создается новый PostVersion, указывающий на предыдущий PostVersion, и Post.head_id сбрасывается, чтобы указывать на новый PostVersion.Чтобы переустановить пост-версию на более раннюю версию - ну, я не дошел до этого, но кажется тривиальным, скопировать предыдущую версию или просто сбросить указатель на предыдущую версию.

Мой вопрос такойТем не менее: как я могу написать отношение между Post и Tag таким образом, чтобы

  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

пока что справляется, но второе условие для меня сейчас почти неразрешимо.В настоящее время я использую противный метод в Tag, где я запрашиваю все 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 до Tag или Tag до Post до PostVersion, но это должно быть вторичное соединение для или, и у меня естьПонятия не имею, как вообще начать писать это.

Оглядываясь назад на мой код, я начинаю задумываться, почему некоторые из этих отношений требуют определения параметра foreign_keys, а другие - нет.Я думаю, что это связано с тем, где они определены (сразу после столбца идентификатора FK или нет) и замечая, что есть список для foreign_keys, я думаю это , как я мог бы его определить,Но я не уверен, как добиться этого.

Мне также интересно теперь, могу ли я обойтись без pointer_id на PostVersion с хорошо настроенными отношениями.Это, однако, не имеет отношения к вопросу (хотя циклическая ссылка вызывает головные боли).

Для справки, я использую Flask-SQLAlchemy, Flask-migrate и MariaDB.Я очень следую Мегуторию Мигеля Гринберга "Настой" .

Любая помощь или совет будут находкой.

ОБНОВЛЕНИЕ

Я разработал следующий запрос 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>";

Ответы [ 2 ]

0 голосов
/ 05 октября 2018

Я решил проблему самостоятельно, и она на самом деле состоит из определения первичного и вторичного соединения с помощью 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")

Как вы можете видеть, я смешиваю имена таблиц и классов.Я обновлю ответ, когда буду экспериментировать, чтобы сделать его более регулярным.

0 голосов
/ 04 октября 2018

Можете ли вы изменить дизайн базы данных, или вы должны заставить свое приложение работать на БД, которую вы не можете изменить?Если последнее, я не могу вам помочь.Если вы можете изменить дизайн, вы должны сделать это следующим образом:

  1. Замените связанную цепочку PostVersions на отношение один-ко-многим от Post к PostVersions.Ваш класс «Post» в конечном итоге будет иметь «версии» отношения ко всем экземплярам PostVersion, относящимся к этой публикации.

  2. Замените элементы tag_id отношением «многие ко многим», используятаблица дополнительных ассоциаций.

Оба метода хорошо описаны в документации по SQLAlchemy.Обязательно начните с минимального кода, тестирование в небольших не командной программе Flask.Как только вы отключите базовую функциональность, перенесите эту концепцию в ваши более сложные классы.После этого снова задайте себе исходные вопросы.Ответы придут намного легче.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...