用Python+SQLAlchemy实战解析PTA数据库ER模型填空题
当你在PTA数据库原理考试中遇到"根据语义设计ER图"的填空题时,是否感到抽象概念难以落地?本文将以医院管理系统为例,带你用SQLAlchemy将ER模型填空题转化为可执行的Python代码。通过这种"逆向工程"式学习,你会发现那些难记的"实体完整性"、"参照完整性"规则,不过是数据库设计中的自然约束。
1. 从填空题到实体类:医院系统建模实战
假设我们拿到这样一道PTA填空题:
"某医院管理系统包含科室、医生、病人实体。其中:一个科室有多个医生,一个医生属于一个科室;一个医生可诊治多个病人,一个病人有唯一主管医生;一个科室有多个病房,一个病房属于一个科室;一个病房可入住多个病人。"
1.1 识别实体与属性
首先提取题目中的实体和属性。根据常见医院管理系统,我们可以补充典型属性:
from sqlalchemy import Column, Integer, String, ForeignKey, Date from sqlalchemy.orm import relationship from sqlalchemy.ext.declarative import declarative_base Base = declarative_base() class Department(Base): # 科室实体 __tablename__ = 'departments' id = Column(Integer, primary_key=True) name = Column(String(50), unique=True) # 科室名称 location = Column(String(100)) # 科室位置 phone = Column(String(20)) # 联系电话 # 定义一对多关系 doctors = relationship("Doctor", back_populates="department") wards = relationship("Ward", back_populates="department")这里体现了ER模型到类定义的转换技巧:
- 实体 → Python类
- 实体属性 → 类属性(Column)
- 主键 → primary_key=True
- 唯一约束 → unique=True
1.2 处理1:N联系类型
题目中"一个科室有多个医生"是典型的1:N关系。在SQLAlchemy中,这种关系通过外键+relationship实现:
class Doctor(Base): # 医生实体 __tablename__ = 'doctors' id = Column(Integer, primary_key=True) name = Column(String(30)) title = Column(String(20)) # 职称 hire_date = Column(Date) # 聘用日期 # 外键指向科室表 department_id = Column(Integer, ForeignKey('departments.id')) # 定义关系 department = relationship("Department", back_populates="doctors") patients = relationship("Patient", back_populates="doctor")注意:在1:N关系中,外键总是放在"N"方(这里是医生表)。这与ER模型转换规则完全一致——"在N端实体类型中加入1端实体类型的主键"。
2. 复杂关系建模:M:N联系与关联表
当遇到"一个医生可诊治多个病人,一个病人有唯一主管医生"这样的描述时,初学者常会困惑。实际上这是两个不同的关系:
2.1 1:N的诊治关系
class Patient(Base): # 病人实体 __tablename__ = 'patients' id = Column(Integer, primary_key=True) name = Column(String(30)) admission_date = Column(Date) # 入院日期 # 外键指向主管医生(1:N) doctor_id = Column(Integer, ForeignKey('doctors.id')) doctor = relationship("Doctor", back_populates="patients") # 外键指向病房(1:N) ward_id = Column(Integer, ForeignKey('wards.id')) ward = relationship("Ward", back_populates="patients")2.2 M:N的会诊关系(扩展场景)
如果题目变为"病人可由多名医生会诊",就需要建立M:N关系。这正是PTA常考的考点:
# 首先创建关联表 doctor_patient_association = Table( 'doctor_patient_association', Base.metadata, Column('doctor_id', Integer, ForeignKey('doctors.id')), Column('patient_id', Integer, ForeignKey('patients.id')), Column('consultation_date', Date) # 会诊日期作为联系属性 ) # 修改Doctor类添加多对多关系 class Doctor(Base): # ... 其他属性同上 ... consulting_patients = relationship( "Patient", secondary=doctor_patient_association, back_populates="consulting_doctors" ) # 修改Patient类 class Patient(Base): # ... 其他属性同上 ... consulting_doctors = relationship( "Doctor", secondary=doctor_patient_association, back_populates="consulting_patients" )这种模式完美对应了ER模型中"M:N联系转换为独立关系模式"的规则,关联表的主键由两端实体的主键组成。
3. 完整性约束的代码实现
PTA填空题常考的"实体完整性"、"参照完整性"等概念,在SQLAlchemy中都有直接对应:
3.1 实体完整性规则
class Ward(Base): # 病房实体 __tablename__ = 'wards' id = Column(Integer, primary_key=True) # 主键非空 → 实体完整性 room_number = Column(String(10), unique=True, nullable=False) bed_count = Column(Integer) department_id = Column(Integer, ForeignKey('departments.id')) department = relationship("Department", back_populates="wards") patients = relationship("Patient", back_populates="ward")primary_key=True保证主键唯一且非空nullable=False强制属性非空unique=True确保值唯一
3.2 参照完整性实践
当设置外键约束时,SQLAlchemy自动实现参照完整性:
from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker engine = create_engine('sqlite:///hospital.db') Base.metadata.create_all(engine) Session = sessionmaker(bind=engine) session = Session() # 尝试插入违反参照完整性的数据会报错 invalid_doctor = Doctor( name="张医生", title="主任医师", department_id=999 # 不存在的科室ID ) # session.add(invalid_doctor) # 这将引发IntegrityError4. 从模型到数据库:完整工作流演示
让我们用代码回答一道典型PTA填空题:"医院系统中,病房与科室是___联系"。
通过实际建模,我们可以验证答案是1:N:
# 创建测试数据 cardiology = Department( name="心内科", location="住院部3楼", phone="123456" ) ward301 = Ward( room_number="301", bed_count=4, department=cardiology # 建立关系 ) # 添加到数据库 session.add(cardiology) session.add(ward301) session.commit() # 验证关系 test_ward = session.query(Ward).filter_by(room_number="301").first() print(f"病房所属科室:{test_ward.department.name}") # 输出:心内科 print(f"科室下病房数:{len(cardiology.wards)}") # 输出:1这种实践方式让抽象的ER概念变得具体可见。当你在PTA考试中遇到类似问题时,脑海中会自然浮现出对应的代码结构。
通过SQLAlchemy的automap扩展,我们甚至可以从现有数据库逆向生成模型类,这种"逆向工程"体验能加深对ER模型的理解:
from sqlalchemy import create_engine from sqlalchemy.ext.automap import automap_base # 假设已有医院数据库 engine = create_engine('sqlite:///hospital_existing.db') Base = automap_base() Base.prepare(engine, reflect=True) # 获取自动生成的类 Department = Base.classes.departments Doctor = Base.classes.doctors # 查询示例 doctors = session.query(Doctor).join(Department).filter(Department.name=="心内科").all()这种从实践到理论再回到实践的学习闭环,正是掌握数据库设计的关键。下次当你在PTA中遇到ER模型填空题时,不妨先想象对应的Python类结构,答案往往会变得清晰明了。