现在我们将处理放在不同表中的 关联 数据。

所以,第一步是创建多个表并将它们连接起来,这样在一个表中的每一行都可以引用另一个表中的另一行。

我们一直在使用单个 hero 表中的英雄。现在我们添加一个 team 表。

团队表将如下所示

idname总部 1阻止者锐利之塔 2Z-部队玛格丽特修女酒吧

为了连接它们,我们将在英雄表中添加另一列 team_id ,通过 ID 指向每个团队

idnamesecret_nameageteam_id ✨ 1死侍戴夫·威尔逊空2 ✨ 2蜘蛛男孩佩德罗·帕尔克多空1 ✨ 3锈人汤米·夏普481 ✨

这样, hero 表中的每一行都可以指向 team 表中的一行

一对多和多对一

这里我们创建的关联数据是一种关系,其中 一个 团队可以拥有 多个 英雄。因此它通常被称为 一对多 多对一 关系。

如果从英雄开始, 多个 英雄可以是 一个 团队的一部分,这就是 多对一 的部分。

这可能是最流行的关系类型,所以我们从它开始。但也有 多对多 一对一 关系。

在代码中创建表

创建 team

我们先在代码中创建表。

sqlmodel 导入所需内容并创建一个新的 Team 模型

from sqlmodel import Field, SQLModel, create_engine
class Team(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str = Field(index=True)
    headquarters: str
# Code below omitted 👇
class Team(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str = Field(index=True)
    headquarters: str
class Hero(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str = Field(index=True)
    secret_name: str
    age: int | None = Field(default=None, index=True)
    team_id: int | None = Field(default=None, foreign_key="team.id")
sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"
engine = create_engine(sqlite_url, echo=True)
def create_db_and_tables():
    SQLModel.metadata.create_all(engine)
def main():
    create_db_and_tables()
if __name__ == "__main__":
    main()
class Team(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    name: str = Field(index=True)
    headquarters: str
class Hero(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    name: str = Field(index=True)
    secret_name: str
    age: Optional[int] = Field(default=None, index=True)
    team_id: Optional[int] = Field(default=None, foreign_key="team.id")
sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"
engine = create_engine(sqlite_url, echo=True)
def create_db_and_tables():
    SQLModel.metadata.create_all(engine)
def main():
    create_db_and_tables()
if __name__ == "__main__":
    main()
class Team(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str = Field(index=True)
    headquarters: str
class Hero(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str = Field(index=True)
    secret_name: str
    age: int | None = Field(default=None, index=True)
    team_id: int | None = Field(default=None, foreign_key="team.id")
# Code below omitted 👇
class Team(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str = Field(index=True)
    headquarters: str
class Hero(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name




    
: str = Field(index=True)
    secret_name: str
    age: int | None = Field(default=None, index=True)
    team_id: int | None = Field(default=None, foreign_key="team.id")
sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"
engine = create_engine(sqlite_url, echo=True)
def create_db_and_tables():
    SQLModel.metadata.create_all(engine)
def main():
    create_db_and_tables()
if __name__ == "__main__":
    main()
class Team(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    name: str = Field(index=True)
    headquarters: str
class Hero(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    name: str = Field(index=True)
    secret_name: str
    age: Optional[int] = Field(default=None, index=True)
    team_id: Optional[int] = Field(default=None, foreign_key="team.id")
sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"
engine = create_engine(sqlite_url, echo=True)
def create_db_and_tables():
    SQLModel.metadata.create_all(engine)
def main():
    create_db_and_tables()
if __name__ == "__main__":
    main()

大部分内容应该看起来很熟悉

该列将被命名为 team_id。它将是一个整数,并且在数据库中可以为 NULL(或在 Python 中为 None),因为可能有一些英雄不属于任何团队。

我们在 Field() 中添加了一个默认值 None,这样在创建英雄时就不必显式传递 team_id=None

现在,这是新的部分

Field() 中,我们传递参数 foreign_key="team.id"。这告诉数据库该列 team_id 是表 team 的外键。一个“外键”仅仅意味着该列将拥有识别外部表中的行的

此列 team_id 中的值将与 team 表中 id 列中某一行中的整数相同。这就是连接两个表的原因。

foreign_key 的值

请注意,foreign_key 是一个字符串。

它内部包含的名称,然后是一个点,然后是的名称。

这是数据库中的名称,因此它是 "team",而不是模型Team(大写 T)的名称。

如果您有自定义表名,您将使用该自定义表名。

您可以在高级用户指南中了解如何为模型设置自定义表名。

创建表

现在我们可以添加与之前相同的代码来创建引擎和创建表的函数

# Code above omitted 👆
sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"
engine = create_engine(sqlite_url, echo=True)
def create_db_and_tables():
    SQLModel.metadata.create_all(engine)
# Code below omitted 👇
class Team(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str = Field(index=True)
    headquarters: str
class Hero(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str = Field(index=True)
    secret_name: str
    age: int | None = Field(default=None, index=True)
    team_id: int | None = Field(default=None, foreign_key="team.id")
sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"
engine = create_engine(sqlite_url, echo=True)
def create_db_and_tables():
    SQLModel.metadata.create_all(engine)
def main():
    create_db_and_tables()
if __name__ == "__main__":
    main()
class Team(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    name: str = Field(index=True)
    headquarters: str
class Hero(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    name: str = Field(index=True)
    secret_name: str
    age: Optional[int] = Field(default=None, index=True)
    team_id: Optional[int] = Field(default=None, foreign_key="team.id")
sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"
engine = create_engine(sqlite_url, echo=True)
def create_db_and_tables():
    SQLModel.metadata.create_all(engine)
def main():
    create_db_and_tables()
if __name__ == "__main__":
    main()
class Team(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str = Field(index=True)
    headquarters: str
class Hero(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str = Field(index=True)
    secret_name: str
    age: int | None = Field(default=None, index=True)
    team_id: int | None = Field(default=None, foreign_key="team.id")
sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"
engine = create_engine(sqlite_url, echo=True)
def create_db_and_tables():
    SQLModel.metadata.create_all(engine)
def main():
    create_db_and_tables()
if __name__ == "__main__":
    main()
class Team(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    name: str = Field(index=True)
    headquarters: str
class Hero(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    name: str = Field(index=True)
    secret_name: str
    age: Optional[int] = Field(default=None, index=True)
    team_id: Optional[int] = Field(default=None, foreign_key="team.id")
sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"
engine = create_engine(sqlite_url, echo=True)
def create_db_and_tables():
    SQLModel.metadata.create_all(engine)
def main():
    create_db_and_tables()
if __name__ == "__main__":
    main()

在运行代码之前,请确保删除文件 database.db 以确保从头开始。

如果我们运行到目前为止的代码,它将创建数据库文件 database.db 并在其中创建我们刚刚定义的表 teamhero

$ python app.py
// Automatically start a new transaction
INFO Engine BEGIN (implicit)
// Check if the tables exist already
INFO Engine PRAGMA main.table_info("team")
INFO Engine [raw sql] ()
INFO Engine PRAGMA temp.table_info("team")
INFO Engine [raw sql] ()
INFO Engine PRAGMA main.table_info("hero")
INFO Engine [raw sql] ()
INFO Engine PRAGMA temp.table_info("hero")
INFO Engine [raw sql] ()
// Create the tables
INFO Engine
CREATE TABLE team (
        id INTEGER,
        name VARCHAR NOT NULL,
        headquarters VARCHAR NOT NULL,
        PRIMARY KEY (id)
INFO Engine [no key 0.00010s] ()
INFO Engine
CREATE TABLE hero (
        id INTEGER,
        name VARCHAR NOT NULL,
        secret_name VARCHAR NOT NULL,
        age INTEGER,
        team_id INTEGER,
        PRIMARY KEY (id),
        FOREIGN KEY(team_id) REFERENCES team (id)
INFO Engine [no key 0.00026s] ()
INFO Engine COMMIT

在 SQL 中创建表

让我们看看相同的生成的 SQL 代码。

如前所述,这些 VARCHAR 列在 SQLite 中被转换为 TEXT,SQLite 是我们用于这些实验的数据库。

所以,第一个 SQL 也可以写成

CREATE TABLE team (
    id INTEGER,
    name TEXT NOT NULL,
    headquarters TEXT NOT NULL,
    PRIMARY KEY (id)

第二个表可以写成

CREATE TABLE hero (
    id INTEGER,
    name TEXT NOT NULL,
    secret_name TEXT NOT NULL,
    age INTEGER,
    team_id INTEGER,
    PRIMARY KEY (id),
    FOREIGN KEY(team_id) REFERENCES team (id)

唯一的新内容是 FOREIGN KEY 行,如您所见,它告诉数据库此表中的哪一列是外键(team_id),它引用哪个其他(外部)表(team),以及该表中的哪一列是定义要连接哪一行的键(id)。

随意在 DB Browser for SQLite 中进行实验。

使用 SQLModel,在大多数情况下,您只需要一个带有 Field()foreign_key 的字段(列),其中包含指向另一个表和列的字符串即可连接两个表。

现在我们已经创建并连接了表,让我们在下一章中创建一些行。🚀