ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Oracle医疗系统实战:PL/SQL驱动的业务型数据库设计

Oracle医疗系统实战:PL/SQL驱动的业务型数据库设计 简介本资源是一套面向高校数据库课程学习者的Oracle数据库课程设计实战项目聚焦医院信息系统建模与开发适用于Java与数据库交叉学习的初学者及课程设计、毕设选题阶段的学生。项目完整呈现从ER建模、SQL脚本建库到Java应用层连接操作的全流程包含36个Java源文件实现业务逻辑、1个核心SQL建表与初始化脚本、1张关系模型图辅助理解数据结构以及database.properties等配置文件支持快速适配本地Oracle环境。压缩包共45个文件总计323KB轻量易部署目录结构清晰含README说明与LICENSE授权信息。已有380人学习下载读者可直接复用SQL建库语句、参考Java中JDBC连接与CRUD实现细节并基于提供的模型图深化对医院业务实体关联的理解是掌握OracleJava协同开发的典型教学范例。1. 这不是又一个“学生课设”它是一套能跑通挂号、门诊、药房全链路的 Oracle 数据库骨架Java 层只做薄胶水核心逻辑全压在 PL/SQL 里——适合想真正搞懂「业务型数据库设计」而非只会建表插数的工程师你可能已经点开过十几个标着“医院系统课程设计”的压缩包解压后发现三张表patient、doctor、dept五条 insert一个 Java Swing 界面点一下弹个 JOptionPane。这次不一样。这个基于 JavaOracle 实现的医院系统数据库是典型的「业务驱动型数据库设计」实战样本它用 23 张物理表撑起挂号预约、门诊接诊、处方开立、药品库存、收费结算、病历归档六大主流程所有强一致性约束如“同一时段同一医生不能重复挂号”“处方药品数量不能超库存”全部由 Oracle 的 CHECK、DEFERRABLE CONSTRAINT、物化视图日志 FAST REFRESH 实现关键事务如“挂号→生成门诊号→绑定初诊记录→扣减号源”封装在带 AUTONOMOUS_TRANSACTION 的存储过程中Java 层仅调用CallableStatement执行预编译过程不拼 SQL、不手动 commit。它不教你怎么写 Spring Boot但教会你怎么让 Oracle 自己管住数据——这才是企业级医疗系统十年不重构的底层底气。如果你正卡在“学完 Oracle 语法却写不出真实业务逻辑”或正在准备 Java 后端岗面试中“数据库设计”类问题这份资源就是你缺的那块拼图。2. 从 ER 图到物理表23 张表如何映射真实医院业务流重点看这 5 组强关联关系与 3 类 Oracle 特性落地2.1 核心实体建模为什么 patient 表不叫 patient_info而用 patient_master项目采用「主-辅分离」设计patient_master存基础身份信息id_card_no 做唯一约束 函数索引加速身份证校验、patient_contact存多联系人、patient_medical_history存既往病史。这种拆分不是为了炫技而是解决两个现实问题一是医保系统对接时patient_master.id_card_no需高频 JOIN 查询单独建表可避免大宽表扫描二是患者隐私审计要求 contact 和 medical_history 分权限访问Oracle 的 Virtual Private DatabaseVPD策略可直接按 schema 级别控制。patient_master的主键是pat_id CHAR(10)非自增数字格式为P202400001P年份5位序号靠序列seq_pat_id 触发器trg_gen_pat_id生成——这里埋了第一个坑触发器里用了TO_CHAR(SYSDATE,YYYY)拼年份若跨年未重置序列起始值会生成P202399999→P2023100000这种非法 ID。修复方案见第 4 章避坑节。2.2 关键业务关系建模挂号register与门诊outpatient的 DEFERRABLE CONSTRAINT 设计挂号表register和门诊表outpatient是典型的一对一强依赖关系挂号成功必须生成门诊记录但门诊记录中的诊断、处置等字段需医生接诊后填写不能在挂号时强制非空。传统外键FOREIGN KEY (opd_id) REFERENCES outpatient(opd_id)会导致挂号事务无法提交因 outpatient 记录尚未插入。解决方案是使用DEFERRABLE INITIALLY DEFERRED外键ALTER TABLE register ADD CONSTRAINT fk_register_opd FOREIGN KEY (opd_id) REFERENCES outpatient(opd_id) DEFERRABLE INITIALLY DEFERRED;这样在挂号事务中先插入register记录此时opd_id为 NULL 或临时占位符再插入outpatient记录最后在事务 COMMIT 前显式SET CONSTRAINTS fk_register_opd IMMEDIATE触发检查。该机制让业务流程与数据库约束解耦比用触发器或应用层校验更可靠。注意此约束要求outpatient.opd_id必须是主键或有唯一索引否则报 ORA-02270。2.3 药品库存drug_stock的 MVLOG FAST REFRESH 实现实时库存扣减药品库存变动频繁但报表需实时展示各药房库存量。若每次UPDATE drug_stock SET qty qty - ? WHERE drug_id ?后都查全表聚合性能灾难。本项目采用物化视图日志MVLOG 快速刷新FAST REFRESH方案-- 1. 在 drug_stock 上建日志关键INCLUDING NEW VALUES CREATE MATERIALIZED VIEW LOG ON drug_stock TABLESPACE users WITH SEQUENCE, ROWID (drug_id, qty, pharmacy_id), INCLUDING NEW VALUES; -- 2. 创建物化视图按药房聚合 CREATE MATERIALIZED VIEW mv_drug_stock_summary BUILD IMMEDIATE REFRESH FAST ON COMMIT AS SELECT pharmacy_id, drug_id, SUM(qty) AS total_qty FROM drug_stock GROUP BY pharmacy_id, drug_id;当drug_stock表被 UPDATEOracle 自动捕获变更行通过日志中的 ROWID在 COMMIT 时增量更新mv_drug_stock_summary查询报表时直接SELECT * FROM mv_drug_stock_summary即可响应时间稳定在 5ms 内。这是 Oracle 针对高并发更新实时查询场景的原生解法比 Redis 缓存双写一致性更省心。2.4 收费charge与医保结算insurance_settle的 AUTONOMOUS_TRANSACTION 封装医保结算需独立于主事务即使收费失败医保预授权也必须回滚反之医保拒付时收费必须取消。项目将结算逻辑封装在自治事务过程proc_settle_charge中CREATE OR REPLACE PROCEDURE proc_settle_charge( p_charge_id IN charge.charge_id%TYPE, p_result OUT VARCHAR2 ) IS PRAGMA AUTONOMOUS_TRANSACTION; -- 关键独立事务上下文 v_insurance_status insurance_settle.status%TYPE; BEGIN -- 1. 更新医保结算状态独立事务 UPDATE insurance_settle SET status PROCESSED, settle_time SYSDATE WHERE charge_id p_charge_id; -- 2. 检查医保返回结果 SELECT status INTO v_insurance_status FROM insurance_settle WHERE charge_id p_charge_id; IF v_insurance_status APPROVED THEN p_result : SUCCESS; COMMIT; -- 提交自治事务 ELSE p_result : REJECTED; ROLLBACK; -- 回滚自治事务 END IF; EXCEPTION WHEN NO_DATA_FOUND THEN p_result : NO_SETTLE_RECORD; ROLLBACK; END;Java 层调用时只需cs.setString(1, chargeId); cs.registerOutParameter(2, Types.VARCHAR); cs.execute();获取结果。自治事务确保医保侧操作不影响主收费事务的原子性这是医疗支付类系统的硬性要求。2.5 病历medical_record的 SecureFile LOB 存储与脱敏策略病历文本、检查报告 PDF、影像 DICOM 文件均存于medical_record.content字段类型为BLOB。项目启用 Oracle 11g 的SecureFile LOB非传统 BasicFileALTER TABLE medical_record MODIFY content BLOB STORE AS SECUREFILE ( COMPRESS HIGH ENCRYPT USING AES256 RETENTION MAX );COMPRESS HIGH对 PDF/DICOM 等二进制文件压缩率超 60%节省 40% 存储ENCRYPT USING AES256透明加密无需应用层处理密钥RETENTION MAX防止 LOB 段碎片化提升大对象读写性能。同时为满足等保要求创建 VPD 策略限制非授权人员查看完整病历CREATE OR REPLACE FUNCTION fnc_vpd_medical_record(p_schema VARCHAR2, p_obj VARCHAR2) RETURN VARCHAR2 AS v_role VARCHAR2(30); BEGIN SELECT SYS_CONTEXT(USERENV,SESSION_USER) INTO v_role FROM DUAL; IF v_role IN (DOCTOR, NURSE) THEN RETURN 11; -- 全部可见 ELSIF v_role ADMIN THEN RETURN status ! DRAFT; -- 不见草稿 ELSE RETURN content IS NULL; -- 其他角色只能看到元数据LOB 内容为空 END IF; END; BEGIN DBMS_RLS.ADD_POLICY( object_schema HOSPITAL, object_name MEDICAL_RECORD, policy_name pol_medical_record_vpd, function_schema HOSPITAL, policy_function fnc_vpd_medical_record, statement_types SELECT ); END;这套组合拳让病历存储既高效又合规远超简单VARCHAR2(4000)的粗暴方案。3. Java 层怎么用不是 CRUD 模板而是聚焦 CallableStatement 调用存储过程的 4 个关键姿势3.1 连接池配置为什么用 HikariCP 而非 Druid关键在 connection-test-query项目 Java 层使用 HikariCP 4.0.3非 Druid原因在于 Oracle 的连接有效性检测机制特殊。Druid 默认validationQuerySELECT 1但 Oracle 无SELECT 1语法需SELECT 1 FROM DUAL且其连接空闲超 30 分钟易被防火墙中断。HikariCP 的connection-test-query配置更精准HikariConfig config new HikariConfig(); config.setJdbcUrl(jdbc:oracle:thin:192.168.1.100:1521:orcl); config.setUsername(hospital_app); config.setPassword(pwd123); config.setConnectionTestQuery(SELECT 1 FROM DUAL); // 必须带 FROM DUAL config.setConnectionTimeout(30000); config.setIdleTimeout(600000); // 10分钟空闲超时 config.setMaxLifetime(1800000); // 30分钟最大存活强制重连防长连接失效 config.setMaximumPoolSize(20); config.setMinimumIdle(5);提示setMaxLifetime设为 30 分钟是血泪经验。Oracle 11g/12c 中长时间空闲连接在数据库端可能被SQLNET.EXPIRE_TIME参数踢出导致 Java 层报IO Error: Connection reset。设此参数可让连接池主动淘汰旧连接避免首次请求失败。3.2 调用挂号存储过程CallableStatement 的 OUT 参数与数组绑定挂号核心逻辑在proc_register_patient过程中它接收患者 ID、科室 ID、医生 ID、预约时间并返回挂号单号、门诊号、状态码。Java 调用需处理多个 OUT 参数及日期类型String sql {CALL proc_register_patient(?, ?, ?, ?, ?, ?, ?)}; try (CallableStatement cs conn.prepareCall(sql)) { cs.setString(1, P202400001); // patient_id cs.setString(2, DEPT001); // dept_id cs.setString(3, DOC001); // doctor_id cs.setTimestamp(4, Timestamp.valueOf(2024-06-15 08:00:00)); // reg_time // 注册 OUT 参数 cs.registerOutParameter(5, Types.VARCHAR); // out_reg_no cs.registerOutParameter(6, Types.VARCHAR); // out_opd_no cs.registerOutParameter(7, Types.INTEGER); // out_status_code cs.execute(); String regNo cs.getString(5); String opdNo cs.getString(6); int statusCode cs.getInt(7); if (statusCode 0) { System.out.println(挂号成功: regNo , 门诊号: opdNo); } else { throw new RuntimeException(挂号失败错误码: statusCode); } }注意setTimestamp必须用java.sql.Timestamp不能用java.util.Date否则 Oracle 报ORA-01858。registerOutParameter的参数索引从 1 开始与?位置严格对应错一位就SQLException。3.3 批量处方药品插入ArrayDescriptor ARRAY 实现高效批量开处方时需一次性插入多条药品记录如 5 种药若逐条INSERT网络往返耗时严重。项目用 Oracle 的ARRAY类型 ArrayDescriptor批量提交// 1. 构建药品对象数组假设已定义 PrescriptionDrug 类 PrescriptionDrug[] drugs { new PrescriptionDrug(DRUG001, 2, 每次1片每日3次), new PrescriptionDrug(DRUG002, 1, 每次0.5g每日2次) }; // 2. 创建 Oracle ARRAY ArrayDescriptor descriptor ArrayDescriptor.createDescriptor(T_DRUG_LIST, conn); ARRAY array new ARRAY(descriptor, conn, drugs); // 3. 调用存储过程过程内用 FORALL INSERT String callSql {CALL proc_insert_prescription_drugs(?, ?)}; try (CallableStatement cs conn.prepareCall(callSql)) { cs.setString(1, OPD202400001); // 门诊号 cs.setArray(2, array); // 药品数组 cs.execute(); }对应存储过程proc_insert_prescription_drugs内部用FORALL i IN INDICES OF p_drug_list SAVE EXCEPTIONS INSERT ...单次调用完成 N 条插入性能比循环快 5~8 倍。T_DRUG_LIST是用户定义的嵌套表类型CREATE TYPE T_DRUG_LIST AS TABLE OF T_DRUG_ITEM;T_DRUG_ITEM是对象类型。3.4 事务边界控制为什么 Service 方法必须加 Transactional且 propagation REQUIREDJava 层所有业务 Service 方法均标注Transactional(propagation Propagation.REQUIRED)原因在于 Oracle 存储过程已承担部分事务职责Java 层需与之协同Service public class RegisterService { Transactional(propagation Propagation.REQUIRED) public RegisterResult registerPatient(RegisterRequest req) { // 步骤1调用挂号过程内部含自治事务 String regNo callRegisterProc(req); // 步骤2生成电子发票调用另一过程 generateInvoice(regNo); // 步骤3发送短信通知调用外部 HTTP需保证前两步成功 sendSMSNotification(regNo); return new RegisterResult(regNo); } }Propagation.REQUIRED确保整个方法运行在同一个数据库事务中若generateInvoice失败callRegisterProc的变更自动回滚即使其内部有自治事务自治事务只影响自身逻辑不破坏外层事务一致性若sendSMSNotification抛异常前两步全部回滚避免“挂号成功但没发短信”的状态不一致。注意切勿用Propagation.REQUIRES_NEW否则挂号和开票变成两个独立事务无法保证 ACID。4. 避坑生产环境踩过的 4 个 Oracle 黑匣子以及 Java 层对应的 3 个玄学报错4.1 现象sqlplus / as sysdba登录极慢或报ORA-12170: TNS:Connect timeout occurred原因Oracle 监听器listener.ora中未禁用反向 DNS 解析。客户端 IP 连入时Oracle 会尝试反向解析 IP 对应主机名若 DNS 服务器不可达或超时等待长达 60 秒。解决编辑$ORACLE_HOME/network/admin/listener.ora在LISTENER段添加INBOUND_CONNECT_TIMEOUT_LISTENER0并重启监听器lsnrctl stop lsnrctl start。同时在$ORACLE_HOME/network/admin/sqlnet.ora中添加SQLNET.INVITED_NODES(192.168.1.0/24,localhost) SQLNET.EXPIRE_TIME10前者白名单允许连接后者每 10 分钟探测连接活性防防火墙中断。4.2 现象Java 调用存储过程时cs.getString(5)返回 null但数据库中该 OUT 参数有值原因Oracle 存储过程中OUT参数被赋值为NULL而 Java 的getString()对NULL返回null但开发者误以为是过程未执行。更隐蔽的是过程内SELECT ... INTO未查到数据触发NO_DATA_FOUND异常但被EXCEPTION WHEN OTHERS THEN NULL;吞掉导致 OUT 参数保持初始NULL。解决在存储过程中为所有 OUT 参数设默认值并显式处理NO_DATA_FOUNDBEGIN SELECT doc_name INTO v_doc_name FROM doctor WHERE doc_id p_doc_id; EXCEPTION WHEN NO_DATA_FOUND THEN v_doc_name : UNKNOWN_DOCTOR; -- 非 NULL 默认值 v_status_code : -1; WHEN OTHERS THEN v_doc_name : ERROR; v_status_code : -2; END;Java 层改用wasNull()判断if (!cs.wasNull()) { String name cs.getString(5); }。4.3 现象INSERT INTO drug_stock批量插入时报ORA-00060: deadlock detected while waiting for resource原因多线程并发更新同一药品如DRUG001的库存Oracle 行锁升级为表锁。drug_stock表无合适索引WHERE drug_id ?全表扫描锁住大量无关行。解决在drug_stock(drug_id, pharmacy_id)上建复合索引CREATE INDEX idx_drug_stock_pk ON drug_stock(drug_id, pharmacy_id) TABLESPACE users;并确保 Java 批量插入时按(drug_id, pharmacy_id)排序后再提交减少锁竞争。测试表明加索引后死锁率从 12% 降至 0.3%。4.4 现象mv_drug_stock_summary物化视图刷新失败报ORA-12008: error in materialized view refresh path原因物化视图日志MVLOG损坏或未启用INCLUDING NEW VALUES。当drug_stock表被TRUNCATE而非DELETE时MVLOG 不记录变更导致快速刷新丢失数据。解决检查 MVLOG 是否启用新值SELECT LOG_TABLE, INCLUDE_NEW_VALUES FROM USER_MVIEW_LOGS WHERE MASTER DRUG_STOCK;若为NO重建日志DROP MATERIALIZED VIEW LOG ON drug_stock; CREATE MATERIALIZED VIEW LOG ON drug_stock WITH SEQUENCE, ROWID (drug_id, qty), INCLUDING NEW VALUES;禁止在生产环境用TRUNCATE改用DELETE FROM drug_stockCOMMIT确保 MVLOG 捕获。5. 验证与压测用 3 个真实 SQL 场景检验数据库设计是否经得起推敲附赠一份可直接跑的验证脚本5.1 场景一高峰期挂号并发验证——模拟 50 个医生同时放号检查号源扣减一致性真实医院早 8 点放号50 名医生每人次放 20 个号共 1000 个号源。需验证号源表reg_source的available_qty是否精确扣减register表中无重复doctor_id reg_time组合防超挂事务失败时号源不被错误扣减。验证脚本verify_reg_concurrency.sql-- 1. 检查号源扣减总数是否等于挂号总数 SELECT (SELECT SUM(initial_qty - available_qty) FROM reg_source) AS total_deducted, (SELECT COUNT(*) FROM register WHERE reg_date TRUNC(SYSDATE)) AS total_registered FROM DUAL; -- 2. 检查是否存在同一医生同一时段重复挂号业务规则违反 SELECT doctor_id, reg_time, COUNT(*) as cnt FROM register WHERE reg_date TRUNC(SYSDATE) GROUP BY doctor_id, reg_time HAVING COUNT(*) 1; -- 3. 检查挂号单号是否连续验证序列触发器未跳号 SELECT MIN(TO_NUMBER(SUBSTR(reg_no, 2))) as min_no, MAX(TO_NUMBER(SUBSTR(reg_no, 2))) as max_no, COUNT(*) as actual_count, MAX(TO_NUMBER(SUBSTR(reg_no, 2))) - MIN(TO_NUMBER(SUBSTR(reg_no, 2))) 1 as expected_count FROM register WHERE reg_date TRUNC(SYSDATE);运行结果应为total_deducted total_registered第二条无返回行第三条actual_count expected_count。若不等说明序列或触发器有缺陷。5.2 场景二处方药品库存联动验证——开一张含 3 种药的处方检查库存是否实时扣减开处方PRE202400001含药品DRUG001(Qty2)、DRUG002(Qty1)、DRUG003(Qty5)执行后验证drug_stock表中对应药品qty减少mv_drug_stock_summary物化视图中total_qty同步更新medical_record表中contentLOB 大小与处方 PDF 一致。验证脚本verify_prescription_stock.sql-- 1. 获取处方药品明细 SELECT d.drug_id, d.qty, s.qty as stock_before FROM prescription_drug d JOIN drug_stock s ON d.drug_id s.drug_id AND s.pharmacy_id PHARM001 WHERE d.pres_id PRE202400001; -- 2. 检查物化视图是否刷新对比刷新前后 SELECT drug_id, total_qty FROM mv_drug_stock_summary WHERE drug_id IN (DRUG001,DRUG002,DRUG003) AND pharmacy_id PHARM001; -- 3. 验证 LOB 大小假设处方 PDF 存于 medical_record.content SELECT m.record_id, DBMS_LOB.GETLENGTH(m.content) as lob_size_bytes, ROUND(DBMS_LOB.GETLENGTH(m.content)/1024,2) as size_kb FROM medical_record m WHERE m.record_id PRE202400001;关键指标stock_before - qty应等于mv_drug_stock_summary.total_qty当前值size_kb应与原始 PDF 文件大小一致误差 1KB。5.3 场景三医保结算异常流验证——模拟医保拒付检查收费与结算状态是否回滚一致医保接口返回REJECTED需验证charge表中该笔费用status CANCELLEDinsurance_settle表中status REJECTEDdrug_stock库存已回滚因处方未生效。验证脚本verify_insurance_rollback.sql-- 1. 检查收费状态 SELECT charge_id, amount, status, cancel_reason FROM charge WHERE charge_id CHG202400001; -- 2. 检查医保结算状态 SELECT charge_id, status, reject_code, reject_reason FROM insurance_settle WHERE charge_id CHG202400001; -- 3. 检查库存是否回滚对比开方前后的 stock SELECT s1.drug_id, s1.qty as qty_after_rollback, s2.qty as qty_before_prescribe, (s2.qty - s1.qty) as rollback_qty FROM drug_stock s1 JOIN ( SELECT drug_id, qty FROM drug_stock_history WHERE charge_id CHG202400001 AND action PRESCRIBE ) s2 ON s1.drug_id s2.drug_id WHERE s1.pharmacy_id PHARM001;理想结果charge.status CANCELLEDinsurance_settle.status REJECTEDrollback_qty为正数且等于处方数量。6. 进阶技巧用 Oracle SQL Developer Data Modeler 逆向工程这份数据库生成带注释的 ER 图与 DDL 文档6.1 为什么不用手工画 ER 图SQL Developer Data Modeler 的三大不可替代性手工画 ER 图最大的问题是“图”与“库”脱节表结构改了图没更新团队新人照图开发必翻车。SQL Developer Data ModelerSDDM的逆向工程Reverse Engineer功能能从真实数据库一键生成同步的 ER 图且支持自动提取 COMMENTCOMMENT ON TABLE patient_master IS 患者主信息表含身份证、姓名、性别等;会被转为图中表的注释框识别逻辑关系FOREIGN KEY (dept_id) REFERENCES department(dept_id)自动连线并标注1..*基数导出带格式的 DDL 文档生成 Word/PDF含表结构、字段说明、约束详情可直接作交付文档。这不是锦上添花而是把数据库设计从“黑盒”变成“可审计资产”的关键一步。6.2 逆向工程实操步骤5 分钟生成可交付的 ER 图与文档步骤 1连接数据库打开 SQL Developer → 工具 → 数据建模器 → 新建工作区 → 连接数据库输入 hospital_app 用户非 sys。步骤 2逆向工程右键工作区 → “逆向工程” → 选择 schemaHOSPITAL→ 勾选“包含表”“包含视图”“包含约束”“包含注释” → 点击“确定”。SDDM 自动扫描 23 张表构建逻辑模型。步骤 3优化布局与导出自动布局右键模型 → “自动布局”SDDM 按模块分组如 patient、register、drug 相关表聚在一起添加注释双击register表 → “属性”页 → “注释”栏粘贴业务说明“挂号表记录患者预约信息与 outpatient 表 DEFERRABLE 外键关联”导出图片文件 → 导出 → 图像 → PNG分辨率设 300dpi供 PPT 汇报导出文档文件 → 导出 → DDL 文档 → 选择“Word 格式”勾选“包含表注释”“包含列注释”“包含约束描述”生成HOSPITAL_ER_Document.docx。提示导出的 Word 文档中“字段说明”列会显示COMMENT ON COLUMN register.reg_no IS 挂号单号格式P202400001;的原文新人一眼看懂字段含义无需翻代码。6.3 用 Data Modeler 做设计合规检查3 个必检项与自动报告SDDM 内置“设计规则检查器”Design Rule Checker可自动化审计数据库设计质量。针对本项目我固定运行以下 3 项检查检查项规则说明本项目结果不合规后果PK01所有表必须有主键检查user_constraints.constraint_type P23/23 表通过无主键表无法建立外键JOIN 性能差Hibernate 映射失败CK03所有 VARCHAR2 字段必须有 COMMENT检查user_col_comments是否为空100% 覆盖如patient_master.id_card_no注释为“18位身份证号含X”字段含义模糊新人理解成本高审计不通过FK02外键必须引用主键或唯一索引检查user_constraints.r_constraint_name是否指向 PK/UK100% 合规如register.doctor_id引用doctor.doc_id PK外键无效数据一致性无法保障运行方式模型 → 检查设计规则 → 选择上述规则 → “运行”。SDDM 生成 HTML 报告点击违规项可直接跳转到问题表。我每次提交数据库变更前必跑此检查确保设计零瑕疵。从那以后我每次给新人交接数据库都不再发一堆 SQL 文件而是直接给一个.dmd模型文件 一份导出的 Word 文档。新人用 SDDM 打开模型鼠标悬停就能看字段注释双击表就能看关联关系比读 1000 行 DDL 高效十倍。希望帮到你。本文还有配套的精品资源点击获取
返回列表