前几天写了关于单物料进出仓的存储过程,现在要求从Delphi 7编写的Client端可以同时操作多物料进仓,也就是说可以从客户端一次向数据库提交多条记录。
在这个题目中,有人采用了在数据库中建立临时表,每次在客户端增加一条记录,就将这条记录提交到临时表,输入结束后,通过调用SQL SERVER 中的存储过程将临时表中的记录再转到正式表中。这种办法可行,但是每增加一条记录,都需将记录先送入临时表,这样就需要访问一次数据库,无疑增加了客户端和数据库之间的通信量,造成不必要的浪费。
但是,由于SQL SERVER不能接受数组参数,所以无法在Client端将多条记录用数组形式提交到SQL SERVER 的存储过程,这时可以使用一种变通的办法。
由于 SQL SERVER 有强大的字符串处理能力,因此可以将输入的多条进出仓记录在Client以一定得格式组织成字符串,然后使用SQL SERVER 中的字符串处理功能将提交的字符串再一次分割成独立的记录,供进出仓操作。
以下是具体的实现。
首先建立一个视图:
create view multigio
as
select j060230gtb.gid,j060230gtb.gname,j060230gtb.gunit,j060230giotb.iosize,j060230giotb.iomoney,j060230giotb.remarks,j060230giotb.iodate,j060230utb.uid from j060230utb,j060230gtb,j060230giotb
where 1=2
该视图用来在Client端使用ADOTable生成一个包含所需参数的空表(之所以是空表是因为where 1=2)。为了能直观的看到输入的结果,再添加一个TDBGrid组件,将其和ADOTable组件关联。设置ADOTable不自动提交更新,否则就出错了,因为它对应的是一个多张表连接得到视图,这个ADOTable只是用来暂存数据的,里头的数据是永远不送到数据库的。
进出仓记录添加完成后,通过循环将ADOTable的记录按行循环读出,每个字段之间用指定的符号分割(本例使用的是键盘TAB键上面的那个'`'),记录之间用不同的分隔符,本例使用分号";",连接成一个字符串。然后调用指定的存储过程,将这个字符串作为参数。Delphi代码如下:
if RbtnMulti.Checked = true then //多物料
begin
//构造字符串
rec_set := '';
DBGridTmpView.DataSource.DataSet.First;
if DBGridTmpView.DataSource.DataSet.FieldByName('gid').AsString = '' then
begin
showmessage('请输入信息!');
exit;
end;
while not DBGridTmpView.DataSource.DataSet.eof do
begin
rec_set := rec_set + DBGridTmpView.DataSource.DataSet.FieldByName('gid').AsString + '`';
rec_set := rec_set + DBGridTmpView.DataSource.DataSet.FieldByName('gname').AsString + '`';
rec_set := rec_set + DBGridTmpView.DataSource.DataSet.FieldByName('gunit').AsString + '`';
rec_set := rec_set + DBGridTmpView.DataSource.DataSet.FieldByName('iosize').AsString + '`';
rec_set := rec_set + DBGridTmpView.DataSource.DataSet.FieldByName('iomoney').AsString + '`';
rec_set := rec_set + DBGridTmpView.DataSource.DataSet.FieldByName('iodate').AsString + '`';
rec_set := rec_set + DBGridTmpView.DataSource.DataSet.FieldByName('remarks').AsString + '`';
rec_set := rec_set + DBGridTmpView.DataSource.DataSet.FieldByName('uid').AsString + '`';
rec_set := rec_set + ';';
DBGridTmpView.DataSource.DataSet.Next;
end;
//showmessage(rec_set);
with DataMo.ADOSProcMGio do
begin
close;
Parameters.ParamByName('@m_rec_set').Value := rec_set;
if RbtnPush.Checked = true then
Parameters.ParamByName('@m_iomark').Value := '1'
else
Parameters.ParamByName('@m_iomark').Value := '0';
Parameters.ParamByName('@m_stat').Value := '';
Parameters.ParamByName('@m_msg').Value := '';
execProc;
pmsg := Parameters.ParamByName('@m_stat').Value;
billno := Parameters.ParamByName('@m_msg').Value;
showmessage(pmsg+ ' 单号:' + billno);
RbtnSingle.Checked := true;
RefreshTables(billno,'');
end;
end;
通过以上代码可以将输入连接成符合存储过程切分需要格式的字符串,并且执行进出仓的操作,里头的RefreshTables()函数是自定义的。
一下是具体的存储过程代码:
--drop proc ProcMultiGio
/*-----------------------多物料进出仓-------------------------*/
/*该存储过程使用";"(分号)做记录分割符,使用"`"(TAB键上面的键)做字段分割符*/
create proc ProcMultiGio
@m_rec_set varchar(2000), --客户端提交的总集合
@m_iomark char(1), --进出标志
@m_stat varchar(30) output, --返回状态
@m_msg varchar(10) output --成功返回单号
as
declare
--内部字段参数
@m_gid char(4), --1、物料代码
@m_gname varchar(20), --2、物料名称
@m_gunit varchar(8), --3、物料计量单位
@m_iosize numeric(10,2), --4、进出数量
@m_iomoney numeric(10,2), --5、进出钱数
@m_iodate datetime, --6、进出日期
@m_remarks varchar(60), --7、备注信息
@m_uid char(4), --8、操作人员代码
--内部中间量
@m_billno char(10), --单号
@rowpoint int, --行分割符索引
@colpoint int, --列分割符索引
@eachrow char (200), --分割出来的整行
@m_storesize numeric(10,2), --库存量
@p_space varchar(2), --填充
@p_now varchar(12) --时间串
begin transaction --启动事务
/*生成单号*/
set @m_billno = (select top 1 billno from j060230giotb order by bid desc);/*获取最后一个单号*/
if @m_billno IS NULL
set @m_billno = 'S000000000';
set @m_billno = substring(@m_billno,8,3);
set @m_billno = convert(varchar(10),(cast(@m_billno as int) + 1));
set @p_space = replace(space(3-len(@m_billno)),' ','0');
set @p_now = (SELECT stuff(replace(CAST(GETDATE() AS CHAR(10)),' ',''),5,2,''));
set @m_billno = 'T' + @p_now + @p_space + @m_billno; /*形成最终单号 进出标志+时间+填充0 + 编号*/
/*单号生成完成*/
/*进行字符串的拆分*/
while len(@m_rec_set) > 0 --循环进行记录分割
begin
select @rowpoint = charindex(';',@m_rec_set) --读第一个记录分割符的字符串索引
select @eachrow = substring(@m_rec_set,1,@rowpoint-1) --取出一条记录
select @m_rec_set = substring(@m_rec_set,@rowpoint+1,len(@m_rec_set)-@rowpoint) --从记录集合中删除读过的记录
if len(@eachrow) > 0 --还有记录,对行进行字段分割,将个值赋予每个字段参数
begin
--取第一个字段gid->@m_gid
select @colpoint = charindex('`',@eachrow) --第一字段末尾
select @m_gid = substring(@eachrow,1,@colpoint-1) --去第一个字段
select @eachrow = substring(@eachrow,@colpoint+1,len(@eachrow)-@colpoint) --取剩下的字符串
-- 取第二个字段gname->@m_gname
select @colpoint = charindex('`',@eachrow)
select @m_gname = substring(@eachrow,1,@colpoint-1)
select @eachrow = substring(@eachrow,@colpoint+1,len(@eachrow)-@colpoint)
--取第三个字段gunit->@m_gunit
select @colpoint = charindex('`',@eachrow)
select @m_gunit = substring(@eachrow,1,@colpoint-1)
select @eachrow = substring(@eachrow,@colpoint+1,len(@eachrow)-@colpoint)
--取第四个字段iosize->@m_iosize
select @colpoint = charindex('`',@eachrow)
select @m_iosize = substring(@eachrow,1,@colpoint-1)
select @eachrow = substring(@eachrow,@colpoint+1,len(@eachrow)-@colpoint)
--取第五个字段iomoney->@m_iomoney
select @colpoint = charindex('`',@eachrow)
select @m_iomoney = substring(@eachrow,1,@colpoint-1)
select @eachrow = substring(@eachrow,@colpoint+1,len(@eachrow)-@colpoint)
--取第六个字段iodate->@m_iodate
select @colpoint = charindex('`',@eachrow)
select @m_iodate = substring(@eachrow,1,@colpoint-1)
select @eachrow = substring(@eachrow,@colpoint+1,len(@eachrow)-@colpoint)
--取第七个字段remarks->@m_remarks
select @colpoint = charindex('`',@eachrow)
select @m_remarks = substring(@eachrow,1,@colpoint-1)
select @eachrow = substring(@eachrow,@colpoint+1,len(@eachrow)-@colpoint)
--取第八个字段uid->@m_uid
select @colpoint = charindex('`',@eachrow)
select @m_uid = substring(@eachrow,1,@colpoint-1)
select @eachrow = substring(@eachrow,@colpoint+1,len(@eachrow)-@colpoint)
--以下根据取得的参数判断进出仓操作
if @m_iomark = '1' --if input
---------进仓--------------------
begin
if exists (select gid from j060230gtb where gid=@m_gid) /*判断是否物料是否存在*/
update j060230gtb set gsize=gsize+@m_iosize where gid=@m_gid; /*存在就更新物料的库存数量*/
else
insert into j060230gtb (gid,gname,gsize,gunit) values (@m_gid,@m_gname,@m_iosize,@m_gunit); /*不存在先添加物料到物料表*/
insert into j060230giotb (billno,gid,iosize,iomark,iomoney,remarks,uid,iodate) /*将进仓信息进表*/
values(@m_billno,@m_gid,@m_iosize,'1',@m_iomoney,@m_remarks,@m_uid,@m_iodate)
end --end if input
---------进仓------------------
if @m_iomark = '0' --if output
---------出仓------------------
begin
if not exists (select gid from j060230gtb where gid=@m_gid) /*判断是否物料是否存在*/
begin --物料不存在
select @m_stat = @m_gid + '物料不存在!出库失败!'
select @m_msg = '无'
rollback transaction --事务回滚
return --结束存储过程
end
--物料存在
set @m_storesize =(select gsize from j060230gtb where gid=@m_gid) --读库存量
if @m_storesize < @m_iosize --不够出
begin
select @m_stat = @m_gid +'库存不足,出库失败!'
select @m_msg = '无'
rollback transaction --事务回滚
return --结束存储过程
end
--满足出仓条件
update j060230gtb set gsize=gsize-@m_iosize where gid=@m_gid --存在就更新物料的库存数量
-- set @m_stat = '出库成功!'
insert into j060230giotb (billno,gid,iosize,iomark,iomoney,remarks,uid,iodate) /*将出仓信息进表*/
values(@m_billno,@m_gid,@m_iosize,'0',@m_iomoney,@m_remarks,@m_uid,@m_iodate)
--select @m_msg = @m_billno
end --end if output
---------出仓--------------------
end --end if
end --end while
select @m_stat = '操作成功!'
select @m_msg = @m_billno
commit transaction --end transaction
go
/*-----------------多物料进出仓----------------------------*/
这个存储过程完成了记录的切分和进出仓的操作。
到此多物料进出仓就完成了。
补充:
前一次单物料和这次的多物料共同的缺陷在于生成单号的地方。两次所使用的单号生成方法是相同的,但是这种方法的原理是读最后的单号,生成本次的单号。如果考虑到并发,这种的问题是显而易见的,非常容易出错。因此,这个问题需要采用另外的办法解决,考虑到与本文无关,这里不详细写。
在这个题目中,有人采用了在数据库中建立临时表,每次在客户端增加一条记录,就将这条记录提交到临时表,输入结束后,通过调用SQL SERVER 中的存储过程将临时表中的记录再转到正式表中。这种办法可行,但是每增加一条记录,都需将记录先送入临时表,这样就需要访问一次数据库,无疑增加了客户端和数据库之间的通信量,造成不必要的浪费。
但是,由于SQL SERVER不能接受数组参数,所以无法在Client端将多条记录用数组形式提交到SQL SERVER 的存储过程,这时可以使用一种变通的办法。
由于 SQL SERVER 有强大的字符串处理能力,因此可以将输入的多条进出仓记录在Client以一定得格式组织成字符串,然后使用SQL SERVER 中的字符串处理功能将提交的字符串再一次分割成独立的记录,供进出仓操作。
以下是具体的实现。
首先建立一个视图:
create view multigio
as
select j060230gtb.gid,j060230gtb.gname,j060230gtb.gunit,j060230giotb.iosize,j060230giotb.iomoney,j060230giotb.remarks,j060230giotb.iodate,j060230utb.uid from j060230utb,j060230gtb,j060230giotb
where 1=2
该视图用来在Client端使用ADOTable生成一个包含所需参数的空表(之所以是空表是因为where 1=2)。为了能直观的看到输入的结果,再添加一个TDBGrid组件,将其和ADOTable组件关联。设置ADOTable不自动提交更新,否则就出错了,因为它对应的是一个多张表连接得到视图,这个ADOTable只是用来暂存数据的,里头的数据是永远不送到数据库的。
进出仓记录添加完成后,通过循环将ADOTable的记录按行循环读出,每个字段之间用指定的符号分割(本例使用的是键盘TAB键上面的那个'`'),记录之间用不同的分隔符,本例使用分号";",连接成一个字符串。然后调用指定的存储过程,将这个字符串作为参数。Delphi代码如下:
if RbtnMulti.Checked = true then //多物料
begin
//构造字符串
rec_set := '';
DBGridTmpView.DataSource.DataSet.First;
if DBGridTmpView.DataSource.DataSet.FieldByName('gid').AsString = '' then
begin
showmessage('请输入信息!');
exit;
end;
while not DBGridTmpView.DataSource.DataSet.eof do
begin
rec_set := rec_set + DBGridTmpView.DataSource.DataSet.FieldByName('gid').AsString + '`';
rec_set := rec_set + DBGridTmpView.DataSource.DataSet.FieldByName('gname').AsString + '`';
rec_set := rec_set + DBGridTmpView.DataSource.DataSet.FieldByName('gunit').AsString + '`';
rec_set := rec_set + DBGridTmpView.DataSource.DataSet.FieldByName('iosize').AsString + '`';
rec_set := rec_set + DBGridTmpView.DataSource.DataSet.FieldByName('iomoney').AsString + '`';
rec_set := rec_set + DBGridTmpView.DataSource.DataSet.FieldByName('iodate').AsString + '`';
rec_set := rec_set + DBGridTmpView.DataSource.DataSet.FieldByName('remarks').AsString + '`';
rec_set := rec_set + DBGridTmpView.DataSource.DataSet.FieldByName('uid').AsString + '`';
rec_set := rec_set + ';';
DBGridTmpView.DataSource.DataSet.Next;
end;
//showmessage(rec_set);
with DataMo.ADOSProcMGio do
begin
close;
Parameters.ParamByName('@m_rec_set').Value := rec_set;
if RbtnPush.Checked = true then
Parameters.ParamByName('@m_iomark').Value := '1'
else
Parameters.ParamByName('@m_iomark').Value := '0';
Parameters.ParamByName('@m_stat').Value := '';
Parameters.ParamByName('@m_msg').Value := '';
execProc;
pmsg := Parameters.ParamByName('@m_stat').Value;
billno := Parameters.ParamByName('@m_msg').Value;
showmessage(pmsg+ ' 单号:' + billno);
RbtnSingle.Checked := true;
RefreshTables(billno,'');
end;
end;
通过以上代码可以将输入连接成符合存储过程切分需要格式的字符串,并且执行进出仓的操作,里头的RefreshTables()函数是自定义的。
一下是具体的存储过程代码:
--drop proc ProcMultiGio
/*-----------------------多物料进出仓-------------------------*/
/*该存储过程使用";"(分号)做记录分割符,使用"`"(TAB键上面的键)做字段分割符*/
create proc ProcMultiGio
@m_rec_set varchar(2000), --客户端提交的总集合
@m_iomark char(1), --进出标志
@m_stat varchar(30) output, --返回状态
@m_msg varchar(10) output --成功返回单号
as
declare
--内部字段参数
@m_gid char(4), --1、物料代码
@m_gname varchar(20), --2、物料名称
@m_gunit varchar(8), --3、物料计量单位
@m_iosize numeric(10,2), --4、进出数量
@m_iomoney numeric(10,2), --5、进出钱数
@m_iodate datetime, --6、进出日期
@m_remarks varchar(60), --7、备注信息
@m_uid char(4), --8、操作人员代码
--内部中间量
@m_billno char(10), --单号
@rowpoint int, --行分割符索引
@colpoint int, --列分割符索引
@eachrow char (200), --分割出来的整行
@m_storesize numeric(10,2), --库存量
@p_space varchar(2), --填充
@p_now varchar(12) --时间串
begin transaction --启动事务
/*生成单号*/
set @m_billno = (select top 1 billno from j060230giotb order by bid desc);/*获取最后一个单号*/
if @m_billno IS NULL
set @m_billno = 'S000000000';
set @m_billno = substring(@m_billno,8,3);
set @m_billno = convert(varchar(10),(cast(@m_billno as int) + 1));
set @p_space = replace(space(3-len(@m_billno)),' ','0');
set @p_now = (SELECT stuff(replace(CAST(GETDATE() AS CHAR(10)),' ',''),5,2,''));
set @m_billno = 'T' + @p_now + @p_space + @m_billno; /*形成最终单号 进出标志+时间+填充0 + 编号*/
/*单号生成完成*/
/*进行字符串的拆分*/
while len(@m_rec_set) > 0 --循环进行记录分割
begin
select @rowpoint = charindex(';',@m_rec_set) --读第一个记录分割符的字符串索引
select @eachrow = substring(@m_rec_set,1,@rowpoint-1) --取出一条记录
select @m_rec_set = substring(@m_rec_set,@rowpoint+1,len(@m_rec_set)-@rowpoint) --从记录集合中删除读过的记录
if len(@eachrow) > 0 --还有记录,对行进行字段分割,将个值赋予每个字段参数
begin
--取第一个字段gid->@m_gid
select @colpoint = charindex('`',@eachrow) --第一字段末尾
select @m_gid = substring(@eachrow,1,@colpoint-1) --去第一个字段
select @eachrow = substring(@eachrow,@colpoint+1,len(@eachrow)-@colpoint) --取剩下的字符串
-- 取第二个字段gname->@m_gname
select @colpoint = charindex('`',@eachrow)
select @m_gname = substring(@eachrow,1,@colpoint-1)
select @eachrow = substring(@eachrow,@colpoint+1,len(@eachrow)-@colpoint)
--取第三个字段gunit->@m_gunit
select @colpoint = charindex('`',@eachrow)
select @m_gunit = substring(@eachrow,1,@colpoint-1)
select @eachrow = substring(@eachrow,@colpoint+1,len(@eachrow)-@colpoint)
--取第四个字段iosize->@m_iosize
select @colpoint = charindex('`',@eachrow)
select @m_iosize = substring(@eachrow,1,@colpoint-1)
select @eachrow = substring(@eachrow,@colpoint+1,len(@eachrow)-@colpoint)
--取第五个字段iomoney->@m_iomoney
select @colpoint = charindex('`',@eachrow)
select @m_iomoney = substring(@eachrow,1,@colpoint-1)
select @eachrow = substring(@eachrow,@colpoint+1,len(@eachrow)-@colpoint)
--取第六个字段iodate->@m_iodate
select @colpoint = charindex('`',@eachrow)
select @m_iodate = substring(@eachrow,1,@colpoint-1)
select @eachrow = substring(@eachrow,@colpoint+1,len(@eachrow)-@colpoint)
--取第七个字段remarks->@m_remarks
select @colpoint = charindex('`',@eachrow)
select @m_remarks = substring(@eachrow,1,@colpoint-1)
select @eachrow = substring(@eachrow,@colpoint+1,len(@eachrow)-@colpoint)
--取第八个字段uid->@m_uid
select @colpoint = charindex('`',@eachrow)
select @m_uid = substring(@eachrow,1,@colpoint-1)
select @eachrow = substring(@eachrow,@colpoint+1,len(@eachrow)-@colpoint)
--以下根据取得的参数判断进出仓操作
if @m_iomark = '1' --if input
---------进仓--------------------
begin
if exists (select gid from j060230gtb where gid=@m_gid) /*判断是否物料是否存在*/
update j060230gtb set gsize=gsize+@m_iosize where gid=@m_gid; /*存在就更新物料的库存数量*/
else
insert into j060230gtb (gid,gname,gsize,gunit) values (@m_gid,@m_gname,@m_iosize,@m_gunit); /*不存在先添加物料到物料表*/
insert into j060230giotb (billno,gid,iosize,iomark,iomoney,remarks,uid,iodate) /*将进仓信息进表*/
values(@m_billno,@m_gid,@m_iosize,'1',@m_iomoney,@m_remarks,@m_uid,@m_iodate)
end --end if input
---------进仓------------------
if @m_iomark = '0' --if output
---------出仓------------------
begin
if not exists (select gid from j060230gtb where gid=@m_gid) /*判断是否物料是否存在*/
begin --物料不存在
select @m_stat = @m_gid + '物料不存在!出库失败!'
select @m_msg = '无'
rollback transaction --事务回滚
return --结束存储过程
end
--物料存在
set @m_storesize =(select gsize from j060230gtb where gid=@m_gid) --读库存量
if @m_storesize < @m_iosize --不够出
begin
select @m_stat = @m_gid +'库存不足,出库失败!'
select @m_msg = '无'
rollback transaction --事务回滚
return --结束存储过程
end
--满足出仓条件
update j060230gtb set gsize=gsize-@m_iosize where gid=@m_gid --存在就更新物料的库存数量
-- set @m_stat = '出库成功!'
insert into j060230giotb (billno,gid,iosize,iomark,iomoney,remarks,uid,iodate) /*将出仓信息进表*/
values(@m_billno,@m_gid,@m_iosize,'0',@m_iomoney,@m_remarks,@m_uid,@m_iodate)
--select @m_msg = @m_billno
end --end if output
---------出仓--------------------
end --end if
end --end while
select @m_stat = '操作成功!'
select @m_msg = @m_billno
commit transaction --end transaction
go
/*-----------------多物料进出仓----------------------------*/
这个存储过程完成了记录的切分和进出仓的操作。
到此多物料进出仓就完成了。
补充:
前一次单物料和这次的多物料共同的缺陷在于生成单号的地方。两次所使用的单号生成方法是相同的,但是这种方法的原理是读最后的单号,生成本次的单号。如果考虑到并发,这种的问题是显而易见的,非常容易出错。因此,这个问题需要采用另外的办法解决,考虑到与本文无关,这里不详细写。
