Friday, April 24, 2009

即将完成毕业论文了,写论文过程中在sourceforge上发现了jlibrary,是一个面向个人和企业使用的文档管理系统CMS,很不错的RCP 的开源学习的东东,有时间要深入的研究一下,写完paper后准备开始进入一直以来感兴趣的项目-JBOSS 源码分析,这是一项长期的过程,去年在linux上转了转jboss 4.1,看了看jboss aop ,但没怎么深入。希望这回毕业了即将工作了,可以利用业余时间专心的也可以为开源运动做些贡献。一切从0 开始! 坚持更新jboss的学习笔记

Saturday, April 18, 2009

oracle portal

Oracle Portal及其门户网站开发概述
导读-- 本文首先介绍了Oracle Portal的定义、特点,接着阐述了portal的体系结构
  摘要: Portal是IT领域的新技术,是企业信息化工作的发展方向之一。本文首先介绍了Oracle Portal的定义、特点,接着阐述了portal的体系结构。随后本文介绍了Oracle9iAS Portal的功能特点及基于它的企业门户网站的开发。最后本文简要探讨了基于portal的门户网站开发的问题。

  关键词: oracle portal,门户,网站,信息入口,企业入口

  引言

  Oracle Portal为企业提供了一个单一的访问企业各种信息资源的入口,利用它,企业可以为员工、供应商、零售商、合作伙伴等提供个性化的信息服务,使他们能通过单一的入口,迅速找到所需的信息。Portal具有众多的核心功能特性,将企业的应用、人员、信息与流程有机地结合起来,为信息化工作的开展提供了可行的思路和解决方案。

  Oracle Portal概述

  1、Oracle Portal是什么

  Portal一词是从Internet所衍生出来的,最初的“门户”一词主要是指信息门户,诸如雅虎、新浪等网站都是一种信息门户。随后,门户的应用领域又得到扩展,出现了企业信息门户(Enterprise Information Portal,EIP)。Oracle Portal是一个建立企业信息门户的集成环境。通过Oracle Portal,企业员工可以很方便地将自己所需要的,来源于各种渠道的信息集成在一个统一的视图之内。例如,在传统企业信息系统环境下,一个财务部门可能要接触这样一些信息源:企业财务软件,企业内部网站的政策、新闻、公告,各种图表、报表,互联网上的财经新闻,股票行情等等;通过Oracle Portal提供的“自助式”的服务,财务部门可以为自己部门量身定制一套财务人员的信息门户,将上述信息有效地组织在Web应用程序之中,并根据不同级别人员的职能设定相应的访问权限。在以前,这可能需要向IT部门提交详细的需求分析,并等待好几个月才能投入使用;通过Oracle Portal提供的快速、易用的开发工具和内建功能模块,非IT人员也可以根据自己的实际业务需求,创建这样的集成化Web应用了。

  2、Oracle Portal的体系结构

  1) Oracle Portal节点

  每台安装Oracle Portal示例的服务器称为一个Oracle Portal节点。每个OraclePortal节点包含有超过200个表及视图,以及超过1000个PL/SQL包。Oracle Portal节点之间可以相互注册,形成一个联合Portal结构。节点的注册和管理可以很容易地由企业的中心Portal管理。Portal容器是存储Oracle Portal对象的地方。它保存着Provider注册表,页面的样式定义,用户对Portlet的定制,访问权限,内容区域及页面的定义,数据驱动的组件等等。数据库Provider管理基于数据库的PL/SQL或Java存储过程形式的Portlet,它存储在Portal节点之外。Portal框架通过数据库Provider调用这些Portlet,并将返回的HTML/XML形式的结果显示在页面的某个区域中。与数据库Provider不同的是,Web Provider面向的是来自Web的数据,它们通常是通过HTTP协议而不是数据库存储过程来调用的,并且返回的结果已经是格式化的HTML/XML内容。

  2) Oracle Logon/SSO Server

  SSO是Single Sign-On的缩写,即单一登录模式。SSO服务器集中管理用户的身份验证和权限管理,这使得其它应用程序可以将这些任务交给SSO服务器统一完成,也使得用户只需要在SSO服务器上登陆一次就可以使用所有相关联的应用程序,而无需记忆多个帐户密码。

  3)Oralcle 9i Application Server

  Oracle 9iAS是Oracle Portal的中间层应用程序服务器。在Oracle9iAS中,Oracle HTTP Server提供主要的HTTP服务,Oracle Jserv提供JSP和servlet服务,modPL/SQL以Apache模块的模式提供从Web访问Oracle数据库的服务。

  4)Oracle Portal Cache

  Oracle Portal Cache使用基于文件系统的存储系统,为页面定义和portlet生成的内容提供缓存,使用缓存系统,Oracle Portal无需每次生成页面的时候都访问对象存储系统或调用所有portlet,这样大大提高了页面的响应时间。

  Oracle9iAS Portal概述

  1、Oracle9ias Portal的功能

  Oracle9iAS Portal是1999年初作为Oracle WebDB一部分引入的技术的下一代。Oracle9iAS Portal是Oracle9i的关键组件,作为一项服务集成到Oracle9i应用服务器中。有了Oracle9iAS Portal,企业就拥有了一种强有力的手段,可以向员工、合作伙伴和供应商提供一致、高效的访问途径,使他们能够访问更有效地完成日常业务任务所需的企业信息和工具。Oracle9iAS Portal通过提供如下功能实现了上述目标。

  1) 单点交互功能

  Oracle9iAS Portal的可扩展的门户框架提供了集成能力和对企业信息的标准访问途径,同时,灵活的工作环境使各机构、部门和个人用户能够对他们的门户风格进行个性化定制。Oracle9iAS Portal作为单一交互点,通过把信息集中在一起来解决这些问题:把用户与他们的信息源更好地连接在一起,同时提供了适应不同个人开展工作与处理信息的方式所需的灵活性。Oracle9iAS Portal作为真正企业级的电子商务门户,用称为portlet的可重复使用的信息组件,在彼此互不相连的动态数据、文档和Web站点的世界中架起了沟通的桥梁。

  2) 集成的成套门户服务

  Oracle9iAS Portal的自助式门户服务使用户和开发人员能够组织与发布信息并创建应用。除了能访问企业数据并对其进行个性化处理外,Oracle9iAS Portal还并入了若干自助式功能,允许门户用户和管理员直接地管理他们的信息。这些服务能够让最终用户控制并负责他们的信息,也向IT专业人员提供了更好地满足最终用户的需求工具。Oracle9iAS Portal还为文档发布、文件上传、页面格式化以及访问控制提供了一套集成功能,可以让用户共享和管理信息。除了文件类型内容外,来自Oracle数据库的信息也能够很容易地集成到Oracle9iAS Portal之中。Oracle9iAS Portal包含有一个数据驱动的组件库,可以用于与Oracle数据库地交互,并把信息无缝地嵌入门户页面内的portlet之中。这些组件包括Javascript增强型地数据输入表单、以HTML、纯文本或MS Excel格式输出的分栏报告、可定制HTML条形图以及其他几个用于发布Oracle数据库中数据的组件。

  3)完整的部署和管理环境

  Oracle9iAS Portal借助简单性而赋予用户力量,因为它基于互联网的开放标准。它不要求用户安装和学习大型复杂的程序,不让用户接触这些不必要的复杂性,而是通过管理基础性的Oracle8i数据库中的内容,而对Oracle9iAS Portal进行集中维护。Oracle9iAS Portal的成熟的三层体系结构支持多种多样的配置,并可扩展以满足复杂的要求。Oracle9iAS具备灵活的部署模式,支持在类型广泛的硬件和操作系统上的多种配置形式,从而产生了高度可伸缩的性能。Oracle9iAS Portal支持向全球社会部署。包含在Oracle9iAS Portal的向导、对话框、消息和帮助内容中的文本已经翻译成20多种语言,以满足不同用户的需求。

  2、基于Oracle9iAS的门户网站的开发

  为满足一般企业的业务需求和技术需求,可以构建它的集成信息咨询门户。该解决方案分为4层结构:客户层、中间层、基础架构层,数据层。

  1)客户层:我们采用浏览器作为客户端的显示工具,使最终用户无需安装任何客户端工具,即可在任何时间任何地点在任何系统平台上访企业的门户平台。

  2)中间层:中间层是标准的基于Apache的Oracle9iAS安装;Oracle Portal直接操纵mod_PL/SQL和mod_OC4J,中间层包括三个部分,Oracle门户,网络提供者(Web Provider)和商务智能。Oracle门户包括HTTP服务器,用以处理Oracle前端的页面请求并负责应答;并行页面引擎,是一个servlet引擎,与Web和数据库提供者通讯生成相应的portlet内容并负责页面装配,也与缓存页面定义和portlet内容的“本地”文件系统交互;可配置多个线程来实现与提供者之间的并行通讯;mod_PL/SQL用于处理PL/SQL的应用模块。 Web-Cache在HTTP服务器上的应用能提高整个Oracle Portal的效率。运用9iAS强大的商务智能分析数据,可提供根据客户需求所定制的报表。Web内容提供者可工作在各种Web环境,通过HTTP通讯并返回HTML/XML格式的结果;通过SOAP XML进行数据交换;适用于现有的和远程的Web服务,并支持订阅模式。

  3)基础构架层:提供单一登陆服务,对整个系统进行单一的安全管理;提供访问后台数据库的统一连接;还包括Oracle的集成和工作流服务,用以连接各种异构平台和系统。

  4)数据层:包括所有为Portal提供数据的客户服务器,内部网站系统和外部网站。

  4、门户网站开发存在的问题

  以Oracle9iAS 为基础,为企业提供快速建立企业入口网站的方案,让企业无需购买、安装软件,无需担心额外增加MIS人员等负担,只要嵌入Oracle网站,即可享受Oracle提供的基础构架,并在网站上勾选所需的功能以组建自己的网站。Web界面容易上手的特性,使得Intranet成为企业沟通、管理的重要工具。这也使得企业门户网站的建设逐渐变成一种必然。然而纵观国内多数企业开设的入口网站,目前仍处于低级阶段,尚未发挥应有的功效,而软件商提出的各种解决方案又令人目不暇接,难以抉择。总的来说,企业门户网站建设存在以下几个问题:1)企业的门户网站有建成大杂烩的趋势;2)企业的文化障碍凌驾于技术之上,阻止了企业门户网站的继续开发和投入使用;3)门户网站内容的完成仅仅是个开始,还有很多其它技术任务有待完成。如何解决上述三个问题无疑是当今企业门户网站建设成功与否的关键。

  结束语

  Oracle9iAS可以用来快速部署和开发个性化门户网站,而它包括的Oracle9iAS Portal具有易用的Web界面,使用户能够有条理的对商业信息、网页内容和应用进行个性化的浏览。使用Oracle的门户解决方案,简单实现门户单点信息访问,将使您的企业门户网站成为一种全新的电子商务桌面,去迎接更为激烈的挑战!

Saturday, March 28, 2009

介绍Linux内核是如何工作的

本文发表于Linux Format magazine杂志,作者从技术深度上解释了Linux Kernel是如何工作的。相信对Linux开发者来说有不小的帮助。
http://news.csdn.net/n/20090327/124513.html

Friday, March 27, 2009



TIOBE Programming Community Index for March 2009
March Headline: All time high for JavaScript, all time low for Perl
The TIOBE Programming Community index gives an indication of the popularity of programming languages. The index is updated once a month. The ratings are based on the number of skilled engineers world-wide, courses and third party vendors. The popular search engines Google, MSN, Yahoo!, and YouTube are used to calculate the ratings. Observe that the TIOBE index is not about the best programming language or the language in which most lines of code have been written.

The index can be used to check whether your programming skills are still up to date or to make a strategic decision about what programming language should be adopted when starting to build a new software system. The definition of the TIOBE index can be found here.
http://www.tiobe.com/index.php/content/paperinfo/tpci/index.html

GT problems

List of problems
1.find shortest distance between two nodes in non-negatively weighted directed graph
2.find topological ordering of the nodes
3.find max flow in flow network
4.maximum matching in unweighted graph
5.matching in weighted bipartite graphs (an assignment problem )
6.find distances between all pairs of nodes in non-weighted directed graph
7.find minimum spanning tree for a connected weighted graph
8.test graph for bipartiteness
9.vertex cover problem

如何投资股票之三大建议

杨百万:如何投资股票之三大建议

  广大股民期待已久的“杨百万炒股秘笈报告会”昨天下午在煤海大剧院成功举行。为时两个多小时的报告会中,被誉为“中国第一股民”的杨百万热情洋溢,妙语连珠,与1500余名热情股民分享了他投身股市20年的独特理念和成功经验。报告会受到了广大股民的热烈追捧,取得了圆满成功。

  ■经典语录

  1、股市政府主导,不可逆市而为。

  2、股市是放大人性弱点的放大镜。

  3、抄底逃顶,落袋为安。

  4、指数在高位时,把套牢卖给别人,指数在低位时,把盈利留给自己。

  5、低位投资,高位投机。

  6、炒股要克服人性的两大弱点:贪婪与恐惧。

  7、花不完的钱,再多也不要挣。

  8、能使新股民相信的谎言就是概念。

  ■“谈股论金”之精彩报告

  “炒股要克服人性的两大弱点:贪婪与恐惧,要坚持落袋为安”

  报告会上,杨百万感慨道:“人民币很有力量,可是有多少人把握得住?”从5·30的股市大跌到现在,大盘虽然已经冲上了5000点,可还有很多人的损失在30%左右,究其原因是广大股民的贪婪和畏惧,不能够做到顺势而为。

  结合自己多年来亲历股票市场的风云变幻,杨百万感叹:“在股票市场上,人性的弱点被最大限度地放大,在追逐财富的过程中,贪婪和恐惧让很多散户最终败走股市。很多股民看到股票涨了,就盲目进场,获利了也没有及时将资金落袋为安,最终被套在了高位。而当股票跌到了低位时,很多人又不敢买进,有些人甚至因为耐不住套牢多日,而选择割肉,结果错过了股票的高位。”

  杨百万预言:股市行情将在一个半月内出现震荡

  “5·30”之前,杨百万综合分析各方面的信息,结合自己的经验,判断出股市将要出现大幅调整,并早早清仓。他的判断曾经被发表在媒体上,但并没有得到热烈回应。最后,事实证明了他当初判断的正确性。

  在昨天的报告会上,杨百万再次大胆预言,在未来一个半月左右的时间里,中国的股市还将经历一场大的调整,因为股市已经进入了一个高风险的领域,国家也已经采取一些措施抑制股市的过快增长。为此,他建议广大投资者尽量回避高位风险,短线投资或波段交易才是理智的。

  如何投资股票,杨百万给出三点建议:

  一是选对时机:

  职业投资者区别于普通投资者的最大之处在于,他们往往能从变化莫测的股市交易细微处,洞察先机。而他们之所以能看出盘中数字变化传递的信息,是一种经验的积累,亦即股市经历。

  杨百万提出,看盘主要应着眼于股指及个股未来趋向的判断,大盘的研判一般从以下3方面来考虑:股指与个股方面选择的研判;盘面股指(走弱或走强)的背后隐性信息;掌握市场节奏,高抛低吸,降低持仓成本。尤其要对个股研判认真落实。

  二是选对股票:

  好股票如何识别?杨百万建议股民可以从以下几个方面进行:

  (1)买入量较小,卖出量特大,股价不下跌的股票。

  (2)买入量、卖出量均小,股价轻微上涨的股票。

  (3)放量突破趋势线(均线)的股票。

  (4)头天放巨量上涨,次日仍然放量强势上涨的股票。

  (5)大盘横盘时微涨,以及大盘下跌或回调时加强涨势的股票。

  (6)遇个股利空,放量不下跌的股票。

  (7)有规律且长时间小幅上涨的股票。

  (8)无量大幅急跌的股票(指在技术调整范围内)。

  (9)送红股除权后又涨的股票。

  三是选对周期:

  股民可根据自己的资金规模、投资喜好,选择股票的投资周期。

  在报告会上,杨百万还结合个人的投资理念和经验,就某些具体股票的表现和大盘走势进行了阐释。

  ■“谈股论金”之会场之外

  杨百万受聘,任晚报“财富管家”顾问

  此次应晚报邀请,来徐州举行“炒股秘笈报告会”,杨百万自言说是“故地重游”,原来早在20多年前,杨百万经常往来徐州和上海之间,在徐州的老天桥进行国库券的交易。杨百万说,正是这样的交易,让当年他有了进入股市成为“杨百万”的资本。

  在报告会举行之前,杨百万还应邀被本报聘为“财富管家” 顾问,杨百万愉快地接受了这一聘任,并感谢晚报为他提供了这样一次与徐州广大股民分享的机会。他热情地表示:“作为一名炒股20余年的老股民,我非常乐意把我的操作理念和经验与广大股民分享。”

  ■“谈股论金”之热情股民

  昔日学生现场送锦旗

  报告会现场,市民苗先生和宋女士带着一幅写着“股海迷津指点、市值连年翻番”的锦旗,准备送给杨百万,这一场景受到了广大股民的关注。

  记者了解到,原来苗先生和宋女士曾经是杨百万的“学生”,到上海跟着杨百万学过炒股票,而且收益不错。“我曾数次到杨老师家里听他的课,第一次是2000年3月28日,听了一天,第二次听了两天,主要是专门学习抄底、逃顶、如何盯庄等。”

  苗先生讲起来一脸的兴奋,“现在杨老师闭着眼睛,就能说出股市的涨停线,他自己做了一套软件,我跟他学了7年,炒股炒了10年,现在已经翻了两三番了。”

  股民排队热抢杨百万新书

  此次来徐,杨百万还携带自己的新书《要做股市赢家——杨百万股经奉献》,来回馈徐州的广大股票爱好者。看到杨百万带来的新书,股民们立即蜂拥而上,不到十分钟,先行运到的上百本书抢购一空,有的股民甚至一口气买了好几本,准备回去赠送给股友。而买到书的读者纷纷追出会场近百米找杨百万签字。

  ■“谈股论金”之股民心得

  纵先生(园林局退休人员):“选准时机非常重要,另外呢,我也用过杨老师的软件,软件挺好的,但也不是全部跟着软件走,杨老师也说了,没有人能清楚知道未来股市能长到几点,要综合考虑各种因素。”

  张女士(投资股票16年):“我觉得,现在理财已经深入人心。这种报告会使理财理念扩大化,不只是炒股这一个方面。我身边的朋友投资证券的特别多,他们都非常依赖杨百万。”

  王先生(家住祥和小区):“炒股看大势,‘跟着政策走’,人民币增值的大势在,我们就敢炒。杨老师讲的是他的实战经验,挺好,所以我准备买他的书回去研究一下。另处,我个人观点,中国最大的股市市场在年轻人,年轻人呢,现在种下一颗苗,将来的大树自己乘凉。”

