Flask-SQLAlchemy Как отфильтровать две разные модели на основе отношения «один ко многим»?

Мне очень жаль, есть много вопросов по этой проблеме, но я просто не могу этого сделать. Пытаюсь буквально два дня.

У меня три модели:

class Post(db.Model):
    ### other columns
    oylar = db.relationship('Vote',backref='post_oylar',lazy='dynamic', cascade = "all, delete")

class Plan(db.Model):
    ### other columns
    oylar = db.relationship('Vote',backref='oylar',lazy='dynamic', cascade = "all, delete")

class Vote(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.Integer, db.ForeignKey("user.id"))
    post_id = db.Column(db.Integer, db.ForeignKey("post.id"))
    plani_id = db.Column(db.Integer, db.ForeignKey("plan.id"))

Мне нужно запросить Plan и Post на основе Vote user_id.

Допустим, у меня есть этот пользователь с идентификатором 1. По сути, я хочу увидеть, за какой план и публикацию проголосовал этот пользователь.

Я перепробовал так много запросов, что даже не могу придумать, какой из них написать здесь. Но чтобы было понятнее, позвольте мне показать вам, что я имею в виду на этом примере:

post = db.session.query(Post).join(Vote).filter(Vote.user_id == 1)
plan = db.session.query(Plan).join(Vote).filter(Vote.user_id == 1)
final_query = #there should be something to merge these two queries.

Это был кошмар для меня. Я не знаю синтаксиса SQL, и я начал учиться с SQL-Alchemy, поэтому, пожалуйста, не стреляйте.

Спасибо за ваше время заранее.

Обновление: решение, которое подходит мне лучше всего, использует наследование одной таблицы. Это сделало мою жизнь проще.

Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
1
0
643
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Поскольку вы создаете отношения со своей моделью Vote, я бы посоветовал вам также установить отношения между Vote и User:

# Modify your class to include the following line
class Vote(db.Model):
    ...
    user_id = db.Column(db.Integer, db.ForeignKey("user.id"))
    user = db.Relationship("User", backref = "vote") #  <--- This assumes you have a `User` model
    ...

Теперь, когда эта связь установлена, я бы получил доступ к этим дочерним объектам из отношения user-vote:

# 1. Get your user
my_user_id = 1
my_user = db.session.query(Post).get(my_user_id)

# Get the vote for this user --> this will return a list
# Note: there may be many votes, I'll use the first index as a demonstrative example.
my_votes = my_user.vote

# If there aren't any votes
if not my_votes:
    raise Exception(f'User id = {my_user.id} has no votes')

# 2. Get the first vote 
my_vote = my_votes[0]

# 3. Get the children from that vote
# These are the keywords specified in the 'backref' parameter
my_post = my_vote.post_oylar
my_plan = my_vote.oylar

# Finally, print them
print('post:', my_post.__dict__)
print('plan:', my_plan.__dict__)

В качестве примечания я бы посоветовал вам провести дополнительное исследование того, как лучше всего устанавливать отношения в ваших моделях ORM — похоже, это может быть источником путаницы.

Большое спасибо за ваше время и ответ. Однако это, к сожалению, не то, что я хочу. Прямо сейчас я могу получить доступ к голосованию за сообщения и планы. Чего я хочу, так это; Я хочу видеть ОБА сообщения и планы, за которые проголосовал пользователь, а не сам объект Vote. Теоретически это будет так: Запрос = Запрос сообщений, за которые проголосовал пользователь + Запрос планов, за которые проголосовал пользователь. Прямо сейчас я могу добиться этого с помощью двух разных запросов, я показал их в своем исходном вопросе. Но мне нужно их как-то слить, вот в чем проблема. Может быть, я делаю что-то не так в своих моделях, я открыт для предложений.

Kerem Nayman 27.12.2020 12:15
Ответ принят как подходящий

Вы используете отношение «многие ко многим» с объектом ассоциации .
Я думаю, что модели в порядке, но я немного изменил их структуру.

