跳到内容

创建关联表

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

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

我们一直在单个 hero 表中处理英雄数据。现在我们添加一个 team 表。

team 表将如下所示

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

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

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

这样,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

# Code below omitted 👇
👀 完整文件预览
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")

# Code below omitted 👇
👀 完整文件预览
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() 中添加了一个默认值 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 👇
👀 完整文件预览
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() 添加到文件的主块中

# Code above omitted 👆

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

运行代码

提示

在运行代码之前,请确保删除文件 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 代码。

如前所述,在 SQLite 中,这些 VARCHAR 列被转换为 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 的字段(列),其中字符串指向另一个表和列,即可连接两个表。

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