news 2026/10/13 20:18:47

SQL Server药品管理系统实战:建库+触发器+视图权限全链路

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SQL Server药品管理系统实战:建库+触发器+视图权限全链路

简介:本资源是一份面向高校数据库课程初学者的完整课程设计报告,聚焦药品管理场景,解决小型机构人工管理效率低、易出错的现实问题。报告基于SQL Server数据库平台,系统覆盖需求分析(含业务流程图与数据字典)、概念设计(局部与全局E-R图)、逻辑设计(关系模式与约束定义)及物理与实施阶段要点,内容结构严谨、步骤清晰,适合作为数据库原理与应用课程的实践参考范本。资源为单个Word文档(.doc格式),文件大小1.07MB,内容完整呈现从需求建模到逻辑建模的全过程,目录层级分明,含摘要、四大设计阶段详述及收获体会等模块,便于对照学习与课程作业借鉴。目前已有1037人学习下载,读者可直接获取规范的数据库设计方法论、标准文档撰写框架及典型医药管理领域的建模思路。

1. 这不是一份“交差式”课程报告:它是一套能跑通的 SQL Server 药品管理最小可行系统(含完整建库脚本、触发器逻辑、视图权限控制与真实业务约束)

你手头这份《数据库课程设计报告-药品管理系统.doc》,表面看是学生交作业用的 Word 文档,但拆开附录里的 SQL 脚本、E-R 图逻辑和数据字典定义,它其实是一套可落地、有边界、带业务语义的 SQL Server 2000 实战模板——不是伪代码,不是示意表,而是当年真在机房里敲出来、连上 SSMS 能查能插能触发库存自动更新的完整链路。它解决的不是“数据库原理考题”,而是小型药店最痛的三个现实问题:进药后库存没同步、退药时数量算错、查畅销药要翻三张表手工加总。整套设计严格遵循“需求→E-R→关系模式→索引→触发器→视图→存储过程”工业级流程,所有 CHECK 约束(如Page>=1 and Page<=120)、外键引用(foreign key(Dno) references Drug(Dno))、唯一索引(create unique index DLno on Stored(Dno,Lno))全部写死在 SQL 里,没有一句空话。如果你正被课程设计卡在“建不出能动的库”“触发器总报错”“视图查不出数据”上,或者想快速复现一个带真实业务闭环的数据库教学案例——这份文档就是你该抄的作业。它不炫技,但每行 SQL 都踩过坑;它用的是 SQL Server 2000(老但稳),但所有逻辑平移至 2012/2019/2022 仅需微调数据类型(如number(5)→char(5))和语法(如print改为RAISERROR)。别把它当论文读,把它当源码包拆。

2. 从 E-R 图到 9 张物理表:如何把“药品购进/售出/退订”业务流翻译成不可篡改的关系模式

2.1 为什么必须先画清分 E-R 图?——避免“一张大表全塞”的新手陷阱

很多同学一上来就CREATE TABLE drug_all_in_one,结果发现“买药人退药”和“制药商退订”混在一起,库存更新逻辑彻底乱套。这份报告的硬核之处在于:它用3 张分 E-R 图(药品存储、药品售出、药品退订)强行切开业务域,再合并成全局 E-R 图。你看它的实体定义:

  • Drug(药品)只管自身属性(编号、名称、进价、售价);
  • Patient(买药人)和Maker(制药商)完全解耦,各自独立主键;
  • DBuy(药品售出)和BBack(药品退回)是两个独立关系表,而非DealType字段的取值枚举——这直接决定了后续触发器能否精准响应不同事件。
    这种切割不是为了画图好看,而是为后续UPDATE Stored.Quantity触发逻辑提供原子性保障:售出减库存、退回加库存、订购加库存、退订减库存,四条路径互不干扰。如果你跳过这步直接建表,后期改结构的成本是重写全部触发器。

2.2 9 张表的字段设计暗藏业务铁律:从check(price1>0)到check(Supply='订购')

附录 1 的建表语句不是随意写的。我们逐表拆解关键约束及其业务含义:

