创建关联表¶
现在我们将处理放在不同表中的关联数据。
所以,第一步是创建多个表并将它们连接起来,这样在一个表中的每一行都可以引用另一个表中的另一行。
我们一直在单个 hero 表中处理英雄数据。现在我们添加一个 team 表。
team 表将如下所示
| id | name | 总部 |
|---|---|---|
| 1 | 阻止者 | 锐利之塔 |
| 2 | Z-部队 | 玛格丽特修女酒吧 |
为了连接它们,我们将在 hero 表中添加另一列 team_id,通过 ID 来指向每个团队
| id | name | secret_name | age | team_id ✨ |
|---|---|---|---|---|
| 1 | 死侍 | 戴夫·威尔逊 | 空 | 2 ✨ |
| 2 | 蜘蛛男孩 | 佩德罗·帕尔克多 | 空 | 1 ✨ |
| 3 | 锈人 | 汤米·夏普 | 48 | 1 ✨ |
这样,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 👇
👀 完整文件预览
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 以及其中我们刚刚定义的表 team 和 hero
$ 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 的字段(列),其中字符串指向另一个表和列,即可连接两个表。
现在我们已经创建并连接了表,让我们在下一章中创建一些行。🚀