这个文章的起源是:金蝶k3wise采购申请单审核时需要自动校验库存,并且生成mto调整单;
直接导致:一个从未写过触发器的门外汉,得到了一点提高;
这篇文章适合像我这样只会在数据库里面进行增删改查,但是还想在数据库中更深入一下的童鞋,通过学习可以初步掌握触发器和存储过程的使用。
文中的示例都是建立在实际项目上的,可能不太适合练习。如果有合适的时间,并且还有一些阅读量的话,我会考虑把案例做成标准的数据。
如果你不慎看到这篇文章,但是对数据库还不熟悉,那么你可以先学习一下基本的语法:SQL语法学习 - 不用真的记不住
一、触发器的一些基础
1.1 触发器的概念
当在`表`中`插入、更新、删除记录`时,`触发`一个或一系列的SQL语句
1.2 触发器类型
- 1.After触发器
当数据表执行插入、更新、删除操作之后,After触发器才被触发。After触发器只能用于数据表,不可用于视图。
分为:
- after insert触发器
- after update触发器
- after delete触发器
- for insert触发器
- for update触发器
- for delete触发器
- 2.Instead Of 触发器
当数据表执行插入、更新、删除操作,Instead Of触发器并不执行插入、更新、删除操作,而是替换成执行触发器本身的T-SQL逻辑。
分为:
- instead of insert触发器
- instead of update触发器
- instead of delete触发器
Instead Of触发器也可用于视图,用来扩展是多表连接的视图设计可以进行更新操作。视图设计是多表连接,是无法更新的。这时候,可以用Instead Of触发器,把更新操作替换为向每个基本表进行更新。
1.3 触发器的2个中间表
触发器触发的时候有两个中间表:插入表(instered表)和删除表(deleted表)。这两张表的字段,和触发器应用主表的字段相同。中间表存储的数据是执行插入、更新、删除操作的数据,当触发器执行完成后,这两张表就会被删除。Inserted表的数据是待插入的数据,或是触发器修改后的数据。deleted表的数据是插入前的原始数据。如果是删除操作的触发器,deleted表是表示删除的数据。
1.4 触发器的语法结构
CREATE TRIGGER trigger_name | |
ON {Table_name|View_name} | |
{FOR|AFTER|INSTEAD OF} [INSERT,UPDATE,DELETE] | |
AS | |
声明部分 | |
BEGIN | |
执行部分 | |
END | |
1. `FOR`和`AFTER`意义一样; 它们不能作用于视图
2. 修改触发器把`CREATE`改成`ALTER`
3. 触发器命名规则: `tr_表名_说明`
触发器常用语句
- 删除触发器
DROP TRIGGER trigger_name
- 查询数据库中已有的触发器
SELECT * FROM sysobjects where xtype = 'TR'
- 查看某个触发器的具体内容
EXEC sp_helptext 'trigger_name'
- 触发器的启用和停用
alter table tablename disable trigger trigname | |
alter table tablename enable trigger trigname |
- sql profiler如何捕捉触发器
sqlserver profiler 的event里选择这两个:
Stored procedures:
- SP:StmtStarting
- SP:StmtCompleted
二、初步测试
初步在`采购申请单`上创建一个触发器, 在`单据审核`时`修改`单据的`编码`
create trigger tr_POrequest_MTOchange | |
on POrequest | |
after update | |
as | |
begindeclare @billno nvarchar(100) | |
if UPDATE(FCheckerID) beginselect @billno = fbillno from deleted | |
update PORequest set FBillNo = 'rp001'where FBillNo = @billno | |
end | |
end |
这是一个字段级的触发器, 为了能识别单据是保存时执行了update还是审核时执行了update,
所以使用了if update(字段); 当FCheckerID字段改变后, 才会触发后面的SQL语句
三、SQL相关知识
3.1 SQL表循环
需要在采购申请明细表中遍历所有行的物料, 通过循环的方法实现. (游标不会)
参考:
https://www.cnblogs.com/yssss/p/10731626.html
set nocount on | |
--创建一个表变量, 用来存储要遍历的内容 | |
declare @t_MTOEntry table (FDetailID int) | |
insert into @t_MTOEntry from select ... --插入表内容, 省略(这里放的是采购申请的FDetailID) | |
select ROW_NUMBER() over(order by FDetailID) as i,* into #t_id from @t_MTOEntry --创建临时表,加上序号 | |
declare @count int | |
declare @i int = 1 | |
select @count = COUNT(1) from #t_id --获取临时表的记录数 | |
while (@i<=@count) | |
begin | |
declare @FDetailID int | |
select @FDetailID=FDetailID from #t_id where i = @i | |
print str(@FDetailID) | |
set @i = @i+1 | |
end | |
drop table #t_id |
3.2 SQL函数
SQL自定义函数 :
https://www.cnblogs.com/happyyangyanghappy/p/10870495.html
SQL自定义函数主要分为2种 :
- 标量函数:
返回单个数据
- 表格值函数:
一种是函数中只有一条查询语句;
一种是函数中可以有多条SQL语句;
3.2.1 标量函数
返回单个数据
语法格式
- 创建一个函数
create function 函数名(参数 类型[,参数2 类型2, ...]) | |
returns 返回值类型 | |
as | |
begin | |
declare @变量 变量类型 | |
set @变量 = 参数 | |
return @变量 | |
end |
调用方法
dbo.函数名(参数)
select dbo.函数名(参数) as "列名"
示例
创建一个函数用来获取计量单位对应的换算率
create function Coeff(@FUnitID int) | |
returns decimal(28,10) | |
as | |
begin | |
declare @Coeff decimal(28,10) | |
select @Coeff = FCoefficient from t_MeasureUnit where FMeasureUnitID = @FUnitID | |
return @Coeff | |
end |
调用方法
declare @n decimal(28,10) | |
select @n = 5 * dbo.coeff(290) | |
print @n |
3.2.2 表格值函数
(1)单条语句, 返回值为一个table
语法格式
create function 函数名(参数 类型[,参数2 类型2, ...]) | |
returns table | |
as | |
return (SQL查询语句) |
示例
create function func_ReqMto(@FDetailID int, @FItemID int) | |
returns table | |
as | |
return (select t1.FInterID , | |
t1.FItemID , | |
t1.FQty , | |
t1.FUnitID , | |
t1.FDetailID , | |
t1.FSecQty , | |
t1.FMTONo as "ToMTONo", | |
t2.FBatchNo , | |
t2.FStockID , | |
t2.FStockPlaceID , | |
t2.FQty as "FQtyInv", | |
t2.FKFPeriod , | |
t2.FKFDate , | |
t2.FSecQty as "SecQtyInv", | |
t2.FMTONo as "FromMTONo" | |
from PORequestEntry t1 left join ICInventory t2 on t1.FItemID = t2.FItemID | |
where t1.FItemID = @FItemID | |
and t1.FDetailID = @FDetailID | |
and t2.FMTONo in ('','999999') | |
) |
调用方法
select * from func_ReqMto(参数1,参数2) | |
(2)多条语句, 返回值为一个table
语法格式
create[alter] function 函数名(参数 类型[,参数2 类型2,...]) | |
returens @表变量 (字段名 类型, [字段名2 类型2, 字段名3 类型3, ...]) | |
as | |
begin | |
SQL过程 | |
insert into @表变量 select 字段名 类型, [字段名2 类型2, 字段名3 类型3, ...] from [条件] | |
end |
示例
create function func_Inv( @FDetailID int, @FItemID int) | |
returns @Inv table (FInterID int, | |
FItemID int, | |
FQty_Req decimal(28,10), | |
FUntiID int, | |
FDetailID int, | |
FSecQty_Req decimal(28,10), | |
FMTONo_Req nvarchar(50), | |
FBatchNo varchar(200), | |
FStockID int, | |
FStockPlaceID int, | |
FQty decimal(28,10), | |
FKFPeriod int, | |
FKFDate varchar(20), | |
FSecQty decimal(28,10), | |
FMTONoFrom nvarchar(50)) | |
as | |
begininsert into @inv | |
select t1.FInterID , | |
t1.FItemID , | |
t1.FQty , | |
t1.FUnitID , | |
t1.FDetailID , | |
t1.FSecQty , | |
t1.FMTONo as "ToMTONo", | |
t2.FBatchNo , | |
t2.FStockID , | |
t2.FStockPlaceID , | |
t2.FQty as "FQtyInv", | |
t2.FKFPeriod , | |
t2.FKFDate , | |
t2.FSecQty as "SecQtyInv", | |
t2.FMTONo as "FromMTONo"from PORequestEntry t1 left join ICInventory t2 on t1.FItemID = t2.FItemID | |
where t1.FItemID = 293and t1.FDetailID = 176and t2.FMTONo in ('','999999') | |
order by t2.FMTONo , t2.FQty descreturn | |
end |
3.3 SQL存储过程
语法格式
create proc 过程名称 | |
@变量1 类型, [可以有默认值] | |
@变量2 类型, [可以有默认值] | |
... | |
as | |
begin | |
SQL语句 | |
end |
调用方法
exec 过程名称 @变量1 = 参数, @变量2 = 参数
示例
create proc proc_mtoChange | |
@FDetailID int , | |
@isAll int , | |
@maxnum int , | |
@reqQty decimal(28,10) | |
as | |
beginselect identity(int,1,1) as "id", * ,null as "chaQty" , null as "MTOChange", null as "fno" into #invfrom func_reqmto(@FDetailID) | |
order by frommtono, fqtyinv descdeclare @m int = 1declare @count int = (select count(id) from #inv)declare @chaQty decimal(28,10) | |
select @chaQty = fqty from PORequestEntry where FDetailID = @FDetailID | |
while @m <= @count beginset @chaQty = @chaQty - (select fqtyinv from #inv where id = @m)update #inv set chaQty = @chaQty where id = @mif @chaQty > 0 beginupdate #inv set MTOChange = FQtyInv where id = @mendelse if @chaQty <= 0 beginupdate #inv set MTOChange = FQtyInv + chaQty where id = @mendset @m = @m +1endupdate #inv set fno = id where chaQty <=0declare @top intdeclare @sumMTOchange decimal(28,10) | |
if @isall = 0 beginselect @top = MIN(fno) from #inv/* [FEntrySelfP0133] MTO调整数量;[FEntrySelfP0134] MTO关联标志 */ | |
update PORequestEntry set FEntrySelfP0133 = @reqQty / dbo.coeff(@FDetailID),FMRPClosed = 1 ,FEntrySelfP0134 = 1where FDetailID = @FDetailID | |
drop table #sumMTOchangeendelse if @isall = 1 beginselect @top = count(id) from #inv/* [FEntrySelfP0133] MTO调整数量;[FEntrySelfP0134] MTO关联标志 */ | |
update PORequestEntry set FAuxQty = (@reqQty-dbo.SumQty ( @FDetailID ))/dbo.coeff(@FDetailID), FEntrySelfP0133 = dbo.SumQty ( @FDetailID ) /dbo.coeff(@FDetailID), FEntrySelfP0134 = 1where FDetailID = @FDetailID | |
endselect top(@top) | |
@maxnum AS "FID", | |
'' AS "FIndex", | |
FItemID, --物料内码0 AS "FAuxPropID", | |
FBatchNo, --批号 | |
FStockID, --仓库 | |
FStockPlaceID, --仓位 | |
FQtyInv, --即时库存基本数量null AS "FSecUnitID", --浮动计量单位 | |
FUnitID, --采购申请单计量单位 | |
( FQtyInv / dbo.coeff ( @FDetailID ) ) AS "FQty", --即时库存常用计量单位数量 | |
FSecCoefficient, --辅助计量单位换算率 | |
SecQtyInv, --即时库存辅助计量单位数量0 AS "FChangeQty_Base", | |
( MTOChange / dbo.coeff ( @FDetailID ) ) AS "FChangeQty", | |
case when FSecCoefficient = 0 then 0 else ( MTOChange / FSecCoefficient ) end AS "FChangeSecQty", | |
case when (fkfdate = '') then null else FKFDate end AS "FKFDate", | |
FKFPeriod, | |
case when (fkfdate = '') then null else FKFDate + fkfperiod end AS "FPeriodDate", | |
FromMTONo, | |
ToMTONo, | |
MTOChange, | |
0 AS "FSelectedProcID", | |
0 AS "FEntrySupply", | |
0 AS "FStockTypeID", | |
'' AS "FMrpNo"into #t_mtoChangeEntryfrom #invinsert into t_MTOChangeEntry select * from #t_mtoChangeEntrydrop table #t_mtoChangeEntrydrop table #inv | |
end |