5. 返回值
对函数返回值的处理不同于存储过程返回值的处理,这常常导致混淆。在函数中,经常是返回一个布尔值来表明函数运行的成功与否。
if somefunctionname() = true then
function succeeded
但在调用一个存储过程时,却不能使用同样的方法,因为存储是用execute方法运行的,同时返回一个记录集。
set rsauthors = cmdauthors.execute
如果得不到一个返回值,如何确定是否已正确执行存储过程?当发生错误时,会报告错误,这样就可使用前一章提供的错误处理代码来处理错误。但对于一些非致命的逻辑错误怎么办?
例如,考虑向employee表添加一个新职员的情形。你可能不想防止两个职员同名的情况,但想注明这个情况。那么,可以使用一个返回值以表明是否已有同名的职员存在。存储过程如下:
create procedure usp_addemployee
@emp_id char(9),
@fname varchar(20),
@minit char(1),
@lname varchar(30),
@job_id smallint,
@job_lvl tinyint,
@pub_id char(4),
@hire_date datetime
as
begin
declare @exists int — return value
— see if an employee with the same name exists
if exists(select *
from employee
where fname = @fname
and minit = @minit
and lname = @lname)
select @exists = 1
else
select @exists = 0
insert into employee (emp_id, fname, minit, lname,
job_id, job_lvl, pub_id, hire_date)
values (@emp_id, @fname, @minit, @lname, @job_id,
@job_lvl, @pub_id, @hire_date)
return @exists
end
该过程首先检查是否有同名的职员存在,并据此设定相应的变量exists,若存在同名,就设为1,否则为0。然后将该职员加到表中,同时把exists的值作为返回值返回。
注意尽管返回了一个值,但并未将其声明为存储过程的参数。
调用该过程的asp代码如下:
<!– #include file="../include/connection.asp" –>
<%
dim cmdemployee
dim lngrecs
dim lngadded
set cmdemployee = server.createobject("adodb.command")
set the properties of the command
with cmdemployee
.activeconnection = strconn
.commandtext = "usp_addemployee"
.commandtype = adcmdstoredproc
create the parameters
notice that the return value is the first parameter
.parameters.append .createparameter ("return_value", adinteger, _
adparamreturnvalue)
.parameters.append .createparameter ("@emp_id", adchar, adparaminput, 9)
.parameters.append .createparameter ("@fname", advarwchar, adparaminput, 20)
.parameters.append .createparameter ("@minit", adchar, adparaminput, 1)
.parameters.append .createparameter ("@lname", advarwchar, adparaminput, 30)
.parameters.append .createparameter ("@job_id", adsmallint, adparaminput)
.parameters.append .createparameter ("@job_lvl", adunsignedtinyint, adparaminput)
.parameters.append .createparameter ("@pub_id", adchar, adparaminput, 4)
.parameters.append .createparameter ("@hire_date", addbtimestamp, _
adparaminput, 8)
set the parameter values
.parameters("@emp_id") = request.form("txtempid")
.parameters("@fname") = request.form("txtfirstname")
.parameters("@minit") = request.form("txtinitial")
.parameters("@lname") = request.form("txtlastname")
.parameters("@job_id") = request.form("lstjobs")
.parameters("@job_lvl") = request.form("txtjoblevel")
.parameters("@pub_id") = request.form("lstpublisher")
.parameters("@hire_date") = request.form("txthiredate")
run the stored procedure
.execute lngrecs, , adexecutenorecords
extract the return value
lngadded = .parameters("return_value")
end with
response.write "new employee added.<p>"
if lngadded = 1 then
response.write "an employee with the same name already exists."
end if
set cmdemployee = nothing
%>
需要重点注意,返回值应当作为集合中第一个参数被创建。即使返回值并不作为一个参数出现在存储过程中,总是parameters集合中的第一个parameters。
因此,特别强调一点:
存储过程的返回值必须声明为parameters集合中第一个参数,同时参数的direction值必须为adparamreturnvalue。
使用返回值
现在定义一个初始窗体,如图9-3所示:
图9-3 初始窗体界面
按下add employee按钮会产生如图9-4所示的显示:
图9-4 按下add employee按钮后显示的界面
再添加同样的细节(id号不同)会得到如图9-5所示的界面:
图9-5 添加细节后显示的界面
6. 更新参数
无需输入所有的参数细节,只需调用refresh方法,就能让ado完成更新。例如,假设已经创建了一个带有与前面例子相同的参数的过程usp_addemployee,并且没有改变运行的页面。
with cmdemployee
.activeconnection = strconn
.commandtext = "usp_addemployee"
.commandtype = adcmdstoredproc
然后调用refresh方法。
.parameters.refresh
这告诉ado向数据存储请求每个参数的细节,并创建parameters集合。然后可以为其赋值。
.parameters("@emp_id") = request.form("txtempid")
.parameters("@fname") = request.form("txtfirstname")
.parameters("@minit") = request.form("txtinitial")
.parameters("@lname") = request.form("txtlastname")
.parameters("@job_id") = request.form("lstjobs")
.parameters("@job_lvl") = request.form("txtjoblevel")
.parameters("@pub_id") = request.form("lstpublisher")
.parameters("@hire_date") = request.form("txthiredate")
注意并不需要创建任何参数,包括返回值。
这似乎真是一条捷径,但应意识到这种方法也造成了性能上的损失,因为ado必须向提供者查询以获得存储过程的参数细节。尽管如此,这种方法还是很有用的,尤其是在从参数中取出正确的值有困难的时候。
实际上,可以编写一个小实用程序作为开发工具使用,用来完成更新并建立append语句,可以将其粘贴到自己的代码中。它看上去应该与图9-6所示的generateparameters.asp asp页面类似。
图9-6 generateparameters.asp asp页面
其代码相当简单。首先是包含连接符串和另一个adox常数文件。
<!– #include file="../include/connection.asp" –>
<!– #include file="../include/adox.asp" –>
接下来创建一个窗体,指定目标为printparameters.asp asp页面。
<form name="procedures" method="post" action="printparameters.asp">
connection string:<br>
<textarea name="txtconnection" cols="80" rows="5">
<% = strconn %>
</textarea>
<p>
stored procedure:<br>
<select name="lstprocedures">
然后,使用adox从sql server中得到存储过程的列表,同时创建一个含有这些存储过程名字的列表框。
<%
dim catpubs
dim procprocedure
predefine the quote character
strquote = chr(34)
set catpubs = server.createobject("adox.catalog")
catpubs.activeconnection = strconn
for each procprocedure in catpubs.procedures
response.write "<option value=" & _
strquote & procprocedure.name & _
strquote & ">" & procprocedure.name
next
set procprocedure = nothing
set catpubs = nothing
%>
</select>
<p>
<input type="submit" value="print paramaters">
</form>
这是一个简单的窗体,包括一个用于显示连接字符串的textarea控件和用于显示存储过程名称的select控件。以前没有见过的是adox,adox是数据定义与安全的ado扩展,可以用来访问数据存储的目录(或是元数据)。
本书不打算介绍adox的内容,但其十分简单。进一步的细节可参见《ado programmers reference》,wrox出版社出版,2.1版或2.5版都行。
上面的例子使用了procedures集合,这个集合包含数据存储中的所有存储过程的列表。按下printparameters按钮时,将得到图9-7所示的显示:
图9-7 按下print parameters按钮时显示的界面
可以简单地从这里拷贝参数行到代码中。在前面使用了一个以前从未见过的包含文件。该文件包含了几个将ado常数(例如数据类型、参数方向等)转换为字符串值的函数:
<!– #include file="../include/descriptions.asp" –>
接下来,定义一些变量,提取用户请求并创建command对象。
<%
dim cmdproc
dim parp
dim strconnection
dim strprocedure
dim strquote
get the connection and procedure name from the user
strquote = chr(34)
strconnection = request.form("txtconnection")
strprocedure = request.form("lstprocedures")
update the user
response.write "connecting to <b>" & strconnection & "</b><br>"
response.write "documenting parameters for <b>" & _
strprocedure & "</b><p><p>"
set cmdproc = server.createobject("adodb.command")
set the properties of the command, using the name
of the procedure that the user selected
with cmdproc
.activeconnection = strconnection
.commandtype = adcmdstoredproc
.commandtext = strprocedure
然后使用refresh方法自动填写parameters集合。
.parameters.refresh
现在可以遍历整个集合,写出包含创建参数所需的细节内容的字符串。
for each parp in .parameters
response.write ".parameters.append & _
"("strquote & parp.name & _
strquote & ", " & _
datatypedesc(parp.type) & ", " & _
paramdirectiondesc(parp.direction) & _
", " & _
parp.size & ")<br>"
next
end with
set cmdproc = nothing
%>
在descriptions.asp包含文件中可以找到函数datatypedesc和paramdirectiondesc。
descriptions.asp包含文件以及其他的例子文件可以在web站点http://www.wrox.com中找到。
这是一个非常简单的技术,它较好地使用了refresh方法。
9.3 优化
优化是每个开发人员应该关心的问题。对于数据库访问,优化是一个关键问题。和其他任务相比,数据的访问显得相对慢些。
因为数据访问的变化是如此之多,以致于几乎不可能提出一套固定的数据库操作的优化规则。通常碰到这类问题,经常得到这样的回答:“这取决于……”,因为这类优化问题取决于准备做什么。
9.3.1 常用的ado技巧
尽管优化取决于所执行的任务,但是仍然有一些常用的技巧:
· 仅选择所需的列。当打开记录集时,不要自动地使用表名(即select *),除非需要获得所有的列。使用单独的列意味着将减少发送到服务器或从服务器取出的数据的数量。即使需要使用全部列,单独地命名每个列也会获得最佳的性能,因为服务器不必再解释这些列是什么名字。
· 尽可能使用存储过程。存储过程是预先编译的程序,含有一个已经准备好的执行计划,所以比sql语句执行得更快。
· 使用存储过程更改数据。这总是比在记录集上使用ado方法执行速度快。
· 除非必需否则不要创建记录集。运行操作查询时,要确定加入了adexecutenorecords选项,这样记录集就不会创建。当仅仅返回一个或两个字段的单行记录时(比如id值),也可以在查询状态下使用这种方法。在这种情况下,存储过程和输出参数将会更快。
· 使用适当的光标和锁定模式。如果所做的全部工作是从记录集中读取数据,并将其显示在屏幕上(比如,创建一个表),那么使用缺省的只能前移的、只读的记录集。ado用来维护记录和锁定细节的工作越少,执行的性能就越高。
9.3.2 对象变量
当遍历记录集时,一个保证能提高性能的方法是使用对象变量指向集合中的成员。例如,考虑下面的遍历含有authors表的记录集的例子。
while not rsauthors.eof
response.write rsauthors("au_fname") & " " & _
rsauthors("au_lname") & "<br>"
rsauthors.movenext
wend
可以用下面的方法加速代码执行,同时使其更易于理解。
set firstname = rsauthors("au_fname")
set lastname = rsauthors("au_lname")
while not rsauthors.eof
response.write firstname & " " & lastname & "<br>"
rsauthors.movenext
wend
这里使用了两个变量,并指向记录集的fidds集合中的特定字段(记住,fidds集合是缺省的集合)。因为这里建立了一个对象的引用,所以可以使用对象变量而不是实际的变量,这意味着脚本引擎的工作减少了,因为在集合中进行索引的次数变少了。
9.3.3 高速缓存大小
高速缓存的大小是指ado每次从数据存储中读取的记录的数量,缺省为1。这意味着当使用基于服务器的光标时,每当移动到另一条记录时,必须从数据存储中提取记录。举一个例子,如果增大高速缓存的大小为10,那么每次读ado缓冲区的记录数将变为10。如果访问位于高速缓存内的记录,那么ado不需要从数据存储中取记录。当访问位于高速缓存外的记录时则下一批记录将读入到高速缓存中。
通过使用记录集的cachesize属性,可以设置高速缓存的大小。
rsauthors.cachesize = 10
可以在记录集生命期的任何时候改变高速缓存的大小,但新的数量只在提取下一批记录后才有效。
与许多改进性能的技巧类似,高速缓存没有通用的最佳大小,因为它随任务、数据和提供者的不同而改变。但是,从1开始增加高速缓存的大小总是能提高性能。
如果你想看到这一点,可以使用sql server profiler并查看使用缺省的高速缓存打开一个记录集发生的情况,并比较增大高速缓存后发生的情况。增大高速缓存的大小不仅减低了ado的工作量,同时也降低了sql server的工作量。
9.3.4 数据库设计
不要希望只通过编程来提高对数据的访问效率,应该同时考虑一下数据库的设计。这里并不打算对数据库设计进行更多的讨论,但在使用web站点数据库时应考虑以下几点:
· 实时数据:向用户显示数据时,确保数据内容总是最新是十分重要的。以一份产品目录为例,目录内容改变的频率有多快?如果该目录并非经常改变,那么不必每次都从数据库中提取数据。每周一次,或在数据改变时从数据库产生一个静态的html页面应是一个更好的办法。
· 索引:如果需要对表进行大量的查询,而不执行太多的添加数据操作,那么可以考虑为表建立索引。
· 不规范化:如果站点有两个不同的目的(数据维护与数据分析),那么可以考虑采用一些不规范化的表以便有助于数据的分析。可以提供独立的、完全不规范化的但能正常更新的分析用表,为了改善性能甚至可以将这些分析表移到另一台机器上。
· 数据库统计:如果使用的是sql server 6.x,如果数据被添加或删附除,那么应定期更新统计结果。这些统计结果用于产生一个查询计划,会影响查询的运行。请阅读sql books online中的update statistic以便了解更详细的内容。在sql server 7.0中这一任务自动完成。 这些都是十分基本的数据库设计技巧,但若只埋头于asp代码可能不会考虑到这些。
