首页 / 资讯中心 / 文章详情

Python+SQLite药物管理系统实战:从数据库设计到库存预警

Python+SQLite药物管理系统实战:从数据库设计到库存预警 ★ FEATURED ARTICLE
简介这是一个基于Python开发的药物管理系统完整项目包适合正在学习Python数据库编程、GUI开发或软件工程实践的高校学生与初级开发者。资源共51个文件主要包含5个Python源码文件、3个SQL数据库脚本、24个HTML页面及相关配置文档压缩包大小约23.73MB。其中源码涵盖系统核心逻辑与Web界面SQL文件提供数据表结构HTML模板则用于前端展示整体结构清晰便于对照学习。目前已有111人学习下载。通过该项目可以系统掌握Python与数据库交互、前后端联调、模块化设计等实际开发技能尤其适合作为课程设计或毕业设计的参考资料。1. 先把话说明白基于Python的药物管理系统含数据库文件解决的是什么事前几天一个以前带过的实习生来找我说导师丢给他一个课题要做一个基于Python的药物管理系统要求必须带数据库文件他手上连个能跑的参考都没有。这种zip压缩包在开发者手里其实是个很常见的型号——一套Python写的中小型桌面管理程序配一个现成的SQLite数据库文件解压后装上依赖就能启动登录进去做药品信息维护、入库、出库、库存预警和效期管理。它听着不起眼但几乎踩中了Python入门项目里所有值得琢磨的点GUI怎么写、数据库表怎么设计、事务怎么保证不把库存扣错。这套系统解决的是小药房、诊所和校内实验室最头疼的账物一致问题。用Excel记账的日子大家都经历过药品批号录错、有效期没人管、月底盘点差几十盒对不上账。含数据库文件的交付方式意味着数据库结构、初始数据和示例记录都已经在压缩包里你打开zip拿到的不只是代码而是一个能直接跑的起点。适合谁准备交课程设计或毕业设计的学生想给家里小药店换个记账方式的开发者以及想快速搭一套内部工具验证业务逻辑的团队。接下来我按自己动手搭这套系统的顺序把选型、步骤、代码和坑一条条讲清楚。2. 为什么选PythonSQLite做药物管理系统从单机药房到课程设计都能落地的技术选型2.1 为什么业务逻辑用Python写交付速度和改需求的底气药物管理系统的核心业务无非是药品增删改查、入库单和出库单登记、库存数量计算、有效期提醒逻辑复杂度不高但表单多、状态多、边界情况多。用Python写这类中后台业务开发效率是最实在的优势不需要写一大坨样板代码一个类几行方法就能把一张表的操作带起来改需求时改动的范围也小。我见过有人非要用Java写同样的系统建工程、配框架就耗掉一半时间最后交出来的东西还没Python版好改。界面层常见做法是用Tkinter它是Python标准库自带的GUI工具包不需要额外装第三方界面框架。为什么够用药物管理系统的操作场景是固定的表单填写、表格展示、按钮点击Tkinter的Treeview做药品列表、Entry做表单输入、Messagebox做提示一套下来非常顺畅而且对运行环境的依赖极少。还有一个现实原因很多同学的运行环境是学校机房或老笔记本装PySide6、PyQt5这种重型GUI库可能因为版本冲突折腾半天Tkinter却在绝大多数Python发行版里开箱即用。数据库操作直接用Python标准库sqlite3模块这意味着只要你机器上有Python整个系统就不依赖任何第三方数据库服务。这是个很关键的交付优势——对方拿去演示的电脑上不一定装了MySQL或SQL Server但只要有Python双击就能跑。选型时我还看重一点Python的sqlite3模块接口和日后换MySQL的pymysql接口非常相似都是建立连接、拿游标、执行SQL、提交或回滚。也就是说现在用SQLite写的业务代码将来数据量上来了想换MySQL改动成本可控不需要推翻重写。2.2 SQLite当“数据库文件”的优势单文件交付、零配置、后路可换标题里特别标了「含数据库文件」这指的通常就是SQLite的库文件——所有表、索引、数据都装在一个单独的文件里大小一般几MB。这个设计对交付有巨大价值压缩包发过去对方解压后数据库就在那里不需要安装数据库服务端不需要执行建库脚本不需要配账号密码。程序启动时用一行代码连上这个文件表和数据全部就位这种「零配置打开即用」的体验是这套系统最容易让使用者产生好感的地方。SQLite的适用边界要心里有数。它支撑的是单机应用和低并发写入场景——一个药房一个窗口录入一天几百笔入库出库流水SQLite处理得游刃有余。但如果你的场景是多门店同时写同一个数据库文件或者系统要部署成Web服务给几十个人在线操作SQLite的单写者模型就会出现写入锁等待甚至报错。判断标准很简单数据量在百万行以内、并发写连接只有个位数选SQLite稳得很超过这个量级就该把数据层迁移到MySQL或PostgreSQL。即使将来要迁移也不用担心被SQLite绑死。建表语句里的字段类型尽量用INTEGER、TEXT、REAL、DATETIME这类通用类型避免用SQLite特有的语法日期统一存成TEXT格式的YYYY-MM-DD主键不让SQLite自动生成rowid而是显式声明INTEGER PRIMARY KEY。按这个习惯写将来数据导出成SQL脚本稍作语法调整就能在MySQL里建出同样的表结构。2.3 这套组合适合谁、不适合谁先看清楚再动手把适用人群说清楚能帮你避免把时间花在错误的方向上。最适合这套组合的是三类场景第一课程设计和毕业设计要求Python实现、功能完整、需要有数据库设计文档SQLite的库文件交上去就是现成的数据层证明第二单机版内部工具比如某小型门诊的药房管理、某实验室的耗材药品台账使用者就一到三个人数据敏感性不高的场景第三做原型验证产品经理想看看药物进销存的交互流程用PythonSQLite三天出个可点可用的Demo比用重型框架快得多。不适合的场景也要直说。如果你要做的是一套SaaS化药物管理系统需要多租户隔离、在线并发、细粒度权限SQLite和Tkinter都不该出现在技术方案里。另外如果业务涉及GSP合规要求、电子监管码追溯、与医保系统对接这类系统需要的不是本地桌面程序而是符合监管要求的专业系统Python课设级别的方案接不住这些需求。选型不是越高级越好而是尺度合适。单机药房和课设这个尺度PythonSQLite就是性价比最高的答案。3. 把zip变成能跑的系统解压、装依赖、检查数据库、登录后台的完整步骤3.1 解压后先认项目结构哪是源码哪是数据库文件拿到zip先别急着双击运行先解压并把目录结构看清楚。这一步能避免后面很多「明明代码没问题为什么跑不起来」的玄学问题。常见的项目结构长这样具体文件名以你手里的压缩包为准drug_management/ ├── main.py # 程序入口双击或命令行运行它 ├── config.py # 配置文件数据库路径、窗口大小等 ├── requirements.txt # 第三方依赖清单 ├── ui/ # 界面层代码登录窗口、主窗口 │ ├── login_window.py │ └── main_window.py ├── models/ # 数据层代码数据库连接、增删改查 │ ├── db.py │ └── drug_dao.py ├── database.db # SQLite数据库文件这就是标题里说的那个文件 └── README.md # 使用说明先读它这段结构说明我一般会对照实际包过一遍。先找database.db或pharmacy.db这类后缀为.db的文件——它一般是整个系统里最关键的交付物里面有建表语句和初始数据。再找README.md里面通常会写Python版本要求、依赖安装命令和默认登录账号。最后确认requirements.txt存在它是后面安装依赖的依据。解压时有个习惯我建议直接养成把整个文件夹放到纯英文路径下比如D:\drug_management不要直接解压到桌面或带中文的目录里。不是在制造焦虑SQLite对中文路径的支持虽然能用但一旦有权限或编码问题报错信息会让你绕很大一圈后面避坑章节会细说。3.2 安装运行环境与依赖虚拟环境与pip命令逐条说接下来创建虚拟环境并安装依赖。虚拟环境的作用是把这套系统的依赖和你电脑上其他Python项目的依赖隔离开避免出现A项目要Flask 2.x、B项目要Flask 3.x互相打架的情况。按顺序执行以下命令cd drug_management # 创建虚拟环境venv是Python自带的不需要额外安装 python -m venv venv # Windows系统激活虚拟环境 venv\Scripts\activate # macOS / Linux 系统激活虚拟环境 # source venv/bin/activate # 安装依赖-r表示从文件读取包列表 pip install -r requirements.txt逐条解释一下。python -m venv venv会在当前目录生成一个venv文件夹里面是一套独立的Python解释器和pip工具。激活后你的命令行提示符前面会出现(venv)字样这表示后续的pip install只装进这个环境里。最后一行pip install -r requirements.txt会读取文本文件里列出的包名并逐个安装如果requirements.txt写得规范你不需要手动指定版本号。这里有一个常见问题如果系统提示python不是内部或外部命令说明Python没装或者没加到环境变量。解决办法是改用python3或py命令试试或者去Python官网装一个3.8到3.11之间的版本——不建议直接上最新的3.12或3.13因为Tkinter的界面程序在解释器兼容性上需要给第三方库留出适配时间。依赖装完后可以继续下一步检查数据库文件。3.3 检查数据库文件启动前先看看表结构和初始数据数据库文件是整套系统能不能跑起来的底座。强烈建议在启动程序之前先直接连一次这个库文件确认里面确实有表、有数据。如果程序一启动就报错而你连库文件里有什么都不知道排错就像在黑匣子里摸。用Python一行命令就能完成检查import sqlite3 conn sqlite3.connect(database.db) cursor conn.cursor() # 查询所有表名确认建表语句已经执行过 cursor.execute(SELECT name FROM sqlite_master WHERE typetable) print(表清单:, cursor.fetchall()) # 查看药品表里有多少条初始数据 cursor.execute(SELECT COUNT(*) FROM drugs) print(药品初始记录数:, cursor.fetchone()[0]) conn.close()这段检查脚本是我拿到任何含数据库文件的项目时都会先跑一遍的。connect负责打开文件sqlite_master是SQLite内部维护的一张元数据表存着所有表、索引、视图的定义。第二条SELECT COUNT查询的作用是确认药品表里不是空的——如果返回0说明数据库文件虽然存在但可能是空库需要找初始化脚本重建而不是直接启动程序。如果这段脚本执行报错优先检查database.db文件和脚本是否在同一个目录下。文件名和路径以实际项目里的config.py所配置的为准有些项目会把数据库路径写死在代码里你需要打开config.py看一眼连接串指向哪里必要时改成当前实际路径。3.4 启动系统用默认账号走一遍登录和首次入库数据库文件确认没问题后启动程序就非常简单了python main.py程序窗口弹出后第一步是登录。初始账号密码通常会写在项目的README.md里或者写在数据库的users表里。这类课设项目的常见约定是admin/admin123但别直接盲试——先打开README确认一下如果找不到说明就用前面Python脚本连上库查一下users表里的记录一翻心里就有底了。登录成功后建议按照「先加供应商 → 再录药品 → 再做一笔入库」的顺序把主流程走一遍。先录一个基本信息完整的药品比如通用名、商品名、规格、生产批号、有效期、生产厂家、供应商再到入库模块登记这批药的进货数量和进货价。做完这些后去库存查询页面看一眼刚才录的药品应该出现在列表里库存数量等于入库数量。如果你走的流程跟刚才描述的顺序一致说明系统核心链路是通的如果中途某个按钮没反应或数据没出现记住卡在哪一步后面的避坑章节会覆盖最常见的几类问题。4. 拆开源码看门道药物管理核心模块与数据表设计的落地读法4.1 数据表设计药品、入库、出库、用户、日志的字段和关系跨过「能跑」这道坎之后真正需要花心思的是读懂源码里数据是怎么组织的。一套药物管理系统再花哨核心都在数据库表结构里。常见的表设计大概是下面这个形态字段名以实际源码为准表名核心字段作用usersid, username, password_hash, role用户登录和权限role区分管理员和普通操作员drugsid, drug_code, generic_name, trade_name, spec, manufacturer, approval_number药品基础信息审批文号是药品身份的标识drug_stockid, drug_id, batch_no, stock_qty, expire_date, safety_stock库存与批次一个药品可能有多条批次记录stock_inid, drug_id, batch_no, qty, unit_price, in_date, operator入库流水记录每一次进货stock_outid, drug_id, batch_no, qty, out_type, out_date, operator出库流水记录销售或领用suppliersid, name, contact, phone, address供应商档案operation_logid, user_id, action, detail, log_time操作日志谁在什么时间做了什么需要特别注意的是drug_stock表的设计它是整套系统的枢纽。一种药品可能有多个批号每个批号有独立的库存数量和失效日期不能合并成一行。比如阿莫西林胶囊2025年3月生产的一批剩200盒2025年6月生产的一批剩150盒两张记录必须分开存。后续做近效期预警、批号追溯时拆开存的价值就会体现出来。库存数量为什么不在drugs表里直接存一个total_stock字段这是一个很典型的课设踩坑点。如果只存一个总数你无法回答「过期的那批还剩多少」这个问题也无法在某一批过期时只扣那一批的库存。drug_stock表把批次维度拆出来库存总数随时可以通过聚合查询算出来但批次明细不会丢。这就是为什么库存模块设计得好的系统都会把「药品档案」和「批次库存」分开两张表。4.2 库存预警和近效期药品两条SQL和它们的查询逻辑库存预警和近效期提醒是药物管理系统区别于普通进销存软件的两个关键功能。库存预警的逻辑很简单某个批次的当前库存低于安全库存时系统需要提醒补货。近效期提醒则是GSP管理里的刚需——药品距离失效日期不到一定天数时要提示处理。这两条查询的价值在于不靠肉眼翻表直接通过SQL筛出来-- 库存预警当前库存低于安全库存的药品 SELECT d.generic_name, d.trade_name, s.batch_no, s.stock_qty, s.safety_stock FROM drug_stock s JOIN drugs d ON s.drug_id d.id WHERE s.stock_qty s.safety_stock ORDER BY s.stock_qty / s.safety_stock ASC;-- 近效期预警90天内即将过期的批次 SELECT d.generic_name, d.trade_name, s.batch_no, s.expire_date, s.stock_qty FROM drug_stock s JOIN drugs d ON s.drug_id d.id WHERE s.expire_date BETWEEN date(now) AND date(now, 90 days) ORDER BY s.expire_date ASC;第二条SQL里date(now)取当天日期date(now, 90 days)取90天后的日期BETWEEN筛出这个时间段内的批次。90天的窗口期是我在同类系统里常用的默认值你可以根据业务需要改成30、60或180天。SQL写好后在Python里执行就是一个标准的查询流程import sqlite3 def get_expiring_batches(days90): conn sqlite3.connect(database.db) cursor conn.cursor() cursor.execute( SELECT d.generic_name, s.batch_no, s.expire_date, s.stock_qty FROM drug_stock s JOIN drugs d ON s.drug_id d.id WHERE s.expire_date BETWEEN date(now) AND date(now, ?) ORDER BY s.expire_date ASC , (f{days} days,)) rows cursor.fetchall() conn.close() return rows注意参数传递的写法不要用字符串拼接把days变量拼进SQL而是用占位符?再把参数传给execute。这既避免了语法错误也防止了SQL注入风险。日期参数写成90 days传给SQLite的date函数SQLite能正确解析这种相对日期表达式。拿到返回结果后再更新到界面上的预警列表里每行显示药品名、批号、失效日期和剩余数量。4.3 出库为什么要包在事务里扣库存和写流水的原子性出库操作是整套系统里最容易出数据事故的地方因为它涉及两步写操作扣减批次库存、写入库出库流水。如果扣了库存但流水没写成功或者流水写了但库存没扣账就对不上了。保证两步要么同时成功、要么同时失败的技术手段叫事务。Python的sqlite3模块里事务的写法是这样import sqlite3 def out_stock(drug_id, batch_no, out_qty, operator): conn sqlite3.connect(database.db) try: # 显式开启事务 conn.execute(BEGIN) # 第一步扣减该批次的库存 cursor conn.execute( UPDATE drug_stock SET stock_qty stock_qty - ? WHERE drug_id ? AND batch_no ? AND stock_qty ?, (out_qty, drug_id, batch_no, out_qty) ) # 影响行数为0说明库存不足主动抛异常触发回滚 if cursor.rowcount 0: raise ValueError(库存不足或批次不存在已回滚) # 第二步写入出库流水 conn.execute( INSERT INTO stock_out (drug_id, batch_no, qty, out_type, operator, out_date) VALUES (?, ?, ?, 销售出库, ?, datetime(now, localtime)), (drug_id, batch_no, out_qty, operator) ) conn.commit() print(出库成功) except Exception as e: conn.rollback() print(出库失败已回滚:, e) raise finally: conn.close()解释几个关键点。UPDATE语句里的stock_qty ?条件是一个防御性写法只有当现有库存大于等于出库数量时才执行扣减数据库层面拦住超卖。cursor.rowcount返回本次UPDATE影响的行数如果是0说明这批次不存在或库存不够这时主动抛异常让程序进入回滚分支。这两步操作之间如果程序崩溃或出异常rollback会把第一步扣掉的库存恢复原样不会出现扣了库没写流水的半截状态。这里有一个很多新手会忽略的细节sqlite3模块默认的隔离级别下执行INSERT、UPDATE这类语句会自动开启事务但自动开启的事务时机并不直观。我习惯在关键写操作前显式执行conn.execute(BEGIN)自己控制事务的边界commit和rollback的行为就完全在掌控之内了。这个习惯在数据一致性敏感的操作上非常值得养成。5. 避坑指南药物管理系统跑不起来、数据错乱、药品过期的排查手册5.1 解压路径带中文或空格数据库文件打不开现象程序启动后弹窗报错sqlite3.OperationalError: unable to open database file或者界面能打开但所有数据都读不出来。原因项目配置文件里写死了数据库路径而你把压缩包解压到了类似C:\Users\张三\桌面\药物管理系统(1)这样的路径。SQLite在解析包含中文、空格、括号的路径时如果没有统一处理编码连接就会失败。这类问题在Windows系统上特别典型因为默认桌面路径往往带中文用户名。解决第一步把整个项目文件夹移动到纯英文路径下比如D:\drug_management。第二步打开config.py或数据库连接代码把数据库路径改成配置文件里的相对路径不要用绝对路径。第三步重新运行前面3.3节的检查脚本确认连接成功后再启动程序。这个操作十次里能解决九次启动报错建议养成「项目路径只用英文」的习惯。5.2 Python 3.12上Tkinter界面模块导入失败现象运行main.py时报错ModuleNotFoundError: No module named tkinter或者import tkinter能过但创建窗口时崩溃。原因两种情况——一种是你安装的是精简版Python安装时取消了Tcl/Tk组件导致标准库里根本没有tkinter另一种是Python版本太新项目里依赖的某个第三方界面库还没跟上新版本解释器。近两年在3.12版本上跑PySide6老项目的翻车案例我见过不止一次。解决先在命令行跑python -m tkinter如果能弹出一个小窗口说明Tkinter可用问题在其他依赖上如果报错说明Python环境本身就没装好。最稳当的做法是重新安装Python 3.10或3.11版本装的时候勾选安装Tcl/Tk选项。如果你用的是虚拟环境直接删掉venv文件夹换解释器后重新执行python -m venv venv和pip install -r requirements.txt。这套重建环境的操作大概五分钟比逐个排查依赖省心得多。5.3 多人同时操作时写入报错database is locked现象两个人同时在系统里录单据其中一个人的操作偶尔报sqlite3.OperationalError: database is locked过一会儿又自己恢复正常。原因SQLite使用的是数据库级锁同一时刻只允许一个写事务。当你某次写入持续了较长时间——比如在事务里做了大量查询或循环——其他写入就会被锁阻塞超过默认等待时间就直接报错。解决在连接数据库时加一个等待超时参数sqlite3.connect(database.db, timeout10)把默认的5秒等待延长到10秒能缓解大部分偶发锁冲突。更彻底的做法是打开SQLite的WAL模式在连接后执行PRAGMA journal_modeWAL。WAL模式允许读和写并发进行写与写之间也不再互相阻塞到无法容忍的程度适合这种两三个操作员同时录单的场景。设置一次后这个设置会持久化保存在数据库文件里不需要每次连接都执行。5.4 批号与有效期格式混乱近效期预警直接失效现象数据里明明有快过期的药品近效期预警列表里却查不出来或者日期排序乱掉。原因expire_date字段设计成TEXT类型但录数据时格式不统一——有人录2025-03-31有人录2025.03.31还有人录2025年3月31日。BETWEEN date(now) AND date(now, 90 days)按字符串比较时字符-和.的比较结果会打乱顺序导致该被筛出来的批次漏掉。解决先清洗脏数据把库里的有效期统一替换成YYYY-MM-DD格式用一条UPDATE语句就能处理大部分情况。更重要的是从源头控制界面录入有效期的地方用日期选择控件或者在保存前做一次格式校验非法格式直接拦截。对于已经存在的库文件打开数据库执行清洗后再启用预警功能。这种事情一旦拖到数据量大了再处理工作量翻倍早发现早解决。5.5 直接DELETE药品档案库存和流水全对不上现象把某条药品记录删掉后库存数量看起来没问题但所有历史出入库流水里查不到这个药了月底对账时总金额和数量对不上。原因项目里的删除按钮执行的是硬删除——直接从drugs表删掉记录。而drug_stock、stock_in、stock_out表里的记录还通过drug_id关联着这个已删除的ID关联变成悬空引用查询结果自然就不完整。解决把删除操作改成软删除也就是不真正删行而是给drugs表加一个status字段删除时执行UPDATE drugs SET status0 WHERE id?所有查询列表的地方默认加上WHERE status1过滤条件。这样药品从当前经营列表里消失但历史流水和库存记录依然能通过JOIN关联查出来。软删除在进销存业务里几乎是必选项它保住了审计追踪链——药品什么时候进的、什么时候出的、什么时候停用的都要有据可查。如果你手里的系统用的是硬删除建议尽早改成软删除越晚改数据迁移成本越高。6. 进阶玩法定时备份、操作审计、库存对账三件套把系统加固到能上线6.1 数据库文件自动备份一条命令和一个计划任务数据库文件最贵的资产就是里面的数据。SQLite的备份很简单用官方自带的backup命令就能生成一致性快照不需要停程序# 手动备份把database.db备份成带日期的文件 sqlite3 database.db .backup backup_20250101.db手动备份容易忘所以我一般建议加进计划任务。Windows上用任务计划程序macOS/Linux上用crontab每天凌晨执行一次这条命令保留最近30天的备份文件即可。这个动作花不了十分钟配置但能防止最坏的情况——库文件损坏或误操作后没有任何后悔药。我做这类系统时默认先配备份再谈其他功能这个习惯希望帮到你。6.2 操作日志升级成审计日志原始系统里operation_log表通常只记录「谁操作了」但审计要的是「谁在什么时间对什么批次做了什么操作、之前是什么值、之后是什么值」。改造方法是在日志表里增加detail_json字段把操作前后的关键数据快照存进去。比如扣库存时把操作前的库存量、操作后的库存量一起写入日志将来出了问题可以直接还原现场。6.3 一条SQL自查库存一致性别等月末盘点才发现问题库存对不上账是这类系统最常见的慢性病有一个快速自查的方法把入库总量减去出库总量和当前库存做对比差异不为零就是有问题。SELECT d.generic_name, s.batch_no, IFNULL((SELECT SUM(qty) FROM stock_in WHERE drug_id d.id), 0) - IFNULL((SELECT SUM(qty) FROM stock_out WHERE drug_id d.id), 0) AS calc_stock, s.stock_qty AS actual_stock FROM drug_stock s JOIN drugs d ON s.drug_id d.id WHERE calc_stock ! s.stock_qty;这条SQL正常情况下应该返回0行。只要返回了记录说明那个批次的出入库流水和当前库存对不上顺着流水去排查是漏记了、重复记了还是批次选错了。每周跑一次这个自查避免月底盘点时才被数据吓一跳。药物管理系统本身不难难的是一直保持数据可信。把备份、审计、对账做成习惯这套系统才真正从「课设能跑」变成「能放心用」。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站