Xi Dan Girl

Wednesday, March 25, 2009

Maternal Love-Earthquake


The Best Work In the Fifth International Press Photo Contest

拓扑排序

拓扑排序
本节说明了如何用深度优先搜索,对一个有向无回路图进行拓扑排序。有向无回路图又称为dag。对这种有向无回路图的拓扑排序的结果为该图所有顶点的一个线性序列,满足如果G包含(u,v),则在序列中u出现在v之前(如果图是有回路的就不可能存在这样的线性序列)。一个图的拓扑排序可以看成是图的所有顶点沿水平线排成的一个序列,使得所有的有向边均从左指向右。因此,拓扑排序不同于通常意义上对于线性表的排序
有向无回路图经常用于说明事件发生的先后次序,图1给出一个实例说明早晨穿衣的过程。必须先穿某一衣物才能再穿其他衣物(如先穿袜子后穿鞋),也有一些衣物可以按任意次序穿戴(如袜子和短裤)。图1(a)所示的图中的有向边(u,v)表明衣服u必须先于衣服v穿戴。因此该图的拓扑排序给出了一个穿衣的顺序。每个顶点旁标的是发现时刻与完成时刻。图1(b)说明对该图进行拓扑排序后将沿水平线方向形成一个顶点序列,使得图中所有有向边均从左指向右。
下列简单算法可以对一个有向无回路图进行拓扑排序。procedure Topological_Sort(G);
begin
1.调用DFS(G)计算每个顶点的完成时间f[v];
2.当每个顶点完成后,把它插入链表前端;
3.返回由顶点组成的链表;
end;
图1(b)说明经拓扑排序的结点以与其完成时刻相反的顺序出现。因为深度优先搜索的运行时间为θ(V+E),每一个v中结点插入链表需占用的时间为θ(1),因此进行拓扑排序的运行时间θ(V+E)。

图1 早晨穿衣的过程
为了证明算法的正确性,我们运用了下面有关有向无回路图的重要引理。
引理1
有向图G无回路当且仅当对G进行深度优先搜索没有得到反向边。
证明:
→:假设有一条反向边(u,v),那么在深度优先森林中结点v必为结点u的祖先,因此G中从v到u必存在一通路,这一通路和边(u,v)构成一个回路。
←:假设G中包含一回路C,我们证明对G的深度优先搜索将产生一条反向边。设v是回路C中第一个被发现的结点且边(u,v)是C中的优先边,在时刻d[v]从v到u存在一条由白色结点组成的通路,根据白色路径定理可知在深度优先森林中结点u必是结点v的后裔,因而(u,v)是一条反向边。(证毕)
定理1
Topological_Sort(G)算法可产生有向无回路图G的拓扑排序。
证明:
假设对一已知有问无回路图G=(V,E)运行过程DFS以确定其结点的完成时刻。那么只要证明对任一对不同结点u,v∈V,若G中存在一条从u到v的有向边,则f[v]另一种拓扑排序的算法基于以下思想:首先选择一个无前驱的顶点(即入度为0的顶点,图中至少应有一个这样的顶点,否则肯定存在回路),然后从图中移去该顶点以及由他发出的所有有向边,如果图中还存在无前驱的顶点,则重复上述操作,直到操作无法进行。如果图不为空,说明图中存在回路,无法进行拓扑排序;否则移出的顶点的顺序就是对该图的一个拓扑排序。
下面是该算法的具体实现:procedure Topological_Sort_II(G);
begin
1 for 每个顶点u∈V[G] do d[u]←0; //初始化d[u],d[u]用来记录顶点u的入度
2 for 每个顶点u∈V[G] do
3 for 每个顶点v∈Adj[u] do d[v]←d[v]+1; //统计每个顶点的入度
4 CreateStack(s); //建立一个堆栈s
5 for 每个顶点u∈V[G] do
6 if d[u]=0 then push(u,s); //将度为0的顶点压入堆栈
7 count←0;
8 while (not Empty(s)) do
begin
9 u←top(s); //取出栈顶元素
10 pop(s); //弹出一个栈顶元素
11 count←count+1;
12 R[count]←u; //线性表R用来记录拓扑排序的结果
13 for 每个顶点v∈Adj[u] do //对于每个和u相邻的节点v
begin
14 d[v]←d[v]-1;
15 if d[v]=0 then push(v,s); //如果出现入度为0的顶点将其压入栈
end;
end;

16 if count<>G.size then writeln('Error! The graph has cycle.')
17 else 按次序输出R;
end;
上面的算法中利用d[u]来记录顶点u的入度,第2-3行用来统计所有顶点的入度,第5-6行将入度为0的顶点压入堆栈,第8-15行不断地从栈顶取出顶点,将该顶点输出到拓扑序列中,并将所有与该顶点相邻的顶点的入度减1,如果某个顶点的入度减至0,则压入堆栈,重复该过程直到堆栈空了为止。显而易见该算法的复杂度为O(VE),因为第2-3行的复杂性就是O(VE),后面8-15行的复杂性也是O(VE)。这个算法虽然简单,但是没有前面一个算法的效率高。
上一页 返回目录 返回页首 下一页
本页最后一次更新于03/26/2009email: starfish@vip.sina.com©2000 算法与数据结构 http://algorithm.126.com/ 版权所有 转载请保留出处

SQL 大全

