`
fuerbosi
  • 浏览: 464060 次
文章分类
社区版块
存档分类
最新评论

TSQL编程之二_存储过程和触发器

 
阅读更多

无论哪个计算机的编程工具,编写出好的子程序、一直是很多程序员的梦想。C语言之所以流行一时,与C语言下庞大的函数库密不可分。

使用T-SQL语言,编程写出子程序/函数的手段,就是编写存储过程和触发器,下面逐一介绍。

一 存储过程

存储过程的格式是:

CREATE PROCEDURE 存储过程名称 (参数) AS

一些SQL语句

例1 有表AVGRADE,表结构如下:

SDEPT VARCHAR(50)

GRADE1 INT

GRADE2 INT

GRADE3 INT

GRADE4 INT

GRADE5 INT

该表用来保存各个专业的平均成绩,其中GRADE1,GRADE5,分别代表CNO=1-5号课程的平均成绩。

设计子程序或者函数的思想,和其他语言是一样的,子程序仅仅处理好一个专业一个课程的平均成绩即可,其余的,就让主程序循环吧。

于是有:

CREATE PROC GETAVGRADE(@MYDEPT VARCHAR(20)) AS

DECLARE @A1 INT

DECLARE @A2 INT

DECLARE @A3 INT

DECLARE @A4 INT

DECLARE @A5 INT

DECLARE @S VARCHAR(100)

SELECT @A1=AVG(GRADE) FROM SC WHERE CNO=1 AND SNO IN (SELECT SNO FROM STUDENT WHERE SDEPT=@MYDEPT)

SELECT @A2=AVG(GRADE) FROM SC WHERE CNO=2 AND SNO IN (SELECT SNO FROM STUDENT WHERE SDEPT=@MYDEPT)

SELECT @A3=AVG(GRADE) FROM SC WHERE CNO=3 AND SNO IN (SELECT SNO FROM STUDENT WHERE SDEPT=@MYDEPT)

SELECT @A4=AVG(GRADE) FROM SC WHERE CNO=4 AND SNO IN (SELECT SNO FROM STUDENT WHERE SDEPT=@MYDEPT)

SELECT @A5=AVG(GRADE) FROM SC WHERE CNO=5 AND SNO IN (SELECT SNO FROM STUDENT WHERE SDEPT=@MYDEPT)

INSERT INTO AVGRADE(SDEPT,GRADE1, GRADE2, GRADE3, GRADE4, GRADE5) VALUES (@MYDEPT,@A1,@A2,@A3,@A4,@A5)

注意此处,使用CREATE PROC、而不是CREATE PROCEDURE,这个写法也是正确的,这个存储过程有个参数@MYDEPT,就是所在院系。

上面的这个范例中,表AVGRADE纯粹是用户报表在数据库系统中的翻版,和任何数据库设计方法没关系,这个把数据库表自身内模式结构转换成用户用户外模式的做法,很有代表性,经常会有把竖表(数据库内模式表)转换成横表(用户外模式表)的要求,这个要求下,例1就是个典型范例。一些大型的报表系统,也是利用这样的表作为报表的基础,例如著名的CRYSTAL报表系统。

上述存储过程,写入查询分析器后,并不会有什么现象,但如果我们新开一个查询窗口,写入:

DELETE FROM AVGRADE

EXEC GETAVGRADE 'CS'

SELECT * FROM AVGRADE

则会看到这个存储过程执行的结果,的确很简单。

在大多计算机开发程序中,如C#,也可以使用如:

String SQL=Exec GETAVGRADE CS’”;

MyDB.Execute(SQL);

这样的语句来执行这个存储过程,假如权限对,结果就有了,这比在C#、VB等程序中实现要高效的多。

实际做法中,上例不仅仅CS专业,也可能是所有专业,那么就需要一个循环来读每个专业,无重复读专业是用:

select distinct sdept from student

但逐一读出,则需要游标方式:

例2 按课程、统计所有专业的学生平均成绩

参照上节课的例18,我们不难组织出以下程序:

DECLARE @MYDEPT VARCHAR(20)

DECLARE MYCURSOR CURSOR FOR SELECT distinct SDEPT FROM STUDENT

OPEN MYCURSOR

FETCH NEXT FROM MYCURSOR INTO @MYDEPT

PRINT @MYDEPT

WHILE (@@FETCH_STATUS=0)

BEGIN

FETCH NEXT FROM MYCURSOR INTO @MYDEPT

IF @@FETCH_STATUS=0

PRINT @MYDEPT

END

CLOSE MYCURSOR

DEALLOCATE MYCURSOR

在查询分析器中执行,则可以看到所有专业,这个程序的含义不再重复。现在,就是把PRINT修改成:

EXEC GETAVGRADE @MYDEPT

即可,当然,不要忘记在开始一定加上:

DELETE FROM AVGRADE

就是清除前次统计的结果,于是有:

DECLARE @MYDEPT VARCHAR(20)

DECLARE MYCURSOR CURSOR FOR SELECT distinct SDEPT FROM STUDENT

DELETE FROM AVGRADE

OPEN MYCURSOR

FETCH NEXT FROM MYCURSOR INTO @MYDEPT

EXEC GETAVGRADE @MYDEPT

WHILE (@@FETCH_STATUS=0)

BEGIN

FETCH NEXT FROM MYCURSOR INTO @MYDEPT

IF @@FETCH_STATUS=0

EXEC GETAVGRADE @MYDEPT

END

CLOSE MYCURSOR

DEALLOCATE MYCURSOR

SELECT * FROM AVGRADE

结果很理想,差不多又有构造新存储过程的欲望了,所以可以修改上面的程序为:

CREATE PROC ALLAVGRADE AS

DECLARE @MYDEPT VARCHAR(20)

DECLARE MYCURSOR CURSOR FOR SELECT distinct SDEPT FROM STUDENT

DELETE FROM AVGRADE

OPEN MYCURSOR FETCH NEXT FROM MYCURSOR INTO @MYDEPT

EXEC GETAVGRADE @MYDEPT

WHILE (@@FETCH_STATUS=0)

BEGIN

FETCH NEXT FROM MYCURSOR INTO @MYDEPT

IF @@FETCH_STATUS=0 EXEC GETAVGRADE @MYDEPT

END

CLOSE MYCURSOR

DEALLOCATE MYCURSOR

这个存储过程,从定义方式看是一个没参数的存储过程。从存储过程的意义看,也没有保留语句

SELECT * FROM AVGRADE

的必要。

执行完上述创建存储过程的语句后,则在“企业管理器->数据库->SCHOOL->存储过程”中看到这两个存储过程,点开“属性”、则能修改看到这些存储过程的代码。很多情况下,一个存储过程是不希望被用户看到的,那么创建这些存储过程的时候,就一定要写上:

CREATE PROC 存储过程名称(参数) WITH { RECOMPILE | ENCRYPTION | RECOMPILE , ENCRYPTION } AS

这些选项,其中:

RECOMPILE:代表每次执行要重新编译;

ENCRYPTION:代表这个存储过程是要加密的。

例2,有:

CREATE PROC ALLAVGRADE WITH ENCRYPTION AS

这样,你在企业管理器中将看不到这个存储过程的代码,但执行起来则没有任何差别。上述存储过程没有重新编译的要求。编译能提供更快的执行速度,在创建的时候要:

CREATE PROC ALLAVGRADE WITH RECOMPILE AS

如果又要重新编译、又要加密,则使用:

CREATE PROC ALLAVGRADE WITH RECOMPILE,ENCRYPTION AS

这样的语句来创建存储过程。

实际的工作中,涉及报表远比上面的两个范例要复杂。例如,很可能是在很多课程中有选择的显示各个平均成绩,假如我们报表宽度就5例成绩,表头要求是课程名称,则这里就需要动态构造表。

如果用户选择的课程编码在SELCOURSE表中,这个表仅仅有一个字段CNO,用来保存用户统计的课程选择。现在就要按这个表的内容建立新表,则GETAVGRADE就要做大幅度的修改。

例3,按用户选择的课程编码(在表SELCOURSE中),以课程名称为表头、统计各个专业这些课程的平均成绩。

(1)考虑到每次统计表的结构都不一定相同(注意需求是按课程名称建立字段名称),所以这个表要每次都删除前面的表重新建立,所以按上节课程的例8来查看系统表中的内容,然后确定删除这个表,于是有:

IF EXISTS(SELECT * FROM SYSOBJECTS WHERE NAME='AVGRADE' AND XTYPE='U')

DROP TABLE AVGRADE

为此,你也要确保此时必须删除表:AVGRADE。

(2)按SELCOURSE表的CNO,读出这些课程的名称,是很简单的,有:

SELECT CNAME FROM COURSE WHERE CNO IN (SELECT CNO FROM SELCOURSE)

但这个竖立的结果,并不代表能转换成横的表头,于是,又要用到游标方法。思路肯定是游标读SELCOURSE的课程编号,然后读COURSE中课程名称。

修改例18的游标范例,于是有:

DECLARE @MYCNAME VARCHAR(20)

DECLARE @MYCNO CHAR(10)

DECLARE MYCURSOR CURSOR FOR SELECT CNO FROM SELCOURSE

OPEN MYCURSOR

FETCH NEXT FROM MYCURSOR INTO @MYCNO

SELECT @MYCNAME=CNAME FROM COURSE WHERE CNO=@MYCNO

PRINT @MYCNAME

WHILE (@@FETCH_STATUS=0)

BEGIN

FETCH NEXT FROM MYCURSOR INTO @MYCNO

IF @@FETCH_STATUS=0

BEGIN

SELECT @MYCNAME=CNAME FROM COURSE WHERE CNO=@MYCNO

PRINT @MYCNAME

END

END

CLOSE MYCURSOR

DEALLOCATE MYCURSOR

这个程序,会按SELCOURSE表中的课程号,打印出这些课程号的名称,当然,仅仅按SELCOURSE表的课程号打印课程名称,这个要求是简单的,但我们要按这个名称建立表AVGRADE。现在,就要根据@MYCNAME的名称

(3)注意(2)中的结果,打印结果每次都是来自一个变量@MYCNAME,修改上面的程序,于是我们可以拼接一个字符串@SQL0出来:

DECLARE @MYCNAME VARCHAR(20)

DECLARE @MYCNO CHAR(10)

DECLARE @SQL0 VARCHAR(200)

SELECT @SQL0='CREATE TABLE AVGRADE (SDEPT VARCHAR(50),'

DECLARE MYCURSOR CURSOR FOR SELECT CNO FROM SELCOURSE

OPEN MYCURSOR

FETCH NEXT FROM MYCURSOR INTO @MYCNO

SELECT @MYCNAME=CNAME FROM COURSE WHERE CNO=@MYCNO

SELECT @SQL0=@SQL0+@MYCNAME+' INT'

WHILE (@@FETCH_STATUS=0)

BEGIN

FETCH NEXT FROM MYCURSOR INTO @MYCNO

IF @@FETCH_STATUS=0

BEGIN

SELECT @MYCNAME=CNAME FROM COURSE WHERE CNO=@MYCNO

SELECT @SQL0=@SQL0+','+@MYCNAME+' INT'

END

END

CLOSE MYCURSOR

DEALLOCATE MYCURSOR

SELECT @SQL0=@SQL0+')'

PRINT @SQL0

如果你的表SELCOURSE中是1、3、5、6、7,那么结果就是:

CREATE TABLE AVGRADE1 (

SDEPT VARCHAR(50),

数据库 INT,

信息系统 INT,

数据结构 INT,

数据处理 INT,

PASCAL语言 INT

)

这是很漂亮的创建表语句。现在我们就应该明白,最后仅仅是修改PRINT ·SQL0为:

EXEC (SQL0)

即可。但随之我们也立刻明白:要给这个表中插入数据,也必须有相宜的语句:

INSERT INTO AVGRADE (SDEPT, 数据库, 信息系统, 数据结构, 数据处理, PASCAL语言) values ()

这样的要求,使得我们必须故计重施、再次构造INSERT INTO 语句,于是再次加变量@SQL1,有


DECLARE @MYCNAME VARCHAR(20)

DECLARE @MYCNO CHAR(10)

DECLARE @SQL0 VARCHAR(200)

DECLARE @SQL1 VARCHAR(200)

SELECT @SQL0='CREATE TABLE AVGRADE (SDEPT VARCHAR(50)'

SELECT @SQL1='INSERT INTO AVGRADE (SDEPT'

DECLARE MYCURSOR CURSOR FOR SELECT CNO FROM SELCOURSE

OPEN MYCURSOR

FETCH NEXT FROM MYCURSOR INTO @MYCNO

SELECT @MYCNAME=CNAME FROM COURSE WHERE CNO=@MYCNO

SELECT @SQL0=@SQL0+@MYCNAME+' INT'

SELECT @SQL1=@SQL1+','+@MYCNAME

WHILE (@@FETCH_STATUS=0)

BEGIN

FETCH NEXT FROM MYCURSOR INTO @MYCNO

IF @@FETCH_STATUS=0

BEGIN

SELECT @MYCNAME=CNAME FROM COURSE WHERE CNO=@MYCNO

SELECT @SQL0=@SQL0+','+@MYCNAME+' INT'

SELECT @SQL1=@SQL1+','+@MYCNAME

END

END

CLOSE MYCURSOR

DEALLOCATE MYCURSOR

SELECT @SQL0=@SQL0+')'

SELECT @SQL1=@SQL1+') VALUES ('

PRINT @SQL0

PRINT @SQL1

(4)上述过程完成后,我们还要计算每个课程的平均成绩,就是类似:

SELECT @A1=AVG(GRADE) FROM SC WHERE CNO=1 AND SNO IN (SELECT SNO FROM STUDENT WHERE SDEPT=@MYDEPT)

我们不难发现:CNO这里将来自SELCOURSE表,要逐个取得,于是再次用到游标,修改例18为:

DECLARE @MYCNO CHAR(10)

DECLARE MYCURSOR CURSOR FOR SELECT CNO FROM SELCOURSE

OPEN MYCURSOR

FETCH NEXT FROM MYCURSOR INTO @MYCNO

PRINT @MYCNO

WHILE (@@FETCH_STATUS=0)

BEGIN

FETCH NEXT FROM MYCURSOR INTO @MYCNO

IF @@FETCH_STATUS=0

PRINT @MYCNO

END

CLOSE MYCURSOR

DEALLOCATE MYCURSOR

上面程序将逐行显示SELCOURSE中的内容,修改上面的程序,和(1)、(2)的完整组成后有:

CREATE PROC GETAVGRADE (@MYDEPTS VARCHAR(50)) AS

DECLARE @MYCNAME VARCHAR(20)

DECLARE @MYCNO CHAR(10)

DECLARE @SQL0 VARCHAR(200)

DECLARE @SQL1 VARCHAR(200)

DECLARE @SQL2 VARCHAR(200)

DECLARE @A1 INT

--原来有平均成绩表就删除

IF EXISTS(SELECT * FROM SYSOBJECTS WHERE NAME='AVGRADE' AND XTYPE='U')

DROP TABLE AVGRADE

--构造建立表和插入语句

SELECT @SQL0='CREATE TABLE AVGRADE (SDEPT VARCHAR(50)'

SELECT @SQL1='INSERT INTO AVGRADE (SDEPT'

DECLARE MYCURSOR CURSOR FOR SELECT CNO FROM SELCOURSE

OPEN MYCURSOR

FETCH NEXT FROM MYCURSOR INTO @MYCNO

SELECT @MYCNAME=CNAME FROM COURSE WHERE CNO=@MYCNO

SELECT @SQL0=@SQL0+@MYCNAME+' INT'

SELECT @SQL1=@SQL1+','+@MYCNAME

WHILE (@@FETCH_STATUS=0)

BEGIN

FETCH NEXT FROM MYCURSOR INTO @MYCNO

IF @@FETCH_STATUS=0

BEGIN

SELECT @MYCNAME=CNAME FROM COURSE WHERE CNO=@MYCNO

SELECT @SQL0=@SQL0+','+@MYCNAME+' INT'

SELECT @SQL1=@SQL1+','+@MYCNAME

END

END

CLOSE MYCURSOR

DEALLOCATE MYCURSOR

SELECT @SQL0=@SQL0+')'

SELECT @SQL1=@SQL1+') VALUES ('

EXEC(@SQL0)

--调试中,此处最好打印这些

--PRINT @SQL1

SELECT @SQL2=@SQL1

SELECT @SQL2=@SQL2+''''+@MYDEPTS+''''

DECLARE MYCURSOR CURSOR FOR SELECT CNO FROM SELCOURSE

OPEN MYCURSOR

FETCH NEXT FROM MYCURSOR INTO @MYCNO

SELECT @A1=ISNULL(AVG(GRADE),0) FROM SC WHERE CNO=@MYCNO AND SNO IN (SELECT SNO FROM STUDENT WHERE SDEPT=@MYDEPTS)

SELECT @SQL2=@SQL2+','+STR(@A1)

WHILE (@@FETCH_STATUS=0)

BEGIN

FETCH NEXT FROM MYCURSOR INTO @MYCNO

IF @@FETCH_STATUS=0

SELECT @A1=ISNULL(AVG(GRADE),0) FROM SC WHERE CNO=@MYCNO AND SNO IN (SELECT SNO FROM STUDENT WHERE SDEPT=@MYDEPTS)

SELECT @SQL2=@SQL2+','+STR(@A1)

END

CLOSE MYCURSOR

DEALLOCATE MYCURSOR

SELECT @SQL2=@SQL2+')'

EXEC (@SQL2)

这个范例比较复杂,但实际工程中使用很多,实际工程中凑最终报表,很多过程和这三个范例有相同的做法。希望不要忘记这些。

上面语句,其中:

SELECT @A1=ISNULL(AVG(GRADE),0)

的含义是:假如平均数AVG(GRADE)没有结果是NULL,则给出0。

存储过程的主要用途,是大型数据库系统的统计功能,在这些地方,使用数据库的存储过序是必须的,例如一个大型的商场,每月发生的项目(买卖项目)都在百万条记录以上,高速统计出结果十分必要。

二 触发器

触发器 是一个特定表上、由INSERT、UPDATE、DELETE引发的一系列其他操作。所以,在企业管理器中,触发器要在“表-》所有任务-》管理触发器”中才能找到。

创建触发器由以下语句实现:

CREATE TRIGGER 触发器名称 ON 表名称

FOR INSERT, UPDATE, DELETE

AS

例4 让我们先建立两个简单表,TABLE1、TABLE2

TABLE1中:

ID:INT 行编号,这里为1,仅仅有一行

C1: INT 奇数个数

C2: INT 偶数个数

TABLE2中:

X: INT

我们现在要在TABLE2中不断插入数字,而在TABLE1的C1、C2中分别统计TABLE2中插入X中奇偶数据的个数,当然,TABLE1中C1、C2开始一定是0。

我们分析后不难发现,这个操作要求是针对表TABLE2的,所以要为TABLE2设计触发器、而且是INSERT触发器。

在INSERT触发器中,有个非常重要的表:INSERTED,这个表代表刚才由INSERT语句插入的记录,插入的所有数据都在这个表里。

所以有:

CREATE TRIGGER INTOTABLE2 ON TABLE2 FOR INSERT AS

DECLARE @MYX INT

DECLARE @NYX INT

DECLARE @C INT

SELECT @MYX=X FROM INSERTED

--注意SQLSERVER没有MOD函数

SELECT @NYX=@MYX/2

IF @NYX*2=@MYX

BEGIN

SELECT @C=C2 FROM TABLE1 WHERE ID=1

SELECT @C=@C+1

UPDATE TABLE1 SET C2=@C WHERE ID=1

END

ELSE

BEGIN

SELECT @C=C1 FROM TABLE1 WHERE ID=1

SELECT @C=@C+1

UPDATE TABLE1 SET C1=@C

END

在SQLSERVER中,UPDATE修改表后,如果要做UPDATE触发器,则被修改记录一样是在INSERTED表中,这点要一定注意。

例5 假如有表TABLE3,结构同TABLE2,也是仅仅有字段X,在TABLE2发生删除的时候,把删除的数据复制到TABLE3

分析这个问题,就知道依然是要在TABLE2上建立触发器,这里是要建立DELETE触发器。

这个就简单的多了,SQLSERVER删除的表中记录,全部在DELETED表中,所以有:

CREATE TRIGGER DELETABLE2 ON TABLE2 FOR DELETE AS

INSERT INTO TABLE3 SELECT X FROM DELETED

例4、5,如果要测试的话,不必一定在查询分析器中用SQL语句实现,在企业管理器中,直接插入、删除一样有作用。

严格说起来,要保证TABLE1中结果正确,上面的两个范例是有漏洞的,如果用户插入一个数字如8后,用UPDATE修改这个它为9,则计数就不一对了,所以还要为该表做UPDATE触发器,同理,删除一个数据后,也要根据删除的数据,修改TABLE1中的记录,所以删除触发器同样过于简单。更加完善的操作,就留给同学们自己完成。


作业 :

1 将例4、5建立的更加完善,让在任何操作下,TABLE1对TABLE2的奇偶数统计都正确;

CREATE TRIGGER INTOTABLE1 ON TABLE2 FOR delete AS删除

DECLARE @MYX INT

DECLARE @NYX INT

DECLARE @C INT

SELECT @MYX=X FROM DELETED

--注意SQLSERVER没有MOD函数

SELECT @NYX=@MYX/2

IF @NYX*2=@MYX

BEGIN

SELECT @C=C2 FROM TABLE1 WHERE ID=1

SELECT @C=@C-1

UPDATE TABLE1 SET C2=@C WHERE ID=1

END

ELSE

BEGIN

SELECT @C=C1 FROM TABLE1 WHERE ID=1

SELECT @C=@C-1

UPDATE TABLE1 SET C1=@C

END

CREATE TRIGGER INTOTABLE2 ON TABLE2 FOR INSERT,update AS插入和修改

DECLARE @MYX INT

DECLARE @NYX INT

DECLARE @C INT

SELECT @MYX=X FROM INSERTED

--注意SQLSERVER没有MOD函数

SELECT @NYX=@MYX/2

IF @NYX*2=@MYX

BEGIN

SELECT @C=C2 FROM TABLE1 WHERE ID=1

SELECT @C=@C+1

UPDATE TABLE1 SET C2=@C WHERE ID=1

END

ELSE

BEGIN

SELECT @C=C1 FROM TABLE1 WHERE ID=1

SELECT @C=@C+1

UPDATE TABLE1 SET C1=@C

END

2 在学生管理系统中,假如休学的同学,其学生记录以及选课成绩等要存放到BSTUDENT、BSC表中,然后删除STUDENT、SC表中记录,请设计触发器完成;

(对于SC表):

CREATE TRIGGER DETETE_SC ON SC FOR delete AS

DECLARE @SNO NCHAR(10)

DECLARE @CNO VARCHAR(10)

DECLARE @GRADE int

SELECT @SNO=Sno,@CNO=Cno,@GRADE=Grade FROM DELETED

if @SNO!=null and @CNO!=null

begin

insert BStCon values(@SNO,@CNO,@GRADE)

end

(对于student表):

CREATE TRIGGER DETETE_ST ON student FOR delete AS

DECLARE @SNO NCHAR(10)

DECLARE @SNAME VARCHAR(36)

DECLARE @SSEX CHAR(2)

DECLARE @SAGE INT

DECLARE @SDEPT VARCHAR(50)

SELECT @SNO=Sno,@SNAME=Sname,@SSEX=Ssex,@SAGE=Sage,@SDEPT=Sdept FROM DELETED

if @SNO!=null

begin

insert Bstudent values(@SNO,@SNAME,@SSEX,@SAGE,@SDEPT)

end

3 仓库管理中有零件表PART,InAccount、OutAccount表,结构分别如下:

PART表:

PartNo: char(10) 零件编码,表主码

Pname: varchar(50)零件名称

BinLOC: char(10) 零件货位

Unit char(2) 单位,如件,个

QTY int 当前库存总数

NCOST float 最近平均成本

InAccount表:

PartNo: char(10) 零件编码,外键参照PART.PartNO

InDate Datetime 入库日期

InQTY int 入库数量

InPrice float 入库单价

InSum float 入库总价

BQTY int 入库后库存总数

Bprice float 入库后平均成本

BSUM float 入库后该零件总价值

OutAccount表:

PartNo: char(10) 零件编码,外键参照PART.PartNO

OutDate Datetime 出库日期

OutQTY int 出库数量

OutPrice float 出库单价

OutSum float 出库总价

BQTY int 出库后库存总数

Bprice float 出库后平均成本

BSUM float 出库后该零件总价值

在PART表中,如果有

PartNo

Pname

BinLoc

Unit

QTY

NCOST

1000

S1

A2100

10

100

说明编号1000、名称为S1的零件,有10个,存在库房A2100位置,每个单价100元,仓库在2008年8月1日投入运营,这个数据代表2008年8月1日前的库存,在10月有以下进出货活动:

(1)

2008年8月5日,出库8个,单价100元,于是在OutAccount表中记录:

PartNo

OutDate

OutQTY

OutPrice

OutSum

BQTY

Bprice

BSUM

1000

2008-8-5

8

100

800

2

100

200

在出库中,出库单价总是来自PART表中的NCOST,同时,PART表则变化为:

PartNo

Pname

BinLoc

Unit

QTY

NCOST

1000

S1

A2100

2

100

(2)

2008年8月10日,进货S1共20个,单价105元,则在InAccount表中为:

PartNo

InDate

InQTY

InPrice

InSum

BQTY

Bprice

BSUM

1000

2008-8-10

20

105

2100

22

104.55

2300

这里,

Bprice=(本次进货数量*本次进货单价+库房余存数量*余存单价)/(本次进货数量+余存数量)

所以有:

Bprice=(20*105+2*100)/(20+2)=104.55

BQTY=20+2

BSUM=20*105+2*100=22*(104.55)=2300

同上式,PART也要同时修改如下:

PartNo

Pname

BinLoc

Unit

QTY

NCOST

1000

S1

A2100

22

104.55

(3)

2008年8月20日,按104.55出库15个

PartNo

OutDate

OutQTY

OutPrice

OutSum

BQTY

Bprice

BSUM

1000

2008-8-5

8

100

800

2

100

200

1000

2008-8-20

15

104.55

158.25

7

104.55

731.85

同时PART表变化为:

PartNo

Pname

BinLoc

Unit

QTY

NCOST

1000

S1

A2100

7

104.55

(4)

2008年8月30日,按104.55出库5个

PartNo

OutDate

OutQTY

OutPrice

OutSum

BQTY

Bprice

BSUM

1000

2008-8-5

8

100

800

2

100

200

1000

2008-8-20

15

104.55

158.25

7

104.55

731.85

1000

2008-8-30

5

104.55

522.75

2

104.55

209.1

同时PART表变化为:

PartNo

Pname

BinLoc

Unit

QTY

NCOST

1000

S1

A2100

2

104.55

(5)

2008年9月5日,按106元入库20个,S1单价变为:105.87元

PartNo

InDate

InQTY

InPrice

InSum

BQTY

Bprice

BSUM

1000

2008-8-10

20

105

2100

22

104.55

2300

1000

2008-9-5

20

106

2120

22

105.87

2329.1

同时PART表变化为:

PartNo

Pname

BinLoc

Unit

QTY

NCOST

1000

S1

A2100

22

105.87

(6)

2008年9月10日,按105.8出库15个

PartNo

OutDate

OutQTY

OutPrice

OutSum

BQTY

Bprice

BSUM

1000

2008-8-5

8

100

800

2

100

200

1000

2008-8-20

15

104.55

158.25

7

104.55

731.85

1000

2008-8-30

5

104.55

522.75

2

104.55

209.1

1000

2008-9-10

15

105.8

1587

7

105.8

740.6

同时PART表变化为:

PartNo

Pname

BinLoc

Unit

QTY

NCOST

1000

S1

A2100

7

105.8

(7)

2008年9月15日,按102元入库30个,求:

1 2008-9-15日入库后S1单价;

2 编写入库、出库的存储过程、入库修改PART最近成本单价NCOST的触发器;

3 编写库存动态表,表头格式为:

零件编号

上月结余(金额)

本月入库(金额)

本月出库(金额)

本月结余(金额)

其中有:

上月结余+本月入库-本月出库=本月结余

这个式子在财务上称为钩稽关系。不满足这个条件,称为帐目不平。这其中如果有:

上月结余+本月入库-本月出库-本月结余,一个项目入S1,其金额差额在1元内,则财务是认可的。

实际工程中,由于这个表规模很大,所以不能使用游标方式。

所以请在诸如临时表建立、Group By PartNo这类语句上下工夫。这个是来自实际购销存、产供销管理系统共同有的子系统:库存-成本管理的实际范例。能满足大规模高速计算非常有意义,否则不能想象类似沃尔玛这类大型跨国超级商业巨头的核算该怎么办。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics