简介:本资源是一份面向计算机专业本科生与数据库初学者的课程设计实践文档,聚焦建材物资管理信息系统的数据库全流程设计,解决企业级物资管理场景中的数据建模、存储优化与业务逻辑落地问题。文档完整覆盖数据库原理应用、外部Schema设计、E-R模型与关系图构建、逻辑/物理结构设计,以及SQL Server 2005环境下的存储过程、触发器、视图脚本和备份恢复方案,并融入网上超市购物车系统等典型电商模块作为业务延伸。资源为单个PDF文件(472KB),内容结构清晰,含引言、外部设计、三阶段结构设计(概念/逻辑/物理)、脚本实现及参考资料,附详细数据表定义(如物资信息表、客户表、员工权限表等)。目前已有189人学习下载,适合用于数据库原理课程设计参考、毕业设计选题支撑或SQL Server实战开发入门。
1. 建材物资管理信息系统数据库设计:不是画ER图就完事,而是让采购、入库、领用、盘点全链路在SQL Server 2005里“不丢数据、不错账、不翻车”
你手头有一份《建材物资管理信息系统数据库设计.pdf》,打开一看全是表结构、字段说明、主外键连线——但真正上线跑起来后,采购员填单发现库存明明够却提示“可用量不足”,仓库员做退料时系统把已出库的物资又加回了可用库存,财务对账时发现同一笔入库单在库存台账和应付账款明细里数量差3吨……这些不是业务逻辑错,是数据库设计没扛住真实业务流。这份PDF本质是一份面向SQL Server 2005环境的、以事务一致性为底线的工业级物资数据底座蓝图:它必须用存储过程封装核心业务规则(比如“领料单审核=库存扣减+成本归集+单据状态变更”这一原子操作),必须靠触发器守住关键约束(比如“物资调拨单生效时,调出库位可用量必须实时校验并锁定”),还得在2005这个老但仍在大量工地项目部、中小型建材贸易公司服役的平台上稳住性能。适合正在接手老旧系统改造、或要从零搭一个能过甲方验收的物资管理后台的工程师——别信“先建表再补逻辑”的说法,这张PDF里的每个字段长度、每个外键指向、每个存储过程参数,都是上过生产环境的血泪经验。
2. 从需求反推表结构:为什么用户信息表不能只存姓名电话,而要拆出组织架构与权限角色
建材物资系统的用户绝不是普通OA里的“员工”,而是分层嵌套的业务角色:集团采购总监能看到所有子公司库存,但只能审批本部超50万的合同;项目部材料员能录本工地的领料单,但无权修改供应商信息;仓库保管员有扫码入库权限,却看不到成本分析报表。这种权限粒度,决定了用户信息表绝不能简单搞成Users(ID, Name, Phone, Role)。我们按SQL Server 2005的实际约束,拆成三张表联动:
2.1 组织架构表(OrgStructure):用自引用实现多级项目部/分公司/总部
CREATE TABLE OrgStructure ( OrgID INT IDENTITY(1,1) PRIMARY KEY, OrgCode VARCHAR(20) NOT NULL UNIQUE, -- 如 'BJ-GC-001' 表示北京工程部第1项目部 OrgName NVARCHAR(100) NOT NULL, ParentID INT NULL, -- 自引用外键,根节点为NULL Level TINYINT NOT NULL DEFAULT 1, -- 1:总部, 2:分公司, 3:项目部 IsActive BIT NOT NULL DEFAULT 1, CONSTRAINT FK_Org_Parent FOREIGN KEY (ParentID) REFERENCES OrgStructure(OrgID) );逻辑说明:SQL Server 2005不支持递归CTE(那是2005 SP2之后才部分支持,且性能差),所以层级查询必须用循环或临时表。这里
Level字段是硬编码层级,避免每次查子部门都遍历树。OrgCode带业务含义,方便前端直接按编码过滤(如WHERE OrgCode LIKE 'BJ-%')。
2.2 用户主表(SysUser):只存身份凭证,不存业务属性
CREATE TABLE SysUser ( UserID INT IDENTITY(1,1) PRIMARY KEY, LoginName VARCHAR(30) NOT NULL UNIQUE, -- 登录账号,非姓名 PasswordHash VARCHAR(50) NOT NULL, -- SQL Server 2005无HASHBYTES('SHA2_256'),用PWDENCRYPT() RealName NVARCHAR(50) NOT NULL, Mobile VARCHAR(11), Email VARCHAR(100), Status TINYINT NOT NULL DEFAULT 1, -- 1:启用, 0:禁用, 2:待审核 CreateTime DATETIME NOT NULL DEFAULT GETDATE() );参数说明:
PasswordHash必须用PWDENCRYPT()而非MD5——这是SQL Server 2005原生密码加密函数,兼容性远高于自己写哈希。Status用TINYINT而非BIT,因为后续可能扩展“冻结中”“试用期”等状态,BIT只有0/1不够用。
2.3 用户-组织-角色关联表(UserOrgRole):三元关系解决“同一人不同岗”
CREATE TABLE UserOrgRole ( UORID INT IDENTITY(1,1) PRIMARY KEY, UserID INT NOT NULL, OrgID INT NOT NULL, RoleID INT NOT NULL, -- 指向RoleDef表,存'采购员','仓管员','项目经理'等 IsDefault BIT NOT NULL DEFAULT 0, -- 1表示该用户登录时默认进入此组织角色 EffectiveDate DATETIME NOT NULL DEFAULT GETDATE(), ExpireDate DATETIME NULL, -- NULL表示永不过期 CONSTRAINT FK_UOR_User FOREIGN KEY (UserID) REFERENCES SysUser(UserID), CONSTRAINT FK_UOR_Org FOREIGN KEY (OrgID) REFERENCES OrgStructure(OrgID), CONSTRAINT FK_UOR_Role FOREIGN KEY (RoleID) REFERENCES RoleDef(RoleID) );为什么必须三张表?
- 单一
Users表加OrgID字段:无法支持“张三在A项目部当材料员,在B项目部当安全员”;Users+Roles二表:无法区分“同是采购员,在总部和项目部的审批权限不同”;- 三表关联后,登录时查
SELECT * FROM UserOrgRole WHERE UserID=@uid AND IsDefault=1,直接拿到当前上下文,所有后续SQL(如库存查询)都带上OrgID过滤,从源头杜绝跨组织数据泄露。
3. 物资主数据与库存动态:一张Material表撑不起“规格型号+批次+库位”三维管理
建材物资最头疼的是“同名不同质”:同样是“HRB400E Φ12mm 螺纹钢”,A厂家的屈服强度实测420MPa,B厂家只有395MPa;同样是“P.O 42.5水泥”,3月生产的和6月生产的凝结时间差2小时。如果Material表只存MatID, MatName, Spec, Unit,那库存表就永远算不准——你领走的是哪批、哪个库位、哪个厂家的货?必须用“主数据+实例化”双层设计。
3.1 物资主表(Material):定义标准规格,不存库存量
CREATE TABLE Material ( MatID INT IDENTITY(1,1) PRIMARY KEY, MatCode VARCHAR(30) NOT NULL UNIQUE, -- 如 'STEEL-REBAR-Φ12-HRB400E' MatName NVARCHAR(100) NOT NULL, CategoryID INT NOT NULL, -- 材料大类:钢材/水泥/砂石/模板... Spec NVARCHAR(200), -- '直径12mm, 屈服强度≥400MPa, 抗拉强度≥540MPa' Unit VARCHAR(10) NOT NULL DEFAULT '吨', -- 计量单位 IsBatchControlled BIT NOT NULL DEFAULT 1, -- 1:需批次管理(水泥/防水卷材),0:无需(脚手架) IsLocationControlled BIT NOT NULL DEFAULT 1, -- 1:需库位管理(钢筋堆场分区),0:无需(散装砂石) SafetyStock DECIMAL(18,4) NOT NULL DEFAULT 0, -- 安全库存,用于预警 CONSTRAINT FK_Mat_Category FOREIGN KEY (CategoryID) REFERENCES MatCategory(CategoryID) );关键点:
IsBatchControlled和IsLocationControlled是开关字段。SQL Server 2005不支持JSON,所以不能像新版本那样用配置字段,必须用布尔值硬编码控制后续逻辑分支——比如插入库存记录时,若IsBatchControlled=1则必须填BatchNo,否则报错。
3.2 物资实例表(MaterialInstance):每一批次每一库位都是独立实体
CREATE TABLE MaterialInstance ( InstID BIGINT IDENTITY(1,1) PRIMARY KEY, -- 用BIGINT防超量 MatID INT NOT NULL, BatchNo VARCHAR(50) NULL, -- 批次号,IsBatchControlled=0时可为空 LocationCode VARCHAR(30) NULL, -- 库位编码,如 'A区-3排-2列',IsLocationControlled=0时可为空 Qty DECIMAL(18,4) NOT NULL DEFAULT 0, -- 当前可用数量 InQty DECIMAL(18,4) NOT NULL DEFAULT 0, -- 累计入库量 OutQty DECIMAL(18,4) NOT NULL DEFAULT 0, -- 累计出库量 Status TINYINT NOT NULL DEFAULT 1, -- 1:正常, 2:冻结, 3:报废 CONSTRAINT FK_Inst_Mat FOREIGN KEY (MatID) REFERENCES Material(MatID), CONSTRAINT UQ_Inst_BatchLoc UNIQUE (MatID, BatchNo, LocationCode) -- 同一物资同一批次同一库位唯一 );为什么不用“库存快照表”?
很多人想建StockSnapshot(Date, MatID, BatchNo, LocationCode, Qty)每天跑一次汇总。但在SQL Server 2005里,INSERT INTO ... SELECT跨千万级记录极慢,且无法实时反映领料瞬间的库存变化。MaterialInstance表是“库存事实表”,所有出入库操作(通过存储过程)都直接UPDATE此表,Qty = InQty - OutQty由程序保证,而不是靠视图计算——这是2005时代保实时性的笨办法,但最稳。
3.3 关联业务单据:用外键绑定实例,而非只绑物资
以入库单为例,InStockDetail表不指向Material,而指向MaterialInstance:
CREATE TABLE InStockDetail ( DetailID INT IDENTITY(1,1) PRIMARY KEY, InStockID INT NOT NULL, -- 外键到主单InStockHeader InstID BIGINT NOT NULL, -- 关键!指向MaterialInstance.InstID Qty DECIMAL(18,4) NOT NULL, Price DECIMAL(18,4) NOT NULL, Remark NVARCHAR(200) NULL, CONSTRAINT FK_Detail_Inst FOREIGN KEY (InstID) REFERENCES MaterialInstance(InstID) );落地效果:当仓库员扫描钢筋二维码入库时,系统根据二维码解析出
MatCode+BatchNo+LocationCode,查MaterialInstance得到InstID,然后往InStockDetail插记录。后续所有领料、盘点、报表,都基于InstID聚合——这样“某批次水泥在某库位还剩多少”这种问题,一条SELECT Qty FROM MaterialInstance WHERE InstID=@id就解决,不用JOIN一堆表。
4. 存储过程封装核心业务:为什么“审核入库单”必须是一个SP,而不是五条UPDATE语句
在SQL Server 2005里,把业务逻辑写在应用层(如ASP.NET)最大的风险是:网络中断时,代码执行到第三步UPDATE Stock就断了,导致“单据已审,库存没扣”,或者更糟——“库存扣了,单据状态还是草稿”。必须用存储过程把“改单据状态+增明细+更新实例+写日志”打包成原子操作。以下是以usp_ApproveInStock为例的典型结构:
4.1 审核入库单存储过程:事务+错误捕获+状态机校验
CREATE PROCEDURE usp_ApproveInStock @InStockID INT, @ApproverID INT, @ApproveTime DATETIME = NULL AS BEGIN SET NOCOUNT ON; DECLARE @ErrNum INT = 0, @ErrMsg NVARCHAR(200); -- 1. 设置默认时间 IF @ApproveTime IS NULL SET @ApproveTime = GETDATE(); -- 2. 开启事务 BEGIN TRY BEGIN TRANSACTION; -- 3. 状态校验:只能审核"已提交"状态的单据 IF NOT EXISTS ( SELECT 1 FROM InStockHeader WHERE InStockID = @InStockID AND Status = 2 -- 2=已提交 ) BEGIN RAISERROR('单据状态非法,无法审核', 16, 1); END -- 4. 更新主单状态 UPDATE InStockHeader SET Status = 3, -- 3=已审核 ApproverID = @ApproverID, ApproveTime = @ApproveTime WHERE InStockID = @InStockID; -- 5. 遍历明细,更新MaterialInstance DECLARE @DetailID INT, @InstID BIGINT, @Qty DECIMAL(18,4); DECLARE cur CURSOR FOR SELECT DetailID, InstID, Qty FROM InStockDetail WHERE InStockID = @InStockID; OPEN cur; FETCH NEXT FROM cur INTO @DetailID, @InstID, @Qty; WHILE @@FETCH_STATUS = 0 BEGIN -- 先查当前实例可用量(防并发) DECLARE @CurrentQty DECIMAL(18,4); SELECT @CurrentQty = Qty FROM MaterialInstance WHERE InstID = @InstID; -- 更新实例:入库量累加,可用量增加 UPDATE MaterialInstance SET InQty = InQty + @Qty, Qty = Qty + @Qty WHERE InstID = @InstID; FETCH NEXT FROM cur INTO @DetailID, @InstID, @Qty; END CLOSE cur; DEALLOCATE cur; -- 6. 写操作日志 INSERT INTO SysLog (LogType, RefID, OperatorID, LogTime, Content) VALUES (1, @InStockID, @ApproverID, @ApproveTime, '入库单审核通过'); COMMIT TRANSACTION; END TRY BEGIN CATCH ROLLBACK TRANSACTION; SET @ErrNum = ERROR_NUMBER(); SET @ErrMsg = ERROR_MESSAGE(); -- 记录错误到日志表(不抛出,由应用层处理) INSERT INTO ErrorLog (ProcName, ErrNum, ErrMsg, Params, OccurTime) VALUES ('usp_ApproveInStock', @ErrNum, @ErrMsg, CAST(@InStockID AS VARCHAR)+','+CAST(@ApproverID AS VARCHAR), GETDATE()); RETURN @ErrNum; END CATCH END参数说明:
@ApproveTime设为NULL默认值,允许调用方不传时间,由SP内部GETDATE()生成,避免客户端时间不一致;CURSOR在SQL Server 2005中是必要选择——当时没有MERGE,也没有OUTPUT子句批量返回,游标虽慢但可控;ERROR_NUMBER()和ERROR_MESSAGE()是2005的TRY/CATCH标配,必须捕获并记日志,否则错误被吞掉,运维找不到问题;- 最后
RETURN @ErrNum是给应用层的明确信号,ASP.NET里用cmd.Parameters("@ReturnValue").Value取返回值判断成功与否。
4.2 为什么不用视图或函数替代SP?
有人提议用视图展示“待审核单据”,用标量函数计算“某物资总库存”。但SQL Server 2005的视图不支持参数,无法按@OrgID过滤;标量函数在WHERE子句中会导致全表扫描(如WHERE dbo.fn_GetTotalStock(MatID) > 0)。而SP可以接收参数、执行DML、控制事务——这是2005环境下唯一能兼顾性能与一致性的方案。
5. 触发器守住最后防线:当应用层绕过SP直接INSERT时,如何让库存不崩
理想很丰满,现实很骨感:总有第三方系统(如财务软件接口)、或开发人员调试时,会绕过存储过程直接INSERT INTO InStockDetail。这时触发器就是最后一道闸门。注意——不是所有操作都加触发器,只加在高危且易错的表上:MaterialInstance(库存事实表)、InStockDetail(影响库存的明细表)。
5.1 在MaterialInstance上建INSTEAD OF触发器:拦截非法负库存
CREATE TRIGGER tr_MaterialInstance_PreventNegativeQty ON MaterialInstance INSTEAD OF UPDATE AS BEGIN SET NOCOUNT ON; -- 只处理Qty被修改的行 IF UPDATE(Qty) BEGIN -- 检查更新后的Qty是否为负 IF EXISTS ( SELECT 1 FROM inserted i WHERE i.Qty < 0 ) BEGIN RAISERROR('库存数量不能为负数,请检查出入库操作', 16, 1); RETURN; END END -- 执行实际更新(注意:INSTEAD OF必须显式写UPDATE) UPDATE mi SET mi.Qty = i.Qty, mi.InQty = i.InQty, mi.OutQty = i.OutQty, mi.Status = i.Status FROM MaterialInstance mi INNER JOIN inserted i ON mi.InstID = i.InstID; END为什么用INSTEAD OF而非AFTER?
AFTER UPDATE触发器在更新完成后才触发,此时Qty已写入负数,再ROLLBACK代价大且可能影响其他事务。INSTEAD OF在更新前拦截,直接拒绝非法值,不碰物理数据——这是2005时代最轻量的防护。
5.2 在InStockDetail上建AFTER INSERT触发器:自动同步库存快照(仅用于报表)
CREATE TRIGGER tr_InStockDetail_UpdateStockSnapshot ON InStockDetail AFTER INSERT AS BEGIN SET NOCOUNT ON; -- 插入明细后,更新StockSnapshot表(用于日报表,非实时) -- 注意:StockSnapshot是汇总表,每天凌晨跑一次全量,此处只增量更新当日 INSERT INTO StockSnapshot (SnapDate, MatID, BatchNo, LocationCode, TotalQty, UpdateTime) SELECT CAST(GETDATE() AS DATE) as SnapDate, m.MatID, mi.BatchNo, mi.LocationCode, SUM(mi.Qty) as TotalQty, GETDATE() as UpdateTime FROM inserted i INNER JOIN MaterialInstance mi ON i.InstID = mi.InstID INNER JOIN Material m ON mi.MatID = m.MatID GROUP BY m.MatID, mi.BatchNo, mi.LocationCode ON DUPLICATE KEY UPDATE -- SQL Server 2005不支持ON DUPLICATE,改用MERGE模拟 -- 实际用法:先DELETE再INSERT,因2005无MERGE ; END避坑重点:SQL Server 2005根本没有
MERGE语句,所以上面注释里的ON DUPLICATE KEY UPDATE是伪代码。真实做法是:-- 先删当天同物资同批次同库位的快照 DELETE ss FROM StockSnapshot ss INNER JOIN inserted i ON ss.MatID = (SELECT MatID FROM MaterialInstance WHERE InstID = i.InstID) WHERE ss.SnapDate = CAST(GETDATE() AS DATE); -- 再插入新汇总 INSERT INTO StockSnapshot (...) SELECT ... FROM inserted JOIN ...;
5.3 触发器必须配的配套措施:禁用即失效,日志即证据
触发器不是银弹,必须配两件事:
- 禁用开关:在
StockSnapshot表加IsTriggerEnabled BIT DEFAULT 1字段,触发器开头加IF NOT EXISTS (SELECT 1 FROM Config WHERE KeyName='TriggerEnabled' AND Value='1') RETURN;,方便紧急关停; - 操作留痕:所有触发器内
INSERT INTO TriggerLog记录EVENT_TYPE, TABLE_NAME, ROW_COUNT, EXEC_TIME,否则出问题时连“谁触发的”都查不到。
6. 避坑指南:SQL Server 2005下数据库设计的5个血泪现场
这5条不是教科书警告,是我在三个建材客户现场连夜救火后记下的——每一条都对应过真实故障。
6.1 现象:存储过程执行超时,但单独跑里面SQL很快
原因:SQL Server 2005默认参数嗅探(Parameter Sniffing)失效,缓存执行计划时用了第一次传入的参数值(如@InStockID=1),后续传@InStockID=99999时仍用旧计划,导致索引失效全表扫描。
解决:在SP开头加WITH RECOMPILE选项,或用局部变量“欺骗”优化器:
DECLARE @LocalInStockID INT = @InStockID; -- 用局部变量代替参数 SELECT * FROM InStockHeader WHERE InStockID = @LocalInStockID;6.2 现象:触发器里INSERT INTO报“无法在触发器中使用INSERT EXEC”
原因:SQL Server 2005触发器内禁止嵌套INSERT...EXEC(如调用另一个SP返回结果集插入),这是硬限制。
解决:把逻辑拆出触发器,改用队列表+作业轮询。建TriggerQueue表存待处理记录,触发器只INSERT INTO TriggerQueue,另起SQL Agent作业每分钟SELECT TOP 100处理——牺牲毫秒级实时,换稳定性。
6.3 现象:MaterialInstance表查询变慢,SELECT COUNT(*)要20秒
原因:2005的统计信息过期,且UQ_Inst_BatchLoc唯一索引未覆盖查询字段(如常查WHERE MatID=123 AND Status=1)。
解决:
- 每日凌晨跑
UPDATE STATISTICS MaterialInstance WITH FULLSCAN; - 在唯一索引上加包含列:
CREATE UNIQUE INDEX IX_Inst_MatBatchLoc ON MaterialInstance(MatID, BatchNo, LocationCode) INCLUDE (Qty, Status)。
6.4 现象:PWDENCRYPT()加密的密码,用PWDCOMPARE()验证总是失败
原因:PWDCOMPARE('明文','密文')在2005中要求明文必须是VARCHAR,若传NVARCHAR(如C#里string默认是Unicode),比较会失败。
解决:应用层传参时强制转VARCHAR,或SP内用CONVERT(VARCHAR, @Password)再比对。
6.5 现象:usp_ApproveInStock里游标遍历1000条明细,耗时3分钟
原因:2005游标默认FAST_FORWARD,但若表上有触发器或复杂索引,性能暴跌。
解决:
- 改用
STATIC游标(内存中建快照,避免锁争用); - 更激进:用临时表+
WHILE循环替代游标:
SELECT DetailID, InstID, Qty INTO #TempDetail FROM InStockDetail WHERE InStockID=@id; WHILE EXISTS(SELECT 1 FROM #TempDetail) BEGIN SELECT TOP 1 @DetailID=DetailID, @InstID=InstID, @Qty=Qty FROM #TempDetail; -- 执行更新 DELETE FROM #TempDetail WHERE DetailID=@DetailID; END7. 验证设计是否落地:用三组SQL跑通“采购-入库-领用”闭环
设计好不好,不看ER图多漂亮,而看这三步能不能在SQL Server 2005里一气呵成、不出错、不丢数据。我习惯用这组SQL当验收checklist,每次新环境部署必跑:
7.1 模拟采购下单:生成采购单,关联供应商与物资
-- 1. 插入采购单头 INSERT INTO PurchaseHeader (PurCode, SupplierID, OrgID, Status, CreateTime) VALUES ('PUR-2024-001', 101, 201, 1, GETDATE()); -- 1=草稿 -- 2. 插入采购明细(注意:此时不碰库存) INSERT INTO PurchaseDetail (PurID, MatID, Qty, Price) SELECT SCOPE_IDENTITY(), 501, 100.0, 4200.0; -- MatID=501是螺纹钢7.2 模拟仓库入库:调用SP完成审核,验证库存增加
-- 3. 调用入库审核SP(先确保MaterialInstance存在对应批次库位) EXEC usp_ApproveInStock @InStockID=1001, @ApproverID=2001; -- 4. 验证:查MaterialInstance,Qty应增加100吨 SELECT Qty, InQty, OutQty FROM MaterialInstance WHERE InstID = 12345; -- 假设这是该批次库位的InstID -- ✅ 预期:Qty=100.0000, InQty=100.0000, OutQty=0.00007.3 模拟项目领料:触发库存扣减,验证可用量准确
-- 5. 插入领料单(同样用SP,非直插) EXEC usp_CreateOutStock @OrgID=201, @ProjectID=301, @ApplyerID=401, @Details='[{"InstID":12345,"Qty":5.5}]'; -- 6. 验证:同一InstID,Qty应减少5.5吨 SELECT Qty, InQty, OutQty FROM MaterialInstance WHERE InstID = 12345; -- ✅ 预期:Qty=94.5000, InQty=100.0000, OutQty=5.5000 -- 7. 关键验证:查库存预警视图(含SafetyStock) SELECT m.MatName, mi.Qty, m.SafetyStock, CASE WHEN mi.Qty < m.SafetyStock THEN '⚠️ 低于安全库存' ELSE '✅ 正常' END as Alert FROM MaterialInstance mi JOIN Material m ON mi.MatID = m.MatID WHERE mi.InstID = 12345;为什么这三步是黄金验证?
- 它覆盖了主数据(Material)→ 实例化(MaterialInstance)→ 业务单据(Purchase/InStock/OutStock)→ 状态流转(Status字段)→ 库存变动(Qty/InQty/OutQty)→ 预警逻辑(SafetyStock)全链路;
- 每一步都强制走存储过程,绕过任何直插直更;
- 最后一步用
CASE WHEN直观反馈业务含义,而不是只看数字——这才是给甲方演示时,他们能看懂的“系统真在管库存”。
我坚持在每个新项目初始化数据库后,用这七步SQL跑一遍,再导出SELECT * FROM SysLog ORDER BY LogTime DESC看日志是否干净。曾经有个项目,usp_CreateOutStock里少写了UPDATE MaterialInstance SET OutQty=OutQty+@Qty,导致Qty正确但OutQty一直是0,财务月底对账时发现“出库量=0”,差点以为系统没记账。后来我把这条验证加进自动化脚本,每次部署自动跑,失败则邮件告警。
希望帮到你。
本文还有配套的精品资源,点击获取