SQL语法大全 本章内容SQL介绍使用SELECT 语句从表中取数据创建新表字段属性 向表中添加数据删除和修改表 为了建立交互站点,你需要使用数据库来存储来自访问者的信息。例如,你要建立一个职业介绍服务的站点,你就需要存储诸如个人简历,所感兴趣的工作等等这样的信息。创建动态网叶也需要使用数据库,如果你想显示符合来访者要求的最好的工作,你就需要从数据库中取出这份工作的信息。你将会发现,在许多情况下需要使用数据库。在这一章里,你将学会怎样使用“结构化查询语言”(SQL〕来操作数据库。SQL语言是数据库的标准语言。在Active Sever Pages 中,无论何时你要访问一个数据库,你就要使用SQL语言。因此,掌握好SQL对ASP编程是非常重要的。注意:你可以把“SQL”读作“sequel”,也可以按单个字母的读音读作S-Q-L。 两种发音都是正确的,每种发音各有大量的支持者。在本书里,认为“SQL”读作“sequel”。通过这一章的学习,你将理解怎样用SQL实现数据库查询,你将学会怎样使用这种查询从数据表中取出信息,最后,你将学会怎样设计和建立自己的数据库。注意:通过下面几章对SQL的介绍,你将对SQL有足够的了解,从而可以有效地使用Active Sever Pages。但是,SQL是一种复杂的语言,本书不可能包括它的全部细节。要全面掌握SQL语言,你需要学习在Microsoft SQL Sever 中使用SQL。你可以到附近的书店去买一本Microsoft SQL Sever 6.5。SQL介绍:本书假设你是在SQL操作Microsoft SQL Sever 的数据库。你也可以用SQL操作许多其它类型的数据库。SQL是操作数据库的标准语言。(事实上,关于SQL语言有一个专门的ANSI标准〕注意:不要在你的站点上试图用Microsoft Access代替Microsoft SQL Sever。SQL Sever可以同时服务于许多用户,如果你希望你的站点有较高的访问率,MS Access是不能胜任的。在学习SQL的细节之前,你需要理解它的两大特点。一个特点容易掌握,另一个掌握起来有点困难。第一个特点是所有SQL数据库中的数据都存储在表中。一个表由行和列组成。例如,下面这个简单的表包括name 和e-mail address:Name Email Address................................................................Bill Gates billg@microsoft.compresident Clinton president@whitehouse.comStephen Walther swalther@somewhere.com这个表有两列(列也称为字段,域〕:Name和Email Address。有三行,每一行包含一组数据。一行中的数据组合在一起称为一条记录。无论何时你向表中添加新数据,你就添加了一条新记录。一个数据表可以有几十个记录,也可以有几千甚至几十亿个记录。虽然你也许永远不需要存储十亿个Email地址,但知道你能这样做总是好的,也许有一天你会有这样的需要。你的数据库很有可能包含几十个表,所有存储在你数据库中的信息都被存储在这些表中。当你考虑怎样把信息存储在数据库中时,你应该考虑怎样把它们存储在表中。SQL的第二个特点有些难于掌握。这种语言被设计为不允许你按照某种特定的顺序来取出记录,因为这样做会降低SQL Sever取记录的效率。使用SQL,你只能按查询条件来读取记录。当考虑如何从表中取出记录时,自然会想到按记录的位置读取它们。例如,也许你会尝试通过一个循环,逐个记录地扫描,来选出特定的记录。在使用SQL时,你必须训练自己,不要有这种思路。假如你想选出所有的名字是“Bill Gates”的记录,如果使用传统的编程语言,你也许会构造一个循环,逐个查看表中的记录,看名字域是否是“Bill Gates”。这种选择记录的方法是可行的,但是效率不高。使用SQL,你只要说,“选择所有名字域等于Bill Gates的记录”,SQL就会为你选出所有符合条件的记录。SQL会确定实现查询的最佳方法。建设你想取出表中的前十个记录。使用传统的编程语言,你可以做一个循环,取出前十个记录后结束循环。但使用标准的SQL查询,这是不可能实现的。从SQL的角度来说,在一个表中不存在前十个记录这种概念。开始时,当你知道你不能用SQL实现某些你感觉应该能实现的功能,你会受到挫折。你也许会以头撞墙甚至想写恶毒的信件给SQL的设计者们。但后来你会认识到,SQL的这个特点不仅不是个限制,反而是其长处。因为SQL不根据位置来读取记录,它读取记录可以很快。综上所述,SQL有两个特点:所有数据存储在表中,从SQL的角度来说,表中的记录没有顺序。在下一节,你将学会怎样用SQL从表中选择特殊的记录。使用SQL从表中取记录。SQL的主要功能之一是实现数据库查询。如果你熟悉Internet 引擎,那么你已经熟悉查询了。你使用查询来取得满足特定条件的信息。例如,如果你想找到有ASP信息的全部站点,你可以连接到 Yahoo!并执行一个对Active Sever Pages的搜索。在你输入这个查询后,你会收到一个列表,表中包括所有其描述中包含搜索表达式的站点。多数Internet 引擎允许逻辑查询。在逻辑查询中,你可以包括特殊的运算符如AND、OR和NOT,你使用这些运算符来选择特定的记录。例如,你可以用AND来限制查询结果。如果你执行一个对Active Sever Pages AND SQL的搜索。你将得到其描述中同时包含Active Sever Pages 和SQL的记录。当你需要限制查询结果时,你可以使用AND。如果你需要扩展查询的结果,你可以使用逻辑操作符OR。例如,如果你执行一个搜索,搜索所有的其描述中包含Active Sever Pages OR SQL的站点,你收到的列表中将包括所有其描述中同时包含两个表达式或其中任何一个表达式的站点。如果你想从搜索结果中排除特定的站点,你可以使用NOT。例如,查询“Active Sever Pages ”AND NOT “SQL”将返回一个列表,列表中的站点包含Active Sever Pages,但不包含SQL。当必须排除特定的记录时,你可以使用NOT。用SQL执行的查询与用Internet搜索引擎执行的搜索非常相似。 当你执行一个SQL查询时,通过使用包括逻辑运算符的查询条件,你可以得到一个记录列表。此时查询结果是来自一个或多个表。SQL查询的句法非常简单。假设有一个名为email_table 的表,包含名字和地址两个字段,要得到Bill Gates 的e_mail地址,你可以使用下面的查询:SELECT email from email_table WHERE name="Bill Gates"当这个查询执行时,就从名为email_table的表中读取Bill Gates的e_mail 地址。这个简单的语句包括三部分:■ SELECT语句的第一部分指名要选取的列。在此例中,只有email列被选取。当执行 时,只显示email列的值 billg@microsoft.com。■ SELECTT语句的第二部份指明要从哪个(些)表中查询数据。在此例中,要查询的表名为email_table 。■ 最后,SELECT语句的WHERE子句指明要选择满足什么条件的记录。在此例中,查询条件为只有name列的值为Bill Gates 的记录才被选取。Bill Gates很有可能拥有不止一个email地址。如果表中包含Bill Gates的多个email地址。用上述的SELECT语句可以读取他所有的email地址。SELECT语句从表中取出所有name字段值为Bill Gates 的记录的email 字段的值。前面说过,查询可以在查询条件中包含逻辑运算符。假如你想读取Bill Gates 或Clinton总统的所有email地址,你可以使用下面的查询语句:SELECT email FROM email_table WHERE name="Bill Gates" OR name="president Clinton"此例中的查询条件比前一个复杂了一点。这个语句从表email_table中选出所有name列为Bill Gates或president Clinton的记录。如果表中含有Bill Gates或president Clinton的多个地址,所有的地址都被读取。SELECT语句的结构看起来很直观。如果你请一个朋友从一个表中为你选择一组记录,你也许以非常相似的方式提出你的要求。在SQL SELECT语句中,你“SELECT特定的列FROM一个表WHERE某些列满足一个特定的条件”。下一节将介绍怎样执行SQL查询来选取记录。这将帮助你熟悉用SELECT语句从表中取数据的各种不同方法。使用ISQL执行SELECT查询当你安装SQL Sever时,你同时安装了一个叫作ISQL/w的应用程序。ISQL/w允许你执行交互的SQL查询。在把查询包括到你的ASP网页中之前,用ISQL/w对其进行测试是非常有用的。注意:在这本书的第一部份,你学习了怎样安装和配置Microsoft SQL Sever 。如果没有安装SQL Sever或者SQL Sever不能运行,请参阅第三章“安装和使用SQL Sever”。选择任务上SQL Sever程序组中的ISQL_w以启动该程序。程序启动时,首先会出现一个对话框,要求输入服务器信息和登录信息(见图10.1)。在Sever框中,输入你的SQL服务器的名字。如果服务器正运行在本地计算机上,服务器名字就是你计算机的名字。在登录信息框中,输入一个登录帐号和密码或选择使用“可信连接”,然后单击Connect按钮。 图10。1注意:如果你将SQL Sever配置为使用完整安全或混合安全,那么你可以使用可信连接。如果你使用标准安全,你则需要提供用户帐号和密码。要了解更多信息,参见第三章。如果一切正常,在你单击连接按钮后会出现一个查询窗口,如图10.2所示。(如果有异常,请参考第三章) 图10.2在执行查询之前,你需要选择数据库。安装 SQL Sever时你已为自己创建了一个数据库,SQL Sever还有许多系统数据库,如master,model,msdb,和tempdb。方便的是,SQL Sever带有一个特殊的名为pubs的例子数据库。库 pubs中包含供一个虚拟的出版商使用的各个表。文档中所有的例子程序都是针对这个库来设计的。本书中的许多例子也使用这个数据库。在查询窗口顶部的DB下拉框中选择数据库pubs,这样你就选择了数据库。你所有的查询都将针对这个库中的各个表来执行。现在你可以执行你的第一个查询了。这真让人兴奋!你的第一个查询将针对一个名为autrors的表,表中包含所有为某个虚拟出版商工作的作者的相关数据。单击查询窗口并输入以下的语句:SELECT phone FROM authors WHERE au_name="Ringer"输入完成后,单击执行查询按钮(一个绿色三角形,看起来像VCR播放键)。单击此按钮后,任何出现在查询窗口中的语句均会被执行。查询窗口会自动变成结果显示窗口,你可以看到查询的结果(见图10.3)。你看到的查询结果也许与图10.3所示的不同。在SQL Sever的不同版本中,库pubs中的数据会有所不同。对SQL Sever 6.5来说,将会找到两条记录。结果显示窗口中应显示如下内容:phone……………….801 826_0752801 826_0752(2 row(s) affected)图10.3你所执行的SELECT语句从表authors中取出所有名字为Ringer的作者的电话号码。你通过在WHERE子句中使用特殊的选择条件来限制查询的结果。你也可以忽略选择条件,从表中取出所有作者的电话号码。要做到这一点,单击Query标签,返回到查询窗口,输入以下的SELECT语句:SELECT Phone FROM authors这个查询执行后,会取出表authors中的所有电话号码(没有特定的顺序)。如果表authors中包含一百个电话号码,会有一百个记录被取出,如果表中有十亿个电话号码,这十亿条记录都会被取出(这也许需要一些时间)。表authrs的字段包括姓,名字,电话号码,地址,城市,州和邮政编码。通过在SELECT语句的第一部份指定它们,你可以从表中取出任何一个字段。你可以在一个SELECT语句中一次取出多个字段,比如:SELECT au_fname ,au_lname, phone FROM authors这个SELECT语句执行后,将取出这三个列的所有值。下面是这个查询的结果的一个示例(为了节省纸张,只显示查询结果的一部分,其余记录用省略号代替):au_fname au_lname phone ………………………………………………………………………….Johnson White 408 496_7223Marjorie Green 415 986_7020Cheryl Carson 415 548_7723Michael O’Leary 408 286_2428… (23 row(s) affected)在SELECT语句中,你需要列出多少个字段,你就可以列出多少。不要忘了把字段名用逗号隔开。你也可以用星号(*)从一个表中取出所有的字段。这里有一个使用星号的例子:SELECT * FROM authors这个SELECT语句执行后,表中的所有字段的值都被取出。你会发现你将在SQL查询中频繁使用星号。技巧:你可以使用星号来查看一个表的所有列的名字。要做到这一点,只需要在执行完SELECT语句后看一下查询结果的列标题。操作多个表到现在为止,你只尝试了用一句SQL查询从一个表中取出数据。你也可以用一个SELECT语句同时从多个表中取出数据,只需在SELECT语句的FROM从句中列出要从中取出数据的表名称即可:SELECT au_lname ,title FROM authors, titles 这个SELECT语句执行时,同时从表authors和表titles中取出数据。从表authors中取出所有的作者名字,从表titles中取出所有的书名。在ISQL/w程序中执行这个查询,看一下查询结果。你会发现一些奇怪的出乎意料的情况:作者的名字并没有和它们所著的书相匹配,而是出现了作者名字和书名的所有可能的组合,这也许不是你所希望见到的。出了什么差错?问题在于你没有指明这两个表之间的关系。你没有通过任何方式告诉SQL如何把表和表关联在一起。由于不知道如何关联两个表,服务器只能简单地返回取自两个表中的记录的所有可能组合。要从两个表中选出有意义的记录组合,你需要通过建立两表中字段的关系来关联两个表。要做到这一点的途径之一是创建第三个表,专门用来描述另外两个表的字段之间的关系。表authors有一个名为au_id的字段,包含有每个作者的唯一标识。表titles有一个名为title_id的字段,包含每个书名的唯一标识。如果你能在字段au_id和字段title_id 之间建立一个关系,你就可以关联这两个表。数据库pubs中有一个名为titleauthor的表,正是用来完成这个工作。表中的每个记录包括两个字段,用来把表titles和表authors关联在一起。下面的SELECT语句使用了这三个表以得到正确的结果:SELECT au_name,title FROM authors,titles,titleauthor WHERE authors.au_id=titleauthor.au_idAND titles.title_id=titleauthor.title_id 当这个SELECT语句执行时,每个作者都将与正确的书名相匹配。表titleauthor指明了表authors和表titles的关系,它通过包含分别来自两个表的各一个字段实现这一点。第三个表的唯一目的是在另外两个表的字段之间建立关系。它本身不包含任何附加数据。注意在这个例子中字段名是如何书写的。为了区别表authors和表titles中相同的字段名au_id,每个字段名前面都加上了表名前缀和一个句号。名为author.au_id 的字段属于表authors,名为titleauthor.au_id的字段属于表titleauthor,两者不会混淆。通过使用第三个表,你可以在两个表的字段之间建立各种类型的关系。例如,一个作者也许写了许多不同的书,或者一本书也许由许多不同的作者共同完成。当两个表的字段之间有这种“多对多”的关系时,你需要使用第三个表来指明这种关系。但是,在许多情况下,两个表之间的关系并不复杂。比如你需要指明表titles和表publishers之间的关系。因为一个书名不可能与多个出版商相匹配,你不需要通过第三个表来指明这两个表之间的关系。要指明表titles和表publishers之间的关系,你只要让这两个表有一个公共的字段就可以了。在数据库pubs中,表titles和表publishers都有一个名为pub_id的字段。如果你想得到书名及其出版商的一个列表,你可以使用如下的语句:SELECT title,pub_name FROM titles,publishersWHERE titles.pub_id=publishers.pub_id当然,如果一本书是由两个出版商联合出版的,那么你需要第三个表来代表这种关系。通常,当你予先知道两个表的字段间存在“多对多”关系时,就使用第三个表来关联这两个表。反之,如果两个表的字段间只有“一对一”或“一对多”关系,你可以使用公共字段来关联它门。操作字段通常,当你从一个表中取出字段值时,该值与创建该表时所定义的字段名联系在一起。如果你从表authors中选择所有的作者名字,所有的值将会与字段名au_lname相联系。但是在某些情况下,你需要对字段名进行操作。在SELECT语句中,你可以在缺省字段名后面仅跟一个新名字来取代它。例如,可以用一个更直观易读的名字Author Last Name来代替字段名au_lname:SELECT au_lname "Author Last Name" FROM authors当这个SELECT语句执行时,来自字段au_lname的值会与“Author Last Name”相联系。查询结果可能是这样:Author Last Name …………………………………………………………………….. WhiteGreenCarsonO’LearyStraight …(23 row(s) affected)注意字段标题不再是au_lname,而是被Author Last Name所取代。你也可以通过执行运算,来操作从一个表返回的字段值。例如,如果你想把表titles中的所有书的价格加倍,你可以使用下面的SELECT语句:SELECT price*2 FROM titles 当这个查询执行时,每本书的价格从表中取出时都会加倍。但是,通过这种途径操作字段不会改变存储在表中的书价。对字段的运算只会影响SELECT语句的输出,而不会影响表中的数据。为了同时显示书的原始价格和涨价后的新价格,你可以使用下面的查询:SELECT price "Original price", price*2 "New price" FROM titles当数据从表titles中取出时,原始价格显示在标题Original price下面,加倍后的价格显示在标题New price下面。结果可能是这样:original price new price……………………………………………………………….19.99 39.9811.95 23.90 2.99 5.9819.99 39.98…(18 row(s) affected)你可以使用大多数标准的数学运算符来操作字段值,如加(+),减(-),乘(*)和除(/)。你也可以一次对多个字段进行运算,例如:SELECT price*ytd_sales "total revenue" FROM titles在这个例子中,通过把价格与销售量相乘,计算出了每种书的总销售额。这个SELECT语句的结果将是这样的:total revenue……………………………………………..81,859,0546,318,2055,978,7881,859,0540,619,68…(18 row(s) affected)最后,你还可以使用连接运算符(它看起来像个加号)来连接两个字符型字段:SELECT au_fname+" "+au_lname "author name" FROM authors在这个例子中,你把字段au_fname和字段au_lname粘贴在一起,中间用一个逗号 隔开,并把查询结果的标题指定为author name。这个语句的执行结果将是这样的:author names…………………………………………………………Johnson WhiteMarjorie GreenCheryl CarsonMichael O’LearyDean Straight…(23 row(s) affected)可以看到,SQL为你提供了对查询结果的许多控制。你应该在ASP编程过程中充分利用这些优点。使用SQL来操作查询结果几乎总是比使用有同样作用的脚本效率更高。排序查询结果本章的介绍中曾强调过,SQL表没有内在的顺序。例如,从一个表中取第二个记录是没有意义的。从SQL的角度看来,没有一个记录在任何其他记录之前。然而,你可以操纵一个SQL查询结果的顺序。在缺省情况下,当记录从表中取出时,记录不以特定的顺序出现。例如,当从表authors中取出字段au_lname时,查询结果显示成这样:au_lname…………………………………….WhiteGreenCarsonO’LearyStraight…(23 row(s) affected)看一列没有特定顺序的名字是很不方便的。如果把这些名字按字母顺序排列,读起来就会容易得多。通过使用ORDER BY子句,你可以强制一个查询结果按升序排列,就像这样:SELECT au_lname FROM authors ORDER BY au_lname当这个SELECT语句执行时,作者名字的显示将按字母顺序排列。ORDER BY子句将作者名字按升序排列。你也可以同时对多个列使用ORDER BY子句。例如,如果你想同时按升序显示字段au_lname和字段au_fname,你需要对两个字段都进行排序:SELECT au_lname,au_fname FROM authors ORDER BY au_lname ,au_fname这个查询首先把结果按au_lname字段进行排序,然后按字段au_fname排序。记录将按如下的顺序取出:au_lname au_fname…………………………………………………………………….Bennet AbrahamRinger AlbertRinger AnneSmith Meander…(23 row(s) affected)注意有两个作者有相同的名字Ringer。名为Albert Ringer的作者出现名为Anne Ringer的作者之前,这是因为姓Albert按字母顺序应排在姓Anne之前。如果你想把查询结果按相反的顺序排列,你可以使用关键字DESC。关键字DESC把查询结果按降序排列,如下例所示:SELECT au_lname,au_fname FROM authors WHERE au_lname=”Ringer” ORDER BY au_lname ,au_fname DESC这个查询从表authors中取出所有名字为Ringer的作者记录。ORDER BY子句根据作者的名字和姓,将查询结果按降序排列。结果是这样的:au_lname au_fname……………………………………………………………………………………….Ringer AnneRinger Albert(2 row(s) affectec)注意在这个表中,姓Anne出现在姓Albert之前。作者名字按降序显示。你也可以按数值型字段对一个查询结果进行排序。例如,如果你想按降序取出所有书的价格,你可以使用如下的SQL查询:SELECT price FROM titles ORDER BY price DESC这个SELECT语句从表中取出所有书的价格,显示结果时,价格低的书先显示,价格高的书后显示。警告:不是特别需要时,不要对查询结果进行排序,因为服务器完成这项工作要费些力气。这意味着带有ORDER BY 子句的SELECT语句执行起来比一般的SELECT语句花的时间长。取出互不相同的记录一个表有可能在同一列中有重复的值。例如,数据库pubs的表authors中有两个作者的名字是Ringer。如果你从这个表中取出所有的名字,名字Ringer将会显示两次。在特定情况下,你可能只有兴趣从一个表中取出互不相同的值。如果一个字段有重复的值,你也许希望每个值只被选取一次,你可以使用关键字DISTINCT来做到这一点:SELCET DISTINCT au_lname FROM authors WHERE au_lname="Ringer"当这个SELECT语句执行时,只返回一个记录。通过在SELECT语句中包含关键字DISTINCT,你可以删除所有重复的值。例如,假设有一个关于新闻组信息发布的表,你想取出所有曾在这个新闻组中发布信息的人的名字,那么你可以使用关键字DISTINCT。每个用户的名字只取一次——尽管有的用户发布了不止一篇信息。警告:如同ORDER BY子句一样,强制服务器返回互不相同的值也会增加运行开销。福气不得不花费一些时间来完成这项工作。因此,不是必须的时候不要使用关键字DISTINCT。创建新表前面说过,数据库中的所有数据存储在表中。数据表包括行和列。列决定了表中数据的类型。行包含了实际的数据。例如,数据库pubs中的表authors有九个字段。其中的一个字段名为为au_lname,这个字段被用来存储作者的名字信息。每次向这个表中添加新作者时,作者名字就被添加到这个字段,产生一条新记录。通过定义字段,你可以创建一个新表。每个字段有一个名字和一个特定的数据类型(数据类型在后面的“字段类型”一节中讲述),例如字段au_lname存储的是字符型数据。一个字段也可以存储其它类型的数据。使用SQL Sever,创建一个新表的方法是很多的。你可以可执行一个SQL语句或使用SQL事务管理器(SQL Enterprise Manager)来创建一个新表。在下一节里,你将学会如何用SQL语句来创建一个新表。用SQL创建新表注意:如果你还没有建立自己的数据库,现在就跳回到第三章创建这个库。你绝不能向master,tempdb或任何其他任何系统数据库中添加数据。从SQL Sever程序组(在任务栏中)中启动ISQL/w程序。出现查询窗口后,从窗口顶部的下拉列表中选择你在第三章所创建的数据库。下一步,在查询窗口中键入下面的SQL语句,单击执行查询按钮,执行这个语句:CREATE TABLE guestbook (visitor VARCHAR(40),comments TEXT,entrydate DATETIME)如果一切正常,你会在结果窗口中看到如下的文字(如果出现异常,请参阅第三章):This command dit not return data ,and it did not return any rows 祝贺你,你已经建立了你的第一个表!你所创建的表名为guestbook,你可以使用这个表来存储来字你站点访问者的信息。你是用REEATE TABLE语句创建的这个表,这个语句有两部分:第一部份指定表的名子;第二部份是括在括号中的各字段的名称和属性,相互之间用逗号隔开。表guestbook有三个字段:visitor,comments 和entrydate。visitor字段存储访问者的名字,comments字段存储访问者对你站点的意见,entrydate字段存储访问者访问你站点的日期和时间。注意每个字段名后面都跟有一个专门的表达式。例如,字段名comments后面跟有表达式TEXT。这个表达式指定了字段的数据类型。数据类型决定了一个字段可以存储什么样的数据。因为字段comments包含文本信息,其数据类型定义为文本型。字段有许多不同的数据类型。下一小节讲述SQL所支持的一些重要的数据类型。字段类型不同的字段类型用来存放不同类型的数据。创建和使用表时,更你应该理解五种常用的字段类型:字符型,文本型,数值型,逻辑性和日期型。字符型数据字符型数据非常有用。当你需要存储短的字符串信息时,你总是要用到字符型数据。例如,你可以把从HTML form的文本框中搜集到的信息放在字符型字段中。要建立一个字段用来存放可变长度的字符串信息,你可以使用表达式 VARCHAR。考虑你前面创建的表guestbook:CREATE TABLE guestbook (visitor VARCHAR(40),comments TEXT,entrydate DATETIME)在这个例子中,字段visitor的数据类型为VARCHAR。注意跟在数据类型后面的括号中的数字。这个数字指定了这个字段所允许存放的字符串的最大长度。在这个例子中,字段visitor能存放的字符串最长为四十个字符。如果名字太长,字符串会被截断,只保留四十个字符。VARCHAR类型可以存储的字符串最长为255个字符。要存储更长的字符串数据,可以使用文本型数据(下一节中讲述)。另一种字符型数据用来存储固定长度的字符数据。下面是一个使用这种数据类型的例子:CREATE TABLE guestbook (visitor CHAR(40),comments TEXT,entrydate DATETIME)在这个例子中,字段visitor被用来存储四十个字符的固定长度字符串。表达式CHAR指定了这个字段应该是固定长度的字符串。VARCHAR型和CHAR型数据的这个差别是细微的,但是非常重要。假如你向一个长度为四十个字符的VARCHAR型字段中输入数据Bill Gates。当你以后从这个字段中取出此数据时,你取出的数据其长度为十个字符——字符串Bill Gates的长度。现在假如你把字符串输入一个长度为四十个字符的CHAR型字段中,那么当你取出数据时,所取出的数据长度将是四十个字符。字符串的后面会被附加多余的空格。当你建立自己的站点时,你会发现使用VARCHAR型字段要比CHAR型字段方便的多。使用VARCHAR型字段时,你不需要为剪掉你数据中多余的空格而操心。VARCHAR型字段的另一个突出的好处是它可以比CHAR型字段占用更少的内存和硬盘空间。当你的数据库很大时,这种内存和磁盘空间的节省会变得非常重要。文本型数据字符型数据限制了字符串的长度不能超过255个字符。而使用文本型数据,你可以存放超过二十亿个字符的字符串。当你需要存储大串的字符时,应该使用文本型数据。这里有一个使用文本型数据的例子:CREATE TABLE guestbook (visitor VARCHAR(40),comments TEXT,entrydate DATETIME)在这个例子中,字段comments被用来存放访问者对你站点的意见。注意文本型数据没有长度,而上一节中所讲的字符型数据是有长度的。一个文本型字段中的数据通常要么为空,要么很大。当你从HTML form的多行文本编辑框(TEXTAREA)中收集数据时,你应该把收集的信息存储于文本型字段中。但是,无论何时,只要你能避免使用文本型字段,你就应该不适用它。文本型字段既大且慢,滥用文本型字段会使服务器速度变慢。文本型字段还会吃掉大量的磁盘空间。警告:一旦你向文本型字段中输入了任何数据(甚至是空值),就会有2K的空间被自动分配给该数据。除非删除该记录,否则你无法收回这部分存储空间。数值型数据SQL Sever支持许多种不同的数值型数据。你可以存储整数、小数、和钱数。通常,当你需要在表中的存放数字时,你要使用整型(INT)数据。INT型数据的表数范围是从-2,147,483,647到2,147,483,647的整数。下面是一个如何使用INT型数据的例子:CREATE TABLE visitlog (visitor VARCHAR(40),numvisits INT)这个表可以用来记录你站点被访问的次数。只要没有人访问你的站点超过2,147,483,647次,nubvisits字段就可以存储访问次数。为了节省内存空间,你可以使用SMALLINT型数据。SMALLINT 型数据可以存储从-32768到32768的整数。这种数据类型的使用方法与INT型完全相同。最后,如果你实在需要节省空间,你可以使用TINYINT型数据。同样,这种类型的使用方法也与INT型相同,不同的是这种类型的字段只能存储从0到255的整数。TINYINT型字段不能用来存储负数。通常,为了节省空间,应该尽可能的使用最小的整型数据。一个TINYINT型数据只占用一个字节;一个INT型数据占用四个字节。这看起来似乎差别不大,但是在比较大的表中,字节数的增长是很快的。另一方面,一旦你已经创建了一个字段,要修改它是很困难的。因此,为安全起见,你应该预测以下,一个字段所需要存储的数值最大有可能是多大,然后选择适当的数据类型。为了能对字段所存放的数据有更多的控制,你可以使用NUMERIC型数据来同时表示一个数的整数部分和小数部分。NUMERIC型数据使你能表示非常大的数——比INT型数据要大得多。一个NUMERIC型字段可以存储从-1038到1038范围内的数。NUMERIC型数据还使你能表示有小数部分的数。例如,你可以在NUMERIC型字段中存储小数3.14。当定义一个NUMERIC型字段时,你需要同时指定整数部分的大小和小数部分的大小。这里有一个使用这种数据类型的例子:CREATE TABLE numeric_data (bignumber NUMERIC(28,0), fraction NUMERIC (5,4) 当这个语句执行时,将创建一个名为numeric_data的包含两个字段的表。字段bignumber可以存储直到28位的整数。字段fraction可以存储有五位整数部分和四位小数部分的小数。一个NUMERIC型数据的整数部分最大只能有28位,小数部分的位数必须小于或等于整数部分的位数,小数部分可以是零。你可以使用INT型或NUMERIC型数据来存储钱数。但是,专门有另外两种数据类型用于此目的。如果你希望你的网点能挣很多钱,你可以使用MONEY型数据。如果你的野心不大,你可以使用SMALLMONEY型数据。MONEY型数据可以存储从-922,337,203,685,477.5808到922,337,203,685,477.5807的钱数。如果你需要存储比这还大的金额,你可以使用NUMERIC型数据。SMALLMONEY型数据只能存储从-214,748.3648到214,748.3647 的钱数。同样,如果可以的话,你应该用SMALLMONEY型来代替MONEY型数据,以节省空间。下面的例子显示了如何使用这两种表示钱的数据类型:CREATE TABLE products (product VARCHAR(40),price MONEY,Discount_price SMALLMONEY) 这个表可以用来存储商品的折扣和普通售价。字段price 的数据类型是MONEY,字段discount_price的数据类型是SMALLMONEY。存储逻辑值如果你使用复选框(CHECKBOX)从网页中搜集信息,你可以把此信息存储在BIT型字段中。BIT型字段只能取两个值:0或1。这里有一个如何使用这种字段的例子:CREATE TABLE opinion (visitor VARCHAR(40),good BIT)这个表可以用来存放对你的网点进行民意调查所得的信息。访问者可以投票表示他们是否喜欢你的网点。如果他们投YES,就在BIT型字段中存入1。反之,如果他们投NO,就在字段中存入0(在下一章里,你将学会如何计算投票)。当心,在你创建好一个表之后,你不能向表中添加BIT型字段。如果你打算在一个表中包含BIT型字段,你必须在创建表时完成。存储日期和时间当你建立一个网点时,你也许需要记录在一段时间内的访问者数量。为了能够存储日期和时间,你需要使用DATETIME型数据,如下例所示:CREATE TABL visitorlog( visitor VARCHAR (40), arrivaltime DATETIME ,departuretime DATETIME)这个表可以用来记录访问者进入和离开你网点的时间和日期。一个DATETIME型的字段可以存储的日期范围是从1753年1月1日第一毫秒到9999年12月31日最后一毫秒。如果你不需要覆盖这么大范围的日期和时间,你可以使用SMALLDATETIME型数据。它与DATETIME型数据同样使用,只不过它能表示的日期和时间范围比DATETIME型数据小,而且不如DATETIME型数据精确。一个SMALLDATETIME型的字段能够存储从1900年1月1日到2079年6月6日的日期,它只能精确到秒。DATETIME型字段在你输入日期和时间之前并不包含实际的数据,认识这一点是重要的。在下一章,你将学习怎样使用大量的SQL函数来读取和操作日期和时间(参见下面的“缺省值”一节)。你也可以在VBScript和JScript 中使用日期和时间函数来向一个DATETIME型字段中输入日期和时间。字段属性上一节介绍了如何建立包含不同类型字段的表。在这一节中,你将学会如何使用字段的三个属性。这些属性允许你控制空值,缺省值和标识值。允许和禁止空值大多数字段可以接受空值(NULL)。当一个字段接受了空值后,如果你不改变它,它将一直保持空值。空值(NULL)和零是不同的,严格的说,空值表示没有任何值。为了允许一个字段接受空值,你要在字段定义的后面使用表达式NULL。例如,下面的表中两个字段都允许接受空值:CREATE TABLE empty (empty1 CHAR (40) NULL,empty2 INT NULL(注意:BIT型数据不能是空值。一个这种类型的字段必须取0或者1。有时你需要禁止一个字段使用空值。例如,假设有一个表存储着信用卡号码和信用卡有效日期,你不会希望有人输入一个信用卡号码但不输入有效日期。为了强制两个字段都输入数据,你可以用下面的方法建立这个表:CREATE TABLE creditcards (creditcard_number CHAR(20) NOT NULL,Creditcard_expire DATETIME NOT NULL)注意字段定义的后面跟有表达式NOT NULL。通过包含表达式NOT NULL,你可以禁止任何人只在一个字段中插入数据,而不输入另一个字段的数据。你将会发现,在你建设自己的网点过程中,这种禁止空值的能力是非常有用的。如果你指定一个字段不能接受空值,那么当你试图输入一个空值时,会有错误警告。这些错误警告可以为程序调试提供有价值的线索。缺省值假设有一个存储地址信息的表,这个表的字段包括街道、城市、州、邮政编码和国家。如果你预计地址的大部分是在美国,你可以把这个值作为country字段的缺省值。为了在创建一个表时指定缺省值,你可以使用表达式DEFAULT。请看下面这个在创建表时使用缺省值的例子:CREATE TABLE addresses (street VARCHAR(60) NULL,city VARCHAR(40) NULL,state VARCHAR(20) NULLzip VARCHAR(20) NULL,country VARCHAR(30) DEFAULT ‘USA’)在这个例子中,字段country的缺省值被指定为美国。注意单引号的使用,引号指明这是字符型数据。为了给非字符型的字段指定缺省值,不要把该值扩在引号中:CREATE TABLE orders(price MONEY DEFAULT $38.00,quantity INT DEFAULT 50,entrydate DATETIME DEFAULT GETDATE())在这个CREATE TABLE语句中,每个字段都指定了一个缺省值。注意DATETIME型字段entrydate所指定的缺省值,该缺省值是函数Getdate()的返回值,该函数返回当前的日期和时间。标识字段每个表可以有一个也只能有一个标识字段。一个标识字段是唯一标识表中每条记录的特殊字段。例如,数据库pubs中的表jobs包含了一个唯一标识每个工作标识字段: job_id job_desc…………………………………………………………….1 New Hire Job not specified2 Chief Executive officer3 Bushness Operations Manager4 Chief Financial Officier5 Publisher字段job_id为每个工作提供了唯一的一个数字。如果你决定增加一个新工作,新增记录的job_id字段会被自动赋给一个新的唯一值。为了建立一个标识字段,你只需在字段定义后面加上表达式IDENTITY即可。你只能把NUMERIC型或INT型字段设为标识字段,这里有一个例子:CREATE TABLE visitorID (theID NUBERIC(18) IDENTITY,name VARCHAR(40))这个语句所创建的表包含一个名为theid的标识字段。每当一个新的访问者名字添加到这个表中时,这个字段就被自动赋给一个新值。你可以用这个表为你的站点的每一个用户提供唯一标识。技巧:建立一个标示字段时,注意使用足够大的数据类型。例如你使用TINYINT型数据,那么你只能向表中添加255个记录。如果你预计一个表可能会变得很大,你应该使用NUMERIC型数据。标识字段的存在会使你想尝试许多不可能的事情。例如,你也许想利用标识字段来对记录进行基于它们在表中位置的运算。你应该抛弃这种意图。每个记录的标识字段的值是互不相同的,但是,这并不禁止一个标识字段的标识数字之间存在间隔。例如,你永远不要试图利用一个表的标识字段来取出表中的前十个记录。这种操作会导致失败,比如说6号记录和7号记录根本不存在。使用SQL事务管理器创建新表你可以使用前面几节所讲的方法创建新表。但是,使用事务管理器创建新表会更容易。这一节介绍如何使用这个程序创建新表。从任务栏的SQL Sever程序组中选择SQL Enterprise Manager,启动该程序,你会看到如图10.4所示的窗口。浏览服务管理器窗口中的树形结构,选择名为Database的文件夹。打开文件夹Database后,选择你在第三章中所建立的数据库。注意:如果你还没有创建自己的数据库,回到第三章创建它。你决不要向master,tempdb或任何其它系统数据库中添加数据。在选择了数据库之后,你会看到一个名为Group/users的文件夹和一个名为objects的文件夹。打开文件夹objects,你会看到许多文件夹,其中一个名为Tables。用右键单击文件夹Tables并选择New table,就会出现如图10.5所示的窗口。你可以使用Manager Tables窗口来创建一个新表。Manager Tables窗口有7个列:Key,Column,Name,Datatype,Size,Nulls和Default。Manager Tables窗口中的每一行标明表中一个字段的信息。图10.410.5要建立一个新表,你至少要输入一行信息。在名为Column Name的列下面键入mycolumn。下一步,选择Datatype列,并从下拉列表中选择CHAR。当你在这两个列中输入信息后,窗口将是如图10.6所示的样子。图10.6你已经建立了一个只有一个字段的简单的表。单击保存按扭保存这个新表。当要求你输入新表的名字时,输入mytable并单击OK。现在这个表已经保存到了你的数据库中。如果你打开服务管理器窗口中的文件夹Tables,你会看到你所建立的新表被列出。你可以双击该表的图表来编辑它,这时Manager Tables窗口会重新出现,你可以增加新的字段并重新保存。用SQL事务管理器可以做的工作,你都可以用SQL语句来实现。但是,事务管理器使得建表过程变得更加简单。向表中添加数据下一章将讨论如何使用SQL向一个表中插入数据。但是,如果你需要向一个表中添加许多条记录,使用SQL语句输入数据是很不方便的。幸运的是,Microsoft SQL Sever带有一个称为Microsoft Query 的客户端应用程序,这个程序使得向表中添加数据变得容易了。启动位于任务栏SQL Sever程序组中的Microsoft Query程序。从窗口顶部的菜单中选择FileNew Query。这时会显示一个Select Data Source对话框(见图10.7)。选择你的数据源名字并单击Use。图10。7输入你的登录帐号和密码后,程序要求你选择一个表和一个数据库。选择你在上一节中所建立的表(mytable ),单击按钮Add,然后单击按钮Close关闭该对话框。在窗口的左上角会出现一个对话框,框中是取自表mytable的一列字段名。你可以双击任何一个字段,把它添加到主窗口中。如果你双击星号(*)字符,所有的字段都会被添加到主窗口中。如果你的表中有记录,它们现在已经出现在主窗口的字段标题下面了。但是,因为你刚刚建立了这个表,表还是空的。要添加新记录,选择RecordsAllow Editing,主窗口中就会出现一条新记录。输入一行数据完成这个记录,就向表中添加了一条新记录。图10。8当你转到下一条新记录时,你向上一条记录中输入的值会自动被保存。如果你需要,你可以用Microsoft Query 向表中输入几百条记录。删除和修改表你应该在建立表之前仔细设计它们,因为你在改变一个已经存在的表时会受到很大的限制。例如,一旦已经建立了一个表,你就不能删除表中的字段或者改变字段的数据类型。在这种情况你所能做的是删除这个表,然后重头开始(参见第十一章“中级SQL”中的“使用SQL创建记录和表”一节)。要删除一个表,你可以使用SQL语句DROP TABLE。例如,又从数据库中彻底删除表mytable,你要使用如下的语句:DROP TABLE mytable警告:使用DROP TABLE命令时一定要小心。一旦一个表被删除之后,你将无法恢复它。当你建设一个站点时,你很可能需要向数据库中输入测试数据。而当你准备向世界提供你的网点时,你会想清空表中的这些测试信息。如果你想清除表中的所有数据但不删除这个表,你可以使用TRUNCATE TABLE语句。例如,下面的这个SQL语句从表mytable中删除所有数据:TRUNCATE TABLE mytable虽然你不能删除和修改已经存在的字段,但你可以增加新字段。最容易的实现方法是使用SQL事务管理器中的Manager Tables窗口。你也可以使用SQL语句ALTER TABLE。下面是一个如何使用这种语句的例子:ALTER TABLE mytable ADD mynewcolumn INT NULL这个语句向表mytable中增加了一个新字段mynewcolumn。当你增加新字段时,你必须允许它接受空值,因为表中原来可能已经有了许多记录。总结这一章向你介绍了SQL。使用SQL,你可以操作Microsoft SQL Sever数据库。你已经学会了使用SELECT语句从数据库中取出数据,你还学会了怎样使用CREATE TABLE语句和SQL事务管理器来创建新表。最后,你学会了如何指明一系列重要的字段属性。下一章将介绍如何使用索引来增强SQL查询的操作。还将通过许多其它的SQL语句和函数,使你的SQL知识得到进一步扩充。中级SQL本章内容■创建索引■SQL核心语句■集合函数■ 其它常用的SQL表达式,函数,和过程第十章“SQL基础”向你初步介绍了SQL。你学会了如何用SELECT语句进行查询,你还学会了如何建立自己的表。在这一章里,你将加深你的SQL知识。你将学习如何建立索引来加快查询速度。你还将学会如果用更多的SQL语句和函数来操作表中的数据。建立索引假设你想找到本书中的某一个句子。你可以一页一页地逐页搜索,但这会花很多时间。而通过使用本书的索引,你可以很快地找到你要搜索的主题。表的索引与附在一本书后面的索引非常相似。它可以极大地提高查询的速度。对一个较大的表来说,通过加索引,一个通常要花费几个小时来完成的查询只要几分钟就可以完成。因此没有理由对需要频繁查询的表增加索引。注意:当你的内存容量或硬盘空间不足时,也许你不想给一个表增加索引。对于包含索引的数据库,SQL Sever需要一个可观的额外空间。例如,要建立一个聚簇索引,需要大约1.2倍于数据大小的空间。要看一看一个表的索引在数据库中所占的空间大小,你可以使用系统存储过程sp_spaceused,对象名指定为被索引的表名。聚簇索引和非聚簇索引假设你已经通过本书的索引找到了一个句子所在的页码。一旦已经知道了页码后,你很可能漫无目的翻寻这本书,直至找到正确的页码。通过随机的翻寻,你最终可以到达正确的页码。但是,有一种找到页码的更有效的方法。首先,把书翻到大概一半的地方,如果要找的页码比半本书处的页码小,就书翻到四分之一处,否则,就把书翻到四分之三的地方。通过这种方法,你可以继续把书分成更小的部分,直至找到正确的页码附近。这是找到书页的非常有效的一种方法。SQL Sever的表索引以类似的方式工作。一个表索引由一组页组成,这些页构成了一个树形结构。根页通过指向另外两个页,把一个表的记录从逻辑上分成和两个部分。而根页所指向的两个页又分别把记录分割成更小的部分。每个页都把记录分成更小的分割,直至到达叶级页。索引有两种类型:聚簇索引和非聚簇索引。在聚簇索引中,索引树的叶级页包含实际的数据:记录的索引顺序与物理顺序相同。在非聚簇索引中,叶级页指向表中的记录:记录的物理顺序与逻辑顺序没有必然的联系。聚簇索引非常象目录表,目录表的顺序与实际的页码顺序是一致的。非聚簇索引则更象书的标准索引表,索引表中的顺序通常与实际的页码顺序是不一致的。一本书也许有多个索引。例如,它也许同时有主题索引和作者索引。同样,一个表可以有多个非聚簇索引。通常情况下,你使用的是聚簇索引,但是你应该对两种类型索引的优缺点都有所理解。每个表只能有一个聚簇索引,因为一个表中的记录只能以一种物理顺序存放。通常你要对一个表按照标识字段建立聚簇索引。但是,你也可以对其它类型的字段建立聚簇索引,如字符型,数值型和日期时间型字段。从建立了聚簇索引的表中取出数据要比建立了非聚簇索引的表快。当你需要取出一定范围内的数据时,用聚簇索引也比用非聚簇索引好。例如,假设你用一个表来记录访问者在你网点上的活动。如果你想取出在一定时间段内的登录信息,你应该对这个表的DATETIME型字段建立聚簇索引。对聚簇索引的主要限制是每个表只能建立一个聚簇索引。但是,一个表可以有不止一个非聚簇索引。实际上,对每个表你最多可以建立249个非聚簇索引。你也可以对一个表同时建立聚簇索引和非聚簇索引。假如你不仅想根据日期,而且想根据用户名从你的网点活动日志中取数据。在这种情况下,同时建立一个聚簇索引和非聚簇索引是有效的。你可以对日期时间字段建立聚簇索引,对用户名字段建立非聚簇索引。如果你发现你需要更多的索引方式,你可以增加更多的非聚簇索引。非聚簇索引需要大量的硬盘空间和内存。另外,虽然非聚簇索引可以提高从表中 取数据的速度,它也会降低向表中插入和更新数据的速度。每当你改变了一个建立了非聚簇索引的表中的数据时,必须同时更新索引。因此你对一个表建立非聚簇索引时要慎重考虑。如果你预计一个表需要频繁地更新数据,那么不要对它建立太多非聚簇索引。另外,如果硬盘和内存空间有限,也应该限制使用非聚簇索引的数量。索引属性这两种类型的索引都有两个重要属性:你可以用两者中任一种类型同时对多个字段建立索引(复合索引);两种类型的索引都可以指定为唯一索引。你可以对多个字段建立一个复合索引,甚至是复合的聚簇索引。假如有一个表记录了你的网点访问者的姓和名字。如果你希望根据完整姓名从表中取数据,你需要建立一个同时对姓字段和名字字段进行的索引。这和分别对两个字段建立单独的索引是不同的。当你希望同时对不止一个字段进行查询时,你应该建立一个对多个字段的索引。如果你希望对各个字段进行分别查询,你应该对各字段建立独立的索引。两种类型的索引都可以被指定为唯一索引。如果对一个字段建立了唯一索引,你将不能向这个字段输入重复的值。一个标识字段会自动成为唯一值字段,但你也可以对其它类型的字段建立唯一索引。假设你用一个表来保存你的网点的用户密码,你当然不希望两个用户有相同的密码。通过强制一个字段成为唯一值字段,你可以防止这种情况的发生。用SQL建立索引为了给一个表建立索引,启动任务栏SQL Sever程序组中的ISQL/w程序。进入查询窗口后,输入下面的语句:CREATE INDEX mycolumn_index ON mytable (myclumn)这个语句建立了一个名为mycolumn_index的索引。你可以给一个索引起任何名字,但你应该在索引名中包含所索引的字段名,这对你将来弄清楚建立该索引的意图是有帮助的。注意:在本书中你执行任何SQL语句,都会收到如下的信息:This command did not return data,and it did not return any rows这说明该语句执行成功了。索引mycolumn_index对表mytable的mycolumn字段进行。这是个非聚簇索引,也是个非唯一索引。(这是一个索引的缺省属性)如果你需要改变一个索引的类型,你必须删除原来的索引并重建 一个。建立了一个索引后,你可以用下面的SQL语句删除它:DROP INDEX mytable.mycolumn_index注意在DROP INDEX 语句中你要包含表的名字。在这个例子中,你删除的索引是mycolumn_index,它是表mytable的索引。要建立一个聚簇索引,可以使用关键字CLUSTERED。)记住一个表只能有一个聚簇索引。(这里有一个如何对一个表建立聚簇索引的例子:CREATE CLUSTERED INDEX mycolumn_clust_index ON mytable(mycolumn)如果表中有重复的记录,当你试图用这个语句建立索引时,会出现错误。但是有重复记录的表也可以建立索引;你只要使用关键字ALLOW_DUP_ROW把这一点告诉SQL Sever即可:CREATE CLUSTERED INDEX mycolumn_cindex ON mytable(mycolumn)WITH ALLOW_DUP_ROW这个语句建立了一个允许重复记录的聚簇索引。你应该尽量避免在一个表中出现重复记录,但是,如果已经出现了,你可以使用这种方法。要对一个表建立唯一索引,可以使用关键字UNIQUE。对聚簇索引和非聚簇索引都可以使用这个关键字。这里有一个例子:CREATE UNIQUE COUSTERED INDEX myclumn_cindex ON mytable(mycolumn)这是你将经常使用的索引建立语句。无论何时,只要可以,你应该尽量对一个对一个表建立唯一聚簇索引来增强查询操作。最后,要建立一个对多个字段的索引──复合索引──在索引建立语句中同时包含多个字段名。下面的例子对firstname和lastname两个字段建立索引:CREATE INDEX name_index ON username(firstname,lastname)这个例子对两个字段建立了单个索引。在一个复合索引中,你最多可以对16个字段进行索引。用事务管理器建立索引用事务管理器建立索引比用SQL语句容易的多。使用事务管理器,你可以看到已经建立的索引的列表,并可以通过图形界面选择索引选项。使用事务管理器你可以用两种方式建立索引:使用Manage Tables窗口或使用Manage Indexes窗口。要用Manage Tables 窗口建立一个新索引,单击按钮Advanced Options(它看起来象一个前面有一加号的表)。这样就打开了Advanced Options对话框。这个对话框有一部分标名为Primary Key(见图11.1)。图11。1要建立一个新索引,从下拉列表中选择你想对之建立索引的字段名。如果你想建立一个对多字段的索引,你可以选择多个字段名。你还可以选择索引是聚簇的还是非聚簇的。在保存表信息后,索引会自动被建立。在Manage Tables窗口中的字段名旁边,会出现一把钥匙。你已经为你的表建立了“主索引”。主索引必须对不包含空值的字段建立。另外,主索引强制一个字段成为唯一值字段。要建立没有这些限制的索引,你需要使用Manage Indexes窗口。从菜单中选择ManageIndexes,打开Manage Indexes 窗口。在Manage Indexes 窗口中,你可以通过下拉框选择表和特定的索引。(见图11.2)。要建立一个新索引,从Index下拉框中选择New Index.,然后就可以选择要对之建立索引的字段。单击按钮Add,把字段加人到索引中。图11。2你可以为你的索引选择许多不同的选项。例如,你可以选择该索引是聚簇的还是非聚簇的。你还可以指定该索引为唯一索引。设计好索引后,单击按钮Build,建立该索引。注意:唯一索引是指该字段不能有重复的值,而不是只能建立这一个索引。SQL核心语句在第十章,你学会了如何用SQL SELECT 语句从一个表中取数据。但是,到现在为止,还没有讨论如何添加,修改或删除表中的数据。在这一节中,你将学习这些内容。插入数据向表中添加一个新记录,你要使用SQL INSERT 语句。这里有一个如何使用这种语句的例子:INSERT mytable (mycolumn) valueS (‘some data’)这个语句把字符串’some data’插入表mytable的mycolumn字段中。将要被插入数据的字段的名字在第一个括号中指定,实际的数据在第二个括号中给出。INSERT 语句的完整句法如下:INSERT [INTO] {table_nameview_name} [(column_list)] {DEFAULT valueS values_list select_statement}如果一个表有多个字段,通过把字段名和字段值用逗号隔开,你可以向所有的字段中插入数据。假设表mytable有三个字段first_column,second_column,和third_column。下面的INSERT语句添加了一条三个字段都有值的完整记录:INSERT mytable (first_column,second_column,third_column)valueS (‘some data’,’some more data’,’yet more data’)注意:你可以使用INSERT语句向文本型字段中插入数据。但是,如果你需要输入很长的字符串,你应该使用WRITETEXT语句。这部分内容对本书来说太高级了,因此不加讨论。要了解更多的信息,请参考Microsoft SQL Sever 的文档。如果你在INSERT 语句中只指定两个字段和数据会怎么样呢?换句话说,你向一个表中插入一条新记录,但有一个字段没有提供数据。在这种情况下,有下面的四种可能:■ 如果该字段有一个缺省值,该值会被使用。例如,假设你插入新记录时没有给字段third_column提供数据,而这个字段有一个缺省值’some value’。在这种情况下,当新记录建立时会插入值’some value’。■ 如果该字段可以接受空值,而且没有缺省值,则会被插入空值。■ 如果该字段不能接受空值,而且没有缺省值,就会出现错误。你会收到错误信息:The column in table mytable may not be null.■ 最后,如果该字段是一个标识字段,那么它会自动产生一个新值。当你向一个有标识字段的表中插入新记录时,只要忽略该字段,标识字段会给自己赋一个新值。注意:向一个有标识字段的表中插入新记录后,你可以用SQL变量@@identity来访问新记录的标识字段的值。考虑如下的SQL语句:INSERT mytable (first_column) valueS(‘some value’)INSERT anothertable(another_first,another_second)valueS(@@identity,’some value’)如果表mytable有一个标识字段,该字段的值会被插入表anothertable的another_first字段。这是因为变量@@identity总是保存最后一次插入标识字段的值。字段another_first应该与字段first_column有相同的数据类型。但是,字段another_first不能是应该标识字段。Another_first字段用来保存字段first_column的值。删除记录要从表中删除一个或多个记录,需要使用SQL DELETE语句。你可以给DELETE 语句提供WHERE 子句。WHERE子句用来选择要删除的记录。例如,下面的这个DELETE语句只删除字段first_column的值等于’Delete Me’的记录:DELETE mytable WHERE first_column=’Deltet Me’DELETE 语句的完整句法如下:DELETE [FROM] {table_nameview_name} [WHERE clause]在SQL SELECT 语句中可以使用的任何条件都可以在DELECT 语句的WHERE子句 中使用。例如,下面的这个DELETE语句只删除那些first_column字段的值为’goodbye’或second_column字段的值为’so long’的记录:DELETE mytable WHERE first_column=’goodby’ OR second_column=’so long’如果你不给DELETE 语句提供WHERE 子句,表中的所有记录都将被删除。你不应该有这种想法。如果你想删除应该表中的所有记录,应使用第十章所讲的TRUNCATE TABLE语句。注意:为什么要用TRUNCATE TABLE 语句代替DELETE语句?当你使用TRUNCATE TABLE语句时,记录的删除是不作记录的。也就是说,这意味着TRUNCATE TABLE 要比DELETE快得多。更新记录要修改表中已经存在的一条或多条记录,应使用SQL UPDATE语句。同DELETE语句一样,UPDATE语句可以使用WHERE子句来选择更新特定的记录。请看这个例子:UPDATE mytable SET first_column=’Updated!’ WHERE second_column=’Update Me!’这个UPDATE 语句更新所有second_column字段的值为’Update Me!’的记录。对所有被选中的记录,字段first_column的值被置为’Updated!’。下面是UPDATE语句的完整句法:UPDATE {table_nameview_name} SET [{table_nameview_name}]{column_listvariable_listvariable_and_column_list}[,{column_list2variable_list2variable_and_column_list2}…[,{column_listNvariable_listNvariable_and_column_listN}]][WHERE clause]注意:你可以对文本型字段使用UPDATE语句。但是,如果你需要更新很长的字符串,应使用UPDATETEXT语句。这部分内容对本书来说太高级了,因此不加讨论。要了解更多的信息,请参考Microsoft SQL Sever 的文档。如果你不提供WHERE子句,表中的所有记录都将被更新。有时这是有用的。例如,如果你想把表titles中的所有书的价格加倍,你可以使用如下的UPDATE 语句:你也可以同时更新多个字段。例如,下面的UPDATE语句同时更新first_column,second_column,和third_column这三个字段:UPDATE mytable SET first_column=’Updated!’Second_column=’Updated!’Third_column=’Updated!’WHERE first_column=’Update Me1’技巧:SQL忽略语句中多余的空格。你可以把SQL语句写成任何你最容易读的格式。用SELECT 创建记录和表你也许已经注意到,INSERT 语句与DELETE语句和UPDATE语句有一点不同,它一次只操作一个记录。然而,有一个方法可以使INSERT 语句一次添加多个记录。要作到这一点,你需要把INSERT 语句与SELECT 语句结合起来,象这样:INSERT mytable (first_column,second_column)SELECT another_first,another_secondFROM anothertableWHERE another_first=’Copy Me!’这个语句从anothertable拷贝记录到mytable.只有表anothertable中字段another_first的值为’Copy Me!’的记录才被拷贝。当为一个表中的记录建立备份时,这种形式的INSERT 语句是非常有用的。在删除一个表中的记录之前,你可以先用这种方法把它们拷贝到另一个表中。如果你需要拷贝整个表,你可以使用SELECT INTO 语句。例如,下面的语句创建了一个名为newtable的新表,该表包含表mytable的所有数据:SELECT * INTO newtable FROM mytable你也可以指定只有特定的字段被用来创建这个新表。要做到这一点,只需在字段列表中指定你想要拷贝的字段。另外,你可以使用WHERE 子句来限制拷贝到新表中的记录。下面的例子只拷贝字段second_columnd的值等于’Copy Me!’的记录的first_column字段。SELECT first_column INTO newtableFROM mytableWHERE second_column=’Copy Me!’使用SQL修改已经建立的表是很困难的。例如,如果你向一个表中添加了一个字段,没有容易的办法来去除它。另外,如果你不小心把一个字段的数据类型给错了,你将没有办法改变它。但是,使用本节中讲述的SQL语句,你可以绕过这两个问题。例如,假设你想从一个表中删除一个字段。使用SELECT INTO 语句,你可以创建该表的一个拷贝,但不包含要删除的字段。这使你既删除了该字段,又保留了不想删除的数据。如果你想改变一个字段的数据类型,你可以创建一个包含正确数据类型字段的新表。创建好该表后,你就可以结合使用UPDATE语句和SELECT 语句,把原来表中的所有数据拷贝到新表中。通过这种方法,你既可以修改表的结构,又能保存原有的数据。集合函数到现在为止,你只学习了如何根据特定的条件从表中取出一条或多条记录。但是,假如你想对一个表中的记录进行数据统计。例如,如果你想统计存储在表中的一次民意测验的投票结果。或者你想知道一个访问者在你的站点上平均花费了多少时间。要对表中的任何类型的数据进行统计,都需要使用集合函数。Microsoft SQL 支持五种类型的集合函数。你可以统计记录数目,平均值,最小值,最大值,或者求和。当你使用一个集合函数时,它只返回一个数,该数值代表这几个统计值之一。注意:要在你的ASP网页中使用集合函数的返回值,你需要给该值起一个名字。要作到这一点,你可以在SELECT语句中,在集合函数后面紧跟一个字段名,如下例所示:SELECT AVG(vote) ‘the_average’ FROM opinion在这个例子中,vote的平均值被命名为 the_average。现在你可以在你的ASP网页的数据库方法中使用这个名字。统计字段值的数目函数COUNT()也许是最有用的集合函数。你可以用这个函数来统计一个表中有多少条记录。这里有一个例子:SELECT COUNT(au_lname) FROM authors这个例子计算表authors中名字(last name)的数目。如果相同的名字出现了不止一次,该名字将会被计算多次。如果你想知道名字为某个特定值的作者有多少个,你可以使用WHERE子句,如下例所示:SELECT COUNT(au_lname) FROM authors WHERE au_lname=’Ringer’这个例子返回名字为’Ringer’的作者的数目。如果这个名字在表authors中出现了两次,则次函数的返回值是2。假如你想知道有不同名字的作者的数目。你可以通过使用关键字DISTINCT来得到该数目。如下例所示:SELECT COUNT(DISTINCT au_lname) FROM authors如果名字’Ringer’出现了不止一次,它将只被计算一次。关键字DISTINCT 决定了只有互不相同的值才被计算。通常,当你使用COUNT()时,字段中的空值将被忽略。一般来说,这正是你所希望的。但是,如果你仅仅想知道表中记录的数目,那么你需要计算表中所有的记录─不管它是否包含空值。下面是一个如何做到这一点的例子:SELECT COUNT(*) FROM authors注意函数COUNT()没有指定任何字段。这个语句计算表中所有记录所数目,包括有空值的记录。因此,你不需要指定要被计算的特定字段。函数COUNT()在很多不同情况下是有用的。例如,假设有一个表保存了对你站点的质量进行民意调查的结果。这个表有一个名为vote的字段,该字段的值要么是0,要么是1。0表示反对票,1表示赞成票。要确定赞成票的数量,你可以所有下面的SELECT 语句:SELECT COUNT(vote) FROM opinion_table WHERE vote=1计算字段的平均值使用函数COUNT(),你可以统计一个字段中有多少个值。但有时你需要计算这些值的平均值。使用函数AVG(),你可以返回一个字段中所有值的平均值。假如你对你的站点进行一次较为复杂的民意调查。访问者可以在1到10之间投票,表示他们喜欢你站点的程度。你把投票结果保存在名为vote的INT型字段中。要计算你的用户投票的平均值,你需要使用函数AVG():SELECT AVG(vote) FROM opinion这个SELECT语句的返回值代表用户对你站点的平均喜欢程度。函数AVG()只能对数值型字段使用。这个函数在计算平均值时也忽略空值。计算字段值的和假设你的站点被用来出售卡片,已经运行了两个月,是该计算赚了多少钱的时候了。假设有一个名为orders的表用来记录所有访问者的定购信息。要计算所有定购量的总和,你可以使用函数SUM():SELECT SUM(purchase_amount) FROM orders函数SUM()的返回值代表字段purchase_amount中所有值的平均值。字段purchase_amount的数据类型也许是MONEY型,但你也可以对其它数值型字段使用函数SUM()。返回最大值或最小值再一次假设你有一个表用来保存对你的站点进行民意调查的结果。访问者可以选择从1到10 的值来表示他们对你站点的评价。如果你想知道访问者对你站点的最高评价,你可以使用如下的语句:SELECT MAX(vote) FROM opinion你也许希望有人对你的站点给予了很高的评价。通过函数MAX(),你可以知道一个数值型字段的所有值中的最大值。如果有人对你的站点投了数字10,函数MAX()将返回该值。另一方面,假如你想知道访问者对你站点的的最低评价,你可以使用函数MIN(),如下例所示:SELECT MIN(vote) FROM opinion函数MIN()返回一个字段的所有值中的最小值。如果字段是空的,函数MIN()返回空值。其它常用的SQL表达式,函数,和过程这一节将介绍一些其它的SQL技术。你将学习如何从表中取出数据,其某个字段的值处在一定的范围,你还将学习如何把字段值从一种类型转换成另一种类型,如何操作字符串和日期时间数据。最后,你将学会一个发送邮件的简单方法。通过匹配一定范围的值来取出数据假设你有一个表用来保存对你的站点进行民意调查的结果。现在你想向所有对你的站点的评价在7到10之间的访问者发送书面的感谢信。要得到这些人的名字,你可以使用如下的SELECT 语句:SELECT username FROM opinion WHERE vote>6 and vote<11这个select vote="1" site_desc="’trading">3这个语句使用函数DEFFERENCE()来取得其名字的发音与Microsoft非常相似的站点。函数DIFFERENCE()返回一个0到4之间的数字。如果该函数返回4,表示发音非常相近;如果该函数返回0,说明这两个字符串的发音相差很大。例如,上面的语句将返回站点名Microsoft和Macrosoft。这两个名字的发音与Microsoft都很相似。如果你把上一语句中的大于3改为大于2,那么名为Zicrosoft和Megasoft的站点也将被返回。最后,如果你只需要差别等级大于1即可,则名为Picosoft和Minisoft的站点也将被匹配。要深入了解函数DIFFERENCE()是如何工作的,你可以用函数SOUNDEX()来返回函数DIFFERENCE()所使用的音标码。这里有一个例子:SELECT site_name ‘site name’,SOUNDEX(site_name) ‘sounds like’这个语句选取字段site_name的所有数据及其音标码。下面是这个查询的结果:site name sounds like……………………………………………………………….Yahoo Y000Mahoo M000Microsoft M262Macrosoft M262Minisoft M521Microshoft M262Zicrosoft Z262Zaposoft Z121Millisoft M421Nanosoft N521Megasoft M221Picosoft P221(12 row(s) affected)如果你仔细看一下音标码,你会注意到音标码的第一个字母与字段值的第一个字母相同。例如,Yahoo和Mahoo的音标码只有第一个字母不同。你还可以发现Microsoft和Macrosoft的音标码完全相同。函数DIFFERENDE()比较两个字符串的第一个字母和所有的辅音字母。该函数忽略任何元音字母(包括y),除非一个元音字母是一个字符串的第一个字母。不幸的是,使用SOUNDEX()和DIFFERENCE()有一个欠缺。WHERE子句中包含这两个函数的查询执行起来效果不好。因此,你应该小心使用这两个函数。删除空格有两个函数,TTRIM()和LTRIM(),可以用来从字符串中剪掉空格。函数LTRIM()去除应该字符串前面的所有空格;函数RTRIM()去除一个字符串尾部的所有空格。这里有一个任何使用函数RTRIM()的例子:SELECT RTRIM(site_name) FROM site_directory在这个例子中,如果任何一个站点的名字尾部有多余的空格,多余的空格将从查询结果中删去。你可以嵌套使用这两个函数,把一个字符串前后的空格同时删去:SELECT LTRIM(RTRIM(site_name) FROM site_directory你会发现,在从CHAR型字段中剪掉多余的空格时,这两个函数非常有用。记住,如果你把一个字符串保存在CHAR型字段中,该字符串会被追加多余的空格,以匹配该字段的长度。用这两个函数,你可以去掉无用的空格,从而解决这个问题。操作日期和时间日期和时间函数对建立一个站点是非常有用的。站点的主人往往对一个表中的数据何时被更新感兴趣。通过日期和时间函数,你可以在毫秒级跟踪一个表的改变。返回当前日期和时间通过函数GETDATE(),你可以获得当前的日期和时间。例如,语句SELECT GETDATE()返回如下的结果:……………………………..NOV 30 1997 3:29AM(1 row(s) affected)显然,如果你将来使用这个函数,你得到的日期将比这个时间晚,或者梗早。函数GETDATE()可以用来作为DATEDIME()型字段的缺省值。这对插入记录时保存当时的时间是有用的。例如,假设有一个表用来保存你站点上的活动日志。每当有一个访问者访问到你的站点时,就在表中添加一条新记录,记下访问者的名字,活动,和进行访问的时间。要建立一个表,其中的记录包含有当前的日期和时间,可以添加一个DATETIME型字段,指定其缺省值为函数GETDATE()的返回值,就象这样:CREATE TABLE site_log (username VARCHAR(40),useractivity VARCHAR(100),entrydate DATETIME DEFAULT GETDATE())转换日期和时间你也许已经注意到,在上一节的例子中,函数GETDATE()的返回值在显示时只显示到秒。实际上,SQL Sever内部时间可以精确到毫秒级(确切地说,可以精确到3.33毫秒)。要得到不同格式的日期和时间,你需要使用函数CONVERT()。例如,当下面的这个语句执行时,显示的时间将包括毫秒:SELECT CONVERT(VARCHAR(30),GETDATE(),9)注意例子中数字9的使用。这个数字指明了在显示日期和时间时使用哪种日期和时间格式。当这个语句执行时,将显示如下的日期和时间:…………………………………..Nov 30 1997 3:29:55:170AM(1 row(s) affected)在函数CONVERT()中你可以使用许多种不同风格的日期和时间格式。表11.1显示了所有的格式。表11.1 日期和时间的类型类型值 标准 输出0 Default mon dd yyyy hh:miAM1 USA mm/dd/yy2 ANSI yy.mm.dd3 British/French dd/mm/yy4 German dd.mm.yy5 Italian dd-mm-yy6 - dd mon yy7 - mon dd,yy8 - hh:mi:ss9 Default + milliseconds--mon dd yyyyhh:mi:ss:mmmAM(or 10 USA mm-dd-yy11 JAPAN yy/mm/dd12 ISO yymmdd13 Europe Default + milliseconds--dd mon yyyyhh:mi:ss:mmm(24h)14 - hh:mi:ss:mmm(24h)类型0,9,和13总是返回四位的年。对其它类型,要显示世纪,把style值加上100。类型13和14返回24小时时钟的时间。类型0,7,和13返回的月份用三位字符表示(用Nov代表November).对表11.1中所列的每一种格式,你可以把类型值加上100来显示有世纪的年(例如,00年将显示为2000年)。例如,要按日本标准显示日期,包括世纪,你应使用如下的语句:SELECT CONVERT(VARCHAR(30),GETDATE(),111)在这个例子中,函数CONVERT()把日期格式进行转换,显示为1997/11/30抽取日期和时间在许多情况下,你也许只想得到日期和时间的一部分,而不是完整的日期和时间。例如,假设你想列出你的站点目录中每个站点被查询的月份。这时你不希望完整的日期和时间把网页弄乱。为了抽取日期的特定部分,你可以使用函数DATEPART(),象这样:SELECT site_name ‘Site Name’,DATEPART(mm,site_entrydate) ‘Month Posted’ FROM site_directory函数DATEPART()的参数是两个变量。第一个变量指定要抽取日期的哪一部分;第二个变量是实际的数据。在这个例子中,函数DATEPART()抽取月份,因为mm代表月份。下面是这个SELECT 语句的输出结果:Site Name Month Posted………………………………………………………………Yahoo 2Microsoft 5Magicw3 5(3 row(s) affected)Month Posted列显示了每个站点被查询的月份。函数DATEPART()的返回值是一个整数。你可以用这个函数抽取日期的各个不同部分,如表11.2所示。表11.2 日期的各部分及其简写日期部分 简写 值year yy 1753--9999quarter qq 1--4month mm 1--12day of year dy 1--366day dd 1--31week wk 1--53weekday dw 1--7(Sunday--Saturday)hour hh 0--23minute mi 0--59second ss 0--59milisecond ms 0--999当你需要进行日期和时间的比较时,使用函数DATEPART()返回整数是有用的。但是,上例中的查询结果(2,5)不是十分易读。要以更易读的格式得到部分的日期和时间,你可以使用函数DATENAME(),如下例所示:SELECT site_name ‘Site Name’DATENAME(mm,site_entrydate) ‘Month Posted’FROM site_directory函数DATENAME()和函数DATEPART()接收同样的参数。但是,它的返回值是一个字符串,而不是一个整数。下面是上例该用DATENAME()得到的结果:Site Name Month Postec………………………………………………………………….Yahoo FebruaryMicrosoft JuneMagicw3 June(3 row(s) affected)你也可以用函数DATENAE()来抽取一个星期中的某一天。下面的这个例子同时抽取一周中的某一天和日期中的月份:SELECT site_name ‘Site Name’,DATENAME(dw,site_entrydate)+ ‘-’ + DATENAME(mm,site_entrydate)‘Day and Month Posted’ FORM site_directory这个例子执行时,将返回如下的结果:Site Name Day and Month Posted………………………………………………………………………Yahoo Friday - FebruaryMicrosoft Tuesday - JuneMagicw3 Monday - June(3 row(s) affected)返回日期和时间范围当你分析表中的数据时,你也许希望取出某个特定时间的数据。你也许对特定的某一天中――比如说2000年12月25日――访问者在你站点上的活动感兴趣。要取出这种类型的数据,你也许会试图使用这样的SELECT语句:SELECT * FROM weblog WHERE entrydate=”12/25/20000”不要这样做。这个SELECT语句不会返回正确的记录――它将只返回日期和时间是12/25/2000 12:00:00:000AM的记录。换句话说,只有刚好在午夜零点输入的记录才被返回。注意:在本节的讨论中,假设字段entrydate是DATETIME型,而不是SMALLDATETIME型。本节的讨论对SMALLDATETIME型字段也是适用的,不过SMALLDATETIME型字段只能精确到秒。问题是SQL Sever将用完整的日期和时间代替部分日期和时间。例如,当你输入一个日期,但不输入时间时,SQL Sever将加上缺省的时间“12:00:00:000AM”。当你输入一个时间,但不输入日期时,SQL Sever将加上缺省的日期“Jan 1 1900”。要返回正确的记录,你需要适用日期和时间范围。有不止一种途径可以做到这一点。例如,下面的这个SELECT 语句将能返回正确的记录:SELECT * FROM weblog WHERE entrydate>=”12/25/2000” AND entrydate<”12/26/2000”这个语句可以完成任务,因为它选取的是表中的日期和时间大于等于12/25/2000 12:00:00:000AM并小于12/26/2000 12:00:00:000AM的记录。换句话说,它将正确地返回2000年圣诞节这一天输入的每一条记录。另一种方法是,你可以使用LIKE来返回正确的记录。通过在日期表达式中包含通配符“%”,你可以匹配一个特定日期的所有时间。这里有一个例子:SELECT * FROM weblog WHERE entrydate LIKE ‘Dec 25 2000%’这个语句可以匹配正确的记录。因为通配符“%”代表了任何时间。使用这两种匹配日期和时间范围的函数,你可以选择某个月,某一天,某一年,某个小时,某一分钟,某一秒,甚至某一毫秒内输入的记录。但是,如果你使用LIKE 来匹配秒或毫秒,你首先需要使用函数CONVERT()把日期和时间转换为更精确的格式(参见前面“转换日期和时间”一节)。比较日期和时间最后,还有两个日期和时间函数对根据日期和时间取出记录是有用的。使用函数DATEADD()和DATEDIFF(),你可以比较日期的早晚。例如,下面的SELECT语句将显示表中的每一条记录已经输入了多少个小时:SELECT entrydate ‘Time Entered’DATEDIFF(hh,entrydate,GETDATE()) ‘Hours Ago’ FROM weblog如果当前时间是2000年11月30号下午6点15分,则会返回如下的结果:Time Entered Hours Ago…………………………………………………..Dec 30 2000 4:09PM 2Dec 30 2000 4:13PM 2Dec 1 2000 4:09PM 698(3 row(s) affected)函数DADEDIFF()的参数是三个变量。第个变量指定日期的某一部分。在这个例子中,是按小时对日期进行比较,(要了解日期各部分的详细内容,请参考表11.2)在日期2000年11月1日和2000年11月30日的指定时间之间有689个小时。另外两个参数是要进行比较的时间。为了返回一个正数,较早的时间应该先给。函数DATEADD()把两个日期相加。当你需要计算截止日期这一类的数据时,这个函数是有用处的。例如,假设访问者必须先注册才能使用你的站点。注册以后,他们可以免费使用你的站点一个月。要确定什么时候他们的免费时间会用完,你可以使用如下的SELECT语句:SELECT username ‘User Name’,DATEADD(mm,1,firstvisit_date) ‘Registration Expires’FROM registration_table函数DATEADD()的参数有三个变量。第一个变量代表日期的某一部分(参见表11.2),这个例子用到了代表月份的mm。第二个变量指定了时间的间隔――在本例中是一个月。最后一个变量是一个日期,在这个例子中,日期是取自DATETIME型字段firstvisit_date.假设当前日期是June 30,2000,这个语句将返回如下的内容:User Name Registration Expires……………………………………………………………………………Bill Gates Jul 30 2000 4:09PMPresident Clinton Jul 30 2000 4:13PMWilliam Shakespeare Jul 1 2000 4:09PM(3 row(s) affected)注意:与你预料的相反,使用函数DATEADD()把一个日期加上一个月,它并不加上30天。这个函数只简单地把月份值加1。这意味着在11月注册的人将比在2月注册的人多得到2天或3天的时间。要避免这个问题,你可以用函数DATEADD()直接增加天数,而不是月份。发送邮件你可以用SQL Sever发送简单的e_mail信息。要做到这一点,你需要在你的系统中安装邮件服务器,如Microsoft Exchange Sever(参见第四章“Exchange Active Sever,Index Sever,和NetShow”)。你还需要配置SQL Sever 以识别邮件服务器。要让SQL Sever 能识别邮件服务器,启动事务管理器并从菜单中选择SeverSQL MailConfigue,这时会出现一个如图11.3所示的对话框。输入你在邮件服务器中注册的用户名和口令,然后单击OK。注意:如果你使用Microsoft Exchange Sever,配置SQL Sever的过程将会大大不同。你需要在同一个(域)用户帐号下运行Microsoft SQL Sever和Exchange Sever。你还需要在安装了SQL Sever的机器上安装Exchange Cliect并给这个帐号创建一个一个配置文件。完成这些之后,你就可以在SQL Mail Configuration对话框中输入该配置文件的名字。图11。3在发送邮件之前,你要先启动SQL Mail。从菜单中选择SeverSQL MailStart。如果你的邮件服务器配置正确,并且你输入了正确的用户名和口令,则SQL Mail会成功启动。注意:你可以把SQL Sever配置为自动启动邮件服务。要做到这一点,在Set Sever Optons对话框(从菜单中选择SeverSQL SeverConfigure)中选择Auto Start Mail Client即可。要发送一个邮件,你可以使用名为xp_sendmail的扩展存储过程。这里有一个如何使用这个过程的例子:master..xp_sendmail “president@whitehouse.gov”,”Hello Mr. President”这个过程调用向e_mail地址president@whitehouse.gov发送一个简单的email信息:“Hello Mr. President”。你可以用任何其它的email地址和信息取代上例中相应的内容,但是,你所发送的信息不能超过255个字符长度。当你想随时了解你的站点数据库的状态时,存储过程xp_sendmail是有用处的。例如,你可以向一个页面管理程序发送信息。如果你的站点出了什么问题,你马上就可以知道。下一章将讲述更多有关存储过程的内容。总结这一章加深了你的SQL知识。你学会了如何建立索引,使你的查询速度更快。你还学会了如何插入,删除和更新一个表中的数据,如何使用集合函数得到一个表中数据的统计信息。最后,你学会了许多有价值的表达式,函数和过程,用来操作字符串,日期和时间及邮件。下一章将进一步加深你对Microsoft SQL Sever的掌握。你将学习如何用SQL来进行程序设计,如何建立存储过程,触发器和执行计划。更另人兴奋的是,你将学会让SQL Sever自动创建网页的一个简单方法。

Tuesday, March 24, 2009

A Publisher's Introduction to the Semantic Web

Presentations
White Papers
Newsletters
A Publisher's Introduction to the Semantic Web
by Ed Stevenson
Spamoflage( 'ES', 'Email' );
May 2005
Although the concept of the Semantic Web began to surface in the early part of this century (see this early Scientific American article from Tim Berners-Lee, James Hendler, and Ora Lassila), it is now reaching buzzword status. Some publishers have been using technologies considered to be "semantic" for a few years, but it is a new topic for many others.
What do the concepts behind the Semantic Web mean for publishers? Well, first let's ask—what is the Semantic Web? The point of the Semantic Web is a richer interconnectiveness among all objects (or content), thereby allowing us to pull data from various sources to discover new meaning and present in different formats. A simpler view is that the Semantic Web makes better use of metadata. That is, all objects on the web are assigned rich data to describe themselves (in a universal and standardized format), and tools are better able to make use of that data.
Creating richer metadata
Almost all publishers use metadata in some capacity. Most also use taxonomies (a hierarchy of terms used to categorize content), although they might not call them by that name. The next step beyond that is the use of ontologies. Just as taxonomies make metadata or controlled vocabularies look "flat," ontologies do the same to taxonomies. Ontologies describe more detailed relationships among concepts and provide a higher level of richness in the metadata.
Taxonomies are just like the animal and plant kingdom taxonomies, in which every species lives in a particular branch. However other, more conceptual objects don't always have that clear lineage. If we created a taxonomy based on colors with the three primary colors—red, yellow, and blue—as the top nodes, orange would need to be related to both red and yellow. In a simple taxonomy, we would probably repeat the term "orange" under both, but in a technical sense they would really be two distinct nodes that have the same name.
In an ontology, orange can be represented as the exact same concept appearing in multiple nodes on the tree. In fact, an ontology is not a tree at all. It is a complex mapping of concepts with defined relationships between those concepts (such as "part of" or "subclass of").
In their most expanded use, ontologies can in themselves be valuable collections of information and almost become database-like in nature. Imagine an ontology that captures court "metadata" for a legal publisher. That publisher may currently have a taxonomy with branches for federal courts, district courts, state courts, etc. But in this "flat" taxonomy, there is probably no implicit relationship between the local and district courts or state courts or to geographical boundaries like state or congressional district lines. In an ontology those relationships can be established. Of course, documents are still tagged to nodes in the ontology, but even without the documents, the ontology becomes a very valuable piece of content.
RDF and OWL: expressing richer metadata with W3C standards
The W3C standard framework for expressing metadata (including taxonomies and ontologies) is RDF (Resource Description Framework).
RDF provides a standard framework for expressing information about resources (metadata) that allows for complex definition of relationships, polyheirarchal taxonomies (giving a node multiple parents), and the ability to combine taxonomies (by connecting a detailed taxonomy to a broader taxonomy through a common node). The purpose of RDF is to create a syntax to capture rich metadata and relationships and allow the processing of this data by applications.
The RDF data model expresses relationships among resources in what is called "triples." These triples define two things and the relationship between them. Each triple consists of a subject, a predicate and an object (sometimes called the resource, property, and value). The subject (or resource) is the "thing" the statement is about, the predicate (or property) specifies a characteristic or property of the subject, and the object (or value) is the value of that characteristic or property.
The following illustration is an RDF graph representing a triple that illustrates the simple metadata value of the author of this newsletter article:
Where RDF gets interesting is when you start to combine triples, such as making the author the subject of another triple describing his email address or company affiliation.
The PRISM metadata standard can often be expressed in RDF, many RSS feeds use RDF syntax, and Adobe's XMP (eXtensible Metadata Platform) for embedding metadata within media objects makes use of RDF.
But being a structured framework, RDF is more syntax (structure) than semantics (meaning). OWL (Web Ontology Language) is the W3C effort to provide a standard for the types of relationships that can be expressed in RDF. OWL provides for an XML vocabulary to express hierarchies and relationships. OWL introduces specific property vocabularies, such as "sameAs" and "intertsectionOf." OWL provides a shared meaning in the RDF syntax.
Topic Maps: expressing richer metadata with ISO standards
In semantic circles, there is often discussion about RDF vs. topic maps. In most conceptual ways topic maps are very similar to RDF with some slight and subtle distinctions. Both have different origins. Whereas RDF came through the W3C, topic maps are an ISO standard and arose to address the need to create indexes (like back of the book indexes). Topic maps prime focus is on the topics (or subjects); RDF focuses on the resources. Although both were created for somewhat different purposes, both do very similar things.
Topic maps describe topic structures and associate them with resources. Like RDF, topic maps break from the traditional hierarchal taxonomy and offer much more robust classification, indexing, and relationship descriptions. Topic maps allow for the creation of complex topical descriptions which then point out to resources. There is a separation between the topical information (the index) and the content which is associated to specific topics within it.
The topic maps "language" uses topics, occurrences, and associations in its model where the topic is the resource (the thing or the subject), the occurrence is the resource that has some association with the topic, and the association is a type of relationship. You can see from a very high level the similarities with the RDF model. Note in topic maps the association is two way, that is if my topic is this article, the association is "is authored by" and the occurrence is "Ed Stevenson," the inverse is also true - that is Ed Stevenson (topic) authored (association) this article (occurrence).
It is beyond this introductory article to fully explore the differences between the two and much work has been done in that area. Additionally, the W3C has started a RDF/Topic Maps Interoperability Task Force to look for interoperability between the two. See http://www.w3.org/2001/sw/BestPractices/RDFTM/ for more information.
First steps for publishers
So if you never knew about the Semantic Web but now have the overview, what should you do next? It can be difficult to take the intellectual concepts behind the Semantic Web and apply them to practical day-to-day use in a publishing process. But it is important to be aware of the issues and the potential they have. The following are a few suggestions on preparing your publishing organization for the Semantic Web.
Consider using RDF if you are implementing or re-engineering or enhancing any process capturing metadata. Even if the full power of RDF is not harnessed initially (which it most likely will not be), starting down the path is the first step. There will be more tools and reusable code for translating RDF.
If you are defining or building metadata (or taxonomies or ontologies) look to industry standards first. Consider Dublin Core and PRISM for basic metadata and look to industry specific standards as well (like IEEE and NISO). Borrow where you can.
Value librarians on your staff. They are important now, but are generally undervalued. They will be critical in expanding taxonomies into ontologies and managing the complex relationships.
See the links mentioned in the interview with David Wood in this issue to get some hands-on experience with Semantic Web tools.
Keep an eye on Semantic Web topics through news groups, publications, web sites, etc. Some Semantic Web experts predict the health sciences industry to be some of first adopters of semantic technologies. See the next section for good resources to learn more of the basics as well as stay on top of the latest news and trends.
Monitor the actions of publishers who are using Semantic Web technologies and how they make them worthwhile.
Further reading
In addition to the links found within the document, the following offer good information on the Semantic Web (and were consulted for this article):
W3C RDF Primer
TAO of Topic Maps
O'Reilly's XML.com offers plenty of informative articles all grouped together under a link from the main left navigation: http://www.xml.com/semweb/

Sunday, March 22, 2009

09书单

年又过年,去年在写在译的书又长出了一茬,又是时候订一份今年的读书计划。

架构师类

架构类的书从来都很少,今年又长出两本来。

架构技术类

虽然云计算已经开始代替SOA成为新一代大词,但回顾一下整个SOA出版风潮,自己觉得值得一读不忽悠的居然只有一本《SOA in Practic - SOA实践指南-分布式系统设计的艺术》 。

在热潮彻底退却前,SOA书还在继续出着,OSGI与SCA的书开始出现:

  • 《SOA Design Patterns》  -- 又是Thomas Erl的书,很奇怪的连电子版都找不到。
  • 还 没出版的呀 --《SOA Patterns》、《ESB Architecture for SOA》、《SOA with Java》、《Open Source SOA》、《OSGi in Action》、《SpringSource dm Server in Action》、《Modular Java: Creating Flexible Applications with OSGi and Spring》、《Understanding SCA》、《Apache Tuscany in Action》

编程匠师类

立志做一个匠师的人,今年会比较幸福。

bash编程

bash编程

bash脚本编写教程

说明,这部教程是我在网上找到比较经典的BASH中文教程,对新手学习bash脚本,bash编程的好文章。

建立一个脚本

   Linux中有好多中不同的shell,但是通常我们使用bash (bourne again shell) 进行shell编程,因为bash是免费的并且很容易使用。所以在本文中笔者所提供的脚本都是使用bash(但是在大多数情况下,这些脚本同样可以在 bash的大姐,bourne shell中运行)。
  如同其他语言一样,通过我们使用任意一种文字编辑器,比如nedit、kedit、emacs、vi
  等来编写我们的shell程序。
  程序必须以下面的行开始(必须方在文件的第一行):
#!/bin/sh
  符号#!用来告诉系统它后面的参数是用来执行该文件的程序。在这个例子中我们使用/bin/sh来执行程序。
  当编辑好脚本时,如果要执行该脚本,还必须使其可执行。
  要使脚本可执行:
chmod +x filename
  然后,您可以通过输入: ./filename 来执行您的脚本。
注释
  在进行shell编程时,以#开头的句子表示注释,直到这一行的结束。我们真诚地建议您在程序中使用注释。如果您使用了注释,那么即使相当长的时间内没有使用该脚本,您也能在很短的时间内明白该脚本的作用及工作原理。
变量
  在其他编程语言中您必须使用变量。在shell编程中,所有的变量都由字符串组成,并且您不需要对变量进行声明。要赋值给一个变量,您可以这样写:
变量名=值
  取出变量值可以加一个美元符号($)在变量前面:
#!/bin/sh
#对变量赋值:
a="hello world"
# 现在打印变量a的内容:
echo "A is:"
echo $a
  在您的编辑器中输入以上内容,然后将其保存为一个文件first。之后执行chmod +x first
  使其可执行,最后输入./first执行该脚本。
  这个脚本将会输出:
A is:
hello world
  有时候变量名很容易与其他文字混淆,比如:
num=2
echo "this is the $numnd"
  这并不会打印出"this is the 2nd",而仅仅打印"this is the ",因为shell会去搜索变量numnd的值,但是这个变量时没有值的。可以使用花括号来告诉shell我们要打印的是num变量:
num=2
echo "this is the ${num}nd"
  这将打印: this is the 2nd
  有许多变量是系统自动设定的,这将在后面使用这些变量时进行讨论。
  如果您需要处理数学表达式,那么您需要使用诸如expr等程序(见下面)。
  除了一般的仅在程序内有效的shell变量以外,还有环境变量。由export关键字处理过的变量叫做环境变量。我们不对环境变量进行讨论,因为通常情况下仅仅在登录脚本中使用环境变量。
Shell命令和流程控制
  在shell脚本中可以使用三类命令:
1)Unix 命令:
  虽然在shell脚本中可以使用任意的unix命令,但是还是由一些相对更常用的命令。这些命令通常是用来进行文件和文字操作的。
常用命令语法及功能
  echo "some text": 将文字内容打印在屏幕上
  ls: 文件列表
  wc –l filewc -w filewc -c file: 计算文件行数计算文件中的单词数计算文件中的字符数
  cp sourcefile destfile: 文件拷贝
  mv oldname newname : 重命名文件或移动文件
  rm file: 删除文件
  grep 'pattern' file: 在文件内搜索字符串比如:grep 'searchstring' file.txt
  cut -b colnum file: 指定欲显示的文件内容范围,并将它们输出到标准输出设备比如:输出每行第5个到第9个字符cut -b5-9 file.txt千万不要和cat命令混淆,这是两个完全不同的命令
  cat file.txt: 输出文件内容到标准输出设备(屏幕)上
  file somefile: 得到文件类型
  read var: 提示用户输入,并将输入赋值给变量
  sort file.txt: 对file.txt文件中的行进行排序
  uniq: 删除文本文件中出现的行列比如: sort file.txt | uniq
  expr: 进行数学运算Example: add 2 and 3expr 2 "+" 3
  find: 搜索文件比如:根据文件名搜索find . -name filename -print
  tee: 将数据输出到标准输出设备(屏幕) 和文件比如:somecommand | tee outfile
  basename file: 返回不包含路径的文件名比如: basename /bin/tux将返回 tux
  dirname file: 返回文件所在路径比如:dirname /bin/tux将返回 /bin
  head file: 打印文本文件开头几行
  tail file : 打印文本文件末尾几行
   sed: Sed是一个基本的查找替换程序。可以从标准输入(比如命令管道)读入文本,并将结果输出到标准输出(屏幕)。该命令采用正则表达式(见参考)进行搜索。 不要和shell中的通配符相混淆。比如:将linuxfocus 替换为 LinuxFocus :cat text.file | sed 's/linuxfocus/LinuxFocus/' > newtext.file
  awk: awk 用来从文本文件中提取字段。缺省地,字段分割符是空格,可以使用-F指定其他分割符。cat file.txt | awk -F, '{print $1 "," $3 }'这里我们使用,作为字段分割符,同时打印第一个和第三个字段。如果该文件内容如下: Adam Bor, 34, IndiaKerry Miller, 22, USA命令输出结果为:Adam Bor, IndiaKerry Miller, USA
2) 概念: 管道, 重定向和 backtick
  这些不是系统命令,但是他们真的很重要。
  管道 (|) 将一个命令的输出作为另外一个命令的输入。
grep "hello" file.txt | wc -l
  在file.txt中搜索包含有”hello”的行并计算其行数。
  在这里grep命令的输出作为wc命令的输入。当然您可以使用多个命令。
  重定向:将命令的结果输出到文件,而不是标准输出(屏幕)。
  > 写入文件并覆盖旧文件
  >> 加到文件的尾部,保留旧文件内容。
反短斜线
 使用反短斜线可以将一个命令的输出作为另外一个命令的一个命令行参数。
  命令:
find . -mtime -1 -type f -print
  用来查找过去24小时(-mtime –2则表示过去48小时)内修改过的文件。如果您想将所有查找到的文件打一个包,则可以使用以下脚本:
#!/bin/sh
# The ticks are backticks (`) not normal quotes ('):
tar -zcvf lastmod.tar.gz `find . -mtime -1 -type f -print`
  3) 流程控制
  "if" 表达式 如果条件为真则执行then后面的部分:
if ....; then
  ....
elif ....; then
  ....
else
  ....
fi
  大多数情况下,可以使用测试命令来对条件进行测试。比如可以比较字符串、判断文件是否存在及是否可读等等…
  通常用" [ ] "来表示条件测试。注意这里的空格很重要。要确保方括号的空格。
[ -f "somefile" ] :判断是否是一个文件
[ -x "/bin/ls" ] :判断/bin/ls是否存在并有可执行权限
[ -n "$var" ] :判断$var变量是否有值
[ "$a" = "$b" ] :判断$a和$b是否相等
  执行man test可以查看所有测试表达式可以比较和判断的类型。
  直接执行以下脚本:
#!/bin/sh
if [ "$SHELL" = "/bin/bash" ]; then
 echo "your login shell is the bash (bourne again shell)"
else
 echo "your login shell is not bash but $SHELL"
fi
  变量$SHELL包含了登录shell的名称,我们和/bin/bash进行了比较。
快捷操作符
  熟悉C语言的朋友可能会很喜欢下面的表达式:
[ -f "/etc/shadow" ] && echo "This computer uses shadow passwors"
   这里 && 就是一个快捷操作符,如果左边的表达式为真则执行右边的语句。您也可以认为是逻辑运算中的与操作。上例中表示如果/etc/shadow文件存在则打印” This computer uses shadow passwors”。同样或操作(||)在shell编程中也是可用的。这里有个例子:
#!/bin/sh
mailfolder=/var/spool/mail/james
[ -r "$mailfolder" ]' '{ echo "Can not read $mailfolder" ; exit 1; }
echo "$mailfolder has mail from:"
grep "^From " $mailfolder
  该脚本首先判断mailfolder是否可读。如果可读则打印该文件中的"From" 一行。如果不可读则或操作生效,打印错误信息后脚本退出。这里有个问题,那就是我们必须有两个命令:
  -打印错误信息
  -退出程序
  我们使用花括号以匿名函数的形式将两个命令放到一起作为一个命令使用。一般函数将在下文提及。
  不用与和或操作符,我们也可以用if表达式作任何事情,但是使用与或操作符会更便利很多。
  case表达式可以用来匹配一个给定的字符串,而不是数字。
case ... in
...) do something here ;;
esac
  让我们看一个例子。 file命令可以辨别出一个给定文件的文件类型,比如:
file lf.gz
  这将返回:
lf.gz: gzip compressed data, deflated, original filename,
last modified: Mon Aug 27 23:09:18 2001, os: Unix
 我们利用这一点写了一个叫做smartzip的脚本,该脚本可以自动解压bzip2, gzip 和zip 类型的压缩文件:
#!/bin/sh
ftype=`file "$1"`
case "$ftype" in
"$1: Zip archive"*)
  unzip "$1" ;;
"$1: gzip compressed"*)
  gunzip "$1" ;;
"$1: bzip2 compressed"*)
  bunzip2 "$1" ;;
*) error "File $1 can not be uncompressed with smartzip";;
esac
  您可能注意到我们在这里使用了一个特殊的变量$1。该变量包含了传递给该程序的第一个参数值。也就是说,当我们运行:
