英文:
SQLAlchemy relationship() via intermediary table
问题
我在SQLAlchemy中定义方法以通过一个中介表检索相关记录方面遇到了困难。
考虑以下架构:
- 用户可以创建多个帖子,每个帖子属于一个用户
- 每个帖子可以有多条评论,每条评论属于一个帖子
我想要的是,对于给定的用户实例,能够检索该用户实例的所有帖子中的所有评论。
我已经这样设置了:
from sqlalchemy import ForeignKey
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship
class Base(DeclarativeBase):
id: Mapped[int] = mapped_column(primary_key=True)
# 定义模型类
class User(Base):
__tablename__ = "users"
name: Mapped[str] = mapped_column()
posts: Mapped[list["Post"]] = relationship(back_populates="user")
def __repr__(self) -> str:
return f"(<{__class__.__name__}> name: {self.name})"
class Post(Base):
__tablename__ = "posts"
title: Mapped[str] = mapped_column()
user_id: Mapped[int] = mapped_column(ForeignKey("users.id"))
user: Mapped["User"] = relationship(back_populates="posts")
comments: Mapped[list["Comment"]] = relationship(back_populates="post")
def __repr__(self) -> str:
return f"(<{__class__.__name__}> title: {self.title})"
class Comment(Base):
__tablename__ = "comments"
body: Mapped[str] = mapped_column()
post_id: Mapped[int] = mapped_column(ForeignKey("posts.id"))
post: Mapped["Post"] = relationship(back_populates="comments")
def __repr__(self) -> str:
return f"(<{__class__.__name__}> body: {self.body})"
如果我创建这些模型的几个实例,您可以看到它们之间的关系:
# 创建实例
user = User(name="greta")
post_1 = Post(title="First post", user=user)
post_2 = Post(title="Second post", user=user)
comment_1 = Comment(body="yeah wotever", post=post_1)
comment_2 = Comment(body="lol good one", post=post_1)
comment_3 = Comment(body="lmfao", post=post_2)
# 显示所有帖子及其评论
print(user)
for post in user.posts:
print(f" └── {post}")
for comment in post.comments:
print(f" └── {comment}")
(<User> name: greta)
└── (<Post> title: First post)
└── (<Comment> body: yeah wotever)
└── (<Comment> body: lol good one)
└── (<Post> title: Second post)
└── (<Comment> body: lmfao)
我不确定如何使用relationship()
来在User
类中定义一个all_comments()
方法,该方法将返回该用户实例的所有帖子中的所有评论。您可以指导我吗?
英文:
I am struggling to define methods in SQLAlchemy to retrieve related records via an intermediary table.
Consider the following schema:
- Users can create multiple posts, each post belongs to 1 user
- Each post can have multiple comments on it, with each comment belonging to 1 post
What I want is to be able to, for a given user instance, retrieve all of the comments from all of their posts.
I have set this up as follows:
from sqlalchemy import ForeignKey
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship
class Base(DeclarativeBase):
id: Mapped[int] = mapped_column(primary_key=True)
# define model classes
class User(Base):
__tablename__ = "users"
name: Mapped[str] = mapped_column()
posts: Mapped[list["Post"]] = relationship(back_populates="user")
def __repr__(self) -> str:
return f"(<{__class__.__name__}> name: {self.name})"
class Post(Base):
__tablename__ = "posts"
title: Mapped[str] = mapped_column()
user_id: Mapped[int] = mapped_column(ForeignKey("users.id"))
user: Mapped["User"] = relationship(back_populates="posts")
comments: Mapped[list["Comment"]] = relationship(back_populates="post")
def __repr__(self) -> str:
return f"(<{__class__.__name__}> title: {self.title})"
class Comment(Base):
__tablename__ = "comments"
body: Mapped[str] = mapped_column()
post_id: Mapped[int] = mapped_column(ForeignKey("posts.id"))
post: Mapped["Post"] = relationship(back_populates="comments")
def __repr__(self) -> str:
return f"(<{__class__.__name__}> body: {self.body})"
If I create a few instances of these models, you can see how things are related:
# create instances
user = User(name="greta")
post_1 = Post(title="First post", user=user)
post_2 = Post(title="Second post", user=user)
comment_1 = Comment(body="yeah wotever", post=post_1)
comment_2 = Comment(body="lol good one", post=post_1)
comment_3 = Comment(body="lmfao", post=post_2)
# show all posts, and their comments
print(user)
for post in user.posts:
print(f" └── {post}")
for comment in post.comments:
print(f" └── {comment}")
(<User> name: greta)
└── (<Post> title: First post)
└── (<Comment> body: yeah wotever)
└── (<Comment> body: lol good one)
└── (<Post> title: Second post)
└── (<Comment> body: lmfao)
I am unsure of how to use relationship()
to define a method all_comments()
in the User
class, which would return a list of all of the comments across all of a user
instance's posts
.
Can anyone point me in the right direction?
答案1
得分: 1
使用你的次要表posts
,你可以在relationship
中使用secondaryjoin
和primaryjoin
来获得你想要的结果。
这样你不需要创建一个名为all_comments
的方法,你可以直接使用user.comments
来获取评论,同时反过来,(请查看编辑)comment.user
可以获取用户信息。
你可能需要调整连接条件,从我初步的测试来看,这似乎可以满足你的要求。
我已经创建了两个不同的用户,每个用户有不同的帖子和评论,所以你可以看到区别。
from sqlalchemy import ForeignKey, create_engine, select
from sqlalchemy.orm import DeclarativeBase, mapped_column, relationship, Session
# 在这里是你的类定义,省略了具体内容
engine = create_engine("sqlite:///temp.db")
# 创建数据库表
Base.metadata.create_all(engine)
# 创建会话并插入数据
with Session(engine) as session, session.begin():
# 创建用户和相关内容的示例,省略了具体内容
# 查询并打印结果
with Session(engine) as session:
statement = select(User)
for user in session.scalars(statement):
print(user, user.comments)
输出
(<User> name: greta) [(<Comment> body: yeah wotever), (<Comment> body: lol good one), (<Comment> body: lmfao)]
(<User> name: not greta) [(<Comment> body: wotever), (<Comment> body: good one)]
编辑:在这个实现中,反向关系“从评论获取用户”的部分似乎有问题,一个评论有多个用户,我不确定我哪里出错了,但如果你只想要关系“获取用户的所有评论”,那么这个方法有效。
英文:
Using your secondary table posts
, you can use secondaryjoin
and primaryjoin
in relationship
to get what you want.
This way you needn't create a method all_comments
, you can just do user.comments
and get the comments, <strike>also the other way round, comment.user
gives you the user.</strike> (see edit)
You probably want to tweak the join conditions, from my rudimentary testing this seems to get where you want to be.
I have created two users, which different set of posts and comments so you can see the difference.
from sqlalchemy import ForeignKey, create_engine, select
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship, Session
class Base(DeclarativeBase):
id: Mapped[int] = mapped_column(primary_key=True)
class User(Base):
__tablename__ = "users"
name: Mapped[str] = mapped_column()
posts: Mapped[list["Post"]] = relationship(back_populates="user")
comments: Mapped[list["Comment"]] = relationship(
back_populates="user",
secondary="posts",
primaryjoin="User.id == Post.user_id",
secondaryjoin="Comment.post_id == Post.id",
viewonly=True,
)
def __repr__(self) -> str:
return f"(<{__class__.__name__}> name: {self.name})"
class Post(Base):
__tablename__ = "posts"
title: Mapped[str] = mapped_column()
user_id: Mapped[int] = mapped_column(ForeignKey("users.id"))
user: Mapped["User"] = relationship(back_populates="posts")
comments: Mapped[list["Comment"]] = relationship(back_populates="post")
def __repr__(self) -> str:
return f"(<{__class__.__name__}> title: {self.title})"
class Comment(Base):
__tablename__ = "comments"
body: Mapped[str] = mapped_column()
post_id: Mapped[int] = mapped_column(ForeignKey("posts.id"))
post: Mapped["Post"] = relationship(back_populates="comments")
user: Mapped["User"] = relationship(
back_populates="comments",
secondary="posts",
primaryjoin="User.id == Post.user_id",
secondaryjoin="Comment.post_id == Post.id",
viewonly=True,
uselist=False,
)
def __repr__(self) -> str:
return f"(<{__class__.__name__}> body: {self.body})"
engine = create_engine("sqlite:///temp.db")
Base.metadata.create_all(engine)
with Session(engine) as session, session.begin():
user = User(name="greta")
post_1 = Post(title="First post", user=user)
post_2 = Post(title="Second post", user=user)
comment_1 = Comment(body="yeah wotever", post=post_1)
comment_2 = Comment(body="lol good one", post=post_1)
comment_3 = Comment(body="lmfao", post=post_2)
session.add_all((user, post_1, post_2, comment_1, comment_2, comment_3))
user = User(name="not greta")
post_1 = Post(title="Third post", user=user)
post_2 = Post(title="Fourth post", user=user)
comment_1 = Comment(body="wotever", post=post_1)
comment_2 = Comment(body="good one", post=post_1)
session.add_all((user, post_1, post_2, comment_1, comment_2))
with Session(engine) as session:
statement = select(User)
for user in session.scalars(statement):
print(user, user.comments)
Output
(<User> name: greta) [(<Comment> body: yeah wotever), (<Comment> body: lol good one), (<Comment> body: lmfao)]
(<User> name: not greta) [(<Comment> body: wotever), (<Comment> body: good one)]
Edit: The reverse relation "get user from a comment" seems to be bugged in this implementation, one comment has more than one user, I am not sure where I went wrong, but if all you want is the relation "get all comments for a user" then this works.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论