#一对一关系
典型场景:一个用户有一个用户详情(扩展信息)。
#一、场景说明
用户表 (users) 用户详情表 (user_profiles)
┌────┬──────┐ ┌────┬─────────┬───────┬─────────┐
│ id │ name │ │ id │ user_id │ phone │ address │
├────┼──────┤ ├────┼─────────┼───────┼─────────┤
│ 1 │ 张三 │ ────── 1:1 ────── │ 1 │ 1 │ 138.. │ 北京 │
│ 2 │ 李四 │ │ 2 │ 2 │ 139.. │ 上海 │
└────┴──────┘ └────┴─────────┴───────┴─────────┘- 一个用户只有一份详情(一对一)
- 一份详情只属于一个用户(一对一)
user_id是外键,指向users.id,并且加了unique约束
#二、和一对多的区别
只有一处不同:uselist=False
# 一对多:返回列表(一个用户有多篇文章)
articles = relationship("Article", back_populates="author")
# user.articles → [<Article>, <Article>, ...]
# 一对一:返回单个对象(一个用户只有一份详情)
profile = relationship("UserProfile", back_populates="user", uselist=False)
# user.profile → <UserProfile> 或 None另外,外键字段加 unique=True,保证数据库层面也是一对一:
user_id = Column(Integer, ForeignKey("users.id"), unique=True, nullable=False)#三、定义模型
from sqlalchemy import Column, Integer, String, ForeignKey, Text
from sqlalchemy.orm import relationship
from database import Base
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True)
name = Column(String(50), nullable=False)
# uselist=False:一对一,返回单个对象而不是列表
profile = relationship("UserProfile", back_populates="user", uselist=False)
class UserProfile(Base):
__tablename__ = "user_profiles"
id = Column(Integer, primary_key=True)
user_id = Column(Integer, ForeignKey("users.id"), unique=True, nullable=False)
phone = Column(String(20))
address = Column(Text)
user = relationship("User", back_populates="profile")#四、逐行解释
#1. ForeignKey + unique
user_id = Column(Integer, ForeignKey("users.id"), unique=True, nullable=False)ForeignKey("users.id")— 外键,指向 users 表unique=True— 唯一约束,保证一个 user_id 只出现一次(数据库层面的一对一)nullable=False— 不允许为空
#2. uselist=False
profile = relationship("UserProfile", back_populates="user", uselist=False)uselist=False— 不返回列表,返回单个对象- 没有这个参数的话,
user.profile返回的是[<UserProfile>]列表 - 加了之后,
user.profile返回的是<UserProfile>对象或None
#五、完整示例
#5.1 建表并插入数据
from sqlalchemy import create_engine, Column, Integer, String, ForeignKey, Text, select
from sqlalchemy.orm import sessionmaker, DeclarativeBase, relationship, joinedload
class Base(DeclarativeBase):
pass
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True)
name = Column(String(50), nullable=False)
profile = relationship("UserProfile", back_populates="user", uselist=False)
class UserProfile(Base):
__tablename__ = "user_profiles"
id = Column(Integer, primary_key=True)
user_id = Column(Integer, ForeignKey("users.id"), unique=True, nullable=False)
phone = Column(String(20))
address = Column(Text)
user = relationship("User", back_populates="profile")
engine = create_engine("sqlite:///./test.db", connect_args={"check_same_thread": False})
Base.metadata.create_all(bind=engine)
Session = sessionmaker(bind=engine)
db = Session()
# ---------- 创建用户 ----------
user1 = User(name="张三")
user2 = User(name="李四")
db.add_all([user1, user2])
db.commit()
db.refresh(user1)
db.refresh(user2)
# ---------- 创建用户详情 ----------
profile1 = UserProfile(user_id=user1.id, phone="13800138000", address="北京市朝阳区")
profile2 = UserProfile(user_id=user2.id, phone="13900139000", address="上海市浦东区")
db.add_all([profile1, profile2])
db.commit()#5.2 正向查询:用户 → 详情
user = db.execute(select(User).where(User.id == 1)).scalars().first()
print(user.name) # 张三
print(user.profile) # <UserProfile(id=1, phone='13800138000')>
print(user.profile.phone) # 13800138000
print(user.profile.address) # 北京市朝阳区如果没有详情:
user = User(name="新用户")
db.add(user)
db.commit()
db.refresh(user)
print(user.profile) # None(不是空列表,因为 uselist=False)#5.3 反向查询:详情 → 用户
profile = db.execute(select(UserProfile).where(UserProfile.id == 1)).scalars().first()
print(profile.phone) # 13800138000
print(profile.user.name) # 张三#5.4 预加载(一条 SQL 查完)
# 没有预加载:两条 SQL
user = db.execute(select(User).where(User.id == 1)).scalars().first()
print(user.profile) # 触发第二条 SQL
# 有预加载:一条 SQL(LEFT JOIN)
stmt = select(User).options(joinedload(User.profile))
user = db.execute(stmt).scalars().first()
print(user.profile) # 不触发新 SQL#5.5 通过 relationship 创建详情
# 方式一:手动写 user_id
profile = UserProfile(user_id=user.id, phone="新号码", address="新地址")
db.add(profile)
db.commit()
# 方式二:直接赋值 profile 属性
user.profile = UserProfile(phone="新号码", address="新地址")
db.commit()
# SQLAlchemy 自动设置 user_id#5.6 更新详情
user = db.execute(select(User).where(User.id == 1)).scalars().first()
# 直接改属性
user.profile.phone = "13900139000"
db.commit()
# 没有详情时先创建
if user.profile is None:
user.profile = UserProfile(phone="新号码", address="新地址")
db.commit()#六、什么时候用一对一
| 场景 | 说明 |
|---|---|
| 用户 + 用户详情 | 核心信息和扩展信息分开 |
| 用户 + 用户配置 | 每个用户一份配置 |
| 订单 + 订单物流 | 一个订单一份物流信息 |
| 文章 + 文章统计 | 一个文章一份统计数据 |
核心判断标准:这个信息是不是只有一份? 是的话用一对一,可能有多份的话用一对多。