smartzip articles.zip
$1 就是字符串 articles.zip
  select 表达式是一种bash的扩展应用,尤其擅长于交互式使用。用户可以从一组不同的值中进行选择。
select var in ... ; do
 break
done
.... now $var can be used ....
下面是一个例子:
#!/bin/sh
echo "What is your favourite OS?"
select var in "Linux" "Gnu Hurd" "Free BSD" "Other"; do
    break
done
echo "You have selected $var"
  下面是该脚本运行的结果:
What is your favourite OS?
1) Linux
2) Gnu Hurd
3) Free BSD
4) Other
#? 1
You have selected Linux
  您也可以在shell中使用如下的loop表达式:
while ...; do
....
done
   while-loop 将运行直到表达式测试为真。will run while the expression that we test for is true. 关键字"break" 用来跳出循环。而关键字”continue”用来不执行余下的部分而直接跳到下一个循环。
  for-loop表达式查看一个字符串列表 (字符串用空格分隔) 然后将其赋给一个变量:
for var in ....; do
 ....
done
  在下面的例子中,将分别打印ABC到屏幕上:
#!/bin/sh
for var in A B C ; do
 echo "var is $var"
done
  下面是一个更为有用的脚本showrpm,其功能是打印一些RPM包的统计信息:
#!/bin/sh
# list a content summary of a number of RPM packages
# USAGE: showrpm rpmfile1 rpmfile2 ...
# EXAMPLE: showrpm /cdrom/RedHat/RPMS/*.rpm
for rpmpackage in $*; do
 if [ -r "$rpmpackage" ];then
  echo "=============== $rpmpackage =============="
  rpm -qi -p $rpmpackage
 else
  echo "ERROR: cannot read file $rpmpackage"
 fi