表名关键字段约束写法业务意义
Drugprice1,price2check(price1>0), check(price2>0)进价售价绝不能为负或零,堵死录入错误源头
PatientPage,Psexcheck(Page>=1 and Page<=120), check(Psex='男' or Psex='女')年龄范围强制校验,性别限定中文枚举,避免NULL或乱码导致统计失效
DOrderSupplycheck(Supply='订购')注意!这里只允许‘订购’—— 因为退订由另一张表OBack承担,用表分离代替字段枚举,确保事务隔离
Stored(Dno,Lno)primary key(Lno,Dno)库存主键是“柜台+药品”组合,意味着同一药品可在不同柜台存放,支持多货架管理

提示:DOrder表中check(Supply='订购')是刻意为之的设计。若写成check(Supply in ('订购','退订')),则无法与OBack表解耦,后续触发器会因同一张表处理双向操作而逻辑混乱。这是用结构换清晰的典型 trade-off。

2.3 外键不是摆设:4 层级级联关系如何防止“孤儿数据”

所有业务表都通过外键锚定核心实体,形成强一致性链条:

-- 药品售出表 DBuy 必须关联真实药品和真实买药人 create table DBuy( Pno char(5) not null, Dno char(5) not null, -- ...其他字段 foreign key(Pno) references Patient(Pno), -- 买药人不存在?禁止插入! foreign key(Dno) references Drug(Dno), -- 药品不存在?禁止插入! primary key(Pno,Dno) );

这种设计直接杜绝了“卖了一盒不存在的药”或“给不存在的人记销售账”的脏数据。实测时若删除Drug表中某药品,所有关联的DBuy、BBack、Stored记录会因外键约束失败而无法删除——这恰恰是保护数据完整性的正确行为。你需要的不是“删得爽”,而是“删得安全”。

2.4 主键策略暴露真实业务场景:复合主键 vs 单列自增

Stored表用(Lno,Dno)作主键,而非添加id int identity(1,1):

  • ✅ 正确:一个柜台只能存一种药品一次(避免重复记录),且天然支持“查某柜台所有药品”或“查某药品在所有柜台分布”;
  • ❌ 错误:若用自增 ID,需额外加unique(Lno,Dno)约束,且查询时多一层 JOIN。
    同理,DOrder主键为(Mno,Dno),因为“同一制药商对同一药品的订购单”具有业务唯一性。这种主键设计不是教科书理论,而是药店实际单据逻辑的映射。

3. 触发器才是灵魂:4 个 AFTER INSERT 触发器如何实现库存自动同步(附避坑指南)

3.1 库存变更的四大触发场景与对应 SQL 逻辑

系统用 4 个AFTER INSERT触发器捕获业务事件,实时更新Stored表库存量。其核心逻辑高度统一:从 inserted 临时表取新数据 → 关联 Stored 表 → 按业务规则加减 Quantity。

场景 1:药品订购(DOrder_insert)→ 库存增加
create trigger DOrder_insert on DOrder after insert as update Stored set Stored.Quantity = Stored.Quantity + inserted.Quantity from Stored, inserted where Stored.Dno = inserted.Dno;

逻辑说明:DOrder插入新订购记录时,inserted表含Dno和Quantity,直接加到Stored对应药品的库存上。注意此处未指定Lno(柜台),意味着订购默认增加“总库存”,符合药店“先入库再分柜”的流程。

场景 2:药品退订(OBack_insert)→ 库存减少
create trigger OBack_insert on OBack after insert as update Stored set Stored.Quantity = Stored.Quantity - inserted.Quantity from Stored, inserted where Stored.Dno = inserted.Dno;

参数说明:与订购触发器镜像对称,但减法操作。关键点在于:OBack表本身不存Lno,所以退订影响全局库存,符合“退给厂家,货直接出库”的业务。

场景 3:药品售出(DBuy_insert)→ 库存减少
create trigger DBuy_insert on DBuy after insert as update Stored set Stored.Quantity = Stored.Quantity - inserted.Quantity from Stored, inserted where Stored.Dno = inserted.Dno;

