Мне очень жаль, есть много вопросов по этой проблеме, но я просто не могу этого сделать. Пытаюсь буквально два дня.
У меня три модели:
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, поэтому, пожалуйста, не стреляйте.
Спасибо за ваше время заранее.
Обновление: решение, которое подходит мне лучше всего, использует наследование одной таблицы. Это сделало мою жизнь проще.
Поскольку вы создаете отношения со своей моделью 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 — похоже, это может быть источником путаницы.
Вы используете отношение «многие ко многим» с объектом ассоциации .
Я думаю, что модели в порядке, но я немного изменил их структуру.
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()
комплексное решение!
@Kerem Nayman Я думаю, вам следует изменить голосование за модель, чтобы упростить вашу работу. Удалите идентификатор столбца и сделайте внешние ключи также первичными ключами. Теперь вы можете добавить дополнительный целочисленный столбец в качестве счетчика для нескольких голосов пользователя за одни и те же комбинации план-сообщение.
Вы имеете в виду вот так? Тогда как мне запросить эту модель? i.ibb.co/s967th6/Ekran-Al-nt-s.jpg
Поскольку у вас есть сгенерированный первичный ключ, состоящий из трех столбцов, может быть только один голос с определенной комбинацией плана, поста и пользователя. Чтобы проголосовать за эту комбинацию несколько раз, требуется дополнительная сумма столбца.
В основном запрос остается прежним. Теперь вы больше не будете напрямую запрашивать идентификатор голосования, а будете фильтровать комбинации плана, публикации, пользователя и использования отношений. Затем вы можете использовать следующий запрос, чтобы узнать обо всех планах и сообщениях, за которые проголосовал пользователь. db.session.query(Plan, Post, Vote.amount).select_from(Vote).outerjoin(Plan, Post).filter(Vote.user_id == user_id).all()
Это просто предложение, вы можете остаться с вашей текущей структурой.
@Kerem Nayman Я намеревался уменьшить количество записей в базе данных, удалив повторяющиеся записи. Определение первичных ключей приводит к уникальности, которую вы также можете достичь с помощью уникальных ограничений или уникальных индексов . В sqlalchemy их также можно определить с помощью табличных аргументов. Я не могу вдаваться во все это здесь.
Спасибо, я понимаю, что вы имеете в виду. Я пытался показать этот список пользователям, но это комбинации. Кроме того, оказывается, что Flask-SQLAlchemy Pagination использует только объект BaseQuery для разбиения на страницы. Поэтому мне пришлось превратить мои запросы в моем исходном вопросе в список и использовать что-то еще для разбиения на страницы. И с этой моделью, которую вы предложили, она работает без изъянов. Поэтому я думаю, что не буду использовать эти запросы, которые дают мне комбинации Post и Plans.
Но я подумал об использовании наследования SQLAlchemy для этих двух моделей: Post и Plan. В публикации есть несколько столбцов, а в плане их около 15. Я действительно мало знаю о наследстве. Если бы я сделал это, это облегчило бы или усложнило бы мою работу со временем?
Я думаю, что наследование помогает, когда вы разбиваете модель на несколько подтипов. В случае с Post и Plan мне это не кажется хорошим вариантом. Вы можете создать более четкую структуру с помощью миксинов. Для меня это в настоящее время невозможно не заметить. Что касается нумерации страниц, этого также можно добиться с помощью функций смещения и ограничения запроса. Однако я могу понять предпочтение реализации flask-sqlalchemy.
Большое спасибо, вы помогли более чем достаточно :)
@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)
Большое спасибо. Я уверен, что где-нибудь буду использовать это решение, но в основном я использовал наследование одной таблицы для моделей Post и Plan, и это сделало мою жизнь намного проще, чем я думал.
Большое спасибо за ваше время и ответ. Однако это, к сожалению, не то, что я хочу. Прямо сейчас я могу получить доступ к голосованию за сообщения и планы. Чего я хочу, так это; Я хочу видеть ОБА сообщения и планы, за которые проголосовал пользователь, а не сам объект Vote. Теоретически это будет так: Запрос = Запрос сообщений, за которые проголосовал пользователь + Запрос планов, за которые проголосовал пользователь. Прямо сейчас я могу добиться этого с помощью двух разных запросов, я показал их в своем исходном вопросе. Но мне нужно их как-то слить, вот в чем проблема. Может быть, я делаю что-то не так в своих моделях, я открыт для предложений.