前段时间写的一个关于成本计算的存储过程!数据库教程

时间:2025年07月09日

/

来源:释天

/

编辑:本站小编

收藏本文

下载本文

以下是小编为大家整理的前段时间写的一个关于成本计算的存储过程!数据库教程,本文共5篇,欢迎阅读与收藏。本文原稿由网友“释天”提供。

篇1:前段时间写的一个关于成本计算的存储过程!数据库教程

存储过程

--                             \\\\\\|///

--                            \\\\ - - //

--                            ( @ @ )

--┏━━━━━━━━━oOOo-(_)-oOOo━┓

--┃  定义于-07-16                                ┃

--┃ 成本试算存储过程定义,计算成本 ┃

--┃                                                 Oooo  ┃

--┗━━━━━━━━━ oooO━-(  )━┛

--                                          (  )  ) /

--                                           \\ (  (_/

--                                            \\_)

CREATE PROCEDURE sp_costing_compute

( @out_matnr varchar(18),

@out_werks varchar(4),

@out_datetime datetime)

AS

--插入赠品

declare @matnr varchar(18),@werks varchar(4),@kriqi datetime,@meins varchar(10)

declare @bomatnr varchar(18),@bomsehl varchar(10),@menge float

declare @price float,@danw varchar(20)

declare @umrez float,@aaprice float,@jjprice float,@arate float,@jrate float,@asy float,@jsy float

declare @cpjg float,@umren float

set @cpjg = 0

--计算产成品成本价格

Declare mycursor cursor for select meins,aprice,jprice from tb_matnr where convert(varchar(10),kriqi,120)=convert(varchar(10),@out_datetime,120) and matnr = @out_matnr and plant=@out_werks

open mycursor

fetch mycursor into @meins,@aaprice,@jjprice

close mycursor

deallocate mycursor

declare bccursor cursor for select bomatnr,bomsehl,menge from tb_bom where cpmatnr=@out_matnr and werks=@out_werks and convert(varchar(10),kriqi,120)=convert(varchar(10),@out_datetime,120)

open bccursor

fetch bccursor into @bomatnr,@bomsehl,@menge

while @@fetch_status = 0

begin

declare jgcursor cursor for select price,meins from tb_bcjg where matnr=@bomatnr and plant=@out_werks and convert(varchar(10),kriqi,120)=convert(varchar(10),@out_datetime,120)

open jgcursor

fetch jgcursor into @price,@danw

close jgcursor

deallocate jgcursor

if @price = 0

begin

update tb_matnr set mstatus='置疑' where matnr = @out_matnr and plant = @out_werks and convert(varchar(10),kriqi,120)=convert(varchar(10),@out_datetime,120)

break

end

--如果价格单位和bom单位不同,需要进行转换

if @bomsehl @danw

begin

declare zhgxcursor cursor for select umrez from tb_switch where matnr = @bomatnr and msehl = @danw

open zhgxcursor

fetch zhgxcursor into @umrez

close zhgxcursor

deallocate zhgxcursor

end

if @umrez is null

set @umrez = 1

set @cpjg = @cpjg + @menge * @price / @umrez

fetch next from bccursor into @bomatnr,@bomsehl,@menge

end

close bccursor

deallocate bccursor

--查看bom成品单位与成品销售单位,如果不同添加转换关系

declare cpzhcursor cursor for select umren from tb_switch where matnr = @out_matnr and msehl = @meins

open cpzhcursor

fetch cpzhcursor into @umren

close cpzhcursor

deallocate cpzhcursor

if @umren is null

set @umren = 1

if @cpjg 0

set @cpjg = @cpjg / @umren

if not @aaprice is null

begin

set @arate = @aaprice - @cpjg

if @aaprice 0

set @asy=@arate / @aaprice

end

else

begin

set @arate=0

set @asy=0

end

if not @jjprice is null

begin

set @jrate = @jjprice - @cpjg

if @jjprice 0

set @jsy = @jrate / @jjprice

end

else

begin

set @jrate = 0

set @jsy = 0

end

update tb_matnr set cbprice = @cpjg,apeyoff=@arate,jpayoff=@jrate,arate=@asy,jrate=@jsy where matnr=@out_matnr and plant=@out_werks and convert(varchar(10),kriqi,120)=convert(varchar(10),@out_datetime,120)

GO

篇2:通用存储过程的编写数据库教程

存储过程

通用存储过程的编写

对数据库的操作基本上就四种:Insert、Update、Delete和Select,而Update和Insert两种操作又可以作简单的合并,这样下来,基本上一个数据表对应三个存储过程便可以完成绝大多数的数据库操作,存储过程命名规则:Operate_TableName。比如表Order_Info对应三个存储过程:AddEdit_Order_Info、Delete_Order_Info、Search_Order_Info,下面先列出相关代码,然后作总体分析。

一、AddEdit_Order_Info

/*************************************************************

** Name     :   AddEdit_Order_Info

** Creater       :   PPCoder Designed By PPCode Studio(PPTech.Net)

** Create Date   :   2004-9-6 8:30:17

** Modifer       :  Rexsp

** Modify Date   :   2004-9-6 8:30:17

** Description : AddEdit information for Order_Info

**************************************************************/

ALTER PROCEDURE dbo.AddEdit_Order_Info

(

@OrderStateID Int = -1,

@OrderStateID_Min Int = -1,

@OrderStateID_Max Int = -1,

@OrderUserID Int = -1,

@OrderUserID_Min Int = -1,

@OrderUserID_Max Int = -1,

@OrderID Int = -1,

@OrderID_Min Int = -1,

@OrderID_Max Int = -1,

@ProductID Int = -1,

@ProductID_Min Int = -1,

@ProductID_Max Int = -1,

@CustomizeID Int = -1,

@CustomizeID_Min Int = -1,

@CustomizeID_Max Int = -1,

@OutID INT = 0 OUTPUT

)

AS

IF @OrderID=-1

BEGIN

INSERT INTO [Order_Info] (

[OrderStateID],

[OrderUserID],

[ProductID],

[CustomizeID]

)

VALUES(

@OrderStateID,

@OrderUserID,

@ProductID,

@CustomizeID

)

Set @OutID = @@IDENTITY

END

ELSE

BEGIN

DECLARE @strSQL NVARCHAR(1000)

SET @strSQL = 'UPDATE [Order_Info] SET @tmpOrderID = @tmpOrderID'

IF @OrderStateID -1

BEGIN

SET @strSQL = @strSQL + ', [OrderStateID] = @tmpOrderStateID'

END

IF @OrderUserID -1

BEGIN

SET @strSQL = @strSQL + ', [OrderUserID] = @tmpOrderUserID'

END

IF @ProductID -1

BEGIN

SET @strSQL = @strSQL + ', [ProductID] = @tmpProductID'

END

IF @CustomizeID -1

BEGIN

SET @strSQL = @strSQL + ', [CustomizeID] = @tmpCustomizeID'

END

SET @strSQL = @strSQL + ' WHERE [OrderID] = @tmpOrderID'

BEGIN TRAN

EXECUTE sp_executesql @strSQL, N'

@tmpOrderStateID INT,

@tmpOrderUserID INT,

@tmpOrderID INT,

@tmpProductID INT,

@tmpCustomizeID INT',

@tmpOrderStateID=@OrderStateID,

@tmpOrderUserID=@OrderUserID,

@tmpOrderID=@OrderID,

@tmpProductID=@ProductID,

@tmpCustomizeID=@CustomizeID

Set @OutID = @OrderID

IF @@error!=0

BEGIN

ROLLBACK

END

ELSE

BEGIN

COMMIT

END

END

RETURN

二、Delete_Order_Info

/*************************************************************

** Name     :   Delete_Order_Info

** Creater       :   PPCoder Designed By PPCode Studio(PPTech.Net)

** Create Date   :   2004-9-6 8:30:17

** Modifer       :  Rexsp

** Modify Date   :   2004-9-6 8:30:17

** Description : Delete information for Order_Info

**************************************************************/

ALTER PROCEDURE dbo.Delete_Order_Info

(

@OrderStateID Int = -1,

@OrderStateID_Min Int = -1,

@OrderStateID_Max Int = -1,

@OrderUserID Int = -1,

@OrderUserID_Min Int = -1,

@OrderUserID_Max Int = -1,

@OrderID Int = -1,

@OrderID_Min Int = -1,

@OrderID_Max Int = -1,

@ProductID Int = -1,

@ProductID_Min Int = -1,

@ProductID_Max Int = -1,

@CustomizeID Int = -1,

@CustomizeID_Min Int = -1,

@CustomizeID_Max Int = -1,

@OutID INT = 0 OUTPUT

)

AS

DECLARE @strSQL NVARCHAR(1000)

SET @strSQL = 'DELETE FROM [Order_Info] WHERE @tmpOrderID = @tmpOrderID '

IF @OrderStateID-1

BEGIN

SET @strSQL = @strSQL + ' AND rderStateID = @tmpOrderStateID'

END

IF @OrderStateID_Min-1

BEGIN

SET @strSQL = @strSQL + ' AND OrderStateID_Min = @tmpOrderStateID_Min'

END

IF @OrderStateID_Max-1

BEGIN

SET @strSQL = @strSQL + ' AND OrderStateID_Max = @tmpOrderStateID_Max'

END

IF @OrderUserID-1

BEGIN

SET @strSQL = @strSQL + ' AND rderUserID = @tmpOrderUserID'

END

IF @OrderUserID_Min-1

BEGIN

SET @strSQL = @strSQL + ' AND OrderUserID_Min = @tmpOrderUserID_Min'

END

IF @OrderUserID_Max-1

BEGIN

SET @strSQL = @strSQL + ' AND OrderUserID_Max = @tmpOrderUserID_Max'

END

IF @OrderID-1

BEGIN

SET @strSQL = @strSQL + ' AND rderID = @tmpOrderID'

END

IF @OrderID_Min-1

BEGIN

SET @strSQL = @strSQL + ' AND OrderID_Min = @tmpOrderID_Min'

END

IF @OrderID_Max-1

BEGIN

SET @strSQL = @strSQL + ' AND OrderID_Max = @tmpOrderID_Max'

END

IF @ProductID-1

BEGIN

SET @strSQL = @strSQL + ' AND ProductID = @tmpProductID'

END

IF @ProductID_Min-1

BEGIN

SET @strSQL = @strSQL + ' AND ProductID_Min = @tmpProductID_Min'

END

IF @ProductID_Max-1

BEGIN

SET @strSQL = @strSQL + ' AND ProductID_Max = @tmpProductID_Max'

END

IF @CustomizeID-1

BEGIN

SET @strSQL = @strSQL + ' AND CustomizeID = @tmpCustomizeID'

END

IF @CustomizeID_Min-1

BEGIN

SET @strSQL = @strSQL + ' AND CustomizeID_Min = @tmpCustomizeID_Min'

END

IF @CustomizeID_Max-1

BEGIN

SET @strSQL = @strSQL + ' AND CustomizeID_Max = @tmpCustomizeID_Max'

END

BEGIN TRAN

EXECUTE sp_executesql @strSQL, N'

@tmpOrderStateID INT,

@tmpOrderUserID INT,

@tmpOrderID INT,

@tmpProductID INT,

@tmpCustomizeID INT',

@tmpOrderStateID=@OrderStateID,

@tmpOrderUserID=@OrderUserID,

@tmpOrderID=@OrderID,

@tmpProductID=@ProductID,

@tmpCustomizeID=@CustomizeID

Set @OutID = @OrderID

IF @@error!=0

BEGIN

ROLLBACK

END

ELSE

BEGIN

COMMIT

END

RETURN

三、Search_Order_Info

/*************************************************************

** Name     :   Search_Order_Info

** Creater       :   PPCoder Designed By PPCode Studio(PPTech.Net)

** Create Date   :   2004-9-6 8:30:17

** Modifer       :  Rexsp

** Modify Date   :   2004-9-6 8:30:17

** Description : Search information for Order_Info

**************************************************************/

ALTER PROCEDURE dbo.Search_Order_Info

(

@OrderStateID Int = -1,

@OrderStateID_Min Int = -1,

@OrderStateID_Max Int = -1,

@OrderUserID Int = -1,

@OrderUserID_Min Int = -1,

@OrderUserID_Max Int = -1,

@OrderID Int = -1,

@OrderID_Min Int = -1,

@OrderID_Max Int = -1,

@ProductID Int = -1,

@ProductID_Min Int = -1,

@ProductID_Max Int = -1,

@CustomizeID Int = -1,

@CustomizeID_Min Int = -1,

@CustomizeID_Max Int = -1,

@ReturnCount INT=-1,

@OutID INT = 0 OUTPUT

)

AS

DECLARE @strSQL NVARCHAR(1000)

IF @ReturnCount-1

BEGIN

SET @strSQL = 'SELECT TOP '+@ReturnCount+' * FROM [Order_Info] WHERE @tmpOrderID = @tmpOrderID '

END

ELSE

BEGIN

SET @strSQL = 'SELECT * FROM [Order_Info] WHERE @tmpOrderID = @tmpOrderID '

END

IF @OrderStateID-1

BEGIN

SET @strSQL = @strSQL + ' AND rderStateID = @tmpOrderStateID'

END

IF @OrderStateID_Min-1

BEGIN

SET @strSQL = @strSQL + ' AND OrderStateID_Min = @tmpOrderStateID_Min'

END

IF @OrderStateID_Max-1

BEGIN

SET @strSQL = @strSQL + ' AND OrderStateID_Max = @tmpOrderStateID_Max'

END

IF @OrderUserID-1

BEGIN

SET @strSQL = @strSQL + ' AND rderUserID = @tmpOrderUserID'

END

IF @OrderUserID_Min-1

BEGIN

SET @strSQL = @strSQL + ' AND OrderUserID_Min = @tmpOrderUserID_Min'

END

IF @OrderUserID_Max-1

BEGIN

SET @strSQL = @strSQL + ' AND OrderUserID_Max = @tmpOrderUserID_Max'

END

IF @OrderID-1

BEGIN

SET @strSQL = @strSQL + ' AND rderID = @tmpOrderID'

END

IF @OrderID_Min-1

BEGIN

SET @strSQL = @strSQL + ' AND OrderID_Min = @tmpOrderID_Min'

END

IF @OrderID_Max-1

BEGIN

SET @strSQL = @strSQL + ' AND OrderID_Max = @tmpOrderID_Max'

END

IF @ProductID-1

BEGIN

SET @strSQL = @strSQL + ' AND ProductID = @tmpProductID'

END

IF @ProductID_Min-1

BEGIN

SET @strSQL = @strSQL + ' AND ProductID_Min = @tmpProductID_Min'

END

IF @ProductID_Max-1

BEGIN

SET @strSQL = @strSQL + ' AND ProductID_Max = @tmpProductID_Max'

END

IF @CustomizeID-1

BEGIN

SET @strSQL = @strSQL + ' AND CustomizeID = @tmpCustomizeID'

END

IF @CustomizeID_Min-1

BEGIN

SET @strSQL = @strSQL + ' AND CustomizeID_Min = @tmpCustomizeID_Min'

END

IF @CustomizeID_Max-1

BEGIN

SET @strSQL = @strSQL + ' AND CustomizeID_Max = @tmpCustomizeID_Max'

END

BEGIN TRAN

EXECUTE sp_executesql @strSQL, N'

@tmpOrderStateID INT,

@tmpOrderUserID INT,

@tmpOrderID INT,

@tmpProductID INT,

@tmpCustomizeID INT',

@tmpOrderStateID=@OrderStateID,

@tmpOrderUserID=@OrderUserID,

@tmpOrderID=@OrderID,

@tmpProductID=@ProductID,

@tmpCustomizeID=@CustomizeID

Set @OutID = @OrderID

IF @@error!=0

BEGIN

ROLLBACK

END

ELSE

BEGIN

COMMIT

END

分析:

1、             三个存储过程的入参基本上相同,只有Search_Order_Info多了一个@ReturnCount用来控制搜索信息的条数的,

入参很有特点:与数据表字段的扩展对应。扩展方式有三种:数字型和日期型扩展出“极小”和“极大”两个属性,例如数字型的OrderStateID对应的参数有三个@OrderStateID、@OrderStateID_Min 、@OrderStateID_Max ,时间型的AddTime对应@AddTime、@AddTime_Rof、@AddTime_Eof ;如果是字符型的,则会扩展出一个用来进行模糊搜索的属性,例如Title对应@Title、@Title_Like。之所以这样设计,是为了组合出更具适应性的条件语句。三个存储过程都有一个出参,就是表的唯一标识ID。这个主要在“添加和更新”操作中使用。当然搜索的时候也可以当唯一键返回。这个唯一标识ID也是来判断是Insert或Update的标识。

2、             入参都有赋初值,然后动态构建Sql语句的时候,会判断各入参是否等于初值,如果不等于表示是外面传进来的传,便参与Sql语句的构建。这种灵活性是程序适应性的保证。这样,我们就可以在程序员通过控制是否给入参传值来判断是否要进行某一栏位进行更新或是否要把某一栏位的信息参与条件语句的构成。

3、             用系统存储过程sp_executesql来执行Sql语句,完全数据库操作。用系统存储过程来执行sql语句有一个好处,就是可以实现特殊字符的自动转义。

4、             三个存储过程都有统一的构建规律,所以可以使用自动化工具依据表结构直接生成。

篇3:一个将数据分页的存储过程数据库教程

存储过程|分页|数据

CREATE PROCEDURE sp_page

@tb        varchar(50), --表名

@col       varchar(50), --按该列来进行分页

@coltype   int,        --@col列的类型,0-数字类型,1-字符类型,2-日期时间类型

@orderby   bit,        --排序,0-顺序,1-倒序

@collist   varchar(800),--要查询出的字段列表,*表示全部字段

@pagesize  int,        --每页记录数

@page      int,        --指定页

@condition varchar(800),--查询条件

@pages     int OUTPUT  --总页数

AS

/*

功能描述:对指定表中满足条件的记录按指定列进行分页查询,分页可以顺序、倒序

查询可以指定页大小、指定查询任意页、指定输出字段列表,返回总页数

作   者:pbsql

版   本:1.10

最后修改:-11-29

*/

DECLARE @sql nvarchar(4000),@where1 varchar(800),@where2 varchar(800)

IF @condition is null or rtrim(@condition)=''

BEGIN--没有查询条件

SET @where1=' WHERE '

SET @where2=' '

END

ELSE

BEGIN--有查询条件

SET @where1=' WHERE ('+@condition+') AND '--本来有条件再加上此条件

SET @where2=' WHERE ('+@condition+') '--原本没有条件而加上此条件

END

SET @sql='SELECT @pages=CEILING((COUNT(*)+0.0)/'+CAST(@pagesize AS varchar)+

') FROM '+@tb+@where2

EXEC sp_executesql @sql,N'@pages int OUTPUT',@pages OUTPUT--计算总页数

IF @orderby=0

SET @sql='SELECT TOP '+CAST(@pagesize AS varchar)+' '+@collist+

' FROM (SELECT“>'+@tb+@where1+@col+'>(SELECT MAX('+@col+') '+

' FROM (SELECT TOP '+CAST(@pagesize*(@page-1) AS varchar)+' '+

@col+' FROM '+@tb+@where2+'ORDER BY '+@col+') t) ORDER BY '+@col

ELSE

SET @sql='SELECT TOP '+CAST(@pagesize AS varchar)+' '+@collist+

' FROM '+@tb+@where1+@col+'<(SELECT MIN('+@col+') '+

' FROM (SELECT TOP '+CAST(@pagesize*(@page-1) AS varchar)+' '+

@col+' FROM '+@tb+@where2+'ORDER BY '+@col+' DESC) t) ORDER BY '+

@col+' DESC'

IF @page=1--第一页

SET @sql='SELECT TOP '+CAST(@pagesize AS varchar)+' '+@collist+' FROM '+@tb+

@where2+'ORDER BY '+@col+CASE @orderby WHEN 0 THEN '' ELSE ' DESC' END

EXEC(@sql)

GO

本存储过程高效,曾用500万条数据测试(已建索引),只返回分页只需3秒,影响效率的地方是计算总页数,若不需要可以注释掉

--测试示例

declare @pages int

select identity(int,1,1) id,getdate dt,xx=cast('' as varchar(10)) into #t

from sysobjects

update #t set dt=dateadd(day,id-200,dt),

xx='xxxx'+right('000000'+cast(id as varchar(10)),6)

exec sp_page '#t','id',0,0,'*',10,2,'',@pages output--按id顺序取第二页

exec sp_page '#t','id',0,1,'*',10,2,'',@pages output--按id倒序取第二页

exec sp_page '#t','xx',1,0,'*',10,3,'',@pages output--按xx顺序取第三页

exec sp_page '#t','xx',1,1,'*',10,3,'',@pages output--按xx倒序取第三页

exec sp_page '#t','dt',2,0,'*',10,2,'',@pages output--按dt顺序取第二页

exec sp_page '#t','dt',2,1,'*',10,2,'',@pages output--按dt倒序取第二页

select 总页数=@pages

drop table #t

篇4:一个将数据导出到EXCEL的存储过程数据库教程

excel|存储过程|数据

/*--数据导出EXCEL

导出查询中的数据到Excel,包含字段名,文件为真正的Excel文件

,如果文件不存在,将自动创建文件

,如果表不存在,将自动创建表

基于通用性考虑,仅支持导出标准数据类型

作者:邹建

--*/

/*--调用示例

p_exporttb @sqlstr='select * from 地区资料'

,@path='c:\\',@fname='aa.xls',@sheetname='地区资料'

--*/

create proc p_exporttb

@tbname sysname,   --要导出的表名

@path nvarchar(1000),  --文件存放目录

@fname nvarchar(250)='' --文件名,默认为表名

as

declare @err int,@src nvarchar(255),@desc nvarchar(255),@out int

declare @obj int,@constr nvarchar(1000),@sql varchar(8000),@fdlist varchar(8000)

--参数检测

if isnull(@fname,'')='' set @fname=@tbname+'.xls'

--检查文件是否已经存在

if right(@path,1)'' set @path=@path+''

create table #tb(a bit,b bit,c bit)

set @sql=@path+@fname

insert into #tb exec master..xp_fileexist @sql

--数据库创建语句

set @sql=@path+@fname

if exists(select 1 from #tb where a=1)

set @constr='DRIVER={Microsoft Excel Driver (*.xls)};DSN='''';READONLY=FALSE'

+';CREATE_DB=”'+@sql+'“;DBQ='+@sql

else

set @constr='Provider=Microsoft.Jet.OLEDB.4.0;Extended Properties=”Excel 8.0;HDR=YES'

+';DATABASE='+@sql+'“'

--连接数据库

exec @err=sp_oacreate 'adodb.connection',@obj out

if @err0 goto lberr

exec @err=sp_oamethod @obj,'open',null,@constr

if @err0 goto lberr

/*--如果覆盖已经存在的表,就加上下面的语句

--创建之前先删除表/如果存在的话

select @sql='drop table ['+@tbname+']'

exec @err=sp_oamethod @obj,'execute',@out out,@sql

--*/

--创建表的SQL

select @sql='',@fdlist=''

select @fdlist=@fdlist+',['+a.name+']'

,@sql=@sql+',['+a.name+'] '

+case when b.name in('char','nchar','varchar','nvarchar') then

'text('+cast(case when a.length>255 then 255 else a.length end as varchar)+')'

when b.name in('tynyint','int','bigint','tinyint') then 'int'

when b.name in('smalldatetime','datetime') then 'datetime'

when b.name in('money','smallmoney') then 'money'

else b.name end

FROM syscolumns a left join systypes b on a.xtype=b.xusertype

where b.name not in('image','text','uniqueidentifier','sql_variant','ntext','varbinary','binary','timestamp')

and object_id(@tbname)=id

select @sql='create table ['+@tbname

+']('+substring(@sql,2,8000)+')'

,@fdlist=substring(@fdlist,2,8000)

exec @err=sp_oamethod @obj,'execute',@out out,@sql

if @err0 goto lberr

exec @err=sp_oadestroy @obj

--导入数据

set @sql='openrowset(''MICROSOFT.JET.OLEDB.4.0'',''Excel 8.0;HDR=YES

;DATABASE='+@path+@fname+''',['+@tbname+'$])'

exec('insert into '+@sql+'('+@fdlist+') select '+@fdlist+' from '+@tbname)

return

lberr:

exec sp_oageterrorinfo 0,@src out,@desc out

lbexit:

select cast(@err as varbinary(4)) as 错误号

,@src as 错误源,@desc as 错误描述

select @sql,@constr,@fdlist

GO

========================================

NinGoo注:

Excel文件每个工作表不能超过65536条记录

解决办法:

.如果数据量大于65536,可以在调用存储过程前先将要导出的table拆分成几个小的临时table,然后

在分别导出到不同的工作表中

篇5:编写安全的SQL Server扩展存储过程数据库教程

server|安全|存储过程

SQL Server 的扩展存储过程,其实就是一个普通的 Windows DLL,只不过按照某种规则实现了某些函数而已,

编写安全的SQL Server扩展存储过程数据库教程

近日在写一个扩展存储过程时,发现再写这类动态库时,还是有一些需要特别注意的地方。之所以会特别注意,是因为DLL运行于SQL Server的地址空间,而SQL Server到底是怎么进行线程调度的,却不是我们能了解的,即便了解也无法控制。

我们写动态库一般是自己用,即便给别人用,也很少像SQL Server这样,一个动态库很有可能加载多次,并且都是加载到一个进程的地址空间中。我们知道,当一个动态库加载到进程的地址空间时,DLL所有全局与局部变量初始化且仅初始化一次,以后再次调用 LoadLibrary函数时,仅仅增加其引用计数而已,那么很显然,假如有一全局 int ,初始化为0,调用一个函数另其自加,此时其值为1,然后再调用LoadLibray,并利用返回的句柄调用输出函数输出该值,虽然调用者觉得自己加载后立即输出,然后该值确实1而不是0。windows是进程独立的,而在线程方面,假如不注意,上面的情况很可能会程序员带来麻烦。

介绍一下我的扩展存储过程,该动态库导出了三个函数: Init,work,Final,Init读文件,存储信息于内存,work简单的只是向该内存检索信息,Final回收内存。如上所说,假如不考虑同一进程空间多次加载问题,两次调用Init将造成无谓的浪费,因为我第一次已经读进了内存,要是通过堆分配内存,还会造成内存泄露。

我使用的引用计数解决的该问题,代码很短,直接贴上来:

#include ”stdafx.h“

#include

using namespace std;

extern ”C“ {

RETCODE __declspec(dllexport) xp_part_init(SRV_PROC *srvproc);

RETCODE __declspec(dllexport) xp_part_process(SRV_PROC *srvproc);

RETCODE __declspec(dllexport) xp_part_finalize(SRV_PROC *srvproc);

}

#define XP_NOERROR 0

#define XP_ERROR 1

HINSTANCE hInst = NULL;

int nRef = 0;

void printError (SRV_PROC *pSrvProc, CHAR* szErrorMsg);

ULONG __GetXpVersion(){ return ODS_VERSION;}

SRVRETCODE xp_part_init(SRV_PROC* pSrvProc){

typedef bool (*Func)();

if(nRef == 0){

hInst = ::LoadLibrary(”part.dll“);

if(hInst == NULL){

printError(pSrvProc,”不能加载part.dll“);

return XP_ERROR;

}

Func theFunc = (Func)::GetProcAddress(hInst,”Init“);

if(!theFunc()){

::FreeLibrary(hInst);

printError(pSrvProc,”不能获得分类号与专辑的对应表“);

return XP_ERROR;

}

}

++ nRef;

return (XP_NOERROR);

}

SRVRETCODE xp_part_process(SRV_PROC* pSrvProc){

typedef bool (*Func)(char*);

if(nRef == 0){

printError(pSrvProc,”函数尚未初始化,请首先调用xp_part_init“);

return XP_ERROR;

}

Func theFunc = (Func)::GetProcAddress(hInst,”Get“);

BYTE bType;

ULONG cbMaxLen,cbActualLen;

BOOL fNull;

char szInput[256] = {0};

if (srv_paraminfo(pSrvProc, 1, &bType, (ULONG*)&cbMaxLen, (ULONG*)&cbActualLen, (BYTE*)szInput, &fNull) == FAIL){

printError(pSrvProc,”srv_paraminfo 返回 FAIL“);

return XP_ERROR;

}

szInput[cbActualLen] = 0;

string strInput = szInput;

string strOutput = ”;“;

int cur,old = 0;

while(string::npos != (cur = strInput.find(’;’,old)) ){

strncpy(szInput,strInput.c_str() + old,cur - old);

szInput[cur - old] = 0;

ld = cur + 1;

theFunc(szInput);

if(string::npos ==strOutput.find((string)”;“ + szInput))

strOutput += szInput;

}

strcpy(szInput,strOutput.c_str());

if (FAIL == srv_paramsetoutput(pSrvProc, 1, (BYTE*)(szInput + 1), strlen(szInput) - 1,FALSE)){

printError (pSrvProc, ”srv_paramsetoutput 调用失败“);

return XP_ERROR;

}

srv_senddone(pSrvProc, (SRV_DONE_COUNT | SRV_DONE_MORE), 0, 0);

return XP_NOERROR;

}

SRVRETCODE xp_part_finalize(SRV_PROC* pSrvProc){

typedef void (*Func)();

if(nRef == 0)

return XP_NOERROR;

Func theFunc = (Func)::GetProcAddress(hInst,”Fin\");

if((--nRef) == 0){

theFunc();

::FreeLibrary(hInst);

hInst = NULL;

}

return (XP_NOERROR);

}

我想虽然看上去不是很高明,然而问题应该是解决了的,

还有一点说明,为什么不使用Tls,老实说,我考虑过使用的,因为其实代码是有一点问题的,假如一个用户调用xp_part_init,然后另一个用户也调用xp_part_init,注意我们的存储过程可是服务器端的,然后第一个用户调用xp_part_finalize,那么会怎样,他仍然可以正常使用xp_part_process,这倒无所谓,然而第一个用户调用两次xp_part_finalize,就能够影响第二个用户了,他的xp_part_process将返回错误。

使用Tls 似乎可以解决这问题,例如再添加一个tls_index变量,调用 TlsSetValue保存用户私人数据,TlsGetValue检索私人数据,当xp_part_init时,假如该私人数据为0,执行正常的初始化过程,(即上面的xp_part_init)执行成功后存储私人数据为1,假如是1,直接返回,xp_part_finalize时,假如私人数据为1,则执行正常的xp_part_finalize,然后设私人数据为0,假如是0,直接返回。

好像想法还是不错的,这样隔离了多个用户,安全性似乎提高了不少,然而事实是不可行的。因为Tls保存的并不是私人数据,而是线程本地变量,我们不能保证一个用户的多次操作都是用同一个线程执行的,这个由SQL Server自己控制,事实上我在查询分析器里多次执行的结果显示,SQL Server内部似乎使用了一个线程池。既然如此,那这种想法也只能作罢。

动态创建SQL Server数据库、表、存储过程数据库教程

存储过程与 SQL Server语句大比拼综合教程

一个基于ROWNUMBER的通用分页存储过程代码

SQL 存储过程基础语法之一

Oracle 9i 约束条件数据库教程

下载前段时间写的一个关于成本计算的存储过程!数据库教程(共5篇)
前段时间写的一个关于成本计算的存储过程!数据库教程.doc
将本文的Word文档下载到电脑,方便收藏和打印
推荐度:
点击下载文档
点击下载本文文档