done
  这里出现了第二个特殊的变量$*,该变量包含了所有输入的命令行参数值。如果您运行showrpm openssh.rpm w3m.rpm webgrep.rpm
  此时 $* 包含了 3 个字符串,即openssh.rpm, w3m.rpm and webgrep.rpm.
引号
   在向程序传递任何参数之前,程序会扩展通配符和变量。这里所谓扩展的意思是程序会把通配符(比如*)替换成合适的文件名,它变量替换成变量值。为了防 止程序作这种替换,您可以使用引号:让我们来看一个例子,假设在当前目录下有一些文件,两个jpg文件, mail.jpg 和tux.jpg。

#!/bin/sh
echo *.jpg
  这将打印出"mail.jpg tux.jpg"的结果。
  引号 (单引号和双引号) 将防止这种通配符扩展:
#!/bin/sh
echo "*.jpg"
echo '*.jpg'
  这将打印"*.jpg" 两次。
  单引号更严格一些。它可以防止任何变量扩展。双引号可以防止通配符扩展但允许变量扩展。
#!/bin/sh
echo $SHELL
echo "$SHELL"
echo '$SHELL'
  运行结果为:
/bin/bash
/bin/bash
$SHELL
  最后,还有一种防止这种扩展的方法,那就是使用转义字符——反斜杆:
