Skip to content

创建连接表格

现在我们将处理存储在不同表格中的 连接 数据。

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

我们之前一直在处理单一表格 hero 中的英雄数据。现在,我们来添加一个表格 team

team 表格的结构如下所示:

idnameheadquarters
1PreventersSharp Tower
2Z-ForceSister Margaret's Bar

为了将它们连接起来,我们将在 hero 表中添加另一个列 team_id,通过该 ID 指向每个团队:

idnamesecret_nameageteam_id ✨
1DeadpondDive Wilsonnull2 ✨
2Spider-BoyPedro Parqueadornull1 ✨
3Rusty-ManTommy Sharp481 ✨

这样,hero 表中的每一行就可以指向 team 表中的某一行:

table relationships

一对多和多对一

在这里,我们创建了一个连接的数据关系,其中 一个 团队可以拥有 多个 英雄。所以,这通常被称为 一对多多对一 关系。

从英雄的角度来看,多个 英雄可以属于 一个 团队,这就是 多对一 关系。

这是最常见的关系类型,因此我们从这一种关系开始。但也有 多对多一对一 关系。

在代码中创建表格

创建 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

# 代码省略 👇
from typing import Optional

from sqlmodel import Field, SQLModel, create_engine


class Team(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    name: str = Field(index=True)
    headquarters: str

# 代码省略 👇
👀 完整文件预览
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


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()
from typing import Optional

from sqlmodel import Field, SQLModel, create_engine


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()

这与我们之前对 Hero 模型的操作非常相似。

Team 模型会自动对应一个名为 "team" 的表,并且会包含以下列:

  • id:主键,由数据库自动生成
  • name:团队的名称
    • 我们还告诉 SQLModel 为该列创建索引
  • headquarters:团队的总部

最后,我们在配置中将其标记为一个表格。

创建新的 hero 表格

现在我们来创建 hero 表格。

这与我们之前使用的模型相同,只是我们添加了新的 team_id 列:

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


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")

# 代码省略 👇
from typing import Optional

from sqlmodel import Field, SQLModel, create_engine


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")

# 代码省略 👇
👀 完整文件预览
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


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()
from typing import Optional

from sqlmodel import Field, SQLModel, create_engine


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() 中为 team_id 添加了默认值 None,这样在创建英雄时就不需要显式地传递 team_id=None

现在,这里是新的部分:

Field() 中,我们传递了 foreign_key="team.id" 参数。这告诉数据库,team_id 列是指向 team 表的外键。 外键 意味着这个列将包含用来标识 外部 表中某行的 主键

该列 team_id 中的值将是 team 表中 id 列的某一行的整数值。正是这个值将连接两个表格。

foreign_key 的值

注意,foreign_key 是一个字符串。

字符串中包含了 表名,然后是一个点,再接着是 列名

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

如果你使用了自定义的表名,就应该使用那个自定义的表名。

Tip

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

创建表格

现在我们可以像之前一样添加代码来创建引擎,并创建一个函数来创建表格:

# 代码省略 👆

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)
# 代码省略 👆

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)
👀 完整文件预览
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


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()
from typing import Optional

from sqlmodel import Field, SQLModel, create_engine


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()

与之前一样,我们将从另一个函数 main() 调用此函数,并将 main() 函数添加到文件的主块中:

# 代码省略 👆

def main():
    create_db_and_tables()


if __name__ == "__main__":
    main()
# 代码省略 👆

def main():
    create_db_and_tables()


if __name__ == "__main__":
    main()
👀 完整文件预览
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


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()
from typing import Optional

from sqlmodel import Field, SQLModel, create_engine


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()

运行代码

Tip

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

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

$ python app.py

// 自动开始一个新的事务
INFO Engine BEGIN (implicit)

// 检查表格是否已经存在
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] ()

// 创建表格
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 类型,这是我们用来进行实验的数据库。

所以,第一条 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,它指向另一个表和列,从而连接两个表。

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