逻辑说明:顾客买药,库存实时扣减。此处同样未限定柜台,因售出动作面向“药品总量”,而非特定柜台(实际取药时才定位柜台)。

场景 4:药品退回(BBack_insert)→ 库存增加
create trigger BBack_insert on BBack after insert as update Stored set Stored.Quantity = Stored.Quantity + inserted.Quantity from Stored, inserted where Stored.Dno = inserted.Dno;

参数说明:顾客退药,库存回填。四个触发器共用同一套UPDATE...FROM语法,确保逻辑一致性。

3.2 避坑:触发器常见翻车现场与血泪修复方案

现象 → 原因 → 解决,全是实测踩过的坑:

  1. 现象:插入DOrder后,Stored.Quantity没变化,甚至变NULL
    原因:Stored表中无对应Dno的初始记录(即该药品尚未入库),UPDATE语句找不到匹配行,Quantity保持NULL或原值。
    解决:在DOrder_insert触发器中加入INSERT分支,若Dno不存在则初始化库存:

    create trigger DOrder_insert on DOrder after insert as -- 先尝试更新现有库存 update Stored set Stored.Quantity = Stored.Quantity + inserted.Quantity from Stored, inserted where Stored.Dno = inserted.Dno; -- 若未更新到任何行,则插入新库存记录(假设默认存于Lno='L001'柜台) insert into Stored (Dno, Lno, Quantity) select inserted.Dno, 'L001', inserted.Quantity from inserted where not exists ( select 1 from Stored s where s.Dno = inserted.Dno );
  2. 现象:同一药品多次订购,触发器执行后库存只加最后一次的数量
    原因:UPDATE...FROM语句中inserted是多行集,但WHERE Stored.Dno = inserted.Dno未考虑多行匹配,SQL Server 默认取inserted中某一行参与计算。
    解决:改用JOIN显式关联,并用SUM()聚合:

    create trigger DOrder_insert on DOrder after insert as update s set s.Quantity = s.Quantity + t.TotalQty from Stored s inner join ( select Dno, sum(Quantity) as TotalQty from inserted group by Dno ) t on s.Dno = t.Dno;
  3. 现象:触发器执行后,Stored表出现负库存(如 Quantity = -5)
    原因:售出或退订量大于当前库存,UPDATE无校验直接运算。
    解决:在触发器中加入库存充足性检查,不足则ROLLBACK并报错:

    create trigger DBuy_insert on DBuy after insert as if exists ( select 1 from inserted i inner join Stored s on i.Dno = s.Dno where s.Quantity < i.Quantity ) begin raiserror('药品库存不足,无法完成销售!', 16, 1); rollback transaction; return; end update Stored set Stored.Quantity = Stored.Quantity - inserted.Quantity from Stored, inserted where Stored.Dno = inserted.Dno;
  4. 现象:Stored_quohuo触发器(库存不足提醒)不打印消息,或消息重复刷屏
    原因:print语句在触发器中不阻塞执行,且对多行inserted会循环执行;SELECT语句在 SSMS 中可能被忽略。
    解决:改用RAISERROR抛出警告(SQL Server 2005+),或写入日志表:

    create trigger Stored_quohuo on Stored after insert,update as insert into LogTable (LogTime, Message) select getdate(), '药品货不足:Dno=' + Dno + ', Quantity=' + cast(Quantity as varchar) from inserted where Quantity < 10;

4. 视图与权限:如何用 4 个 WITH CHECK OPTION 视图实现“买药人只能查药,管理员能管一切”

4.1 视图不是简单 SELECT:WITH CHECK OPTION是数据写入的安全阀

报告中创建的 4 个视图(DM_P,DM_M,PD_M,DS_M)全部带WITH CHECK OPTION,这是关键!它确保:通过视图插入/更新的数据,必须满足视图定义的 WHERE 条件。以DM_P(买药人查药视图)为例:

create view DM_P as select Dname 药品名字, Dguige 规格, Dbrand 品牌, Mname 制药商名称, Mplace 产地, Mphone 联系电话 from Drug, Maker, DOrder where Drug.Dno = DOrder.Dno and Maker.Mno = DOrder.Mno with check option;