echo *.jpg
echo $SHELL
  这将输出:
*.jpg
$SHELL
Here documents
   当要将几行文字传递给一个命令时,here documents(译者注:目前还没有见到过对该词适合的翻译)一种不错的方法。对每个脚本写一段帮助性的文字是很有用的,此时如果我们四有那个 here documents就不必用echo函数一行行输出。 一个 "Here document" 以 << 开头,后面接上一个字符串,这个字符串还必须出现在here document的末尾。下面是一个例子,在该例子中,我们对多个文件进行重命名,并且使用here documents打印帮助:
#!/bin/sh
# we have less than 3 arguments. Print the help text:
if [ $# -lt 3 ] ; then
cat <
ren -- renames a number of files using sed regular expressions
USAGE: ren 'regexp' 'replacement' files...
EXAMPLE: rename all *.HTM files in *.html:
 ren 'HTM$' 'html' *.HTM
HELP
 exit 0
fi
OLD="$1"
NEW="$2"
# The shift command removes one argument from the list of
# command line arguments.
shift
shift
# $* contains now all the files:
for file in $*; do
  if [ -f "$file" ] ; then
   newfile=`echo "$file" | sed "s/${OLD}/${NEW}/g"`
   if [ -f "$newfile" ]; then
    echo "ERROR: $newfile exists already"
   else
    echo "renaming $file to $newfile ..."
    mv "$file" "$newfile"
   fi
  fi
done
   这是一个复杂一些的例子。让我们详细讨论一下。第一个if表达式判断输入命令行参数是否小于3个 (特殊变量$# 表示包含参数的个数) 。如果输入参数小于3个,则将帮助文字传递给cat命令,然后由cat命令将其打印在屏幕上。打印帮助文字后程序退出。如果输入参数等于或大于3个,我们 就将第一个参数赋值给变量OLD,第二个参数赋值给变量NEW。下一步,我们使用shift命令将第一个和第二个参数从参数列表中删除,这样原来的第三个 参数就成为参数列表$*的第一个参数。然后我们开始循环,命令行参数列表被一个接一个地被赋值给变量$file。接着我们判断该文件是否存在,如果存在则 通过sed命令搜索和替换来产生新的文件名。然后将反短斜线内命令结果赋值给newfile。这样我们就达到了我们的目的:得到了旧文件名和新文件名。然 后使用mv命令进行重命名。
函数
  如果您写了一些稍微复杂一些的程序,您就会发现在程序中可能在几个地方使用了相同的代码,并且您也会发现,如果我们使用了函数,会方便很多。一个函数是这个样子的:
functionname()
{
# inside the body $1 is the first argument given to the function
# $2 the second ...
body
}
  您需要在每个程序的开始对函数进行声明。

  下面是一个叫做xtitlebar的脚本,使用这个脚本您可以改变终端窗口的名称。这里使用了一个叫做help的函数。正如您可以看到的那样,这个定义的函数被使用了两次。
#!/bin/sh
# vim: set sw=4 ts=4 et:
help()
{
  cat <
xtitlebar -- change the name of an xterm, gnome-terminal or kde konsole
USAGE: xtitlebar [-h] "string_for_titelbar"
OPTIONS: -h help text
EXAMPLE: xtitlebar "cvs"
HELP
  exit 0
}
# in case of error or if -h is given we call the function help:
[ -z "$1" ] && help
[ "$1" = "-h" ] && help
# send the escape sequence to change the xterm titelbar:
echo -e "33]0;$107"
#
  在脚本中提供帮助是一种很好的编程习惯,这样方便其他用户(和您)使用和理解脚本。
命令行参数
   我们已经见过$* 和 $1, $2 ... $9 等特殊变量,这些特殊变量包含了用户从命令行输入的参数。迄今为止,我们仅仅了解了一些简单的命令行语法(比如一些强制性的参数和查看帮助的-h选项)。 但是在编写更复杂的程序时,您可能会发现您需要更多的自定义的选项。通常的惯例是在所有可选的参数之前加一个减号,后面再加上参数值 (比如文件名)。
  有好多方法可以实现对输入参数的分析,但是下面的使用case表达式的例子无遗是一个不错的方法。
#!/bin/sh
help()
{
 cat <
This is a generic command line parser demo.
USAGE EXAMPLE: cmdparser -l hello -f -- -somefile1 somefile2
HELP
 exit 0
}
while [ -n "$1" ]; do
case $1 in
  -h) help;shift 1;; # function help is called
  -f) opt_f=1;shift 1;; # variable opt_f is set
  -l) opt_l=$2;shift 2;; # -l takes an argument -> shift by 2
  --) shift;break;; # end of options
  -*) echo "error: no such option $1. -h for help";exit 1;;
  *) break;;