class Vote(db.Model):
    __tablename__ = 'votes'

    id = db.Column(db.Integer, primary_key=True)

    # Foreign Keys
    plan_id = db.Column(db.Integer, db.ForeignKey('plans.id'))
    post_id = db.Column(db.Integer, db.ForeignKey('posts.id'))
    user_id = db.Column(db.Integer, db.ForeignKey('users.id'))

    # Relationships
    # If a user, post or plan is deleted, referencing votes are also removed.
    # The associated plans and posts are loaded with the vote using a JOIN statement.
    plan = db.relationship('Plan',
        backref=db.backref('votes', cascade='all, delete-orphan'),
        lazy='joined')
    post = db.relationship('Post',
        backref=db.backref('votes', cascade='all, delete-orphan'),
        lazy='joined')
    user = db.relationship('User',
        backref=db.backref('votes', cascade='all, delete-orphan'))

    def __repr__(self):
        return f'Vote(plan_id = {self.plan_id}, post_id = {self.post_id}, user_id = {self.user_id})'

class Plan(db.Model):
    __tablename__ = 'plans'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(255), index=True, nullable=False)

    def __repr__(self):
        return f'Plan(name = {self.name})'

class Post(db.Model):
    __tablename__ = 'posts'
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(255), index=True, nullable=False)

    def __repr__(self):
        return f'Post(title = {self.title})'

class User(db.Model):
    __tablename__ = 'users'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(64), index=True, nullable=False)

    # All plans and posts that have been voted for can be reached via jointable.
    # CAUTION, objects can be added to the lists, but because of the viewonly flag
    # they are not transferred to the database during a commit.
    # An inconsistent state is therefore possible.
    #
    # _voted_plans = db.relationship(
    #     'Plan',
    #     secondary='votes',
    #     backref=db.backref('users_voted', viewonly=True),
    #     viewonly=True
    # )
    #
    # _voted_posts = db.relationship(
    #     'Post',
    #     secondary='votes',
    #     backref=db.backref('users_voted', viewonly=True),
    #     viewonly=True
    # )

    def __repr__(self):
        return f'User(name = {self.name})'

С одной стороны, вы можете использовать метод ORM, используя виртуальные отношения для список всех связанных моделей. В этом случае ваши голоса за столом действуют как объединенные и связанные объекты классов Post и Plan могут быть запрошены напрямую через отношения.

plan_post_pairs = [(vote.plan, vote.post) for vote in Vote.query.filter_by(user_id=user_id).all()]

В качестве альтернативы вы также можете написать свой собственный запрос. В качестве примера я приведу вам оператор SQL SELECT и оператор JOIN. Я также прошу указать идентификатор голосования, чтобы перечислить дублирующиеся голоса пользователя по одним и тем же комбинациям план-сообщение.

# SELECT stmt
items = db.session.query(       # SELECT ... FROM ...
    Vote.id, Plan, Post
).filter(                       # WHERE ...
    Vote.plan_id == Plan.id,    # ... AND
    Vote.post_id == Post.id,    # ... AND
    Vote.user_id == user_id     # ...
).all()
# JOIN stmt
items = db.session.query(Vote.id, Plan, Post)\      # SELECT ...
    .select_from(Vote)\                             # FROM ...
    .outerjoin(Plan, Post)\                         # LEFT OUTER JOIN ... ON ...
    .filter(Vote.user_id == user_id)\               # WHERE ...
    .all()

Следующий пример немного сложнее и может помочь вам в будущем. Запрашиваются все комбинации план-публикация пользователя, включая количество голосов за соответствующую пару от этого пользователя.

subquery = db.session\
    .query(Vote.plan_id, Vote.post_id, db.func.count('*').label('count'))\
    .group_by(Vote.plan_id, Vote.post_id)\
    .filter(Vote.user_id == user_id)\
    .subquery()

items = db.session\
    .query(Plan, Post, subquery.c.count)\
    .select_from(subquery)\
    .outerjoin(Plan, subquery.c.plan_id == Plan.id)\
    .outerjoin(Post, subquery.c.post_id == Post.id)\
    .all()