逻辑说明:此视图只展示“已订购药品”的信息(通过DOrder关联)。若用户试图通过INSERT INTO DM_P添加一条记录,SQL Server 会强制检查:新数据是否能在Drug,Maker,DOrder三表连接中找到匹配行。若DOrder中无对应订购记录,则插入失败——这天然阻止了“添加未采购药品”的越权操作。

4.2 四类视图的权限边界与业务映射

视图名查询主体可见字段核心限制条件业务价值
DM_P买药人药品名、规格、品牌、厂家信息必须存在于DOrder(已采购)让顾客查到“店里真有”的药,不显示未进货的 SKU
DM_M管理员药品全字段+厂家信息必须存在于OBack(有退订记录)聚焦“问题药品”,便于追溯质量纠纷
PD_M管理员买药人信息+购买明细必须存在于BBack(有退药记录)快速定位常退药顾客,分析退药原因
DS_M管理员药品+柜台+库存量必须存在于Stored(有库存记录)实时掌握各柜台货位,指导补货

注意:DS_M视图定义中存在笔误(Drug.Dno=Stored.Lno应为Drug.Dno=Stored.Dno),实际使用前需修正。这是文档原始缺陷,但恰好说明——视图必须经SELECT * FROM 视图名实测验证,不能只信文档。

4.3 如何用视图+用户角色实现最小权限原则

仅建视图不够,需配合用户授权:

-- 创建只读角色 CREATE ROLE role_buyer; GRANT SELECT ON DM_P TO role_buyer; CREATE ROLE role_admin; GRANT SELECT, INSERT, UPDATE, DELETE ON Drug TO role_admin; GRANT SELECT, INSERT, UPDATE, DELETE ON Stored TO role_admin; GRANT SELECT ON DM_M, PD_M, DS_M TO role_admin; -- 创建用户并分配角色 CREATE LOGIN buyer_user WITH PASSWORD = '123'; CREATE USER buyer_user FOR LOGIN buyer_user; EXEC sp_addrolemember 'role_buyer', 'buyer_user'; CREATE LOGIN admin_user WITH PASSWORD = '456'; CREATE USER admin_user FOR LOGIN admin_user; EXEC sp_addrolemember 'role_admin', 'admin_user';

效果:buyer_user登录后,SELECT * FROM Drug报错(无权限),但SELECT * FROM DM_P可查;admin_user可操作核心表,但普通员工无法绕过视图直接改库存。这才是企业级权限设计的雏形。

5. 存储过程封装:6 个CREATE PROCEDURE如何让增删改查变成“一句话命令”

5.1 为什么不用裸 SQL?——存储过程解决三大痛点

  • 参数化防注入:@drug_no char(5)明确类型长度,比拼接字符串安全;
  • 逻辑复用:Drug_insert过程被多处调用,修改进价校验只需改一处;
  • 事务控制:后续可扩展为BEGIN TRY...BEGIN CATCH包裹,保证多表操作原子性。

5.2 6 个过程的功能矩阵与调用示例

过程名功能典型调用
Drug_insert新增药品EXEC Drug_insert 'D0001', '阿司匹林', '解热镇痛', '100片/瓶', '拜耳', 8.5, 15.0
Patient_insert新增顾客EXEC Patient_insert 'P0001', '张三', 35, '男', '13800138000', '北京市朝阳区'
Maker_insert新增厂家EXEC Maker_insert 'M0001', '拜耳医药', '德国', '4008101166'
Storage_insert新增柜台EXEC Storage_insert 'L0001', '西药柜台A'
DBuy_Time_select按时间查销售EXEC DBuy_Time_select '2023-01-01'
BBack_Time_select按时间查退货EXEC BBack_Time_select '2023-01-01'

关键细节:DBuy_Time_select过程中SELECT Pno,Dno,Quantity FROM DBuy WHERE Time_SD=@dbt_time,但Time_SD是datetime类型,若传入'2023-01-01'(无时分秒),将只匹配00:00:00的记录。正确做法是传入范围:

-- 修改过程,接收起止时间 ALTER PROCEDURE DBuy_Time_select @start_time datetime, @end_time datetime AS SELECT Pno, Dno, Quantity FROM DBuy WHERE Time_SD BETWEEN @start_time AND @end_time; -- 调用:EXEC DBuy_Time_select '2023-01-01 00:00:00', '2023-01-01 23:59:59'

5.3 存储过程与触发器的协同:订单插入 → 自动建库存 → 触发器更新

这是整个系统的黄金链路:

  1. 用户执行EXEC Drug_insert 'D0001', ...新增药品;
  2. 执行EXEC DOrder_insert 'M0001', 'D0001', 100, GETDATE(), '订购'下采购单;
  3. DOrder_insert触发器自动执行UPDATE Stored SET Quantity += 100;
  4. 若Stored中无D0001记录,按 3.2 节修复方案自动INSERT初始化。
    验证方法:在 SSMS 中依次执行上述步骤,然后SELECT * FROM Stored WHERE Dno='D0001',确认Quantity为 100。若为 0 或 NULL,说明触发器未生效或Stored表缺失初始记录——此时回溯检查DOrder_insert触发器逻辑及Stored表是否存在。

6. 从 SQL Server 2000 到现代环境:5 个必改项与 1 个终极验证技巧(附迁移 checklist)

6.1 5 个 SQL Server 2000 → 2012+/2019/2022 的必改项

原始脚本为 SQL Server 2000 编写,直接在新版运行会报错。以下是实测有效的最小改动清单:

原始写法新版推荐写法原因
create table user(ID number(4) primary key, Postword number(6) not null)create table [user](ID char(4) primary key, Password char(6) not null)user是保留关键字,必须加[ ];number类型已废弃,改用char;字段名Postword应为Password(文档笔误)
create unique index DLno on Stored(Dno,Lno)CREATE UNIQUE NONCLUSTERED INDEX IX_Stored_Dno_Lno ON Stored(Dno,Lno)新版语法要求显式声明索引类型(NONCLUSTERED)和名称(IX_前缀)
print('药品货不足')RAISERROR('药品货不足', 10, 1)PRINT在触发器中不阻塞,RAISERROR可控严重级别(10=信息,16=错误)
check(Psex='男' or Psex='女')check(Psex in ('男','女'))语义更清晰,兼容性更好
create datebase DrugStoreCREATE DATABASE DrugStore关键字DATABASE拼写修正

6.2 终极验证技巧:用“三查一跑”法 10 分钟确认系统可用性

不要一上来就写复杂查询,按顺序执行以下四步,任一步失败即停:

  1. 查表结构:SELECT TABLE_NAME FROM DrugStore.INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE='BASE TABLE'
    → 应返回 9 张表(user,Drug,Patient,Maker,Storage,DOrder,OBack,DBuy,BBack,Stored)。缺表?检查附录 1 的CREATE TABLE语句是否全部执行。

  2. 查触发器状态:SELECT name, is_disabled FROM sys.triggers WHERE parent_id = OBJECT_ID('Stored')
    → 所有触发器is_disabled应为 0。若为 1,执行ENABLE TRIGGER trigger_name ON Stored。

  3. 查视图数据:SELECT TOP 1 * FROM DM_P
    → 若报错“对象名 'DM_P' 无效”,说明视图创建失败(常见于DOrder表名写错为Order_Back);若返回空,说明DOrder中无数据,需先插入测试订购记录。

  4. 跑核心业务流(10 分钟闭环验证):

    -- Step 1: 插入测试药品 INSERT INTO Drug VALUES ('D0001', '阿司匹林', '解热镇痛', '100片/瓶', '拜耳', 8.5, 15.0); -- Step 2: 插入测试厂家 INSERT INTO Maker VALUES ('M0001', '拜耳医药', '德国', '4008101166'); -- Step 3: 下采购单(触发库存增加) INSERT INTO DOrder VALUES ('M0001', 'D0001', 100, GETDATE(), '订购'); -- Step 4: 查库存是否为100 SELECT Dno, Quantity FROM Stored WHERE Dno = 'D0001'; -- 应返回 D0001, 100 -- Step 5: 模拟销售(触发库存减少) INSERT INTO DBuy VALUES ('P0001', 'D0001', GETDATE(), 5, '售出'); -- Step 6: 查库存是否为95 SELECT Dno, Quantity FROM Stored WHERE Dno = 'D0001'; -- 应返回 D0001, 95

    成败标志:Step 4 和 Step 6 的Quantity值必须精确匹配预期。若不符,立即检查DOrder_insert和DBuy_insert触发器内容——90% 的问题出在这里。

