基于 FastAPI 的多表设计(一对多,多对多)

Posted on Fri, 22 May 2026 13:49:12 +0800 by LiangMingJian


概述

在数据库表设计中,关系模型是一个很重要的设计部分。多表关系的构建可以让我们的数据库查询更加高效,表之间的结果更加完整,且可控。

在数据库表设计中,关系模型主要分为以下三种:一对一,一对多,多对多。

一对一

一对一指两个表中的一个记录仅与另一个表中的一个记录关联。比如,一个人只能有一个身份证号,而一个身份证号也只能属于一个人。

在 SQL 数据库设计时,可以通过在两个表中数据量较少的一方设置一个唯一的外键 FOREIGN KEY 来实现这一个关系。

CREATE TABLE Users (
    id INT PRIMARY KEY,
    name VARCHAR(100)
);
 
CREATE TABLE Ids (
    id INT PRIMARY KEY,
    card_id VARCHAR(20),
    user_id INT UNIQUE,
    FOREIGN KEY (user_id) REFERENCES Users(id)
);

在 FastAPI 的 Models 设计时,可以通过构建以下的代码,来实现这个关系。

from sqlalchemy import MetaData
from sqlalchemy.orm import DeclarativeBase

# 命名约定(用于 SQLAlchemy 数据库迁移)  
NAMING_CONVENTION = {  
    "ix": "ix_%(column_0_label)s",  
    "uq": "uq_%(table_name)s_%(column_0_name)s",  
    "ck": "ck_%(table_name)s_%(constraint_name)s",  
    "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",  
    "pk": "pk_%(table_name)s",  
}  

metadata = MetaData(naming_convention=NAMING_CONVENTION)  

# 继承 DeclarativeBase 声明性基类并绑定 metadata 
class Base(DeclarativeBase):    
	metadata = metadata

class Users(Base):

    __tablename__ = 'Users'
    
    id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    name: Mapped[str] = mapped_column(String(50), unique=True, nullable=False, index=True)

class Ids(Base):

	__tablename__ = "Ids" 
	
	id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) 
	card_id: Mapped[str] = mapped_column(String(50), unique=True, nullable=False, index=True)
	# 创建唯一(unique=True)外键(ForeignKey) user_id;nullable 非空外键,避免出现孤立数据
	user_id:Mapped[int] = mapped_column(ForeignKey("Users.id"), unique=True, nullable=False)

一对多

一对多指一个记录与另一个表中的多个记录关联。比如,一个人有多个地址。

在 SQL 数据库设计时,可以通过在【多方】设置一个外键 FOREIGN KEY 指向【一方】的主键来实现这一个关系。

CREATE TABLE Users (
    id INT PRIMARY KEY,
    name VARCHAR(100)
);
 
CREATE TABLE Addresses (
    id INT PRIMARY KEY,
    address_name VARCHAR(20),
    user_id INT,
    FOREIGN KEY (user_id) REFERENCES Users(id)
);

从上面不难看出,与一对一关系不同,外键 user_id 在一对多关系时,是不唯一的(没有 UNIQUE)。

因此,在 FastAPI 的 Models 设计时,也可以通过类似操作实现这个关系。

注意,这样操作只是在数据库层创建了外键,实现了一对多,但在 Python 的 ORM 操作中,你还不能通过在获取 Addresses 表数据后,通过点式 .user.id 来便捷的获取 Users 表的数据。

你还需要在两个模型中添加 relationship,让 ORM 操作时可以双向同步。即,可以使用 Addresses.user.id,通过 Addresses 表的数据来获取 Users 表的数据,也可以反过来使用 Users.address.id 通过 Users 表的数据,来获取 Addresses 表的数据。

class Users(Base):

    __tablename__ = 'Users'
    
    id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    name: Mapped[str] = mapped_column(String(50), unique=True, nullable=False, index=True)
    
    # 定义 Addresses 数据,back_populates 需要与 Addresses 中对应的数据一致
    address = Mapped[list["Addresses"]] = relationship("Addresses", back_populates="user")

class Addresses(Base):

	__tablename__ = "Addresses" 
	
	id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) 
	card_id: Mapped[str] = mapped_column(String(50), unique=True, nullable=False, index=True)
	
	# 创建唯一(unique=True)外键(ForeignKey) user_id;nullable 非空外键,避免出现孤立数据
	user_id:Mapped[int] = mapped_column(ForeignKey("Users.id"), unique=True, nullable=False)
	
	# 定义 User 数据,back_populates 需要与 Users 中对应的数据一致
	user: Mapped["Users"] = relationship("Users", back_populates="address")

注意!不要觉得写了 relationship 就可以不定义 ForeignKey。务必记得 ForeignKey 才是数据层的物理连接定义,relationship 只是 Python 代码的对象映射,只有 relationship 而没有 ForeignKey,SQLAlchemy 无法判断这两个表是怎么联系的,会抛出错误。

可能有时候你会听到多对一的关系,但其实多对一和一对多是相互的,用户和地址是一对多,那地址和用户便是多对一,这两者只是说法上的不同而已。

多对多

多对多指一个表中的数据既可以与另一个表中的多个数据关联,另一个表的数据也可以和这个表的多个数据关联。比如,一个学生可以选择多门课程,同时一门课程也可以被多个学生进行选择。

在 SQL 数据库设计时,往往需要通过新建第三张表来实现这个一关系模型,这个第三张表可以称为关联表。关联表中包含两个外键,这两个外键分别指向对应表中的主键。

CREATE TABLE Users (
    id INT PRIMARY KEY,
    name VARCHAR(100)
);
 
CREATE TABLE Courses (
    id INT PRIMARY KEY,
    name VARCHAR(100)
);

CREATE TABLE UsersRelateCourses (
    UserID INT,
    CourseID INT,
    PRIMARY KEY (UserID, CourseID),
    FOREIGN KEY (UserID) REFERENCES Users(id),
    FOREIGN KEY (CourseID) REFERENCES Courses(id)
);

与 SQL 设计方法类似,FastAPI 在设计多对多关系时同样需要新建第三张关联表,在这个表中加入外键,然后通过 relationship 方法中的 secondary 指定多对多关系。

class Users(Base):

    __tablename__ = 'Users'
    
    id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    name: Mapped[str] = mapped_column(String(50), unique=True, nullable=False, index=True)
    
    # 通过 secondary 指定中间表,back_populates 对应连接属性
    course = Mapped[list["Courses"]] = relationship(secondary="UsersRelateCourses", back_populates="user")

class Courses(Base):

	__tablename__ = "Courses" 
	
    id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    name: Mapped[str] = mapped_column(String(50), unique=True, nullable=False, index=True)
    
    # 通过 secondary 指定中间表,back_populates 对应连接属性
    user = Mapped[list["Users"]] = relationship(secondary="UsersRelateCourses", back_populates="course")
    
class UsersRelateCourses(Base):

	__tablename__ = "UsersRelateCourses"
	
	# 定义关联表,连接属性
	user_id: Mapped[int] = mapped_column(ForeignKey('Users.id'), primary_key=True) 
	course_id: Mapped[int] = mapped_column(ForeignKey('Courses.id'), primary_key=True)