комплексное решение!

Yaakov Bressler 27.12.2020 17:07

@Kerem Nayman Я думаю, вам следует изменить голосование за модель, чтобы упростить вашу работу. Удалите идентификатор столбца и сделайте внешние ключи также первичными ключами. Теперь вы можете добавить дополнительный целочисленный столбец в качестве счетчика для нескольких голосов пользователя за одни и те же комбинации план-сообщение.

Detlef 29.12.2020 14:30

Вы имеете в виду вот так? Тогда как мне запросить эту модель? i.ibb.co/s967th6/Ekran-Al-nt-s.jpg

Kerem Nayman 29.12.2020 16:20

Поскольку у вас есть сгенерированный первичный ключ, состоящий из трех столбцов, может быть только один голос с определенной комбинацией плана, поста и пользователя. Чтобы проголосовать за эту комбинацию несколько раз, требуется дополнительная сумма столбца.

Detlef 29.12.2020 17:45

В основном запрос остается прежним. Теперь вы больше не будете напрямую запрашивать идентификатор голосования, а будете фильтровать комбинации плана, публикации, пользователя и использования отношений. Затем вы можете использовать следующий запрос, чтобы узнать обо всех планах и сообщениях, за которые проголосовал пользователь. db.session.query(Plan, Post, Vote.amount).select_from(Vote).outerjoin(Plan, Post).filter(Vote.user_id == user_id).all() Это просто предложение, вы можете остаться с вашей текущей структурой.

Detlef 29.12.2020 17:45

@Kerem Nayman Я намеревался уменьшить количество записей в базе данных, удалив повторяющиеся записи. Определение первичных ключей приводит к уникальности, которую вы также можете достичь с помощью уникальных ограничений или уникальных индексов . В sqlalchemy их также можно определить с помощью табличных аргументов. Я не могу вдаваться во все это здесь.

Detlef 29.12.2020 23:19

Спасибо, я понимаю, что вы имеете в виду. Я пытался показать этот список пользователям, но это комбинации. Кроме того, оказывается, что Flask-SQLAlchemy Pagination использует только объект BaseQuery для разбиения на страницы. Поэтому мне пришлось превратить мои запросы в моем исходном вопросе в список и использовать что-то еще для разбиения на страницы. И с этой моделью, которую вы предложили, она работает без изъянов. Поэтому я думаю, что не буду использовать эти запросы, которые дают мне комбинации Post и Plans.

Kerem Nayman 30.12.2020 08:27

Но я подумал об использовании наследования SQLAlchemy для этих двух моделей: Post и Plan. В публикации есть несколько столбцов, а в плане их около 15. Я действительно мало знаю о наследстве. Если бы я сделал это, это облегчило бы или усложнило бы мою работу со временем?

Kerem Nayman 30.12.2020 08:30

Я думаю, что наследование помогает, когда вы разбиваете модель на несколько подтипов. В случае с Post и Plan мне это не кажется хорошим вариантом. Вы можете создать более четкую структуру с помощью миксинов. Для меня это в настоящее время невозможно не заметить. Что касается нумерации страниц, этого также можно добиться с помощью функций смещения и ограничения запроса. Однако я могу понять предпочтение реализации flask-sqlalchemy.

Detlef 30.12.2020 14:31

Большое спасибо, вы помогли более чем достаточно :)

Kerem Nayman 30.12.2020 17:12

@KeremNayman Извините, я решил проблему с нумерацией страниц. Ты все еще интересуешься? post_plan_amount = Vote.query.filter_by(user_id = user_id).join(Post, Plan).with_entities(Post, Plan, Vote.amount).paginate(page, per_page)

Detlef 16.02.2021 03:33

Большое спасибо. Я уверен, что где-нибудь буду использовать это решение, но в основном я использовал наследование одной таблицы для моделей Post и Plan, и это сделало мою жизнь намного проще, чем я думал.

Kerem Nayman 17.02.2021 19:29

Другие вопросы по теме