赞
踩
EXEC ExportToExcel @server = '.',
@uname = 'sa',
@pwd = '',
@QueryText = 'SELECT * FROM dldata..bbbbbb',
@filename = 'd:/ImportToExcel.xls'
*/
IF OBJECT_ID('ExportToExcel') IS NOT NULL DROP PROC ExportToExcel
GO
CREATE PROCEDURE ExportToExcel (
@server sysname = null,
@uname sysname = null,
@pwd sysname = null,
@QueryText varchar(200) = null,
@filename varchar(200) = 'd:/ImportToExcel.xls'
)
AS
DECLARE @SQLServer int, --SQLDMO.SQLServer对象
@QueryResults int, --QueryResults对象
@CurrentResultSet int,
@object int, --Excel.Application对象
@WorkBooks int,
@WorkBook int,
@Range int,
@hr int,
@Columns int,
@Rows int,
@indColumn int,
@indRow int,
@off_Column int,
@off_Row int,
@code_str varchar(100),
@result_str varchar(255)
IF @QueryText IS NULL
BEGIN
PRINT 'Set the query string'
RETURN
END
--设置服务器名为本地服务器(@@servername返回运行SQL Server的本地服务器名称)
IF @server IS NULL SELECT @server = @@servername
--设置用户名为当前系统用户名(使用SYSTEM_USER返回当前系统用户名)
IF @uname IS NULL SELECT @uname = SYSTEM_USER
SET NOCOUNT ON
--创建SQLDMO.SQLServer对象
EXEC @hr = sp_OACreate 'SQLDMO.SQLServer', @SQLServer OUT
IF @hr <> 0
BEGIN
PRINT 'error create SQLDMO.SQLServer'
RETURN
END
--连接到SQL Server系统
IF @pwd IS NULL
BEGIN
EXEC @hr = sp_OAMethod @SQLServer, 'Connect', null, @server, @uname
IF @hr <> 0
BEGIN
PRINT 'error Connect'
RETURN
END
END
ELSE
BEGIN
EXEC @hr = sp_OAMethod @SQLServer, 'Connect', null, @server, @uname, @pwd
IF @hr <> 0
BEGIN
PRINT 'error Connect'
RETURN
END
END
--The ExecuteWithResults method executes a Transact-SQL command batch
--returning batch result sets in a QueryResults object
SELECT @result_str = 'ExecuteWithResults("' + @QueryText + '")'
EXEC @hr = sp_OAMethod @SQLServer, @result_str, @QueryResults OUT
IF @hr <> 0
BEGIN
PRINT 'error with method ExecuteWithResults'
RETURN
END
--The CurrentResultSet property controls access to the result sets of a
QueryResults object
EXEC @hr = sp_OAMethod @QueryResults, 'CurrentResultSet', @CurrentResultSet OUT
IF @hr <> 0
BEGIN
PRINT 'error get CurrentResultSet'
RETURN
END
--The Columns property exposes the number of columns contained
--in the current result set of a QueryResults object
EXEC @hr = sp_OAMethod @QueryResults, 'Columns', @Columns OUT
IF @hr <> 0
BEGIN
PRINT 'error get Columns'
RETURN
END
--The Rows property returns the number of rows in a referenced
--query result set or the number of rows existing in a table
EXEC @hr = sp_OAMethod @QueryResults, 'Rows', @Rows OUT
IF @hr <> 0
BEGIN
PRINT 'error get Rows'
RETURN
END
--创建Excel.Application对象
EXEC @hr = sp_OACreate 'Excel.Application', @object OUT
IF @hr <> 0
BEGIN
PRINT 'error create Excel.Application'
RETURN
END
--获得Excel工作簿对象
EXEC @hr = sp_OAGetProperty @object, 'WorkBooks', @WorkBooks OUT
IF @hr <> 0
BEGIN
PRINT 'error create WorkBooks'
RETURN
END
--在工作簿对象中加入一工作表
EXEC @hr = sp_OAGetProperty @WorkBooks, 'Add', @WorkBook OUT
IF @hr <> 0
BEGIN
PRINT 'error with method Add'
RETURN
END
--Range对象(A1单元格)
EXEC @hr = sp_OAGetProperty @object, 'Range("A1")', @Range OUT
IF @hr <> 0
BEGIN
PRINT 'error create Range'
RETURN
END
SELECT @indRow = 1
SELECT @off_Row = 0
SELECT @off_Column = 1
WHILE (@indRow <= @Rows)
BEGIN
SELECT @indColumn = 1
WHILE (@indColumn <= @Columns)
BEGIN
--The GetColumnString method returns a QueryResults object result set
member converted to a String value
EXEC @hr = sp_OAMethod @QueryResults, 'GetColumnString', @result_str OUT,
@indRow, @indColumn
IF @hr <> 0
BEGIN
PRINT 'error get GetColumnString'
RETURN
END
EXEC @hr = sp_OASetProperty @Range, 'value', @result_str
IF @hr <> 0
BEGIN
PRINT 'error set value'
RETURN
END
EXEC @hr = sp_OAGetProperty @Range, 'Offset', @Range OUT, @off_Row,
@off_Column
IF @hr <> 0
BEGIN
PRINT 'error get Offset'
RETURN
END
SELECT @indColumn = @indColumn + 1
END
SELECT @indRow = @indRow + 1
SELECT @code_str = 'Range("A' + LTRIM(str(@indRow)) + '")'
EXEC @hr = sp_OAGetProperty @object, @code_str, @Range OUT
IF @hr <> 0
BEGIN
PRINT 'error create Range'
RETURN
END
END
SELECT @result_str = 'exec master..xp_cmdshell ''del ' + @filename + ''',
no_output'
EXEC(@result_str) --如果存在@filename文件,则先删除
SELECT @result_str = 'SaveAs("' + @filename + '")'
EXEC @hr = sp_OAMethod @WorkBook, @result_str
IF @hr <> 0
BEGIN
PRINT 'error with method SaveAs'
RETURN
END
EXEC @hr = sp_OAMethod @WorkBook, 'Close'
IF @hr <> 0
BEGIN
PRINT 'error with method Close'
RETURN
END
EXEC @hr = sp_OADestroy @object
IF @hr <> 0
BEGIN
PRINT 'error destroy Excel.Application'
RETURN
END
EXEC @hr = sp_OADestroy @SQLServer
IF @hr <> 0
BEGIN
PRINT 'error destroy SQLDMO.SQLServer'
RETURN
END
GO
作者Blog:http://blog.csdn.net/azsoft/
相关文章
SQL Server 与 Excel
------------------------------------
一问一答:存储过程经典问题 选择自 wangnewton 的 Blog
关键字 一问一答:存储过程经典问题
出处
只涉及到一个表:xkb_treeNode
表结构是这样:
node_id int //节点id
parentNode_id int //父节点id
node_text varchar //节点内容
isModule bit //是否叶子节点
现在保存的数据有:
node_id parentNode_id node_text isModule
1 -1 语言与文学 0
2 -1 数学 0
3 -1 技术 0
4 1 语文 0
5 1 外语 0
6 5 英语 0
7 6 初中英语 0
8 7 特斯塔 1
9 4 测定是2 1
10 2 测试3 1
现在问题是:
能否通过做一个存储过程,
根据表中的isModule字段的取值(取值为1的表示最终叶子结点),
比如“特斯塔”为叶子节点,层层向上递进找到”特斯塔“的祖先节点:
特斯塔-〉初中英语-〉英语-〉外语-〉语言与文学
即通过”特斯塔“找到”语言与文学“来
最终返回的形态为:
叶子节点id 父节点id 节点名称 祖先节点名称 祖先节点id
8 7 特斯塔 语言与文学 1
9 4 测定是2 语言与文学 1
10 2 测试3 数学 2
/
正确答案:
--生成测试数据
create table xkb_treeNode(
node_id int,
parentNode_id int,
node_textvarchar(10),
isModulebit)
insert into xkb_treeNode select 1 ,-1,'语言与文学',0
insert into xkb_treeNode select 2 ,-1,'数学',0
insert into xkb_treeNode select 3 ,-1,'技术',0
insert into xkb_treeNode select 4 , 1,'语文',0
insert into xkb_treeNode select 5 , 1,'外语',0
insert into xkb_treeNode select 6 , 5,'英语',0
insert into xkb_treeNode select 7 , 6,'初中英语',0
insert into xkb_treeNode select 8 , 7,'特斯塔' ,1
insert into xkb_treeNode select 9 , 4,'测定是2',1
insert into xkb_treeNode select 10 , 2,'测试3',1
--创建存储过程
create procedure sp_test
as
begin
select
a.node_id,
a.parentNode_id,
a.node_text,
b.node_id as ancestor_id ,
b.node_text as ancestor_text
into
#t
from
xkb_treeNode a,xkb_treeNode b
where
a.parentNode_id = b.node_id and a.isModule = 1
while(exists(select 1 from xkb_treeNode a,#t b where a.node_id=ancestor_id and
a.parentNode_id != -1))
begin
update #t
set
ancestor_id = b.p_id,
ancestor_text = b.p_text
from
#t a,
(select
c.node_id,
d.node_id as p_id,
d.node_text as p_text
from
xkb_treeNode c,xkb_treeNode d
where
c.parentNode_id = d.node_id) b
where
a.ancestor_id = b.node_id
end
select * from #t order by node_id
end
--执行存储过程,结果楼主自己看
exec sp_test
作者Blog:http://blog.csdn.net/wangnewton/
相关文章
关于javabean的一些文档
Java 关于中文乱码问题的解决方案与经验
Java项目中连接数据库的几种方式和范例
Tomcat 服务器server.xml的关键参数配置
一问一答:存储过程经典问题
++++++++++++++++++++++++++++++++++++++++++++++++++++++++
数据库设计经验谈 一 (引) 选择自 lkpdragon 的 Blog
关键字 数据库设计经验谈 一 (引)
出处
数据库设计经验谈
作者: 水若寒
一个成功的管理系统,是由:[50% 的业务 + 50% 的软件] 所组成,而 50% 的成功软件又有
[25% 的数据库 + 25% 的程序] 所组成,数据库设计的好坏是一个关键。如果把企业的数据
比做生命所必需的血液,那么数据库的设计就是应用中最重要的一部分。有关数据库设计的
材料汗牛充栋,大学学位课程里也有专门的讲述。不过,就如我们反复强调的那样,再好的
老师也比不过经验的教诲。
插入一些数据库设计心得:
一、 设计思想
对许多程序员来说,设计一个数据库应用程序并不是很难的一件事。但是却有许多数据库应
用软件得不到用户的承认,其原因就是前期调研中,信息化设计单位和使用单位没有得到相
应的思想沟通。
这里所说的沟通包括用户对软件功能的要求,时间效益的要求,软件平台的要求,价格的要
求和软件维护的要求。这五种要求构成一个成功应用的软件的所有的调研项目。
但是这里最重要的就是对软件功能的要求,不同的企业对软件要求的是不一样的。下面就软
件功能的需求要求做一个概要介绍:
1. 对象性:
这并不是软件工程或者其他参考书中所描绘的软件设计要求,但是这是一个必然的发展趋势
。我国软件主要由财务软件起步,财务业务流程是国家统一规定的,零售业的财务流程和建
材业的财务业务流程并没有多大不同,所以设计一种软件就可以应用不同的公司甚至是跨行
业的公司也就是很正常的一件事,但是随着我国市场经济的发展,用信息化技术来推动企业
发展成为一种切实有效的手段,许多不同行业的企业甚至同行业不同企业对信息化应用软件
都有不同的要求。
在现代程序开发技术中,面对对象的技术是一个大的飞跃。但是许多开发的数据库应用软件
并没由认识到这一点,所以开发的软件就没有市场。有一次,一个软件推销员到我公司来推
销软件,是明煌软件公司的人事管理软件,公司人事部门领导很感兴趣,随口问了几个问题
,其中一个是有没有临时工的管理,一个是工资统计查询能不能按照职工年龄,岗位,职称
,学历分类统计查询。结果这个软件没有这两项功能,所以人事部门领导很客气的拒绝了这
个应用软件推销员的关于演示软件的请求。
作为一个开发人员来说,在一个数据库应用软件加上以上两个功能实在是很一般的工作,但
是就是因为在开发时没有面对对象的考虑用户的需求导致了这次软件推销的失败。
所以对一个应用软件来说一开始就考虑软件的对象性是一个成功的必要因素。
2.易用性
关于易用性的好坏不是由开发部门测定的,也不是由软件评测机构认定的,而是由用户认定
的。这是在工作交流中得到确认的。
许多软件考虑精细,例如ORACLE数据库为后台数据库的ORACLE公司的ERP软件解决方案,就没
有考虑到中国的国情,不但应用界面分类复杂,而且在工作业务繁忙的时候,由于操作复杂
往往还适得其反,到耽误了工作,惹得领导埋怨,职工抱怨,反而不如不用。
在销售系统软件的调试过程中,我认识了一个销售公司的业务员,他跟我谈了使用软件后的
许多感想。他说软件本来是减轻工作量的,但是销售系统有的应用界面就很不友好,在向网
络数据库中录入数据时,录入数据很多,但是软件总要求一会用键盘打字,一会用鼠标点击
,这几千项数据输入时,人一会用键盘一会用鼠标,活就像个钟摆,累死了,干吗不设计的
都能用键盘控制呢。
事实上就是这样,软件在编制的过程中一定要多与业务人员交流,了解工作流程很重要,但
是决不能忽视易用性在整个软件性能中不可忽视的比例。
3. 扩展性
作为现代软件系统的一部分,可扩展性越来越成为构成软件生命的主要功能之一。无论什么
公司都希望买的软件能够适应并满足公司业务发展变化的需求,还希望能够和其他购买的软
件一起构成一个完整的企业软件系统。
在软件上来说,这有点困难,因为要满足这项要求不但要预测企业发展方向,并且在软件中
预留出数据交换接口,在应用文档中要公布部分数据库构成甚至时部分源码。
但是从大的应用方向上,我们设计的软件必须达到这样使用的功能。金蝶,用友这两个大的
软件公司已经实现的客户开发工具包来实现客户化二次开发的需求。
4. 维护功能
为了保证软件正常工作,软件维护是必要的。但是远水救不了近火,谁也不能保证软件在故
障的时候软件维护人员能够及时维护,这就要求在软件设计是要增加软件维护功能。有了软
件维护功能,哪怕是简单的备份功能,也能够在突发事件中将数据损失降到最低点。
除了一般功能外,在软件设计时,我认为上述四个功能是注意要添加和完善的,这样我们作
出来的数据库应用软件才能够具有更高的使用价值。
。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
。
所以我归纳历年来所走的弯路及体会,并在网上找了些对数据库设计颇有造诣的专业人士给
大家传授一些设计数据库的技巧和经验。精选了其中的 60 个最佳技巧,并把这些技巧编写
成了本文,为了方便索引其内容划分为 5 个部分:
第 1 部分 - 设计数据库之前
这一部分罗列了 12 个基本技巧,包括命名规范和明确业务需求等。
第 2 部分 - 设计数据库表
总共 24 个指南性技巧,涵盖表内字段设计以及应该避免的常见问题等。
第 3 部分 - 选择键
怎么选择键呢?这里有 10 个技巧专门涉及系统生成的主键的正确用法,还有何时以及如何
索引字段以获得最佳性能等。
第 4 部分 - 保证数据完整性
讨论如何保持数据库的清晰和健壮,如何把有害数据降低到最小程度。
第 5 部分 - 各种小技巧
不包括在以上 4 个部分中的其他技巧,五花八门,有了它们希望你的数据库开发工作会更轻
松一些。
第 1 部分 - 设计数据库之前
1. 考察现有环境
在设计一个新数据库时,你不但应该仔细研究业务需求而且还要考察现有的系统。大多数数
据库项目都不是从头开始建立的;通常,机构内总会存在用来满足特定需求的现有系统(可
能没有实现自动计算)。显然,现有系统并不完美,否则你就不必再建立新系统了。但是对
旧系统的研究可以让你发现一些可能会忽略的细微问题。一般来说,考察现有系统对你绝对
有好处。
2. 定义标准的对象命名规范
一定要定义数据库对象的命名规范。对数据库表来说,从项目一开始就要确定表名是采用复
数还是单数形式。此外还要给表的别名定义简单规则(比方说,如果表名是一个单词,别名
就取单词的前 4 个字母;如果表名是两个单词,就各取两个单词的前两个字母组成 4 个字
母长的别名;如果表的名字由 3 个单词组成,你不妨从头两个单词中各取一个然后从最后一
个单词中再取出两个字母,结果还是组成 4 字母长的别名,其余依次类推)对工作用表来说
,表名可以加上前缀 WORK_ 后面附上采用该表的应用程序的名字。表内的列[字段]要针对键
采用一整套设计规则。比如,如果键是数字类型,你可以用 _N 作为后缀;如果是字符类型
则可以采用 _C 后缀。对列[字段]名应该采用标准的前缀和后缀。再如,假如你的表里有好
多“money”字段,你不妨给每个列[字段]增加一个 _M 后缀。还有,日期列[字段]最好以
D_ 作为名字打头。
检查表名、报表名和查询名之间的命名规范。你可能会很快就被这些不同的数据库要素的名
称搞糊涂了。假如你坚持统一地命名这些数据库的不同组成部分,至少你应该在这些对象名
字的开头用 Table、Query 或者 Report 等前缀加以区别。
如果采用了 Microsoft Access,你可以用 qry、rpt、tbl 和 mod 等符号来标识对象(比如
tbl_Employees)。我在和 SQL Server 打交道的时候还用过 tbl 来索引表,但我用
sp_company (现在用 sp_feft_)标识存储过程,因为在有的时候如果我发现了更好的处理
办法往往会保存好几个拷贝。我在实现 SQL Server 2000 时用 udf_ (或者类似的标记)标
识我编写的函数。
3. 工欲利其器
采用理想的数据库设计工具,比如:SyBase 公司的 PowerDesign,她支持 PB、VB、Delphe
等语言,通过 ODBC 可以连接市面上流行的 30 多个数据库,包括 dBase、FoxPro、VFP、
SQL Server 等,今后有机会我将着重介绍 PowerDesign 的使用。
4. 获取数据模式资源手册
正在寻求示例模式的人可以阅读《数据模式资源手册》一书,该书由 Len Silverston、W.
H. Inmon 和 Kent Graziano 编写,是一本值得拥有的最佳数据建模图书。该书包括的章节
涵盖多种数据领域,比如人员、机构和工作效能等。其他的你还可以参考:[1]萨师煊 王珊
著 数据库系统概论(第二版)高等教育出版社 1991、[2][美] Steven M.Bobrowski 著
Oracle 7 与客户/服务器计算技术从入门到精通 刘建元等译 电子工业出版社,1996、
[3]周中元 信息系统建模方法(下) 电子与信息化 1999年第3期,1999
5. 畅想未来,但不可忘了过去的教训
我发现询问用户如何看待未来需求变化非常有用。这样做可以达到两个目的:首先,你可以
清楚地了解应用设计在哪个地方应该更具灵活性以及如何避免性能瓶颈;其次,你知道发生
事先没有确定的需求变更时用户将和你一样感到吃惊。
一定要记住过去的经验教训!我们开发人员还应该通过分享自己的体会和经验互相帮助。即
使用户认为他们再也不需要什么支持了,我们也应该对他们进行这方面的教育,我们都曾经
面临过这样的时刻“当初要是这么做了该多好..”。
6. 在物理实践之前进行逻辑设计
在深入物理设计之前要先进行逻辑设计。随着大量的 CASE 工具不断涌现出来,你的设计也
可以达到相当高的逻辑水准,你通常可以从整体上更好地了解数据库设计所需要的方方面面
。
7. 了解你的业务
在你百分百地确定系统从客户角度满足其需求之前不要在你的 ER(实体关系)模式中加入哪
怕一个数据表(怎么,你还没有模式?那请你参看技巧 9)。了解你的企业业务可以在以后
的开发阶段节约大量的时间。一旦你明确了业务需求,你就可以自己做出许多决策了。
一旦你认为你已经明确了业务内容,你最好同客户进行一次系统的交流。采用客户的术语并
且向他们解释你所想到的和你所听到的。同时还应该用可能、将会和必须等词汇表达出系统
的关系基数。这样你就可以让你的客户纠正你自己的理解然后做好下一步的 ER 设计。
8. 创建数据字典和 ER 图表
一定要花点时间创建 ER 图表和数据字典。其中至少应该包含每个字段的数据类型和在每个
表内的主外键。创建 ER 图表和数据字典确实有点费时但对其他开发人员要了解整个设计却
是完全必要的。越早创建越能有助于避免今后面临的可能混乱,从而可以让任何了解数据库
的人都明确如何从数据库中获得数据。
有一份诸如 ER 图表等最新文档其重要性如何强调都不过分,这对表明表之间关系很有用,
而数据字典则说明了每个字段的用途以及任何可能存在的别名。对 SQL 表达式的文档化来说
这是完全必要的。
9. 创建模式
一张图表胜过千言万语:开发人员不仅要阅读和实现它,而且还要用它来帮助自己和用户对
话。模式有助于提高协作效能,这样在先期的数据库设计中几乎不可能出现大的问题。模式
不必弄的很复杂;甚至可以简单到手写在一张纸上就可以了。只是要保证其上的逻辑关系今
后能产生效益。
10. 从输入输出下手
在定义数据库表和字段需求(输入)时,首先应检查现有的或者已经设计出的报表、查询和
视图(输出)以决定为了支持这些输出哪些是必要的表和字段。举个简单的例子:假如客户
需要一个报表按照邮政编码排序、分段和求和,你要保证其中包括了单独的邮政编码字段而
不要把邮政编码糅进地址字段里。
11. 报表技巧
要了解用户通常是如何报告数据的:批处理还是在线提交报表?时间间隔是每天、每周、每
月、每个季度还是每年?如果需要的话还可以考虑创建总结表。系统生成的主键在报表中很
难管理。用户在具有系统生成主键的表内用副键进行检索往往会返回许多重复数据。这样的
检索性能比较低而且容易引起混乱。
12. 理解客户需求
看起来这应该是显而易见的事,但需求就是来自客户(这里要从内部和外部客户的角度考虑
)。不要依赖用户写下来的需求,真正的需求在客户的脑袋里。你要让客户解释其需求,而
且随着开发的继续,还要经常询问客户保证其需求仍然在开发的目的之中。一个不变的真理
是:“只有我看见了我才知道我想要的是什么”必然会导致大量的返工,因为数据库没有达
到客户从来没有写下来的需求标准。而更糟的是你对他们需求的解释只属于你自己,而且可
能是完全错误的。
作者Blog:http://blog.csdn.net/lkpdragon/
相关文章
数据库经验谈 二(引)
数据库设计经验谈 一 (引)
安装 Oracle 9iR2 on RHEL AS 3 (摘录)
数据库经验谈 二(引) 选择自 lkpdragon 的 Blog
关键字 数据库经验谈 二(引)
出处
第 2 部分 - 设计表和字段
1. 检查各种变化
我在设计数据库的时候会考虑到哪些数据字段将来可能会发生变更。比方说,姓氏就是如此
(注意是西方人的姓氏,比如女性结婚后从夫姓等)。所以,在建立系统存储客户信息时,
我倾向于在单独的一个数据表里存储姓氏字段,而且还附加起始日和终止日等字段,这样就
可以跟踪这一数据条目的变化。
2. 采用有意义的字段名
有一回我参加开发过一个项目,其中有从其他程序员那里继承的程序,那个程序员喜欢用屏
幕上显示数据指示用语命名字段,这也不赖,但不幸的是,她还喜欢用一些奇怪的命名法,
其命名采用了匈牙利命名和控制序号的组合形式,比如 cbo1、txt2、txt2_b 等等。
除非你在使用只面向你的缩写字段名的系统,否则请尽可能地把字段描述的清楚些。当然,
也别做过头了,比如 Customer_Shipping_Address_Street_Line_1,虽然很富有说明性,但
没人愿意键入这么长的名字,具体尺度就在你的把握中。
3. 采用前缀命名
如果多个表里有好多同一类型的字段(比如 FirstName),你不妨用特定表的前缀(比如
CusLastName)来帮助你标识字段。
时效性数据应包括“最近更新日期/时间”字段。时间标记对查找数据问题的原因、按日期重
新处理/重载数据和清除旧数据特别有用。
5. 标准化和数据驱动
数据的标准化不仅方便了自己而且也方便了其他人。比方说,假如你的用户界面要访问外部
数据源(文件、XML 文档、其他数据库等),你不妨把相应的连接和路径信息存储在用户界
面支持表里。还有,如果用户界面执行工作流之类的任务(发送邮件、打印信笺、修改记录
状态等),那么产生工作流的数据也可以存放在数据库里。预先安排总需要付出努力,但如
果这些过程采用数据驱动而非硬编码的方式,那么策略变更和维护都会方便得多。事实上,
如果过程是数据驱动的,你就可以把相当大的责任推给用户,由用户来维护自己的工作流过
程。
6. 标准化不能过头
对那些不熟悉标准化一词(normalization)的人而言,标准化可以保证表内的字段都是最基
础的要素,而这一措施有助于消除数据库中的数据冗余。标准化有好几种形式,但 Third
Normal Form(3NF)通常被认为在性能、扩展性和数据完整性方面达到了最好平衡。简单来
说,3NF 规定:
* 表内的每一个值都只能被表达一次。
* 表内的每一行都应该被唯一的标识(有唯一键)。
* 表内不应该存储依赖于其他键的非键信息。
遵守 3NF 标准的数据库具有以下特点:有一组表专门存放通过键连接起来的关联数据。比方
说,某个存放客户及其有关定单的 3NF 数据库就可能有两个表:Customer 和 Order。Order
表不包含定单关联客户的任何信息,但表内会存放一个键值,该键指向 Customer 表里包含
该客户信息的那一行。
更高层次的标准化也有,但更标准是否就一定更好呢?答案是不一定。事实上,对某些项目
来说,甚至就连 3NF 都可能给数据库引入太高的复杂性。
为了效率的缘故,对表不进行标准化有时也是必要的,这样的例子很多。曾经有个开发餐饮
分析软件的活就是用非标准化表把查询时间从平均 40 秒降低到了两秒左右。虽然我不得不
这么做,但我绝不把数据表的非标准化当作当然的设计理念。而具体的操作不过是一种派生
。所以如果表出了问题重新产生非标准化的表是完全可能的。
7. Microsoft Visual FoxPro 报表技巧
如果你正在使用 Microsoft Visual FoxPro,你可以用对用户友好的字段名来代替编号的名
称:比如用 Customer Name 代替 txtCNaM。这样,当你用向导程序 [Wizards,台湾人称为
‘精灵’] 创建表单和报表时,其名字会让那些不是程序员的人更容易阅读。
8. 不活跃或者不采用的指示符
增加一个字段表示所在记录是否在业务中不再活跃挺有用的。不管是客户、员工还是其他什
么人,这样做都能有助于再运行查询的时候过滤活跃或者不活跃状态。同时还消除了新用户
在采用数据时所面临的一些问题,比如,某些记录可能不再为他们所用,再删除的时候可以
起到一定的防范作用。
9. 使用角色实体定义属于某类别的列[字段]
在需要对属于特定类别或者具有特定角色的事物做定义时,可以用角色实体来创建特定的时
间关联关系,从而可以实现自我文档化。
这里的含义不是让 PERSON 实体带有 Title 字段,而是说,为什么不用 PERSON 实体和
PERSON_TYPE 实体来描述人员呢?比方说,当 John Smith, Engineer 提升为 John Smith,
Director 乃至最后爬到 John Smith, CIO 的高位,而所有你要做的不过是改变两个表
PERSON 和 PERSON_TYPE 之间关系的键值,同时增加一个日期/时间字段来知道变化是何时发
生的。这样,你的 PERSON_TYPE 表就包含了所有 PERSON 的可能类型,比如 Associate、
Engineer、Director、CIO 或者 CEO 等。
还有个替代办法就是改变 PERSON 记录来反映新头衔的变化,不过这样一来在时间上无法跟
踪个人所处位置的具体时间。
10. 采用常用实体命名机构数据
组织数据的最简单办法就是采用常用名字,比如:PERSON、ORGANIZATION、ADDRESS 和
PHONE 等等。当你把这些常用的一般名字组合起来或者创建特定的相应副实体时,你就得到
了自己用的特殊版本。开始的时候采用一般术语的主要原因在于所有的具体用户都能对抽象
事物具体化。
有了这些抽象表示,你就可以在第 2 级标识中采用自己的特殊名称,比如,PERSON 可能是
Employee、Spouse、Patient、Client、Customer、Vendor 或者 Teacher 等。同样的,
ORGANIZATION 也可能是 MyCompany、MyDepartment、Competitor、Hospital、Warehouse、
Government 等。最后 ADDRESS 可以具体为 Site、Location、Home、Work、Client、Vendor
、Corporate 和 FieldOffice 等。
采用一般抽象术语来标识“事物”的类别可以让你在关联数据以满足业务要求方面获得巨大
的灵活性,同时这样做还可以显著降低数据存储所需的冗余量。
11. 用户来自世界各地
在设计用到网络或者具有其他国际特性的数据库时,一定要记住大多数国家都有不同的字段
格式,比如邮政编码等,有些国家,比如新西兰就没有邮政编码一说。
12. 数据重复需要采用分立的数据表
如果你发现自己在重复输入数据,请创建新表和新的关系。
13. 每个表中都应该添加的 3 个有用的字段
* dRecordCreationDate,在 VB 下默认是 Now(),而在 SQL Server 下默认为 GETDATE()
* sRecordCreator,在 SQL Server 下默认为 NOT NULL DEFAULT USER
* nRecordVersion,记录的版本标记;有助于准确说明记录中出现 null 数据或者丢失数据
的原因
14. 对地址和电话采用多个字段
描述街道地址就短短一行记录是不够的。Address_Line1、Address_Line2 和 Address_Line3
可以提供更大的灵活性。还有,电话号码和邮件地址最好拥有自己的数据表,其间具有自身
的类型和标记类别。
过分标准化可要小心,这样做可能会导致性能上出现问题。虽然地址和电话表分离通常可以
达到最佳状态,但是如果需要经常访问这类信息,或许在其父表中存放“首选”信息(比如
Customer 等)更为妥当些。非标准化和加速访问之间的妥协是有一定意义的。
15. 使用多个名称字段
我觉得很吃惊,许多人在数据库里就给 name 留一个字段。我觉得只有刚入门的开发人员才
会这么做,但实际上网上这种做法非常普遍。我建议应该把姓氏和名字当作两个字段来处理
,然后在查询的时候再把他们组合起来。
我最常用的是在同一表中创建一个计算列[字段],通过它可以自动地连接标准化后的字段,
这样数据变动的时候它也跟着变。不过,这样做在采用建模软件时得很机灵才行。总之,采
用连接字段的方式可以有效的隔离用户应用和开发人员界面。
6. 提防大小写混用的对象名和特殊字符
过去最令我恼火的事情之一就是数据库里有大小写混用的对象名,比如 CustomerData。这一
问题从 Access 到 Oracle 数据库都存在。我不喜欢采用这种大小写混用的对象命名方法,
结果还不得不手工修改名字。想想看,这种数据库/应用程序能混到采用更强大数据库的那一
天吗?采用全部大写而且包含下划符的名字具有更好的可读性(CUSTOMER_DATA),绝对不要
在对象名的字符之间留空格。
17. 小心保留词
要保证你的字段名没有和保留词、数据库系统或者常用访问方法冲突,比如,最近我编写的
一个 ODBC 连接程序里有个表,其中就用了 DESC 作为说明字段名。后果可想而知!DESC 是
DESCENDING 缩写后的保留词。表里的一个 SELECT * 语句倒是能用,但我得到的却是一大堆
毫无用处的信息。
18. 保持字段名和类型的一致性
在命名字段并为其指定数据类型的时候一定要保证一致性。假如字段在某个表中叫做
“agreement_number”,你就别在另一个表里把名字改成“ref1”。假如数据类型在一个表
里是整数,那在另一个表里可就别变成字符型了。记住,你干完自己的活了,其他人还要用
你的数据库呢。
19. 仔细选择数字类型
在 SQL 中使用 smallint 和 tinyint 类型要特别小心,比如,假如你想看看月销售总额,
你的总额字段类型是 smallint,那么,如果总额超过了 $32,767 你就不能进行计算操作了
。
20. 删除标记
在表中包含一个“删除标记”字段,这样就可以把行标记为删除。在关系数据库里不要单独
删除某一行;最好采用清除数据程序而且要仔细维护索引整体性。
21. 避免使用触发器
触发器的功能通常可以用其他方式实现。在调试程序时触发器可能成为干扰。假如你确实需
要采用触发器,你最好集中对它文档化。
22. 包含版本机制
建议你在数据库中引入版本控制机制来确定使用中的数据库的版本。无论如何你都要实现这
一要求。时间一长,用户的需求总是会改变的。最终可能会要求修改数据库结构。虽然你可
以通过检查新字段或者索引来确定数据库结构的版本,但我发现把版本信息直接存放到数据
库中不更为方便吗?。
23. 给文本字段留足余量
ID 类型的文本字段,比如客户 ID 或定单号等等都应该设置得比一般想象更大,因为时间不
长你多半就会因为要添加额外的字符而难堪不已。比方说,假设你的客户 ID 为 10 位数长
。那你应该把数据库表字段的长度设为 12 或者 13 个字符长。这算浪费空间吗?是有一点
,但也没你想象的那么多:一个字段加长 3 个字符在有 1 百万条记录,再加上一点索引的
情况下才不过让整个数据库多占据 3MB 的空间。但这额外占据的空间却无需将来重构整个数
据库就可以实现数据库规模的增长了。身份证的号码从 15 位变成 18 位就是最好和最惨痛
的例子。
24. 列[字段]命名技巧
我们发现,假如你给每个表的列[字段]名都采用统一的前缀,那么在编写 SQL 表达式的时候
会得到大大的简化。这样做也确实有缺点,比如破坏了自动表连接工具的作用,后者把公共
列[字段]名同某些数据库联系起来,不过就连这些工具有时不也连接错误嘛。举个简单的例
子,假设有两个表:
Customer 和 Order。Customer 表的前缀是 cu_,所以该表内的子段名如下:cu_name_id、
cu_surname、cu_initials 和cu_address 等。Order 表的前缀是 or_,所以子段名是:
or_order_id、or_cust_name_id、or_quantity 和 or_description 等。
这样从数据库中选出全部数据的 SQL 语句可以写成如下所示:
Select * From Customer, Order Where cu_surname = "MYNAME" ;
and cu_name_id = or_cust_name_id and or_quantity = 1
在没有这些前缀的情况下则写成这个样子(用别名来区分):
Select * From Customer, Order Where Customer.surname = "MYNAME" ;
and Customer.name_id = Order.cust_name_id and Order.quantity = 1
第 1 个 SQL 语句没少键入多少字符。但如果查询涉及到 5 个表乃至更多的列[字段]你就知
道这个技巧多有用了。
作者Blog:http://blog.csdn.net/lkpdragon/
相关文章
数据库经验谈 二(引)
数据库设计经验谈 一 (引)
安装 Oracle 9iR2 on RHEL AS 3 (摘录)
如何使用SQL Server 2000全文检索 选择自 Jenry 的 Blog
关键字 如何使用SQL Server 2000全文检索
出处
SQL Server 2000全文检索
一、如何在SQL中启用全文检索功能
1、证全文检索服务是否安装
通过储存过程FULLTEXTSERVICEPROPERTY(返回有关全文服务级别属性的信息)来验证搜索服
务(全文组件)是否安装。如Select fulltextserviceproperty (‘IsFullTextInstalled’
):返回1表示已安装全文组件;返回0表示未安装全文组件;返回Null表示输入无效或发生错
误。
2、启动全文检索服务(MircoSoft Search)
3、打开数据库全文索引的支持
为全文索引启用数据库:
Use Northwind
Exec sp_fulltext_database ‘enable’
从数据库中删除所有目录:
Use Northwind
Exec sp_fulltext_database ‘disable’
二、建立全文目录
1、创建全文目录
Use Northwind
Exec sp_fulltext_catalog 'ft_ Catalog ', 'create'
2、重建全文目录
Use Northwind
Exec sp_fulltext_catalog 'ft_ Catalog ', ' rebulid'
3、启动全文目录的填充
Use Northwind
Exec sp_fulltext_catalog 'ft_ Catalog ', 'start_full'
4、停止全文目录的填充
Use Northwind
Exec sp_fulltext_catalog 'ft_ Catalog ', 'stop'
5、删除全文目录
Use Northwind
PK_Products
Exec sp_fulltext_catalog 'ft_ Catalog ', 'drop'
三、为表建立全文索引数据元
1、建立的唯一索引
Use Northwind
Exec sp_fulltext_table 'Products','create', 'ft_ Catalog', ' PK_Products '
2、设置全文索引列名
Use Northwind
Exec sp_fulltext_table 'Products','create', 'ft_ Catalog', ' PK_Products '
3、设置全文索引列名
Use Northwind
Exec sp_fulltext_column ' Products ', ProductName', 'add'
Go
Exec sp_fulltext_column ' Products ', QuantityPerUnit', 'add'
Go
4、激活全文索引
Use Northwind
Exec sp_fulltext_table 'Products','activate
5、填充全文索引目录
Use Northwind
Exec sp_fulltext_catalog 'ft_ Catalog ', 'start_full'
四、使用Transact-SQL进行全文检索,我们使用CONTAINS、FREETEXT等谓词
1、检索ProductName中,即包含b又包含c的所有列
Use Northwind
SELECT *
FROM Products
WHERE CONTAINS(ProductName, '"c*" and "b*" ')
2、检索ProductName中,即包含chai chang tofu之一的所有列
Use Northwind
SELECT *
FROM Products
WHERE FREETEXT(ProductName, 'chai chang tofu ')
五、全文查询有关的系统存储过程
(1)启动数据库的全文处理功能(sp_fulltext_datebase);
(2)建立全文目录(sp_fulltext_catalog);
(3)在全文目录中注册需要全文索引的表(sp_fulltext_table);
(4)指出表中需要全文检索的列名(sp_fulltext_column)
(5)为表创建全文索引(sp_fulltext_table);
(6)填充全文索引(sp_fulltext_catalog)。
全文查询支持
Microsoft® SQL Server™ 2000 在接收带全文构造的 Transact-SQL 语句时,使用全文提供
程序从 Microsoft 搜索服务检索所需信息。全文构造是 CONTAINS 或 FREETEXT 谓词,或者
是 CONTAINSTABLE 或 FREETEXTTABLE 行集函数。如果不知道包含搜索条件的列,全文构造
可以在全文索引中引用多列。该进程的流程如下图所示。
这一进程涉及的步骤包括:
应用程序给 SQL Server 实例发送带全文构造的 Transact-SQL 语句。
SQL Server 关系引擎通过查询系统表验证全文构造,确定全文索引是否覆盖列引用。关系引
擎将每个 SQL 语句简化为一系列行集操作,并使用 OLE DB 将这些操作传递给基础组件,通
常是存储引擎。关系引擎通过全文提供程序而不是存储引擎,将任何全文构造转换成对行集
的请求。请求的行集是满足搜索条件和等级的键集,而这个等级表示每个键的数据满足搜索
条件的程度。向全文提供程序发送的行集请求命令包括全文检索条件。
全文提供程序验证请求并将搜索条件更改为由Microsoft 搜索服务的查询支持组件使用的形
式。将请求发送到搜索服务。
查询支持组件使用搜索引擎组件从全文索引析取所请求的数据。然后以行集的形式将这些数
据传递回全文提供程序。
全文提供程序将这个行集返回给关系引擎。
关系引擎将它从存储引擎和全文提供程序收到的所有行集进行组合,以生成发送回客户端的
最终结果集。
全文目录和索引
Microsoft® SQL Server™ 2000 全文索引为在字符串数据中进行复杂的词搜索提供有效支持
。全文索引存储关于重要词和这些词在特定列中的位置的信息。全文查询利用这些信息,可
快速搜索包含具体某个词或一组词的行。
全文索引包含在全文目录中。每个数据库可以包含一个或多个全文目录。一个目录不能属于
多个数据库,而每个目录可以包含一个或多个表的全文索引。一个表只能有一个全文索引,
因此每个有全文索引的表只属于一个全文目录。
全文目录和索引不存储在它们所属的数据库中。目录和索引由 Microsoft 搜索服务分开管理
。
全文索引必须在基表上定义,而不能在视图、系统表或临时表上定义。 全文索引的定义包括
:
能唯一标识表中各行的列(主键或候选键),而且不允许 NULL 值。
索引所覆盖的一个或多个字符串列。
全文索引由键值填充。每个键的项提供与该键相关联的重要词(干扰词或终止词除外)、它
们所在的列和它们在列中的位置等有关信息。
格式化文本字符串(如 Microsoft® Word™ 文档文件或 HTML 文件)不能存储在字符串或
Unicode 列中,因为这些文件中的许多字节包含不构成有效字符的数据结构。数据库应用程
序可能仍需要访问这些数据并对其应用全文检索。因为 image 列并不要求每一字节都构成一
个有效字符,所以许多站点将这类数据存储在 image 列中。SQL Server 2000 引入了对存储
在 image 列中的这些类型的数据执行全文检索的能力。SQL Server 2000 提供筛选,可从
Microsoft Office™ 文件(.doc、.xls 和 .ppt 文件)、文本文件(.txt 文件)及 HTML
文件(.htm 文件)中析取文本化数据。设计表时除包括保存数据的 image 列外,还需包括
绑定列来保存存储在 image 列中的数据格式的文件扩展名。可以创建引用 image 列和绑定
列的全文索引,以便在存储于 image 列中的文本化信息上启用全文检索。SQL Server 2000
全文检索引擎使用绑定列中的文件扩展名信息,选择从列中析取文本化数据的合适的筛选。
全文索引是用于执行两个 Transact-SQL 谓词的组件,以便根据全文检索条件对行进行测试
:
CONTAINS
FREETEXT
Transact-SQL 还包含两个返回符合全文检索条件的行集的函数:
CONTAINSTABLE
FREETEXTTABLE
SQL Server 在内部将搜索条件发送给 Microsoft 搜索服务。Microsoft 搜索服务查找所有
符合全文检索条件的键并将它们返回给 SQL Server。SQL Server 随后使用键的列表来确定
表中要处理的行。
全文索引
对 Microsoft® SQL Server™ 2000 数据的全文支持涉及两个功能:对字符数据发出查询的能
力和创建及维护基础索引以简化这些查询的能力。
全文索引在许多地方与普通的 SQL 索引不同。
普通 SQL 索引
全文索引
存储时受定义它们所在的数据库的控制。
存储在文件系统中,但通过数据库管理。
每个表允许有若干个普通索引。
每个表只允许有一个全文索引。
当对作为其基础的数据进行插入、更新或删除时,它们自动更新。
将数据添加到全文索引称为填充,全文索引可通过调度或特定请求来请求,也可以在添加新
数据时自动发生。
不分组。
在同一个数据库内分组为一个或多个全文目录。
使用 SQL Server 企业管理器、向导或 Transact-SQL 语句创建和除去。
使用 SQL Server 企业管理器、向导或存储过程创建、管理和除去。
这些差异使大量管理任务变得不可缺少。全文管理是在几个层次上实施的:
服务器
可以对服务器范围的某些属性(如 resource_usage)加以设置,以便增加或减少全文服务所
使用的系统资源数量。
说明 全文引擎作为名为 Microsoft 搜索的服务在 Microsoft Windows NT® Server 和
Microsoft Windows® 2000 Server 上运行。对于 Microsoft SQL Server 个人版,
Microsoft 搜索服务不可用。尽管这意味着 Microsoft 搜索服务既未安装在 Microsoft
Windows 95/98 上,也未安装在 Windows NT 工作站或 Windows 2000 Professional 客户端
上,但这些客户端在连接到 SQL Server 标准版安装或企业版实例时可以使用这项服务。
数据库
必须启用数据库才能使用全文服务。可以在已启用的数据库中创建和除去一个或多个全文目
录的元数据。
全文目录
全文目录包含数据库中的全文索引。每个目录可以用于数据库内的一个或多个表的索引需求
。该目录中的索引是使用这里介绍的管理功能来填充的。(全文目录必须驻留在与 SQL
Server 实例相关联的本地硬盘驱动器上。不支持可移动的驱动器、软盘和网络驱动器)。在
每个服务器上最多可创建 256 个全文目录。
说明 Windows NT 故障转移群集环境完全支持全文索引。有关更多信息,请参见在故障转移
群集中运行全文查询。
表
首先,必须为全文支持启用表。然后,为与该表相关联的全文索引创建元数据(如表名及其
全文目录)。表启用后,可以用为全文支持而启用的列中的数据填充它。如果表的全文定义
被更改(例如,添加一个也将为全文检索而索引的新列),则必须重新填充相关的全文目录
以使全文索引与新的全文定义同步。
列
可以从非活动的注册表中添加或除去支持全文查询的列。
在所有这些级别上,可使用工具检索元数据和状态信息。
和常规 SQL 索引一样,当在相关表中修改数据时,可自动更新全文索引。或者,也可以适当
的间隔手工重新填充全文索引。这种重写可能既耗时又大量占用资源,因此,在数据库活动
较少时,这通常是在后台运行的异步进程。
应将具有相同更新特性的表(如更改少的与更改多的,或在一天的特定时段内频繁更改的表
)组合在一起,并分配给相同的全文目录。通过以此方法设置全文目录填充调度,使得全文
索引和表保持同步,且在数据库活动较多时不对数据库服务器的资源使用产生负面影响。
为全文目录中的表安排全文索引的位置是非常重要的。在为全文目录指定表时,应该注意下
列基本原则:
始终选择可用于全文唯一键的最小唯一索引。(4 个字节且基于整数的索引是最佳的。)这
将显著减少文件系统中 Microsoft 搜索服务所需要的资源。如果主键很大(超过 100 字节
),可以考虑选择表中其它唯一索引(或创建另一个唯一索引)作为全文唯一键。否则,如
果全文唯一键的大小达到允许的上限(450 字节),全文填充将无法继续进行。
如果进行索引的表有成千上万行,请将该表指定给其自己的全文目录。
应该考虑对其进行全文索引的表中发生的更改数以及表的行数。如果要更改的总行数,加上
上次全文填充期间表中出现的行数达到成千上万行,请将该表指定给其自己的全文目录。
---------------------------------------------------
编写高性能 Web 应用程序的10 个技巧 选择自 Jenry 的 Blog
关键字 编写高性能 Web 应用程序的10 个技巧
出处
编写高性能 Web 应用程序的 10 个技巧
作者:Rob Howard
本文讨论
• 常见 ASP.NET 性能难点
• 面向 ASP.NET 的有用性能提示和技巧
• 在 ASP.NET 中使用数据库的建议
• 使用 ASP.NET 进行缓存和后台处理
本文使用了以下技术:
ASP.NET、.NET Framework 和 IIS
本页内容
数据层性能
技巧 1 — 返回多个结果集
技巧 2 — 分页的数据访问
技巧 3 — 连接池
技巧 4 — ASP.NET 缓存 API
技巧 5 — 每请求缓存
技巧 6 — 后台处理
技巧 7 — 页输出缓存和代理服务器
技巧 8 — 运行 IIS 6.0(只要用于内核缓存)
技巧 9 — 使用 Gzip 压缩
技巧 10 — 服务器控件视图状态
小结
使用 ASP.NET 编写 Web 应用程序的简单程度令人不敢相信。正因为如此简单,所以很多开
发人员就不会花时间来设计其应用程序的结构,以获得更好的性能了。在本文中,我将讲述
10 个用于编写高性能 Web 应用程序的技巧。但是我并不会将这些建议仅局限于 ASP.NET 应
用程序,因为这些应用程序只是 Web 应用程序的一部分。本文不作为对 Web 应用程序进行
性能调整的权威性指南 — 一整本书恐怕都无法轻松讲清楚这个问题。请将本文视作一个很
好的起点。
成为工作狂之前,我原来喜欢攀岩。在进行任何大型攀岩活动之前,我都会首先仔细查看指
南中的路线,阅读以前游客提出的建议。但是,无论指南怎么好,您都需要真正的攀岩体验
,然后才能尝试一个特别具有挑战性的攀登。与之相似,当您面临修复性能问题或者运行一
个高吞吐量站点的问题时,您只能学习如何编写高性能 Web 应用程序。
我的个人体验来自在 Microsoft 的 ASP.NET 部门作为基础架构程序经理的经验,在此期间
我运行和管理 www.ASP.NET,帮助设计社区服务器的结构,社区服务器是几个著名 ASP.NET
应用程序(组合到一个平台的 ASP.NET Forums、.Text 和 nGallery)。我确信有些曾经帮
助过我的技巧对您肯定也会有所帮助。
您应该考虑将应用程序分为几个逻辑层。您可能听说过 3 层(或者 n 层)物理体系结构一
词。这些通常都是规定好的体系结构方式,将功能在进程和/或硬件之间进行了物理分离。当
系统需要扩大时,可以很轻松地添加更多的硬件。但是会出现一个与进程和机器跳跃相关的
性能下降,因此应该避免。所以,如果可能的话,请尽量在同一个应用程序中一起运行
ASP.NET 页及其相关组件。
因为代码分离以及层之间的边界,所以使用 Web 服务或远程处理将会使得性能下降 20% 甚
至更多。
数据层有点与众不同,因为通常情况下,最好具有专用于数据库的硬件。然而进程跳跃到数
据库的成本依然很高,因此数据层的性能是您在优化代码时首先要考虑的问题。
在深入应用程序的性能修复问题之前,请首先确保对应用程序进行剖析,以便找出具体的问
题所在。主要性能计数器(如表示执行垃圾回收所需时间百分比的计数器)对于找出应用程
序在哪些位置花费了其主要时间也非常有用。然而花费时间的位置通常非常不直观。
本文讲述了两种类型的性能改善:大型优化(如使用 ASP.NET 缓存),和进行自身重复的小
型优化。这些小型优化有时特别有意思。您对代码进行一点小小的更改,就会获得很多很多
时间。使用大型优化,您可能会看到整体性能的较大飞跃。而使用小型优化时,对于某个特
定请求可能只会节省几毫秒的时间,但是每天所有请求加起来,则可能会产生巨大的改善。
数据层性能
谈到应用程序的性能调整,有一个试纸性的测试可用来对工作进行优先级划分:代码是否访
问数据库?如果是,频率是怎样的?请注意,这一相同测试也可应用于使用 Web 服务或远程
处理的代码,但是本文对这些内容未做讲述。
如果某个特定的代码路径中必需进行数据库请求,并且您认为要首先优化其他领域(如字符
串操作),则请停止,然后执行这个试纸性测试。如果您的性能问题不是非常严重的话,最
好花一些时间来优化一下与数据库、返回的数据量、进出数据库的往返频率相关的花费时间
。
了解这些常规信息之后,我们来看一下可能会有助于提高应用程序性能的十个技巧。首先,
我要讲述可能会引起最大改观的更改。
返回页首
技巧 1 — 返回多个结果集
仔细查看您的数据库代码,看是否存在多次进入数据库的请求路径。每个这样的往返都会降
低应用程序可以提供的每秒请求数量。通过在一个数据库请求中返回多个结果集,可以节省
与数据库进行通信所需的总时间长度。同时因为减少了数据库服务器管理请求的工作,还会
使得系统伸缩性更强。
虽然可以使用动态 SQL 返回多个结果集,但是我首选使用存储过程。关于业务逻辑是否应该
驻留于存储过程的问题还存在一些争议,但是我认为,如果存储过程中的逻辑可以约束返回
数据的话(缩小数据集的大小、缩短网络上所花费时间,不必筛选逻辑层的数据),则应赞
成这样做。
使用 SqlCommand 实例及其 ExecuteReader 方法填充强类型的业务类时,可以通过调用
NextResult 将结果集指针向前移动。图 1 显示了使用类型类填充几个 ArrayList 的示例会
话。只从数据库返回您需要的数据将进一步减少服务器上的内存分配。
返回页首
技巧 2 — 分页的数据访问
ASP.NET DataGrid 具有一个很好的功能:数据分页支持。在 DataGrid 中启用分页时,一次
会显示固定数量的记录。另外,在 DataGrid 的底部还会显示分页 UI,以便在记录之间进行
导航。该分页 UI 使您能够在所显示的数据之间向前和向后导航,并且一次显示固定数量的
记录。
还有一个小小的波折。使用 DataGrid 的分页需要所有数据均与网格进行绑定。例如,您的
数据层需要返回所有数据,那么 DataGrid 就会基于当前页筛选显示的所有记录。如果通过
DataGrid 进行分页时返回了 100,000 个记录,那么针对每个请求会放弃 99,975 个记录(
假设每页大小为 25 个记录)。当记录的数量不断增加时,应用程序的性能就会受到影响,
因为针对每个请求必须发送越来越多的数据。
要编写性能更好的分页代码,一个极佳的方式是使用存储过程。图 2 显示了针对 Northwind
数据库中的 Orders 表进行分页的一个示例存储过程。简而言之,您此时要做的只是传递页
索引和页大小。然后就会计算合适的结果集,并将其返回。
在社区服务器中,我们编写了一个分页服务器控件,以完成所有的数据分页。您将会看到,
我使用的就是技巧 1 中讨论的理念,从一个存储过程返回两个结果集:记录的总数和请求的
数据。
返回记录的总数可能会根据所执行查询的不同而有所变化。例如,WHERE 子句可用来约束返
回的数据。为了计算在分页 UI 中显示的总页数,必须了解要返回记录的总数。例如,如果
总共有 1,000,000 条记录,并且要使用一个 WHERE 子句将其筛选为 1000 条记录,那么分
页逻辑就需要了解记录的总数才能正确呈现分页 UI。
返回页首
技巧 3 — 连接池
在 Web 应用程序和 SQL Server™ 之间设置 TCP 连接可能是一个非常消耗资源的操作。
Microsoft 的开发人员到目前为止能够使用连接池已经有一段时间了,这使得他们能够重用
数据库连接。他们不是针对每个请求都设置一个新的 TCP 连接,而是只在连接池中没有任何
连接时才设置新连接。当连接关闭时,它会返回连接池,在其中它会保持与数据库的连接,
而不是完全破坏该 TCP 连接。
当然,您需要小心是否会出现泄漏连接。当您完成使用连接时,请一定要关闭这些连接。再
重复一遍:无论任何人对 Microsoft?.NET Framework 中的垃圾回收有什么评论,请一定要
在完成使用连接时针对该连接显式调用 Close 或 Dispose。不要相信公共语言运行库 (CLR)
会在预先确定的时间为您清除和关闭连接。尽管 CLR 最终会破坏该类,并强制连接关闭,但
是当针对对象的垃圾回收真正发生时,并不能保证。
要以最优化的方式使用连接池,需要遵守一些规则。首先打开连接,执行操作,然后关闭该
连接。如果您必须如此的话,可以针对每个请求多次打开和关闭连接(最好应用技巧 1),
但是不要一直将连接保持打开状态并使用各种不同的方法对其进行进出传递。第二,使用相
同的连接字符串(如果使用集成身份验证的话,还要使用相同的线程标识)。如果不使用相
同的连接字符串,例如根据登录的用户自定义连接字符串,那么您将无法得到连接池提供的
同一个优化值。如果您使用集成身份验证,同时还要模拟大量用户,连接池的效率也会大大
下降。尝试跟踪与连接池相关的任何性能问题时,.NET CLR 数据性能计数器可能非常有用。
每当应用程序连接资源时,如在另一个进程中运行的数据库,您都应该重点考虑连接该资源
所花时间、发送或检索数据所花时间,以及往返的数量,从而进行优化。优化应用程序中任
何种类的进程跳跃都是获得更佳性能的首要一点。
应用层包含了连接数据层、将数据转换为有意义类实例和业务流程的逻辑。例如社区服务器
,您要在其中填充Forums 或 Threads集合,应用业务规则(如权限);最重要的是要在其中
执行缓存逻辑。
返回页首
技巧 4 — ASP.NET 缓存 API
编写应用程序代码行之前,一个首要完成的操作是设计应用层的结构,以便最大化利用
ASP.NET 缓存功能。
如果您的组件要在 ASP.NET 应用程序中运行,则只需在该应用程序项目中包括一个
System.Web.dll 引用。当您需要访问该缓存时,请使用 HttpRuntime.Cache 属性(通过
Page.Cache 和 HttpContext.Cache 也可访问这个对象)。
对于缓存数据,有几个规则。首先,如果数据可能会多次使用时,则这是使用缓存的一个很
好的备选情况。第二,如果数据是通用的,而不特定于某个具体的请求或用户时,则也是使
用缓存的一个很好的备选情况。如果数据是特定于用户或请求的,但是寿命较长的话,仍然
可以对其进行缓存,但是这种情况可能并不经常使用。第三,一个经常被忽略的规则是,有
时可能您缓存得太多。通常在一个 x86 计算机上,为了减少内存不足错误出现的机会,您会
想使用不高于 800MB 的专用字节运行进程。因此缓存应该有个限度。换句话说,您可能能够
重用某个计算结果,但是如果该计算采用 10 个参数的话,您可能要尝试缓存 10 个排列,
这样有可能给您带来麻烦。一个要求 ASP.NET 的最常见支持是由于过度缓存引起的内存不足
错误,尤其是对于大型数据集。
图 3 ASP.NET缓存
缓存有几个极佳的功能,您需要对它们有所了解。首先,缓存会实现最近最少使用的算法,
使得 ASP.NET 能够在内存运行效率较低的情况下强制缓存清除 - 从缓存自动删除未使用过
的项目。第二,缓存支持可以强制失效的过期依赖项。这些依赖项包括时间、密钥和文件。
时间经常会用到,但是对于 ASP.NET 2.0,引入了一个功能更强的新失效类型:数据库缓存
失效。它指的是当数据库中的数据发生变化时自动删除缓存中的项。有关数据库缓存失效的
详细信息,请参阅 MSDN?Magazine 2004 年 7 月的 Dino Esposito Cutting Edge 专栏。要
了解缓存的体系结构,请参阅图 3。
返回页首
技巧 5 — 每请求缓存
在本文前面部分,我提到了经常遍历代码路径的一些小改善可能会导致较大的整体性能收益
。对于这些小改善,其中有一个绝对是我的最爱,我将其称之为“每请求缓存”。
缓存 API 的设计目的是为了将数据缓存较长的一段时间,或者缓存至满足某些条件时,但每
请求缓存则意味着只将数据缓存为该请求的持续时间。对于每个请求,要经常访问某个特定
的代码路径,但是数据却只需提取、应用、修改或更新一次。这听起来有些理论化,那么我
们来举一个具体的示例。
在社区服务器的论坛应用程序中,页面上使用的每个服务器控件都需要个性化的数据来确定
使用什么外观、使用什么样式表,以及其他个性化数据。这些数据中有些可以长期缓存,但
是有些数据却只针对每个请求提取一次,然后在执行该请求期间对其重用多次,如要用于控
件的外观。
为了达到每请求缓存,请使用 ASP.NET HttpContext。对于每个请求,都会创建一个
HttpContext 实例,在该请求期间从 HttpContext.Current 属性的任何位置都可访问该实例
。该 HttpContext 类具有一个特殊的 Items 集合属性;添加到此 Items 集合的对象和数据
只在该请求持续期间内进行缓存。正如您可以使用缓存来存储经常访问的数据一样,您也可
以使用 HttpContext.Items 来存储只基于每个请求使用的数据。它背后的逻辑非常简单:数
据在它不存在的时候添加到 HttpContext.Items 集合,在后来的查找中,只是返回
HttpContext.Items 中的数据。
返回页首
技巧 6 — 后台处理
通往代码的路径应该尽可能快速,是吗?可能有时您会觉得针对每个请求执行的或者每 n 个
请求执行一次的任务所需资源非常多。发送电子邮件或者分析和验证传入数据就是这样的一
些例子。
剖析 ASP.NET Forums 1.0 并重新构建组成社区服务器的内容时,我们发现添加新张贴的代
码路径非常慢。每次添加新张贴时,应用程序首先需要确保没有重复的张贴,然后必须使用
“坏词”筛选器分析该张贴,分析张贴的字符图释,对张贴添加标记并进行索引,请求时将
张贴添加到合适的队列,验证附件,最终张贴之后,立即向所有订阅者发出电子邮件通知。
很清楚,这涉及很多操作。
经研究发现,大多数时间都花在了索引逻辑和发送电子邮件上。对张贴进行索引是一个非常
耗时的操作,人们发现内置的 System.Web.Mail 功能要连接 SMYP 服务器,然后连续发送电
子邮件。当某个特定张贴或主题领域的订阅者数量增加时,执行 AddPost 功能所需的时间也
越来越长。
并不需要针对每个请求都进行电子邮件索引。理想情况下,我们想要将此操作进行批处理,
一次索引 25 个张贴或者每五分钟发送一次所有电子邮件。我们决定使用以前用于对数据缓
存失效进行原型设计的代码,这个失效是用于最终进入 Visual Studio® 2005 的内容的。
System.Threading 命名空间中的 Timer 类非常有用,但是在 .NET Framework 中不是很有
名,至少对于 Web 开发人员来说是这样。创建之后,这个 Timer 类将以一个可配置的间隔
针对 ThreadPool 中的某个线程调用指定的回调。这就表示,您可以对代码进行设置,使其
能够在没有对 ASP.NET 应用程序进行传入请求的情况下得以执行,这是后台处理的理想情况
。您还可以在此后台进程中执行如索引或发送电子邮件之类的操作。
但是,这一技术有几个问题。如果应用程序域卸载,该计时器实例将停止触发其事件。另外
,因为 CLR 对于每个进程的线程数量具有一个硬性标准,所以可能会出现这样的情形:服务
器负载很重,其中计时器可能没有可在其基础上得以完成的线程,在某种程度上可能会造成
延迟。ASP.NET 通过在进程中保留一定数量的可用线程,并且仅使用总线程的一部分用于请
求处理,试图将上述情况发生的机会降到最低。但是,如果您具有很多异步操作时,这可能
就是一个问题了。
这里没有足够的空间来放置该代码,但是您可以下载一个可以看懂的示例,网址是 www.rob
-howard.net。请了解一下 Blackbelt TechEd 2004 演示中的幻灯片和演示。
返回页首
技巧 7 — 页输出缓存和代理服务器
ASP.NET 是您的表示层(或者说应该是您的表示层);它由页、用户控件、服务器控件
(HttpHandlers 和 HttpModules)以及它们生成的内容组成。如果您具有一个 ASP.NET 页
,它会生成输出(HTML、XML、图像或任何其他数据),并且您针对每个请求运行此代码时,
它都会生成相同的输出,那么您就拥有一个可用于页输出缓存的绝佳备选内容。
将此行内容添加页的最上端
就可以高效地为此页生成一次输出,然后对它进行多次重用,时间最长为 60 秒,此时该页
将重新执行,输出也将再一次添加到 ASP.NET 缓存。通过使用一些低级程序化 API 也可以
完成此行为。对于输出缓存有几个可配置的设置,如刚刚讲到的 VaryByParams 属性。
VaryByParams 刚好被请求到,但还允许您指定 HTTP GET 或 HTTP POST 参数来更改缓存项
。例如,只需设置 VaryByParam="Report" 即可对 default.aspx?Report=1 或
default.aspx?Report=2 进行输出缓存。通过指定一个以分号分隔的列表,还可以指定其他
参数。
很多人都不知道何时使用输出缓存,ASP.NET 页还会生成一些位于缓存服务器下游的 HTTP
标头,如 Microsoft Internet Security and Acceleration Server 或 Akamai 使用的标头
。设置了 HTTP 缓存标头之后,可以在这些网络资源上对文档进行缓存,客户端请求也可在
不必返回原始服务器的情况下得以满足。
因此,使用页输出缓存不会使得您的应用程序效率更高,但是它可能会减少服务器上的负载
,因为下游缓存技术会缓存文档。当然,这可能只是匿名内容;一旦它成为下游之后,您就
再也不会看到这些请求,并且再也无法执行身份验证以阻止对它的访问了。
返回页首
技巧 8 — 运行 IIS 6.0(只要用于内核缓存)
如果您未运行 IIS 6.0 (Windows Server? 2003),那么您就错过了 Microsoft Web 服务器
中的一些很好的性能增强。在技巧 7 中,我讨论了输出缓存。在 IIS 5.0 中,请求是通过
IIS 然后进入 ASP.NET 的。涉及到缓存时,ASP.NET 中的 HttpModule 会接收该请求,并返
回缓存中的内容。
如果您正在使用 IIS 6.0,就会发现一个很好的小功能,称为内核缓存,它不需要对
ASP.NET 进行任何代码更改。当请求由 ASP.NET 进行输出缓存时,IIS 内核缓存会接收缓存
数据的一个副本。当请求来自网络驱动程序时,内核级别的驱动程序(无上下文切换到用户
模式)就会接收该请求,如果经过了缓存,则会将缓存的数据刷新到响应,然后完成执行。
这就表示,当您将内核模式缓存与 IIS 和 ASP.NET 输出缓存一起使用时,就会看到令人不
敢相信的性能结果。在 ASP.NET 的 Visual Studio 2005 开发过程中,我一度是负责
ASP.NET 性能的程序经理。开发人员完成具体工作,但是我要看到每天进行的所有报告。内
核模式缓存结果总是最有意思的。最常见的特征是网络充满了请求/响应,而 IIS 运行时的
CPU 使用率只有大约 5%。这太令人震惊了!当然使用 IIS 6.0 还有一些其他原因,但是内
核模式缓存是其中最明显的一个。
返回页首
技巧 9 — 使用 Gzip 压缩
虽然使用 gzip 并不一定是服务器性能技巧(因为您可能会看到 CPU 使用率的提高),但是
使用 gzip 压缩可以减少服务器发送的字节数量。这就使人们觉得页速度加快了,并且还减
少了带宽的用量。根据所发送数据、可以压缩的程度以及客户端浏览器是否支持(IIS 只会
向支持 gzip 压缩的客户端发送经过 gzip 压缩的内容,如 Internet Explorer 6.0 和
Firefox),您的服务器每秒可以服务于更多的请求。实际上,几乎每当您减少所返回数据的
数量时,都会增加每秒请求数。
Gzip 压缩已经内置到 IIS 6.0 中,并且其性能比 IIS 5.0 中使用的 gzip 压缩要好的多,
这是好消息。但不幸的是,当尝试在 IIS 6.0 中打开 gzip 压缩时,您可能无法在 IIS 的
属性对话中找到该设置。IIS 小组在该服务器中置入了卓越的 gzip 功能,但是忘了包括一
个用于启用该功能的管理 UI。要启用 gzip 压缩,您必须深入到 IIS 6.0 的 XML 配置设置
内部(这样不会引起心脏虚弱)。顺便提一句,这归功于 OrcsWeb 的 Scott Forsyth,他帮
助我提出了在 OrcsWeb 上宿主的 www.asp.net 服务器的这个问题。
本文就不讲述步骤了,请阅读 Brad Wilson 的文章,网址是 IIS6 Compression。还有一篇
有关为 ASPX 启用压缩的知识库文章,网址是 Enable ASPX Compression in IIS。但是您应
该注意,由于一些实施细节,IIS 6.0 中不能同时存在动态压缩和内核缓存。
返回页首
技巧 10 — 服务器控件视图状态
视图状态是一个有趣的名称,用于表示在所生成页的隐藏输出字段中存储一些状态数据的
ASP.NET。当该页张贴回服务器时,服务器可以分析、验证、并将此视图状态数据应用回该页
的控件树。视图状态是一个非常强大的功能,因为它允许状态与客户端一起保持,并且它不
需要 cookie 或服务器内存即可保存此状态。很多 ASP.NET 服务器控件都使用视图状态来保
持在与页元素进行交互期间创建的设置,例如保存对数据进行分页时显示的当前页。
然而使用视图状态也有一些缺点。首先,服务或请求页时,它都会增加页的总负载。对张贴
回服务器的视图状态数据进行序列化或取消序列化时,也会发生额外的开销。最后,视图状
态会增加服务器上的内存分配。
几个服务器控件有着过度使用视图状态的趋势,即使在并不需要的情况下也要使用它,其中
最著名的是 DataGrid。ViewState 属性的默认行为是启用,但是如果您不需要,则可以在控
件或页级别关闭。在控件内,只需将 EnableViewState 属性设置为 false,或者在页中使用
下列设置即可对其进行全局设置:
如果您不回发页,或者总是针对每个请求重新生成页上的控件,则应该在页级别禁用视图状
态。
返回页首
小结
我为您讲述了一些我认为在编写高性能 ASP.NET 应用程序时有所帮助的技巧。正如我在本文
前面部分提到的那样,这是一个初步指南,并不是 ASP.NET 性能的最后结果。(有关改善
ASP.NET 应用程序性能的信息,请参阅 Improving ASP.NET Performance。)只有通过自己
的亲身体验才能找出解决具体性能问题的最好方法。但是,在您的旅程中,这些技巧应该会
为您提供一些好的指南。在软件开发中,几乎没有绝对的东西;每个应用程序都是唯一的。
请参阅提要栏“Common Performance Myths”。
Rob Howard 是 Telligent Systems 的创始人,专门从事高性能 Web 应用程序、知识库管理
和协作系统方面的工作。Rob 以前受雇于 Microsoft,他在那里帮助设计了 ASP.NET 1.0、
1.1 和 2.0 的基础结构。要联系 Rob,请访问 rhoward@telligentsystems.com。
-------------------------------------------
新手求教SQL多表查询问题~~
主要解答者: supsuccess 提交人: dyfh
感谢: scottwhb
审核者: CSDNM 社区对应贴子: 查看
A :
新手求教SQL多表查询问题~~
我在做程序时,遇到一个问题,怎么也解决不了,所以来请教各位高手
现在有一个库存表(KC),一个入库表(RK),一个出库表(CHK),都有CARDTYPE字段,我
需要通过这个字段,在一条语句中同时取出本月库存表中的现有数量(NUM),入库表中的入
库数量(RKNUM),出库表中的出库数量(CHKNUM),可是想得头快BOMO了,都没想出来怎么
写~~
主要是因为要通过一条SQL语句生成一条记录以显示,但入库表和出库表中有时本月会没有入
出库记录,那么应当显示为入出库数量为零,但现在是没有入出库记录就没有查询结果,于
是有的CARDTYPE类型结果没有查到,而如果入出库记录只有其中一个有,那么结果可以查出
,但只有有记录的结果正确,没有记录的数量不正确(实际上和有记录的结果一样),怎么
解决?求助~~~
我的SQL语句大约是这么写的:
select kc.cardtype ,a.rkNum,b.chkNum from (select kc.cardtype cardtype,sum
(rk.NUM) rkNum from rk,kc where rk.cardtype=kc.cardtype) a,(select
kc.cardtype cardtype,sum(chk.NUM) chkNum from chk,kc where
chk.cardtype=kc.cardtype) b where a.cardtype=kc.cardtype
group by kc.cardtype
因为是在公司写的,在家中又没有SQLServer,所以只是凭记忆写的,可能有些地方不对,我
在这方面还是新手,吼吼
我的问题究竟在那里?请各位高手不吝赐教:)
试试:
select t1.cardtype,kcNum=isnull(sum(t1.kcNum),0),rkNum=isnull(sum
(t2.rkNum),0),chkNum=isnull(sum(t3.chkNum),0)
from kc t1 left join rk t2 on t1.cardtype=t2.cardtype
left join chk t3 on t1.cardtype=t3.cardtype
group by t1.cardtype
order by t1.cardtype
------------------------------------------------------
高分请教巨难sql语句,在线等候高手指教。
主要解答者: CSDNM 提交人: delandwu
感谢: CSDNM、hillhx
审核者: tj_dns 社区对应贴子: 查看
A :
每小时雨量表a:
ID 降雨量 日期时间 时最高温度 时最低温
度
-------------------------------------
a001 12 02-10-21 08:00:00 20 18.2
a001 0 02-10-21 09:00:00 22 21.1
.
.
.
a001 0.3 02-10-22 16:00:00 26 25.2
a002 9 02-10-21 08:00:00 17 16.2
.
.
.
a002 0.1 02-10-22 16:00:00 25.8 25.3
表b:
ID 地区 站名
a001 北京 天文台
a002 北京 天安门
a003 广州 天文台
a004 广州 水利会
.
.
.
我需要获得的输出是:(请注意,日期时间输出仅输入时间)
ID 站名 地区 日期 日最高温度 日最低温
度 日降雨量
a001 天文台 北京 2002-10-20 26 18.2
12.3
a002 天安门 北京 2002-10-20 .. ..
..
我要统计每个站每天的最高温度和最低温度,日降雨量。
最高低温度统计方法为取一天中最高最低值,降雨量取每小时加起来的总数
还有就是统计北京的最高低温度和日降雨量,因为北京有两个站,取值方法为:高低温度取
最高最低值,降雨量选最大值,即哪个站录得雨量最大就选哪个。
还有统计全国地区,例如北部地区,南部地区,西部地区,东部地区,统计方法和城市统计
一样。
---------------------------------------------------------------
SELECT B.ID,B.站名,B.地区,CONVERT(CHAR(10),A.日期时间,120) AS 日期,MAX(A.时最
高温度) AS 日最高温度,MIN(A.时最低温度) AS 日最低温度,SUM( 降雨量) AS 日降
雨量
FROM A,B
WHERE A.ID=B.ID
GROUP BY B.ID,B.站名,B.地区,CONVERT(CHAR(10),A.日期时间,120)
题不够难(更不用说巨难),分不够高!!!
---------------------------------------------------------------
其实GROUP BY CASE WHEN 取小时部分 > 20 then 加1天 ELSE 当天 END CASE
办法没有加小时的办法好,但也是个思路,扩展扩展视野,终究CASE使用要比较灵活一些,
如果以上题目对时间的规则会变的更复杂的化只有用CASE才能达到目的
--------------------------------------
SQL Server日期计算(收藏) 选择自 xpilot 的 Blog
关键字 SQL Server日期计算(收藏)
出处
通常,你需要获得当前日期和计算一些其他的日期,例如,你的程序可能需要判断一个月的
第一天或者最后一天。你们大部分人大概都知道怎样把日期进行分割(年、月、日等),然
后仅仅用分割出来的年、月、日等放在几个函数中计算出自己所需要的日期!在这篇文章里
,我将告诉你如何使用DATEADD和DATEDIFF函数来计算出在你的程序中可能你要用到的一些不
同日期。
在使用本文中的例子之前,你必须注意以下的问题。大部分可能不是所有例子在不同
的机器上执行的结果可能不一样,这完全由哪一天是一个星期的第一天这个设置决定。第一
天(DATEFIRST)设定决定了你的系统使用哪一天作为一周的第一天。所有以下的例子都是以
星期天作为一周的第一天来建立,也就是第一天设置为7。假如你的第一天设置不一样,你可
能需要调整这些例子,使它和不同的第一天设置相符合。你可以通过@@DATEFIRST函数来检查
第一天设置。
为了理解这些例子,我们先复习一下DATEDIFF和DATEADD函数。DATEDIFF函数计算两
个日期之间的小时、天、周、月、年等时间间隔总数。DATEADD函数计算一个日期通过给时间
间隔加减来获得一个新的日期。要了解更多的DATEDIFF和DATEADD函数以及时间间隔可以阅读
微软联机帮助。
使用DATEDIFF和DATEADD函数来计算日期,和本来从当前日期转换到你需要的日期的
考虑方法有点不同。你必须从时间间隔这个方面来考虑。比如,从当前日期到你要得到的日
期之间有多少时间间隔,或者,从今天到某一天(比如1900-1-1)之间有多少时间间隔,等
等。理解怎样着眼于时间间隔有助于你轻松的理解我的不同的日期计算例子。
一个月的第一天
第一个例子,我将告诉你如何从当前日期去这个月的最后一天。请注意:这个例子以
及这篇文章中的其他例子都将只使用DATEDIFF和DATEADD函数来计算我们想要的日期。每一个
例子都将通过计算但前的时间间隔,然后进行加减来得到想要计算的日期。
这是计算一个月第一天的SQL 脚本:
SELECT DATEADD(mm, DATEDIFF(mm,0,getdate()), 0)
我们把这个语句分开来看看它是如何工作的。最核心的函数是getdate(),大部分人
都知道这个是返回当前的日期和时间的函数。下一个执行的函数DATEDIFF(mm,0,getdate())
是计算当前日期和“1900-01-01 00:00:00.000”这个日期之间的月数。记住:时期和时间
变量和毫秒一样是从“1900-01-01 00:00:00.000”开始计算的。这就是为什么你可以在
DATEDIFF函数中指定第一个时间表达式为“0”。下一个函数是DATEADD,增加当前日期到
“1900-01-01”的月数。通过增加预定义的日期“1900-01-01”和当前日期的月数,我们可
以获得这个月的第一天。另外,计算出来的日期的时间部分将会是“00:00:00.000”。
这个计算的技巧是先计算当前日期到“1900-01-01”的时间间隔数,然后把它加到“
1900-01-01”上来获得特殊的日期,这个技巧可以用来计算很多不同的日期。下一个例子也
是用这个技巧从当前日期来产生不同的日期。
本周的星期一
这里我是用周(wk)的时间间隔来计算哪一天是本周的星期一。
SELECT DATEADD(wk, DATEDIFF(wk,0,getdate()), 0)
一年的第一天
现在用年(yy)的时间间隔来显示这一年的第一天。
SELECT DATEADD(yy, DATEDIFF(yy,0,getdate()), 0)
季度的第一天
假如你要计算这个季度的第一天,这个例子告诉你该如何做。
SELECT DATEADD(qq, DATEDIFF(qq,0,getdate()), 0)
当天的半夜
曾经需要通过getdate()函数为了返回时间值截掉时间部分,就会考虑到当前日期是
不是在半夜。假如这样,这个例子使用DATEDIFF和DATEADD函数来获得半夜的时间点。
SELECT DATEADD(dd, DATEDIFF(dd,0,getdate()), 0)
深入DATEDIFF和DATEADD函数计算
你可以明白,通过使用简单的DATEDIFF和DATEADD函数计算,你可以发现很多不同的
可能有意义的日期。
目前为止的所有例子只是仅仅计算当前的时间和“1900-01-01”之间的时间间隔数量
,然后把它加到“1900-01-01”的时间间隔上来计算出日期。假定你修改时间间隔的数量,
或者使用不同的时间间隔来调用DATEADD函数,或者减去时间间隔而不是增加,那么通过这些
小的调整你可以发现和多不同的日期。
这里有四个例子使用另外一个DATEADD函数来计算最后一天来分别替换DATEADD函数前
后两个时间间隔。
上个月的最后一天
这是一个计算上个月最后一天的例子。它通过从一个月的最后一天这个例子上减去3
毫秒来获得。有一点要记住,在Sql Server中时间是精确到3毫秒。这就是为什么我需要减
去3毫秒来获得我要的日期和时间。
SELECT dateadd(ms,-3,DATEADD(mm, DATEDIFF(mm,0,getdate()), 0))
计算出来的日期的时间部分包含了一个Sql Server可以记录的一天的最后时刻
(“23:59:59:997”)的时间。
去年的最后一天
连接上面的例子,为了要得到去年的最后一天,你需要在今年的第一天上减去3毫秒
。
SELECT dateadd(ms,-3,DATEADD(yy, DATEDIFF(yy,0,getdate()), 0))
本月的最后一天
现在,为了获得本月的最后一天,我需要稍微修改一下获得上个月的最后一天的语句
。修改需要给用DATEDIFF比较当前日期和“1900-01-01”返回的时间间隔上加1。通过加1个
月,我计算出下个月的第一天,然后减去3毫秒,这样就计算出了这个月的最后一天。这是计
算本月最后一天的SQL脚本。
SELECT dateadd(ms,-3,DATEADD(mm, DATEDIFF(m,0,getdate())+1, 0))
本年的最后一天
你现在应该掌握这个的做法,这是计算本年最后一天脚本
SELECT dateadd(ms,-3,DATEADD(yy, DATEDIFF(yy,0,getdate())+1, 0))。
本月的第一个星期一
好了,现在是最后一个例子。这里我要计算这个月的第一个星期一。这是计算的脚本
。
select DATEADD(wk, DATEDIFF(wk,0,
dateadd(dd,6-datepart(day,getdate()),getdate())
), 0)
在这个例子里,我使用了“本周的星期一”的脚本,并作了一点点修改。修改的部分
是把原来脚本中“getdate()”部分替换成计算本月的第6天,在计算中用本月的第6天来替换
当前日期使得计算可以获得这个月的第一个星期一。
总结
我希望这些例子可以在你用DATEADD和DATEDIFF函数计算日期时给你一点启发。通过
使用这个计算日期的时间间隔的数学方法,我发现为了显示两个日期之间间隔的有用历法是
有价值的。注意,这只是计算出这些日期的一种方法。要牢记,还有很多方法可以得到相同
的计算结果。假如你有其他的方法,那很不错,要是你没有,我希望这些例子可以给你一些
启发,当你要用DATEADD和DATEDIFF函数计算你程序可能要用到的日期时。
---------------------------------------------------------------
附录,其他日期处理方法
1)去掉时分秒
declare @ datetime
set @ = getdate() --'2003-7-1 10:00:00'
SELECT @,DATEADD(day, DATEDIFF(day,0,@), 0)
2)显示星期几
select datename(weekday,getdate())
3)如何取得某个月的天数
declare @m int
set @m=2 --月份
select datediff(day,'2003-'+cast(@m as varchar)+'-15' ,'2003-'+cast(@m+1
as varchar)+'-15')
另外,取得本月天数
select datediff(day,cast(month(GetDate()) as varchar)+'-'+cast(month
(GetDate()) as varchar)+'-15' ,cast(month(GetDate()) as varchar)+'-'+cast
(month(GetDate())+1 as varchar)+'-15')
或者使用计算本月的最后一天的脚本,然后用DAY函数区最后一天
SELECT Day(dateadd(ms,-3,DATEADD(mm, DATEDIFF(m,0,getdate())+1, 0)))
4)判断是否闰年:
SELECT case day(dateadd(mm, 2, dateadd(ms,-3,DATEADD(yy, DATEDIFF
(yy,0,getdate()), 0)))) when 28 then '平年' else '闰年' end
或者
select case datediff(day,datename(year,getdate())+'-02-01',dateadd
(mm,1,datename(year,getdate())+'-02-01'))
when 28 then '平年' else '闰年' end
5)一个季度多少天
declare @m tinyint,@time smalldatetime
select @m=month(getdate())
select @m=case when @m between 1 and 3 then 1
when @m between 4 and 6 then 4
when @m between 7 and 9 then 7
else 10 end
select @time=datename(year,getdate())+'-'+convert(varchar(10),@m)+'-01'
select datediff(day,@time,dateadd(mm,3,@time))
--------------
如何删除表中的重复记录?
---------------------------------------------------------------
select distinct * into #temp from tab
delete tab
insert tab select * from #temp
drop table #temp
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。