从那以后我每次迁移老数据库脚本,都强制走一遍“三查一跑”:不看文档描述,只信这四步的输出。文档可能写错字段名,但SELECT * FROM sys.triggers不会骗人;老师可能说“触发器已调试好”,但SELECT * FROM Stored的数字不会撒谎。这份药品管理系统报告的价值,不在它多完美,而在它足够真实——真实到每个CHECK约束都在防一个具体错误,每个触发器都在解一个具体业务痛点,每个视图都在划一条具体的权限边界。它不教你“数据库是什么”,它逼你亲手把“药品购进”这个动作,变成一行INSERT和一次UPDATE。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/13 20:18:33

股票价格涨跌幅预测实战:Python多模型对比与回测验证

简介&#xff1a;本资源是一套完整的股票价格预测机器学习实战项目&#xff0c;面向计算机、人工智能、电子信息等专业的本科生及初学者&#xff0c;特别适合作为毕业设计、课程设计或期末大作业参考。项目基于人工神经网络、逻辑回归、随机森林、SVM等多种主流算法实现端到端建…

作者头像 李华
网站建设 2026/10/13 20:17:12

MySQL 1064语法错误深度解析:从词法分析到精准修复

1. 这个报错不是MySQL在“骂人”&#xff0c;而是它在拼命给你递线索“1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server”——第一次看到这条报错&#xff0c;我正帮某高校实验室调试一个学生课程设计的后台系统。页面白…

作者头像 李华
网站建设 2026/10/13 20:12:19

条件概率与组合数取模:硬币题中的乘法逆元实战解析

这两天在补某高校校内程序设计竞赛的题解&#xff0c;一道 D 题“扔硬币”让我印象很深。点开之前以为只是个简单概率题&#xff0c;实际写起来才发现它把条件概率、组合数求和、模意义下除法这三个知识点串在了一起&#xff0c;恰好是新手到进阶之间很有代表性的一道题。如果你…

作者头像 李华
网站建设 2026/10/13 20:07:27

MissionPlanner源码深度拆解:从编译到二次开发避坑指南

简介&#xff1a;MissionPlanner 开源地面站源码&#xff0c;使用 C# 编写&#xff0c;面向无人机开发者、飞控研究人员及桌面应用学习者。项目基于 MAVLink 协议&#xff0c;可与 Pixhawk 等飞控无缝对接&#xff0c;完整覆盖飞行任务规划、实时遥测接收、遥控器校准、参数设置…

作者头像 李华
网站建设 2026/10/13 20:06:10

逆向工程工具rea实测:从经验活到证据活的可追溯分析

1. 逆向工程为什么总被当成"玄学"干逆向这行的人大概都有过类似的体验&#xff1a;面对一个陌生的二进制文件&#xff0c;老手扫几眼就能说出个大概&#xff0c;新手盯着同样的界面却完全找不到北。这种差距经常被归结为"经验"——做得多了自然就有感觉。但…

作者头像 李华
网站建设 2026/10/13 20:05:10

3.7万Star开源项目:大模型赋能二进制智能反编译与语义分析

做了几年程序分析和二进制安全&#xff0c;最头疼的话就是&#xff1a;“这程序没有源码&#xff0c;你帮我看看它在干什么。”以前遇到这种需求&#xff0c;要么搬出反汇编工具从十六进制和指令里硬抠逻辑&#xff0c;要么对着伪代码猜变量名&#xff0c;运气好半天能看出个大…

作者头像 李华