esac
done

echo "opt_f is $opt_f"
echo "opt_l is $opt_l"
echo "first arg is $1"
echo "2nd arg is $2"
  您可以这样运行该脚本:
cmdparser -l hello -f -- -somefile1 somefile2
  返回的结果是:
opt_f is 1
opt_l is hello
first arg is -somefile1
2nd arg is somefile2
  这个脚本是如何工作的呢?脚本首先在所有输入命令行参数中进行循环,将输入参数与case表达式进行比较,如果匹配则设置一个变量并且移除该参数。根据unix系统的惯例,首先输入的应该是包含减号的参数。
实例
  一般编程步骤
  现在我们来讨论编写一个脚本的一般步骤。任何优秀的脚本都应该具有帮助和输入参数。并且写一个伪脚本(framework.sh),该脚本包含了大多数脚本都需要的框架结构,是一个非常不错的主意。这时候,在写一个新的脚本时我们只需要执行一下copy命令:
cp framework.sh myscript
 然后再插入自己的函数。
  让我们再看两个例子:
  二进制到十进制的转换
  脚本 b2d 将二进制数 (比如 1101) 转换为相应的十进制数。这也是一个用expr命令进行数学运算的例子:
#!/bin/sh
# vim: set sw=4 ts=4 et:
help()
{
 cat <
b2h -- convert binary to decimal
USAGE: b2h [-h] binarynum
OPTIONS: -h help text
EXAMPLE: b2h 111010
will return 58
HELP
 exit 0
}
error()
{
  # print an error and exit
  echo "$1"
  exit 1
}
lastchar()
{
  # return the last character of a string in $rval
  if [ -z "$1" ]; then
    # empty string
    rval=""
    return
  fi
  # wc puts some space behind the output this is why we need sed:
  numofchar=`echo -n "$1" | wc -c | sed 's/ //g' `
  # now cut out the last char
  rval=`echo -n "$1" | cut -b $numofchar`
}

