共计 14950 个字符,预计需要花费 38 分钟才能阅读完成。
使用 SQLAlchemy ORM 构建具有有意义关系的数据模型。创建一对一、一对多、多对一和多对多关系。
所有 Python 开发人员都可以从 SQLAlchemy 中获益。无论您是在寻找更好的方法来管理数据库连接,还是为您的应用程序构建 ORM 数据层,我们都没有理由 pip install sqlalchemy 从 Python 词汇中省略“”。
从事大型应用程序的工程师绝大多数更喜欢通过 ORM 处理数据,而不是原始 SQL。对于那些有大量数据背景的人(比如我自己)来说,将 SQL 隐藏在 Python 对象后面的抽象可能会令人反感。为什么我们需要外键来执行两个表之间的 JOIN?为什么从事大型软件工作的工程师似乎过度使用“一对多”与“多对多”关系等术语,而 SQL 本身没有这样的术语?如果你也有这种感觉,那么你就有很好的同伴了。
几年后,我发现 ORM 确实减少了构建应用程序的工作量,而且不仅仅是“害怕 SQL”的人的拐杖。我们通过将敏感数据事务处理为可重现的代码模式来节省大量时间,但与我们在安全性和完整性方面获得的收益相比,这种好处相形见绌。ORM 不会编写破坏性的 SQL 查询;人们确实如此。
所以,是的。令人恼火的是,编写 ORM 的工程师与了解底层 SQL 的工程师使用不同的行话,并且设置 ORM 需要大量的前期工作,这令人恼火,但这是值得的。今天,我们通过学习如何在 SQLAlchemy 中定义表关系来解决 ORM 开发中最困难的部分。
设置一些数据模型
我们已经在 上一篇 文章中介绍了 SQLAlchemy 数据模型,因此我将跳过更详细的细节。如果您通过疯狂谷歌搜索有关 SQLAlchemy 的问题到达这里,您可能应该了解什么是模型以及如何定义它们。
我们将创建一些模型来演示如何在它们之间创建 SQL 关系。本着博客的精神,我们将为 User、Post 和创建模型 Comment:
"""声明模型和关系。"""
from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer, String, Text
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship
from sqlalchemy.sql import func
from database import engine
Base = declarative_base()
class User(Base):
"""用户帐号。"""
__tablename__ = "user"
id = Column(Integer, primary_key=True, autoincrement="auto")
username = Column(String(255), unique=True, nullable=False)
password = Column(Text, nullable=False)
email = Column(String(255), unique=True, nullable=False)
first_name = Column(String(255))
last_name = Column(String(255))
bio = Column(Text)
avatar_url = Column(Text)
role = Column(String(255))
last_seen = Column(DateTime)
created_at = Column(DateTime, server_default=func.now())
updated_at = Column(DateTime, onupdate=func.now())
def __repr__(self):
return f""
class Comment(Base):
"""用户对博客文章生成的评论。"""
__tablename__ = "comment"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer)
post_id = Column(Integer, index=True)
body = Column(Text)
upvotes = Column(Integer, default=1)
removed = Column(Boolean, default=False)
created_at = Column(DateTime, server_default=func.now())
def __repr__(self):
return f""
class Post(Base):
"""博客文章。"""
__tablename__ = "post"
id = Column(Integer, primary_key=True, index=True)
author_id = Column(Integer)
slug = Column(String(255), nullable=False, unique=True)
title = Column(String(255), nullable=False)
summary = Column(String(400))
feature_image = Column(String(300))
body = Column(Text)
status = Column(String(255), nullable=False, default="unpublished")
created_at = Column(DateTime, server_default=func.now())
updated_at = Column(DateTime, server_default=func.now())
def __repr__(self):
return f""
Base.metadata.create_all(engine)
一对多关系
一对多(或多对一)关系是最常见的数据库关系类型。如何应用这种关系的一个永恒的例子是客户和订单之间的业务关系。单个客户有多个订单,但订单没有多个客户,因此该术语
使用我们的博客示例,让我们看看对于拥有多个帖子或具有多个评论的帖子的作者来说,一对多关系可能是什么样子:
...
from sqlalchemy.orm import relationship
class User(Base):
"""用户帐号。"""
__tablename__ = "user"
id = Column(Integer, primary_key=True, autoincrement="auto")
username = Column(String(255), unique=True, nullable=False)
password = Column(Text, nullable=False)
email = Column(String(255), unique=True, nullable=False)
first_name = Column(String(255))
last_name = Column(String(255))
bio = Column(Text)
avatar_url = Column(Text)
role = Column(String(255))
last_seen = Column(DateTime)
created_at = Column(DateTime, server_default=func.now())
updated_at = Column(DateTime, onupdate=func.now())
def __repr__(self):
return f""
class Comment(Base):
"""用户对博客文章生成的评论。"""
__tablename__ = "comment"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("user.id")) # FK added
post_id = Column(Integer, ForeignKey("post.id"), index=True) # FK added
body = Column(Text)
upvotes = Column(Integer, default=1)
removed = Column(Boolean, default=False)
created_at = Column(DateTime, server_default=func.now())
# Relationships
user = relationship("User")
def __repr__(self):
return f""
class Post(Base):
"""博客文章 / 文章。"""
__tablename__ = "post"
id = Column(Integer, primary_key=True, index=True)
author_id = Column(Integer, ForeignKey("user.id")) # FK added
slug = Column(String(255), nullable=False, unique=True)
title = Column(String(255), nullable=False)
summary = Column(String(400))
feature_image = Column(String(300))
body = Column(Text)
status = Column(String(255), nullable=False, default="unpublished")
created_at = Column(DateTime, server_default=func.now())
updated_at = Column(DateTime, server_default=func.now())
# 人际关系
author = relationship("User")
comments = relationship("Comment")
def __repr__(self):
return f""
我们在模型中添加了两个关键的附加功能,乍一看可能很难发现。首先,我们将一些属性(列)设置为外键(如果您熟悉 SQL,那么应该很好地转到这里)。外键是列的属性;当存在外键时,我们说这个特定的列表示表之间的关系:一个表中最常见的项目“属于”另一个表的项目,例如当客户“拥有”订单时,或者当用户“拥有”订单时帖子。在我们的示例中,我们说每个帖子都有一个由属性指定的作者(用户)author_id,如下所示:
定义两个数据模型之间的外键关系:
...
author_id = Column(Integer, ForeignKey("user.id"))
...
我们可以将用户表和帖子表之间的数据结合起来,这样获取一个表就可以让我们获得有关另一个表的信息。
这里的另一个新概念是关系。关系是对外键的补充,是告诉我们的应用程序(而不是数据库)我们正在两个模型之间建立关系的一种方式。注意我们的外键的值是多少 ’user.id’。user 是我们表的表名 User。将其与我们传递给关系的值进行比较,该值是 ”User”:目标数据模型的类名(不是表名!)。
定义两个数据模型之间的关系:
...
author = relationship("User")
...
外键告诉 SQL 我们正在构建哪些关系,关系告诉我们的应用程序我们正在构建哪些关系。我们需要两者兼而有之。
所有这一切的重点是能够在我们的应用程序中轻松执行 JOIN。使用 ORM 时,我们无法说“将此模型与该模型连接起来”,因为我们的应用程序不知道要连接哪些列。当我们的模型中指定了关系时,我们可以执行诸如将两个表连接在一起之类的操作,而无需指定任何进一步的细节:SQLAlchemy 将通过查看我们在数据模型中设置的内容(由外键强制执行)来知道如何连接表 / 模型和我们设定的关系)。我们实际上只是减轻了处理数据相关逻辑的负担,同时通过预先定义关系来创建应用程序的业务逻辑。
如果表尚不存在,SQLAlchemy 仅从数据模型创建表。换句话说,如果我们第一次运行应用程序时存在错误的关系,则第二次运行应用程序时错误消息将持续存在,即使我们认为已经解决了问题。要处理奇怪的错误消息,每当对模型进行更改时,请尝试删除 SQL 表,然后再次运行应用程序。
向后参考
指定数据模型上的关系允许我们通过原始模型上的属性访问连接模型的属性。如果我们要将 Comment 模型与 User 模型结合起来,我们就可以通过 访问评论作者的属性 Comment.user.username,其中 user 是我们关系的名称,username 是关联模型的属性。
以这种方式创建的关系是单向的,因为我们可以通过球员访问球队详细信息,但无法从球队访问球员详细信息。我们可以通过设置反向引用轻松解决这个问题。
创建关系时,我们可以传递一个名为 backref 的属性来使关系成为双向的。以下是我们修改之前设置的关系的方法:
定义数据模型之间的双向关系:
...
# Relationships
author = relationship("User", backref="posts")
...
有了 backref,我们现在可以通过调用 来访问帖子的用户详细信息 Post.author。
创建相关数据
是时候创建一些数据了。我们需要一个用户作为我们博客的作者。当我们这样做时,让我们给他们一些博客文章:
from .models import Post, User
admin_user = User(
username="toddthebod",
password="Password123lmao",
email="todd@example.com",
first_name="Todd",
last_name="Birchard",
bio="I write tutorials on the internet.",
avatar_url="https://storage.googleapis.com/hackersandslackers-cdn/authors/todd_small@2x.jpg",
role="admin",
)
post_1 = Post(
author_id=admin_user.id,
slug="fake-post-slug",
title="Fake Post Title",
status="published",
summary="A fake post to have some fake comments.",
feature_image="https://cdn.hackersandslackers.com/2021/01/logo-smaller@2x.png",
body="Cheese slices monterey jack cauliflower cheese dolcelatte cheese and wine fromage frais rubber cheese gouda. Rubber cheese cheese and wine cheeseburger cheesy grin paneer paneer taleggio caerphilly. Edam mozzarella.",
)
post_2 = Post(
author_id=admin_user.id,
slug="an-additional-post",
title="Yet Another Post Title",
status="published",
summary="An in-depth exploration into writing your second blog post.",
feature_image="https://cdn.hackersandslackers.com/2021/01/logo-smaller@2x.png",
body="Smelly cheese cheese slices fromage. Pepper jack taleggio monterey jack cheeseburger pepper jack swiss everyone loves. Cheeseburger say cheese brie fromage frais swiss when the cheese comes out everybody's happy babybel cheddar. Cheese and wine cheesy grin",
)
我们创建了对象,但尚未将它们保存到数据库中。我将为此组合几个函数;create_user()将处理用户创建,并将 create_post()创建 …… 好吧,你知道:
"""通过 SQLAlchemy 的 ORM 创建彼此相关的记录。"""
from typing import Tuple
from sqlalchemy.exc import IntegrityError, SQLAlchemyError
from sqlalchemy.orm import Session
from logger import LOGGER
from sqlalchemy_tutorial.part3_relationships.models import Post, User
def create_user(session: Session, user: User) -> User:
"""
如果用户名尚未被占用,则创建一个新用户.
:param session: SQLAlchemy 数据库 Session。:type session: Session
:param user: 要创建的新用户记录。:type user: User
:return: Optional[User]
"""
try:
existing_user = session.query(User).filter(User.username == user.username).first()
if existing_user is None:
session.add(user) # Add the user
session.commit() # Commit the change
LOGGER.success(f"创建的用户:{user}")
else:
LOGGER.warning(f"用户已存在于数据库中:{existing_user}")
return session.query(User).filter(User.username == user.username).first()
except IntegrityError as e:
LOGGER.error(e.orig)
raise e.orig
except SQLAlchemyError as e:
LOGGER.error(f"创建用户时出现意外错误:{e}")
raise e
def create_post(session: Session, post: Post) -> Post:
"""
创建帖子。:param session: SQLAlchemy 数据库 session.
:type session: Session
:param post: 待创建的博客文章。:type post: Post
:return: Post
"""
try:
existing_post = session.query(Post).filter(Post.slug == post.slug).first()
if existing_post is None:
session.add(post) # Add the post
session.commit() # Commit the change
LOGGER.success(f"已创建由用户发布的帖子 {post} {post.author.username}"
)
return session.query(Post).filter(Post.slug == post.slug).first()
else:
LOGGER.warning(f"数据库中已存在帖子:{post}")
return existing_post
except IntegrityError as e:
LOGGER.error(e.orig)
raise e.orig
except SQLAlchemyError as e:
LOGGER.error(f"创建用户时出现意外错误:{e}")
raise e
如果您对这些函数的复杂性感到措手不及,请知道除了 session.add()和之外的所有内容 session.commit()都是为了错误处理和避免重复而存在的。我们不想要重复的帖子或用户,因此我们在继续各自的功能之前检查 existing_user 和。existing_post
让我们创建这些记录:
from .orm import create_user, create_post
# 创建管理员用户和两个帖子
admin_user = create_user(session, admin_user)
post_1 = create_post(session, post_1)
post_2 = create_post(session, post_2)
我们现在有一个用户和许多(ish)帖子!如果您要检查数据库,您会看到在那里创建的这些记录。该阶段是为一对多连接查询设置的。
执行一对多 JOIN
当我们在 SQLAlchemy 模型上执行 JOIN 时,我们可以利用我们获取的每个记录的关系属性,就好像该属性本身就是一个完整的记录一样。向您展示我的意思可能会更容易。
在下面的查询中,我们获取数据库中属于用户 1 的所有帖子。然后,我们通过扩展查询来加入属于该用户的帖子.join(User, Post.author_id == User.id):
"""对具有关系的模型执行 JOIN 查询。"""
from sqlalchemy.orm import Session
from logger import LOGGER
from sqlalchemy_tutorial.part3_relationships.models import Post, User
def get_all_posts(session: Session, admin_user: User):
"""
获取属于作者用户的所有帖子。:param session: SQLAlchemy database session.
:type session: Session
:param admin_user: 博客文章的作者。:type admin_user: User
:return: None
"""
posts = (session.query(Post)
.join(User, Post.author_id == User.id)
.filter_by(username=admin_user.username)
.all())
for post in posts:
post_record = {
"post_id": post.id,
"title": post.title,
"summary": post.summary,
"status": post.status,
"feature_image": post.feature_image,
"author": {
"id": post.author_id,
"username": post.author.username,
"first_name": post.author.first_name,
"last_name": post.author.last_name,
"role": post.author.role,
},
}
LOGGER.info(post_record)
执行查询后,我们循环遍历每条记录并打印一个 JSON 对象,该对象代表我们获取的帖子及其作者数据:
输出 get_all_posts():
{
"post_id": 1,
"title": "Fake Post Title",
"summary": "A fake post to have some fake comments.",
"status": "published",
"feature_image": "https://cdn.hackersandslackers.com/2021/01/logo-smaller@2x.png",
"author": {
"id": 2,
"username": "toddthebod",
"first_name": "Todd",
"last_name": "Birchard",
"role": "admin"
}
}, {
"post_id": 2,
"title": "Yet Another Post Title",
"summary": "An in-depth exploration into writing your second blog post.",
"status": "published",
"feature_image": "https://cdn.hackersandslackers.com/2021/01/logo-smaller@2x.png",
"author": {
"id": 2,
"username": "toddthebod",
"first_name": "Todd",
"last_name": "Birchard",
"role": "admin"
}
}
多对多关系
当我们期望关系中的一个表在另一个表中的多个记录中只有一条记录时(即:每个球队一个球员),设置外键关系对我们很有帮助。如果球员可以属于多个球队怎么办?这就是事情变得复杂的地方。
正如您可能已经猜到的,多对多关系发生在表之间,其中表 1 中的 n 条记录可以与表 2 中的 n 条记录相关联。SQLAlchemy 通过关联表实现此类关系。关联表是一张 SQL 表,其创建的唯一目的是解释这些关系,我们将构建一个关联表。
看看我们如何定义下面的 association_table 变量:
from sqlalchemy import Column, Integer, String, ForeignKey, Table
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
association_table = Table(
'association',
Base.metadata,
Column(
'team_id',
Integer,
ForeignKey('example.sqlalchemy_tutorial_players.team_id')
),
Column(
'id',
Integer,
ForeignKey('example.sqlalchemy_tutorial_teams.id')
)
)
class Player(Base):
"""Individual player belonging to a team."""
__tablename__ = "player"
id = Column(Integer, primary_key=True, autoincrement="auto")
team_id = Column(Integer, ForeignKey("team.id"), nullable=False)
first_name = Column(String(255), nullable=False)
last_name = Column(String(255), nullable=False)
position = Column(String(100), nullable=False)
injured = Column(Boolean)
description = Column(Text, nullable=True)
created_at = Column(DateTime, server_default=func.now())
updated_at = Column(DateTime, onupdate=func.now())
# Relationships
team = relationship("Team")
def __repr__(self):
return f""
class Team(Base):
"""Team consisting of many players."""
__tablename__ = "team"
id = Column(Integer, primary_key=True, autoincrement="auto")
name = Column(String(255), nullable=False)
city = Column(String(255), nullable=False)
created_at = Column(DateTime, server_default=func.now())
updated_at = Column(DateTime, onupdate=func.now())
def __repr__(self):
return f""
我们使用新的数据类型 Table 来定义构建多对多关联的表。我们传递的第一个参数是结果表的名称,我们将其命名为 association。接下来,我们将 Base.metadata 表与数据模型扩展的相同声明基础关联起来。最后,我们创建两列作为我们关联的每个表的外键:我们将 Player 的 team_id 列与 Team 的 id 列链接起来。
我们在这里真正做的事情的本质是创建第三个表来关联我们的两个表。我们还可以通过创建第三个数据模型来实现这一点,但创建关联表更简单一些。从现在开始,我们现在可以直接查询 association_table 以从我们的球员和球队表中获取记录。
实现关联表的最后一步是在我们的数据模型上设置关系。请注意我们如何像之前一样在 Player 上设置关系,但这次我们将次要属性设置为等于关联表的名称。文章来源:https://www.toymoban.com/diary/sql/578.html
[
{
"comment_id": 1,
"body_summary": "This post about SQLAlchemy is awful. You didnt ev...",
"upvotes": 2,
"comment_author_id": 3,
"post": {
"slug": "fake-post-slug",
"title": "Fake Post Title",
"post_author": "toddthebod"
}
},
{
"comment_id": 2,
"body_summary": "By the way, you SUCK!!! I HATE you!!!! I have a pr...",
"upvotes": 5,
"comment_author_id": 3,
"post": {
"slug": "fake-post-slug",
"title": "Fake Post Title",
"post_author": "toddthebod"
}
},
{
"comment_id": 3,
"body_summary": "YOU RUINED MY LIFE!!!!...",
"upvotes": 5,
"comment_author_id": 3,
"post": {
"slug": "fake-post-slug",
"title": "Fake Post Title",
"post_author": "toddthebod"
}
}
]
关键词:SQLAlchemy 数据模型,关系,一对一,一对多,多对一,多对多,ORM,Python 开发 文章来源地址 https://www.toymoban.com/diary/sql/578.html
到此这篇关于 SQLAlchemy 数据模型之间的关系 – 构建有意义关系的数据模型的文章就介绍到这了, 更多相关内容可以在右上角搜索或继续浏览下面的相关文章,希望大家以后多多支持 TOY 模板网!