chop()
{
  # remove the last character in string and return it in $rval
  if [ -z "$1" ]; then
    # empty string
    rval=""
    return
  fi
  # wc puts some space behind the output this is why we need sed:
  numofchar=`echo -n "$1" | wc -c | sed 's/ //g' `
  if [ "$numofchar" = "1" ]; then
    # only one char in string
    rval=""
    return
  fi
  numofcharminus1=`expr $numofchar "-" 1`
  # now cut all but the last char:
  rval=`echo -n "$1" | cut -b 0-${numofcharminus1}`
}
while [ -n "$1" ]; do
case $1 in
  -h) help;shift 1;; # function help is called
  --) shift;break;; # end of options
  -*) error "error: no such option $1. -h for help";;
  *) break;;
esac
done
# The main program
sum=0
weight=1
# one arg must be given:
[ -z "$1" ] && help
binnum="$1"
binnumorig="$1"

while [ -n "$binnum" ]; do
  lastchar "$binnum"
  if [ "$rval" = "1" ]; then
    sum=`expr "$weight" "+" "$sum"`
  fi
  # remove the last position in $binnum
  chop "$binnum"
  binnum="$rval"
  weight=`expr "$weight" "*" 2`
done
echo "binary $binnumorig is decimal $sum"
#
  该脚本使用的算法是利用十进制和二进制数权值 (1,2,4,8,16,..),比如二进制"10"可以这样转换成十进制:
0 * 1 + 1 * 2 = 2
  为了得到单个的二进制数我们是用了lastchar 函数。该函数使用wc –c计算字符个数,然后使用cut命令取出末尾一个字符。Chop函数的功能则是移除最后一个字符。
  文件循环程序
   或许您是想将所有发出的邮件保存到一个文件中的人们中的一员,但是在过了几个月以后,这个文件可能会变得很大以至于使对该文件的访问速度变慢。下面的脚 本rotatefile 可以解决这个问题。这个脚本可以重命名邮件保存文件(假设为outmail)为outmail.1,而对于outmail.1就变成了outmail.2 等等等等...
#!/bin/sh
# vim: set sw=4 ts=4 et:
ver="0.1"
help()
{
  cat <
rotatefile -- rotate the file name

USAGE: rotatefile [-h] filename

OPTIONS: -h help text
EXAMPLE: rotatefile out
This will e.g rename out.2 to out.3, out.1 to out.2, out to out.1
and create an empty out-file
The max number is 10
version $ver
HELP
  exit 0
}

error()
{
  echo "$1"
  exit 1
}
while [ -n "$1" ]; do
case $1 in
  -h) help;shift 1;;
  --) break;;
  -*) echo "error: no such option $1. -h for help";exit 1;;
  *) break;;
esac
done
# input check:
if [ -z "$1" ] ; then
error "ERROR: you must specify a file, use -h for help"
fi
filen="$1"
# rename any .1 , .2 etc file:
for n in 9 8 7 6 5 4 3 2 1; do
  if [ -f "$filen.$n" ]; then
    p=`expr $n + 1`
    echo "mv $filen.$n $filen.$p"
    mv $filen.$n $filen.$p
  fi
done
# rename the original file:
if [ -f "$filen" ]; then
  echo "mv $filen $filen.1"
  mv $filen $filen.1
fi
echo touch $filen
touch $filen
  这个脚本是如何工作的呢?在检测用户提供了一个文件名以后,我们进行一个9到1的循环。文件9被命名为10,文件8重命名为9等等。循环完成之后,我们将原始文件命名为文件1同时建立一个与原始文件同名的空文件。
调试
  最简单的调试命令当然是使用echo命令。您可以使用echo在任何怀疑出错的地方打印任何变量值。这也是绝大多数的shell程序员要花费80%的时间来调试程序的原因。Shell程序的好处在于不需要重新编译,插入一个echo命令也不需要多少时间。
  shell也有一个真实的调试模式。如果在脚本"strangescript" 中有错误,您可以这样来进行调试:
sh -x strangescript
  这将执行该脚本并显示所有变量的值。
  shell还有一个不需要执行脚本只是检查语法的模式。可以这样使用:
sh -n your_script
  这将返回所有语法错误。
源地址:http://www.opensourceproject.org.cn/article.php?id=355

The Stony Brook Algorithm Repository


Algorithm Implementations in Java




Go to Main Page
This page last modified on 2008-07-10 .
www.algorist.com