登陆大富翁两周年了，自己学到了许多东西，尤其在自己喜欢的SQLSERVER方面吧

登陆大富翁两周年了，自己学到了许多东西，尤其在自己喜欢的SQLSERVER方面吧。从论坛里一页一页的搜索自己喜欢的东西,并整理下来。并得到了许多大富翁的指导，在此表示感谢。并感谢周周共享论坛(www.pigtwo.com)给我一点空间，把自己喜爱的东西发表在上面。自己搜索了许多网站，整理了这个东西。里面可能有许多英文的东西，自己的翻译水平差，也没翻译。类别可能也分的不对，希望大家提出自己的意见，并共同探讨。更希望大家能找到自己有用的东西。

Ugvanxk

2003-09-11


包属性(1)
本文节选自铁道出版社最近出版的
《SQL Server 2000 完全实战:数据转换服务（DTS）》由章立民编著
版权属以上两者所有，请不要转载。

2-9 察看与修改DTS包属性
Microsoft? SQL Server? 2000的数据转换服务允许您透过「DTS设计器」的图形化操作接口或是以程控方式来察看或修改DTS包属性。不过本节的重点，将着重如何使用「DTS设计器」来完成DTS包属性的察看与修改作业。
欲使用「DTS设计器」来察看与修改DTS包属性，请将DTS包开启于「DTS设计器」中，然后如图表2-37所示，使用鼠标右键点击设计空间的空白区域，并从快捷菜单中选取「包属性」指令，接下来，您可以在「DTS包属性」对话框的各个页面中，察看或修改DTS包的各类属性。

图表2-37
在以下的各个段落中，我们就将DTS包属性分门别类，并说明它们的用途以及如何于「DTS包属性」对话框中察看与修改。
包识别信息
于图表2-38所示的「常规」页面中会显示出DTS包的各项识别信息，包括：名称、描述、包与版本的GUID、建立者、建立日期…等等。我们可以发现，其中大多数的属性都是只读的，允许您修改的属性只有描述、执行参数的优先级类别与同时间执行任务的最大数量是多少。

图表2-38
执行与错误信息
您可以切换至图表2-39所示的「日志记录」页面，以便设置是否要将DTS包的执行信息与错误记录下来，并决定是否一发生错误就要停止执行。此外，您还可以设置一旦用来记录DTS包执行信息的记录文件（Log File）发生错误时就停止执行DTS包。

图表2-39
如何记录执行信息
「记录」页面上半部的「记录」区域用来决定要如何记录DTS包的执行信息。
如果您勾选复选框 □将包执行日志记录到 SQL Server，包与步骤的执行信息将会储存至您利用「服务器」下拉列表所指定之SQL Server的msdb数据库中，包的相关信息会存放至sysdtspackagelog表，包中每一个步骤的信息则会存放至sysdtssteplog表。
那些并未执行步骤的相关信息并不会被储存起来。

如果您希望在将执行信息写入Log的过程中若Log本身出现错误，则停止执行DTS包的话，请勾选复选框 □出现日志错误时使包失败。一般而言，Log本身之所以会发生错误，最常见的原因不外乎是事件记录缓冲区太小或是用以记录执行信息的服务器记录表的名称被更改。
如果您想要将sysdtspackagelog与sysdtssteplog表中有关DTS包与其各个步骤之执行信息的记录删除，请按下「删除记录」按钮。
如何记录错误信息
「记录」页面下半部的「错误处理」区域用来决定要如何记录DTS包的错误信息。
欲将DTS包执行时的错误信息记录下来，首先必须于「错误文件」文本输入框中指定错误文件的名称（必须包含完整的磁盘目录路径），由于错误文件是一个文本文件，因此请以 .TXT作为扩展名。如果您所指定的错误文件于DTS包执行时并不存在，它将会自动被建立。错误文件除了会列出各个已执行的步骤与它们的执行结果外，亦会列出所有未被执行的步骤。
如果您希望当DTS包发生第一个错误后就立即停止执行，请勾选复选框 □第一次出错时使包失败；如果您并未勾选此复选框，不论发生多少个错误，DTS包都会持续执行，并且在执行完毕时产生成功的状态信息。
包全局变量
切换至「全局变量」页面，即可替DTS包建立、修改、删除全局变量，全局变量的用途与维护方式另有专节说明，于此不再浪费篇幅赘述。
数据沿袭（Data Lineage）
如果您的DTS包系储存至Meta Data Services，则可以如图表2-40所示，使用「高级」页面之「历程」区域中的选项用来设置数据沿袭属性。如果您将DTS包储存至Meta Data Services，将可透过数据转换服务的数据沿袭功能，判断出任何一部分数据的来源以及套用至该数据的转换。
勾选复选框 □将沿袭变量显示为源列 表示要新建全局数据沿袭变量至此包，但是不将它们写入Meta Data Services中。一般来说，当您需要建立自定义任务以便写入历程追踪与审核信息，可以勾选此复选框。说得更明白些，勾选复选框 □将沿袭变量显示为源列 表示告知数据转换服务您要启用数据审核功能，此意味着，您希望将进出此DTS包之数据的相关信息储存起来，以便据此判断出哪些数据被DTS包导入或修改。
勾选复选框 □将沿袭变量显示为源列 之后，接下来的任务便是决定如何去储存数据沿袭变量中的审核信息，此处唯一的选择，是去勾选复选框 □将沿袭变量写入知识库 以便将审核信息写入储存机制（Repository）中。

您可别被「储存机制」（Repository）这个名词给吓坏了，储存机制其实就是存放数据的地方，在大多数人的眼中，储存机制就是一个数据库，事实上任何可以存放数据的地方都可以被视为是储存机制。因此，储存机制可以是SQL Server数据库、Access数据库、文本文件、XML文件…等等。

然而，在您勾选复选框 □将沿袭变量显示为源列之前，请仔细想想是否真的需要去审核数据并记录下哪些数据被修改。毕竟此举会降低DTS包的执行速度，它甚至会因DTS包本身所处理的数据就非常庞大而造成系统严重的负荷。基于此原因，我们通常只有在测试系统时才会去勾选复选框 □将沿袭变量显示为源列，抑或是当DTS包在实际运行时发生问题而想要去追踪相关数据被做了哪些修改，另外一种状况则是自一个您无权掌控的外部数据来源导入数据，但是却在导入时发生一些问题而需要去追踪导入的数据。

图表2-40
Meta Data Services扫描
透过Meta Data Services扫描选项，您可以替SQL Server以外的数据来源产生「数据库目录」（Catalog）信息。比方说，您想要将一个Microsoft Visual FoxPro数据库中的数据导入SQL Server中，欲达此目的，您可以在Meta Data Services中建立一个DTS包，并使用OLE DB作为所要导入数据的数据来源，此包会去扫描将被导入的数据并将必要的数据库目录信息（包括：表、字段、中继数据…等等）置入DTS Meta Data Services信息模型中。

1. 所谓的「数据库目录」（Catalog）其实指的就是「数据结构」（Data Structure），包括SQL Server、Oracle、Informix、Access、Visual FoxPro…等在内的任何数据库都拥有一个数据结构。在此之所以要称之为数据库目录，主因是数据转换服务实际上是透过OLE DB的catalog函数调用来取得相关信息。
2. 此处的中继数据（Meta Data）指下列信息：
- 主索引键与外部索引键。
- 字段型态、大小、精确度、小数点位数与是否允许Null值。
- 索引。

Microsoft? SQL Server? 2000 Meta Data Services的OLE DB扫描程序会去扫描所要导入的数据，并使用OLE DB的内建功能去分析结构描述（Schema）。扫描程序会使用数据库信息模型（DBM：Database Information Model）与DTS Meta Data Services信息模型，在Meta Data Services中建立一组对应的默认实例对象。

图表2-41
欲使用扫描选项，请切换至「高级」页面，并按下「扫描选项」区域中的「选项」按钮来开启图表2-41所示的「扫描选项」对话框，此对话框中的选项能够让我们进行下列设置：（注：DTS包必须储存至Meta Data Services才能使用扫描选项）
要求DTS包要使用既存的数据库目录，或是固定去彻底扫描数据来源并返回其数据库目录的一个新默认实例。
采用所有的字段或是仅采用相关联的字段。
藉由将整个数据库目录返回，自数据库目录上一次被建立之后在任何结构或数据上的异动都可以取得。以下我们就来说明如何使用「扫描选项」对话框中的选项，首先，您必须勾选复选框 □将包引用解析到所扫描的目录元数据，以便将包连结至Meta Data Services中继数据，然后才能够使用下列两组选项：
应扫描哪些目录？
如果 知识库中存在已扫描的目录，则使用这些目录
如果先前已经扫描过数据库目录并储存下来，而且该数据库目录自前一次扫描后没有异动过，请选取此选项钮，以便沿用前一次所扫描的数据库目录。此举的好处是，不需要再次扫描与储存，因此DTS包能够执行得较快速。但是请注意，如果数据库目录有任何异动，而且您仍然选取此选项钮，将造成DTS包所参考的Meta Data Services中继数据是无意义的。
将所有引用的目录扫描到知识库中
如果选取此选项钮，则每当DTS包被执行时，便会将所有的数据库目录信息（包括：所有的表、字段与中继数据信息）扫描至Meta Data Services中，并且会去更新Meta Data Services信息模型。显而易见地，此举虽然会耗费较多的时间与资源并降低执行效率，但是却是较妥善的做法。
选择扫描选项
如果知识库中没有目录，则扫描目录
如果选取此选项钮，则当Meta Data Services中不存在任何数据库目录时，便会加入数据来源的数据库目录。一般来说，当一个DTS包仍处于开发期间，抑或是DTS包每次被执行时便会寻找一个新的数据来源时，便会选取此选项钮。比方说，假设DTS每天都会接受一个以日期作为文件名称的文件，此时就应该选取此选项钮。
总是扫描目录
如果选取此选项钮，则不论Meta Data Services中是否已存在数据库目录，都会固定将数据来源的数据库目录扫描至Meta Data Services中。时间成本将是选取此选项钮的最大考虑，因此，只有当数据来源的数据库目录异动频率很高时，才选取此选项钮。
至此可以了解，透过上述的扫描选项，将能确保数据库目录信息（亦即数据来源的结构描述）是最新的。当然，您也必须考虑到各个选项对DTS包之执行效率的影响，因此，请根据自身的需求做出适切的设置吧！
事务（Transaction）
所谓的「事务」（Transaction）即是将多项数据库更新作业组合成单一个任务单元，此任务单元中的各项数据库更新作业要就全部被认可（Commit）、否则就全部被复原（RollBack），事务使我们能达到不可部分完成、一致性、隔离与持续等特性。至于更一步的「分布式事务」（Distributed Transaction）则是允许您将多个平台上的各项操作并入单一事务中。
数据转换服务允许您于DTS包中使用事务，然而由于DTS包中的各项作业往往会涉及多个平台，因此其事务将是一种分布式事务，由于Microsoft? SQL Server? 2000的分布式事务系透过「分布式事务协调员」（MS DTC：Microsoft Distributed Transaction Coordinator）来支持，因此欲使DTS包事务能够运行，执行DTS包的计算机必须启动「分布式事务协调员」。
您可以使用下列两种方式来启动「分布式事务协调员」：
如图表2-42所示，使用「SQL Server服务管理器」来启动「分布式事务协调员」。

图表2-42
如图表2-43所示，于SQL Server Enterprise Manager中启动「分布式事务协调员」。

图表2-43
至此我们了解，DTS包亦可是构成事务之各项数据库作业的一份子，如果某一个事务是由多个DTS包所组成，则每一个DTS包与其中的每一步骤都必须执行成功，则该事务才会被视为成功并加以认可，「认可」（Commit）会去实际储存事务中所做的任何异动；只要事务中的任何一个部分失败，则整个事务就被视为失败，当一个事务失败时，系统会恢复至事务开始前的状态，而此一恢复事务中所作之任何更新的程序称之为「复原」（RollBack）。
加入包事务
一个包事务并不会存在，直到包内的某一个步骤尝试加入事务才会替此包建立一个新的事务。如果在第一个事务已认可或复原之前，其它步骤尝试去加入此包事务，这些步骤将会被加入第一个事务中。虽然一个包可以启动好几个事务，但是同一时间只能有一个事务处于作用状态中。
如果一个步骤加入包事务，该步骤所做的任何异动将累积在包事务中；如果一个步骤并未加入包事务，该步骤所做的任何异动会立即认可。一个步骤要能够加入包事务，必须满足下列条件：
必须是支持的任务类型之一
某些任务所执行的操作是不能加入包事务中的。举例来说，文件传输协议任务会直接将文件写入磁盘，此动作根本不可能复原，所以文件传输协议任务不能加入包事务中。
以下所列的各个任务不能加入包事务中：
「文件传输协议任务」
「动态属性任务」
「发送邮件任务」
「复制SQL Server对象任务」
某些任务允许用户去建立它们自己的Script或程序并自DTS中来执行。虽然这些任务能够建立并独自管理它们自己的本机或分布式事务，它们仍然不能访问DTS包事务。这些任务包括：
「ActiveX Script任务」
「执行进程任务」
就以下所列的各个任务而言，如果使用了支持的连接，将可以加入包事务中：
「大量插入任务」
「数据驱动的查询任务」
「转换数据任务」
「执行包任务」
「执行SQL任务」
「消息队列任务」
DTS连接的数据来源必须支持分布式事

[此贴子


DTS包属性(2)
本文节选自铁道出版社最近出版的
《SQL Server 2000 完全实战:数据转换服务（DTS）》由章立民编著
版权属以上两者所有，请不要转载。

一个步骤要能够加入包事务，其DTS连接的数据来源必须支持分布式事务。比方说，如果一个步骤的DTS连接系连接至Microsoft? SQL Server，它就可以加入包事务；如果一个步骤的DTS连接系连接至Microsoft? Access 2000，它将不能加入包事务。尝试将一个使用不被支持之DTS连接的步骤加入包事务，于执行时期将会发生错误。
以下所列的数据来源支持分布式事务
Microsoft OLE DB Provider for SQL Server
ODBC数据来源
ODBC驱动程序必须支持连接属性SQL_ATT_ENLIST_IN_DTS而且此属性务必加以设置。
Microsoft Data Link
Microsoft Data Link系被用来访问任何已安装的OLE DB Provider。一个OLE DB Provider要想加入分布式事务中，它必须采用ITransactionJoin接口。
一般来说，于DTS包中使用事务的整个设置流程如下所示：
1. 启用事务
要能够在DTS包中使用事务的第一件事，就是必须如图表2-44所示，于「DTS包属性」对话框的「高级」页面中，勾选复选框 □ 使用事务，此举表示允许在DTS包中定义及使用任务的事务单元；如果您并未勾选此复选框，将没有任何的包事务会被建立，而且步骤要求加入事务的请求亦会被忽略。

图表2-44
2. 将某一个步骤加入包事务
欲将某一个步骤加入包事务，请开启该步骤的「工作流属性」对话框并切换至「选项」页面，然后勾选复选框 □有事务时联接（如图表2-45所示）。此举会将此步骤加入包事务，而且更新将会一直累积直到认可或复原为止。
当一个步骤加入包事务，此步骤所使用的每一个DTS连接也都会加入分布式事务中。这种DTS连接的所有更新将会累积在包事务中，即使它们起源于一个并未加入包事务的步骤。因此，欲于一个包中针对同一个数据库执行事务与非事务的更新，必须使用两个DTS连接。

图表2-45
3. 反复步骤2的操作，直到您已将所需的各个步骤加入包事务为止。
4. 认可或复原
当包事务被认可，所有累积的更新将会实际写入。当发生下列事件之一时，包事务便会被认可：
一个步骤成功地完成而且复选框 □成功完成此步骤时提交事务 被勾选（此复选框位于「工作流属性」对话框的「选项」页面中）。
包成功地完成而且复选框 □ 成功完成包时提交 被勾选（此复选框位于「DTS包属性」对话框的「高级」页面中）。
当包事务被复原，所有累积的更新将会被恢复而视同没有发生过。当发生下列事件之一时，包事务便会被复原：
包 执行失败。
包执行完毕但是该包的 □ 成功完成包时提交 复选框并未被勾选。
某一个步骤执行失败而且该步骤的 □ 失败时回滚事务 复选框被勾选（此复选框位于「工作流属性」对话框的「选项」页面中）。
继承的事务
我们可以在DTS包之工作流的某一个步骤以执行包任务去执行其它的DTS包，内含执行包任务的包称为「父包」（Parent Package），而被执行包任务所执行的包则称为「子包」（Child Package）。子包可以建立它自己的包事务，抑或是继承父包事务。
当下列两项条件皆成立时，子包就可继承父包事务：
子包系被一个执行包任务所调用。
调用子包的执行包任务亦加入父包事务中。
如图表2-46所示，其中的6个包全部使用事务。每一个包其实都内含数个任务，但是我们仅标示出执行包任务。包A执行包B与C，包B与C则依序执行包D、E与F。已加入包事务的执行包任务会被加上底线。
包A、B与D会在一个包事务中执行；包C与F会在第二个包事务中执行；包E则拥有它自己个别的包事务。

图表2-46
如果以继承的事务执行包，事务行为将有很大的不同，说明如下：
不会有任何新的包事务被启动。步骤会加入继承的父事务中。
不会进行任何认可。特别是：
如果复选框 □ 成功完成此步骤时提交事务 被勾选，则会忽略父事务。
如果复选框 □ 成功完成包时提交 被勾选，则会忽略父事务。
当包完成时不会进行任何复原，即使包执行失败亦是如此。然而，如果您勾选「工作流属性」对话框中的复选框 □ 失败时将事务复原，个别的步骤可能会复原父包事务。
如果一个子包失败，则执行它的执行包任务也会失败。如果一个子包成功地完成，则执行它的执行包任务也会成功地完成。如果一个子包发生许多错误或复原包事务，但是并未勾选其复选框 □第一次出错时使包失败（此选项位于「DTS包属性」对话框的「日志记录」页面中），它将会成功地完成，执行它的执行包任务也会成功地完成。

事务隔离层级
先前定义事务时就曾提及，"隔离"是事务的特性之一，本段落我们就要说明如何去设置事务隔离层级。隔离层级亦指事务准备去接受不一致数据的层级。替您的包设置正确的隔离层级是非常重要的。
隔离层级是一个事务与其它事务隔离的程度。较低的隔离层级可以增加并行性，但却会牺牲数据的正确性。反之，较高的隔离层级可以确保数据是正确的，但会减低并行性。
并行性意指当一个用户去修改数据的同时其它用户目前亦正在修改该数据。您可以透过悲观式并行性或乐观式并行性来解决此一状况。
悲观式并行性会去锁定它所欲读取或更新的数据，而且直到它离开该笔数据时才会解除锁定。因此，锁定的时间可能很长，不过此举可以确保当您在读取该数据时没有任何其它用户能够修改它。
乐观式并行性则是在对数据进行写入时才会去锁定它，因此，您无法确认数据在您读取与更新时是否相同。虽然如此，乐观式并行性只会锁定数据很短的时间，因此有助于数据共享。

图表2-47
您可以如图表2-47所示，从「事务隔离层级」下拉列表中选取一选项来设置隔离层级。透过设置事务隔离层级，可以决定在事务外部的异动有多大范围是事务可见的。特别值得一提的是，事务隔离层级是由是否出现下列现象所定义：
Dirty读取（Dirty Read）
Dirty读取意指一个事务读取了未被认可的数据。举例来说，假设事务1更改了一笔数据记录，事务2在事务1认可它所做的修改前读取了该笔已修改的数据记录，如果事务1放弃该项更新，事务2就会读取到逻辑上被视为是不存在的数据记录。
非可重复读取（Nonrepeatable read）
非可重复读取意指当一个事务读取相同的数据记录超过一次以上，而在两次或多次的读取之间有另外一个事务修改此笔数据记录的内容。由于在相同事务内的多次读取之间数据记录已被修改，使得每次读取到不同的数据值，因而造成不一致的问题。
举例来说，假设事务1读取一笔数据记录，事务2修改或删除了该笔数据记录并认可所做的修改或删除，如果事务1尝试再次读取该笔数据记录，所提取的将是不同的数据值或是发现该笔数据记录已被删除。
幻像（Phantom）
某一个任务读取了某一范围的数据记录且尚未认可，尔后有另外一个任务在此范围中新建了一笔数据记录或删除某一笔既存的数据记录，此举将因为该范围中的记录笔数已经改变而导致未认可该事务的任务无法重复其原先的读取作业。如果一个连接将其事务隔离层级设置成Serializable，SQL Server将使用索引键范围锁定来防止幻像。
举例来说，事务1搜寻出符合特定条件的多笔数据记录以便加以读取，尔后事务2新建了一笔符合事务1之搜寻条件的数据记录，如果事务1再次执行搜寻命令，搜寻所得的数据记录将与先前不尽相同。
以下是「事务隔离层级」下拉列表中各个选项的说明：
Chaos
此选项只有当您在DTS中使用非SQL Server数据来源时才会使用到，而且它亦是最危险的选项。如果您的DTS包写入任何数据，而且没有出现任何错误，您将不能够复原所做的更新－－亦即一旦建立就没有办法被恢复。因此，一个以Chaos层级作业的DTS任务事务并无法将您与其它正在使用数据来源的任务单元隔离开来，因此您包中的事务将会见到其它事务所有未认可的异动。如果其它事务后来复原了它们的异动，您的DTS包将不会受影响而且会如同数据已被认可般的继续执行。此外，包执行的任何更新锁定将不会持续到事务结束。
Read Committed
我想这是您最经常使用到的选项，它提供您包最高等级的数据完整性。一个以此隔离层级运行的包不会见到其它事务所做的异动，直到这些事务已认可为止。在此隔离层级中，不可能发生Dirty读取，但是非可重复读取与幻像则是可能发生的。
Read Uncommitted
此选项与Chaos选项有点类似，然而此选项可适用于SQL Server。一个以此隔离层级运行的包将能够见到其它事务所做的异动。在此隔离层级中，Dirty读取、非可重复读取与幻像都是可能发生的。
Repeatable Read
一个以Repeatable Read隔离层级运行的DTS包可保证包内的每一个事务不会见到其它事务对已读取之数据值所做的任何异动。因此，如果一个事务读取相同的数据两次，它将固定见到原始数据。在此隔离层级中，不会发生Dirty读取与非可重复读取，然而幻像则是可能发生的。
Serializable
一个以Serializable隔离层级运行的DTS包可保证所有并行事务交互所产生的结果与个别完整执行每个事务的结果一样。在此隔离层级中， Dirty读取、非可重复读取与幻像皆不会发生。
OLE DB
如果您勾选复选框 □ 使用OLE DB服务组件，表示使用OLE DB服务组件（预设为IDataInitialize::CreateDBInstance）来启始OLE DB Provider数据来源；如果您不勾选复选框 □ 使用OLE DB服务组件，表示直接以CoCreateInstance来启始数据来源对象。
OLE DB服务组件可提供类似任务阶段共享（Session Pooling）与IRowsetChange的服务，但有些OLE DB Provider可能不支持这类服务。DTS提供者（PackageDSO、RowQueue与FlatFile）以及OLE DB Provider for SQL Server会忽略此一设置


DTS(下)
数据泵：转换数据

    DTS 数据泵是一个 DTS对象，它驱动数据的导入、导出和转换操作。
在执行数据转换、数据驱动查询和并行数据泵任务期间将使用数据泵。执
行这些任务的过程是：在源和目标连接上创建行集合，然后创建数据泵实
例在源和目标间移动这些行。在复制每一行时，将进行转换操作。

    下图中，在第二个步骤中，转换数据任务在 Access DB任务和 SQL
Production DB任务之间进行。转换数据任务用连接间的灰色箭头表示。

                  图 7. 示例：转换数据任务

    要定义从源连接收集的数据，可以生成一个转换任务的查询。DTS 支
持参数化查询，参数化查询允许在执行查询时定义查询值。

    可以将查询键入任务的“属性”对话框，也可以使用数据转换服务查
询设计器，它是一个用图形化方式生成 DTS任务查询的工具。下图中，使
用查询设计器来生成一个查询，该查询联接 pubs 数据库中的三个表。

                图 8. 数据转换服务查询设计器界面

    在转换任务中，还可以定义要对数据进行的更改。下表说明 DTS提供
的内置转换。

转换 说明
复制列 用于在不应用任何转换的情况下将数据直接从源列复制到目标列。
ActiveX 脚本 用于生成自定义转换。请注意，因为转换将逐行进行，ActiveX 脚本可能影响 DTS 软件包的执行速度。
DateTime 字符串 用于将源列中的日期或时间转换为目标列中的另一种格式。
小写字符串 用于将源列转换为小写字符形式，如需要，将其转换为目标数据类型。
大写字符串 用于将源列转换为全大写字符形式，如需要，将其转换为目标数据类型。
中间字符串 用于从源列抽取子字符串，对其进行转换后将结果复制到目标列。
修剪字符串 用于删除源列中字符串的前导空格、尾随空格和嵌入空格，然后将结果复制到目标列。
读取文件 用于打开文件内容（文件名称在源列中指定），然后将内容复制到目标列。
写入文件 用于将源列（数据列）的内容复制到一个文件中，该文件的路径由第二个源列（文件名列）指定。

    还可以用编程方式创建自己的自定义转换。生成自定义转换最快速的
方式是使用活动模板库（ATL ）自定义转换模板，SQL Server 2000 DTS
样本程序中包括该模板。

数据泵错误记录

    SQL Server 2000 中提供了一种记录转换错误的新方法。可以定义三
种异常情况记录文件来记录软件包执行期间的情况：错误文本文件、源错
误行文件和目标错误行文件。
●一般错误信息将写入错误文本文件中。
●如果转换失败，源行出现错误，该行将写入源错误行文件。
●如果插入失败，目标行出现错误，该行将写入目标错误行文件。

    在转换数据的任务中将定义异常情况日志文件。每个转换任务都有自
己的日志文件。

数据泵阶段

    默认情况下，数据泵有一个阶段，即行转换。该阶段是在未选择阶段
的情况下，在转换数据任务、数据驱动查询任务和并行数据泵任务中映射
列一级的转换时配置的。

    多个数据泵阶段是 SQL Server 2000中新增的。通过在 SQL Server
企业管理器中选择多阶段数据泵选项，在数据泵操作过程中的不同阶段都
可以访问数据泵并添加功能。

    将数据行从源复制到目标时，数据泵将按照下图中所显示的基本进程
进行操作。

                        图 9. 数据泵进程

    数据泵处理完最后一行数据后，任务结束，数据泵操作终止。

    如果高级用户要向软件包添加功能，以便软件包能支持任何数据泵阶
段，可以执行以下操作：
●为每个要自定义的数据泵阶段编写一个 ActiveX 脚本阶段函数。如果
  使用 ActiveX 脚本函数来自定义数据泵阶段，不需要该软件包以外的
  其它任何代码。
●使用 Microsoft Visual C++? 创建 COM 对象来自定义所选的数据泵阶
  段。在该软件包之外开发此程序，在执行转换过程中每个所选的阶段时
  将调用该程序。访问数据泵阶段的 ActiveX 脚本方法中每个所选阶段
  都使用不同的函数和输入点；而这种方法则不同，它在数据泵任务执行
  期间提供一个单一输入点，该输入点将被多个数据泵阶段调用。

保存 DTS 软件包的选项

以下选项可用于保存 DTS 软件包：
●Microsoft SQL Server
    如果要将软件包存储到网络中 SQL Server 的任意实例上，该选项将
DTS 软件包保存到 Microsoft SQL Server ，保留一个便于使用的这些软
件包的清单，并在软件包开发进程中添加和删除软件包版本。

●SQL Server 2000 元数据服务
    如果准备跟踪软件包版本、元数据和数据系列信息，该选项将 DTS软
件包保存到元数据服务。

●结构化存储文件
    如希望在网络间复制、移动和发送软件包而不必将其存储在Microsoft
SQL Server数据库中，该选项将 DTS软件包保存到结构化存储文件。

●Microsoft Visual Basic
    如果要将 DTS软件包合并到 Visual Basic 程序中或将其用作 DTS应
用程序开发的原型，该选项将通过 DTS设计器或 DTS导入/ 导出向导所创
建的 DTS软件包保存到 Microsoft Visual Basic 文件。

DTS 作为应用程序开发平台

    DTS 设计器为数据移动任务提供了多种解决方案。因为提供通过编程
方式访问 DTS对象模型的功能，DTS 扩展了可用的解决方案的数目。使用
Microsoft Visual Basic、Microsoft Visual C++或任何其它支持 COM的
应用程序开发系统，都可以使用图形化工具所不支持的功能开发出适合于
您的环境的自定义 DTS解决方案。

DTS 为开发人员提供多种不同方式的支持：
●生成软件包
    无需使用 DTS设计器或 DTS导入/ 导出向导，您就可以开发极为复杂
的软件包，并可访问对象模型中的全套功能。

●扩展软件包
    通过构建自定义任务和转换，可以添加一些适用于您的业务并可在 DTS
内重复使用的新功能。

●执行程序包
    并非一定要使用所提供的工具来执行 DTS软件包，可以用编程方式执
行DTS软件包并通过 COM事件显示进度，并允许构建嵌入的或自定义的DTS
执行环境。

    样本DTS程序有助于了解DTS编程的入门知识。该样本可与SQL Server
2000一起安装。

    如果开发 DTS应用程序，可以重新分发 DTS文件。有关详细信息，请
参阅 SQL Server 2000光盘上的 Redist.txt。

       摘自 [image] http://www.microsoft.com/china/msdn/?MSCOMTB=ICP_MSDN


DTS(中)
还可以用编程方式创建自定义任务，然后使用“注册自定义任务”命
令将其集成到 DTS设计器中。

    为说明如何使用这些任务，这里显示一个简单的 DTS软件包，其中包
括两项任务： Microsoft ActiveX？脚本任务以及发送邮件任务：

              图 2. 具有两项任务的 DTS 软件包

    ActiveX脚本任务可包含任何ActiveX脚本引擎，包括Microsoft Visual
Basic Scripting Edition（VBScript）、Microsoft JScriptActiveState
ActivePerl。这些搜索引擎可以从 [image] http://www.activestate.com 下载。
发送邮件任务可以发送消息，指示软件包已运行。请注意，这些任务尚未
排序。执行软件包时，ActiveX 脚本任务和发送邮件任务将同时运行。

工作流：设置任务优先级

    定义任务组时，通常有一个执行任务的顺序。如果任务已排序，每个
任务将成为进程中的一个步骤。在 DTS设计器中，在 DTS设计器工作表上
操作任务，并使用优先级约束条件来控制执行任务的顺序。

    优先级约束条件继而将软件包中的任务链接起来。下表显示在 DTS中
可以使用的优先级约束条件类型。

优先级约束条件 说明

按完成顺序
（蓝色箭头）
如果希望在任务 1 完成前不执行任务 2，而不考虑执行结果如何，则应使用“按完成顺序”优先级约束条件链接任务 1 和任务 2。

按成功情况
（绿色箭头）
如果希望在任务 1 成功完成前不执行任务 2，应使用“按成功情况”优先级约束条件链接任务 1 和任务 2。

按失败情况
（红色箭头）
如果希望只在任务 1 无法成功执行的情况下才执行任务 2，应使用“按失败情况”优先级约束条件链接任务 1 和任务 2。

    下图显示使用“按完成顺序”优先级约束条件的 ActiveX脚本任务和
发送邮件任务。当 Active X 脚本任务完成后，无论成功或失败，都将运
行发送邮件任务。

图 3. 使用“按完成顺序”优先级约束条件的 ActiveX 脚本任务和发送邮件任务

    可以分别配置发送邮件任务，一个配置为使用“按成功情况”约束条
件，一个配置为使用“按失败情况”约束条件。两个发送邮件任务可根据
ActiveX 脚本成功还是失败发送不同的消息。

                          图 4. 邮件任务

    您还可以对一个任务采用多个优先级约束条件。例如，发送邮件任务
“管理通知”可以在脚本 #1 采用“按成功情况”约束条件，在脚本 #2
采用“按失败情况”约束条件。在这些情况下，DTS 假设存在逻辑关系
“AND ”。因此，只有在脚本 #1 成功执行而脚本 #2 失败的情况下，才
会发送“管理通知”消息。

            图 5.示例：对一项任务采用多个优先级约束条件

连接：访问和移动数据

    要成功执行复制和转换数据的 DTS任务，DTS 软件包必须与它的源和
目标数据以及所有其它数据源（如查找表）建立有效连接。

    创建软件包时，通过从可用的 OLE DB 提供程序和 ODBC 驱动程序列
表选择连接类型，可对连接进行配置。可用的连接类型包括：

● Microsoft 数据访问组件 (MDAC) 驱动程序
用于 SQL Server 的 Microsoft OLE DB 提供程序
Microsoft 数据链接
用于 Oracle 的 Microsoft ODBC 驱动程序

● Microsoft 数据访问组件 (MDAC) 驱动程序
dBase 5
Microsoft Access
HTML 文件（源）
Microsoft Excel 97-2000
Paradox 5.X

● 其它驱动程序
文本文件（源）
文本文件（目标）
其它连接

    DTS 允许您使用任何 OLE DB 连接。使用“连接”工具栏上的图标可
以便捷地访问常用的各种连接。

    下图显示一个使用两种连接的软件包。数据将从 Access 数据库（源
连接）复制到 SQL Server 产品数据库（目标连接）。

              图 6. 示例：使用两种连接的软件包

    此软件包中的第一个步骤是一个执行 SQL任务，该任务检查是否目标
表已经存在。如果存在，将删除它然后重新创建。成功完成执行 SQL任务
后，第二个步骤是将数据复制到 SQL Server 数据库。如果复制操作失败，
第三个步骤是发送一份电子邮件。

       摘自 [image] http://www.microsoft.com/china/msdn/?MSCOMTB=ICP_MSDN


DTS(上)
DTS简介

    大多数组织都使用多种格式和多个位置来存储数据。为了支持决策、
改善系统性能或对现有系统进行升级，经常必须将数据从一个数据存储位
置移动到另一个存储位置。

    Microsoft SQL Server 2000 数据转换服务（DTS ）提供一组工具，
可以从不同的源将数据抽取、转换和合并到一个或多个目标位置。借助于
DTS工具，您可以创建适合于您的组织特定需要的自定义移动解决方案，
如以下方案所示：

    您已在较早版本的 SQL Server 或另一平台上部署了一个数据库应用
程序，如 Microsoft Access.该应用程序的新版本要求 SQL Server 2000，
因此需要您更改数据库架构并转换某些数据类型。

    要复制和转换数据，您可以生成一个 DTS解决方案，该方案可把数据
库对象从原数据源复制到 SQL Server 2000数据库中，同时重新映射列并
更改数据类型。可以使用 DTS工具运行此解决方案，或者将该解决方案嵌
入您的应用程序。

    您必须将几个主要的 Microsoft Excel电子表格合并到 SQL Server
数据库。几个部门会在月末创建这些电子表格，但是并没有设定完成所有
电子表格的时间。

    要合并电子表格，可以生成一个 DTS解决方案，在消息传送到消息队
列时运行该解决方案。此消息触发 DTS从电子表格抽取数据，执行所有定
义的转换然后将数据加载到 SQL Server 数据库中。

    您的数据仓库包含有关业务操作的历史数据，您使用 Microsoft SQL
Server 2000 分析服务来汇总数据。而该数据仓库每晚需要从联机事务处
理（OLTP）数据库进行更新。OLTP系统每天 24 小时都在运行，所以性能
至关重要。

    您可以生成一个 DTS解决方案，该方案使用文件传输协议（FTP ）将
数据文件移动到本地驱动器，将数据加载到一个实际的表中，然后使用分
析服务对数据进行合计。可以安排每晚运行 DTS解决方案，还可以使用新
的 DTS记录选项来跟踪该进程的进行时间，从而可以分析不同时间段内的
性能。

什么是 DTS？

    DTS是一组工具，用于一个或多个数据源（如Microsoft SQL Server、
Microsoft Excel 或 Microsoft Access ）间导入、导出和转换各种数据。
通过 OLE DB （一种数据访问的开放式标准）提供连接，通过用于 ODBC
的 OLE DB 提供程序来支持 ODBC （开放式数据库连接）数据源。

    可以将 DTS解决方案创建为一个或多个软件包。每个软件包可包含一
组已安排好的任务，它们定义要执行的工作、对数据和对象的转换、定义
任务执行的工作流约束条件以及数据源和目标间的连接方式。DTS 软件包
还提供一些服务，如记录软件包执行详细情况、控制事务和处理全局变量。

以下工具可用于创建和执行 DTS 软件包：
●导入/导出向导用于生成相对简单的 DTS 软件包，并支持数据移植和简
  单转换。
●DTS设计器以图形方式来实现 DTS 对象模型，可用于创建具有一系列功
  能的 DTS 软件包。
●DTSRun 是用于执行现有 DTS 软件包的命令提示实用程序。
●DTSRunUI 是 DTSRun 的图形界面，可以传递全局变量以及生成命令行。
●SQLAgent 不是 DTS 应用程序，DTS 将其用于安排软件包的执行。

    使用 DTS对象模型还可以用编程方式创建和运行软件包，生成自定义
任务和自定义转换。

DTS 的新功能

Microsoft SQL Server 2000 中加入了多种 DTS 增强功能和新功能：
●新的 DTS 任务包括 FTP 任务、执行软件包任务、动态属性任务和信息
  队列任务。
●增强的记录功能可以保存每个软件包的执行信息，从而为您保留一个完
  整的执行历史记录，并可以查看每一任务的每个进程的信息。可以生成
  异常情况文件，文件中包含因错误而无法处理的数据行。
●您可以将 DTS 软件包另存为 Microsoft Visual Basic? 文件。
●新的多阶段数据泵允许高级用户自定义不同阶段的数据转换操作。还可
  以将全局变量用作查询的输入参数。
●您可以在 DTS 转换任务和执行 SQL 任务时使用参数化源查询。
●可以使用执行软件包任务将全局变量的值从父软件包动态分配到子软件
  包。

使用 DTS 设计器

    DTS 设计器以图形方式实现 DTS对象模型，使您可以用图形方式创建
DTS 软件包。您可以使用 DTS设计器执行以下任务：
●创建包含一个或多个步骤的的简单软件包。
●创建包括复杂工作流的软件包，这些工作流包括多个步骤，而这些步骤
  使用条件逻辑、事件驱动代码或与数据源的多种连接。
●编辑现有软件包。
    DTS 设计器界面包括一个工作区（用于生成软件包）、几个工具栏
（其中包含可拖动到设计工作表上的软件包元素）以及几个菜单（其中包
含工作流和软件包管理命令）。

                        图 1. DTS 设计器界面

    通过将连接和任务拖动到设计工作表，然后指定工作流的执行顺序，
可以方便地使用 DTS设计器来生成功能强大的 DTS软件包。以下各部分将
对任务、工作流、连接和转换进行定义，并阐释使用 DTS设计器实施 DTS
解决方案的方便性。

任务：定义软件包中的步骤

    一个 DTS软件包通常包括一个或多个任务。每个任务定义一个在软件
包执行期间可能要执行的工作项。可以使用这些任务进行以下操作：
● 转换数据
转换数据任务 用于在源和目标间移动数据，可以选择对数据应用列一级的转换。
数据驱动的查询任务 用于对数据执行灵活的基于 SQL 事务的操作，包括存储过程和 INSERT、UPDATE 或 DELETE 语句。
并行数据泵任务1 只可按编程方式使用，并行数据泵任务执行的功能与转换数据和数据驱动的查询任务相同，但它支持 OLE DB 2.5 或更高版本所定义的分层次的行集合。

● 复制和管理数据
批量插入任务 用于快速将大量数据加载到 SQL Server 表或视图中。
执行 SQL 任务 用于在软件包执行期间运行 SQL 语句。执行 SQL 任务还可以保存作为查询结果的数据。
复制 SQL Server 对象任务 用于将 SQL Server 对象从一个 SQL Server 或其实例复制到另一个 SOL Server 或实例。可以复制对象（如：数据和表）以及对象（如：视图和存储过程）的定义。
传输数据库任务1 用于将 SQL Server 数据库从 SQL Server 7.0 版本或 SQL Server 2000 的一个实例移动或复制到 SQL Server 2000 的一个实例中。
传输错误消息任务1 用于将针对用户的错误消息（通过 sp_addmessage 系统存储过程创建）从 SQL Server 7.0 或 SQL Server 2000 的一个实例复制到 SQL Server 2000 的一个实例中。
传输登录任务 1 用于将登录信息从 SQL Server 7.0 或 SQL Server 2000 的一个实例复制到 SQL Server 2000 的一个实例中。
传输作业任务1 用于将作业从 SQL Server 7.0 或 SQL Server 2000 的一个实例复制到 SQL Server 2000 的一个实例中。
传输主存储过程任务 1 用于将存储过程从 SQL Server 7.0 或 SQL Server 2000 的一个实例的主数据库复制到 SQL Server 2000 的一个实例的主数据库。

● 将任务作为软件包中的作业运行
ActiveX 脚本任务 用于编写代码以执行在其它 DTS 任务中无法执行的功能。
动态属性任务1 用于在软件包运行时从 DTS 软件包以外的源检索值，并将这些值分配给选定的软件包属性。
执行软件包任务1 用于运行软件包内其它的 DTS 软件包。
执行进程任务 用于运行可执行程序或批处理文件。
文件传输协议 (FTP) 任务1 用于从远程服务器或 Internet 下载数据文件。
消息队列任务1 用于从 Microsoft 消息队列发送和接收消息。
发送邮件任务 用于发送电子邮件消息。
分析服务处理任务2 用于对 SQL Server 2000 分析服务中定义的一个或多个对象执行处理。
数据挖掘任务1,2 用于从 SQL Server 2000 分析服务中定义的数据挖掘模式对象中创建谓词查询或输出表。

1 SQL Server 2000 中的新功能。
2 只在安装了 SQL Server 2000 分析服务的前提下可用。


Troubleshooting DTS Problems
Troubleshooting DTS Problems
By Alexander Chigrik

Should you have problems with Data Transformation Services (DTS), review this troubleshooting checklist to find potential solutions.

1. If you receive an error that the license for the installation of SQL Server does not permit the use of DTS to transform data, switch SQL Server to "per-seat" mode.

This error means that you cannot perform distributed operations (for example, replication, data transformation or heterogeneous query services) between MSDE or SQL Server Desktop and the Standard/Enterprise version of SQL Server if it has been installed in "per server" mode.

This is from the SQL Server 7.0 END-USER LICENSE AGREEMENT:

Note Regarding Interaction between Microsoft SQL Server and
Microsoft Data Engine ("MSDE").
If you use MSDE to access or use the services of Microsoft
SQL Server (for example, replication, data transformation or
heterogeneous query services), you must use Microsoft SQL Server in
Per Seat Mode (that is, each unique Device accessing SQL Server must
have a Client Access License dedicated to it).

Note Regarding Use of SQL Server Desktop.
You may only install and use the SQL Server Desktop component of
Microsoft SQL Server on each Device to which a valid Client Access
License for the Server Software has been dedicated. If you use
SQL Server Desktop to access or use the services of any version
of Microsoft SQL Server (for example, replication, data
transformation or heterogeneous query services), you must use
such Microsoft SQL Server in Per Seat Mode.

2. If you can run a DTS package but cannot schedule it, verify that the account the SQLServerAgent service runs under has access to network resources.

When you try to schedule a DTS package, the appropriate job will run under the user account that the SQLServerAgent service is running under. For example, if the SQLServerAgent service runs under the LocalSystem account, you cannot schedule a DTS package as the LocalSystem account does not have network access.

Note. Before scheduling a DTS package, the Schedule service must first be started.

To start the Schedule service on Windows NT, choose:
Start -> Settings -> Control panel -> Services
(choose Startup "Automatic" type) and Start.

To start the Schedule service on Windows 2000, choose:
Start -> Settings -> Control panel -> Administration -> Services
(choose Startup "Automatic" type) and Start.

3. If you get the error: 'DTS Wizard Error - CoCreateInstance Class not Registered', apply the latest service pack or add MAXDOP(1) to the query, which will prevent the query from being parallelized.

This error happens with parallel queries only. It means that one thread of the query is waiting for a message packet from another. To work around this, you should apply the latest service pack or add MAXDOP(1) to the query in order to prevent it from being parallelized.

4. If a DTS package stored in the repository is not listed, try to download the DTS package from the sysdtspackages system table into file on disk for future use.

The text of the DTS package is stored in the PackageData image field of the sysdtspackages system table in the msdb database. To save the DTS package in file, you can use the textcopy.exe utility. See this FAQ question for more details: My DTS package is stored inside SQL Server. Now I cannot open it.

5. Verify that you are using the latest SQL Server service pack.

To check which SQL service pack are you running, see this link: How can I check what SQL service pack am I running?

6. If you receive an error when exporting a database from SQL Server 7.0 to SQL Server 6.5 using the DTS Export Wizard, try to create a destination database before attempting the transfer.

This error arises because DTS incorrectly sends SQL Server 7.0 CREATE DATABASE syntax to SQL Server 6.5.

7. If you receive a 'not enough memory' error when attempting to import a text file larger than the available memory on a Windows 9x client, try to separate the text file into several small files or use a client computer running Windows NT or Windows 2000.

This error arises because DTS Import Wizard calls the command MapViewOfFile in order to display the structure of the file being imported. If the client computer is running Windows 9x, the MapViewOfFile call will attempt to reserve a contiguous area of memory large enough to map the entire file.

So, you can recieve a 'not enough memory' error when attempting to import a text file larger than the available memory on a Windows 9x client.

8. If your database contains space in its name and the DTS Designer does not show columns in build query, try to rename the database to eliminate the space(s) in the database name.

This is a SQL Server 7.0 bug; SQL Server 2000 does not contain such problems.

9. When you use the "Copy table(s) from the source database" option of the SQL Server 7.0 DTS Import Wizard to import tables with a timestamp column, DTS fails.

To work around this problem, you can use the "Use a query to specify the data to transfer" option of the DTS Import Wizard and exclude the timestamp column, you can use the "Transfer objects and data between SQL Server 7.0 databases" option of the DTS Import Wizard and specify which table you want to transfer, or you can define the destination table to use binary(8) for the datatype for the column which will contain the timestamp.

10. The DTS Import/Export Wizard may truncate column strings that are over 255 characters long when exporting columns to a text file and delimited fields are used for exporting.

To work around this problem, you can use fixed fields instead of delimited fields, you can use the bcp utility instead of DTS, or you can create the table column by using a text data type.

11. If the DTS package created on MDAC 2.5/2.6 fails on a computer with a downlevel MDAC (for example, version 2.1), install MDAC 2.5 (or later) on the computer that is to run the DTS package.

12. The DTS Query Builder does not correctly build an SQL statement that begins with the SQL Server inline comment '--'.

To work around this problem, avoid using the inline comments with the DTS Query Builder. Use the '/*...*/' style of comments instead of the '--' inline comments.

13. The SQL Server 7.0 DTS package with an ActiveX script can be truncated to approximately 32,400 characters when saved to a local repository.

To work around this problem, save the DTS package to a file or to SQL Server instead of to the local repository. This is another SQL Server 7.0 bug; SQL Server 2000 does not contain such problems.

14. When you use the "Transfer objects and data between SQL Server 7.0 databases" option of the SQL Server 7.0 DTS Export Wizard, data in columns of a table with datatypes of char or varchar larger than 4000 characters will not be copied.

To work around this problem, use the "Copy table(s) from the source database" option of the SQL Server 7.0 DTS Export Wizard instead of the "Transfer objects and data between SQL Server 7.0 databases".

15. The DTS Import/Export Wizard fails if an Excel file was open during the import/export operation.

To work around this problem, close the Excel file before the import/export operation.

16. You can get a connection busy error if you transfer data using 'Microsoft ODBC Driver for SQL Server' and attempt to preview a stored procedures.

To work around this problem, use 'Microsoft OLE DB Provider for SQL Server' instead of 'Microsoft ODBC Driver for SQL Server'.


DTS 导入导出向导

返回

DTS 导入导出向导

豆豆技术网制作 修改于:2003-5-14 投稿信箱：web@ddhome.net

请记住我们的口号是:让计算机真正大众化,让更多的人了解计算机,使用计算机！！

非本站原创文章可以自由转载、本站原创文章未经同意不得转载！

原创文章版权归本站及作者所有，未经同意不得转载，转载文章必须注明选自豆豆技术网，谢谢合作！


编程细节
1、触发器
2、游标
3、函数
4、存储过程
5、事务

－－－－－－－－－－－－－－－－－－－－－
作者：懒虫 # SapphireStudio .
欢迎访问我们的站点 [image] www.chair3.com
欢迎转载。
－－－－－－－－－－－－－－－－－－－－

这里只打算讲解四部分了，也就最简单、最常用的四部分。

1、触发器。

定义： 何为触发器？在SQL Server里面也就是对某一个表的一定的操作，触发某种条件，从而执行的一段程序。触发器是一个特殊的存储过程。
常见的触发器有三种：分别应用于Insert , Update , Delete 事件。(SQL Server 2000定义了新的触发器，这里不提）

我为什么要使用触发器？比如，这么两个表：

Create Table Student( --学生表
StudentID int primary key, --学号
....
)

Create Table BorrowRecord( --学生借书记录表
BorrowRecord int identity(1,1), --流水号
StudentID int , --学号
BorrowDate datetime, --借出时间
ReturnDAte Datetime, --归还时间
...
)

用到的功能有:
1.如果我更改了学生的学号,我希望他的借书记录仍然与这个学生相关(也就是同时更改借书记录表的学号);
2.如果该学生已经毕业，我希望删除他的学号的同时，也删除它的借书记录。
等等。

这时候可以用到触发器。对于1，创建一个Update触发器：

Create Trigger truStudent
On Student
for Update
-------------------------------------------------------
--Name:truStudent
--func:更新BorrowRecord 的StudentID，与Student同步。
--Use :None
--User:System
--Author: 懒虫 # SapphireStudio (www.chair3.com)
--Date : 2003-4-16
--Memo : 临时写写的，给大家作个Sample。没有调试阿。
-------------------------------------------------------
As
if Update(StudentID)
begin

Update BorrowRecord
Set br.StudentID=i.StudentID
From BorrowRecord br , Deleted d ,Inserted i
Where br.StudentID=d.StudentID

end

理解触发器里面的两个临时的表：Deleted , Inserted 。注意Deleted 与Inserted分别表示触发事件的表“旧的一条记录”和“新的一条记录”。
一个Update 的过程可以看作为：生成新的记录到Inserted表，复制旧的记录到Deleted表，然后删除Student记录并写入新纪录。

对于2，创建一个Delete触发器
Create trigger trdStudent
On Student
for Delete
-------------------------------------------------------
--Name:trdStudent
--func:同时删除 BorrowRecord 的数据
--Use :None
--User:System
--Author: 懒虫 # SapphireStudio (www.chair3.com)
--Date : 2003-4-16
--Memo : 临时写写的，给大家作个Sample。没有调试阿。
-------------------------------------------------------
As
Delete BorrowRecord
From BorrowRecord br , Delted d
Where br.StudentID=d.StudentID

从这两个例子我们可以看到了触发器的关键：A.2个临时的表；B.触发机制。
这里我们只讲解最简单的触发器。复杂的容后说明。
事实上，我不鼓励使用触发器。触发器的初始设计思想，已经被“级联”所替代。

2.游标

在SQL 2000之前，游标可谓是SQL Server心中的痛： 老牛般的速度（CPU），河马般的胃口（内存）。可你却不能不用他。
什么叫游标呢？说白了就是像高级语言一样，是存放数据集，并逐条访问的一种机制。
比如在Delphi里面，要实现类似于这样的功能：（呵呵，不好意思，我只会Delphi，所以只能举一个Delphi的例子）

//这是一段Delphi的源代码
adoDataSet1.Close;
adoDataSet1.CommandText:=' Select * From Student order by StudentID ';
adoDataSet1.Open;
While Not adoDAtaSet1.Eof Do
Begin

YourVar:=adoDAtaSet1.FieldByName('StudentID').AsInteger;
DoSomeThing();
....
adoDataSet1.Next;
End

在SQL Server 并没有很好的数据逐条访问机制，如果有，那就是游标。

还是举例子：

对于表

Create Table BorrowRecord( --学生借书记录表
BorrowRecord int identity(1,1), --流水号
StudentID int , --学号
StudentFeeID int , --费用结算号 （外键）
BorrowDate datetime, --借出时间
ReturnDAte Datetime, --归还时间
Fee Money --借书费用
...
)

Create Table StudentFee( --学生费用结算表
StudentFeeID int primarykey , --费用结算号 （主键）
StudentID int , --学号
BorrowBookAllFee Money, --所有借书总费用
...
)

两者关系为多对一的关系，关联字段为StudentFeeID

由于某种原因StudentFee表的数据遭到了破坏，我想StudentFee循环一遍将“所有借书总费用”重算 。

-----------------------------------------------------------------------
-------------------------------------------------------
--Name:一部分代码
--func:更新学生借书总费用
--Use :
--User:
--Author: 懒虫 # SapphireStudio (www.chair3.com)
--Date : 2003-4-16
--Memo : 临时写写的，给大家作个Sample。没有调试阿。
-------------------------------------------------------

--声明一个游标
Declare curStudentFee Cursor
for
Select StudentFeeID From StudentFee

--声明两个费用变量
Declare @mBorrowBookAllFee Money --总费用
Declare @iStudentFeeID Int --借书结算号

--初始化
Set @mBorrowBookAllFee=0
Set @iStudentFeeID=0

--打开游标
Open curStudentFee

--循环并提取记录
Fetch Next From curStudentFee Into @iStudentFeeID
While ( @@Fetch_Status=0 )
begin

--从借书记录中计算某一学生的借书总记录的总费用
Select @mBorrowBookAllFee=Sum(BorrowBookAllFee)
From BorrowRecord
Where StudentFeeID=@iStudentFeeID

--更新到汇总表。
Update StudentFee Set BorrowBookAllFee=@mBorrowBookAllFee
Where StudentFeeID=@iStudnetFeeID

Fetch Next From curStudentFee Into @mFee
end

--关闭游标
Close curStudentFee

--释放游标
Deallocate curStudentFee

-----------------------------------------------------------------------
关注游标的要点：1、声明、打开、关闭、释放 ； 2、@@Fetch_Status 游标提取状态标志，0表示正确

这里，我也要提到，我不鼓励使用游标。更多的情况下，在SQL 2000 里面 ，函数已经能够实现绝大部分游标的功能。

好累，好不容易算是将1、2点讲完，算是上部分把。后面的几点等会再说了……：（：（。

大家给点鼓励？

懒虫 # SapphireStudio ，精彩世界，尽在 3腿网 （ [image] www.chair3.com ）。


用触发器实现用户登录记录下登录信息(sysprocesses)
如果你非要用触发器，不妨看看select * from sysprocesses的内容。可以给sysprocesses表加个触发器来完成你的要求。如果你用7.0，可以看看Microsoft提供的sp_who的过程！如下：
create procedure sp_who --- 1995/11/28 15:48
@loginame sysname = NULL --or 'active'
as

declare @spidlow int,
@spidhigh int,
@spid int,
@sid varbinary(85)

select @spidlow = 0
,@spidhigh = 32767

if ( @loginame is not NULL
AND upper(@loginame) = 'ACTIVE'
)
begin

select spid ,status
,loginame=rtrim(loginame)
,hostname ,blk=convert(char(5),blocked)
,dbname= db_name(dbid),cmd
from master.dbo.sysprocesses
where spid >= @spidlow and spid <= @spidhigh AND
upper(cmd) <> 'AWAITING COMMAND'

return (0)
end

if (@loginame is not NULL
AND upper(@loginame) <> 'ACTIVE'
)
begin
if (@loginame like '[0-9]%') -- is a spid.
begin
select @spid = convert(int, @loginame)
select spid, status,
loginame=rtrim(loginame),
hostname,blk = convert(char(5),blocked),
dbname=db_name(dbid),
cmd
from master.dbo.sysprocesses
where spid = @spid
end
else
begin
select @sid = suser_sid(@loginame)
if (@sid is null)
begin
raiserror(15007,-1,-1,@loginame)
return (1)
end
select spid, status,
loginame=rtrim(loginame),
hostname ,blk=convert(char(5),blocked),
dbname=db_name(dbid),
cmd
from master.dbo.sysprocesses
where sid = @sid
end
return (0)
end

/* loginame arg is null */
select spid,
status,
loginame=rtrim(loginame),
hostname,
blk=convert(char(5),blocked),
dbname=db_name(dbid),
cmd
from master.dbo.sysprocesses
where spid >= @spidlow and spid <= @spidhigh

return (0) -- sp_who


触发器
1.如果用dbgrid形式编辑，用触发器限制比较好，系统能够提示用中文显示
CREATE TRIGGER [TRGJCDNAME] ON dbo.TABLEJCD
FOR INSERT, UPDATE
AS
DECLARE
@CODE VARCHAR(50),
@XS FLOAT,
@ROWCOUNT INT
--检查代码是否为空

//只支持1条操作，如果有多条操作则跳过
IF @@ROWCOUNT>1 RETURN

SELECT @CODE=LTRIM(ISNULL(CODE,'''')) FROM INSERTED
IF @CODE=''''
BEGIN
ROLLBACK TRAN
RAISERROR(''代码不允许为空或空格!'',16,1)
END
--检查代码是否有重复的
SELECT @ROWCOUNT=COUNT(*) FROM TABLEJCD,INSERTED
WHERE UPPER(TABLEJCD.CODE)=UPPER(INSERTED.CODE) AND (INSERTED.SYSTEMID=TABLEJCD.SYSTEMID)
--如果有重复
IF @ROWCOUNT>1
BEGIN
ROLLBACK TRAN
RAISERROR(''代码重复,请修改!'',16,1)
END
--不允许为0或负数
SELECT @XS=ISNULL(XS,0) FROM INSERTED
IF @XS<=0
BEGIN
ROLLBACK TRAN
RAISERROR(''不允许为0或负数!'',16,1)
END
2.当操作多条记录时（及联删除或更新）
create trigger del_id on tablename for delete
as
delete from tablename where id in(select id from deleted)
3.递规触发器
create tablebudget
(
dept_name varchar(30) not null,
part_name varchar(30) null,
budget_amt money not null)
在上面的表，记录之间是关联，比如部门a,隶属于部门b，顶级部门的父部门为空，我们想到，子部门的预算更新时，其对应的父部门的预算，也要更新

create trigger update_budget
on budget for update as

if (@@rowcount>1)
begin
print ''only on row can be update at a time''
rollback tran
return
end
if (select parent_name from inserted ) is null return
update
set budget_amt=budget_amt+(select budget_amt from inserted)-
(select budget_amt from deleted)
where dept_name=(select parent_name from inserted)

更新时，其对应的父部门

用游标控制还不会，希望大家跟帖。还有其他的好的帖子。


使用嵌套触发器
如果一个触发器在执行操作时引发了另一个触发器，而这个触发器又接着引发下一个触发器……这些触发器就是嵌套触发器。触发器可嵌套至 32 层，并且可以控制是否可以通过"嵌套触发器"服务器配置选项进行触发器嵌套。

如果允许使用嵌套触发器，且链中的一个触发器开始一个无限循环，则超出嵌套级，而且触发器将终止。

可使用嵌套触发器执行一些有用的日常工作，如保存前一触发器所影响行的一个备份。例如，可以在 titleauthor 上创建一个触发器，以保存由 delcascadetrig 触发器所删除的 titleauthor 行的备份。在使用 delcascadetrig 时，从 titles 中删除title_id PS2091 将删除 titleauthor 中相应的一行或多行。要保存数据，可在 titleauthor 上创建 DELETE 触发器，该触发器的作用是将被删除的数据保存到另一个单独创建的名为 del_save 表中。例如：

CREATE TRIGGER savedel
ON titleauthor
FOR DELETE
AS
INSERT del_save
SELECT * FROM deleted

不推荐按依赖于顺序的序列使用嵌套触发器。应使用单独的触发器层叠数据修改。

说明 由于触发器在事务中执行，如果在一系列嵌套触发器的任意层中发生错误，则整个事务都将取消，且所有的数据修改都将回滚。在触发器中包含 PRINT 语句，用以确定错误发生的位置。

递归触发器
触发器不会以递归方式自行调用，除非设置了 RECURSIVE_TRIGGERS 数据库选项。有两种不同的递归方式：

直接递归
即触发器激发并执行一个操作，而该操作又使同一个触发器再次激发。例如，一应用程序更新了表 T3，从而引发触发器 Trig3。Trig3 再次更新表 T3，使触发器 Trig3 再次被引发。

间接递归
即触发器激发并执行一个操作，而该操作又使另一个表中的某个触发器激发。第二个触发器使原始表得到更新，从而再次引发第一个触发器。例如，一应用程序更新了表 T1，并引发触发器 Trig1。Trig1 更新表 T2，从而使触发器 Trig2 被引发。Trig2 转而更新表 T1，从而使 Trig1 再次被引发。

当将 RECURSIVE_TRIGGERS 数据库选项设置为 OFF 时，仅防止直接递归。若要也禁用间接递归，请将 nested triggers 服务器选项设置为 0。

示例
A. 使用递归触发器解决自引用关系
递归触发器的一种用法是用于带有自引用关系的表（亦称为传递闭包）。例如，表 emp_mgr 定义了：

一个公司的雇员 (emp)。

每个雇员的经理 (mgr)。

组织树中向每个经理汇报的雇员总数 (NoOfReports)。
递归 UPDATE 触发器在插入新雇员记录的情况下可以使 NoOfReports 列保持最新。INSERT 触发器更新经理记录的 NoOfReports 列，而该操作递归更新管理层向上其它记录的 NoOfReports 列。

USE pubs
GO
-- Turn recursive triggers ON in the database.
ALTER DATABASE pubs
SET RECURSIVE_TRIGGERS ON
GO
CREATE TABLE emp_mgr (
emp char(30) PRIMARY KEY,
mgr char(30) NULL FOREIGN KEY REFERENCES emp_mgr(emp),
NoOfReports int DEFAULT 0
)
GO
CREATE TRIGGER emp_mgrins ON emp_mgr
FOR INSERT
AS
DECLARE @e char(30), @m char(30)
DECLARE c1 CURSOR FOR
SELECT emp_mgr.emp
FROM emp_mgr, inserted
WHERE emp_mgr.emp = inserted.mgr

OPEN c1
FETCH NEXT FROM c1 INTO @e
WHILE @@fetch_status = 0
BEGIN
UPDATE emp_mgr
SET emp_mgr.NoOfReports = emp_mgr.NoOfReports + 1 -- Add 1 for newly
WHERE emp_mgr.emp = @e -- added employee.

FETCH NEXT FROM c1 INTO @e
END
CLOSE c1
DEALLOCATE c1
GO
-- This recursive UPDATE trigger works assuming:
-- 1. Only singleton updates on emp_mgr.
-- 2. No inserts in the middle of the org tree.
CREATE TRIGGER emp_mgrupd ON emp_mgr FOR UPDATE
AS
IF UPDATE (mgr)
BEGIN
UPDATE emp_mgr
SET emp_mgr.NoOfReports = emp_mgr.NoOfReports + 1 -- Increment mgr's
FROM inserted -- (no. of reports) by
WHERE emp_mgr.emp = inserted.mgr -- 1 for the new report.

UPDATE emp_mgr
SET emp_mgr.NoOfReports = emp_mgr.NoOfReports - 1 -- Decrement mgr's
FROM deleted -- (no. of reports) by 1
WHERE emp_mgr.emp = deleted.mgr -- for the new report.
END
GO
-- Insert some test data rows.
INSERT emp_mgr(emp, mgr) VALUES ('Harry', NULL)
INSERT emp_mgr(emp, mgr) VALUES ('Alice', 'Harry')
INSERT emp_mgr(emp, mgr) VALUES ('Paul', 'Alice')
INSERT emp_mgr(emp, mgr) VALUES ('Joe', 'Alice')
INSERT emp_mgr(emp, mgr) VALUES ('Dave', 'Joe')
GO
SELECT * FROM emp_mgr
GO
-- Change Dave's manager from Joe to Harry
UPDATE emp_mgr SET mgr = 'Harry'
WHERE emp = 'Dave'
GO
SELECT * FROM emp_mgr
GO

以下是更新前的结果：

emp mgr NoOfReports
------------------------------ ----------------------------- -----------
Alice Harry 2
Dave Joe 0
Harry NULL 1
Joe Alice 1
Paul Alice 0

以下为更新后的结果：

emp mgr NoOfReports
------------------------------ ----------------------------- -----------
Alice Harry 2
Dave Harry 0
Harry NULL 2
Joe Alice 0
Paul Alice 0


Drop All Triggers
Drop All Triggers

This script produces a script that drops and creates all triggers on a certain database. To use it create the procedure in the master db and save the result as *.sql.

Author: Marino De Veirman
if exists (select * from sysobjects where id = object_id(N'[dbo].[sp_ScriptTriggers]') and OBJECTPROPERTY(id, N'IsProcedure') = 1)
drop procedure [dbo].[sp_ScriptTriggers]
GO

use master
go

create procedure sp_ScriptTriggers
as

/* ************************************************************************************************************* */
/* AUTHOR : De Veirman Marino CREATION DATE : 06/07/2000 */
/* COMPANY : Spector Photo Group N.V. */
/* DESCRIPTION : To use this procedure create it in the master database and run it from any tool that can return */
/* a print statement. Save the output result as *.sql */
/* ************************************************************************************************************* */

set nocount on

declare @name sysname
declare @text varchar(4096)
declare @SQL varchar(255)

declare cur_triggers insensitive cursor for

select name from sysobjects where OBJECTPROPERTY(id, 'ISTRIGGER') = 1

open cur_triggers

fetch next from cur_triggers into @name
while (@@fetch_status <> -1) begin

print '/* DROP TRIGGER ' + @name + ' ----------------------------------------- */'
print '/* SCRIPTED ' + Convert(varchar(50),GETDATE()) + ' ---------------- */'

select @text = 'if exists (select * from sysobjects where id = object_id(N''' + @name + ''') and OBJECTPROPERTY(id, N''IsTrigger'') = 1)'
print @text
select @text = 'drop trigger ' + @name
print @text
print 'GO'

print '/* CREATE TRIGGER ' + @name + '---------------------------------------- */'
print '/* SCRIPTED ' + Convert(varchar(50),GETDATE()) + ' -------------- */'
print ''

select @text = text from syscomments where id = OBJECT_ID(@name)
print @text
print 'GO'

fetch next from cur_triggers into @name
end

close cur_triggers

deallocate cur_triggers

set nocount off


 Drop All Constraints
Drop All Constraints

sp_drop_constraints will drop all constraints on the specified table, including CHECK, FOREIGN KEY, PRIMARY KEY, UNIQUE, and DEFAULT constraints. Compile it in your master database and use it from any user database.

Author: Doug Bass

SET QUOTED_IDENTIFIER OFF SET ANSI_NULLS ON
GO

if exists (select * from sysobjects where id = object_id(N'[dbo].[sp_drop_constraints]') and OBJECTPROPERTY(id, N'IsProcedure') = 1)
drop procedure [dbo].[sp_drop_constraints]
GO

create proc sp_drop_constraints

@tablename sysname

as

-- name: sp_drop_constraints
-- author: douglas bass
-- date: 06/19/2000

-- sp_drop_constraints will drop all constraints on the specified table,
-- including CHECK, FOREIGN KEY, PRIMARY KEY, UNIQUE, and DEFAULT constraints.
-- Compile it in your master database and use it from any user database.

set nocount on

declare @constname sysname,
@cmd varchar(1024)

declare curs_constraints cursor for
select name
from sysobjects
where xtype in ('C', 'F', 'PK', 'UQ', 'D')
and (status & 64) = 0
and parent_obj = object_id(@tablename)

open curs_constraints

fetch next from curs_constraints into @constname
while (@@fetch_status = 0)
begin
select @cmd = 'ALTER TABLE ' + @tablename + ' DROP CONSTRAINT ' + @constname
exec(@cmd)
fetch next from curs_constraints into @constname
end

close curs_constraints
deallocate curs_constraints

return 0

GO
SET QUOTED_IDENTIFIER OFF SET ANSI_NULLS ON
GO


CreateTriggers
CreateTriggers.sql

Create TimeStamp triggers for all user defined Tables This uses a two pass system. The first pass generates the Triggers that affect the UpdateStamp fields and the second pass creates the triggers that affect the CreateStamp fields.

Author: Mike Meerding

/*
* Create TimeStamp triggers for all user defined Tables
* This uses a two pass system. The first pass generates the Triggers that affect the UpdateStamp fields and the
* second pass creates the triggers that affect the CreateStamp fields.
*/
CREATE PROCEDURE CreateTriggers AS

/* Local Variables */
Declare @TableName NVarChar(128)
Declare @Statement NVarChar(512)
Declare @Column_Name NVarChar (128)
Declare @KeyFields NVarChar(256)
Declare @FieldName NVarChar(128)
Declare @Table_ID Int
Declare @PassCount Int

Set @PassCount = 0

WHILE(@PassCount <= 1)
Begin
/* Cursor declaration to figure out names of all user tables */
Declare AllTables CURSOR
For Select Name from sysobjects
Where xtype = "U"
Open AllTables

/* Scan the sysobjects table for names of all user tables */
FETCH NEXT FROM AllTables Into @TableName
While (@@Fetch_Status = 0 )
Begin
Print 'Checking Table ' + @TableNAME

Print 'Finding Primary Key Fields...'

Select @Table_ID = Object_ID(@TableName)

/* Does this table have the required fields ? */
if @PassCount = 0
Set @FieldName = 'UpdateStamp'
else
Set @FieldName = 'CreateStamp'

exec sp_columns @TABLE_NAME = @TableName, @Column_name = @FieldName

IF (@@RowCount = 1)
BEGIN

/* Cursor declaration to figure out names of key fields for a specific table (swiped from sp_pkeys) */
Declare ThisTable CURSOR
FOR
SELECT COLUMN_NAME = convert(sysname, c.name)
From sysindexes i, syscolumns c, sysobjects o, syscolumns c1
Where
o.id = @Table_ID and
o.id = c.id and
o.id = i.id and
(i.status & 0x800) = 0x800 and
c.name = index_col (@TableName, i.indid, c1.colid) and
c1.colid <= i.keycnt and
c1.id = @table_id

Open ThisTable

/* Generate SQL string that will create the trigger which creates the timestamps */
Set @Statement = ''
if exists (select * from sysobjects where id = object_id(N'dbo.' + @FieldName+'_' + @TableName) and OBJECTPROPERTY(id, N'IsTrigger') = 1)
BEGIN
Set @Statement = 'drop trigger dbo.' + @FieldName+'_' + @TableName + char(13)
Print char(13)
Print @Statement
Execute sp_executesql @Statement
Print char(13)
END

Set @Statement = 'Create Trigger dbo.' + @FieldName+'_' + @TableName + ' ON ' + @TableName + char(13)
Set @Statement = @Statement + ' FOR '
IF @PassCount = 0
Set @Statement = @Statement + ' Update '
else
Set @Statement = @Statement + ' Insert '
Set @Statement = @Statement + ' AS ' + char(13)
Set @Statement = @Statement + ' BEGIN ' + char(13)
Set @Statement = @Statement + ' UPDATE ' + @TABLENAME + char(13)
Set @Statement = @Statement + ' SET ' + @FieldName + ' = GetDate() ' + char(13)

/* scan the system tables to build a list of primary keyfield names */
Set @KeyFields = ""
FETCH NEXT FROM ThisTable into @COLUMN_NAME
WHILE (@@FETCH_STATUS= 0)
BEGIN
IF @KeyFields = ""
BEGIN
Set @KeyFields = @COLUMN_NAME
Set @Statement = @Statement + ' WHERE ' + @COLUMN_NAME +' in (SELECT ' + @COLUMN_NAME + ' FROM INSERTED)' + char(13)
END
ELSE
BEGIN
Set @KeyFields = @KeyFields + ", " + @COLUMN_NAME
Set @Statement = @Statement + ' AND ' + @COLUMN_NAME +' in (SELECT ' + @COLUMN_NAME + ' FROM INSERTED)' + char(13)
END

FETCH NEXT FROM ThisTable into @COLUMN_NAME
END

Print 'Keyfields are :- ' + @KeyFields

Close ThisTable
Deallocate ThisTable

Set @Statement = @Statement + ' END' + char(13)
Print char(13)
Print @Statement

If @KeyFields != ""
Execute sp_executesql @Statement
Print char(13)
END

FETCH NEXT FROM AllTables Into @TableName
END
Set @PassCount = @PassCount + 1

Close AllTables
Deallocate AllTables

End


 Get Triggers for the Table
By: mohit108
Use this script to get Insert, Update and Delete trigger for all the tables. This script will give you just the first level of triggers. So if you have more than 1 insert trigger, it will give you the first one.

Mohit Nayyar
MCP (.Net), MCSD, MCDBA

select a.name 'Table', i.name 'Insert Trigger', u.name 'Update Trigger', d.name 'Delete Trigger'
from sysObjects a, sysObjects i, sysObjects u, sysObjects d
where ((a.deltrig>0 or a.instrig>0 or a.updtrig>0) and a.type != 'TR')
and a.instrig *= i.id and a.updtrig *= u.id and a.deltrig *= d.id

--Use the next line for specific table check
-- and a.name = 'TableName'


Auditing Your SQL Server - Part 1
Auditing Your SQL Server - Part 1
Introduction
I constantly see postings in our forums and others regarding auditing SQL Server. I know there is lots of confusion as well as a variety of philosophies for how one goes about auditing, so I decided to drop my thoughts down and provide some guidance for others. Not that I'm correct or these are the best ways, but hey, I like to write and share my opinions.

So, what is auditing?

Well, there are two types of auditing to me. Server auditing and data auditing. I'll tackle server auditing in later articles and start my look at data auditing here. It's a complex topic and I'm expecting at least 3 articles on data auditing, but we'll see as I dive into this and get feedback from all of you out there.

To me, data auditing is the act of looking at the changes that occur within your database to your data. Someone changes a row, adds a new one, deletes one, etc. and you need to know what happened. This is typical in most systems that I've worked with in that a manager somewhere wants to know who changed a particular row. It may be rows in multiple tables, but the manager is looking at his screen and the order screen shows him something different than (what he remembers) it did yesterday. Without some type of system in place for tracking changes of data, this can be a HUGE pain in your rear when he comes traipsing down to your office.

So where do you start? Well, I'll start by examining a simple auditing system.

Simple Auditing
The easy and simple way to audit is to track the changes in a row. I know, I know, there are all kinds of holes in this approach, but bear with me a minute because I've seen this type of auditing (and implemented it) in a number of cases.

Let's setup a simple table, similar to one I built for a scheduling application.

create table ServerBackup
( MyPK int -- PK for the table. The values aren't important here
, ServerName varchar(80) -- Name of the server
, BackupFile varchar(200) -- Name of the backup file
, BackupDate datetime -- Date of the backup file
, BackupSize int -- Size of the file in bytes
, Modified datetime -- date the record was modified
, ModifiedBy varchar(80) -- user who modified the record.
)

This table is part of a scheduling system and contains information that is manually entered by a person who "backs up" or archives a server resource. The application allows each person to perform the backup (a manual process) and then record the information about the file name, date, and size of the file. Some of this information is pre-filled, but the user verifies it.

The auditing in this case is handled by triggers such as the one below:

create trigger ServerBackup_tri on ServerBackup for Insert
as
update Sb
set Modified = getdate()
, ModifiedBy = suser_sname()
from inserted i
inner join ServerBackup sb
on i.mypk = sb.mypk

There is a similar trigger for updates, but both of these triggers work by "stamping" the record with the last update based on the server time and the logged in user.

Pros and Cons
I'm sure lots of people are thinking this is a stupid way to audit records. And you may be right, but this method works well in our case for a few reasons. First, this is for internal auditing of a small application. Only about 6 people have access to this information and it is primarily for catching mistakes, like someone put the size at 6MB instead of 6GB. There are no external reporting requirements and the last information saved is "good enough" for this application.

This auditing also has very little impact on the server. There is nothing to install, not overhead for object creation, and it's simple and self documenting. Anyone who can look at this record can see the audit.

There are problems with this type of audit, however. Not in my case, but most times the tracking of changes for a record is required. For example, if there were multiple backups made, and the filename was changed, the old data is lost. Only the most recent data is in the row and there is no way to backtrack through the old data.

There is also the loss of auditing data itself. If Bob changes this row and puts in the right information and then Andy updates the row with incorrect information, we lose the fact that Bob entered anything since we cannot track this change.

Conclusions
I'm sure lots of you have other suggestions and I'd welcome hearing them, however keep in mind this is a very simple auditing system. It can be used in conjunction with other systems, like logging to a separate table, something I will tackle in the next article.

Steve Jones
ヾkRanch.net February 2003


Auditing Your SQL Server - Part 2
Auditing Your SQL Server - Part 2 - Using a Mirror
Introduction

Auditing your SQL Server can be a tough task. The amount of activity and variety of events make this a challenging operation. This article continues my series on auditing by looking at a more advanced method of auditing changes to a table. Part 1 of this series examined simple auditing using in row fields for tracking the last time and user who made a change.

More Advanced Auditing

In the last article, we looked at simple auditing using additional fields in a row. This article takes this a step further by using a mirror table to track the changes to all fields. While the amount of detail captured in this level of auditing is much greater, you pay the price with additional overhead on the server during insert/update/delete operations as well as additional space needs for storing the audit data.

What is a mirror table? The easiest way to explain this is with an example. Let's start with the Northwind database and assume that we want to track the changes to the Orders table. I'll start with a clean Northwind database, so you can reset yours to follow along. The instnwnd.sql script in your /MSSQL/Install folder will clean your version. This table is a fairly standard orders table that looks like many I have seen. Let's create a mirror table:

I cheated by using the Object Browser in Query Analyzer to script this table out quickly. Each field in this table corresponds to the matching field in the original table. Now let's add a few fields. Specifically, let's add these rows to the end of the script:

, modifiedby varchar(40)
, modified datetime
, action char(1)

These are similar fields to those used in part 1 to "mark" the time and user who made a change. These are an important part of performing an audit, allowing us to quickly and easily determine the order in which changes occur. this table is designed to "mirror" the Orders table and record all of the information that is in that table along with dates and times of change.

Auditing to a Mirror Table
The next step is to implement the auditing of actions on the Orders table. To do this, we'll again use a trigger that records the audit information for us. Specifically, we'll use 3 triggers, one for inserts, one for updates, one for deletes. For the sake of brevity, I'm only showing the insert trigger in the article. The update and delete triggers are available in the code download for this article.

The insert trigger, which I'll call Orders_tri, according to my naming convention, will duplicate the information inserted into Orders into Orders_audit. The code looks like:

create trigger Orders_tri on orders for insert
as
insert orders_audit
select
*
, suser_sname()
, getdate()
, 'I'
from inserted i
return

A pretty basic trigger, and one which will take all the rows inserted into this table and add them to the Orders_Audit table along with the login user name, the current server date and time, and mark these rows as "I" for inserts. The delete trigger functions in the same manner by inserting all the rows from "deleted" into the Orders_Audit table using a "D" to mark them. The update trigger is just like this trigger, only inserting the new data, but using a "U" to mark the update.

In practice, this looks like the following. Let's assume we've just implemented auditing (which you have if you've run the code download) and Orders_Audit is empty. We now add a new order:

insert Orders
select 1,2,3,4,5

If we now check the Order_Audit table, we will see that there is a new row that exactly matches the new row in Orders with a few additional fields:

select * from Orders where orderid = xxx
select * from Orders_Audit

-------------------
OrderID CustomerID ......

OrderID CustomerID ...... ModifiedBy Modified Action

Suppose I now wish to modify the order because the quantity was incorrect. I issue the following:

update orders
set quantity = 11
where orderid =

A further check of Order_Audit shows that there are now two rows. One is the original insert and the other is the updated row.

select * from Orders_Audit

-------------------
OrderID CustomerID ...... ModifiedBy Modified Action

The results are abbreviated, but here we see the two rows. As a final test, let's remove the order with a "delete orders where orderid = xx". After issuing this statement, we will see an Order_Audit table that looks like:

select * from Orders where orderid = xxx
select * from Orders_Audit

-------------------
OrderID CustomerID ......
--------- ----------
0 rows affected

OrderID CustomerID ...... ModifiedBy Modified Action

I reformatted the results, but basically the order is removed from the system. Without some type of auditing, we would have no idea that there even was an order like this ever entered. However by tracing through the audit table, we can find out the history of this row.

Pros and Cons
So is this a good idea? the end all for your auditing needs? Probably not. This type of auditing imposes overhead, essentially an extra write for each updated row along with index overhead on the indexes (you do want indexes on the audit table). Essentially 2x the overhead for each transaction that modifies data.

It also increases the amount of storage that is required for each table. Sometimes exponentially as there will be a copy of each row for every modification. If your application were to issue:

update orders set quantity = 6 where orderid = 12
update orders set price = 12.5 where orderid = 12

then you will have two copies of this row, one for each update. An inefficiently written application (ever run into one of these?) can quickly overwhelm the auditing capabilities of the server.

On the positive side, this type of auditing can allow you to trace exactly what the history of each row may be. I have implemented this type of auditing in financial applications (for certain tables) where the audit trail must be extremely precise. and detailed. The tradeoffs are worth it because the liability can be very high.

Conclusions
A step above Part 1 and auditing just the date and time of a change. However this type of auditing forces you to pay a price in disk space and overhead. In Part 3, I'll examine a few ways to manage this audit data and keep control of your server.

As always, I welcome feedback on this article, including your suggestions, ideas, and criticisms.

Steve Jones
ヾkRanch.net March 2003


Auditing Your SQL Server - Part 3
Auditing Your SQL Server - Part 3 - Managing Audit Data
Introduction

Auditing anything generates a tremendous amount of data. In the past, line printers were used in many applications, merely printing a new line on a piece of paper each time an event occurred, someone entered a door, a phone call was completed, etc. If you've ever used a system like this you have a great appreciation for the amount of paper that is consumed.

In Part 2 - Using a Mirror, I used a mirror table, essentially a copy of the original table to store copies of the data rows at points in time to track the changes that were made. This type of auditing generates large numbers of rows, sometimes a large multiple of rows relative to the original table. So how do you manage this data?
Separation

One of the ways that I've used to manage this data is to store the audit data in a separate database. In many applications, you will find that the audit data is examined very rarely. In fact, you will usually find that most of the audit data is never looked at again.

Using a separate database is very easy, simply create a database, continuing the example from Part 2, I'd create a Northwind_Audit database, potentially on a separate physical disk if the amount of activity was high. When creating this database, keep in mind that your users will need access to this database to write the audit data, so user management becomes slightly more complex.

If I were auditing the Orders table, as in Part 2, I'd next create my Orders_Audit table in the Northwind_Audit database. The structure would be the same, but the location would be different. My triggers would stay the same, but I'd add an additional object to Northwind:

create view Orders_Audit
as
select *
from Northwind_Audit.dbo.Orders_Audit
go

By using a view, I've abstracted out the location of the audit table. I've also used this type of structure to "move" my audit data from the main database to another one as it grew. If I implemented linked servers, this technique would also allow me to move the audit data to another server without disturbing the application. I would merely recompile the view.

Using a separate database also brings some additional benefits. First the data is stored in another file, so this data can be moved without disturbing the main data. In the event of a disaster, I can more quickly restore the application data because I do not have to restore the audit data at the same time.

Archiving
Generating this much data will eventually become a problem for everyone. At some point, the amount of archive data can quickly grow larger than the amount of real data. Dealing with this data can be another problem in and of itself.

Not having an archive plan is a worst practice, but doubly so for audit data. This is something that's easy to implement and then you can forget about it, but you need to do it. I've got a couple of different methods of doing this.

First you can merely detach the audit database and create a new audit database periodically. This works well and keeps the data handy in a form that it can easily be restored. I did this quarterly for one system and it allowed us to attach the database back as Northwind_Audit_2002_Q3, or whatever the date was and then access the data. The downsides were that our application could not easily find this data and custom queries were required to report on the audit.

Another solution that worked much better was to BCP or DTS out some amount of old data periodically. In most cases I've found that the last 30 or 60 days of audit data was most often accessed. After that it was rare that someone needed to track the history of a record, and even then, they usually knew the time period that needed to be audited.

To handle this, I wrote a script that would BCP out data that was older than 60 days into a file that was named for the time period. Using the Northwind.dbo.Orders example, I'd bcp out the data from December 2002 as "Northwind_Orders_Audit_200212.txt. I like text files because I can easily view them in Notepad or any editor. This script was set to run monthly and would also zip up the text file, which was then backed up to tape. If someone needed older audit data, we could BCP it back into the Northwind_Audit.dbo.Orders_Audit table, allow them to query it from their application, and then delete it out of the table. We still had the text file, so this could be repeated as needed.

The downside to this was that we had to take manual action to restore the data as well as remember to remove it (otherwise the next time the script ran, it would duplicate audit data). This also could be cumbersome if someone wanted a large amount of audit data. You might spend your entire data doing BCPs.

Conclusions
Building an archive plan and managing your audit data is something that invariable is a custom solution. You need to understand the audit review requirements to know what frequency to archive data and whether or now a separation of the audit data from the source data is needed.

In Part 4, I'll look at selective auditing of particular types of data, a custom solution that makes the audit data management easier to deal with.

As always, I welcome feedback on this article, including your suggestions, ideas, and criticisms.

Steve Jones
ヾkRanch.net March 2003


首先备份数据库
然后备份文件，备份日志文件，可改名
在 查询分析器里执行 sp_attach_single_file_db,将生成新的日志文件
具体怎么做，我也没做过，让有经验的人回答。，我去收集一下这方面的资料


从大洋网摘录的方法，未试过
用 bcp命令把数据库中的记录都导出来保存到另一台机器，然后用truncate table tablename
的方式把所有记录都清空，然后执行dump transaction dbname with no_log,发现log文件已显著减少，再用bcp命令导入，导入后log文件又增大，但再用 dump transaction dbname with no_log,效果不仅是使日志占的空间减少，日志文件的size也显著减少。


作者 几回魂梦

前几天也碰到日志文件过大的问题，数据库实际大小为600M, 日志文件实际大小为33M, 但日志文件占用空间为2.8G!!!
试了多种方式，SHIRNK DATABASE， TRUNCATE LOG FILE, 都没办法将文件缩小。无论如何，这应该算SQL SERVER的一个BUG吧。

后来找到下面的代码，就可以将日志文件缩小到自己想要的大小了。把代码COPY到查询分析器里，，然后修改其中的3个参数(数据库名，日志文件名，和目标日志文件的大小)，运行即可(我已经用过多次了)
-----
SET NOCOUNT ON
DECLARE @LogicalFileName sysname,
@MaxMinutes INT,
@NewSize INT

USE Marias -- 要操作的数据库名
SELECT @LogicalFileName = 'Marias_log', -- 日志文件名
@MaxMinutes = 10, -- Limit on time allowed to wrap log.
@NewSize = 100 -- 你想设定的日志文件的大小(M)

-- Setup / initialize
DECLARE @OriginalSize int
SELECT @OriginalSize = size
FROM sysfiles
WHERE name = @LogicalFileName
SELECT 'Original Size of ' + db_name() + ' LOG is ' +
CONVERT(VARCHAR(30),@OriginalSize) + ' 8K pages or ' +
CONVERT(VARCHAR(30),(@OriginalSize*8/1024)) + 'MB'
FROM sysfiles
WHERE name = @LogicalFileName
CREATE TABLE DummyTrans
(DummyColumn char (8000) not null)

DECLARE @Counter INT,
@StartTime DATETIME,
@TruncLog VARCHAR(255)
SELECT @StartTime = GETDATE(),
@TruncLog = 'BACKUP LOG ' + db_name() + ' WITH TRUNCATE_ONLY'

DBCC SHRINKFILE (@LogicalFileName, @NewSize)
EXEC (@TruncLog)
-- Wrap the log if necessary.
WHILE @MaxMinutes > DATEDIFF (mi, @StartTime, GETDATE()) -- time has not expired
AND @OriginalSize = (SELECT size FROM sysfiles WHERE name = @LogicalFileName)
AND (@OriginalSize * 8 /1024) > @NewSize
BEGIN -- Outer loop.
SELECT @Counter = 0
WHILE ((@Counter < @OriginalSize / 16) AND (@Counter < 50000))
BEGIN -- update
INSERT DummyTrans valueS ('Fill Log')
DELETE DummyTrans
SELECT @Counter = @Counter + 1
END
EXEC (@TruncLog)
END
SELECT 'Final Size of ' + db_name() + ' LOG is ' +
CONVERT(VARCHAR(30),size) + ' 8K pages or ' +
CONVERT(VARCHAR(30),(size*8/1024)) + 'MB'
FROM sysfiles
WHERE name = @LogicalFileName
DROP TABLE DummyTrans
SET NOCOUNT OFF


发贴心情
理解日志截断选项
不同的备份日志截断选项经常被DBA新手所忽视。DBA新手通常并不知道选项存在或了解它们的作用。怎样使用或什么时候时候它们，以下的几小节详细解释每个选项的作用以及在什么时候使用它。
TRUNCATE_ONLY
TRUNCATE_ONLY选项截掉事务日志的非活动部分，而不备份（拷贝）日志到备份设备上。因为日志没有拷贝，所以在使用TRUNCATE_ONLY时，不必指明备份设备。例如，用TRUNCATE_ONLY选项备份主数据库事务日志的语法如下：
Backup Log master
WITH TRUNCATE_ONLY
在以下情况下使用TRUNCATE_ONLY：
如果你不是为了恢复目的使用事务日志，并且依赖于完整数据库备份（完整或差异）。如果没有执行数据库备份就使用TRUNCATE_ONLY选项，你将不能在带有TRUNCATE_ONLY的BACKUP LOG命令执行时，恢复事务日志非活动部分的已完成事务。
NO_LOG
当有NO_LOG选项的BACKUP LOG命令执行时，SQL SERVER不记录BACKUP LOG命令，就截断事务日志的非活动部分。
仅当事务日志完全填满时才使用NO_LOG选项，当日志完全填满时，你不能通过执行一条普通的BACKUP LOG命令来截断事务日志。这样是因为SQL SERVER试图记录BACKUP命令，而在事务日志中却没有剩余空间。和TRUNCATE_ONLY选项一样，NO_LOG选项不需要备份设备，因为日志并不拷贝到设备上去。
NO_TRUNCATE
当你试图进入的数据库被破坏并打算恢复数据库时，使用NO_TRUNCATE选项。要使用NO_TRUNCATE，必须满足以下条件：
事务日志必须和数据库在不同的设备上。
MASTER数据库必须没有被破坏
NO_TRUNCATE记录从最近一次事务日志备份，到数据库破坏点的所有事务日志项。然后恢复事务日志备份作为最近一次备份，它在恢复过程中，可精确到毫秒级。


很多时候，我们被数据库日志文件大小不断在增加而困挠，虽然可以用截短事务日志的命令dump transaction database_name with no_log来使日志占用实际物理log文件的空间的百分比减小，但数据库log文件的把磁盘的空间霸占着不用，使其他的程序所需的空间受到影响。为此，我做了很多次试验，以探讨能够直接减小log文件大小的方法，请方家指教！

根据db_option中的有关选项，在不同设置时，做dump transaction database_name with no_log操作后，发现log文件的total space都不变化，只是used space变小，而free space相应变大。这样的变化意味着，你以后的日志还有可写入的空间，因为空间被预留了。但当这个log文件已经太大，而影响了其他程序的使用空间时，这样的结果并不是我需要的。

后来我做了一个这样的操作，用bcp命令把数据库中的记录都导出来保存到另一台机器的硬盘上。然后用truncate table table_name的方式把所有的记录都清空，然后执行dump transaction database_name with no_log，发现log文件已经显著地减小，再用bcp命令将之前导出的数据导入到数据库中，导入完成后，log文件又增大了，但再用dump transaction database_name with no_log命令操作时，效果不仅是使日志占用空间减少，日志文件的size也显著地减小。

对于以上现象，请有兴趣的同道予以验证，至于这个操作对数据库的物件和完整性是否有影响，还望高手指教！


周周共享论坛--『数 据 库』浏览：UNDO Committed Transactions

总访问量: 43764 今日访问: 119 昨日访问: 277 您的访量:
加入收藏
联系我们
新手上路
上线 [image] 重登陆 [image] 用户控制面板 [image] 搜索 [image] 论坛状态 [image] 帮助 [image] 退出

>> 欢迎 ugvanxk [ 发短信 :: 发表的主题 :: 参与的主题 :: 我能做什么 ] 我的收件箱 (0 新)
周周共享论坛 → 『数 据 库』 → 浏览：UNDO Committed Transactions

您是本帖的第 4 个阅读者 浏览上一篇主题 刷新本主题 树形显示贴子 　 浏览下一篇主题
* 贴子主题： UNDO Committed Transactions
ugvanxk 帅哥哟，在线，有人找我吗？

等级：版主
文章：219
积分：569
注册：2002-11-2
搜索ugvanxk在『数 据 库』的所有贴子 楼顶
发贴心情 UNDO Committed Transactions
Script to UNDO Committed Transactions

This script is designed to undo committed transactions, functioning as an "UNDO" service in multiuser mode. The script works only in SQL Server 2000.

The following is a description of how the script works -- for more information see the readme file included in the script download:

"When you do changes in the database, SQL-UNDO saves previous data, types of the actions, ID of the transaction and additional information into special tables with the simple structure: SUActions, SURowsInfo, SUColsInfo and SULogins. Then, when you want to undo an transaction, you run stored procedure spSUUndoTransaction or spSUUndoLastOwnTransaction, that takes data from the tables, builds dynamic queries for back actions and executes them within one transaction. spSUUndoTransaction has one parameter "@IdAction". You can take value for the parameter from the field "Id" of the table "SUActions". If the action was runned within a transaction, all actions of the transaction will be undone too."
Author: Gleb Oufimtsev

SQL-UNDO 3.0 service for Microsoft SQL server 2000
==================================================

The service is meant for undoing any commited in the past transaction. In point of fact, it is a service of "UNDO" in the multiuser mode.

Installation
------------

1. Run install.sql script on database where you want to have the service. The library's objects will be installed. The objects are following:
1) Table "SUActions"
2) Table "SURowsInfo"
3) Table "SUColsInfo"
4) Table "SULogins"
5) Stored procedure "spSUSaveActions"
6) Stored procedure "spSUUndoTransaction"
7) Stored procedure "spSUUndoLastOwnTransaction"
8) Stored procedure "spSUSetup"

2. Run procedure "spSUSetup" with the first string parameter "Create SQL-UNDO triggers".

exec spSUSetup 'Create SQL-UNDO triggers'

Triggers of the service will be installed by the stored procedure. After the action SQL-UNDO library will work and you will be have the prepared UNDO-service in multiuser mode within your client-server application.
NB. You can place a table's name as the second parameter. Then the SQL-UNDO trigger will be created only on this table. Thus, you can solve where UNDO-service will work and where - not. Also you can drop the triggers on some separate tables by the following syntax:

exec spSUSetup 'Remove SQL-UNDO triggers' [, TableName ]

Thus, you can manage creating-removing of the SQL-UNDO triggers in work process at any time.

Deinstallation
--------------

Run procedure "spSUSetup" with the first string parameter "Remove SQL-UNDO objects" and the second string parameter "Yes, I really want to delete all SQL-UNDO objects with data"

exec spSUSetup 'Remove SQL-UNDO objects','Yes, I really want to delete all SQL-UNDO objects with data'

All tables, stored procedures and triggers of SQL-UNDO library will be killed.

Work restrictions
-----------------

1. SQL-UNDO will work on tables where there are the Primary Keys and the Primary Keys containt only one field. Tables, where there are the composite Primary Keys, will be omitted by SQL-UNDO service.

2. SQL-UNDO will work on tables where there are not IMAGE, TEXT and NTEXT fields, because to work with blobs are impossible within triggers in MSSQL2000. It is a restriction of Microsoft. Tables, where there are blobs fields, will be omitted by SQL-UNDO service.

3. An ordinary user cannot undo an transaction of another user. Only DBA or member of "db_owner" can undo such transaction.

4. It is impossible - to undo an transaction, if the data within the records was changed after. SQL-UNDO puzzles out correctly such situations and gives warnings.

5. There is an particular situation. When table has IDENTITY column, the action was DELETE and the user is not the table's owner and not member of db_ddladmin, db_owner or sysadmin roles. Undoing of such action will be fail, because the undo-action will be INSERT with the directive "SET IDENTITY_INSERT", but an ordinary user has no rights to set the directive. And changing the rights are impossible. It is a restriction of Microsoft. Such action can be undo by DBA.

6. If your triggers modify other tables, set up the 'nested trigger' option for correct work of SQL-UNDO. The service saves data for undoing with the help of triggers.

7. The SQL-UNDO service cannot be useful for databases with a big degree of transaction's activity because of the reason of the performance.

8. If you change Primary Key's value, you will not be able to undo the action and all previous actions on this record.

How it works
------------

When you do changes in the database, SQL-UNDO saves previous data, types of the actions, ID of the transaction and another information into special tables with the simple structure: SUActions, SURowsInfo, SUColsInfo and SULogins. Then, when you want to undo an transaction, you run stored procedure spSUUndoTransaction or spSUUndoLastOwnTransaction, that takes data from the tables, builds dynamic queries for back actions and executes them within one transaction. spSUUndoTransaction has one parameter "@IdAction". You can take value for the parameter from the field "Id" of the table "SUActions". If the action was runned within a transaction, all actions of the transaction will be undone too. You can build a service of viewing actions on the SQL-UNDO tables for comfortable work with the SQL-UNDO service. The tables' structures are very easy and understandable.

Licensing and limitations
-------------------------

SQL-UNDO library is fully freeware. It is "AS IS". You can use, modify, copy it without any restrictions. But any warranties, responsibilities are not given and any claims will not be taken. You use the library on your own risk.

Gleb Oufimtsev
gvu@newmail.ru
http://www.gvu.newmail.ru
Moscow, Russia, January 2002

If you like my work, why you didn't still invite me to work? :-)
(It's a joke. Almost)

点击查看用户来源及管理<br>发贴IP：61.48.16.123 2003-6-5 11:58:37
编辑这个贴子 鲜花(0) 鸡蛋(0)
ugvanxk 帅哥哟，在线，有人找我吗？

等级：版主
文章：219
积分：569
注册：2002-11-2
搜索ugvanxk在『数 据 库』的所有贴子 第2楼
发贴心情
/*************************************************************************/
/* Library SQL-UNDO 3.0 for MSSQL2000 */
/* Copyright (C) 2002 Gleb Oufimtsev */
/* */
/* gvu@newmail.ru, [image] http://www.gvu.newmail.ru */
/* Moscow, Russia */
/*************************************************************************/
CREATE TABLE dbo.SULogins (
Id smallint IDENTITY(1,1) NOT NULL PRIMARY KEY CLUSTERED,
Login sysname NOT NULL UNIQUE
)

CREATE TABLE dbo.SUActions (
Id bigint IDENTITY(1,1) NOT NULL PRIMARY KEY CLUSTERED,
RegDate datetime NOT NULL ,
IdLogin smallint NOT NULL REFERENCES dbo.SULogins (Id),
ActionType tinyint NOT NULL ,
TableId int NOT NULL ,
TranID varchar(255) NULL ,
CONSTRAINT CK_SUActions_ActionType CHECK (ActionType between 1 and 3)
)

PRINT 'Please, ignore the following two warnings: '+char(10)

CREATE TABLE dbo.SURowsInfo (
Id bigint IDENTITY(1,1) NOT NULL PRIMARY KEY CLUSTERED,
IdAction bigint NOT NULL REFERENCES dbo.SUActions (Id),
PKvalue sql_variant NOT NULL ,
CONSTRAINT UQ_SURowsInfo UNIQUE (IdAction, PKvalue)
)

CREATE TABLE dbo.SUColsInfo (
IdRowInfo bigint NOT NULL REFERENCES dbo.SURowsInfo (Id),
ColumnId smallint NOT NULL ,
Oldvalue sql_variant NULL ,
CONSTRAINT PK_SUColsInfo PRIMARY KEY CLUSTERED (IdRowInfo, ColumnId)
)

CREATE INDEX idx_SUActions_Tran ON dbo.SUActions (TranID)
CREATE INDEX idx_SUActions_Table ON dbo.SUActions (TableId)
CREATE INDEX idx_SUActions_Login ON dbo.SUActions (IdLogin)
CREATE INDEX idx_SUActions_RegDate ON dbo.SUActions (RegDate)
CREATE INDEX idx_SUColsInfo_Column ON dbo.SUColsInfo (ColumnId)
CREATE INDEX idx_SURowsInfo_PKvalue ON dbo.SURowsInfo (PKvalue)

GO
/*************************************************************************/
/* Library SQL-UNDO 3.0 for MSSQL2000 */
/* Copyright (C) 2002 Gleb Oufimtsev */
/* */
/* gvu@newmail.ru, [image] http://www.gvu.newmail.ru */
/* Moscow, Russia */
/*************************************************************************/
CREATE PROCEDURE dbo.spSUSetup

@Action varchar(50),
@Parameter nvarchar(4000)=NULL

AS

if @Action not in ('Create SQL-UNDO triggers','Remove SQL-UNDO triggers','Remove SQL-UNDO objects')
begin
raiserror('SQL-UNDO error. Incorrect value of @Action. It may be only ''Create SQL-UNDO triggers'',''Remove SQL-UNDO triggers'' or ''Remove SQL-UNDO objects''',16,-1)
return
end

if @Action='Remove SQL-UNDO objects' and IsNull(@Parameter,'')<>'Yes, I really want to delete all SQL-UNDO objects with data'
begin
raiserror('SQL-UNDO error. Incorrect value of @Parameter. It may be only ''Yes, I really want to delete all SQL-UNDO objects with data by @Action=''Remove SQL-UNDO objects''',16,-1)
return
end

if @Action in ('Create SQL-UNDO triggers','Remove SQL-UNDO triggers') and @Parameter is not NULL
and not exists (select * from sysobjects where xtype='U' and name=@Parameter)
begin
raiserror('SQL-UNDO error. Incorrect value of @Parameter. It may be only correct table''s name or NULL by @Action=''%s''',16,-1,@Action)
return
end

set nocount on

declare
@TableName nvarchar(231),
@TriggerName sysname, @OldTriggerName sysname,
@NumFieldInPK smallint,
@BlobsCount int,
@dynsql nvarchar(4000)

if @Action in ('Create SQL-UNDO triggers','Remove SQL-UNDO triggers')
begin
declare TBLS cursor local fast_forward for
select
'['+u.name+'].['+t.name+']',
'tr_SQL_UNDO_30_on_'+t.name,
(select name from sysobjects where xtype='TR' and parent_obj=t.id and name like 'tr_SQL_UNDO_30_on_%'),
(select count(distinct k.colid)
from sysindexes i
inner join sysindexkeys k on i.id=k.id and k.indid=i.indid
inner join sysobjects o on o.parent_obj=i.id and o.name=i.name and o.xtype='PK'
where i.id=t.id),
(select count(*) from syscolumns where id=t.id and xtype in (34/*image*/,99/*ntext*/,35/*text*/))
from sysobjects t
inner join sysusers u on t.uid=u.uid
where t.xtype='U'
and (t.name=@Parameter or @Parameter is NULL)
and (@Parameter is not NULL
or t.name not in ('SUActions','SURowsInfo','SUColsInfo','SULogins','dtproperties'))
-- You can extend the list
open TBLS
fetch TBLS into @TableName, @TriggerName, @OldTriggerName, @NumFieldInPK, @BlobsCount
while @@FETCH_STATUS=0
begin
if @OldTriggerName is not NULL
begin
set @dynsql=N'drop trigger ['+@OldTriggerName+']'
exec sp_executesql @dynsql
if @@ERROR=0 print 'SQL-UNDO old trigger on table '+@TableName+' has been dropped'
end
if @Action='Create SQL-UNDO triggers'
begin
if @NumFieldInPK>1 print char(10)+'*** SQL-UNDO trigger cannot be added on table '+@TableName+' because the table''s Primary Key has more than one column'+char(10)
if @NumFieldInPK=0 print char(10)+'*** SQL-UNDO trigger cannot be added on table '+@TableName+' because the table has no Primary Key'+char(10)
if @BlobsCount>0 print char(10)+'*** SQL-UNDO trigger cannot be added on table '+@TableName+' because the table has blobs (image, text, ntext)'+char(10)
set @dynsql=''
+'/*************************************************************************/'+NCHAR(10)
+'/* Library SQL-UNDO 3.0 for MSSQL2000 */'+NCHAR(10)
+'/* Copyright (C) 2002 Gleb Oufimtsev */'+NCHAR(10)
+'/* */'+NCHAR(10)
+'/* gvu@newmail.ru, [image] http://www.gvu.newmail.ru */'+NCHAR(10)
+'/* Moscow, Russia */'+NCHAR(10)
+'/*************************************************************************/'+NCHAR(10)
+'CREATE TRIGGER ['+@TriggerName+'] ON '+@TableName+' '+NCHAR(10)
+'FOR INSERT, UPDATE, DELETE '+NCHAR(10)
+'AS '+NCHAR(10)
+'/* */'+NCHAR(10)
+'/* Trigger of SQL-UNDO 3.0 library */'+NCHAR(10)
+'/* Generated by procedure spSUSetup */'+NCHAR(10)
+'/* */'+NCHAR(10)+NCHAR(10)
+'set nocount on '+NCHAR(10)
+'select * into #SU03dt from deleted '+NCHAR(10)
+'select * into #SU03it from inserted '+NCHAR(10)
+'exec spSUSaveActions @@PROCID '+NCHAR(10)
exec sp_executesql @dynsql
if @@ERROR=0 print 'SQL-UNDO trigger has been added on table '+@TableName
end
fetch TBLS into @TableName, @TriggerName, @OldTriggerName, @NumFieldInPK, @BlobsCount
end
close TBLS
deallocate TBLS
end
else if @Action='Remove SQL-UNDO objects'
begin
exec sp_executesql N'exec spSUSetup ''Remove SQL-UNDO triggers'''
drop table SUColsInfo
drop table SURowsInfo
drop table SUActions
drop table SULogins
drop procedure spSUSaveActions
drop procedure spSUUndoTransaction
drop procedure spSUUndoLastOwnTransaction
drop procedure spSUSetup
end

return
GO

点击查看用户来源及管理<br>发贴IP：61.48.16.123 2003-6-5 12:02:10
编辑这个贴子
ugvanxk 帅哥哟，在线，有人找我吗？

等级：版主
文章：219
积分：569
注册：2002-11-2
搜索ugvanxk在『数 据 库』的所有贴子 第3楼
发贴心情
/*************************************************************************/
/* Library SQL-UNDO 3.0 for MSSQL2000 */
/* Copyright (C) 2002 Gleb Oufimtsev */
/* */
/* gvu@newmail.ru, [image] http://www.gvu.newmail.ru */
/* Moscow, Russia */
/*************************************************************************/
CREATE PROCEDURE dbo.spSUSaveActions @TriggerId int -- @@PROCID of the trigger

AS

-- Do not use this procedure directly.
-- It is an internal service for using by triggers

declare
@SaveIdentity bigint,
@TypeAction tinyint, -- 1-insert, 2-delete, 3-update
@TranID varchar(255), @IdAction bigint, @IdLogin smallint,
@TableId int, @ColId smallint, @ColName sysname,
@PKname sysname,
@dynsql nvarchar(4000)

set nocount on

if OBJECT_ID('tempdb..#sql_undo_30_keys')>0 RETURN 0 -- This action is an UNDO action, not saveable.

set @SaveIdentity=@@IDENTITY

set @TypeAction=
(case when (select count(*) from #SU03it)>0 then 1 else 0 end)
+(case when (select count(*) from #SU03dt)>0 then 2 else 0 end)

if @TypeAction=0 RETURN

-- Determination of Transaction ID

exec sp_getbindtoken @TranID OUTPUT

-- Determination of the target-table

select @TableId=parent_obj from sysobjects where type='TR' and id=@TriggerId

-- Determination of the login

select @IdLogin=Id from SULogins where Login=SUSER_SNAME()

if @IdLogin is NULL
begin
insert into SULogins (Login) values (SUSER_SNAME())
set @IdLogin=SCOPE_IDENTITY()
end

-- Saving the action

insert into SUActions (IdLogin, ActionType, TableId, RegDate, TranID)
values (@IdLogin, @TypeAction, @TableId, GETDATE(), @TranID)
set @IdAction=SCOPE_IDENTITY()

-- Determination of the target-table's PK

select @PKname=COL_NAME(@TableId, k.colid)
from sysindexes i
inner join sysindexkeys k on i.id=k.id and k.indid=i.indid
inner join sysobjects o on o.parent_obj=i.id and o.name=i.name and o.xtype='PK'
where i.id=@TableId

if @@ROWCOUNT<>1
begin
raiserror('SQL-UNDO error. Violation of PK condition. PK must present and PK must have only one column',16,-1)
return
end

--Saving of rows-info

create table #rinfo (PKvalue sql_variant not null,
IdRowInfo bigint null)
set @dynsql='insert into #rinfo (PKvalue) '
+'select distinct i.['+@PKname+'] from #SU03it i '
+'union select distinct d.['+@PKname+'] from #SU03dt d '
exec sp_executesql @dynsql

insert into SURowsInfo (IdAction, PKvalue)
select @IdAction, PKvalue from #rinfo

update t set IdRowInfo=r.Id
from #rinfo t
inner join SURowsInfo r on r.IdAction=@IdAction and t.PKvalue=r.PKvalue

if @TypeAction in (2,3)
-- By INSERT, we don't save the fields' values,
-- because UNDO-action will be "delete from Table where PK=@PK"
-- and the fields' values are not required for it.
begin

-- Loop of saving the fields' values

create table #cinfo (IdRowInfo bigint not null,
ColumnId smallint not null,
Oldvalue sql_variant null)
declare VALS_CR cursor local fast_forward for
select c.colid, c.name
from syscolumns c
where c.id=@TableId and c.name<>@PKname and c.xtype<>189/*timestamp*/ and c.iscomputed=0
order by 1
open VALS_CR
fetch VALS_CR into @ColId, @ColName
while @@FETCH_STATUS=0
begin
if @TypeAction=2 --was DELETE
set @dynsql=
'insert into #cinfo (IdRowInfo, ColumnId, Oldvalue) '
+'select r.IdRowInfo,'+cast(@ColId as NVARCHAR)+',d.['+@ColName+'] '
+'from #SU03dt d '
+'inner join #rinfo r on r.PKvalue=d.['+@PKname+'] '
+'where d.['+@ColName+'] is not NULL '
else if @TypeAction=3 --was UPDATE
set @dynsql=
'insert into #cinfo (IdRowInfo, ColumnId, Oldvalue) '
+'select r.IdRowInfo,'+cast(@ColId as NVARCHAR)+',d.['+@ColName+'] '
+'from #SU03dt d '
+'inner join #SU03it i on i.['+@PKname+']=d.['+@PKname+'] '
+'inner join #rinfo r on r.PKvalue=d.['+@PKname+'] '
+'where (d.['+@ColName+'] is not NULL or i.['+@ColName+'] is not NULL) '
+'and (d.['+@ColName+']<>i.['+@ColName+'] or i.['+@ColName+'] is NULL or d.['+@ColName+'] is NULL) '
exec sp_executesql @dynsql
fetch VALS_CR into @ColId, @ColName
end
close VALS_CR
deallocate VALS_CR

insert into SUColsInfo (IdRowInfo, ColumnId, Oldvalue)
select IdRowInfo, ColumnId, Oldvalue
from #cinfo
order by 1,2

end -- of "if @TypeAction..."

-- Restore @@IDENTITY value

set @dynsql='select identity(decimal(38,0),'+cast(@SaveIdentity as nvarchar)+',1) Id into #tmpident'
exec sp_executesql @dynsql

-- Some maintenance and optimization

-- This code may be transfered to a scheduler and executed once a day
-- For it, kill the row "and a1.TranID<>@TranID"

update SUActions with (rowlock) set TranID=NULL
from SUActions
where Id in (select a1.Id from SUActions a1 with (READPAST)
where a1.TranID is not NULL
and a1.TranID<>@TranID
and a1.RegDate<GETDATE()-0.05
and not exists (select * from SUActions a2 with (READPAST)
where a2.TranID=a1.TranID
and a2.Id<>a1.Id))

RETURN 0
GO
/*************************************************************************/
/* Library SQL-UNDO 3.0 for MSSQL2000 */
/* Copyright (C) 2002 Gleb Oufimtsev */
/* */
/* gvu@newmail.ru, [image] http://www.gvu.newmail.ru */
/* Moscow, Russia */
/*************************************************************************/
CREATE PROCEDURE dbo.spSUUndoTransaction @IdAction bigint AS

declare
@dynsql nvarchar(4000), @dyn1 nvarchar(4000), @dyn2 nvarchar(4000),
@IdRowInfo bigint,
@ActionType tinyint,
@TableId int, @TableName sysname,
@ColName sysname, @ColumnId smallint, @ColType sysname,
@PKname sysname, @PKvalue sql_variant, @PKtype sysname

set nocount on

if IS_MEMBER('db_owner')=0
and exists (select * from SUActions a
inner join SULogins l on a.IdLogin=l.Id
where a.Id=@IdAction and l.Login<>SUSER_SNAME())
begin
raiserror('SQL-UNDO error. Only a member of "db_owner" or DBA can undo an action of another user',16,-1)
goto EXIT_WITH_ERROR
end

SET XACT_ABORT ON
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
BEGIN TRAN

declare ACNSCR cursor local fast_forward for
select distinct a.Id, a.ActionType, a.TableId,
N'['+USER_NAME(ot.uid)+N'].['+ot.name+N']' TableName,
N'['+COL_NAME(a.TableId, k.colid)+N']' PKname,
t.name PKtype
from SUActions a
inner join SUActions a0
on ((a0.TranID is not NULL and a0.TranID=a.TranID)
or (a0.TranID is NULL and a0.Id=a.Id))
and a0.Id=@IdAction
and (a.RegDate-a0.RegDate) between '18991231' and '19000102' -- just in case
inner join sysindexes i on i.id=a.TableId
inner join sysindexkeys k on i.id=k.id and k.indid=i.indid
inner join sysobjects o on o.parent_obj=i.id and o.name=i.name and o.xtype='PK'
inner join sysobjects ot on a.TableId=ot.id
inner join syscolumns sc on sc.id=a.TableId and sc.colid=k.colid
inner join systypes t on sc.xtype=t.xtype
order by 1 desc
open ACNSCR
fetch ACNSCR into @IdAction, @ActionType, @TableId, @TableName, @PKname, @PKtype
while @@FETCH_STATUS=0
begin
if not exists (select * from SURowsInfo r
inner join SUActions a on r.IdAction=a.Id
where r.IdAction=@IdAction
and (a.ActionType=1
or exists (select * from SUColsInfo where IdRowInfo=r.Id)))
begin
raiserror('SQL-UNDO error. Data for undoing are not enough. Check content in tables "SURowsInfo" and "SUColsInfo"',16,-1)
goto EXIT_WITH_ERROR
end

if exists (select * from SURowsInfo r1
inner join SUActions a1 on r1.IdAction=a1.Id
inner join SURowsInfo r2 on r1.PKvalue=r2.PKvalue
inner join SUActions a2 on r2.IdAction=a2.Id
where a1.TableId=a2.TableId and a1.Id=@IdAction and a2.Id>a1.Id)
begin
raiserror('SQL-UNDO error. Attempt to restore a row that was changed after the action',16,-1)
goto EXIT_WITH_ERROR
end

select @IdAction IdAction, Id IdRowInfo, PKvalue
into #sql_undo_30_keys from SURowsInfo where IdAction=@IdAction

create table #v (R bigint not NULL,
V sql_variant NULL,
C smallint not NULL)

if @ActionType=1 -- Was: INSERT. UNDO-action: DELETE
set @dynsql=N'delete from '+@TableName+N' where '+@PKName+N' '
+N' in (select PKvalue from #sql_undo_30_keys) '
else
begin

insert into #v (R, V, C)
select c.IdRowInfo, Oldvalue, c.ColumnId
from SUColsInfo c
inner join SURowsInfo r on c.IdRowInfo=r.Id
inner join SUActions a on r.IdAction=a.Id
where r.IdAction=@IdAction

set @dynsql=N''
set @dyn1=N''
set @dyn2=N''

if @ActionType=2 -- Was: DELETE. UNDO-action: INSERT
begin
if exists (select * from syscolumns where id=@TableId and status&0x80=0x80)
set @dynsql=@dynsql+N'set identity_insert '+@TableName+N' on'+NCHAR(10)
set @dynsql=@dynsql+N'insert into '+@TableName+N' ('+@PKname
end
else -- @ActionType=3 Was: UPDATE. UNDO-action: UPDATE
begin
set @dynsql=N'update trg set'+NCHAR(10)
end

declare COLSCR cursor local fast_forward for
select distinct N'['+COL_NAME(@TableId, C)+N']', C, t.name
from #v v inner join syscolumns c on c.id=@TableId and c.colid=v.C
inner join systypes t on c.xtype=t.xusertype
open COLSCR
fetch COLSCR into @ColName, @ColumnId, @ColType
while @@FETCH_STATUS=0
begin
if @ActionType=2
begin
set @dyn1=@dyn1+N','+@ColName
set @dyn2=@dyn2+N','+NCHAR(10)+N' (select CAST(V as '+@ColType+N') from #v where R=k.IdRowInfo and C='+CAST(@ColumnId as NVARCHAR)+N')'
end
else
set @dynsql=@dynsql+N' '+@ColName+N'=(CASE WHEN (select count(*) from #v where R=k.IdRowInfo and C='+CAST(@ColumnId as NVARCHAR)+N')=0 THEN trg.'+@ColName
+N' ELSE (select CAST(V as '+@ColType+N') from #v where R=k.IdRowInfo and C='+CAST(@ColumnId as NVARCHAR)+N') END),'+NCHAR(10)
fetch COLSCR into @ColName, @ColumnId, @ColType
end
close COLSCR
deallocate COLSCR

if @ActionType=2
begin
set @dynsql=@dynsql+@dyn1
+N')'+NCHAR(10)+N'select CAST(k.PKvalue as '+@PKtype+N')'+@dyn2+NCHAR(10)
+N'from #sql_undo_30_keys k'+NCHAR(10)
if exists (select * from syscolumns where id=@TableId and status&0x80=0x80)
set @dynsql=@dynsql+N'set identity_insert '+@TableName+N' off '
end
else
set @dynsql=SUBSTRING(@dynsql,1,LEN(@dynsql)-2)+CHAR(10)
+N'from '+@TableName+' trg'+NCHAR(10)
+N'inner join #sql_undo_30_keys k on k.PKvalue=trg.'+@PKname
end

exec sp_executesql @dynsql
if @@ERROR<>0 GOTO EXIT_WITH_ERROR

delete from SUColsInfo where IdRowInfo in
(select Id from SURowsInfo where IdAction=@IdAction)
delete from SURowsInfo where IdAction=@IdAction
delete from SUActions where Id=@IdAction

drop table #v
drop table #sql_undo_30_keys
fetch ACNSCR into @IdAction, @ActionType, @TableId, @TableName, @PKname, @PKtype
end
close ACNSCR
deallocate ACNSCR

COMMIT

EXIT_WITH_ERROR:
if @@TRANCOUNT>0 ROLLBACK
RETURN
GO
/*************************************************************************/
/* Library SQL-UNDO 3.0 for MSSQL2000 */
/* Copyright (C) 2002 Gleb Oufimtsev */
/* */
/* gvu@newmail.ru, [image] http://www.gvu.newmail.ru */
/* Moscow, Russia */
/*************************************************************************/
CREATE PROCEDURE dbo.spSUUndoLastOwnTransaction AS

set nocount on

declare @IdAction bigint

select @IdAction=max(a.Id)
from SUActions a (READPAST)
inner join SULogins l on a.IdLogin=l.Id and l.Login=SUSER_SNAME()

if @IdAction is not NULL
exec spSUUndoTransaction @IdAction
else
raiserror('SQL-UNDO error. There isn''t such transactions for undoing',16,-1)

return
GO
GRANT EXECUTE ON dbo.spSUUndoTransaction TO public
GRANT EXECUTE ON dbo.spSUUndoLastOwnTransaction TO public
GRANT EXECUTE ON dbo.spSUSaveActions TO public
GO

点击查看用户来源及管理<br>发贴IP：61.48.16.123 2003-6-5 12:02:53
编辑这个贴子

本主题贴数3，分页： [1]
*快速回复：UNDO Committed Transactions
你的用户名: 还没注册? 密码: 忘记密码?
内容
  • HTML标签： 不可用
  • UBB标签： 可用
  • 贴图标签： 可用
  • Flash标签：可用
  • 表情字符转换：可用
  • 上传图片：可用
  • 最多16KB
    • 邮件回复 显示签名 [Ctrl+Enter直接提交贴子]

    锁定 | 解锁 | 删除 | 移动 | 固顶 | 奖励 | 惩罚 | 发布公告

    Powered by：DVBBS Ver5.0 Final
    Copyright ?2002 - 2003 pigtwo.com , 页面执行时间：1,437.500毫秒


Search SQL Error Log
Search SQL Error Log Script

This script creates an sp that can be used to search the SQL Error Log for a user specified search criteria. This can be very handy when trying to find a particular problem in very large SQL Error Logs.

The procedure

Reads the registry to determine where Your SQL install exists so that you do not have to tell it where your SQL Error Logs reside;
Accept a log number variable for searching offline error logs;
Tells you if it does not find anything in the error log for the search criteria you specified.
The script has been tested on SQL 7.0 and 2000. It should work in SQL 6.5 if the registry entries are the same, but the script has not been tested on this version of SQL Server. If it does not work on v6.5, either modify the registry call so that it is looking in the right place for the DataRoot Key or hard code the SQL Error Log Path.

It's recommened that this script be put in the Master db because it calls the xp_cmdshell extended stored procedure.

Author: Phillip Snipes
if exists (select * from sysobjects where id =
object_id(N'[dbo].[spKM_search_sqlerrorlog]') and OBJECTPROPERTY(id,
N'IsProcedure') = 1)
drop procedure [dbo].[spKM_search_sqlerrorlog]
GO

SET QUOTED_IDENTIFIER OFF SET ANSI_NULLS OFF
GO

CREATE proc dbo.spKM_search_sqlerrorlog

@lookfor varchar(25)= '', @LogNum char(2)= ''

AS
/******************************

sql-scripting 04/04/2001

Visit www.sql-scripting.com
Edward J Pochinski III

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

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

This script creates an sp that can be used to search the
SQL Error Log for a user specified search criteria.
This can be very handy when trying to find a particular
problem in very large SQL Error Logs.
I can not take full credit for this idea as
I first found the script on www.sql-scripting.com.
I have since modified it to
1) read the registry to determine
where Your SQL install exists so that you do not have to tell
it where your SQL Error Logs reside;
2) accept a log number variable for searching offline error logs;
3) tell you if it does not find anything in the error log for the search
criteria you specified.

Phillip D. Snipes

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

DECLARE @SQLLogPath varchar(20)

EXEC master..xp_regread @rootkey='HKEY_LOCAL_MACHINE',
@key='SOFTWARE\Microsoft\MSSQLServer\Setup', @value_name='SQLDataRoot',
@value=@SQLLogPath OUTPUT

Declare @cmd varchar(65)

/*********************
Print help message if no search criteria specified.
**********************/
if @lookfor = ''

Begin

Print '#################################################'

Print char(13)+'You must pass a string to search for.'

Print char(13)+'example: To search current SQL Error Log for
the word kernel'

Print char(13)+'exec spKM_search_sqlerrorlog kernel'

Print char(13)+'Optional parameter to pass is the Log
Number. The SQL Server starts a new'

Print char(13)+'Error Log each time it is started. To search
a previous SQL Error Log You must specify the Log Number.'

Print char(13)+'If you do not pass a Log Number the
procedure will default to searching the current SQL Error Log.'

Print char(13)+'If you want to search previous Logs, then
specify which Log to search by passing a number.'

Print char(13)+'example: To search the most recent previous
SQL Error Log for the word kernel'

Print char(13)+'exec spKM_search_sqlerrorlog kernel, 1'

Print
char(13)+'#################################################'

Return

End

/********************
Defaults to searching current SQL Error Log if no LogNum specified.
*********************/
If @LogNum = ''
Begin
Set NoCount On
set @cmd = 'FINDSTR /I /C:"'+@lookfor+'" ' + @SQLLogPath +
'\Log\errorlog'
Create Table #Results ( Results varchar(8000))
insert into #Results
Exec xp_cmdshell @cmd
Select * from #Results
IF @@ROWCOUNT = 0
BEGIN
Print char(13)+'There are no entries in the
Current SQL Error Log matching the search criteria "'+@lookfor+'".'
End
Drop Table #Results
Set NoCount Off

End
/****************
Print which SQL Error Log file is being searched.
*****************/
If @LogNum > 0 And @LogNum < 7
Begin
If @logNum = 1
Begin
Print char(13)+'SQL Error Log Archive #1'
End
If @logNum = 2
Begin
Print char(13)+'SQL Error Log Archive #2'
End
If @logNum = 3
Begin
Print char(13)+'SQL Error Log Archive #3'
End
If @logNum = 4
Begin
Print char(13)+'SQL Error Log Archive #4'
End
If @logNum = 5
Begin
Print char(13)+'SQL Error Log Archive #5'
End
If @logNum = 6
Begin
Print char(13)+'SQL Error Log Archive #6'
End

Set NoCount On
set @cmd = 'FINDSTR /I /C:"'+@lookfor+'" ' + @SQLLogPath +
'\Log\errorlog.' + @LogNum
Create Table #Results1 ( Results varchar(8000))
insert into #Results1
Exec xp_cmdshell @cmd
Select * from #Results1
IF @@ROWCOUNT = 0
BEGIN
Print char(13)+'There are no entries in the
SQL Error Log Archive #'+@LogNum+' matching the search criteria
"'+@lookfor+'".'
End
Drop Table #Results1
Set NoCount Off
End

/*************************
Prinnt an error message if the LogNum specified is too high.
**************************/
If @logNum > 6
Begin
Print char(13)+'SQL Error Log Number out of
Range. MS SQL Server does not maintain more than 6 Archived Error Logs.'


用加密函数加密
A common question I'm asked by clients is how to encrypt data and store it in SQL Server. One of the major problems I see in the field is when people store sensitive data unencrypted into SQL Server. For example, if a password is stored unencrypted into SQL Server, a malitious user could easily read all of the passwords with a simple select statement. You can develop your own COM mechanisms to encrypt the password but in this article, we'll discuss a method that is build into SQL Server.
Since SQL Server 6.x, you can use the ENCRYPT function to encrypt data with the same method used by the WITH ENCRYPTION keyword. There's a rather large problem that I will discuss after the example. To use the ENCRYPT function, use it before the string value as shown below:

SELECT ENCRYPT('TestPW1')

Will output the following result:

------------------------------
0x5400650073007400500057003100

(1 row(s) affected)

Let's go ahead and create a sample table and try out this function. Create the following user table and load the sample data below:
CREATE TABLE Users (
UserID Varchar(10),
UserPW Varchar (20))

INSERT INTO USERS values('TestUser1',ENCRYPT('TestPW1'))
INSERT INTO USERS values('TestUser2',ENCRYPT('TestPW2'))
INSERT INTO USERS values('TestUser3',ENCRYPT('TestPW3'))
INSERT INTO USERS values('TestUser4',ENCRYPT('TestPW4'))

If you now select the data it will appear encrypted. Notice if you run a SELECT ENCRYPT('TestPW1') that the data that you see different that what appears when you select out of the Users table after you insert the value. The algorithm that SQL Server uses to encrypt the data is relativily easy and is case sensitive until it's stored into a table. At that point it becomes very difficult to read.
Data stored in an encrypted column can be used to store passwords. Once encrypted, you can't directly unencrypt the data. You could only perform checks against it as shown below:

SELECT * from Users where UserID = 'TestUser2'
and UserPW = ENCRYPT('TestPW2')

Keep in mind that the above command is case sensitive. If you want this to be case-insensitive, it is best to store all the data in uppercase by using the UPPER function. The UPPER function will have to be used in the insert statement as well as the select statement that we've mentioned. For example, the insert statement would look like this:

INSERT INTO USERS values('TestUser1',ENCRYPT(UPPER('TestPW1')))

SELECT * from Users where UserID = 'TestUser2'
and UserPW = ENCRYPT(UPPER('TestPW2'))

Another note to mention is that like any nicely encrypted data, the data may appear one length when viewing it, but is actually stored at a different length.

UserID UserPW
---------- --------------------
TestUser1 T
TestUser2 T
TestUser3 T
TestUser4 T

(4 row(s) affected)

But in actuality if we select the length of the field by using the LEN function, we can see the true length.

userid Length
---------- -----------
TestUser1 14
TestUser2 14
TestUser3 14
TestUser4 14

(4 row(s) affected)

With that point made, make sure that the length of your column represents the encrypted length, not the unencrypted lenth. Now, for the large caveat that I mentioned earlier. This stored procedure is unsupported by Micrososft and they could easily change it or rip it out. A good example of this function changing was between SQL Server 6.5 and 7.0. Another common question is decryption. Obiously, Microsoft does not make this easy and strings can only be easily decrypted using the comparison technique I showed earlier. If you don't use this method to encrypt your passwords, there are other methods that are much better. These methods are much more robust and secure than what we discussed. For example, Les Smith has 2 articles that show you how to use Java or COM to encrypt passwords at : http://www.sqlservercentral.com/columnists/lsmith/usingjavatoencryptpasswords.asp. If you're serious


Advice on using COUNT( )
In the SQL Server community, one thing that I sometimes encounter is the question on whether you should use COUNT(*) or COUNT(columnname), where columnname is a column in the table that you want to count the rows for. Often the advice given to people in forums and mailing lists is that COUNT(columnname) will perform better than COUNT(*). This is not always the correct advice though, in many cases it is even entirely wrong. Although there are situations where you can (or even should) use COUNT(columnname), you definitely shouldn't always use it. This incorrect piece of advice is probably based on a lack of understanding of how SQL Server handles data internally.

Description of COUNT( )
The first thing that you need to know is that there is a difference between the alternative ways of using COUNT( ), and what this difference is. The complete syntax for COUNT( ) is this:

COUNT ( { [ ALL | DISTINCT ] expression ] | * } )

The word expression means any expression except for uniqueidentifier, text, ntext or image data, and it may not use aggregated functions or subqueries. Most often though, expression is just a column in the table. ALL is the default, which means that writing COUNT(expression) is equal to writing COUNT(ALL expression).

COUNT(*) returns the total number of rows in the table, while COUNT(expression) returns the number of rows where the result of the expression is not NULL. Naturally, COUNT(DISTINCT expression) means that duplicates are only counted once. This means that COUNT( ) can return different results depending on how you write it.

Myths and facts
As I said earlier, many people believe COUNT(columnname) is faster than using COUNT(*), because COUNT(*) would have to read all columns of each row (just like executing a SELECT * FROM MYTABLE statement), while COUNT(columnname) only need to read the specified column. This is not true though, for several reasons.

First of all, SQL Server can't read just the contents of a single column without reading the entire row. SQL Server stores the rows with the data on 8 KB data pages on disk. These pages contain one or more rows (depending on the size of each individual row, which may be up to 8060 bytes, with some exceptions), and these pages are placed in the internal memory (RAM) when SQL Server needs to access them for any reason. To check the value of a single column (or several of course), an entire page has to be read from disk and placed in memory. The pages may of course already be cached in memory, in which case the read will be much faster, but SQL still needs to read an entire page from memory just to check a single column of a row.

Now, to avoid having to read these entire data pages when all you are really interested in is how many rows there are in a table, SQL Server will use an index instead, if one exists. Indexes are stored in the same way as data, on 8 KB index pages. Since an index is probably not as wide as a data row (the index only consists of some or even one of the columns in the row), an index page can usually fit a lot more rows per page than the data pages can. This means that SQL Server doesn't have to read as many pages to check the number of rows in the index as it does with the data pages, which is of course a good thing.

This does not only apply to COUNT(columnname_with_an_index_defined_on_it), COUNT(*) will of course also use the index to count the rows. In some cases there may not be an index that covers the specified column in a COUNT(columnname) query, but there is an index defined on another column of the table. In this case COUNT(*) would use this other index to count the number of rows, but COUNT(columnname_without_an_index) would have to read the data pages to check the column for NULL values and count the rows.

To try this for yourself, run the following script in SQL Query Analyzer (if it is not already set to show the results in text mode, use Ctrl-T to set it that way):

USE Northwind
GO

SET STATISTICS IO ON

SELECT COUNT(*) FROM Orders
SELECT COUNT(CustomerId) FROM Orders
SELECT * FROM Orders

SET STATISTICS IO OFF

The statement SET STATISTICS IO ON configures SQL Server to output statistics showing the amount of I/O that was required to execute the query, and you can use it to compare the amount of resources used by different queries to decide which one to use. You can find this output directly after the results of the statement executed. The statistics we are interested in here is the number of logical and/or physical page reads. Logical page reads is the amount of pages (data and/or index pages) that was read from memory, and physical page reads is the number of pages read from disk. On my computer the result of COUNT( ) shows 830 rows for both alternatives, which is probably also what you got if you haven't added or deleted any rows from the Orders table. Now note the number of logical page reads for these statements (run the script a couple of times if you're getting physical page reads to cache the data in memory). I have 3 logical page reads for the first alternative, and 21 logical page reads for the second one! Also note that the third statement that SELECTs all of the rows from the table also resulted in 21 logical page reads. This shows us that the second statement had to read all of the data pages just to count the number of rows in Orders because there is no index on the CustomerId column, but the first statement is able to use an index (on my computer the index ShippersOrders was used; I checked the execution plan for the query to find that out) to count the rows.

Which one to use?
As I have shown, using COUNT(*) does certainly not mean poor performance. On the contrary, in some cases you may instead get poor performance from using COUNT(expression). Normally you probably won't encounter the problem in the example above, as you will probably have an index on the column you specified. What is worse though is that you may receive a different result from what you were expecting! Let's say that you have a legacy application that uses COUNT(columnname) to count the number of rows of a table, where columnname represents a column that does not allow NULL values. Now, sometime later, the definition for the column is changed to allow NULL values. As soon as someone enters a NULL value in the column, your application will no longer show the number of rows in the table but instead the number of rows with non-NULL values in the specified column! That may not be what the designers of the application intended and expected, and could possibly cause major problems.

But...
So, normally there is no reason not to use COUNT(*). But as I mentioned in the beginning of the article there are situations where you want to (or rather should) use COUNT(expression). One obvious example is of course if you are really only interested in the number of rows where the column value is not NULL. A typical example of a situation like that is when you use COUNT( ) together with another aggregated function. Let's say we have a table with some sort of measure data, with NULL values in some rows. Now we're looking for an average of these values. Normally, we would use AVG( ) for this, but to see the point we'll say we're not allowed to use it. Compare these two statements and see if you spot the problem:

SELECT SUM(column) / COUNT(*) FROM table

SELECT SUM(column) / COUNT(column) FROM table

These statements will return different average results, since SUM( ) ignores NULL values (they are not counted as 0). If the sum is 1500, and the number of rows is 150, of which 50 have NULL in the specified column, the result of the first query will be 10 (1500/150) and the result of the second query will be 15 (1500/100). This is actually a problem that I encounter quite often in my work as a database consultant, and most often it exists due to the fact that the person who wrote the SQL statement where not aware of how NULL values are handled differently in different aggregated functions (SUM( ) and COUNT( ) in the example above).


字符串-Quoting
Introduction
More and more I see people using dynamic sql to solve their problems. While I am not a fan of dynamic sql and think it should be avoided where possible, I understand it can be helpful and solve some problems. Recently I ran across an interesting problem where someone was creating a string of dynamic sql that appeared to work fine, but cause problems when executed.

The Problem
Suppose you wanted to build a dynamic string to select names from a table. For example, let's assume you have the following table:

create table Users
( UserName varchar( 100)
, Password varchar( 100)
)

A simple table, however let's now add some data to this table:
insert Users select 'sjones', 'mypassword'
insert Users select 'bgates', 'richguy'
insert Users select 'to''reilly', 'writer'
insert Users select 'lellison', 'karatechop'

Now, suppose that we have a stored procedure that will verify the password for a given username using dynamic sql. Why use dyanmic sql? No real reason in this case, but I tried to make a simple example to show the problem.

So what is the problem? Take a look at this procedure

alter procedure spValidateUser
@user varchar( 100)
, @pwd varchar( 100)
as

declare @sql nvarchar( 1000)

select @sql = 'select 1 from Users where [username] = ''' + @user + ''' and password = ''' + @pwd + ''' '

exec sp_executesql @sql

return

If I execute this with my own information, I get:
exec spValidateUser 'sjones', 'mypassword'

(no column name)
----------------
1

which my client application can use to process the validation. However, what if I look for Tim O'Reilly.
exec spValidateUser 'to''reilly', 'mypassword'

Server: Msg 170, Level 15, State 1, Line 1
Line 1: Incorrect syntax near 'reilly'.
Server: Msg 105, Level 15, State 1, Line 1
Unclosed quotation mark before the character string ' '.

Why would I get an error here? After all I escaped out the quote in O'Reilly. Apparently this is not enough.

The Second Solution
The problem is that when building a string for dyanmic sql, I must re-escape the quote character. Or any other reserver words because the new string will now have only a single quote inside. Since dynamic sql is executed as it's own batch, all reserver characters must be escaped. Dynamic sql is executed just as if you connected to SQL Server using Query Analyzer, pasted the sql in the window and executed it. If we examine the dynamic sql string for the last submission, it looks like:

select 1 from Users where [username] = 'to'reilly' and password = 'mypassword'

Note that the username only has a single quote in it.

So how can we fix this? The easy way is to use the same string manipulation technique previously discussed in Part 3, REPLACE. In this case, however, we need to escape the characters we are searching for as well as the replacement, so the REPLACE function looks like:

replace( @pwd, '''', '''''')

with each single quote we want to match replaced with two single quotes, plus the opening and closing single quotes. Note that we only want to replace the parameters passed in because these are the items we have no control over. If we were to run this replace on the dynamic string, the opening and closing quotes for each term of the where clause would get replaced and cause additional formatting problems.

Here's new new procedure:

alter procedure spValidateUser
@user varchar( 100)
, @pwd varchar( 100)
as

declare @sql nvarchar( 1000)

select @sql = 'select 1 from Users where [username] = ''' + replace( @user, '''', '''''') + ''' and password = ''' + replace( @pwd, '''', '''''') + ''' '

exec sp_executesql @sql

return
go

This will return the following results:
exec spValidateUser 'to''reilly', 'writer'

(no column name)
----------------
1

We now get the expected result.

Conclusions
Once again, this isn't anything that is tremndously enlightening, but it is something that you might miss and spend some time tracking down. As I mentioned at the start of this article, I don't like dynamic sql, but if you are building dynamic sql based on parameters passed into the procedure, this is something you should be aware of.

As always I welcome feedback on this article using the "Your Opinion" button below. Please also rate this article.

Steve Jones
ヾkRanch.net March 2002


字符串-Bending Results
Introduction
I discovered an interesting string technique the other day while reading a SQL programming book. It presented a great way that one can build a string with a delimited list of items. It can also be used to "bend" a result set from a series of rows to a column. I am sure many of you can come up with a whole bunch of ideas that I would never think of, but this actually solved a problem that I had previously used a cursor to solve. And eliminating cursors is almost always a good thing.

The Problem
I have a number of email integration processes that send notifications to people. Most of these are business processes and the names of the people who recieve the emails can change at any time. We store these emails in a table called, appropriately, email_notification. This table is defined with the following DDL:

create table email_notification
( id int identity( 1, 1),
process varchar( 80),
recipients varchar( 255),
cc varchar( 255)
)

and contains data like the following:
id process recipients cc
---- -------------------- ----------------------------------------------------- ------
1 Business Process 1 steve@dkranch.net;steve@sqlservercentral.com
2 Business Process 2 delaney@dkranch.net
3 Business Process 3 kyle@dkranch.net;kendall@dkranch.net
4 Business Process 4 tia@dkranch.net

This has worked fine as in various stored procedures as they can select a field from this table and use it in the xp_sendmail stored procedure. Emails go out fine and to multiple recipients. The problems come if I want to edit this table. If I want to add or remove an email from the list, I have to perform some string manipulation to ensure that I edit everything correctly.

Let's say that I want to remove Delaney from all the lines above. I have to first find all instances of delaney@dkranch.net and then replace them with spaces. Not the REPLACE function (see Part 3 of this series) will perform this work, but I have two cases here. I have to allow for the email at the end of the email separately from being in the middle. The reason: the semicolons that separate the emails.

This is not a huge problem and the REPLACE function will handle most edits that are needed, the other problem is that this storage is fundamentally a poor design. I am using a single field to hold multiple data elements. If I wanted to search on multiple emails, I have problems. If I want to total the number of emails a series of people recieve, I cannot easily do this.

Using this storage solution, however, has allowed me to easily send a group of emails into the mail stored procedure without using a cursor to concatenate the data. I can get a list of emails using the following code:

declare @r varchar( 255)
select @r = recipients
from email_notification
where process='Business Process 1'

However, what I really want to get to is to have the data stored like this:
id process recipients cc
---- -------------------- ----------------------------------------------------- ------
1 Business Process 1 steve@dkranch.net
2 Business Process 1 steve@sqlservercentral.com
3 Business Process 2 delaney@dkranch.net
4 Business Process 3 kyle@dkranch.net
5 Business Process 3 kendall@dkranch.net
6 Business Process 4 tia@dkranch.net

How can I do it? Read on...

The Solution
I have to admit that I would probably never have stumbled on this technique if I had read about it. That's a good reason to keep reading (hint, hint).

I have known for some time that there is a SET operator in T-SQL that can be used to set the value of a variable. I have never bothered to use it, preferring the SELECT operator. I don't have a good reason, I just like the SELECT operator. I had also assumed that these two operators performed the same function and one was kept for backwards compatibility.

Then I read about what the differences were in an advanced T-SQL programming book. The SET statement can only assign a single value, where SELECT can assing multiple values.

OK, that is interesting, but how does that help me. I don't really want to assign two variables like this:

declare @r varchar( 255), @c varchar( 255)
select @r = recipients, @c = cc
from email_notification
where process='Business Process 1'

Or even this:
declare @r varchar( 255), @c varchar( 255)
select @r = substring( recipients, 1, charindex( ';', recipients)-1),
@c = substring( recipients, charindex( ';', recipients)+1, 255)
from email_notification
where process='Business Process 1'
select @r, @c

It was then that I kept reading and learned about another feature of the SELECT statemet.

I have known that I can assign a single value to a variable even if the query returns mutliple results. For example, look at his code:

declare @r varchar( 255)
select @r = recipients
from email_notification

What will be the value of @r? It will be the last value returned by the result set. In this case the value will be "tia@dkranch.net". So does this help me?

Well, T-SQL is incredibly flexible and able to embed it's commands within other commands, much like I used to do in LISP early in my career. The SELECT statement will allow me to add an expression to the query inside the assinment and allow T-SQL to perform my concatenation.

Assume my data set now looks like this:

id process recipients cc
---- -------------------- ----------------------------------------------------- ------
1 Business Process 1 steve@dkranch.net
2 Business Process 1 steve@sqlservercentral.com
3 Business Process 2 delaney@dkranch.net
4 Business Process 3 kyle@dkranch.net
5 Business Process 3 kendall@dkranch.net
6 Business Process 4 tia@dkranch.net

I can run the following:
declare @r varchar( 1000)
select @r = ''
select @r = rtrim(@r) + recipients + ';'
from email_notification
where process = 'business process 1'
select substring( @r, 1, len( @r) - 1)

Note that I initialize my variable to a blank string and then allow T-SQL to concatenate my string for me. The result is "steve@dkranch.net;steve@sqlservercentral.com". The last substring is designed to merely remove the final semicolon from the string.

I included the sample script that I used to write this article here:tam


字符串-Using STUFF
Introduction
Continuing on with taming strings...

The first three parts of this series dealt with manipulating strings in response to some business issue, usually dealing with phone numbers or zip codes. This article continues in the same vein with another alternative for dealing with the formatting issues that occur with these types of data.

The Problem
Usually I receive data in a variety of formats and need to force it into a standard format. In past articles, I have used various string techniques to remove unnecessary data and reformat the data into a standard format. A lack of validation of input data, or no control over the data being input is the cause, but it happens. However, once I remove all extraneous formatting to standardize the data, I sometimes need to add additional formating to standardize the data for a front end application(s).

Suppose that I had scrubbed and cleansed all my phone number data from Part 1 of this series to look like the following:

phone
----------
6055552862
5615552700
9045555680
N/A
5805555371
2815558368
2545558430
3365552797
3365557233
5925553181x4951
96615551222x249
447930555271

Now I wish to format this data so that all phone numbers are stored in the following format:

(999) 999-9999

for US phone numbers. For this article, assume that I have some method of determining which rows are US phone numbers and which are not. This leaves me with the following rows

phone
----------
6055552862
5615552700
9045555680
5805555371
2815558368
2545558430
3365552797
3365557233
5925553181x4951
96615551222x249

that I want to convert to:

phone
----------
(605) 555-2862
(561) 555-2700
(904) 555-5680
(580) 555-5371
(281) 555-8368
(254) 555-8430
(336) 555-2797
(336) 555-7233
(592) 555-3181x4951
(966) 155-51222x249

The Solution
Once again, I decided to search my handy-dandy Books Online (for those of you with young kids, you will get the joke. For the rest of you look here) in the string functions area and I decided to use STUFF. This is a string function that allows you to delete characters from a string and insert a string value into another string with control over the positioning of the insertion. The difference from REPLACE is that this function uses a position in the string to make replacement rather than a pattern.

The format for STUFF is as follows:
STUFF ( character_expression 1, start , length , character_expression 2)
where the parameters are:

Character_expression 1 - The string expression in which to insert data
Start - The start position for the insertion
Length - The number of characters in character_expression 1 to delete.
Character_expression 2 - the string to insert into character_expression 1
Normally, I have used this function in the same manner as REPLACE to remove some data from a string and add different data. However, there is a trick to using this function to convert the phone number data above.

The trick is to not delete any characters. If I use the following code:

declare @d char( 14)
select @d = '6055552862'
select stuff( @d,1 ,1,'('), @d

I get
(055552862

which is not what I want. Instead, I want to change the length parameter to a 0, so no characters are deleted. In this case, an insertion occurs with no characters being deleted. The code now looks like:

declare @d char( 14)
select @d = '6055552862'
select stuff( @d,1 , 0,'('), @d

I get
(6055552862

By expanding this to include all the characters that I need, I can reformat the entire string in one (messy) SQL statement. Here is it:
declare @d char( 14)
select @d = '6055552862'
select stuff( stuff( stuff( stuff( @d,1 ,0, '('), 5, 0, ')'), 6, 0, ' '), 10, 0, '-')

which yeilds
(605) 555-2862

Conclusion
This statement could have easily been done with the SUBSTRING command and a few concatenations. I think the STUFF command is cleaner and easier to read. Once you get used to using it's syntax. Of course, to update the table of information, I would have to be sure that all rows were in the same format or use a WHERE clause to limit the update to those rows that match the criteria.

I hope and am sure this function will really spark some ideas in many of you. Hopefully I will come in handy for you in solving some problem.

As always, I welcome feedback and please rate this article below (and any you read on Swynk). It helps to motivate and assist us authors in writing better columns.

Steve Jones
March 2001


 字符串-Numeric Conversions
Introduction
Continuing on with taming strings...

Often when I am reporting data from SQL Server, I am concatenating information together to form more readable output. Many times this output will also include some numeric data that needs to be placed inside a string. I am not sure exactly why an integer is not implicitly converted to a string, but it doesn't work, so I have to use another solution.

The Problem
Numeric values (whether integer or float) are not implicitly converted to characters within a string concatenation statement. Instead, the following statement (using Northwind):

select customerID + 5
from customers

returns this:

Server: Msg 245, Level 16, State 1, Line 1
Syntax error converting the nvarchar value 'ALFKI' to a column of data type int.

And this code:

select 5 + customerID
from customers

still returns this:

Server: Msg 245, Level 16, State 1, Line 1
Syntax error converting the nvarchar value 'ALFKI' to a column of data type int.

Apparently the numeric data type takes some precendence over the character data types. In some searching of Books Online, I have not found any documentation of this.
In the past I have used CONVERT, and more recently CAST, to convert the numeric data into character data. However there are a few issues with this technique. Consider the following code:

select quantity,
cast( quantity as char( 1)) 'Char_quantity'
from [order details]

This results in something that looks like:
quantity Char_quantity
-------- -------------
12 *
10 *
5 5
9 9

Notice that a number of the results are not returned because the data type does not fit into the space allowed. So what can you do?

In the past, I have usually used code like the following:

select quantity,
rtrim( cast( quantity as varchar( 50))) 'Char_quantity'
from [order details]

This will ensure that I get all results, but still contains some issues.

Suppose that I have a series of decimals like the following:

select 4.35,
12.423,
234.34,
6.3345

which will return:
----- ------- ------- -------
4.35 12.423 234.34 6.3345

Let us apply the previous formatting to this set of data and see the results.

select rtrim( cast( 4.35 as varchar( 50))),
rtrim( cast( 12.423 as varchar( 50))),
rtrim( cast( 234.34 as varchar( 50))),
rtrim( cast( 6.3345 as varchar( 50)))

------------ -------------- ----------- ----------------
4.35 12.423 234.34 6.3345

I have shortened the result set, but this results in a wide result set in Query Analyzer, though the actual strings are the proper length.

But what if I need specific formatting? What if I need a specific length for formatting columns in a report? What if I am looking for xx number of decimals? It is easy to use SUBSTRING and SPACE to trim and then pad the columns, but this is cumbersome. For the decimals, then you run into another problem.

select rtrim( cast( round( 4.35, 2) as varchar( 50))),
rtrim( cast( round( 12.423, 2) as varchar( 50))),
rtrim( cast( round( 234.34, 2) as varchar( 50))),
rtrim( cast( round( 6.3345, 2) as varchar( 50)))

------------ ------------ ---------- ----------
4.35 12.420 234.34 6.3300

In this code, the numbers are rounded to the proper number of decimals, but there are still the original number of characters in the converted strings. SUBSTRING presents a problem here unless I use CHARINDEX to find the decimal and then perform the proper operations. This gets cumbersome and I decided to search for an easier solution.

The Solution
I decided to search my handy-dandy Books Online (for those of you with young kids, you will get the joke. For the rest of you look here) in the string functions area and I found STR. This is a string function that is designed to convert numeric values to characters. Let us apply this to our sample data set.

select str( 4.35, 5, 2),
str( 12.423, 5, 2),
str( 234.34, 5, 2),
str( 6.3345, 5, 2)

----- ----- ----- -----
4.35 12.42 234.3 6.33

This almost appears to work, but there are still some issues. Notice that in column 3, the decimals are not set to 2 because the length of the string exceeds the total length. If we adjust the query as follows:
select ltrim( str( 4.35, 25, 2)),
ltrim( str( 12.423, 25, 2)),
ltrim( str( 234.34, 24, 2)),
ltrim( str( 6.3345, 25, 2))

------------- ------------- ------------ --------------
4.35 12.42 234.34 6.33

Now I have strings with the proper number of decimals. Of course, if I want to get each string set to the same length and right justified, I still have some formatting to do, but I will stop here for now.

Conclusion
I hope that I have shed some light on a little used funciton and an alternative for converting numeric values into strings. No earth shattering technical knowledge in this article, but perhaps I will spark an idea or two in some of you.

As always, I welcome feedback and please rate this article below (and any you read on Swynk). It helps to motivate and assist us authors in writing better columns.

Steve Jones
November 2000


字符串- Using CHARINDEX
Introduction
It''s time to talk about string manipulation. What is it? Why do it? When should we do it? All good questions and some of the answers are in Part 1. This article will work on some advanced string manipulation techniques that I used to solve another problem. If you are new, I write about specific problems, not general ones, in general :), so I hope you enjoy this and learn something.

My Problem
I hope that few of you have run into this, but given the experiences I have had, it''s quite likely more than a few of you have had this problem. In one table, the developers had decided that they needed to store answers to questions asked on the website. Some of the questions that were asked contained multiple possible answers. An example of this type of questions is shown below:

1. What types of computers do you have in your company?
A. Servers
B. Workstations
C. Laptops
D. Mainframes

From this you can see that the user may choose to select 1, 2, 3, or all 4 of the answers. Not a problem, but the developers decided that the easiest way to store this information was in a table like this:

UserID Answer
------ -----------
1 A,B,C
2 B,C
3 A,C,D
4 D

So why is this a problem? Well, how would you find all the people that answered "A"? This one is easy, you would select all users like this:

select UserID, answer from Answer where Answer like ''%A%''

No problem. Well, what about all people that answered "B" and "C"? This one is tougher; you could try

select UserID, answer from Answer where Answer like ''%B%'' and ''%C%''

It does not work. I want to get this result set:

UserID Answer
------ -------
1 A,B,C
2 B,C
3 A,C,D

What you end up with is a result that looks like this:

UserID Answer
------ -------
1 A,B,C

In order to get the result set I want, what I would have to do is union multiple queries. This really does not scale and as the number of answers grows ( our biggest had 12 answers) the queries become increasingly complex.

The Solution
The solution in this case was to "bend" that data so that it is in a more normal form. What I mean by this is to take the table above and change it to look like this:

UserID Answer
------ ------
1 A
1 B
1 C
2 B
2 C
3 A
3 C
3 D
4 D

With the data in this form, we can more easily build a scalable and self-maintaining solution that will grow and handle new questions and answers with minimal work from the development staff and (more importantly) the DBA.

The Code
The objects involved are given below. Since this was corporate code, I have altered names and schemas slightly to protect my company. Other than name changes and some missing fields for other data, this is the same code in our database.

Answer - Table that holds the original data
NewAnswer - Table that holds the new data
dbspBendAnswers - stored procedure that "bends" the data in the Answer table.

In order to extract each answer out and place it in a separate row, I decided to row process this data one row at a time rather than develop a set oriented solution. I normally frown upon this type of code, but this needed to be done quickly and was a one-time maintenance operation with a piece of code that I would have thrown away (if I was not writing this article).

To process this data, I built a cursor of the primary key from this table which was the userID field and the questionID field. I then looped through this cursor and processed each row''s answer data. The processing of each row was where the string manipulation came in.

Each row can be formatted as a pattern that looks like this:

"x,y,z,...,n"

where x is a single answer and the number of answers in this field is unknown. However, each field can also be written in the following manner.

"x" + ",y" + ",z" + ",..." + ",n"

In this format, an algorithm starts to become apparent. If I can delimit each answer by looking at the commas, then what I need to do is parse this string by finding the location of the commas. Once I do this, then the SUBSTRING function can easily be used to extract the answers that are between the commas. If there is no comma in the string, then the assumption is that this is a single answer, so we insert the entire string as a new answer and loop to the next row of data.

In my haste to build this, I decided that I would use the charindex function to find commas and then process the string based on the commas. charindex allows you to find the numeric location in a string of another string. In other words, it returns the position in any string of some character that you are looking for (in my case, commas). So if I had the following string:

''A,B,C''

I could run the following code:

select charindex( '','', ''A,B,C'')

and get these results:

------
2

Where 2 is the position (2nd character) in the string of the comma. One thing to be aware of is that only the first occurance of the string is found. So now I have the location of the first comma. If I include this in a SUBSTRING statement that starts at the beginning of the string, I can extract the first answer.

answer
------
A,B,C

select @c = charindex('','', answer)
select @a = substring( answer, 1, @comma - 1)

select @a

-------
A

Now I have a single answer, how does this help me get the rest of the answers? In my algorithm I have stord the location of the first comma off. What I now do is remove both the first answer and the first comma from the string. This would result in this code and result:

select @str = substring( @str, @comma + 1, len( @str))
select @str

------
B,C

Now I repeat the process and find the next comma. Again, if I get a zero (0) back for the location of the comma, I know I have a single answer left, insert it and end the loop. I check with an IF statement if there is a comma since passing a -1 to the SUBSTRING function returns an error.

Conclusion
This is probably not the most elegant or efficient solution, but it does work. And it was very quick to develop. In fact, it took three or four times as long to write this article as it did to develop the algorithm, test it, and execute it on my data. I do think, however, that this technique will be useful in some situations and is a worthwhile addition to the DBA toolbox. I find many times when I need string manipulation, especially in adhoc queries I write. Like any other tool in SQL Server, it has a time and a place where it is helpful and effective in its application. Spend some time experimenting and learning about the CHARINDEX() function. It will probably come in handy at some point.

Here is the online reference for CHARINDEX for those of you interested.

As always, I welcome feedback and comments and hope that you learned something from this article.

Steve Jones
?000 dkranch


字符串-Using REPLACE
Introduction
Continuing on with taming strings.

In Part 1, I worked with phone numbers to find the area codes in phone number data. This data was in a variety of formats and frankly, quite a mess. Since we allowed users to enter data with very little validation, we got all sorts of invalid data as well as typos. Well, the time finally came for us to clean and validate as much phone data as possible, and so I decided to write about my little adventure.

The Problem
Dealing with phone number data is more than a trivial exercise, both from the technical as well a business viewpoints. If you limit yourself to US phone numbers, then the problem is simplified, but still requires some work. In today's Internet, 24x7 uptime requirements, and international applications, I have seen this become less and less of an option, so I plan on designing the db to hold all kinds of information. Another article will tackle the database design side of storing phone data; this article looks at getting all the data in a standard format.

We had initially designed a single phone number field on forms as a way to handle all types of data in an ergonomic fashion. There was no validation of the data being entered so as to make the data entry as smooth as possible. The result of this was phone number data that looked like this:

1-(520)-555-5821
(904)555-1877
2711 5555458
610-555-3723
000
6195557693
11111111111111111
(111) 555-0986
181-5554761
31 471 55505
31 22555599
1-800-555-9495
415 555 3851
+11455561022
1603555775
++492055599971
407.555.4770
555-6609
202/555-2228
248-555-8300*8263
812-555-8444x312
9999999
1800555SOFT ext. 132
512/555-2000x2563
(800) 555-2588 XT 7
none
800-555-0963 X90454

As you can see, there are a variety of formats in which data was entered (BTW, these are not real phone numbers, I scrambled quite a few of the numbers and made the exchanges 555. If any of them actually work, I apologize in advance). Users have entered parenthesis, dashes, periods, +, etc. as delimiters for various sections of the phone numbers. The spacing, abbreviations, and formats vary from company to company (much to the chagrin of developers) and cry out for some standardization. However, forcing standardization during the data entry annoys and angers ( and loses) users. My company finally came up with a standard data entry solution with some validation of data, but I needed to then standardize the existing data.

The technical solution to storing the data was to standardize the formatting and then include parsing routines in the application logic to handle the display and data entry. We decided a single field will contain the user entered data as numerics only with all other characters stripped out with one exception. For users that have a phone extension, we would include an "x" between the phone number and the extension. By standardizing on this format, all developers can expect a certain format and apply simpler parsing rules to format the data for display.

What String Manipulation?
Here is a problem that needs to be solved, what does this have to do with string manipulation? After all, shouldn't the validation be occuring in the presentation or business layers? Couldn't I use a rule or user defined datatype to standardize the system?

Patience, patience, all will be clear soon.

In order to clean this data, I will be using string manipulation to reformat all the existing phone data in the standard format. Regardless of future validation and schema changes, I would still have to get the existing data into a standard format to ensure the application works.

Now it would be nice to write one SQL statement that would rip through the data and reformat all the data as expected. A couple problems with this; one, it would be difficult to explain in an article. Two, it would probably take more time to decipher than most of you would want to spend. Three, I can't come up with one. Since reason three will tend to override reasons one and two in anything I write, here is what I came up with.

Since I try to avoid row processing which is slow and the kind of thing I usually chastise junior DBAs for implementing, I decided to look for patterns which would enable me to develop an algorithm to clean up this data. I made notes as I conducted the analysis so that I could present readers with some insight into how I go about solving a problem or developing a solution.

Analysis
The first thing I do when solving most problems is look for a pattern. After all, computers are very good at performing repetitive actions and SQL is especially well suited to working with a batch of data. In my examination of the data, I found that most data was basically in one of a few formats:

999 999-9999
(999) 999-9999
999-999-9999

and the extensions tended to look like:

x999
x.999
ext 999
ext.999

The rest of the data is pretty close to these formats with some variations (more or less spaces, +xx for international numbers, some people entered slashes ("/") or periods (".") or some other character, but since I may end up handling these individually or as "one-offs" (things that do not fit a pattern or are an exception), I will ignore these for now.

The format that I am trying to standardize all data into is the following:

9999999999x9999

where all non-numeric characters and spaces are removed. Actually spaces are not important for the parsing routines we employ, so I will ignore embedded spaces, but I think you will be able to adapt my solution easily to remove them.

Back to the analysis...

There are multiple places in the field where each non-numeric character can occur. I could write a series of substring updates (like in Part 1), but this would be quite a bit of work, lots and lots of statements, and probably not of interest to any of you. As I examine the data, what I really want to do is remove all the dashes ( or other characters) from the field.

So how can we remove the parenthesis, dashes, and periods from the phone numbers? One way that I think should work is a simple CONVERT or CAST of the field to an integer value. If I run the following,

select cast( '(555) 555-5555' as int)

I would expect to get:

5555555555

but instead, I get:
Server: Msg 245, Level 16, State 1, Line 1

Syntax error converting the varchar value '(555) 555-55555' to a column of data type int.

Why this occurs, I am not sure (if you are on the SQL Server team, let me know), but it does not. SUBSTRING is not a great option, so I decided to search BOL a little and see what my options were.

The Solution
The first thing I saw in BOL which looked promising was PATINDEX which lets you find the location of a particular string within another string. This along with CHARINDEX (see Part 2) is useful for finding the location of a pattern within a string. I suppose I could have used this and then written a bunch of substrings that would concatenate the before and after portions of the string, but this did not seem very efficient either.

It was around this time that one of our senior developers was doing some data cleaning and sent me a script to execute on the production database. This script contained a series of SQL statements in a batch that ran REPLACE to change the wording in a number of rows in a particular table. The idea then came to me that I could use this function to quickly remove all the non-numeric characters in my data!

The Code
The REPLACE function is very powerful and is used to do exactly what you would expect it to do. It searches a string for some other string that you supply (much like CHARINDEX and PATINDEX), but replaces the second string with a third string that you supply. So if you ran the following code:

declare @a char( 30)
select @a = 'Bob and Jim played catch.'
select @a, replace( @a, 'Jim', 'Jeff')

You get

--------------------------- ---------------------------
Bob and Jim played catch. Bob and Jeff played catch.

As you can see, the first string, @a, is searched for any occurence of the second string, "Jim". If an occurence is found, then it is replaced with the third string, "Jeff".

To apply this to my phone number problem, I decided to write a series of statements that would replace the characters I wanted removed. The three obvious characters I wanted to remove were the dashes, paranthesis, and periods. Actually these are four characters and I ended up with the following script:

update UserInfo
set phone = replace( phone, '-', '')
update UserInfo
set phone = replace( phone, '(', '')
update UserInfo
set phone = replace( phone, ')', '')
update UserInfo
set phone = replace( phone, '.', '')

One thing that many people do not know about many functions (at least I am surprised how many people do not know) is that you can often use an empty string wherever a character parameter is called for. In this case, substituting an empty string as the replacement character allows me to remove offending characters.

Conclusions
There are more possible solutions for solving this problem, but some are better left to another article. I hope this has helped some of you understand how to manipulate and clean data a little better as well as sparked some ideas for even better solutions. My solution worked well and was set based so it was somewhat efficient as well.

For those of you interested in further information about REPLACE, here is the URL for the online reference for REPLACE

As always, I welcome feedback and please rate this article below (and any you read on Swynk). It helps to motivate and assist us authors in writing better columns.

Steve Jones
December 2000


解決关于字符串合併的方法(即類似SUM(字符串字段))
id value
----------- ----------
1 cool
1 nice
1 wow
2 cool
2 wow
3 cool
3 nice
4 nice

This is a result:
id value
----------- -------------
1 cool,nice,wow,
2 cool,wow,
3 cool,nice,
4 nice,

set nocount on
declare @YourResultTable table (id int, value varchar(10))
insert @YourResultTable values(1, ''''cool'''')
insert @YourResultTable values(1, ''''nice'''')
insert @YourResultTable values(1, ''''wow'''')
insert @YourResultTable values(2, ''''cool'''')
insert @YourResultTable values(2, ''''wow'''')
insert @YourResultTable values(3, ''''cool'''')
insert @YourResultTable values(3, ''''nice'''')
insert @YourResultTable values(4, ''''nice'''')
select * from @YourResultTable

declare @z varchar(100),
@Step int,
@q int
select @Step = 0,
@q = 0

declare @ProcessTable table (id int, value varchar(100), ProcessStep int)
insert @ProcessTable
select id, value, 0
from @YourResultTable
order by id, value

update @ProcessTable
set @z = value = case @q when id then @z else '''''''' end + value +
'''','''' ,
@q = @q + case @q when id then 0 else 1 end,
@Step = ProcessStep = @Step + 1
from @ProcessTable

select id,
value
from @ProcessTable t
join (select max(ProcessStep) MaxStep
from @ProcessTable
group by id) x
on ProcessStep = MaxStep

set nocount off

---2000環境下測試通過，大伙如有什麼好的方法，請提出來@!@

其實這是SQL Server里Update語句的妙用。
或許這樣寫明白點:

update @processTable
SET @z=value=CASE WHEN @q=id THEN @z else '''''''' end +value +'''','''',
@q=@q+Case WHEN @q=ID THEN 0 ELSE 1 end,
@step=ProcessStep=@Step+1
from @processTable

Update語句可以這樣，先把一個變量計算後賦給一個欄位，然後再賦給自身。但大家發現沒有，這語句有一定的局限性，就是ID之間必須是相連的，且間隔值是1，否則結果會不正


将一个字符串字段每个记录中的值连接成一个字符串
declare @str varchar(8000)
set @str=''
select @str=@str+fieldname+',' from autohors

select @str 'name'


replace_substring
Version: SQL Server 7.0/2000
Created by: Alexander Chigrik

This stored procedure can be used to search and replace substring
in the char, nchar, varchar and nvarchar columns in all tables
in the current database. You should pass the text value to
search and the text value to replace. So, to replace all
char, nchar, varchar and nvarchar columns which contain
the substring 'John' with the substring 'Bill', you can use the
following (in comparison with the SetTbColvalues stored procedure,
this stored procedure replace only substring, not the entire
column's value):

EXEC replace_substring @search_value = 'John',
@replace_value = 'Bill'

/*
Version: SQL Server 7.0/2000
Created by: Alexander Chigrik
http://www.MSSQLCity.com/ - all about MS SQL
(SQL Server Articles, FAQ, Scripts, Tips and Test Exams).

This stored procedure can be used to search and replace substring
in the char, nchar, varchar and nvarchar columns in all tables
in the current database. You should pass the text value to
search and the text value to replace. So, to replace all
char, nchar, varchar and nvarchar columns which contain
the substring 'John' with the substring 'Bill', you can use the
following (in comparison with the SetTbColvalues stored procedure,
this stored procedure replace only substring, not the entire
column's value):

EXEC replace_substring @search_value = 'John',
@replace_value = 'Bill'
*/

IF OBJECT_ID('replace_substring') IS NOT NULL DROP PROC replace_substring
GO

CREATE PROCEDURE replace_substring (
@search_value varchar(128) = null,
@replace_value varchar(128) = null
)
AS
DECLARE
@execstr varchar(1000),
@objectname sysname,
@colname sysname

SET NOCOUNT ON
IF @search_value IS NULL
BEGIN
RAISERROR ('You must specify the value to search', 16, 1)
RETURN
END
IF @replace_value IS NULL
BEGIN
RAISERROR ('You must specify the value to replace', 16, 1)
RETURN
END

DECLARE tb_fetch_cursor CURSOR FOR
SELECT name FROM sysobjects WHERE type = 'U'
OPEN tb_fetch_cursor
FETCH NEXT FROM tb_fetch_cursor INTO @objectname
WHILE (@@fetch_status <> -1)
BEGIN
DECLARE col_fetch_cursor CURSOR FOR
SELECT name FROM syscolumns WHERE id = OBJECT_ID(@objectname) AND type IN
(SELECT type FROM systypes WHERE name = 'char' OR name = 'nchar' OR name = 'varchar' OR name = 'nvarchar')
OPEN col_fetch_cursor
FETCH NEXT FROM col_fetch_cursor INTO @colname
WHILE (@@fetch_status <> -1)
BEGIN
SELECT @execstr = 'IF EXISTS (SELECT * FROM ' + @objectname + ' WHERE ' + @colname + ' like ''%' + @search_value + '%'') BEGIN SELECT ''' + @objectname + ''' as tbname, ''' + @colname + ''' as colname PRINT '''' UPDATE ' + @objectname + ' SET ' + @colname + ' = REPLACE(' + @colname + ',''' + @search_value + ''',''' + @replace_value + ''') WHERE ' + @colname + ' like ''%' + @search_value + '%'' END'
EXEC (@execstr)
FETCH NEXT FROM col_fetch_cursor INTO @colname
END
DEALLOCATE col_fetch_cursor
FETCH NEXT FROM tb_fetch_cursor INTO @objectname
END
DEALLOCATE tb_fetch_cursor
GO


 default当前的用户
SESSION_USER
是一个 niladic 函数，允许在未指定默认值时将系统为当前会话的用户名提供的值插入到表中。还允许在查询、错误信息等中使用用户名。

语法
SESSION_USER

返回类型
nchar

注释
SESSION_USER 可在 CREATE TABLE 或 ALTER TABLE 语句中与 DEFAULT 约束一起使用，或者用作任何标准函数。

示例
A. 使用 SESSION_USER 返回会话的当前用户名
下例声明一个 char 类型的变量，并将 SESSION_USER 赋为当前值，然后输出该变量并带有文本描述。

DECLARE @session_usr char(30)
SET @session_usr = SESSION_USER
SELECT ''This session''''s current user is: ''+ @session_usr
GO

下面是结果集：

--------------------------------------------------------------
This session''s current user is: dbo

(1 row(s) affected)

B. 将 SESSION_USER 与 DEFAULT 约束一起使用
下例将 SESSION_USER niladic 函数用作 DEFAULT 约束为交货人创建表。

USE pubs
GO
CREATE TABLE deliveries2
(
order_id int IDENTITY(5000, 1) NOT NULL,
cust_id int NOT NULL,
order_date datetime NOT NULL DEFAULT GETDATE(),
delivery_date datetime NOT NULL DEFAULT DATEADD(dd, 10, GETDATE()),
delivery_person char(30) NOT NULL DEFAULT SESSION_USER
)
GO
INSERT deliveries2 (cust_id)
valueS (7510)
INSERT deliveries2 (cust_id)
valueS (7231)
INSERT deliveries2 (cust_id)
valueS (7028)
INSERT deliveries2 (cust_id)
valueS (7392)
INSERT deliveries2 (cust_id)
valueS (7452)
GO

以下查询选择 deliveries2 表中的全部信息。

SELECT order_id AS ''Ord#'', cust_id AS ''Cust#'', order_date,
delivery_date, delivery_person AS ''Delivery''
FROM deliveries2
ORDER BY order_id
GO

下面是结果集：

Ord# Cust# order_date delivery_date Delivery
---- ------ ------------------ -------------------- ----------------
5000 7510 Mar 4 1998 10:21AM Mar 14 1998 10:21AM dbo
5001 7231 Mar 4 1998 10:21AM Mar 14 1998 10:21AM dbo
5002 7028 Mar 4 1998 10:21AM Mar 14 1998 10:21AM dbo
5003 7392 Mar 4 1998 10:21AM Mar 14 1998 10:21AM dbo
5004 7452 Mar 4 1998 10:21AM Mar 14 1998 10:21AM dbo

(5 row(s) affected)

CURRENT_USER
返回当前的用户。此函数等价于 USER_NAME()。

语法
CURRENT_USER

返回类型
sysname

示例
A. 使用 CURRENT_USER 返回当前的用户名
下面的示例将一个变量声明为 char，并将 CURRENT_USER 的当前值指派给它，然后返回该变量，返回时还带有一个文本描述。

SELECT ''The current user is: ''+ convert(char(30), CURRENT_USER)

下面是结果集：

---------------------------------------------------
The current user is: dbo

(1 row(s) affected)

B. 将 CURRENT_USER 用作 DEFAULT 约束
下面的示例创建一个表，该表针对销售行的 order_person 列将 CURRENT_USER 用作 DEFAULT 约束。

USE pubs
IF EXISTS (SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_NAME = ''orders2'')
DROP TABLE orders2
GO
SET NOCOUNT ON
CREATE TABLE orders2
(
order_id int IDENTITY(1000, 1) NOT NULL,
cust_id int NOT NULL,
order_date datetime NOT NULL DEFAULT GETDATE(),
order_amt money NOT NULL,
order_person char(30) NOT NULL DEFAULT CURRENT_USER
)
GO
INSERT orders2 (cust_id, order_amt)
valueS (5105, 577.95)
GO
SET NOCOUNT OFF

下面的查询从 orders2 表中选择所有信息。

SELECT *
FROM orders2

下面是结果集：

order_id cust_id order_date order_amt order_person
----------- ----------- ------------------- ------------- --------------
1000 5105 Mar 4 1998 10:13AM 577.95 dbo

(1 row(s) affected)


SQLSERVER内部函数及内部变量 (潇湘剑公子)
内部函数：

select @@CONNECTIONS
select @@CURSOR_ROWS
select @@DATEFIRST
SELECT @@DBTS
select @@ERROR
select @@rowcount
select @@FETCH_STATUS
SELECT @@CPU_BUSY AS 'CPU ms', GETDATE() AS 'As of'
sELECT @@IDENTITY AS 'Identity'
sELECT @@IDLE AS 'Idle ms', GETDATE() AS 'As of'
SELECT @@IO_BUSY AS 'IO ms', GETDATE() AS 'As of'
SELECT @@LANGID AS 'Language ID'
SELECT @@LANGUAGE AS 'Language Name'
SELECT @@LOCK_TIMEOUT
SELECT @@MAX_CONNECTIONS
SELECT @@MAX_PRECISION
select @@NESTLEVEL AS 'Inner Level'
select @@OPTIONS
SELECT @@PACKET_ERRORS
SELECT @@PACK_RECEIVED
SELECT @@PACK_SENT
SELECT @@PROCID AS 'ProcID'
SELECT @@REMSERVER
SELECT @@SERVERNAME
SELECT @@SERVICENAME
SELECT @@SPID
SELECT @@TEXTSIZE
SET TEXTSIZE 2048
SELECT @@TEXTSIZE
SELECT @@TIMETICKS
SELECT @@TOTAL_ERRORS AS 'Errors', GETDATE() AS 'As of'
SELECT @@TOTAL_READ AS 'Reads', @@TOTAL_WRITE AS 'Writes', GETDATE() AS 'As of'
SELECT @@TOTAL_READ AS 'Reads', @@TOTAL_WRITE AS 'Writes', GETDATE() AS 'As of'
select @@TRANCOUNT
SELECT @@VERSION
select @@FETCH_STATUS

内部变量：

APP_NAME IDENTITY (Function)
CASE ISDATE
CAST and CONVERT ISNULL
COALESCE ISNUMERIC
CURRENT_TIMESTAMP NEWID
CURRENT_USER NULLIF
DATALENGTH PARSENAME
@@ERROR PERMISSIONS
FORMATMESSAGE @@ROWCOUNT
GETANSINULL SESSION_USER
HOST_ID STATS_DATE
HOST_NAME SYSTEM_USER
IDENT_INCR @@TRANCOUNT
IDENT_SEED USER_NAME
@@IDENTI


OPENQUERY
OPENQUERY
在给定的链接服务器（一个 OLE DB 数据源）上执行指定的直接传递查询。可以在查询的 FROM 子句中像引用表名那样引用 OPENQUERY 函数。依据 OLE DB 提供程序的能力，还可以将 OPENQUERY 函数引用为 INSERT、UPDATE 或 DELETE 语句的目标表。尽管查询可能返回多个结果集，但是 OPENQUERY 只返回第一个。

语法
OPENQUERY ( linked_server , 'query' )

参数
linked_server

一个标识符，表示链接的服务器的名称。

'query'

在链接的服务器中执行的查询字符串。

注释
OPENQUERY 不接受参数变量。

示例
下面的示例利用用于 Oracle 的 Microsoft OLE DB 提供程序针对 Oracle 数据库创建一个名为 OracleSvr 链接的服务器。然后，该示例对此链接的服务器使用一个直接传递查询。

说明 本示例假定已经创建了一个名为 ORCLDB 的 Oracle 数据库别名。

EXEC sp_addlinkedserver 'OracleSvr',
'Oracle 7.3',
'MSDAORA',
'ORCLDB'
GO
SELECT *
FROM OPENQUERY(OracleSvr, 'SELECT name, id FROM joe.titles')


OPENROWSET
OPENROWSET
包含访问 OLE DB 数据源中的远程数据所需的全部连接信息。当访问链接服务器中的表时，这种方法是一种替代方法，并且是一种使用 OLE DB 连接并访问远程数据的一次性的、特殊的方法。可以在查询的 FROM 子句中像引用表名那样引用 OPENROWSET 函数。依据 OLE DB 提供程序的能力，还可以将 OPENROWSET 函数引用为 INSERT、UPDATE 或 DELETE 语句的目标表。尽管查询可能返回多个结果集，然而 OPENROWSET 只返回第一个。

语法
OPENROWSET ( 'provider_name'
, { 'datasource' ; 'user_id' ; 'password'
| 'provider_string' }
, { [ catalog.] [ schema.] object
| 'query' }
)

参数
'provider_name'

字符串，它代表在注册表中指定的 OLE DB 提供程序的友好名。provider_name 没有默认值。

'datasource'

字符串常量，它对应着某个特定的 OLE DB 数据源。datasource 是将被传递到提供程序 IDBProperties 接口以初始化提供程序的 DBPROP_INIT_DATASOURCE 属性。通常，这个字符串包含数据库文件的名称、数据库服务器的名称，或者提供程序能理解的用于查找数据库的名称。

'user_id'

字符串常量，它是传递到指定 OLE DB 提供程序的用户名。user_id 为连接指定安全上下文，并将它作为 DBPROP_AUTH_USERID 属性传递进来以初始化提供程序。

'password'

字符串常量，它是将被传递到 OLE DB 提供程序的用户密码。当初始化提供程序时，将 password 作为 DBPROP_AUTH_PASSWORD 属性传递进来。

'provider_string'

提供程序特定的连接字符串，将它作为 DBPROP_INIT_PROVIDERSTRING 属性传递进来以初始化 OLE DB 提供程序。通常 provider_string 封装初始化提供程序所需的所有连接信息。

catalog

目录或数据库的名称，其中驻留着指定的对象。

schema

架构的名称或指定对象的对象所有者名称。

object

对象名称，它唯一地标识出将要操作的对象。

'query'

是字符串常量，发送到提供程序并由提供程序执行。Microsoft? SQL Server? 不处理该查询，但处理由提供程序返回的查询结果（直接传递查询）。对于有些提供程序，它们并没有通过表名而是通过命令语言表现自己的表格格式数据，那么将直接传递查询用于这些提供程序是非常有用的。只要查询提供程序支持 OLE DB Command 对象及其强制接口，那么在远程服务器上就支持直接传递查询。有关更多信息，请参见 SQL Server OLE DB 程序员参考。

注释
如果 OLE DB 提供程序在指定的数据源中支持多个目录和架构，那么就需要目录及架构名称。如果 OLE DB 提供程序并不支持目录和架构，那么可以省略 catalog 及 schema 的值。

如果提供程序只支持架构名，那么必须指定一个两部分名称，形式为 schema.object。如果提供程序只支持目录名，那么必须指定一个三部分名称，形式为 catalog.schema.object。

OPENROWSET 不接受参数变量。

权限
OPENROWSET 权限由传递到 OLE DB 提供程序的用户名的权限确定。

示例
A. 将 OPENROWSET 与 SELECT 语句及用于 SQL Server 的 Microsoft OLE DB 提供程序一起使用
下面的示例使用用于 SQL Server 的 Microsoft OLE DB 提供程序访问 pubs 数据库中的 authors 表，该数据库在一个名为 seattle1 的远程服务器上。从 datasource、user_id 及 password 中初始化提供程序，并且使用 SELECT 语句定义返回的行集。

USE pubs
GO
SELECT a.*
FROM OPENROWSET('SQLOLEDB','seattle1';'sa';'MyPass',
'SELECT * FROM pubs.dbo.authors ORDER BY au_lname, au_fname') AS a
GO

B. 将 OPENROWSET 与对象及用于 ODBC 的 OLE DB 提供程序一起使用
下面的示例使用用于 ODBC 的 OLE DB 提供程序以及 SQL Server ODBC 驱动程序访问 pubs 数据库中的 authors 表，该数据库在一个名为 seattle1 的远程服务器中。提供程序用在 ODBC 提供程序所用的 ODBC 语法中指定的 provider_string 进行初始化，定义返回的行集时使用 catalog.schema.object 语法。

USE pubs
GO
SELECT a.*
FROM OPENROWSET('MSDASQL',
'DRIVER={SQL Server};SERVER=seattle1;UID=sa;PWD=MyPass',
pubs.dbo.authors) AS a
ORDER BY a.au_lname, a.au_fname
GO

C. 使用用于 Jet 的 Microsoft OLE DB 提供程序
下面的示例通过用于 Jet 的 Microsoft OLE DB 提供程序访问 Microsoft Access Northwind 数据库中的 orders 表。

说明 下面的示例假定已经安装了 Access。

USE pubs
GO
SELECT a.*
FROM OPENROWSET('Microsoft.Jet.OLEDB.4.0',
'c:\MSOffice\Access\Samples\northwind.mdb';'admin';'mypwd', Orders)
AS a
GO

D. 使用 OPENROWSET 和 INNER JOIN 中的另一个表
下面的示例从本地 SQL Server Northwind 数据库的 customers 表中，以及存储在相同计算机上 Access Northwind 数据库的 orders 表中选择所有数据

说明 下面的示例假定已经安装了 Access。

USE pubs
GO
SELECT c.*, o.*
FROM Northwind.dbo.Customers AS c INNER JOIN
OPENROWSET('Microsoft.Jet.OLEDB.4.0',
'c:\MSOffice\Access\Samples\northwind.mdb';'admin';'mypwd', Orders)
AS o
ON c.CustomerID = o.CustomerID
GO


OPENDATASOURCE
OPENDATASOURCE
不使用链接的服务器名，而提供特殊的连接信息，并将其作为四部分对象名的一部分。

语法
OPENDATASOURCE ( provider_name, init_string )

参数
provider_name

注册为用于访问数据源的 OLE DB 提供程序的 PROGID 的名称。provider_name 的数据类型为 char，没有默认值。

init_string

连接字符串，这些字符串将要传递给目标提供程序的 IDataInitialize 接口。提供程序字符串语法是以关键字值对为基础的，这些关键字值对由分号隔开，例如："keyword1=value; keyword2=value."

在 Microsoft? Data Access SDK 中定义了基本语法。有关所支持的特定关键字值对的信息，请参见提供程序中的文档。下表列出 init_string 参数中最常用的关键字。

关键字 OLE DB 属性 有效值和描述
数据源 DBPROP_INIT_DATASOURCE 要连接的数据源的名称。不同的提供程序用不同的方法对此进行解释。对于 SQL Server OLE DB 提供程序来说，这会指明服务器的名称。对于 Jet OLE DB 提供程序来说，这会指明 .mdb 文件或 .xls 文件的完整路径。
位置 DBPROP_INIT_LOCATION 要连接的数据库的位置。
扩展属性 DBPROP_INIT_PROVIDERSTRING 提供程序特定的连接字符串。
连接超时 DBPROP_INIT_TIMEOUT 超时值，在该超时值后，连接尝试将失败。
用户 ID DBPROP_AUTH_USERID 用于该连接的用户 ID。
密码 DBPROP_AUTH_PASSWORD 用于该连接的密码。
目录 DBPROP_INIT_CATALOG 连接到数据源时的初始或默认的目录名称。

注释
OPENDATASOURCE 函数可以在能够使用链接服务器名的相同 Transact-SQL 语法位置中使用。因此，就可以将 OPENDATASOURCE 用作四部分名称的第一部分，该名称指的是 SELECT、INSERT、UPDATE 或 DELETE 语句中的表或视图的名称；或者指的是 EXECUTE 语句中的远程存储过程。当执行远程存储过程时，OPENDATASOURCE 应该指的是另一个 SQL Server。OPENDATASOURCE 不接受参数变量。

与 OPENROWSET 函数类似，OPENDATASOURCE 应该只引用那些不经常访问的 OLE DB 数据源。对于访问次数稍多的任何数据源，请为它们定义链接的服务器。无论 OPENDATASOURCE 还是 OPENROWSET 都不能提供链接的服务器定义的全部功能，例如，安全管理以及查询目录信息的能力。每次调用 OPENDATASOURCE 时，都必须提供所有的连接信息（包括密码）。

示例
下面的示例访问来自某个表的数据，该表在 SQL Server 的另一个实例中。

SELECT *
FROM OPENDATASOURCE(
'SQLOLEDB',
'Data Source=ServerName;User ID=MyUID;Password=MyPass'
).Northwind.dbo.Categories

下面是个查询的示例，它通过用于 Jet 的 OLE DB 提供程序查询 Excel 电子表格。

SELECT *
FROM OpenDataSource( 'Microsoft.Jet.OLEDB.4.0',
'Data Source="c:\Finance\account.xls";User ID=Admin;Password=;Extended properties=Excel 5.0')...xactions


 关于MSSQL Server中DATETIME类型数据的处理
作者:潇湘剑公子

返回当前日期和时间
通过函数GETDATE（），你可以获得当前的日期和时间。函数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（）中你可以使用许多种不同风格的日期和时间格式。下表显示了所有的格式。

日期和时间的类型：
类型值 标准 输出
0 Default mon dd yyyy hh:miAM
1 USA mm/dd/yy
2 ANSI yy.mm.dd
3 British/French dd/mm/yy
4 German dd.mm.yy
5 Italian dd-mm-yy
6 - dd mon yy
7 - mon dd,yy
8 - hh:mi:ss
9 Default + milliseconds--mon dd yyyy
hh:mi:ss:mmmAM(or )
10 USA mm-dd-yy
11 JAPAN yy/mm/dd
12 ISO yymmdd
13 Europe Default + milliseconds--dd mon yyyy
hh:mi:ss:mmm(24h)
14 - hh:mi:ss:mmm(24h)

类型0，9，和13总是返回四位的年。对其它类型，要显示世纪，把style值加上100。类型13和14返回24小时时钟的时间。类型0，7，和13返回的月份用三位字符表示（用Nov代表November）.

对表中所列的每一种格式，你可以把类型值加上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 2
Microsoft 5
Magicw3 5
(3 row(s) affected)

Month Posted列显示了每个站点被查询的月份。函数DATEPART（）的返回值是一个整数。你可以用这个函数抽取日期的各个不同部分，如下表所示。

日期的各部分及其简写
日期部分 简写 值
year yy 1753--9999
quarter qq 1--4
month mm 1--12
day of year dy 1--366
day dd 1--31
week wk 1--53
weekday dw 1--7(Sunday--Saturday)
hour hh 0--23
minute mi 0--59
second ss 0--59
milisecond 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 February
Microsoft June
Magicw3 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 - February
Microsoft Tuesday - June
Magicw3 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的记录。换句话说，只有刚好在午夜零点输入的记录才被返回。

问题是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 2
Dec 30 2000 4:13PM 2
Dec 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（）的参数有三个变量。第一个变量代表日期的某一部分,这个例子用到了代表月份的mm。第二个变量指定了时间的间隔――在本例中是一个月。最后一个变量是一个日期，在这个例子中，日期是取自DATETIME型字段firstvisit_date.假设当前日期是June 30,2000，这个语句将返回如下的内容：

User Name Registration Expires
……………………………………………………………………………
Bill Gates Jul 30 2000 4:09PM
President Clinton Jul 30 2000 4:13PM
William Shakespeare Jul 1 2000 4:09PM
(3 row(s) affected)

注意：
使用函数DATEADD（）把一个日期加上一个月，它并不加上30天。这个函数只简单地把月份值加1。


未公开的加密函数。(怡红公子)
如果对MSSQL的用户信息有兴趣的，可能会发现master.dbo.sysxlogins里面存放着用户的口令，可是呢，password字段如果不是null就是一堆看不懂的binary，这个口令是怎么加密的呢？
其实只要仔细看看master.dbo.sp_addlogin就知道了，MSSQL的sp都可以看到代码，真是不错。
让我们来看看它是怎么做的，注意这一行select @passwd = pwdencrypt(@passwd)，这个时后@passwd就被加密了，让我们也来试一下
DECLARE @ClearPWD varchar(255)
DECLARE @EncryptedPWD varbinary(255)
SELECT @ClearPWD = 'test'
SELECT @EncryptedPWD = CONVERT(varbinary(255), pwdencrypt(@ClearPWD))
SELECT @EncryptedPWD
看上去不错，确实被加密了，可是我怎么还原呢？

呵呵，这就没戏了，口令加密都是单向的，用加密后的密文来比较就可以了。
继续看看其它用户相关的sp，可以发现master.dbo.sp_password里面有口令比较的内容。
pwdcompare(@old, password, (CASE WHEN xstatus&2048 = 2048 THEN 1 ELSE 0 END))
不用去理会xstatus，这是一个状态掩码，一般我们用的时候就直接用0就可以了
DECLARE @ClearPWD varchar(255)
DECLARE @EncryptedPWD varbinary(255)
SELECT @ClearPWD = 'test'
SELECT @EncryptedPWD = CONVERT(varbinary(255), pwdencrypt(@ClearPWD))
SELECT pwdcompare(@ClearPWD, @EncryptedPWD, 0)
SELECT pwdcompare('ErrorPassword', @EncryptedPWD, 0)
这样我们就可以使用这两个函数来加密自己的密码了，怎么样，还不错吧？


操作日期型数据和日期函数
1.year(date) 返回年份
month(date) 返回月份
day（date）返回日数
getdate（）以sql规定的标准内部格式返回系统当前的日期和时间
dateadd(datepart,number,date)返回datetime或smalldatetime类型的数据，其值为date值加上datepart和number参数指定的时间间隔
datediff（datepart,date1,date2)返回date1和date2间的时间间隔，其单位由datepart参数决定
datename（detepart，date）返回日期中指定部分的字符串
datepart（datepart，date）返回日期中指定对应的整数值

2. 日期元素对应取值范围
元素名称 缩写 取值范围
year yy 1753-9999
quarter qq 1-4
month mm 1-12
dayof yeat dy 1-366
day dd 1-31
week wk 0-51
hour hh 0-23
minute mi 1-59
second ss 1-59
millsecond ms 0-999
3.系统提供的datetime和smalldatetime类型数据的转换样式
样式1为年的样式为yy，样式2年的格式为yyyy
样式1 样式2
mm/dd/yy usa 1 101
yy.mm.dd ansi 2 102
dd/mm/yy french 3 103
dd.mm.yy german 4 104
dd-mm-yy italian 5 105
dd mon yy - 6 106
mon dd,yy - 7 107
hh:mm:ss - 8 108
mon dd yyyy
hh:mi:ss:mmmmam - 109或9
mm-dd-yy usa 10 110
yy/mm/dd japan 11 111
yymmdd iso 12 112
dd mon yyyy - -113或13
hh:mm:ss:mmm(Am) 14 114
4.select convert(char(6),getdate(),12)

//以下未验证，仅供参考,欢迎批评指正
access

月m,日d，小时h，分mm，秒ss，毫秒zzz
datediff('m',date1，now）
paradox

select * from tablename
where extract(year from rq)=2002
and extract(month from rq)=9
and extract(day from rq)=3


函数-A Few Meta Data UDFs
Introduction
I would like to write the series of articles about useful User-Defined
Functions grouped by the following categories:

Date and Time User-Defined Functions
Mathematical User-Defined Functions
Metadata User-Defined Functions
Security User-Defined Functions
String User-Defined Functions
System User-Defined Functions
Text and Image User-Defined Functions

In this article, I wrote some useful Meta Data User-Defined
Functions.

Meta Data UDFs
These scalar User-Defined Functions return information about the
database and database objects.

To download Meta Data User-Defined Functions click this link:
Download
Meta Data UDFs

COL_LENGTH2
Returns the defined length (in bytes) of a column for a given table
and for a given database.

Syntax
COL_LENGTH2 ( 'database' , 'table' , 'column' )

Arguments
'database'

Is the name of the database. database is an expression of type nvarchar.

'table'

Is the name of the table for which to determine column length
information. table is an expression of type nvarchar.

'column'

Is the name of the column for which to determine length.
column is an expression of type nvarchar.

Return Types
int

The function's text:

CREATE FUNCTION COL_LENGTH2
( @database sysname,
@table sysname,
@column sysname )
RETURNS int
AS
BEGIN
RETURN (COL_LENGTH(@database + '..' + @table, @column))
END
GO

Examples
This example returns the defined length (in bytes) of the au_id
column
of the authors table in the pubs database:

SELECT dbo.COL_LENGTH2('pubs', 'authors', 'au_id')
GO

Here is the result set:

-----------
11

(1 row(s) affected)

COL_ID
Returns the ID of a database column given the corresponding
table name and column name.

Syntax
COL_ID ( 'table' , 'column' )

Arguments
'table'

Is the name of the table. table is an expression of type nvarchar.

'column'

Is the name of the column. column is an expression of type nvarchar.

Return Types
int

The function's text:

CREATE FUNCTION COL_ID
( @table sysname,
@column sysname )
RETURNS int
AS
BEGIN
DECLARE @col_id int
SELECT @col_id = colid FROM syscolumns
WHERE id = OBJECT_ID(@table) AND name = @column
RETURN @col_id
END
GO

Examples
This example returns the ID of the au_fname column of the
authors table in the pubs database:

USE pubs
GO
SELECT dbo.COL_ID('authors', 'au_fname')
GO

Here is the result set:

-----------
3

(1 row(s) affected)

INDEX_ID
Returns the ID of an index given the corresponding
table name and index name.

Syntax
INDEX_ID ( 'table' , 'index_name' )

Arguments
'table'

Is the name of the table. table is an expression of type nvarchar.

'index_name'

Is the name of the index. index_name is an expression of type nvarchar.

Return Types
int

The function's text:

CREATE FUNCTION INDEX_ID
( @table sysname,
@index_name sysname )
RETURNS int
AS
BEGIN
DECLARE @indid int
SELECT @indid = indid FROM sysindexes
WHERE id = OBJECT_ID(@table) AND name = @index_name
RETURN @indid
END
GO

Examples
This example returns the ID of the aunmind index of the
authors table in the pubs database:

USE pubs
GO
SELECT dbo.INDEX_ID('authors', 'aunmind')
GO

Here is the result set:

-----------
2

(1 row(s) affected)

INDEX_COL2
Returns the indexed column name for a given table and for
a given database.

Syntax
INDEX_COL2 ( 'database' , 'table' , index_id , key_id )

Arguments
'database'

Is the name of the database. database is an expression of type nvarchar.

'table'

Is the name of the table.

index_id

Is the ID of the index.

key_id

Is the ID of the key.

Return Types
nvarchar (256)

The function's text:

CREATE FUNCTION INDEX_COL2
( @database sysname,
@table sysname,
@index_id int,
@key_id int )
RETURNS nvarchar (256)
AS
BEGIN
RETURN (INDEX_COL(@database + '..' + @table, @index_id, @key_id))
END
GO

Examples
This example returns the indexed column name of the authors table
in the pubs database (for index_id = 2 and key_id = 1):

SELECT dbo.INDEX_COL2('pubs', 'authors', 2, 1)
GO

Here is the result set:

-----------------------
au_lname

(1 row(s) affected)

ROW_COUNT
Returns the total row count for a given table.

Syntax
ROW_COUNT ( 'table' )

Arguments
'table'

Is the name of the table for which to determine the total row count.
table is an expression of type nvarchar.

Return Types
int

The function's text:

CREATE FUNCTION ROW_COUNT
( @table sysname )
RETURNS int
AS
BEGIN
DECLARE @row_count int
SELECT @row_count = rows FROM sysindexes
WHERE id = OBJECT_ID(@table) AND indid < 2
RETURN @row_count
END
GO

Examples
This example returns the total row count of the authors table
in the pubs database:

USE pubs
GO
SELECT dbo.ROW_COUNT('authors')
GO

Here is the result set:

-----------
23

(1 row(s) affected)

See this link for more information:


函数-生成临时表
create function ufn_emp(@vcity nvarchar(15))
returns @emptab table
(
fname char(10),
lname char(10)
)
begin
insert @emptab select firstname,lastname
from employees where city=@vcity
return
end

//应用
select * from ufn_emp(''seattle'')


 函数-Some Useful UDFs
Introduction
SQL Server 2000 supports User-Defined Functions (UDFs) - one or more
Transact-SQL statements that can be used to encapsulate code for reuse.
User-defined functions cannot make a permanent changes to the data or
modify database tables. UDF can change only local objects for this UDF,
such as local cursors or variables.

There are three types of UDF in SQL Server 2000:

Scalar functions
Inline table-valued functions
Multistatement table-valued functions

Scalar functions return a single data value (not a table)
with RETURNS clause. Scalar functions can use all scalar
data types, with exception of timestamp and user-defined data types.

Inline table-valued functions return the result set of a single
SELECT statement.

Multistatement table-valued functions return a table, that was
built with many TRANSACT-SQL statements.

User-defined functions can be invoked from a query like built-in
functions such as OBJECT_ID, LEN, DATEDIFF, or can be executed
through an EXECUTE statement like stored procedures.

UDF examples

Database creation date
This UDF will return the creation date for a given database (you should
specify database name as parameter for this UDF):

CREATE FUNCTION dbo.DBCreationDate
( @dbname sysname )
RETURNS datetime
AS
BEGIN
DECLARE @crdate datetime
SELECT @crdate = crdate FROM master.dbo.sysdatabases
WHERE name = @dbname
RETURN ( @crdate )
END
GO

This is the example for use:

SELECT dbo.DBCreationDate('pubs')
GO

Date the object was created
This UDF will return the creation date for a given object in the
current database:

CREATE FUNCTION dbo.ObjCreationDate
( @objname sysname)
RETURNS datetime
AS
BEGIN
DECLARE @crdate datetime
SELECT @crdate = crdate FROM sysobjects WHERE name = @objname
RETURN ( @crdate )
END
GO

This is the example for use:

SELECT dbo.ObjCreationDate('authors')
GO

Get date part of datetime value
This UDF will return the date part of datetime value:

CREATE FUNCTION dbo.DatePart
( @fDate datetime )
RETURNS varchar(10)
AS
BEGIN
RETURN ( CONVERT(varchar(10),@fDate,101) )
END
GO

This is the example for use:

SELECT dbo.DatePart('11/11/2000 11:15AM')
GO

Get time part of datetime value
This UDF will return the time part of datetime value:

CREATE FUNCTION dbo.TimePart
( @fDate datetime )
RETURNS varchar(10)
AS
BEGIN
RETURN ( CONVERT(varchar(7),right(@fDate,7),101) )
END
GO

This is the example for use:

SELECT dbo.TimePart('11/11/2000 11:15AM')
GO

Get the number of working days between two dates
This UDF will return the number of working days between two dates
(not including these dates):

CREATE FUNCTION dbo.GetWorkingDays
( @StartDate datetime,
@EndDate datetime )
RETURNS INT
AS
BEGIN
DECLARE @WorkDays int, @FirstPart int
DECLARE @FirstNum int, @TotalDays int
DECLARE @LastNum int, @LastPart int
IF (DATEDIFF(day, @StartDate, @EndDate) < 2)
BEGIN
RETURN ( 0 )
END
SELECT
@TotalDays = DATEDIFF(day, @StartDate, @EndDate) - 1,
@FirstPart = CASE DATENAME(weekday, @StartDate)
WHEN 'Sunday' THEN 6
WHEN 'Monday' THEN 5
WHEN 'Tuesday' THEN 4
WHEN 'Wednesday' THEN 3
WHEN 'Thursday' THEN 2
WHEN 'Friday' THEN 1
WHEN 'Saturday' THEN 0
END,
@FirstNum = CASE DATENAME(weekday, @StartDate)
WHEN 'Sunday' THEN 5
WHEN 'Monday' THEN 4
WHEN 'Tuesday' THEN 3
WHEN 'Wednesday' THEN 2
WHEN 'Thursday' THEN 1
WHEN 'Friday' THEN 0
WHEN 'Saturday' THEN 0
END
IF (@TotalDays < @FirstPart)
BEGIN
SELECT @WorkDays = @TotalDays
END
ELSE
BEGIN
SELECT @WorkDays = (@TotalDays - @FirstPart) / 7
SELECT @LastPart = (@TotalDays - @FirstPart) % 7
SELECT @LastNum = CASE
WHEN (@LastPart < 7) AND (@LastPart > 0) THEN @LastPart - 1
ELSE 0
END
SELECT @WorkDays = @WorkDays * 5 + @FirstNum + @LastNum
END
RETURN ( @WorkDays )
END
GO

This is the example for use:

SELECT dbo.GetWorkingDays ('11/13/2000', '12/27/2000')
GO

Literature
1. SQL Server Books Online

2. Super Administrator: Granting Wishes with UDF
http://msdn.microsoft.com/library/periodic/period00/sql00i11.htm


函数-Function Primer
Introduction
Many programming languages have supported User Defined Functions for years, but they are new to SQL Server 2000. In this article we will look at some of the ways functions can be used within SQL Server 2000.

This document was developed against SQL Server 2000 Beta 2, so some points may be subject to change in the release version.

What are functions?
Functions are pre-prepared pieces of code that may accept parameters, and always return a value. Older versions of SQL Server supply some built-in functions, such as getdate() and object_name() but to be able to define your own functions is a new feature in SQL Server 2000.

Throughout the text I will use the abbreviation UDF for User Defined Function.

All functions (not just UDFs) are split into two groups, deterministic and non-deterministic. Deterministic functions will always return the save value if provided with the same parameters. Non-deterministic functions may produce different results each time they are called, even if any supplied parameters are unchanged.

SQL Server Books Online also uses the term "scalar functions" - these are simply functions that return one value, as opposed to functions that return a table.

getdate() is a good example of both a scalar and a non-deterministic function - call it twice in a row and you will get two different date-time results, but only one value from each call.

A simple UDF
It's not uncommon to want to know the number of days in particular month, so let's look at a simple function to do this. Here is the code:

create function DaysInMonth (@when datetime)
returns int
as
BEGIN
declare @rv int

if datepart(month, @when) = 2
begin
if datepart(year, @when) % 400 = 0
select @rv = 29
else
if datepart(year, @when) % 100 = 0
select @rv = 28
else
if datepart(year, @when) % 4 = 0
select @rv = 29
else
select @rv = 28
end -- if
else
begin
select @rv = case (datepart(month, @when))
when 4 then 30
when 6 then 30
when 9 then 30
when 11 then 30
else 31
end -- case
end -- else

return @rv

END -- fn def

To execute the function,

select dbo.DaysInMonth('1 may 2000')
select dbo.DaysInMonth(getdate())

As you can see, most of the code could have come from any SQL Server stored procedure. The new features are:

create function DaysInMonth (@when datetime)
returns int

Clearly this is a function definition header - the returns int states that the function returns a single integer value.

return @rv

The return @rv command must be the final line of the function, and it's this instruction that sends the output value back to the call. Almost any SQL Server data type can be returned by a UDF.

Using function calls
You can code a function call anywhere you could code a variable or literal of the same data type as the function returns. For example, to calculate the number of days between now and the end of the month:

select dbo.DaysInMonth(getdate()) - datepart(day, getdate())

Note the repeated use of the owner qualification before the UDF name, (dbo.DaysInMonth() instead of DaysInMonth() - This qualification is a SQL Server requirement in Beta 2, and will probably go forward into the release version.

Functions within functions
You can nest a function within another of you so wish.

create function daysTillMonthEnd (@when Datetime)
returns int
as
begin
return dbo.DaysInMonth(@when) - datepart(day, @when)
end

Restrictions on using functions within functions
There is a restriction on this which I ran straight into when I first started to play with UDFs. You cannot use a built-in non-deterministic function within a user defined function. When I made my first stab at coding the DaysInMonth function shown above, I tried to use the getdate() function as a default in case no parameters were supplied in the function call. SQL Server will not allow this.

Using other data types
You can return almost any data type in a function, including User Defined Data Types. The following simple example returns a UDDT based on the varchar data type.

sp_addtype varstring, 'varchar(32)'
go
create function capitalise (@string varstring)
returns varstring
as
BEGIN
return UPPER(left(@string, 1)) +
LOWER(substring (@string, 2, datalength(@string)))
END
go
select dbo.capitalise ('abcdef')

SQL Server 2000 will not allow you to return text, image, cursor or timestamp data types from a function.

Recursive functions
Functions have the ability to call themselves, or "recurse". The example below works out the factorial of the integer input.

create function factorial (@i bigint)
returns bigint
as
BEGIN

declare @internalI bigint

if @i > 20 OR @i IS NULL
select @internalI = NULL
else
if @i < 2
select @internalI = @i
else
select @internalI = @i * dbo.factorial(@i - 1)

return @internalI

END -- fn def
go
select dbo.factorial (3)
select dbo.factorial (8)

For the non-mathematically inclined, factorials are what you get when you multiply together all the whole numbers between 1 and the number you thought of. For example:

The Factorial of 3 = 1 x 2 x 3 = 6
The Factorial of 8 = 1 x 2 x 3 x 4 x 5 x 6 x 7 x 8 = 40320
Functions can recurse up to level 32 deep, after which SQL Server will generate an error. The example above is restricted to 20 times because Factorial 21 is too big for the bigint data type.

Passing tables out of functions
Create a function that passes back a table and what you end up with is a kind of "parameterized view" - something you can treat as a view, but which will return different values depending on the parameters passed in. The following example for the PUBS database takes an author_id as it's parameter and returns a table containing all the author IDs that have collaborated with them, and the title_id they worked together on.

create function collaborator (@au_id char(11))
returns @collaborator table (
target_author char(11),
title_id char(6),
partner_author char(11)
)
as
BEGIN
insert @collaborator
select l.au_id, l.title_id, r.au_id
from titleauthor l inner join titleauthor r
on l.title_id = r.title_id AND l.au_id <> r.au_id
where l.au_id = COALESCE(@au_id, l.au_id)
order by l.title_id, l.au_id

RETURN

END -- fn def

select * from dbo.collaborator('472-27-2349')
select * from dbo.collaborator(NULL)

Note the use of the COALESCE function. We use this to return data for all authors that have collaborated on a book if no specific author_id is given.

You could code the line create function collaborator (@au_id char(11) = NULL) to define a default value for the parameter, and SQL Server would accept this. However, if you then try calling the function without parameters (a perfectly valid thing to do with stored procedure parameters) then SQL Server will complain. Instead you have to code dbo.collaborator(DEFAULT) for the function call.

In-line table functions
SQL Server differentiates between "user defined functions that return a table" and "in-line table functions". The previous example is a "user defined functions that return a table". We can re-code this as an in-line function as follows:

create function collaborator_inline (@au_id char(11))
returns table as
RETURN (
select l.au_id as "author",
l.title_id as "title",
r.au_id as "co-author"
from titleauthor l inner join titleauthor r
on l.title_id = r.title_id AND l.au_id <> r.au_id
where l.au_id = COALESCE(@au_id, l.au_id)
-- order by l.title_id, l.au_id
)
go

select * from dbo.collaborator_inline('724-80-9391')
select * from dbo.collaborator_inline(NULL)

This syntax is more compact than the previous example, but also more restrictive. While the "user defined functions that return a table" can include any amount of code, the in-line version is restricted to a single select statement. Even the "order by" clause needs to be removed from the in-line version.

Table function in joins
Parameterized functions that return tables may be part of a standard SQL join statement.

select *
from dbo.collaborator('472-27-2349') inner join authors as a
on partner_author = a.au_id

Functions as columns in views
UDFs can be used as columns in a view.

Building on the daysInMonth() function we defined earlier, and a new function startOfMonth() function, we can create this NORTHWIND database view showing daily average sales figures per month without going to excessive lengths to cater for months with different numbers of days.

create function startOfMonth (@when datetime)
returns smalldatetime as
BEGIN
return dateadd (day, 1 + datepart(day, @when) * -1, @when)
END -- fn def
go

create view monthlyAverages as
select month,
sum(quantity) /
dbo.daysInMonth(month)
as dailyAvgSale
from (select dbo.startofMonth (orderDate)
as month,
quantity,
o.OrderID
from orders o
inner join "order details" od
on o.OrderID = od.OrderID
) as derived
group by month
go

select * from monthlyAverages order by month

Functions and constraints
You can use functions as to define constraints, provided that both the table and the function share the same owner. This example defines a function called " midnight" that truncates a date-time to midnight, then uses this in a table's check constraint to ensure that the prevShutdown column in test_table is always at least a day earlier then the value in the startup column.

create function midnight (@when smalldatetime)
returns smalldatetime
as
BEGIN
return convert(varchar(11), @when)
END -- fn def
go

create table test_table (
id int identity,
prevShutdown smalldatetime,
startup smalldatetime,
CONSTRAINT chk_midnight CHECK (prevShutdown < dbo.midnight(startup))
)
go

You can also use a function as the basis for a default constraint, but presently you are limited to using constants in the function parameters, which restricts their usefulness.

Schema bound functions
Particularly with older versions of SQL Server, you could get into trouble if you defined one object that was dependent on another, then dropped or changed the subordinate object. (In plain English: if you created a view on a table, then dropped the table, the view would still exist but it would obviously not work)

Schema binding tries to put a stop to this. If you create a function and specify it is Schema Bound, then the objects it depends on cannot be altered without you first dropping the schema binding. There are limitations as to when this will work (it will not work across multiple databases for example) but it is still a very useful feature.

Schema binding is invoked at function creation time. We can change our collaborator() function to a schema bound version as shown below. Note that in addition to the with schemabinding option, we need to specify a two part name (dbo.titleauthor instead of just titleauthor) for all referenced objects.

create function collaborator_sb (@au_id char(11))
returns table
with schemabinding
as
RETURN (
select l.au_id as "author",
l.title_id as "title",
r.au_id as "co-author"
from dbo.titleauthor l inner join dbo.titleauthor r
on l.title_id = r.title_id AND l.au_id <> r.au_id
where l.au_id = COALESCE(@au_id, l.au_id)
-- order by l.title_id, l.au_id
)

As long as schema binding is in place for this function, any attempt to change the structure of the titleauthor table will generate an error.

Overloading functions
Overloading is the practice of having multiple functions with the name name, each doing different things. The '+' operator is overloaded as it can both add up numbers and concatenate strings.

select 1 + 2 + 3
select '1' + '2' + '3'

In Beta 2 at least, overloading functions is a non-starter due to the UNIQUE constraint on the sysobjects columns name and uid. There is some discussion in the Beta support newsgroups about implementing this in future, but I would not hold my breath.

About the author


函数-A Few Mathematical UDFs
Introduction
I would like to write the series of articles about useful User-Defined
Functions grouped by the following categories:

Date and Time User-Defined Functions
Mathematical User-Defined Functions
Metadata User-Defined Functions
Security User-Defined Functions
String User-Defined Functions
System User-Defined Functions
Text and Image User-Defined Functions

In this article, I wrote some useful Mathematical User-Defined
Functions.

Mathematical UDFs
These scalar User-Defined Functions perform a calculation, usually
based on input values provided as arguments, and return a numeric
value.

To download Mathematical User-Defined Functions click this link:
Download Mathematical UDFs

Factorial
Returns the factorial value of the given number.

Syntax
Factorial ( number )

Arguments
number

Is the integer value.

Return Types
bigint

The function''s text:

CREATE FUNCTION Factorial
( @number int )
RETURNS bigint
AS
BEGIN
DECLARE @i int, @factorial bigint
IF @number > 20 RETURN 0
SET @i = 1
SET @factorial = 1
WHILE @i <= @number
BEGIN
SET @factorial = @factorial * @i
SET @i = @i + 1
END
RETURN @factorial
END
GO

Examples
This example returns the factorial value of the number 5:

SELECT dbo.Factorial(5)
GO

Here is the result set:

--------------------
120

(1 row(s) affected)

PercentFrom
Returns the percent of the expression1 in the expression2.

Syntax
PercentFrom ( expression1, expression2 )

Arguments
expression1

Is an expression of the exact numeric or approximate numeric
data type category.

expression2

Is an expression of the exact numeric or approximate numeric
data type category.

Return Types
float

The function''s text:

CREATE FUNCTION PercentFrom
( @expression1 SQL_VARIANT,
@expression2 SQL_VARIANT )
RETURNS float
AS
BEGIN
RETURN (CAST(@expression1 AS FLOAT)/CAST(@expression2 AS FLOAT)*100)
END
GO

Examples
This example returns the percent of the 137 in the 273:

SELECT dbo.PercentFrom(137, 273)
GO

Here is the result set:

-----------------------------------------------------
50.183150183150182

(1 row(s) affected)

Percentvalue
Returns the percent''s value from the given expression for the
given percent.

Syntax
Percentvalue ( expression, percent )

Arguments
expression

Is an expression of the exact numeric or approximate numeric
data type category.

percent

Is the integer value.

Return Types
float

The function''s text:

CREATE FUNCTION Percentvalue
( @expression SQL_VARIANT,
@percent int )
RETURNS float
AS
BEGIN
RETURN ( CAST(@expression AS FLOAT) / 100 * @percent )
END
GO

Examples
Returns the percent''s value from the number 137 for the percent 11:

SELECT dbo.Percentvalue (137, 11)
GO

Here is the result set:

-----------------------------------------------------
15.07

(1 row(s) affected)

Degree
Returns the degree for the given number and degree value.

Syntax
Degree ( number, degree )

Arguments
number

Is an expression of the exact numeric or approximate numeric
data type category.

degree

Is the integer value.

Return Types
float

The function''s text:

CREATE FUNCTION Degree
( @number SQL_VARIANT,
@degree int )
RETURNS float
AS
BEGIN
DECLARE @i int, @res float
SET @i = 1
SET @res = 1
WHILE @i <= @degree
BEGIN
SET @res = CAST(@number AS FLOAT) * @res
SET @i = @i + 1
END
RETURN @res
END
GO

Examples
Returns the degree 4 for the number 3:

SELECT dbo.Degree(3, 4)
GO

Here is the result set:

-----------------------------------------------------
81.0

(1 row(s) affected)

Allocation
Returns the allocation from the m by n.

Syntax
Allocation ( m, n )

Arguments
m

Is the integer value.

n

Is the integer value.

Return Types
int

The function''s text:

CREATE FUNCTION Allocation
( @m int,
@n int )
RETURNS int
AS
BEGIN
RETURN (dbo.Factorial(@m)/dbo.Factorial(@m-@n))
END
GO

Examples
Returns the allocation from the 5 by 3:

SELECT dbo.Allocation(5,3)
GO

Here is the result set:

-----------
60

(1 row(s) affected)

Combination
Returns the combination from the m by n.

Syntax
Combination ( m, n )

Arguments
m

Is the integer value.

n

Is the integer value.

Return Types
int

The function''s text:

CREATE FUNCTION Combination
( @m int,
@n int )
RETURNS int
AS
BEGIN
RETURN (dbo.Factorial(@m)/(dbo.Factorial(@m-@n)*dbo.Factorial(@n)))
END
GO

Examples
Returns the combination from the 5 by 3:

SELECT dbo.Combination(5,3)
GO

Here is the result set:

-----------
10

(1 row(s) affected


CREATE FUNCTION [DBO].[FUN_BJ] (@N INT,@I INT,@RATE FLOAT,@T FLOAT)
RETURNS FLOAT
AS
BEGIN
DECLARE
@JC FLOAT,
@NI INT
IF (@N<@I)OR(@RATE=0)OR(@T=0)
BEGIN
RETURN(0.0)
END

SET @NI=@N-@I
SET @JC=POWER(EXP(-1*@RATE*@T),@I)*POWER((1-EXP(-1*@RATE*@T)),@NI)
--循环计算
WHILE(@I>0)
BEGIN
--求阶乘
SET @JC=@JC*@N/@I
SET @I=@I-1
SET @N=@N-1
END
RETURN(@JC)
END


金额阿拉伯数字转换为中文的自定义函数(_rambo)
CREATE FUNCTION ChangeBigSmall
(@ChangeMoney money)
RETURNS VarChar(100) AS

BEGIN
Declare @String1 char(20)
Declare @String2 char(30)
Declare @String4 Varchar(100)
Declare @String3 Varchar(100) --从原A值中取出的值
Declare @i int --循环变量
Declare @J Int --A的值乘以100的字符串长度
Declare @Ch1 Varchar(100) --数字的汉语读法
Declare @Ch2 Varchar(100) --数字位的汉字读法
Declare @Zero Int --用来计算连续有几个零
Declare @Returnvalue VarChar(100)

Select @Returnvalue = ''
Select @String1 = '零壹贰叁肆伍陆柒捌玖'
Select @String2 = '万仟佰拾亿仟佰拾万仟佰拾元角分'

Select @String4 = Cast(@ChangeMoney*100 as int)

select @J=len(cast((@ChangeMoney*100) as int))

Select @String2=Right(@String2,@J)

Select @i = 1

while @i<= @j Begin

Select @String3 = Substring(@String4,@i,1)

if @String3<>'0' Begin

Select @Ch1 = Substring(@String1, Cast(@String3 as Int) + 1, 1)
Select @Ch2 = Substring(@String2, @i, 1)
Select @Zero = 0 --表示本位不为零
end
else Begin
If (@Zero = 0) Or (@i = @J - 9) Or (@i = @J - 5) Or (@i = @J - 1)
Select @Ch1 = '零'
Else
Select @Ch1 = ''

Select @Zero = @Zero + 1 --表示本位为0

--如果转换的数值需要扩大，那么需改动以下表达式 I 的值。
Select @Ch2 = ''

If @i = @J - 10 Begin
Select @Ch2 = '亿'
Select @Zero = 0
end

If @i = @J - 6 Begin
Select @Ch2 = '万'
Select @Zero = 0
end

if @i = @J - 2 Begin
Select @Ch2 = '元'
Select @Zero = 0
end

If @i = @J
Select @Ch2 = '整'

end

Select @Returnvalue = @Returnvalue + @Ch1 + @Ch2

select @i = @i+1
end

--最后将多余的零去掉
If CharIndex('仟仟',@Returnvalue) <> 0
Select @Returnvalue = Replace(@Returnvalue, '仟仟', '仟')

If CharIndex('佰佰',@Returnvalue) <> 0
Select @Returnvalue = Replace(@Returnvalue, '佰佰', '佰')

If CharIndex('零元',@Returnvalue) <> 0
Select @Returnvalue = Replace(@Returnvalue, '零元', '元')

If CharIndex('零万',@Returnvalue) <> 0
Select @Returnvalue = Replace(@Returnvalue, '零万', '万')

If CharIndex('零亿',@Returnvalue) <> 0
Select @Returnvalue = Replace(@Returnvalue, '零亿', '亿')

If CharIndex('零整',@Returnvalue) <> 0
Select @Returnvalue = Replace(@Returnvalue, '零整', '整')

If CharIndex('零佰',@Returnvalue) <> 0
Select @Returnvalue = Replace(@Returnvalue, '零佰', '零')

If CharIndex('零仟',@Returnvalue) <> 0
Select @Returnvalue = Replace(@Returnvalue, '零仟', '零')

If CharIndex('元元',@Returnvalue) <> 0
Select @Returnvalue = Replace(@Returnvalue, '元元', '元')

return @Returnvalue

END


SQL Server里函数的两种用法（可以代替游标）
1． 因为update里不能用存储过程，然而要根据更新表的某些字段还要进行计算。我们常常采用游标的方法，这里用函数的方法实现。

函数部分：
CREATE FUNCTION [DBO].[FUN_GETTIME] (@TASKPHASEID INT)
RETURNS FLOAT AS
BEGIN
DECLARE @TASKID INT,
@HOUR FLOAT,
@PERCENT FLOAT,
@RETURN FLOAT
IF @TASKPHASEID IS NULL
BEGIN
RETURN(0.0)
END

SELECT @TASKID=TASKID,@PERCENT=ISNULL(WORKPERCENT,0)/100
FROM TABLETASKPHASE
WHERE ID=@TASKPHASEID

SELECT @HOUR=ISNULL(TASKTIME,0) FROM TABLETASK
WHERE ID=@TASKID

SET @RETURN=@HOUR*@PERCENT
RETURN (@RETURN)
END

调用函数的存储过程部分
CREATE PROCEDURE [DBO].[PROC_CALCCA]
@ROID INT
AS
BEGIN
DECLARE @CA FLOAT

UPDATE TABLEFMECA
SET
Cvalue_M= ISNULL(MODERATE,0)*ISNULL(FMERATE,0)*ISNULL(B.BASFAILURERATE,0)*[DBO].[FUN_GETTIME](C.ID)
FROM TABLEFMECA ,TABLERELATION B,TABLETASKPHASE C
WHERE ROID=@ROID AND TASKPHASEID=C.ID AND B.ID=@ROID

SELECT @CA=SUM(ISNULL(Cvalue_M,0)) FROM TABLEFMECA WHERE ROID=@ROID

UPDATE TABLERELATION
SET CRITICALITY=@CA
WHERE ID=@ROID
END
GO

2． 我们要根据某表的某些记录，先计算后求和，因为无法存储中间值，平时我们也用游标的方法进行计算。但sqlserver2000里支持
SUM ( [ ALL | DISTINCT ] expression )

expression

是常量、列或函数，或者是算术、按位与字符串等运算符的任意组合。因此我们可以利用这一功能。

函数部分：

CREATE FUNCTION [DBO].[FUN_RATE] (@PARTID INT,@ENID INT,@SOURCEID INT, @QUALITYID INT,@COUNT INT)

RETURNS FLOAT AS
BEGIN
DECLARE @QXS FLOAT, @G FLOAT, @RATE FLOAT

IF (@ENID=NULL) OR (@PARTID=NULL) OR (@SOURCEID=NULL) OR (@QUALITYID=NULL)
BEGIN
RETURN(0.0)
END

SELECT @QXS= ISNULL(XS,0) FROM TABLEQUALITY WHERE ID=@QUALITYID
SELECT @G=ISNULL(FRATE_G,0) FROM TABLEFAILURERATE
WHERE (SUBKINDID=@PARTID) AND( ENID=@ENID) AND ( DATASOURCEID=@SOURCEID) AND( ( (ISNULL(MINCOUNT,0)<=ISNULL(@COUNT,0)) AND ( ISNULL(MAXCOUNT,0)>=ISNULL(@COUNT,0)))
OR(ISNULL(@COUNT,0)>ISNULL(MAXCOUNT,0)))

SET @RATE=ISNULL(@QXS*@G,0)
RETURN (@RATE)
END

调用函数的存储过程部分：

CREATE PROC PROC_FAULTRATE

@PARTID INTEGER, @QUALITYID INTEGER, @SOURCEID INTEGER, @COUNT INTEGER, @ROID INT, @GRADE INT,@RATE FLOAT=0 OUTPUTAS
BEGIN
DECLARE
@TASKID INT
SET @RATE=0.0

SELECT @TASKID=ISNULL(TASKPROID,-1) FROM TABLERELATION WHERE ID=(SELECT PID FROM TABLERELATION WHERE ID=@ROID)

IF (@TASKID=-1) OR(@GRADE=1) BEGIN
SET @RATE=0
RETURN
END

SELECT @RATE=SUM([DBO].[FUN_RATE] (@PARTID,ENID,@SOURCEID, @QUALITYID,@COUNT) *ISNULL(WORKPERCENT,0)/100.0)

FROM TABLETASKPHASE
WHERE TASKID=@TASKID
END
GO

函数还可以返回表等，希望大家一起讨论sqlserver里函数的妙用。


TitleCase
TitleCase.sql

This function converts a string into title case. It does have some limitations but should fit most scenarios. This code was originally developed by Russell Jones, our resident VB developer, and turned into a UDF by myself. The script has been tested on SQL Server 2000.

Author: Darryl Fryatt

CREATE FUNCTION TitleCase (@DataIn VARCHAR(50))
RETURNS Varchar(50)
AS BEGIN
DECLARE @Store VARCHAR(50)
DECLARE @i INT
SET @Store = LOWER(@DataIn)
SET @Store = UPPER(LEFT(@Store,1)) + SUBSTRING(@Store,2,LEN(@Store))

WHILE CHARINDEX(' ',@Store,1) > 0
BEGIN
SET @i = CHARINDEX(' ',@Store,1)
SET @Store = LEFT(@Store,@i-1) + '~*' + UPPER(SUBSTRING(@store,@i + 1,1)) +SUBSTRING(@Store,@i+2,LEN(@Store))
END

SET @Store = REPLACE(@Store,'~*',' ')
SET @DataIn = @Store
RETURN @DataIn
END


FormatDate
FormatDate.sql

This script will create a UDF called FormatDate that allows you to format a date by simply supplying a format such as 'mm/dd/yyyy', 'yyyymmdd', 'dddd, mmmm d1, yyyy', etc. The comment header provides additional examples of how to use the function. The script has been tested on SQL Server 2000.

Author: Rusty Hansen

Author: Rusty Hansen 8-21-2001
Description: Formats a date to a specific format.
Parameters:
@dDate = A value or field of datatype datetime or a value or field that can be explicitly converted to
a datetime datatype.
@sFormat varchar(40) = Format codes using the characters described below

MMMM or DDDD = the full name for the day or month
MMM or DDD = the first 3 letters of the month or day
MM or DD = the two digit code signifying the month or day
M1 or D1 = the month or day value without a preceding zero
YYYY = a four digit year
YY = a two digit year

All other characters will not be replaced such as / - . * # a b z x % and will show
up in the date in the same relative position that they appear in the format
parameter.

Examples
select dbo.FormatDate('9/21/2001','dddd, mmmm d1, yyyy') --> Friday, September 21, 2001
select dbo.FormatDate('9/21/2001','mm/dd/yyyy') --> 09/21/2001
select dbo.FormatDate('9/21/2001','mm-dd-yyyy') --> 09/21/2001
select dbo.FormatDate('9/21/2001','yyyymmdd') --> 20010921
select dbo.FormatDate('9/5/2001','m1/d1/yy') --> 9/5/01
select dbo.FormatDate('9/21/2001','mmm-yyyy') --> Sep-2001

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

create function [dbo].[FormatDate]
(
@dDate datetime --Date value to be formatted
,@sFormat varchar(40) --Format for date value
)
returns varchar(40)
as
begin

-- Insert the Month
-- ~~~~~~~~~~~~~~~~
set @sFormat = replace(@sFormat,'MMMM',datename(month,@dDate))
set @sFormat = replace(@sFormat,'MMM',convert(char(3),datename(month,@dDate)))
set @sFormat = replace(@sFormat,'MM',right(convert(char(4),@dDate,12),2))
set @sFormat = replace(@sFormat,'M1',convert(varchar(2),convert(int,right(convert(char(4),@dDate,12),2))))

-- Insert the Day
-- ~~~~~~~~~~~~~~
set @sFormat = replace(@sFormat,'DDDD',datename(weekday,@dDate))
set @sFormat = replace(@sFormat,'DDD',convert(char(3),datename(weekday,@dDate)))
set @sFormat = replace(@sFormat,'DD',right(convert(char(6),@dDate,12),2))
set @sFormat = replace(@sFormat,'D1',convert(varchar(2),convert(int,right(convert(char(6),@dDate,12),2))))

-- Insert the Year
-- ~~~~~~~~~~~~~~~
set @sFormat = replace(@sFormat,'YYYY',convert(char(4),@dDate,112))
set @sFormat = replace(@sFormat,'YY',convert(char(2),@dDate,12))

-- Return the function's value
-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~
return @sFormat
end


some useful udf for sqlserver2000
Introduction
SQL Server 2000 supports User-Defined Functions (UDFs) - one or more
Transact-SQL statements that can be used to encapsulate code for reuse.
User-defined functions cannot make a permanent changes to the data or
modify database tables. UDF can change only local objects for this UDF,
such as local cursors or variables.

There are three types of UDF in SQL Server 2000:

Scalar functions
Inline table-valued functions
Multistatement table-valued functions

Scalar functions return a single data value (not a table)
with RETURNS clause. Scalar functions can use all scalar
data types, with exception of timestamp and user-defined data types.

Inline table-valued functions return the result set of a single
SELECT statement.

Multistatement table-valued functions return a table, which was
built with many TRANSACT-SQL statements.

User-defined functions can be invoked from a query like built-in
functions such as OBJECT_ID, LEN, DATEDIFF, or can be executed
through an EXECUTE statement like stored procedures.

UDF examples

Database creation date
This UDF will return the creation date for a given database (you should
specify database name as parameter for this UDF):

CREATE FUNCTION dbo.DBCreationDate
( @dbname sysname )
RETURNS datetime
AS
BEGIN
DECLARE @crdate datetime
SELECT @crdate = crdate FROM master.dbo.sysdatabases
WHERE name = @dbname
RETURN ( @crdate )
END
GO

This is the example for use:

SELECT dbo.DBCreationDate('pubs')
GO

Date the object was created
This UDF will return the creation date for a given object in the
current database:

CREATE FUNCTION dbo.ObjCreationDate
( @objname sysname)
RETURNS datetime
AS
BEGIN
DECLARE @crdate datetime
SELECT @crdate = crdate FROM sysobjects WHERE name = @objname
RETURN ( @crdate )
END
GO

This is the example for use:

SELECT dbo.ObjCreationDate('authors')
GO

Get date part of datetime value
This UDF will return the date part of datetime value:

CREATE FUNCTION dbo.DatePart
( @fDate datetime )
RETURNS varchar(10)
AS
BEGIN
RETURN ( CONVERT(varchar(10),@fDate,101) )
END
GO

This is the example for use:

SELECT dbo.DatePart('11/11/2000 11:15AM')
GO

Get time part of datetime value
This UDF will return the time part of datetime value:

CREATE FUNCTION dbo.TimePart
( @fDate datetime )
RETURNS varchar(10)
AS
BEGIN
RETURN ( CONVERT(varchar(7),right(@fDate,7),101) )
END
GO

This is the example for use:

SELECT dbo.TimePart('11/11/2000 11:15AM')
GO

Get the number of working days between two dates
This UDF will return the number of working days between two dates
(not including these dates):

CREATE FUNCTION dbo.GetWorkingDays
( @StartDate datetime,
@EndDate datetime )
RETURNS INT
AS
BEGIN
DECLARE @WorkDays int, @FirstPart int
DECLARE @FirstNum int, @TotalDays int
DECLARE @LastNum int, @LastPart int
IF (DATEDIFF(day, @StartDate, @EndDate) < 2)
BEGIN
RETURN ( 0 )
END
SELECT
@TotalDays = DATEDIFF(day, @StartDate, @EndDate) - 1,
@FirstPart = CASE DATENAME(weekday, @StartDate)
WHEN 'Sunday' THEN 6
WHEN 'Monday' THEN 5
WHEN 'Tuesday' THEN 4
WHEN 'Wednesday' THEN 3
WHEN 'Thursday' THEN 2
WHEN 'Friday' THEN 1
WHEN 'Saturday' THEN 0
END,
@FirstNum = CASE DATENAME(weekday, @StartDate)
WHEN 'Sunday' THEN 5
WHEN 'Monday' THEN 4
WHEN 'Tuesday' THEN 3
WHEN 'Wednesday' THEN 2
WHEN 'Thursday' THEN 1
WHEN 'Friday' THEN 0
WHEN 'Saturday' THEN 0
END
IF (@TotalDays < @FirstPart)
BEGIN
SELECT @WorkDays = @TotalDays
END
ELSE
BEGIN
SELECT @WorkDays = (@TotalDays - @FirstPart) / 7
SELECT @LastPart = (@TotalDays - @FirstPart) % 7
SELECT @LastNum = CASE
WHEN (@LastPart < 7) AND (@LastPart > 0) THEN @LastPart - 1
ELSE 0
END
SELECT @WorkDays = @WorkDays * 5 + @FirstNum + @LastNum
END
RETURN ( @WorkDays )
END
GO

This is the example for use:

SELECT dbo.GetWorkingDays ('11/13/2000', '12/27/2000')
GO


Number of days for a given month
By: mohit108
Use this function to get the number of days in a given month.

Mohit Nayyar
MCP (.Net), MCSD, MCDBA

create function GetMonths (@Month smallint, @Year int)
returns smallint
as
begin
return(DAY(dateadd(dd,-1,DATEADD(m,1,cast(@Year as varchar(4)) + '-' + cast(@Month as varchar(2)) + '-01'))))
end

select DatabaseName.dbo.GetMonths (2,2002)


Dynamic String Compare
Dynamic String Compare (W/O the dynamic sql)
Script Rating Total number of votes [3]
By: tcartwright
Purpose: To compare two strings with a supplied operator and return true if the strings evaluate to true using the operator provided. This is useful for stored procs to use instead of dynamic sql. YAY!?Allowing for the operator and value to be passed in as parms. So that the user on the front end can pick the comparison he wants to use against the field being searched.?I based my idea for this function upon 2 excellent articles from Robert Marda :
http://www.sqlservercentral.com/columnists/rmarda/howdynamiccanstaticbe.asp

http://www.sqlservercentral.com/columnists/rmarda/howdynamicsqlcanbestatic2.asp

EX: SELECT dbo.fnStringCompare('test', default, 'test')
OR
??SELECT * FROM TABLE WHERE dbo.fnStringCompare(TABLE.COLUMN, 'LIKE %VAL%', 'test') = 1
???????????
NOTE : Depending upon the collation of your sql server
text case may be important and affect the results
???????????牋
Returns (smallint) :??
-1 : Null or empty string operator
-2 : Invalid operator type
?1 : The expression was valid
?0 : None of the above was true, and somehow got
牋 past my checks, should never happen

SET QUOTED_IDENTIFIER ON
GO
SET ANSI_NULLS ON
GO

if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[fnStringCompare]') and xtype in (N'FN', N'IF', N'TF'))
drop function [dbo].[fnStringCompare]
GO

CREATE FUNCTION fnStringCompare(@String1 nvarchar(4000),
@Operator varchar(40) = '=',
@String2 nvarchar(4000))
RETURNS smallint
AS
BEGIN
-- '---------------------------------------------
-- ' Function fnStringCompare
-- '
-- ' Created by tcartwright / The System Shop Inc.
-- ' Date: 12-06-2002 Time: 23:14
-- '
-- ' Purpose: To compare two strings with a supplied operator and return true if the strings
-- ' evaluate to true using the operator provided. This is useful for stored procs to
-- ' use for where clauses instead of dynamic sql. YEE-HA! Allowing for the operator and value to be
-- ' passed in as parms. I based my idea for this function upon 2 excellent
-- ' articles from Robert Marda :
-- ' 1) [image] http://www.sqlservercentral.com/columnists/rmarda/howdynamiccanstaticbe.asp
-- ' 2) [image] http://www.sqlservercentral.com/columnists/rmarda/howdynamicsqlcanbestatic2.asp
-- '
-- ' EX: SELECT dbo.fnStringCompare('test', default, 'test')
-- '
-- ' OR
-- '
-- ' SELECT * FROM TABLE WHERE dbo.fnStringCompare(TABLE.COLUMN, 'LIKE %VAL%', 'test') = 1
-- '
-- ' OR
-- '
-- ' CREATE PROCEDURE spWhereTest(@au_fname varchar(40) = NULL, @au_fname_op varchar(30))
-- ' AS
-- ' SELECT * FROM pubs..authors WHERE dbo.fnStringCompare(au_fname, @au_fname_op, @au_fname) = 1
-- ' GO
-- '
-- ' -- =============================================
-- ' -- example(s) to execute the store procedure
-- ' -- =============================================
-- ' EXECUTE spWhereTest 'e', 'LIKE %VAL%' --contains
-- ' GO
-- '
-- ' EXECUTE spWhereTest 'e', 'LIKE %VAL' --ends with
-- ' GO
-- '
-- ' EXECUTE spWhereTest default, '=' --null string, and not checing for nullable so all records
-- ' GO
-- '
-- ' EXECUTE spWhereTest default, 'IS NULL' --IS NULL
-- ' GO
-- '
-- ' DROP PROCEDURE spWhereTest
-- ' GO
-- '
-- ' NOTE : Depending upon the collation of your sql server
-- ' text case may be important and affect the results
-- '
-- ' Returns (smallint) :
-- ' -1 : Null or empty string operator
-- ' -2 : Invalid operator type
-- ' 1 : The expression was Valid
-- ' 0 : None of the above was true, and somehow got
-- ' past my checks, should never happen
-- '---------------------------------------------

--check if they sent in an operator
IF @Operator IS NULL OR LEN(RTRIM(LTRIM(@Operator))) = 0
RETURN -1

--prep the operator
SET @Operator = UPPER(LTRIM(RTRIM(@Operator)))
--remove any arbitrary single quotes
SET @Operator = REPLACE(@Operator, CHAR(39), '')

--check for a Valid operator
IF PATINDEX('%;' + @Operator + ';%', ';=;!=;<;<=;>;>=;LIKE %VAL%;NOT LIKE %VAL%;LIKE VAL%;NOT LIKE VAL%;LIKE %VAL;NOT LIKE %VAL;IS NULL;IS NOT NULL;') <= 0
RETURN -2

--sent in a null @string2 but not checking for nullable
--so it invalidates the rest of the checks
--allows for the possibility to NOT check
IF @String2 IS NULL AND (@Operator <> 'IS NULL' AND @Operator <> 'IS NOT NULL')
RETURN 1

IF @Operator = '=' AND @String1 = @String2
RETURN 1

IF (@Operator = '!=' OR @Operator = '<>') AND @String1 <> @String2
RETURN 1

IF @Operator = '<' AND @String1 < @String2
RETURN 1

IF @Operator = '<=' AND @String1 <= @String2
RETURN 1

IF @Operator = '>' AND @String1 > @String2
RETURN 1

IF @Operator = '>=' AND @String1 >= @String2
RETURN 1

--Contains
IF @Operator = 'LIKE %VAL%' AND @String1 LIKE '%' + @String2 + '%'
RETURN 1

--Does not contain
IF @Operator = 'NOT LIKE %VAL%' AND @String1 NOT LIKE '%' + @String2 + '%'
RETURN 1

--begins with
IF @Operator = 'LIKE VAL%' AND @String1 LIKE @String2 + '%'
RETURN 1

--does not begin with
IF @Operator = 'NOT LIKE VAL%' AND @String1 NOT LIKE @String2 + '%'
RETURN 1

--starts with
IF @Operator = 'LIKE %VAL' AND @String1 LIKE '%' + @String2
RETURN 1

--does not start with
IF @Operator = 'NOT LIKE %VAL' AND @String1 NOT LIKE '%' + @String2
RETURN 1

--is null :)
IF @Operator = 'IS NULL' AND @String1 IS NULL
RETURN 1

--is not null
IF @Operator = 'IS NOT NULL' AND @String1 IS NOT NULL
RETURN 1

--IF we got here something is not right :( or we have a PEBCAK issue
--of course it can't be my code..... :D LOL {{Tim C 12-06-2002 23:13:25}}
--OR it COULD be that the check was invalid.........
RETURN 0
END

GO

SET QUOTED_IDENTIFIER OFF
GO
SET ANSI_NULLS ON
GO

Either cut-and-paste the script from the above box or click the below Full Screen text to go to full screen mode.


SPLIT Function
SPLIT Function
Script Rating Total number of votes [8]
By: pnbushan
This function works just inverse of COALESCE Function.

CREATE FUNCTION SPLIT (
@str_in VARCHAR(8000),
@separator VARCHAR(4) )
RETURNS @strtable TABLE (strval VARCHAR(8000))
AS
BEGIN

DECLARE
@Occurrences INT,
@Counter INT,
@tmpStr VARCHAR(8000)

SET @Counter = 0
IF SUBSTRING(@str_in,LEN(@str_in),1) <> @separator
SET @str_in = @str_in + @separator

SET @Occurrences = (DATALENGTH(REPLACE(@str_in,@separator,@separator+'#')) - DATALENGTH(@str_in))/ DATALENGTH(@separator)
SET @tmpStr = @str_in

WHILE @Counter <= @Occurrences
BEGIN
SET @Counter = @Counter + 1
INSERT INTO @strtable
valueS ( SUBSTRING(@tmpStr,1,CHARINDEX(@separator,@tmpStr)-1))

SET @tmpStr = SUBSTRING(@tmpStr,CHARINDEX(@separator,@tmpStr)+1,8000)

IF DATALENGTH(@tmpStr) = 0
BREAK

END
RETURN
END


 今天是星期几
Day of Week
Script Rating Total number of votes [5]
By: michael.rosqvist
To find the day of week, regardless of
-SET DATEFIRST
-SET DATEFORMAT
-SET LANGUAGE

The first one is based on Zellers Congruence.
The second one is a SQL server proprietary.

DECLARE @sDate char(8)
SET @sDate = ''''19650124'''' --Winston Churchills last day
SELECT CASE CAST(DATEDIFF(DAY,''''17530101'''',@sDate) AS int)
- ( CAST(DATEDIFF(DAY,''''17530101'''',@sDate) AS int) / 7) * 7
WHEN 0 THEN ''''Monday''''
WHEN 1 THEN ''''Tuesday''''
WHEN 2 THEN ''''Wednesday''''
WHEN 3 THEN ''''Thursday''''
WHEN 4 THEN ''''Friday''''
WHEN 5 THEN ''''Saturday''''
WHEN 6 THEN ''''Sunday''''
ELSE NULL
END

SELECT CASE(datepart(dw, ''''19650124'''')+ @@datefirst)%7
WHEN 2 THEN ''''Monday''''
WHEN 3 THEN ''''Tuesday''''
WHEN 4 THEN ''''Wednesday''''
WHEN 5 THEN ''''Thursday''''
WHEN 6 THEN ''''Friday''''
WHEN 0 THEN ''''Saturday''''
WHEN 1 THEN ''''Sunday''''
ELSE NULL
END

//今天是星期几,第一个不知什么意思
SELECT CASE(datepart(dw, getdate())+ @@datefirst)%7
WHEN 2 THEN ''Monday''
WHEN 3 THEN ''Tuesday''
WHEN 4 THEN ''Wednesday''
WHEN 5 THEN ''Thursday''
WHEN 6 THEN ''Friday''
WHEN 0 THEN ''Saturday''
WHEN 1 THEN ''Sunday''
ELSE NULL
END


个月多少天
By: mohit108
Use this function to get the number of days in a given month.

Mohit Nayyar
MCP (.Net), MCSD, MCDBA

create function GetMonths (@Month smallint, @Year int)
returns smallint
as
begin
return(DAY(dateadd(dd,-1,DATEADD(m,1,cast(@Year as varchar(4)) + ''-'' + cast(@Month as varchar(2)) + ''-01''))))
end

select DatabaseName.dbo.GetMonths (2,2002)


Handling The Text Data Type
Introduction
In this article I plan to show you various ways you can manipulate the data stored in a text data type. These techniques will be particularly useful when the number of characters in the text column exceeds 8,000. They may not work when the text in row option has been set for a table. Everything I explain here will focus on manipulating columns of data type text when the text in row option is not enabled for a table. I will focus on the text data type because I am most familiar with it, although it is possible that much of what I show here will work for ntext and maybe even image data types.

This article will give you an understanding of the text data type, show you how to get the pointer for a column with data type text, how to update the value of that column using the pointer, and a few other functions that work with the text data type.
Text Data Type
Most of the functions and techniques you can use for other character data types will not work on a text data type. The data in columns of data type text are not normally stored on the same data page with the data in other columns of a table. What is stored with the other columns of a table is the pointer that tells SQL Server where it can find the characters you store in the text column. I believe this is what makes SQL Server treat the text data type differently.

You can抰 concatenate characters to a text data type the way you would for a varchar column. You must first cast the text column to a varchar and then concatenate. That works fine until you have more than 8,000 characters in your text column.

You can抰 use the function抯 LEFT, RIGHT, or LEN on a column of data type text without casting the column as varchar(8000). Again, you will be fine unless you have more than 8,000 characters in the column. To get the length you can use the function DATALENGTH instead of LEN.
Getting A Text Pointer
The following code will get and display a pointer value:
CREATE TABLE #TestTable
(
RowID int,
ColText text
)

INSERT INTO #TestTable (RowID,ColText)
SELECT 1,''

DECLARE @ptrColText varbinary(16)

SELECT @ptrColText = TEXTPTR(ColText)

FROM #TestTable

SELECT @ptrColText
The pointer must be binary(16) or varbinary(16). There must be a value for the column with data type text before a pointer will be established for it, hence the INSERT statement in the above code. The function TEXTPTR gets the pointer value for each row in a result set.

Example 1: Concatenation

The UPDATETEXT command can be used to correct spelling errors and to make any other modifications and/or additions to the characters in a column of data type text. It also works well when you need to concatenate a list that has more than 8,000 characters in it.

Here is an example that can be executed after executing the code from the previous section. This code will concatenate numbers to create a coma delimited list that is more than 8,000 characters long:

DECLARE @value varchar(10),
@Counter int
SET @Counter = 1

WHILE @Counter <= 2000
BEGIN
SET @value = LTRIM(STR(@Counter))
IF @Counter < 2000
SET @value = @value + ','
UPDATETEXT #TestTable.ColText @ptrColText NULL 0 @value

SET @Counter = @Counter + 1
END

SELECT DATALENGTH(ColText), * FROM #TestTable
Let us look at the UPDATETEXT command a moment. After the keyword UPDATETEXT you see the name of the table followed by a period and the name of the column. Next is the variable that holds the pointer. Since each text column has its own pointer this will focus on one row (and only one column) of a table no matter how many rows (and columns) are in a table.

After this you see the word NULL. This is in the position of the insert offset. When it is NULL it tells SQL Server to append the new characters to the end of all the characters currently in the text column. If the value is 0 then SQL Server will insert the new characters before all the characters currently in the text column. If you specify a number then it will count from the first character to the number specified and insert the new characters after the one found at that point.

SQL Server will not delete any characters from those in the text column unless you specify a number other than 0. In the example above I used a 0 after the NULL as the delete length to indicate I didn抰 want to delete any characters from those already stored. If I had used a value other than 0 I would have gotten the following error:

Server: Msg 7135, Level 16, State 2, Line 29
Deletion length 1 is not in the range of available text, ntext, or image data.
The statement has been terminated.

Last I used a variable to supply the characters I wanted to insert into the text column.

Also you will notice that I used the function DATALENGTH in the last query to get the number of characters now stored in the column ColText. The number of characters in the column are 8,892. This number is 700 more than can be viewed in Query Analyzer since it only allows you to view the first 8,192 characters of each column. In Example 3, I will show you a way you can view those extra 700 characters.

Example 2: Misspelled Words

The delete length parameter is useful for correcting spelling errors in a text. Here is an example that will replace a misspelled word with the word spelled correctly:
CREATE TABLE #TestTable
(
RowID int,
ColText text
)

INSERT INTO #TestTable (RowID,ColText)
SELECT 1,'Here I have one mispellet word.'
UNION ALL
SELECT 2,'Here I have one mispellet word in the first sentance. Another one is mispellet in the second sentance.'

DECLARE @ptrColText varbinary(16),
@RowID int,
@MisspelledWord varchar(40),
@CorrectedWord varchar(40),
@LenMisspelledWord int,
@StartingPosition int

SET @RowID = 1
SET @MisspelledWord = 'mispellet'
SET @CorrectedWord = 'misspelled'
SET @LenMisspelledWord = LEN(@MisspelledWord)

PRINT 'Rows in table before correcting spelling errors.'

SELECT * FROM #TestTable WHILE @RowID <= 2
BEGIN
SET @StartingPosition = 1
--get pointer
SELECT @ptrColText = TEXTPTR(ColText) FROM #TestTable WHERE RowID = @RowID

WHILE @StartingPosition > 0
BEGIN
--get starting position of first occurence of characters in @MisspelledWord
SELECT @StartingPosition = CHARINDEX(@MisspelledWord, ColText) - 1
FROM #TestTable
WHERE RowID = @RowID

IF @StartingPosition > 0
UPDATETEXT #TestTable.ColText @ptrColText @StartingPosition @LenMisspelledWord @CorrectedWord
END

SET @RowID = @RowID + 1
END

PRINT 'Rows in table after correcting spelling errors.'

SELECT * FROM #TestTable

DROP TABLE #TestTable
Upon execution you will notice that the three occurrences of the word mispellet were replaced with misspelled. CHARINDEX will return a 0 when the character combination in the variable @MisspelledWord is not found in the column called ColText. Thus all occurrences will be replaced in both rows of the temp table.

Notice that in this example I used variables for the insert offset and the delete length. In the previous example these two parameters were hard coded to one value.
Example 3: SUBSTRING

With a column of data type text it can sometimes be difficult to view the characters beyond the first 8,000 or 8,192. A varchar variable can only hold 8,000 characters. Casting the text column to varchar also will only get you the first 8,000 characters. Viewing the column in Query Analyzer will only let you see the first 8,192 characters. Enterprise Manager will display <Long Text> if there are more than 900 characters in a text field. A way to view what is beyond the first 8,192 characters can be useful. One way is to use the function SUBSTRING. Here is how this can be used on the data in the temp table from Example 1:

SELECT SUBSTRING(ColText, 8193, 700)
FROM #TestTable
The above query displays the 700 characters that you couldn抰 see in Query Analyzer without using the SUBSTRING function. Using SUBSTRING you can view any section of characters stored in a column with data type text by giving the starting position number and how many characters from that point you want to see. You can use the function CHARINDEX or PATINDEX to find the desired starting point.
Conclusions
In this article you have learned how to get the pointer of a text column and how to use that pointer with the keyword UPDATETEXT to concatenate characters and replace characters in a column of data type text. You抳e also read how you can view characters stored in a column of data type text when they exceed the max normal capabilities of the tools that come with SQL Server. I have not covered every possibility. My intent in this article is to give you a starting point to develop solutions that will be helpful to you.


Divide and Conquer
Introduction
There are many queries and processes that a DBA may perform on an SQL Server that run quickly and efficiently with small amounts of data, but do not scale up when run on large sets of data. The power behind a RDBMS and SQL Server come from the extremely efficient and rapid processing of sets of data. Most other programming languages operate in a row-by-row or procedural fashion working with one piece or line of data at a time. The ability to write SQL code that operates on a batch of data allows the alteration of a large amount of data with a small amount of code. It also allows the very quick and headache creating destruction of data with even smaller amounts of code, but that is another subject and another article.

I always preach about the writing of tight code that operates on sets of data rather than a row by row approach. I avoid cursors and temp tables when possible to ensure that ever operation is a batch-oriented statement rather than an individual row operation. When I see another DBA send me a procedure with a set rowcount xx in it, I am often returning this for a rewrite to work on a batch of data. As I write this, however, I am getting ready to eat my words.

There are times where the use of SET ROWCOUNT can be a great time saver and maximize the use of resources. As with most of my rules, there are always exceptions and the use of the best tool for the job is something that should always be kept in mind. Batches are something that all SQL programmers use to operate on a set of data, and there are times when it may sometimes be necessary to sub-divide your batches into sub-batches to more efficiently complete the process.

The Problem
An example I will give may best illustrate the reason why batching a process is sometimes better involves the following objects:

--Transaction table
Create table MyTrans
(
AccountID int,
TransID int identity (1, 1),
TranDt datetime,
Amt float,
Balance float
)

With these items defined as follows:

MyTrans table
AccountID Foreign key to the account table
TransID Primary key for this table
TranDt Date of transaction
Amt Amount of transaction
Balance Balance to date for this account
MyTrans had an Insert, Update trigger that updates the balance field for the inserted ledger with the sum all previous amounts for the same account ID. This trigger contains the following code:
Update transaction
Set balance = sum( t.amount) from inserted I, transaction t
Where i.accountID = t.accountID
And i.accountID = transaction.accountid
And t.date <= i.date)

This table contains about 5 million rows of transactions spanning 15,000 or so accounts over about a decade. Recently I needed to correct a large number of rows historically in this transaction table. The corrections involved changing amounts of previous transactions that had been rounded incorrectly. I had received a file of data that contained the ids, dates, and corrected amounts. This data was loaded into a temporary table called TempSteve. It would have taken only one line of SQL code to perform this update:
Update transaction
Set amount = t.amount
From tempsteve t
Where t.id = transaction.id

This code would update each existing transaction with the new amount and fire the trigger to reload the balance with the sum of the previous transaction. In theory this looks like a relatively simple process that I could run from my SQL window and leave early for a light run before dinner.

It Didn't Work
In actuality there is a problem with this code. Not a bug, since it will work and perform the updates, the problem lies in the actual execution of this code on the server. Performing a single update, for any transaction, whether the most recent or one five years ago takes a split second on my SQL Server. Running the ten thousand corrections on my decent sized SQL Server (a 4-way PP200, 2048MB RAM) was still running nearly twelve hours later. Why the delay?

Normally I would not have allowed this query to run so long, but it was started on a Friday evening and let run until I found it Saturday morning. That first cup of coffee was not very enjoyable when I found this still running on my server. Let's examine what has to occur for this code to complete:

Transaction 1 is altered with a new inserted table that the trigger evaluates for the sum of prior transactions (0), and updates the ledger. Before this occurs, however, all the remaining transactions in the statement must be completed. The next transaction being evaluated is for the same account and its amount is updated (in memory again) and its trigger fires, but the balance update query requires the amount update from the previous update be evaluated in order to get the accurate sum. Therefore the query processor must search its current altered table in memory to get the sums along with the rows of the physical disk. Carry this forward a few hundred transaction and you find a recursive effect where each new update for an account requires results from a pending update of the same account in this same batch.

I suspect my SQL Server had problems with the recursive requirements of this query because of the trigger. With my morning coffee I reluctantly canceled the query and set about examining the process and trying to devise a better solution before my kids woke up and came down to help.

The Solution - Batching!
Initial testing of single row updates, showed each one completing in less than a second. After a few of these queries completing and a second cup of coffee, I realized the recursive problems in this update. At that point I added a tinyint column to the TempSteve table for remaining rows, set the value to zero and ran the following code:

Declare @t int, @i int
Set @i = 5000
Set rowcount 1

While @I > 0
Begin
Select @t = transid from TempSteve

Update transaction
Set amount = t.amount
From tempsteve t
Where t.id = transaction.id
And t.transactionid= @t

Update tempsteve set status = 1
Where transid = @t

Select @I = @I - 1
End

This ran for the remaining 9000+ rows in a couple minutes. This was timed perfectly as I heard the kids starting to move around upstairs. By batching this very simple SQL statement and removing the requirement the trigger implement recursion the server could perform the update with a minimal use of resources.

Conclusions
So where else does batching come in handy? Preventing locks and blocks is another area I use this. When I have a process that requires a significant amount of time, like inserting new rows into a table from a large batch or deleting old archived information, I use this. To prevent issues with other users, I usually use some type of flag to indicate processed or unprocessed, then set a batch size and process x number of rows. I often also include a short waitfor in my loop to slow this down for a second or two and let other users get in a query on the table.

I hope nobody was laughing about my "decent" sized SQL Server. This process actually occurred a couple years ago. Now even my desktop is bigger than that server. The next part of batching will look at an alternative method of batching data that does not use a cursor.

As always, please rate this article and feel free to send some feedback.

Steve Jones
December 2000


Removing Duplicate Rows
Introduction

"How do I remove duplicate rows from a table" is a common question in the newsgroups, and here we will look at a few different techniques for achieving this.

Databases can easily be designed to disallow duplicates (which I will discuss at the bottom of the article) but the issue can still arise. For example, duplicate data might arrive at your database via an interface to another system.

The code examples in this article were all developed on SQL 2000, but should work fine in versions 6.5 and 7.

The "Distinct" technique

This is the easiest way of de-duplicating a table, and is at it抯 best with fairly small rowsets, and where all the columns in a table are duplicates. We simply run a SELECT DISTINCT on the offending table, storing the results in a temporary table, then delete the contents of the permanent table and re-populate it from the temporary one.

The downside of this is that you need enough free data space in your database (or in tempdb) to store the entire table again, plus plenty of log space when you are de-duplicating large tables. This simple example loads a table with duplicate data then removes the duplicates:

create table marxBrothers (
Number int,
Name varchar(32)
)
go
insert marxBrothers
select 1, 'Groucho Marx' UNION ALL
select 2, 'Harpo Marx' UNION ALL
select 3, 'Chico Marx' UNION ALL
select 1, 'Groucho Marx' UNION ALL
select 2, 'Harpo Marx' UNION ALL
select 3, 'Chico Marx' UNION ALL
select 6, 'Zeppo Marx' UNION ALL
select 7, 'Gummo Marx' UNION ALL
select 6, 'Zeppo Marx'

begin tran deduplicate
select DISTINCT *
into #temp
from marxBrothers

truncate table marxBrothers

insert marxBrothers
select *
from #temp

select * from marxBrothers

drop table #temp
commit tran deduplicate

drop table marxBrothers

Using derived tables

For tables where duplication is defined by a subset of the columns in the table, you can use one of the other columns in the table to identify which rows to keep and which to delete. Here is a simple example ?it is similar to the previous one, but note that only the names are duplicated, because the "ident" column has been defined as an Identity column. Here we only want to keep one instance of each name, regardless of the value in the Ident column. In the example I have arbitrarily decided to keep the name with the lowest Ident value

create table marxBrothers (
ident int IDENTITY,
Name varchar(32)
)
go
insert marxBrothers (Name)
select 'Groucho Marx' UNION ALL
select 'Harpo Marx' UNION ALL
select 'Chico Marx' UNION ALL
select 'Groucho Marx' UNION ALL
select 'Harpo Marx' UNION ALL
select 'Chico Marx' UNION ALL
select 'Zeppo Marx' UNION ALL
select 'Gummo Marx' UNION ALL
select 'Zeppo Marx'

select * from marxBrothers

delete marxBrothers
from marxBrothers,
(
select min(ident) as minIdent, name
from marxBrothers m
group by name
having count(1) > 1
) as derived
where marxBrothers.name = derived.name
and ident > minIdent

select * from marxBrothers

drop table marxBrothers

The inner query selects all the names in the table that are duplicated, and the minimum value of the Ident column for each name. These results are compared against the main table in order to select the records for deletion

With this technique you may be able to get away with having much less free room than the earlier technique, but this depends on the ratio of duplicated to unique records ?the more records are duplicated, the more space SQL Server needs to temporarily store, and work with, the results that makeup the derived table. The derived table technique is the one I usually try first.

Using correlated subqueries

This example uses a correlated sub-query to achieve de-duplication

create table marxBrothers (
ident int IDENTITY,
Name varchar(32)
)
go
insert marxBrothers (Name)
select 'Groucho Marx' UNION ALL
select 'Harpo Marx' UNION ALL
select 'Chico Marx' UNION ALL
select 'Groucho Marx' UNION ALL
select 'Harpo Marx' UNION ALL
select 'Chico Marx' UNION ALL
select 'Groucho Marx' UNION ALL
select 'Harpo Marx' UNION ALL
select 'Chico Marx' UNION ALL
select 'Groucho Marx' UNION ALL
select 'Harpo Marx' UNION ALL
select 'Chico Marx' UNION ALL
select 'Zeppo Marx' UNION ALL
select 'Gummo Marx' UNION ALL
select 'Zeppo Marx'

select * from marxBrothers

delete marxBrothers
where ident > (
select min(ident)
from marxBrothers m
where m.name = marxBrothers.name
)

select * from marxBrothers

drop table marxBrothers

Correlated sub-queries can be pretty slow and inefficient to run against large tables.

De-duplicating using dynamic SQL

This technique uses a cursor and the "SET ROWCOUNT" feature to work through the table one name at a time, deleting all but one of the rows for each name. Cursors and Dynamic SQL both have their place, but I generally try to avoid using them for reasons of efficiency, As this example uses both a cursor and dynamic SQL, it can bet pretty inefficient with large numbers of duplicated rows, and I would prefer to use one of the previous techniques. However, the technique does work, so I think it is still worth a mention.

create table marxBrothers (
ident int IDENTITY,
Name varchar(32)
)
insert marxBrothers (Name)
select 'Groucho Marx' UNION ALL
select 'Groucho Marx' UNION ALL
select 'Groucho Marx' UNION ALL
select 'Groucho Marx' UNION ALL
select 'Harpo Marx' UNION ALL
select 'Chico Marx' UNION ALL
select 'Chico Marx' UNION ALL
select 'Chico Marx' UNION ALL
select 'Groucho Marx' UNION ALL
select 'Harpo Marx' UNION ALL
select 'Chico Marx' UNION ALL
select 'Zeppo Marx' UNION ALL
select 'Gummo Marx' UNION ALL
select 'Zeppo Marx' UNION ALL
select 'Zeppo Marx'
go

declare @sql varchar(255),
@name varchar(32),
@count int

select * from marxBrothers

declare CI insensitive cursor for
select name, count(1) ?1 from marxBrothers m
group by name
having count(1) > 1

open CI
fetch next from CI into @name, @count

while @@fetch_status = 0
begin
select @sql =
'set rowcount ' + convert(varchar(9), @count) + '
delete marxBrothers where name = "' + @name + '"'

exec (@sql)

fetch next from CI into @name, @count

end-- while fetch

close CI
deallocate CI

select * from marxBrothers

go
drop table marxBrothers

Preventing duplication

Prevention is, after all, better than cure, so It would be pointless discussing de-duplicating a table without mentioning how to prevent duplicate rows appearing again afterwards. There are three main declarative methods for doing this in SQL:

The Primary Key constraint

This is implemented as an index in SQL Server, and so it can also help speed up select and join operations from the table. One or more columns may make up a primary key, and SQL Server will automatically ensure that duplicates do not exist in a table抯 primary key. An error will be returned if you try to insert duplicates.

create table marxBrothers (
ident int IDENTITY,
name varchar(32),
constraint PK_marxBrothers PRIMARY KEY (name)
)
go

The Unique constraint

This is also Implemented as an index in SQL Server. The main difference between a primary key and a unique constraint is that the Primary Key can never contain a NULL value.

create table marxBrothers (
ident int IDENTITY,
name varchar(32),
constraint U_marxBrothers UNIQUE (name)
)
go

The IGNORE_DUP_KEY option

This is an option on the create index command, which will cause SQL Server to discard rows where a duplicate of the columns in the specified index already exists. Use this option with care ?you have to be certain that you do really need to do this. Also remember that the results of using this option can easily be misinterpreted as an error by end users.

create table marxBrothers (
ident int IDENTITY,
name varchar(32),
)
go
create unique index UI_marxBrothers on marxBrothers (name) with ignore_dup_key

About the author
Neil Boyle is an independant SQL Server consultant working out of London, England. Neil's free SQL Server guide is available on-line at [image] http://www.impetus-sql.co.uk


When Is Your Anniversary?
Introduction
Do you remember when your wedding anniversary is? The birthdays of your in-laws? Other important dates? A reader posted a question about how you can query for important dates that occur within a time span. At first, this would seem to be a simple task, but as I conducted a few experiments, it turned out to not be as straightforward as I thought.

The Problem
All important dates have some beginning date, a "birth date" of sorts. Of course, birthdays have a birth date as well. This is the first occurrence of the event. It also occurs every year on that some month and day as the first occurrence. So how do you query for all the events that are coming up in the next, say, one month? It's not as straightforward as one would expect.

Suppose I have a table that looks like the following:

Person Birthday
----------- -------------
Steve 09/15/1967
Tia 02/01/1969
Kendall 05/15/2001
Delaney 11/18/1998
Kyle 06/01/1992

and I want to find out whose birthday will occur in this month or next month and send a reminder. I can easily find today's date and the end date for next month, but how do I find which dates occur within this timeframe? If I query for anything between the two dates, nothing will be returned because none of the events occur within this timeframe. Suppose today is May 3, then I am looking for events between May 3 and June 30. I can run the following:
create table myTest
( person varchar( 80)
, birthday datetime
)
go
insert MyTest select 'Steve', '09/15/1967'
insert MyTest select 'Tia', '02/01/1969'
insert MyTest select 'Kendall', '05/15/2001'
insert MyTest select 'Delaney', '11/18/1998'
insert MyTest select 'Kyle', '06/01/1992'

go
declare @today datetime
, @lastday datetime

select @today = '05/03/2001'
select @lastday =

'06/30/2001' select *

from MyTest where
birthday > =

@today and
birthday < = @lastday

This returns the following:

Person Birthday
----------- -------------
Kendall 05/15/2001

which is not the correct result set. What we really should be returning is both Kendall and Kyle since they both have birthdays in this range. OK, so we need to change the query to not use the date compares and instead examine the month and date. Let's try:

declare @today datetime
, @lastday datetime

select @today = '05/03/2001'
select @lastday = '06/30/2001'

select *
from MyTest
where month( birthday) >=

month(@today) and month(
birthday) < =

month( @lastday) and
day( birthday) > =
day(@today) and
day( birthday) < = day( @lastday)

This returns the following:

Person Birthday
----------- -------------
Kendall 05/15/2001

We are still not getting what we want. Why not? Because we are comparing months and dates together, rather than month and then date. Now I can implement some subqueries to find items within the month range and then find those within the date range, but I have a better solution (I think).

The Solution
The first thing that I want to do is make my script a bit more general so I can test a range of dates. My first change it to calculate the end date of the next month. The first few line of the script change to:

declare @today datetime
, @lastday datetime
, @months int

select @months = 2

select @today = '05/03/2001'
select @lastday = dateadd( day, -1 * (day(@today)), dateadd( month, @months, @today))

This uses a great technique submitted by jasoningram in our forum.

Now, instead of looking at days and months, I decided that the thing I really wanted to do was use the date functions to compare the date on which an event occurs with the date range. To do that I needed to "move" the dates from the table to the current year. I did not, however, want to change the table data. Instead, I took a cue from Mr. Ingram and wrote this query:

declare @today datetime
, @lastday datetime
, @months int

select @months = 2
select @today = '05/03/2001'
select @lastday = dateadd( day, -1 * (day(@today)),
dateadd( month, @months,
@today)) select *
from MyTest where

dateadd( year, datediff(
year, birthday, @today),
birthday) > =

@today and
dateadd( year, datediff( year, birthday, @today), birthday) < = @lastday

This now returns both expected dates.
Person Birthday
----------- -------------
Kendall 05/15/2001
Kyle 06/01/1992

By using the built in date functions to calculate the years between the current date and each date allows me to "move" each date to the present year using the dateadd function. However, we are not done yet. The advanced SQL guys out there probably caught the one bug right away. What about year ends?

Suppose today is 11/01/2001 and I am looking for all events in the next 4 months. This would mean that I want to return both Delaney (11/18) and Tia (2/1). If I change my date and timeframe, I get:

declare @today datetime
, @lastday datetime
, @months int

select @months = 4

select @today = '11/01/2001'
select @lastday = dateadd( day, -1 * (day(@today)),
dateadd( month, @months,
@today)) select *
from MyTest where

dateadd( year, datediff(

year, birthday, @today),
birthday) > =

@today and
dateadd( year, datediff( year, birthday, @today), birthday) < = @lastday

This only returns one date, however.
Person Birthday
----------- -------------
Delaney 11/18/1998

Why?

Let's examine what happens when we "move" all the dates. If we print out the dates after the calculation we can see ths following:

declare @today datetime
, @lastday datetime
, @months int

select @months = 4

select @today = '11/01/2001'
select @lastday = dateadd( day, -1 * (day(@today)), dateadd( month, @months, @today))

select birthday
, dateadd( year, datediff( year, birthday, @today), birthday)
from MyTest

this returns:

1967-09-15 00:00:00.000 2001-09-15 00:00:00.000
1969-02-01 00:00:00.000 2001-02-01 00:00:00.000
2001-05-15 00:00:00.000 2001-05-15 00:00:00.000
1998-11-18 00:00:00.000 2001-11-18 00:00:00.000
1992-06-01 00:00:00.000 2001-06-01 00:00:00.000

I left out the names, but we can see that all the dates were moved to the year 2001. However, the @lastday value is "02/28/2002". This is one year into the future. So how can we fix this?

Well, I haven't found a way to do this in a single query, but I did find a way to do this in two queries, using the UNION operator to combine them. What I reasoned was that for any date whose month is the same or greater than the current month (from @today) needs to be moved to the current year. Any date from a month prior to the current month, however, needs to be moved to the next year. This allows the standard date functions to perform the comparisons correctly. My new query is:

declare @today datetime
, @future datetime
, @months int

select @months = 4

select @today = '11/1/01'
select @future = dateadd( day, -1 * (day(@today)), dateadd( month, @months, @today))

select *
from MyTest
where dateadd( year, datediff( year, birthday, @today), birthday) >=
@today and
dateadd( year, datediff(
year, birthday, @today),

birthday) < =

@future and month(
birthday) > =

month( @today) union select * from
MyTest where dateadd( year, datediff( year, birthday, @today) +
1, birthday) > =

@today and
dateadd( year, datediff( year, birthday, @today) + 1, birthday) < =
@future and
month( birthday) < month( @today )

this returns:

Person Birthday
----------- -------------
Delaney 11/18/1998
Tia 02/01/1969

Conclusions
While not too terribly complex, this was more difficult than I had thought at first glance. It was also an interesting problem that I had never encountered, but one that would be useful in any environment that is handling repeating events. Hopefully you learned something or found a solution to a problem that you have had.

I am sure some of you will have some great ideas for enhancing this solution. As always I welcome feedback on this article using the "Your Opinion" button below. Please also rate this article.

Steve Jones
ヾkRanch.net October 2001


How Dynamic SQL Can Be Static SQL Part 2
Introduction

This is a continuation of my previous article How Dynamic SQL Can Be Static SQL. You do not need to read my previous article to understand this one. In this article I will explain a few more techniques that will allow you to modify the results of a static SQL query using variables. They are extremely useful if you don’t want to create multiple queries that have only a few minor differences.

Throughout this article I will use the Northwind database for all sample code.

Example 1 – Greater Than and Less Than

This example will show how to dynamically determine greater than and less than using only one condition statement in the where clause. To accomplish this I’ll use the CASE function and a variable called @sign. We’ll suppose we want to see all employees that have a birthday greater than or less than a date we specify. The following code will do this:

DECLARE @Sign char(1), @date datetime

SET @Sign = '>'

SET @date = '7/3/1963'

SELECT *

FROM employees

WHERE CASE @Sign WHEN '>' THEN BirthDate

WHEN '<' THEN @date END >

CASE @Sign WHEN '>' THEN @date

WHEN '<' THEN BirthDate END

You’ll notice that when you use the greater than sign you get everyone with a birth date greater than the date assigned to @date and when you change the sign to less than you get everyone with a birth date less than the date in @date.

Example 2 – Where Clause Condition Elimination

This example will show how you can use variables to eliminate or use various conditions in a where clause. In this example we’ll look at having only two conditions in our where clause. Here is the code you can use:

DECLARE @LastName varchar(30), @FirstName varchar(30)

SET @LastName = 'd'

SET @FirstName = 'a'

SELECT *

FROM employees

WHERE CASE WHEN @LastName <> '' THEN LastName

WHEN @LastName = '' THEN 'Eliminate' END Like

CASE WHEN @LastName <> '' THEN @LastName + '%'

WHEN @LastName = '' THEN 'Eliminate' END

AND

CASE WHEN @FirstName <> '' THEN FirstName

WHEN @FirstName = '' THEN 'Eliminate' END Like

CASE WHEN @FirstName <> '' THEN @FirstName + '%'

WHEN @FirstName = '' THEN 'Eliminate' END

You’ll notice that running it as is in the Northwind data base you should get only one record. Now delete the a from the @FirstName variable and rerun the query. You should see another name appear. Now bring back the a and delete the d from @LastName and rerun the query. One of the names will change. Now delete the a again and run the query with both variables equal to ‘’. You’ll get all records.

Example 3 – Cascading Where Clause Conditions

This next example I have called cascading where clause conditions because it uses only certain variables bases on a specific precedence and each CASE function takes the first qualifying condition and then moves on. Here is the code you can run in Query Analyzer:

DECLARE @LastName varchar(30), @FirstName varchar(30), @Country varchar(5), @City varchar(20),

@Title varchar(30)

SET @Country = 'usa'

SET @City = 'k'

SET @LastName = ''

SET @FirstName = ''

SET @Title = 's'

SELECT *

FROM employees

WHERE CASE WHEN @Title <> '' THEN Title

WHEN @Country <> '' THEN Country

WHEN @City <> '' THEN City

WHEN @LastName <> '' THEN LastName

WHEN @FirstName <> '' THEN FirstName END LIKE

CASE WHEN @Title <> '' THEN @Title + '%'

WHEN @Country <> '' THEN @Country

WHEN @City <> '' THEN '%' + @City + '%'

WHEN @LastName <> '' THEN @LastName + '%'

WHEN @FirstName <> '' THEN @FirstName + '%' END

AND

CASE WHEN @Title <> '' THEN CASE WHEN @Country = '' THEN 'Eliminate' ELSE Country END

WHEN @Country <> '' THEN CASE WHEN @City = '' THEN 'Eliminate' ELSE City END

WHEN @City <> '' THEN CASE WHEN @LastName = '' THEN 'Eliminate' ELSE LastName END

WHEN @LastName <> '' THEN CASE WHEN @FirstName = '' THEN 'Eliminate' ELSE FirstName END

WHEN @LastName = '' THEN 'Eliminate'

WHEN @FirstName = '' THEN 'Eliminate' END LIKE

CASE WHEN @Title <> '' THEN CASE WHEN @Country = '' THEN 'Eliminate' ELSE @Country END

WHEN @Country <> '' THEN CASE WHEN @City = '' THEN 'Eliminate' ELSE @City + '%' END

WHEN @City <> '' THEN CASE WHEN @LastName = '' THEN 'Eliminate' ELSE '%' + @LastName + '%' END

WHEN @LastName <> '' THEN CASE WHEN @FirstName = '' THEN 'Eliminate' ELSE '%' + @FirstName + '%' END

WHEN @LastName = '' THEN 'Eliminate'

WHEN @FirstName = '' THEN 'Eliminate' END

AND

CASE WHEN @Title <> '' THEN City

WHEN @Country <> '' THEN LastName

WHEN @City <> '' THEN FirstName

WHEN @LastName = '' THEN 'Eliminate'

WHEN @LastName <> '' THEN 'Eliminate'

WHEN @FirstName <> '' THEN 'Eliminate' END LIKE

CASE WHEN @Title <> '' THEN '%' + @City + '%'

WHEN @Country <> '' THEN @LastName + '%'

WHEN @City <> '' THEN '%' + @FirstName + '%'

WHEN @LastName = '' THEN 'Eliminate'

WHEN @LastName <> '' THEN 'Eliminate'

WHEN @FirstName <> '' THEN 'Eliminate' END

The first precedence is on the title. When there is a value for @Title the only variables used will be @Title, @Country, and @City. If you use SET @Title = ‘’ you’ll find that the only variables used are @Country, @City, and @LastName. When you SET @Country = ‘’ the only variables used will be @City, @LastName, and @FirstName. Having @Title = ‘’, @Country = ‘’, and @City = ‘’ will make the WHERE clause only use @LastName and @FirstName. Finally, having all the variables set equal to ‘’ except @FirstName will make the WHERE clause only use @FirstName. Here are some sample SET combinations you can use with this query:

SET @Country = 'uk' SET @City = 'l' SET @LastName = 'b' SET @FirstName = ''

SET @Title = ''

SET @Country = '' SET @City = 'l' SET @LastName = 'a' SET @FirstName = 'a'

SET @Title = ''

SET @Country = '' SET @City = '' SET @LastName = 'd' SET @FirstName = 'n'

SET @Title = ''

SET @Country = '' SET @City = '' SET @LastName = '' SET @FirstName = 'a'

SET @Title = ''

This query was intentionally designed to not return all rows if all variables are set equal to ‘’. This shows you an example of only allowing certain combinations of result sets controlled by what variables have values and if more than three values are provided then there is a systematic way to select which three should be used.

Conclusions

This article shows a few ways to design static SQL that can have different records returned depending on what values are sent into variables. Using these techniques you can develop compact code and have only one query do all the work. This is an alternative to using dynamic SQL


Using Exotic Joins in SQL – Part 2
Chris Cubley, MCSD

www.queryplan.com

In the previous article, you saw how the BETWEEN operator could be used in joins to solve problems dealing with range-based data. In this article, I will show you how to take joins even further by using multiple criteria in joins as well as using the greater than, less than, and not equals operators in joins.

Compound Joins
Compound joins are joins which use multiple criteria combined with a logical operator such as AND. This is a relatively simple concept and is commonly used in database systems that employ compound primary keys.

For a simple example of a database schema in which compound joins are necessary, consider a school management system where one of the features is tracking which classes are taught in which classrooms. The system must match up the features of the classrooms to the needs of the classes. In order to perform these functions, the following two tables are defined:

CREATE TABLE tb_Classroom(

BuildingName char(10) NOT NULL,

RoomNumber int NOT NULL,

RoomCapacity int NOT NULL,

HasLabEquip smallint NOT NULL,

CONSTRAINT PK_Classroom PRIMARY KEY(BuildingName, RoomNumber)

)

CREATE TABLE tb_ClassSection(

CourseID char(5) NOT NULL,

SectionNumber smallint NOT NULL,

BuildingName char(10) NOT NULL,

RoomNumber int NOT NULL,

InstructorID int NOT NULL,

ScheduleID int NOT NULL,

SectionCapacity int NOT NULL,

RequiresLabEquip smallint NOT NULL,

CONSTRAINT PK_ClassSection PRIMARY KEY(CourseID, SectionNumber),

CONSTRAINT FK_ClassSection_Classroom

FOREIGN KEY(BuildingName, RoomNumber)

REFERENCES tb_Classroom(BuildingName, RoomNumber)

)

In this example, the tb_Classroom table defines a list of classrooms in which classes are taught. The tb_ClassSection table contains instances of various courses taught at the school. A class section is taught in a particular classroom by an instructor according to a standard class schedule. Both the tb_Classroom and tb_ClassSection tables use natural compound primary keys.

One of the reports in the school management system lists the class sections being taught along with the capacity of their respective classrooms. In order to construct this report, the tb_ClassSection table must be joined the tb_Classroom table based upon the compound primary key of the tb_Classroom table. This can be accomplished by using a compound join to return rows where both the BuildingName AND RoomNumber columns match.

SELECT

s.CourseID,

s.SectionNumber,

c.RoomCapacity

FROM

tb_ClassSection s

INNER JOIN

tb_Classroom c

ON(

s.BuildingName = c.BuildingName

AND

s.RoomNumber = s.RoomNumber

)

This query is relatively straightforward. If you’ve been using SQL for a while, chances are you’ve seen queries like it. The query is a simple equijoin that uses the AND logical operator to include multiple criteria. Despite its simplicity, this example provides the basis for a much more powerful query construction tool.

Joins Using Inequality Comparison Operators
The school management system from the first example also contains a report listing all class sections in which the classroom is not large enough to accommodate the maximum number of students for the class section. To determine which class sections meet these criteria, the system must compare the class section’s capacity to the capacity of the classroom in which it is being taught. If the classroom’s capacity is less than the class section’s capacity, then the class section should be included in the result set.

With this query, the trick is to first join each class section to the classroom in which is being taught and then add the additional criterion that the classroom’s capacity is less than that of the class section. To do this, simply take the query from the last example and add the additional criterion.

SELECT

s.CourseID,

s.SectionNumber,

c.RoomCapacity,

s.SectionCapacity

FROM

tb_ClassSection s

INNER JOIN

tb_Classroom c

ON(

s.BuildingName = c.BuildingName

AND

s.RoomNumber = s.RoomNumber

AND

c.RoomCapacity < s.SectionCapacity

)

A common mistake when constructing queries such as this is not including the equijoin criteria necessary to match up the rows to be compared by the inequality operator. If only the inequality comparison is included in the criteria, the query returns all the rows where a classroom’s capacity is less than that of any class section, regardless of whether or not the class section was taught in that classroom.

Not Equals Joins
You may be wondering how in the world it could be useful to use the not equals operator in a join. For an example, consider another report in the school management system in which you must the misallocation of laboratory-equipped classrooms. This report must list all of the class sections that require laboratory equipment, but are scheduled to be taught in classrooms that do not have it. The report should also include all non-lab class sections being taught in laboratory classrooms. In the tables, the class sections that require laboratory equipment are indicated with a RequiresLabEquip value of 1, and the classrooms equipped with laboratory equipment are indicated with a HasLabEquip value of 1.

This problem follows a similar pattern to that of the capacity problem. The only difference is the use of the not equals operator in place of the less than operator. After matching the class section with the classroom in which it is being taught, the value of the RequiresLabEquip column must be compared with the HasLabEquip column. If these values are not equal, there is a laboratory equipment allocation problem and the class section should be included on the report. Applying these criteria result in the following query:

SELECT

s.CourseID,

s.SectionNumber,

c.HasLabEquip,

s.RequiresLabEquip

FROM

tb_ClassSection s

INNER JOIN

tb_Classroom c

ON(

s.BuildingName = c.BuildingName

AND

s.RoomNumber = s.RoomNumber

AND

c.HasLabEquip <> s.RequiresLabEquip

)

When using the not equals operator in joins, it is even more vital to remember to use additional join criteria than it is when using the greater than and less than operators. In this case, if only the not equals criterion was specified, the query would perform a cross join and then exclude only the class section-classroom pairs where the laboratory indicator was not equal. If there were 100 classrooms and 500 class sections, this could possibly return a result set of 25,000 - 50,000 rows – definitely not what was intended.

Beyond the Basics
Compound joins, especially those employing the inequality and not equals operators, can be used with other advanced SQL techniques to construct queries that are even more powerful. The key to leveraging these advanced joins is to spell out the requirements of the problem clearly in terms of relating the available sets of data. Once the relationships between the sets of data are understood, these relationships can be encoded into join criteria. This technique, along with testing and validation of the output, will enable you to solve complicated problems effectively


Using Exotic Joins in SQL– Part 1
Using Exotic Joins in SQL – Part 1
Chris Cubley, MCSD

www.queryplan.com

When most developers think of joins, they think of “a.SomethingID = b.SomethingID”. This type of join, the equijoin, is vitally important to SQL programming; however, it only scratches the surface of the power of the SQL join.

This is the first in a series of articles that will look at several different types of “exotic” joins in SQL. This article will focus on using the BETWEEN operator in joins when dealing with range-based data.

Introducing the BETWEEN Join
When dealing with things like calendars, grading scales, and other range-based data, the BETWEEN operator comes in very handy in the WHERE clause. It is often forgotten that the BETWEEN operator can also be used in join criteria.

In the WHERE clause, the BETWEEN operator is usually used to test whether some field is between two constants. However, the BETWEEN operator can take any valid SQL expression for any or all of its three arguments. This includes columns of tables.

One use of a BETWEEN join is to determine in which range a particular value falls. Joins of this nature tend to have the following pattern:

<FactData> BETWEEN <RangeMinimum> AND <RangeMaximum>

In this pattern, the “fact data” is contained in a table with instances of data such as payments, test scores, login attempts, or clock in/out events. The other table, the “range lookup table”, is usually a smaller table which provides a range minimum and maximum and other data for the various ranges.

For example, consider a scenario in which a student is enrolled in a class. A student receives a numeric grade for a class on a scale of 0 to 100. This numeric grade corresponds to a letter grade of A, B, C, D, or E. However, the school does not use the traditional grading scale in which 90 to 100 corresponds to an A, 80-89 corresponds to a B, and so forth. Instead, the school uses the following grading scale:

Letter Grade
Numeric Grade

A
92 - 100

B
84 - 91

C
76 - 83

D
68 - 75

E
0 - 68

To accommodate the school’s custom grading scale, their records database has the following table defined:

CREATE TABLE tb_GradeScale(

LetterGrade char(1) NOT NULL,

MinNumeric int NOT NULL,

MaxNumeric int NOT NULL,

IsFailing smallint NOT NULL,

CONSTRAINT PK_GradeScale PRIMARY KEY(LetterGrade),

CONSTRAINT CK_MinMax CHECK(MinNumeric <= MaxNumeric)

)

The students’ numeric scores are stored in the following table:

CREATE TABLE tb_StudentGrade(

StudentID int NOT NULL,

ClassID varchar(5) NOT NULL,

NumericGrade int NOT NULL,

CONSTRAINT PK_StudentGrade PRIMARY KEY(StudentID, ClassID),

CONSTRAINT CK_StudentGrade_NumericGrade

CHECK(NumericGrade BETWEEN 0 AND 100)

)

In this scenario, the tb_StudentGrade table is the “fact table” and the tb_GradeScale table is the “range lookup table”. The NumericGrade field serves as “fact data” while the MinNumeric and MaxNumeric fields serve as the “range minimum” and “range maximum”. Thus, following the fact-min-max pattern, we can construct the following join criteria:

NumericGrade BETWEEN MinNumeric AND MaxNumeric

If we put these join criteria into the context of a query which generates a report containing all the students’ letter grades for English 101, we end up with the following:

SELECT

s.StudentID,

g.LetterGrade

FROM

tb_StudentGrade s

INNER JOIN

tb_GradeScale g

ON(

s.NumericGrade BETWEEN g.MinNumeric AND g.MaxNumeric

)

WHERE

ClassID = 'EH101'

In this query, we join the student grade table with the grading scale table in order to translate a numeric grade to a letter grade. In order to accomplish this, we use the BETWEEN operator to specify the relationship between the two tables being joined.

Using BETWEEN With Temporal Data
Some of the trickiest queries to write are those that deal with temporal data like calendars, appointment times, and class schedules. For example, many businesses have a fiscal calendar that they use for accounting. Accounting periods may start on the 26th of the month and end on the 25th of the following month. The company may vary the starting and ending dates of each accounting period to even out the number of days in each accounting period. In order to generate reports by accounting period, you need to define a table that lays out the fiscal calendar being used. Such a table may look like this:

CREATE TABLE tb_FiscalCalendar(

FiscalYear int NOT NULL,

AcctPeriod int NOT NULL,

StartDatetime datetime NOT NULL,

EndDatetime datetime NOT NULL,

CONSTRAINT PK_FiscalCalendar PRIMARY KEY(FiscalYear, AcctPeriod), CONSTRAINT CK_FiscalCalendar_DateCheck

CHECK(StartDatetime < EndDatetime)

)

In this table, the FiscalYear column indicates the fiscal year to which the accounting period belongs. The AcctPeriod column identifies the accounting period within the fiscal year. The StartDatetime and EndDatetime columns specify the actual starting and ending date and time of the accounting period.

Suppose you are trying to write a report as part of a customer payment processing system. This report summarizes the total number and amount of payments by accounting period. The records of the customer payments are stored in the following table:

CREATE TABLE tb_Payment(

PaymentID int NOT NULL IDENTITY(1, 1),

AccountID int NOT NULL,

PostedDatetime datetime NOT NULL DEFAULT(GETDATE()),

PaymentAmt money NOT NULL,

CONSTRAINT PK_Payment PRIMARY KEY(PaymentID)

)

In order to construct the query needed for the report, you must first determine the fiscal year and accounting period in which each payment occurred. You must then group by the fiscal year and accounting period, summing the PaymentAmt field and counting the number of records in each group.

To determine each payment’s accounting period, you can use a BETWEEN join to the tb_FiscalCalendar table:

FROM

tb_Payment p

INNER JOIN

tb_FiscalCalendar c

ON(

p.PostedDatetime BETWEEN c.StartDatetime AND c.EndDatetime

)

As do many other joins using the BETWEEN operator, this join follows the fact-min-max pattern seen in the grading scale example. Each payment record (of which there are many) provides a “fact” stating that a certain payment occurred at a particular date and time. The fiscal calendar table acts more as a configuration table that specifies a range of datetime values and provides configuration data about this range.

To finish off the payment reporting query, we add the grouping, aggregate functions, and an ORDER BY clause to make the output more readable:

SELECT

c.FiscalYear,

c.AcctPeriod,

COUNT(*) AS PaymentCount,

SUM(PaymentAmt) AS TotalPaymentAmt

FROM

tb_Payment p

INNER JOIN

tb_FiscalCalendar c

ON(

p.PostedDatetime BETWEEN c.StartDatetime AND c.EndDatetime

)

GROUP BY

c.FiscalYear,

c.AcctPeriod

ORDER BY

c.FiscalYear,

c.AcctPeriod

The output yields the needed report easily and efficiently. With proper indexing, this query should run quite well even against large sets of data.

Other Uses of BETWEEN Joins
The BETWEEN join can be put to use in a number of other scenarios. Coupling the BETWEEN operator with a self-join can be a useful technique for concurrency-checking queries such as validating calendars and appointment schedules. BETWEEN joins can be used to produce histograms by aggregating ranges of data. In a situation where you must join precise data to rounded data, the BETWEEN operator can be used to perform a “fuzzy” join. Once you put the BETWEEN join in your query toolbox, you’ll find even more uses for it and wonder how you ever did without it.

About the Author
Chris Cubley is an MCSD with over four years of experience designing and implementing SQL Server-based solutions in the education, healthcare, and telecommunications industries


Reusing Identities
What is the best way to Find and reuse deleted identities

By: Dinesh Priyankara

In most table designs, Identity columns are used to maintain the uniqueness of records. There is no problem with insertion and modification of data using an identity column. With deletions though, gaps can occur between identity values. There are several ways to reuse these deleted (removed) identity values.

You can find a good solution in Books Online but I wanted to find a new way and my research ended up with a good solution. After several comparisons, I decided to continue with my solution. So, I'd like to share my method with you all and let you decide what solution to use.

First of all, let抯 create a table called ?OrderHeader'?that has three columns. Note that the first column intID is identity type column.

IF OBJECT_ID('OrderHeader') IS NOT NULL
DROP TABLE OrderHeader
GO
CREATE TABLE OrderHeader
(intID int IDENTITY(1,1) PRIMARY KEY,
strOrderNumber varchar(10) NOT NULL,
strDescription varchar(100))

Now let抯 add some records to table. If you want, you can add small amount of records but I added 10000 records because most tables have more than 10000 records and we must always try to make our testing environment real.
DECLARE @A smallint
SET @A = 1
WHILE (@A <> 10001)
BEGIN
INSERT INTO OrderHeader
(strOrderNumber,
strDescription)
valueS
(慜D-' + CONVERT(varchar(3), @A), -- Adding something for Order Number
'Description' + CONVERT(varchar(3), @A)) -- Adding something for Description
SET @A = @A + 1
END

OK. Let抯 delete some randomly selected records from the table.

DELETE OrderHeader WHERE intID = 9212
DELETE OrderHeader WHERE intID = 2210
DELETE OrderHeader WHERE intID = 3200

If you run now a simple select query against the table, you will see some gaps between the column intID values.

Now it is time to find these gaps and reuse. As I mentioned above there are two methods (or more methods if you have already done in some other way). First let抯 see the BOL example.

Method 1

DECLARE @NextIdentityvalue int

SELECT @NextIdentityvalue = MIN(IDENTITYCOL) + IDENT_INCR('OrderHeader')
FROM OrderHeader t1
WHERE IDENTITYCOL BETWEEN IDENT_SEED('OrderHeader') AND 32766
AND NOT EXISTS (SELECT * FROM OrderHeader t2
WHERE t2.IDENTITYCOL = t1.IDENTITYCOL + IDENT_INCR('OrderHeader'))

SELECT @NextIdentityvalue AS NextIdentityvalue

Output:
NextIdentityvalue
--------------------
2210

This is very simple query. You can find the first deleted identity value and can reuse it. But remember you have to set the IDENTITY_INSERT ON that is allowed to explicit values to be inserted into identity column.

SET IDENTITY_INSERT OrderHeader ON

INSERT INTO OrderHeader
(intID,
strOrderNumber,
strDescription)
valueS
(@NextIdentityvalue,
慜D-' + CONVERT(varchar(3), @A),
'Description' + CONVERT(varchar(3), @A))

SET IDENTITY_INSERT OrderHeader OFF

Now let抯 see the method 2.

Method 2

Now I am going to create another table that is called 搕b_Numbers?and has only one column that contains numbers in sequence. In my most databases, I have created and used this table for many tasks. Let me come with those in my future articles.

IF OBJECT_ID('tb_Numbers') IS NOT NULL
DROP TABLE tb_Numbers
GO
CREATE TABLE tb_Numbers
(intNumber int PRIMARY KEY)

Note that I have inserted 30000 records (numbers) into the table. The range is depending on the usage of this table. In my some of databases, this range was 1 to 1000000.

DECLARE @A1 int
SET @A1 = 1

WHILE (@A1 <> 30000)
BEGIN
INSERT INTO tb_Numbers (intNumber) valueS (@A1)
SET @A1 = @A1 + 1
END

Now let抯 query the gaps (or first deleted identity value) in the OrderHeader table

SELECT TOP 1 @NextIdentityvalue = intNumber
FROM OrderHeader
RIGHT OUTER JOIN tb_Numbers
ON tb_Numbers.intNumber = OrderHeader.intID
WHERE intID IS NULL AND intNumber < = (SELECT MAX(intID) FROM OrderHeader)

SELECT @NextIdentityvalue AS NextIdentityvalue
Output:
NextIdentityvalue
--------------------
2210

This is very simple query too. I have used RIGHT OUTER JOIN to join the OrderHeader table with tb_Numbers. This join causes to return all rows (numbers) from tb_Numbers table. Then I have used some search conditions (WHERE clauses) to get the correct result set. This result set contains all missing values in intID column. By using TOP 1, we can get the desired result.

You can do the insertion same way as I have done in method 1.

Now it is time to compare these two methods. I simply used STATISTICS IO and the EXECUTION TIME to get the evaluation.

Comparison

DECLARE @StartingTime datetime, @EndingTime datetime

Print 憁ethod1:?BR>
SET STATISTICS IO ON
SET @StartingTime = getdate()

SELECT MIN(IDENTITYCOL) + IDENT_INCR('OrderHeader')
FROM OrderHeader t1
WHERE IDENTITYCOL BETWEEN IDENT_SEED('OrderHeader') AND 32766
AND NOT EXISTS (SELECT * FROM OrderHeader t2
WHERE t2.IDENTITYCOL = t1.IDENTITYCOL + IDENT_INCR('OrderHeader'))

SET @EndingTime = getdate()
SET STATISTICS IO OFF

SELECT DATEDIFF(ms, @StartingTime, @EndingTime ) AS ExecTimeInMS

Print 憁ethod2:?BR>
SET STATISTICS IO ON
SET @StartingTime = getdate()

SELECT TOP 1 intNumber
FROM OrderHeader
RIGHT OUTER JOIN tb_Numbers
ON tb_Numbers.intNumber = OrderHeader.intID
WHERE intID IS NULL AND intNumber < = (SELECT MAX(intID) FROM OrderHeader)

SET @EndingTime = getdate()
SET STATISTICS IO OFF

SELECT DATEDIFF(ms, @StartingTime, @EndingTime ) AS ExecTimeInMS
Output:
Method1:

2210

Table 'OrderHeader'. Scan count 9998, logical reads 20086, physical reads 0, read-ahead reads 0.

ExecTimeInMS
------------
200

Method2:
2210

Table 'tb_Numbers'. Scan count 1, logical reads 5, physical reads 0, read-ahead reads 0.
Table 'OrderHeader'. Scan count 2, logical reads 14, physical reads 0, read-ahead reads 0.

ExecTimeInMS
------------
0

As per the output, there are 20086 logical reads and it has taken 200 ms for the first method. But in second method there are only 19 logical reads and the execution time is less considerable.

That抯 why I selected to continue in my way. But there may be a side that I have not seen but you can see. So, try on this and see whether how this T-SQL solution suit for you.

I highly appreciate your comments and suggestion.


 选择随机的记录
A number of other writers have suggested using a client-side cursor, loading the table and then using move by Rand()*recordcount to get a random record. This works well for small datasets or choosing just a few records, but when 20 or more records need to be selected from a large database, there can be a large performance hit.

The Rand() function in SQL seems ideal for the task, but the problem is that if used thus:

SELECT TOP 5 Questions.*, Rand() as ID
FROM Questions

the ID field always has the same number in it (ie: the Rand() was only evaluated once).

I have found an alternative method that seems to ''trick'' the backend into evaluating the expression each time. It involves changing the seed for each record and is easiest to use if there is an AutoInc field in the table:

SELECT TOP 20 Questions.*
FROM Questions
ORDER BY (Rand(Questions.QID))

where QID is an AutoInc field. This does produce a random list quite easily and on my testing the list does seem to be reasonably random.

Note: Using MS Access - Rnd() is the function, not Rand()


I recently had the basic need to retrieve a record from the database at random. What seemed to be an easy task quickly became a complex one. This case showed an interesting quirk with T-SQL that was resolved in an equally quirky way. This quick article shows you a method to retrieve random data or randomize the display of data.
Why would you ever want to retrieve random data?

In my case, I wanted to pull a random article to display on this site抯 homepage
Choose a random user to receive a prize
Choose a random employee for a drug test
The problem with retrieving random data using the RAND() function is how it抯 actually used in the query. For example, if you run the below query against the Northwind database, you can see that you will see the same random value and date for each row in the results.

SELECT TOP 3 RAND(), GETDATE(), ProductID, ProductName
FROM Products

Results:
0.54429273766415864 2003-03-19 15:06:27.327 17 Alice Mutton
0.54429273766415864 2003-03-19 15:06:27.327 3 Aniseed Syrup
0.54429273766415864 2003-03-19 15:06:27.327 40 Boston Crab Meat

This behavior prohibits the obvious way to retrieve random data by using a query like this:

SELECT TOP 3 ProductID, ProductName
FROM products
ORDER BY RAND()

Results in:

ProductID ProductName
----------- ----------------------------------------
17 Alice Mutton
3 Aniseed Syrup
40 Boston Crab Meat

If you execute this query over and over again, you should see the same results each time. The trick then is to use a system function that doesn抰 use this type of behavior. The newid() function is a system function used in replication that produces a Global Unique Identifier (GUID). You can see in the following query that it produces unique records at the row-level.

SELECT TOP 3 newid(), ProductID, ProductName
FROM Products

Results in:
ProductID ProductName
------------------------------------ ----------- ----------------------------------------
8D0A4758-0C90-49DC-AF3A-3FC949540B45 17 Alice Mutton
E6460D00-A5D1-4ADC-86D5-DE8A08C2DCF0 3 Aniseed Syrup
FC0D00BF-F3A2-4341-A584-728DC8DDA513 40 Boston Crab Meat

You can also execute the following query to randomize your data (TOP clause optional):

SELECT TOP 1 ProductID, ProductName
FROM products
ORDER BY NEWID()

Results in:

ProductID ProductName
----------- ----------------------------------------
7 Uncle Bob's Organic Dried Pears

Each time you fire it off, you should retrieve a different result. There抯 also an additional way to actually use the rand() function that Itzik Ben-Gan has discovered using user defined functions and views as a workaround. The secret there is to produce a view that uses the rand() function as shown below:

CREATE VIEW VRand
AS
SELECT RAND() AS rnd
GO

Then create a user defined function (only works in SQL Server 2000) that selects from the view and returns the random value.

CREATE FUNCTION dbo.fn_row_rand() RETURNS FLOAT
AS
BEGIN
RETURN (SELECT rnd FROM VRand)
END

To use the function, you can use syntax as shown below to retrieve random records.

SELECT TOP 1 ProductID, ProductName
FROM Products
ORDER BY dbo.fn_row_rand()
GO

This is also handy if you wish to use the getdate() function at the record level to display data. I have found that this method has slight performance enhancement but it is negligible. Make sure you test between the two methods before you use either.


FAQ
Q.How can I convert a SQL date/time to just date?

A. The SQL date/time field stores a date and time but if you have a populated table and you want to convert a column so it

lists data only run the following:

update <table>
where <column>=convert(datetime,convert(char(10),<column>,101))

For example

update faq
where created=convert(datetime,convert(char(10),created,101))

Q:How can I find the Microsoft Data Access Component version install on my computer?
You can use the xp_regread extended function to read the registry to accomplish this. This UDF returns the MDAC version:
USAGE: print dbo.MDACversion()

CREATE FUNCTION MDACversion ()
RETURNS varchar(255) AS

BEGIN
DECLARE @Ver varchar( 255 )
EXEC master..xp_regread N''HKEY_LOCAL_MACHINE'',
N''Software\Microsoft\DataAccess'',
N''FullInstallVer'' , @Ver OUT , N''no_output''
RETURN (@Ver)
END

得到sqlserver的安装路径
declare @path varchar(200)
exec master.dbo.xp_regread
''HKEY_LOCAL_MACHINE'',
''SOFTWARE\MICROSOFT\MSSQLSERVER\SETUP''
''SQLPATH'',@path output
set @path=@path+''\data''
print @path


Dynamic SQL
The IN Clause
The IN clause is a good example of a use for Dynamic SQL. A lot of SQL Server developers use ASP or a similar web scripting language.

If in an asp page you have a Select list with multiple allowed values, the value of request.form("myList") on the processing page might look like this "1,3,4,6".

So we try to write a stored proc around this

Create Procedure Search
@strIDs VarChar(100)
AS

SELECT *
FROM
Products
WHERE
ProductID in (@strIDs)

GO
Oooops! No Go.

This will work

Create Procedure Search
@strIDs VarChar(100)
AS

Declare @SQL VarChar(1000)

Select @SQL = 'SELECT * FROM Products '
Select @SQL = @SQL + 'WHERE ProductID in (' + @strIDs +')'

Exec ( @SQL)

GO
N.B. This can also be solved using a technique like this.

Aliases
Giving a table or column a dynamic alias is a use for dynamic SQL.

This will not work

Select UserName FROM Table as @Alias
This will

Exec('Select UserName FROM Table as ' @Alias)
DDL
A common question asked of SQL Team is "How do I write a stored procedure that will create a table/database. I want to pass in the name"

SQL Server will not allow this

Create Table @TableName (
ID int NOT NULL Primary Key,
FieldName VarChar(10)
)
Once again, dynamic SQL to the rescue

Declare @SQL VarChar(1000)

SELECT @SQL = 'Create Table ' + @TableName + '('
SELECT @SQL = @SQL + 'ID int NOT NULL Primary Key, FieldName VarChar(10))'

Exec (@SQL)
Similarly, the code to create a database would look like this:

Exec('Create Database ' + @myDBName)
sp_executesql
sp_executesql is a system stored procedure that you can use in place of "exec" to execute your dynamic sql.

This allows you to have parameters in your dynamic query and pass them in. The end result is that SQL Server will try to cache the execution plan for your query giving you some of the advantages of a fully compiled query.

An example

Declare @SQL nVarChar(1000) --N.B. string must be unicode for sp_executesql
SELECT @SQL = 'SELECT * FROM pubs.DBO.Authors WHERE au_lname = @AuthorName'

Exec sp_executesql @SQL, N'@AuthorName nVarChar(50)', @AuthorName = 'white'
The first parameter here is the SQL statement, then you must declare the parameters, after that you pass the in parameters as normal, comma separated.

sp_executesql is also useful when you want to execute code in another database as it will run code in the context of it's database, rather than the one it was called from.

Try this from a database that is not Pubs

Create View pubs.dbo.Auths AS (SELECT au_id, au_lname, au_fname FROM Authors)
You will get this error: 'CREATE VIEW' does not allow specifying the database name as a prefix to the object name.

So you build the dynamic sql, then run it in Pub's copy of sp_executesql

I.E.

Declare @SQL nVarChar(1000)

Select @SQL = 'Create View Auths AS (SELECT au_id, au_lname, au_fname FROM Authors)'

Execute pubs.dbo.sp_executesql @sql
Permissions
When executing dynamic SQL from a stored procedure, keep in mind that the SQL is executed in the permission context of the user, not the calling procedure. This means that if your user has no rights to the tables, only to the procedure, you may run into problems.

Scope
When you run dynamic sql, it runs in it's own scope.

This

exec('set rowcount 3')

Select * from Authors

exec('set rowcount 0')
Will have no effect on the result set returned from Authors. This is because by the rowcount statements have gone out of scope by the time the Select occurs.

This would be solved by this

exec('set rowcount 3 Select * from Authors Set rowcount 0')
Declaring variables inside a dynamic SQL batch will also not be available outside the batch and vice versa. As a result, this would also not work.

declare @i int
Exec ('Select @i = 1')
Temp tables can be used to interact between batches of standard SQL and dynamic SQL. A temp table created within a dynamic SQL batch will be destroyed when the batch completes, however a temp table created before the batch will be available to it.

Create Table #tempauth(
au_id VarChar(100),
au_fname VarChar(100),
au_lname VarChar(100)

)

declare @SQL VarChar(1000)
Select @SQL = 'Insert into #tempauth Select au_id, au_fname, au_lname FROM Authors'
exec(@SQL)

Select * from #tempauth

drop table #tempauth
Summary
That wraps up my intro to dynamic SQL. I hope it is a little clearer now than it was before. Dynamic SQL is a very powerful tool to have in your arsenal as long as it doesn't substitute for bad application design. If you can avoid it with better data modelling, then that is the best way to go as your code will end up neater and generally faster.

Until next time

Happy coding.


Converting Multiple Rows into a CSV String
Based the problem outlined in RocketScientist's article (entitled Converting Multiple Rows into a CSV String), I would like to present an alternative method for accomplishing a similar task. This article will show a faster method to convert multiple rows into multiple CSV strings.

--------------------------------------------------------------------------------
Say you had a table with a composite key, and you wanted to return each distinct first member and a CSV list of all the applicable second members. Using the approach outlined by RocketScientist, you would have to iterate through each distinct first member and run the sp_MakeCharList proc N times. I think there is a better way.
First, the DDL and sample data:

create table Page47 (
i int not null,
vc varchar(5) not null,
constraint pk_Page47 primary key (i,vc) )
go

set nocount on
declare @i int
select @i = 0
while @i <5000
begin
insert into Page47 (i,vc)
select round((rand() * 100), 0),
char(round((rand() * 25), 0) + 97) +
char(round((rand() * 25), 0) + 97) +
char(round((rand() * 25), 0) + 97) +
char(round((rand() * 25), 0) + 97) +
char(round((rand() * 25), 0) + 97)
select @i = @i + 1
end
go
The sample data should look something like this:

select top 10
i,
vc
from
Page47
order by
newid()

i vc
----------- -----
743 nndsn
267 krtub
582 lgrrg
185 qmcku
907 rrpqe
635 cpbcp
237 uovqh
85 bpixy
554 mfsqh
49 geabv

(10 row(s) affected)
First, lets look at adapting RocketScientists solution to this problem. Here is how you might iterate through the table to build all of the CSVs

--create a temp table
select
identity(int,1,1) as rowid,
i
into
#workingtable
from
Page47
group by
i

--create a table to hold the lists
create table #Rocket (
i int not null primary key,
list varchar(8000) not null)

declare
@i int,
@maxrowid int,
@sql varchar(8000),
@list varchar(8000)

select
@maxrowid = max(rowid),
@i = 0
from
#workingtable

--loop through the all the i's
while @i <= @maxrowid
begin

select
@sql = 'select vc from Page47 where i = ' + convert(varchar,i)
from
#workingtable
where
rowid = @i

exec sp_MakeCharList
@codelistselect=@sql,
@delimitedlist=@list output

insert into #rocket (i,list)
select i, @list
from
#workingtable
where
rowid = @i and
@list is not null
select @i = @i + 1
end

--return a sample from the final rowset
select top 10
i,
case
when len(list) > 50 then convert(varchar(50), left(list,47) + '...')
else convert(varchar(50),list)
end as list
from
#rocket
order by
newid()
go

i list
----------- --------------------------------------------------
790 fpflf, hjutq, inahr, kgkox, kroox, nbkiy, rsscw...
752 imokv, jcrgz, khsui, lbmwm, pkjee, ujxoi, ulgsm...
74 aphdr, cdmoc, fogdm, gslsg, hushk, icchc, lybud...
320 bvevh, ohyqv, oivyc, yevjb, ypnrk, yxfji
956 aisgk, emeup, fhtad, hffnm, iidpn, ivqlk, jhklu...
530 bqcjr, dnfzr, dqwas, egqpm, fplip, ibrxp, jcbhk...
137 aorpw, bngki, cibny, hwsrp, jdown, qlepc, zekbo, z
72 ghrxi, gmkzk, mufmc, uixpr, vjvbu, ymruj, zmucr, z
911 fibrs, fuhub, hgcwh, kuvcf, lcfux, nrqht, pfaae...
62 edxyj, grrmt, hmkfv, hprlt, ieqvg, mojsk, pdrmv...
Now, here is a set based method for doing the same thing.

--create a table to work with
create table #workingtable (
i int not null,
vc varchar(5) not null,
list varchar(8000),
constraint pk_wt primary key (i,vc) )

insert into #workingtable (i,vc)
select i,vc
from Page47
order by i,vc

declare
@list varchar(8000),
@lasti int

select
@list = '',
@lasti = -1

--here is the meat of the work
update
#workingtable
set
@list = list = case
when @lasti <> i then vc
else @list + ', ' + vc
end,
@lasti = i

--return a sample from the final rowset
select top 10
i,
case
when len(max(list)) > 50 then convert(varchar(50), left(max(list),47) + '...')
else convert(varchar(50),max(list))
end as list
from
#workingtable
group by
i
order by
newid()
go

i list
----------- --------------------------------------------------
127 itvgq, ljosw, nxmdj, oshrp, plxff, pubig, sthck...
849 gcifo, hbxkf, njkdl, sfesm, sjhky, uxhfq, vjeno...
684 fejly, fqyqf, gpfce, hutht, kwywo, mapco, momqn...
461 fsofv, fzked, murat, vzmek, yrqjo
612 nmmey, tfjhv, ulwuj, xxaaq
374 bbthd, jvjwz, klcsq, mrakf, peztf, pixww, rtwdd
730 dlynf, egqei, hhckx, nsvdn, obnhh, rfbwh, ytgfi
458 eijdr, gtxhu, lhtqh, phprf, qjhcr, vqnos
655 bijer, fwlgk, nrcbm, sohho, trjtw, usjdj, uvpie...
837 ayxcv, epurf, flvtj, ftxcj, imjap, pmygd, sqhcc...
My tests show the iterative method taking 2.813 seconds and the set-based method taking 0.670 seconds, with this set of sample data. Increasing the number of rows shows the cursor solution creeping up on the set based solution; however, increasing the number of distinct i抯 allowed show the performance of the cursor sharply declining. With 1000 distinct i抯, the cursor solution dropped to 89.420 seconds while the set based solution screamed at a 1.513 seconds. As you can see, the implementation choice will be dependent on your data set, so you should test both methods to find which is right for you.


Access Get lastinsertid
We have a table in MsAccess like :

Test, Fields (id=autoinc, name=text);

First we have to have a function like the one below :

function GetLastInsertID : integer;
begin

// datResult = TADODataSet

datResult.Active := False;
datResult.CommandText := 'select @@IDENTITY as [ID]';
datResult.Active := True;

Result := datResult.FieldByName('id').AsInteger;

datResult.Active := False;

end;

Now before getting the last inserted record record id = autoincrement field, in other words calling the above function. You have to do a SQL insert like the following

procedure InsertRec;
begin

// datCommand = TADOCommand

datCommand.CommandText := 'insert into [test] ( [name] ) values ( "Test" )';
datCommand.Execute;

end;

Now if we like to know which is the last autoinc value ( notice that the getlastinsertid proc. only works after the insertrec proc)


 关于@@identity(_rambo)
SCOPE_IDENTITY、IDENT_CURRENT 和 @@IDENTITY 在功能上相似，因为它们都返回插入到 IDENTITY 列中的值。

IDENT_CURRENT 不受作用域和会话的限制，而受限于指定的表。IDENT_CURRENT 返回为任何会话和作用域中的特定表所生成的值。有关更多信息，请参见 IDENT_CURRENT。

SCOPE_IDENTITY 和 @@IDENTITY 返回在当前会话中的任何表内所生成的最后一个标识值。但是，SCOPE_IDENTITY 只返回插入到当前作用域中的值；@@IDENTITY 不受限于特定的作用域。

例如，有两个表 T1 和 T2，在 T1 上定义了一个 INSERT 触发器。当将某行插入 T1 时，触发器被激发，并在 T2 中插入一行。此例说明了两个作用域：一个是在 T1 上的插入，另一个是作为触发器的结果在 T2 上的插入。

假设 T1 和 T2 都有 IDENTITY 列，@@IDENTITY 和 SCOPE_IDENTITY 将在 T1 上的 INSERT 语句的最后返回不同的值。

@@IDENTITY 返回插入到当前会话中任何作用域内的最后一个 IDENTITY 列值，该值是插入 T2 中的值。

SCOPE_IDENTITY() 返回插入 T1 中的 IDENTITY 值，该值是发生在相同作用域中的最后一个 INSERT。如果在作用域中发生插入语句到标识列之前唤醒调用 SCOPE_IDENTITY() 函数，则该函数将返回 NULL 值。

有关说明，请参见示例。

示例
下列示例将创建两个表 TZ 和 TY，并在 TZ 上创建一个 INSERT 触发器。当将某行插入表 TZ 中时，触发器 (Ztrig) 将激发并在 TY 中插入一行。

USE tempdb
GO
CREATE TABLE TZ (
Z_id int IDENTITY(1,1)PRIMARY KEY,
Z_name varchar(20) NOT NULL)

INSERT TZ
valueS ('Lisa')
INSERT TZ
valueS ('Mike')
INSERT TZ
valueS ('Carla')

SELECT * FROM TZ

--Result set: This is how table TZ looks
Z_id Z_name
-------------
1 Lisa
2 Mike
3 Carla

CREATE TABLE TY (
Y_id int IDENTITY(100,5)PRIMARY KEY,
Y_name varchar(20) NULL)

INSERT TY (Y_name)
valueS ('boathouse')
INSERT TY (Y_name)
valueS ('rocks')
INSERT TY (Y_name)
valueS ('elevator')

SELECT * FROM TY
--Result set: This is how TY looks:
Y_id Y_name
---------------
100 boathouse
105 rocks
110 elevator

/*Create the trigger that inserts a row in table TY
when a row is inserted in table TZ*/
CREATE TRIGGER Ztrig
ON TZ
FOR INSERT AS
BEGIN
INSERT TY valueS ('')
END

/*FIRE the trigger and find out what identity values you get
with the @@IDENTITY and SCOPE_IDENTITY functions*/
INSERT TZ valueS ('Rosalie')

SELECT SCOPE_IDENTITY() AS [SCOPE_IDENTITY]
GO
SELECT @@IDENTITY AS [@@IDENTITY]
GO

--Here is the result set.
SCOPE_IDENTITY
4
/*SCOPE_IDENTITY returned the last identity value in the same scope, which was the insert on table TZ*/

@@IDENTITY
115
/*@@IDENTITY returned the last identity value inserted to TY by the trigger, which fired due to an earlier insert on


 替换text,ntext列中的字符串(不支持中文)
author:amjn
date:2003-03-28
version:1.0
function:替换text,ntext列中的字符串(不支持中文)
*/
declare @ptr varbinary(16)
declare @amjnId int
declare @Position int,@len int
set @len=datalength('ILOVEHJL')
declare wux_Cursor scroll Cursor
for
select textptr([Name]),[amjnId] from USA201
for read only
open wux_Cursor
fetch next from wux_Cursor into @ptr,@amjnId
while @@fetch_status=0
begin
select @Position=patindex('%ILOVEHJL%',[Name]) from USA201 where [amjnId]=@amjnId
while @Position>0
begin
set @Position=@Position-1
updatetext USA201.[Name] @ptr @Position @len 'i love hjl'
select @Position=patindex('%ILOVEHJL%',[Name]) from USA201 where [amjnId]=@amjnId
end
fetch next from wux_Cursor into @ptr,@amjnId
end
close wux_cursor
deallocate wux_cursor


select [UserName],
(select count(*) from company where Comid=T1.UserName) as 招聘站,
(select count(*) from TrainOrg where OrgID=T1.UserName) as 培训机构,
(select count(*) from Train_Company Where Comid=T1.UserName) as 培训公司,
(select count(*) from Com_Class where Comid=T1.UserName) as 校园招聘,
(select count(*) from Cjoleng.dbo.CompanyInfo where CompanyID in (select [Id] from Combaseuser where [UserName]=T1.[UserName])) as 英文站
from Combaseuser T1
group by [UserName]
having count(*)>1


周周共享论坛--『数 据 库』浏览：怎样将Varbinary数据类型的数据转换成varchar型？

总访问量: 41378 今日访问: 407 昨日访问: 507 您的访量:
加入收藏
联系我们
新手上路
上线 [image] 重登陆 [image] 用户控制面板 [image] 搜索 [image] 论坛状态 [image] 帮助 [image] 退出

>> 欢迎 ugvanxk [ 发短信 :: 发表的主题 :: 参与的主题 :: 我能做什么 ] 我的收件箱 (0 新)
周周共享论坛 → 『数 据 库』 → 浏览：怎样将Varbinary数据类型的数据转换成varchar型？

您是本帖的第 8 个阅读者 浏览上一篇主题 刷新本主题 树形显示贴子 　 浏览下一篇主题
* 贴子主题： 怎样将Varbinary数据类型的数据转换成varchar型？
ugvanxk 帅哥哟，离线，有人找我吗？

等级：版主
文章：168
积分：441
注册：2002-11-2
楼顶
发贴心情 怎样将Varbinary数据类型的数据转换成varchar型？

有一个段是varbinary型，有一条记录存放的内容为0x06555755565A5A5B595F5C5C5C41434142464647454B4848484D4F4D4EB2B2B3

请问怎样将此内容转换成字符串“0x06555755565A5A5B595F5C5C5C41434142464647454B4848484D4F4D4EB2B2B3
”

Select Convert(Varchar, columnname) From tablename
WUVZZ[Y_\\\ACABFFGEKHHHMOMN

不符合我的要求

Select Convert(Varchar(8000), columnname) From tablename
不行，执行结果如下：

UWUVZZ[Y_\\\ACABFFGEKHHHMOMN
SELECT CAST(CAST(0x06555755565A5A5B595F5C5C5C41434142464647454B4848484D4F4D4EB2B2B3 AS varchar(8000)) AS varbinary)

RESULT
0x06555755565A5A5B595F5C5C5C41434142464647454B4848484D4F4D4EB2
SQL 自动截断了数据长度。
请高手指教，为何CAST(0x06555755565A5A5B595F5C5C5C41434142464647454B4848484D4F4D4EB2B2B3 AS varchar(8000)不可执行？？

declare @b varbinary(300), @str varchar(300)

select @b = 0x06555755565A5A5B595F5C5C5C41434142464647454B4848484D
exec master..xp_varbintohexstr @b, @str out
print @str

--給分吧~~~~，這玩意整整花了我一天半的時間
又是个没有公布的存储过程，微软怎么老藏着些功能！！

点击查看用户来源及管理<br>发贴IP：61.48.16.158 2003-5-10 11:29:14
编辑这个贴子 鲜花(0) 鸡蛋(0)

本主题贴数1，分页： [1]
*快速回复：怎样将Varbinary数据类型的数据转换成varchar型？
你的用户名: 还没注册? 密码: 忘记密码?
内容
  • HTML标签： 不可用
  • UBB标签： 可用
  • 贴图标签： 可用
  • Flash标签：可用
  • 表情字符转换：可用
  • 上传图片：可用
  • 最多16KB
    • 邮件回复 显示签名 [Ctrl+Enter直接提交贴子]

    锁定 | 解锁 | 删除 | 移动 | 固顶 | 奖励 | 惩罚 | 发布公告

    Powered by：DVBBS Ver5.0 Final
    Copyright ?2002 - 2003 pigtwo.com , 页面执行时间：140.625毫秒


MS SQL SERVER 图像或大文本的输入输出
主题：MS SQL SERVER 图像或大文本的输入输出
发信人: tidycc()
整理人: tidycc(2001-02-11 22:05:07), 站内信件
在MS SQL SERVER 安装目录下有个可执行文件叫 TEXTCOPY.EXE
可对 MS SQL SERVER 中的文本或图像数据进行输入输出.
不过你可以在MS-DOS方式下执行textcopy /? 得到它的描述。
下面是这个工具的描述:
Copies a single text or image value into or out of SQL Server. The val
ue
is a specified text or image 'column' of a single row (specified by th
e
"where clause") of the specified 'table'.

If the direction is IN (/I) then the data from the specified 'file' is

copied into SQL Server, replacing the existing text or image value. If
the
direction is OUT (/O) then the text or image value is copied from
SQL Server into the specified 'file', replacing any existing file.

TEXTCOPY [/S ][sqlserver]] [/U [login]] [/P ][password]]
[/D ][database]] [/T table] [/C column] [/W"where clause"]
[/F file] [{/I | /O}] [/K chunksize] [/Z] [/?]

/S sqlserver The SQL Server to connect to. If 'sqlserver' is n
ot
specified, the local SQL Server is used.
/U login The login to connect with. If 'login' is not spec
ified,
a trusted connection will be used.
/P password The password for 'login'. If 'password' is not
specified, a NULL password will be used.
/D database The database that contains the table with the tex
t or
image data. If 'database' is not specified, the d
efault
database of 'login' is used.
/T table The table that contains the text or image value.

/C column The text or image column of 'table'.
/W "where clause" A complete where clause (including the WHERE keyw
ord)
that specifies a single row of 'table'.
/F file The file name.
/I Copy text or image value into SQL Server from 'fi
le'.
/O Copy text or image value out of SQL Server into '
file'.
/K chunksize Size of the data transfer buffer in bytes. Minimu
m
value is 1024 bytes, default value is 4096 bytes.

/Z Display debug information while running.
/? Display this usage information and exit.

You will be prompted for any required options you did not specify.

为此, 可写一个存储过程,调用这个命令
CREATE PROCEDURE sp_textcopy (
@srvname varchar (30),
@login varchar (30),
@password varchar (30),
@dbname varchar (30),
@tbname varchar (30),
@colname varchar (30),
@filename varchar (30),
@whereclause varchar (40),
@direction char(1))
AS
DECLARE @exec_str varchar (255)
SELECT @exec_str =
'textcopy /S ' + @srvname +
' /U ' + @login +
' /P ' + @password +
' /D ' + @dbname +
' /T ' + @tbname +
' /C ' + @colname +
' /W "' + @whereclause +
'" /F ' + @filename +
' /' + @direction
EXEC master..xp_cmdshell @exec_str

下面是一个拷贝图像到SQL Server的pubs数据库的例子, 表名pub_info, 字段名
logo，图像文件名picture.bmp，保存到pub_id='0736'记录 sp_textcopy @srvn
ame = 'ServerName',
@login = 'Login',
@password = 'Password',
@dbname = 'pubs',
@tbname = 'pub_info',
@colname = 'logo',
@filename = 'c:\picture.bmp',
@whereclause = " WHERE pub_id='0736' ",
@direction = 'I'


//bde要设置blobsize否则存进去,读不出来完整
数据库SQL Server，存放文件的字段类型Image
Create Table MyTable
(
FileName VarChar(20),
FileSource Image
)
存放文件到数据库

var FileName :String;
FileName := ExtractFileName(OpenDialog1.FileName);
with Query do
begin
Close;
Sql.Clear;
Sql.Add('INSERT INTO MyTable valueS (:FileName,:FileSource)');
ParamByName('FileName').AsString := FileName;
ParamByName('FileSource').LoadFromFile(OpenDialog1.FileName,ftBolob);
ExecSQL;
end;

从数据库中取出文件

var FileName :String;
begin
with Query do
begin
Close;
Sql.Clear;
Sql.Add('SELECT * FROM MyTable WHERE FileName = '?'');
Open; FileName := 'c:\'+FieldByName('FileName').AsString;
(FieldByName('FileSource') AS TBlobField).SaveToFile(FileName);
end;
end;


动态SQL四种类型的语句格式(潇湘剑公子)
1.Dynamic SQL Format 1

EXECUTE IMMEDIATE SQLStatement {USING TransactionObject} ;

eg:
string Mysql
Mysql = "CREATE TABLE Employee "&
+"(emp_id integer not null,"&
+"dept_id integer not null, "&
+"emp_fname char(10) not null, "&
+"emp_lname char(20) not null)"
EXECUTE IMMEDIATE :Mysql ;

2.Dynamic SQL Format 2

PREPARE DynamicStagingArea FROM SQLStatement {USING TransactionObject} ;
EXECUTE DynamicStagingArea USING {ParameterList} ;

eg:
INT Emp_id_var = 56
PREPARE SQLSA
FROM "DELETE FROM employee WHERE emp_id=?" ;
EXECUTE SQLSA USING :Emp_id_var ;

3.Dynamic SQL Format 3

DECLARE Cursor | Procedure DYNAMIC CURSOR | PROCEDURE FOR DynamicStagingArea ;
PREPARE DynamicStagingArea FROM SQLStatement {USING TransactionObject} ;
OPEN DYNAMIC Cursor {USING ParameterList} ;
EXECUTE DYNAMIC Procedure {USING ParameterList} ;
FETCH Cursor | Procedure INTO HostVariableList ;
CLOSE Cursor | Procedure ;
eg:
integer Emp_id_var

DECLARE my_cursor DYNAMIC CURSOR FOR SQLSA ;
PREPARE SQLSA FROM "SELECT emp_id FROM employee" ;
OPEN DYNAMIC my_cursor ;
FETCH my_cursor INTO :Emp_id_var ;
CLOSE my_cursor ;

4.Dynamic SQL Format 4

DECLARE Cursor | Procedure DYNAMIC CURSOR | PROCEDURE FOR DynamicStagingArea ;
PREPARE DynamicStagingArea FROM SQLStatement {USING TransactionObject} ;
DESCRIBE DynamicStagingArea INTO DynamicDescriptionArea ;
OPEN DYNAMIC Cursor | Procedure USING DESCRIPTOR DynamicDescriptionArea ;
EXECUTE DYNAMIC Cursor | Procedure USING DESCRIPTOR DynamicDescriptionArea ;
FETCH Cursor | Procedure USING DESCRIPTOR DynamicDescriptionArea ;
CLOSE Cursor | Procedure ;

eg:

string Stringvar, Sqlstatement
integer Intvar
Sqlstatement = "SELECT emp_id FROM employee"
PREPARE SQLSA FROM :Sqlstatement ;
DESCRIBE SQLSA INTO SQLDA ;
DECLARE my_cursor DYNAMIC CURSOR FOR SQLSA ;
OPEN DYNAMIC my_cursor USING DESCRIPTOR SQLDA ;
FETCH my_cursor USING DESCRIPTOR SQLDA ;

// If the FETCH is successful, the output
// descriptor array will contain returned
// values from the first row of the result set.
// SQLDA.NumOutputs contains the number of
// output descriptors.
// The SQLDA.OutParmType array will contain
// NumOutput entries and each entry will contain
// an value of the enumerated data type ParmType
// (such as TypeInteger!, or TypeString!).

CHOOSE CASE SQLDA.OutParmType[1]
CASE TypeString!
Stringvar = GetDynamicString(SQLDA, 1)
CASE TypeInteger!
Intvar = GetDynamicNumber(SQLDA, 1)

END CHOOSE
CLOSE my_cursor ;


关于distinct id的取法
型如表：t_test
--------------------------------------
id(int) cost(int) des Autoid(id)
--------------------------------------
1 10 aaaa 1
1 15 bbbb 2
1 20 cccc 3
2 80 aaaa 4
2 100 bbbb 5
2 60 dddd 6
3 500 dddd 7
--------------------------------------
要在其中取每一类id中间cost最大的所有纪录，很多网友都对此进行了讨论，对此，我提出一点自己的看法，
我认为用一条很难（对我而言）达到要求，同时也引出了一个问题，即：在SQL中定义自己的函数（/存储过程），达到特定的目的。具体对这个例子而言，可以这么写：

CREATE PROCEDURE sp_test
AS
begin
SELECT * INTO #mytbl FROM t_test

SELECT id, MAX(cost) AS max_cost
INTO #mytemp
FROM t_test
GROUP BY id
HAVING (COUNT(*) > 1)
DELETE #mytbl FROM #mytemp, #mytbl WHERE #mytbl.id = #mytemp.id AND #mytbl.cost <> #mytemp.max_cost

select * from #mytbl
end

execute sp_test
返回：
id(int) cost(int) des Autoid(id)
--------------------------------------
1 20 cccc 3
2 100 bbbb 5
3 500 dddd 7
--------------------------------------

以上是我在SQL 2000下调是通过的

点击查看用户来源及管理<br>发贴IP：61.48.16.203 2003-5-1 17:38:50
编辑这个贴子 鲜花(0) 鸡蛋(0)
chshanghai 帅哥哟，离线，有人找我吗？

等级：新手上路
文章：1
积分：3
注册：2003-5-6
搜索chshanghai在『数 据 库』的所有贴子 第2楼
发贴心情
其实 一句话就可以啊 为什么要这么烦,而且效率又不高
select * from t_test a,
( SELECT id, MAX(cost) AS max_cost
INTO #mytemp
FROM t_test
GROUP BY id
HAVING (COUNT(*) > 1) ) b
where a.id=b.id and a.cost=b.max_cost


sql server的cube操作符使用详解
cube操作符
要使用cube，首先要了解group by
其实cube和rollup区别不太大，只是在基于group by 子句创建和汇总分组的可能的组合上有一定差别，
cube将返回的更多的可能组合。如果在 group by 子句中有n个列或者是有n个表达式的话，
sqlserver在结果集上会返回2的n－1次幂个可能组合。
注意：
使用cube操作符时，最多可以有10个分组表达式
在cube中不能使用all关键字
例子：
我们在数据库统计中常常要查询以下情况：
如一个定单数据库，我们要知道每个定单的每个产品数量，每个定单的所有产品数量，所有定单的某一产品数量，所有定单所有产品总量这些汇总信息。这时使用cube就十分方便了。当然不需要这么多信息或者只想知道某一具体产品、具体某一定单，某一时间关系（前，后，之间）等等具体信息的话，只需在where中限定即可
先举一个例子，是所有情况的：
一个数据库表中记载了一个产品定购情况：
现共有三种产品(1,2,3)，已经下了两个定单(1,2)
sql语句：
select productid,orderid SUM(quantity) AS total FROM order GROUP BY productid,orderid WITH CUBE
ORDER BY productid,orderid
运行后得到结果：
productid orderid total
null null 95 所有定单所有产品总量
null 1 30 定单1所有产品数量
null 2 65 定单2所有产品数量
1 null 15 所有定单产品1总量
1 1 5 定单1产品1数量
1 2 10 定单2产品1数量
2 null 35 所有定单产品2总量
2 1 10 定单1产品2数量
2 2 25 定单2产品2数量
3 null 45 所有定单产品3总量
3 1 15 定单1产品3数量
3 2 30 定单2产品3数量

如果您对sqlserver group by 聚集有一定理解的话，您就可以理解cube操作符的用法和作用。其实在现实运用中cube还是很好有的，我们经常要对一些数据库数据进行统计，以利于我们更好的掌握情况
我想电子商务可以更好的实现合理配置资源，尽量减少库存，只有更好的掌握生产、销售数据的具体情况，才能实现资源的合理配置。希望以后的企业象dell一样，不要象长虹等彩电厂商。


SELECT TOP N问题(2)(fang99)
Hi,everyone:

今天继续讨论SELECT TOP N问题.
1. DB2的M 2
在前一篇讨论中提到,由于DB2的SUBQUERY不支持ORDER BY子句,因此无法构造M2. 实际上,DB2提供了一组新的函数,称为 OLAP FUNCTIONS, 可以很好的支持TOP N问题.
M2:
SELECT * FROM
( SELECT MYKEY,RANK() OVER (ORDER BY ORD_COL DESC) AS MYRANK
FROM MYTABLE ) AS RANKED_TABLE
WHERE MYRANK<=N

利用OLAP函数,除了上一篇中所提方法,DB2的M1又可以表示为:
SELECT * FROM
( SELECT MYKEY,ROW_NUMBER() OVER (ORDER BY ORD_COL DESC) AS MYRANK
FROM MYTABLE ) AS RANKED_TABLE
WHERE MYRANK<=N

2. 除了M1, M2两种TOP N外,还有一种被DB2称为 DENSE_RANK, (M3). 通过下例分别说明.
M1: TOP 3 -- 1(4,101), 2(5,101), 3(1, 100) 或者1(4,101), 2(5,101), 3(2, 100)
M2: TOP 3 -- 1(4,101), 1(5,101), 3(1, 100), 3(2, 100)
M3: TOP 3 -- 1(4,101), 1(5,101), 2(1, 100), 2(2, 100), 3(3,99)
在DB2中, M3如此实现:
SELECT * FROM
( SELECT MYKEY,DENSE_RANK() OVER (ORDER BY ORD_COL DESC) AS MYRANK
FROM MYTABLE ) AS RANKED_TABLE
WHERE MYRANK<=N

3. 为避免DBMSs的方言问题, M2,M3可以有如下标准(SQL 92)表示:
M2:
SELECT * FROM MYTABLE M1 WHERE N>
( SELECT COUNT(*) FROM MYTABLE M2
WHERE M2.ORD_COL>M1.ORD_COL);
注意: 两个比较符都是 >
M3:
SELECT * FROM MYTABLE M1 WHERE N>
( SELECT COUNT(DISTINCT M2.ORD_COL) FROM MYTABLE M2
WHERE M2.ORD_COL>M1.ORD_COL);
注意: 与M2的不同在于DISTINCT的使用
至于M1, 我目前还没有想到合适的标准写法,请补充.

4. 关于MYSQL
相对于各主流RDBMS来说,MYSQL要简单,幼稚地多. 在运行MYSQL时发现居然不支持SUBQUERY. 因此MYSQL中的TOP N问题要复杂一些.
SELECT * FROM MYTABLE, MYTABLE M2
WHERE M2.ORD_COL>=M1.ORD_COL
GROUP BY M1.MYKEY
HAVING COUNT(M2.ORD_COL) <=N
但是,这个语句是有问题的. 一个极端的例子: 当所有的ORD_COL的数值都一样时. 如果哪位想到了如何解决这个问题,请补充.

----------------------------------------------------------------------------------
各语句已经测试过, EXAMPLE:
create table mytable(mykey int, ord_col int);
insert into mytable values(1, 100);
insert into mytable values(2, 100);
insert into mytable values(3, 99);
insert into mytable values(4, 101);
insert into mytable values(5, 101);

fang 6/26/2000


SELECT TOP N 问题(1)(fang99)
Hi, everyone:
本贴讨论SELECT TOP N问题.
现在正在一家计算机公司兼职,开发ASP应用. 在最近的一个商业项目里有这样一个需求:用户在查询时,只需要按照某(些)列排序后TOP 20的纪录. SQL SERVER 7很好地支持了TOP N 操作,但考虑到系统移植问题, 我又考虑在其他几个主流DBMSs中如何实现. 鉴于只有DB2 UDB 7,ORACLE 8i,SQL SERVER 7,本贴仅讨论这三个DBMS.
简单地说,TOP N问题就是:在SELECT中,仅选择按照某(些)列排序后TOP N的纪录. 考虑到等值问题,又可以分为两种: 一是仅仅返回N条纪录(M 1), 二是还包括所有于第N条等值的纪录(M 2). 当然最内层的子查询也可以有其他的子句, 或者TOP N也可以应用在没有ORDER BY的情况下,这样更简单.

1. SQL SERVER 7: 用 TOP N (WITH TIES)
M1:
SELECT TOP N * FROM MYTABLE ORDER BY ORD_COL;

M2:
SELECT TOP N WITH TIES * FROM MYTABLE ORDER BY ORD_COL;
注: SQL SERVER 7提供了PERCENT N WITH TIES, ACCESS 中提供了TOP N,但含义是M 2.

2. ORACLE 8i: 用 ROWNUM<=N
M1:
SELECT * FROM
( SELECT * FROM MYTABLE ORDER BY ORD_COL DESC)
WHERE ROWNUM<=N
M2:
SELECT * FROM MYTABLE WHERE ORD_COL>=
(SELECT MIN(ORD_COL) FROM
( SELECT * FROM MYTABLE ORDER BY ORD_COL DESC)
WHERE ROWNUM<=N)
ORDER BY ORD_COL DESC
注意以下两种错误用法:
WRONG 1:
SELECT * FROM MYTABLE
WHERE ROWID<=N
ORDER BY ORD_COL DESC;

WRONG 2:(因为WHERE ROWNUM<=N 在ORDER BY 前执行)
SELECT * FROM MYTABLE
WHERE ROWNUM<=N
ORDER BY ORD_COL DESC;

3: DB2
用FETCH FIRST N ROWS ONLY
M1:
SELECT * FROM MYTABLE
ORDER BY ORD_COL DESC
FETCH FIRST N ROWS ONLY
M2:
没有找到,因为DB2不允许在FROM中嵌套有ORDER BY子句的子查询.

还不清楚ORACLE的M 2有没有更好的办法,以及其他的DBMS如何实现TOP N操作,请其他朋友补充.
----------------------------------------------------------------------------------
各语句已经测试过, EXAMPLE:
create table mytable(mykey int, ord_col int);
insert into mytable values(1, 100);
insert into mytable values(2, 100);
insert into mytable values(3, 99);
insert into mytable values(4, 101);
insert into mytable values(5, 101);

fang 6/23/2000


 MD5算法的T-SQL实现(2)(_rambo)
IF EXISTS(SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[MD5_ConvertToWordArray]') AND xtype IN(N'FN', N'IF', N'TF'))
DROP FUNCTION [dbo].[MD5_ConvertToWordArray]
GO
/*****************************************************************************
* Name: MD5_ConvertToWordArray
* Description: MD5_ConvertToWordArray
*****************************************************************************/
CREATE FUNCTION dbo.MD5_ConvertToWordArray(
@sOrigMess VARCHAR(8000) = ''
)
RETURNS @tWordArray TABLE([ID] INT IDENTITY(0,1),[Word] INT)
WITH ENCRYPTION
AS
BEGIN
IF @sOrigMess IS NULL
SET @sOrigMess = ''

DECLARE @iLenOfMess INT DECLARE @iWordArrayLen INT
DECLARE @iPosOfWord INT DECLARE @iPosOfMess INT
DECLARE @iCountOfWord INT

SET @iLenOfMess = LEN(@sOrigMess)
SET @iWordArrayLen = ((@iLenOfMess + 8)/64 + 1) * 16
SET @iCountOfWord = 0
WHILE(@iCountOfWord<@iWordArrayLen)
BEGIN
INSERT INTO @tWordArray([Word]) valueS(0)
SET @iCountOfWord = @iCountOfWord + 1
END

SELECT @iPosOfMess = 0, @iPosOfWord = 0, @iCountOfWord = 0
WHILE(@iPosOfMess < @iLenOfMess)
BEGIN
SELECT @iCountOfWord = @iPosOfMess / 4, @iPosOfWord = @iPosOfMess % 4
UPDATE @tWordArray
SET [Word] = [Word] | dbo.MD5_LShift(UNICODE(SUBSTRING(@sOrigMess,@iPosOfMess+1,1)),@iPosOfWord*8)
WHERE [ID] = @iCountOfWord
SET @iPosOfMess = @iPosOfMess + 1
END

SELECT @iCountOfWord = @iPosOfMess / 4, @iPosOfWord = @iPosOfMess % 4
UPDATE @tWordArray
SET [Word] = [Word] | dbo.MD5_LShift(0x80,@iPosOfWord*8)
WHERE [ID] = @iCountOfWord

UPDATE @tWordArray
SET [Word] = [Word] | dbo.MD5_LShift(@iLenOfMess,3)
WHERE [ID] = @iWordArrayLen - 2
UPDATE @tWordArray
SET [Word] = [Word] | dbo.MD5_RShift(@iLenOfMess,29)
WHERE [ID] = @iWordArrayLen - 1
RETURN
END
GO

IF EXISTS(SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[MD5_WordToHex]') AND xtype IN(N'FN', N'IF', N'TF'))
DROP FUNCTION [dbo].[MD5_WordToHex]
GO
/*****************************************************************************
* Name: MD5_WordToHex
* Description: MD5_WordToHex
*****************************************************************************/
CREATE FUNCTION dbo.MD5_WordToHex(
@ivalue INT
)
RETURNS CHAR(8)
WITH ENCRYPTION
AS
BEGIN
DECLARE @sRes VARCHAR(8)
DECLARE @iTmp INT
DECLARE @iCount TINYINT

SELECT @sRes = '', @iCount = 0
WHILE(@iCount<4)
BEGIN
SET @iTmp = dbo.MD5_RShift(@ivalue,@iCount*8) & 0x000000FF
SET @sRes = @sRes + CASE @iTmp / 16 WHEN 0 THEN '0'
WHEN 1 THEN '1' WHEN 2 then '2' WHEN 3 THEN '3'
WHEN 4 THEN '4' WHEN 5 THEN '5' WHEN 6 THEN '6'
WHEN 7 THEN '7' WHEN 8 THEN '8' WHEN 9 THEN '9'
WHEN 10 THEN 'A' WHEN 11 THEN 'B' WHEN 12 THEN 'C'
WHEN 13 THEN 'D' WHEN 14 THEN 'E' WHEN 15 THEN 'F'
ELSE '' END
+ CASE @iTmp % 16 WHEN 0 THEN '0'
WHEN 1 THEN '1' WHEN 2 THEN '2' WHEN 3 THEN '3'
WHEN 4 THEN '4' WHEN 5 THEN '5' WHEN 6 THEN '6'
WHEN 7 THEN '7' WHEN 8 THEN '8' WHEN 9 THEN '9'
WHEN 10 THEN 'A' WHEN 11 THEN 'B' WHEN 12 THEN 'C'
WHEN 13 THEN 'D' WHEN 14 THEN 'E' WHEN 15 THEN 'F'
ELSE '' END
SET @iCount = @iCount + 1
END
RETURN(@sRes)
END
GO

IF EXISTS(SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[MD5]') AND xtype IN(N'FN', N'IF', N'TF'))
DROP FUNCTION [dbo].[MD5]
GO
/*****************************************************************************
* Name: MD5
* Description: MD5
*****************************************************************************/
CREATE FUNCTION dbo.MD5(
@sOrigMess NVARCHAR(4000)
)
RETURNS CHAR(32)
WITH ENCRYPTION
AS
BEGIN
--====================================
DECLARE @S11 TINYINT DECLARE @S12 TINYINT
DECLARE @S13 TINYINT DECLARE @S14 TINYINT
DECLARE @S21 TINYINT DECLARE @S22 TINYINT
DECLARE @S23 TINYINT DECLARE @S24 TINYINT
DECLARE @S31 TINYINT DECLARE @S32 TINYINT
DECLARE @S33 TINYINT DECLARE @S34 TINYINT
DECLARE @S41 TINYINT DECLARE @S42 TINYINT
DECLARE @S43 TINYINT DECLARE @S44 TINYINT

SELECT @S11 = 7, @S12 = 12, @S13 = 17, @S14 = 22
SELECT @S21 = 5, @S22 = 9, @S23 = 14, @S24 = 20
SELECT @S31 = 4, @S32 = 11, @S33 = 16, @S34 = 23
SELECT @S41 = 6, @S42 = 10, @S43 = 15, @S44 = 21
--====================================
DECLARE @a INT DECLARE @b INT
DECLARE @c INT DECLARE @d INT
DECLARE @AA INT DECLARE @BB INT
DECLARE @CC INT DECLARE @DD INT

SELECT @a = 0x67452301
,@b = 0xEFCDAB89
,@c = 0x98BADCFE
,@d = 0x10325476
--====================================
DECLARE @sRes VARCHAR(32)
SET @sRes = ''
DECLARE @iWordArrayLen INT
DECLARE @iWordArrayCount INT

DECLARE @tTmp TABLE([ID] INT, [Word] INT)
INSERT INTO @tTmp SELECT * FROM dbo.MD5_ConvertToWordArray(@sOrigMess)
SELECT @iWordArrayCount=0, @iWordArrayLen = COUNT(*) FROM @tTmp

WHILE(@iWordArrayCount < @iWordArrayLen)
BEGIN
SELECT @AA = @a, @BB = @b, @CC = @c, @DD = @d

SELECT @a = dbo.MD5_FF(@a, @b, @c, @d, (SELECT [Word] FROM @tTmp WHERE [ID] = @iWordArrayCount + 0), @S11, 0xD76AA478)
SELECT @d = dbo.MD5_FF(@d, @a, @b, @c, (SELECT [Word] FROM @tTmp WHERE [ID] = @iWordArrayCount + 1), @S12, 0xE8C7B756)
SELECT @c = dbo.MD5_FF(@c, @d, @a, @b, (SELECT [Word] FROM @tTmp WHERE [ID] = @iWordArrayCount + 2), @S13, 0x242070DB)
SELECT @b = dbo.MD5_FF(@b, @c, @d, @a, (SELECT [Word] FROM @tTmp WHERE [ID] = @iWordArrayCount + 3), @S14, 0xC1BDCEEE)
SELECT @a = dbo.MD5_FF(@a, @b, @c, @d, (SELECT [Word] FROM @tTmp WHERE [ID] = @iWordArrayCount + 4), @S11, 0xF57C0FAF)
SELECT @d = dbo.MD5_FF(@d, @a, @b, @c, (SELECT [Word] FROM @tTmp WHERE [ID] = @iWordArrayCount + 5), @S12, 0x4787C62A)
SELECT @c = dbo.MD5_FF(@c, @d, @a, @b, (SELECT [Word] FROM @tTmp WHERE [ID] = @iWordArrayCount + 6), @S13, 0xA8304613)
SELECT @b = dbo.MD5_FF(@b, @c, @d, @a, (SELECT [Word] FROM @tTmp WHERE [ID] = @iWordArrayCount + 7), @S14, 0xFD469501)
SELECT @a = dbo.MD5_FF(@a, @b, @c, @d, (SELECT [Word] FROM @tTmp WHERE [ID] = @iWordArrayCount + 8), @S11, 0x698098D8)
SELECT @d = dbo.MD5_FF(@d, @a, @b, @c, (SELECT [Word] FROM @tTmp WHERE [ID] = @iWordArrayCount + 9), @S12, 0x8B44F7AF)
SELECT @c = dbo.MD5_FF(@c, @d, @a, @b, (SELECT [Word] FROM @tTmp WHERE [ID] = @iWordArrayCount + 10), @S13, 0xFFFF5BB1)
SELECT @b = dbo.MD5_FF(@b, @c, @d, @a, (SELECT [Word] FROM @tTmp WHERE [ID] = @iWordArrayCount + 11), @S14, 0x895CD7BE)
SELECT @a = dbo.MD5_FF(@a, @b, @c, @d, (SELECT [Word] FROM @tTmp WHERE [ID] = @iWordArrayCount + 12), @S11, 0x6B901122)
SELECT @d = dbo.MD5_FF(@d, @a, @b, @c, (SELECT [Word] FROM @tTmp WHERE [ID] = @iWordArrayCount + 13), @S12, 0xFD987193)
SELECT @c = dbo.MD5_FF(@c, @d, @a, @b, (SELECT [Word] FROM @tTmp WHERE [ID] = @iWordArrayCount + 14), @S13, 0xA679438E)
SELECT @b = dbo.MD5_FF(@b, @c, @d, @a, (SELECT [Word] FROM @tTmp WHERE [ID] = @iWordArrayCount + 15), @S14, 0x49B40821)

SELECT @a = dbo.MD5_GG(@a, @b, @c, @d, (SELECT [Word] FROM @tTmp WHERE [ID] = @iWordArrayCount + 1), @S21, 0xF61E2562)
SELECT @d = dbo.MD5_GG(@d, @a, @b, @c, (SELECT [Word] FROM @tTmp WHERE [ID] = @iWordArrayCount + 6), @S22, 0xC040B340)
SELECT @c = dbo.MD5_GG(@c, @d, @a, @b, (SELECT [Word] FROM @tTmp WHERE [ID] = @iWordArrayCount + 11), @S23, 0x265E5A51)
SELECT @b = dbo.MD5_GG(@b, @c, @d, @a, (SELECT [Word] FROM @tTmp WHERE [ID] = @iWordArrayCount + 0), @S24, 0xE9B6C7AA)
SELECT @a = dbo.MD5_GG(@a, @b, @c, @d, (SELECT [Word] FROM @tTmp WHERE [ID] = @iWordArrayCount + 5), @S21, 0xD62F105D)
SELECT @d = dbo.MD5_GG(@d, @a, @b, @c, (SELECT [Word] FROM @tTmp WHERE [ID] = @iWordArrayCount + 10), @S22, 0x2441453)
SELECT @c = dbo.MD5_GG(@c, @d, @a, @b, (SELECT [Word] FROM @tTmp WHERE [ID] = @iWordArrayCount + 15), @S23, 0xD8A1E681)
SELECT @b = dbo.MD5_GG(@b, @c, @d, @a, (SELECT [Word] FROM @tTmp WHERE [ID] = @iWordArrayCount + 4), @S24, 0xE7D3FBC8)
SELECT @a = dbo.MD5_GG(@a, @b, @c, @d, (SELECT [Word] FROM @tTmp WHERE [ID] = @iWordArrayCount + 9), @S21, 0x21E1CDE6)
SELECT @d = dbo.MD5_GG(@d, @a, @b, @c, (SELECT [Word] FROM @tTmp WHERE [ID] = @iWordArrayCount + 14), @S22, 0xC33707D6)
SELECT @c = dbo.MD5_GG(@c, @d, @a, @b, (SELECT [Word] FROM @tTmp WHERE [ID] = @iWordArrayCount + 3), @S23, 0xF4D50D87)
SELECT @b = dbo.MD5_GG(@b, @c, @d, @a, (SELECT [Word] FROM @tTmp WHERE [ID] = @iWordArrayCount + 8), @S24, 0x455A14ED)
SELECT @a = dbo.MD5_GG(@a, @b, @c, @d, (SELECT [Word] FROM @tTmp WHERE [ID] = @iWordArrayCount + 13), @S21, 0xA9E3E905)
SELECT @d = dbo.MD5_GG(@d, @a, @b, @c, (SELECT [Word] FROM @tTmp WHERE [ID] = @iWordArrayCount + 2), @S22, 0xFCEFA3F8)
SELECT @c = dbo.MD5_GG(@c, @d, @a, @b, (SELECT [Word] FROM @tTmp WHERE [ID] = @iWordArrayCount + 7), @S23, 0x676F02D9)
SELECT @b = dbo.MD5_GG(@b, @c, @d, @a, (SELECT [Word] FROM @tTmp WHERE [ID] = @iWordArrayCount + 12), @S24, 0x8D2A4C8A)

SELECT @a = dbo.MD5_HH(@a, @b, @c, @d, (SELECT [Word] FROM @tTmp WHERE [ID] = @iWordArrayCount + 5), @S31, 0xFFFA3942)
SELECT @d = dbo.MD5_HH(@d, @a, @b, @c, (SELECT [Word] FROM @tTmp WHERE [ID] = @iWordArrayCount + 8), @S32, 0x8771F681)
SELECT @c = dbo.MD5_HH(@c, @d, @a, @b, (SELECT [Word] FROM @tTmp WHERE [ID] = @iWordArrayCount + 11), @S33, 0x6D9D6122)
SELECT @b = dbo.MD5_HH(@b, @c, @d, @a, (SELECT [Word] FROM @tTmp WHERE [ID] = @iWordArrayCount + 14), @S34, 0xFDE5380C)
SELECT @a = dbo.MD5_HH(@a, @b, @c, @d, (SELECT [Word] FROM @tTmp WHERE [ID] = @iWordArrayCount + 1), @S31, 0xA4BEEA44)
SELECT @d = dbo.MD5_HH(@d, @a, @b, @c, (SELECT [Word] FROM @tTmp WHERE [ID] = @iWordArrayCount + 4), @S32, 0x4BDECFA9)
SELECT @c = dbo.MD5_HH(@c, @d, @a, @b, (SELECT [Word] FROM @tTmp WHERE [ID] = @iWordArrayCount + 7), @S33, 0xF6BB4B60)
SELECT @b = dbo.MD5_HH(@b, @c, @d, @a, (SELECT [Word] FROM @tTmp WHERE [ID] = @iWordArrayCount + 10), @S34, 0xBEBFBC70)
SELECT @a = dbo.MD5_HH(@a, @b, @c, @d, (SELECT [Word] FROM @tTmp WHERE [ID] = @iWordArrayCount + 13), @S31, 0x289B7EC6)
SELECT @d = dbo.MD5_HH(@d, @a, @b, @c, (SELECT [Word] FROM @tTmp WHERE [ID] = @iWordArrayCount + 0), @S32, 0xEAA127FA)
SELECT @c = dbo.MD5_HH(@c, @d, @a, @b, (SELECT [Word] FROM @tTmp WHERE [ID] = @iWordArrayCount + 3), @S33, 0xD4EF3085)
SELECT @b = dbo.MD5_HH(@b, @c, @d, @a, (SELECT [Word] FROM @tTmp WHERE [ID] = @iWordArrayCount + 6), @S34, 0x4881D05)
SELECT @a = dbo.MD5_HH(@a, @b, @c, @d, (SELECT [Word] FROM @tTmp WHERE [ID] = @iWordArrayCount + 9), @S31, 0xD9D4D039)
SELECT @d = dbo.MD5_HH(@d, @a, @b, @c, (SELECT [Word] FROM @tTmp WHERE [ID] = @iWordArrayCount + 12), @S32, 0xE6DB99E5)
SELECT @c = dbo.MD5_HH(@c, @d, @a, @b, (SELECT [Word] FROM @tTmp WHERE [ID] = @iWordArrayCount + 15), @S33, 0x1FA27CF8)
SELECT @b = dbo.MD5_HH(@b, @c, @d, @a, (SELECT [Word] FROM @tTmp WHERE [ID] = @iWordArrayCount + 2), @S34, 0xC4AC5665)

SELECT @a = dbo.MD5_II(@a, @b, @c, @d, (SELECT [Word] FROM @tTmp WHERE [ID] = @iWordArrayCount + 0), @S41, 0xF4292244)
SELECT @d = dbo.MD5_II(@d, @a, @b, @c, (SELECT [Word] FROM @tTmp WHERE [ID] = @iWordArrayCount + 7), @S42, 0x432AFF97)
SELECT @c = dbo.MD5_II(@c, @d, @a, @b, (SELECT [Word] FROM @tTmp WHERE [ID] = @iWordArrayCount + 14), @S43, 0xAB9423A7)
SELECT @b = dbo.MD5_II(@b, @c, @d, @a, (SELECT [Word] FROM @tTmp WHERE [ID] = @iWordArrayCount + 5), @S44, 0xFC93A039)
SELECT @a = dbo.MD5_II(@a, @b, @c, @d, (SELECT [Word] FROM @tTmp WHERE [ID] = @iWordArrayCount + 12), @S41, 0x655B59C3)
SELECT @d = dbo.MD5_II(@d, @a, @b, @c, (SELECT [Word] FROM @tTmp WHERE [ID] = @iWordArrayCount + 3), @S42, 0x8F0CCC92)
SELECT @c = dbo.MD5_II(@c, @d, @a, @b, (SELECT [Word] FROM @tTmp WHERE [ID] = @iWordArrayCount + 10), @S43, 0xFFEFF47D)
SELECT @b = dbo.MD5_II(@b, @c, @d, @a, (SELECT [Word] FROM @tTmp WHERE [ID] = @iWordArrayCount + 1), @S44, 0x85845DD1)
SELECT @a = dbo.MD5_II(@a, @b, @c, @d, (SELECT [Word] FROM @tTmp WHERE [ID] = @iWordArrayCount + 8), @S41, 0x6FA87E4F)
SELECT @d = dbo.MD5_II(@d, @a, @b, @c, (SELECT [Word] FROM @tTmp WHERE [ID] = @iWordArrayCount + 15), @S42, 0xFE2CE6E0)
SELECT @c = dbo.MD5_II(@c, @d, @a, @b, (SELECT [Word] FROM @tTmp WHERE [ID] = @iWordArrayCount + 6), @S43, 0xA3014314)
SELECT @b = dbo.MD5_II(@b, @c, @d, @a, (SELECT [Word] FROM @tTmp WHERE [ID] = @iWordArrayCount + 13), @S44, 0x4E0811A1)
SELECT @a = dbo.MD5_II(@a, @b, @c, @d, (SELECT [Word] FROM @tTmp WHERE [ID] = @iWordArrayCount + 4), @S41, 0xF7537E82)
SELECT @d = dbo.MD5_II(@d, @a, @b, @c, (SELECT [Word] FROM @tTmp WHERE [ID] = @iWordArrayCount + 11), @S42, 0xBD3AF235)
SELECT @c = dbo.MD5_II(@c, @d, @a, @b, (SELECT [Word] FROM @tTmp WHERE [ID] = @iWordArrayCount + 2), @S43, 0x2AD7D2BB)
SELECT @b = dbo.MD5_II(@b, @c, @d, @a, (SELECT [Word] FROM @tTmp WHERE [ID] = @iWordArrayCount + 9), @S44, 0xEB86D391)

SET @a = dbo.MD5_AddUnsigned(@a, @AA)
SET @b = dbo.MD5_AddUnsigned(@b, @BB)
SET @c = dbo.MD5_AddUnsigned(@c, @CC)
SET @d = dbo.MD5_AddUnsigned(@d, @DD)

SET @iWordArrayCount = @iWordArrayCount + 16
END

SET @sRes = dbo.MD5_WordToHex(@a) + dbo.MD5_WordToHex(@b) + dbo.MD5_WordToHex(@c) + dbo.MD5_WordToHex(@d)
SET @sRes = LOWER(@sRes)
RETURN(@sRes)
END
GO

--Test
set nocount on
select dbo.MD5('') as 'MD5('''')'
union
select 'd41d8cd98f00b204e9800998ecf8427e'

select dbo.MD5('a') as 'MD5(''a'')'
union
select '0cc175b9c0f1b6a831c399e269772661'

select dbo.MD5('abc') as 'MD5(''abc'')'
union
select '900150983cd24fb0d6963f7d28e17f72'

select dbo.MD5('message digest') as 'MD5(''message digest'')'
union
select 'f96b697d7cb7938d525a2f31aaf161d0'

select dbo.MD5('abcdefghijklmnopqrstuvwxyz') as 'MD5(''abcdefghijklmnopqrstuvwxyz'')'
union
select 'c3fcd3d76192e4007dfb496cca67e13b'

select dbo.MD5('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789') as 'MD5(''ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'')'
union
select 'd174ab98d277d9f5a5611c2c9f419d9f'

select dbo.MD5('12345678901234567890123456789012345678901234567890123456789012345678901234567890') as 'MD5(''12345678901234567890123456789012345678901234567890123456789012345678901234567890'')'
union
select '57edf4a22be3c955ac49da2e2107b67a'

select dbo.MD5('我') as 'MD5(''我'')'
union
select 'a31d0f25367ebe046897f8a939ca4a9f'


MD5算法的T-SQL实现(1)(_rambo)
*****************************************************************************
* Name: T-SQL MD5算法实现
* Author: Rambo Qian
* Create Date: 2003-04-10
* Last Modified by: Rambo Qian
* Last Update Date: 2003-04-16
* Version: V1.0.00
*****************************************************************************/
GO

IF EXISTS(SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[MD5_m_OnBits]') AND xtype IN(N'FN', N'IF', N'TF'))
DROP FUNCTION [dbo].[MD5_m_OnBits]
GO
/*****************************************************************************
* Name: MD5_m_OnBits
* Description: 常数组
*****************************************************************************/
CREATE FUNCTION dbo.MD5_m_OnBits(
@i TINYINT
)
RETURNS INT
WITH ENCRYPTION
AS
BEGIN
DECLARE @iRes INT
SELECT @iRes =
CASE @i
WHEN 0 THEN 1 -- 00000000000000000000000000000001
WHEN 1 THEN 3 -- 00000000000000000000000000000011
WHEN 2 THEN 7 -- 00000000000000000000000000000111
WHEN 3 THEN 15 -- 00000000000000000000000000001111
WHEN 4 THEN 31 -- 00000000000000000000000000011111
WHEN 5 THEN 63 -- 00000000000000000000000000111111
WHEN 6 THEN 127 -- 00000000000000000000000001111111
WHEN 7 THEN 255 -- 00000000000000000000000011111111
WHEN 8 THEN 511 -- 00000000000000000000000111111111
WHEN 9 THEN 1023 -- 00000000000000000000001111111111
WHEN 10 THEN 2047 -- 00000000000000000000011111111111
WHEN 11 THEN 4095 -- 00000000000000000000111111111111
WHEN 12 THEN 8191 -- 00000000000000000001111111111111
WHEN 13 THEN 16383 -- 00000000000000000011111111111111
WHEN 14 THEN 32767 -- 00000000000000000111111111111111
WHEN 15 THEN 65535 -- 00000000000000001111111111111111
WHEN 16 THEN 131071 -- 00000000000000011111111111111111
WHEN 17 THEN 262143 -- 00000000000000111111111111111111
WHEN 18 THEN 524287 -- 00000000000001111111111111111111
WHEN 19 THEN 1048575 -- 00000000000011111111111111111111
WHEN 20 THEN 2097151 -- 00000000000111111111111111111111
WHEN 21 THEN 4194303 -- 00000000001111111111111111111111
WHEN 22 THEN 8388607 -- 00000000011111111111111111111111
WHEN 23 THEN 16777215 -- 00000000111111111111111111111111
WHEN 24 THEN 33554431 -- 00000001111111111111111111111111
WHEN 25 THEN 67108863 -- 00000011111111111111111111111111
WHEN 26 THEN 134217727 -- 00000111111111111111111111111111
WHEN 27 THEN 268435455 -- 00001111111111111111111111111111
WHEN 28 THEN 536870911 -- 00011111111111111111111111111111
WHEN 29 THEN 1073741823 -- 00111111111111111111111111111111
WHEN 30 THEN 2147483647 -- 01111111111111111111111111111111
ELSE 0
END
RETURN(@iRes)
END
GO

IF EXISTS(SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[MD5_m_2Power]') AND xtype IN(N'FN', N'IF', N'TF'))
DROP FUNCTION [dbo].[MD5_m_2Power]
GO
/*****************************************************************************
* Name: MD5_m_2Power
* Description: 常数组
*****************************************************************************/
CREATE FUNCTION dbo.MD5_m_2Power(
@i TINYINT
)
RETURNS INT
WITH ENCRYPTION
AS
BEGIN
DECLARE @iRes INT
SELECT @iRes =
CASE @i
WHEN 0 THEN 1 -- 00000000000000000000000000000001
WHEN 1 THEN 2 -- 00000000000000000000000000000010
WHEN 2 THEN 4 -- 00000000000000000000000000000100
WHEN 3 THEN 8 -- 00000000000000000000000000001000
WHEN 4 THEN 16 -- 00000000000000000000000000010000
WHEN 5 THEN 32 -- 00000000000000000000000000100000
WHEN 6 THEN 64 -- 00000000000000000000000001000000
WHEN 7 THEN 128 -- 00000000000000000000000010000000
WHEN 8 THEN 256 -- 00000000000000000000000100000000
WHEN 9 THEN 512 -- 00000000000000000000001000000000
WHEN 10 THEN 1024 -- 00000000000000000000010000000000
WHEN 11 THEN 2048 -- 00000000000000000000100000000000
WHEN 12 THEN 4096 -- 00000000000000000001000000000000
WHEN 13 THEN 8192 -- 00000000000000000010000000000000
WHEN 14 THEN 16384 -- 00000000000000000100000000000000
WHEN 15 THEN 32768 -- 00000000000000001000000000000000
WHEN 16 THEN 65536 -- 00000000000000010000000000000000
WHEN 17 THEN 131072 -- 00000000000000100000000000000000
WHEN 18 THEN 262144 -- 00000000000001000000000000000000
WHEN 19 THEN 524288 -- 00000000000010000000000000000000
WHEN 20 THEN 1048576 -- 00000000000100000000000000000000
WHEN 21 THEN 2097152 -- 00000000001000000000000000000000
WHEN 22 THEN 4194304 -- 00000000010000000000000000000000
WHEN 23 THEN 8388608 -- 00000000100000000000000000000000
WHEN 24 THEN 16777216 -- 00000001000000000000000000000000
WHEN 25 THEN 33554432 -- 00000010000000000000000000000000
WHEN 26 THEN 67108864 -- 00000100000000000000000000000000
WHEN 27 THEN 134217728 -- 00001000000000000000000000000000
WHEN 28 THEN 268435456 -- 00010000000000000000000000000000
WHEN 29 THEN 536870912 -- 00100000000000000000000000000000
WHEN 30 THEN 1073741824 -- 01000000000000000000000000000000
ELSE 0
END
RETURN(@iRes)
END
GO

IF EXISTS(SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[MD5_LShift]') AND xtype IN(N'FN', N'IF', N'TF'))
DROP FUNCTION [dbo].[MD5_LShift]
GO
/*****************************************************************************
* Name: MD5_LShift
* Description: MD5_LShift
*****************************************************************************/
CREATE FUNCTION dbo.MD5_LShift(
@ivalue INT
,@iShiftBits TINYINT
)
RETURNS INT
WITH ENCRYPTION
AS
BEGIN
DECLARE @iRes BIGINT
SET @iRes = CAST(@ivalue AS BINARY(8))
SET @iRes = @iRes * dbo.MD5_m_2Power(@iShiftBits)
RETURN(CAST(@iRes & 0x00000000FFFFFFFF AS BINARY(4)))
END
GO

IF EXISTS(SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[MD5_RShift]') AND xtype IN(N'FN', N'IF', N'TF'))
DROP FUNCTION [dbo].[MD5_RShift]
GO
/*****************************************************************************
* Name: MD5_RShift
* Description: MD5_RShift
*****************************************************************************/
CREATE FUNCTION dbo.MD5_RShift(
@ivalue INT
,@iShiftBits TINYINT
)
RETURNS INT
WITH ENCRYPTION
AS
BEGIN
DECLARE @iRes BIGINT
SET @iRes = CAST(@ivalue AS BINARY(8))
SET @iRes = @iRes / dbo.MD5_m_2Power(@iShiftBits)
RETURN(CAST(@iRes & 0x00000000FFFFFFFF AS BINARY(4)))
END
GO

IF EXISTS(SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[MD5_RotateLeft]') AND xtype IN(N'FN', N'IF', N'TF'))
DROP FUNCTION [dbo].[MD5_RotateLeft]
GO
/*****************************************************************************
* Name: MD5_RotateLeft
* Description: MD5_RotateLeft
*****************************************************************************/
CREATE FUNCTION dbo.MD5_RotateLeft(
@ivalue INT
,@iShiftBits TINYINT
)
RETURNS INT
WITH ENCRYPTION
AS
BEGIN
RETURN(dbo.MD5_LShift(@ivalue, @iShiftBits) | dbo.MD5_RShift(@ivalue, (32 - @iShiftBits)))
END
GO

IF EXISTS(SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[MD5_AddUnsigned]') AND xtype IN(N'FN', N'IF', N'TF'))
DROP FUNCTION [dbo].[MD5_AddUnsigned]
GO
/*****************************************************************************
* Name: MD5_AddUnsigned
* Description: MD5_AddUnsigned
*****************************************************************************/
CREATE FUNCTION dbo.MD5_AddUnsigned(
@iX INT
,@iY INT
)
RETURNS INT
WITH ENCRYPTION
AS
BEGIN
DECLARE @iRes BIGINT
SET @iRes = CAST(CAST(@iX AS BINARY(8)) AS BIGINT) + CAST(CAST(@iY AS BINARY(8)) AS BIGINT)
RETURN(CAST(@iRes & 0x00000000FFFFFFFF AS BINARY(4)))
END
GO

IF EXISTS(SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[MD5_F]') AND xtype IN(N'FN', N'IF', N'TF'))
DROP FUNCTION [dbo].[MD5_F]
GO
/*****************************************************************************
* Name: MD5_F
* Description: MD5_F
*****************************************************************************/
CREATE FUNCTION dbo.MD5_F(
@x INT
,@y INT
,@z INT
)
RETURNS INT
WITH ENCRYPTION
AS
BEGIN
RETURN((@x & @y) | ((~@x) & @z))
END
GO

IF EXISTS(SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[MD5_G]') AND xtype IN(N'FN', N'IF', N'TF'))
DROP FUNCTION [dbo].[MD5_G]
GO
/*****************************************************************************
* Name: MD5_G
* Description: MD5_G
*****************************************************************************/
CREATE FUNCTION dbo.MD5_G(
@x INT
,@y INT
,@z INT
)
RETURNS INT
WITH ENCRYPTION
AS
BEGIN
RETURN((@x & @z) | (@y & (~@z)))
END
GO

IF EXISTS(SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[MD5_H]') AND xtype IN(N'FN', N'IF', N'TF'))
DROP FUNCTION [dbo].[MD5_H]
GO
/*****************************************************************************
* Name: MD5_H
* Description: MD5_H
*****************************************************************************/
CREATE FUNCTION dbo.MD5_H(
@x INT
,@y INT
,@z INT
)
RETURNS INT
WITH ENCRYPTION
AS
BEGIN
RETURN(@x ^ @y ^ @z)
END
GO

IF EXISTS(SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[MD5_I]') AND xtype IN(N'FN', N'IF', N'TF'))
DROP FUNCTION [dbo].[MD5_I]
GO
/*****************************************************************************
* Name: MD5_I
* Description: MD5_I
*****************************************************************************/
CREATE FUNCTION dbo.MD5_I(
@x INT
,@y INT
,@z INT
)
RETURNS INT
WITH ENCRYPTION
AS
BEGIN
RETURN(@y ^ (@x | (~@z)))
END
GO

IF EXISTS(SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[MD5_FF]') AND xtype IN(N'FN', N'IF', N'TF'))
DROP FUNCTION [dbo].[MD5_FF]
GO
/*****************************************************************************
* Name: MD5_FF
* Description: MD5_FF
*****************************************************************************/
CREATE FUNCTION dbo.MD5_FF(
@a INT
,@b INT
,@c INT
,@d INT
,@x INT
,@s INT
,@ac INT
)
RETURNS INT
WITH ENCRYPTION
AS
BEGIN
SET @a = dbo.MD5_AddUnsigned(@a, dbo.MD5_AddUnsigned(dbo.MD5_AddUnsigned(dbo.MD5_F(@b, @c, @d), @x), @ac))
SET @a = dbo.MD5_RotateLeft(@a, @s)
SET @a = dbo.MD5_AddUnsigned(@a, @b)
RETURN(@a)
END
GO

IF EXISTS(SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[MD5_GG]') AND xtype IN(N'FN', N'IF', N'TF'))
DROP FUNCTION [dbo].[MD5_GG]
GO
/*****************************************************************************
* Name: MD5_GG
* Description: MD5_GG
*****************************************************************************/
CREATE FUNCTION dbo.MD5_GG(
@a INT
,@b INT
,@c INT
,@d INT
,@x INT
,@s INT
,@ac INT
)
RETURNS INT
WITH ENCRYPTION
AS
BEGIN
SET @a = dbo.MD5_AddUnsigned(@a, dbo.MD5_AddUnsigned(dbo.MD5_AddUnsigned(dbo.MD5_G(@b, @c, @d), @x), @ac))
SET @a = dbo.MD5_RotateLeft(@a, @s)
SET @a = dbo.MD5_AddUnsigned(@a, @b)
RETURN(@a)
END
GO

IF EXISTS(SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[MD5_HH]') AND xtype IN(N'FN', N'IF', N'TF'))
DROP FUNCTION [dbo].[MD5_HH]
GO
/*****************************************************************************
* Name: MD5_HH
* Description: MD5_HH
*****************************************************************************/
CREATE FUNCTION dbo.MD5_HH(
@a INT
,@b INT
,@c INT
,@d INT
,@x INT
,@s INT
,@ac INT
)
RETURNS INT
WITH ENCRYPTION
AS
BEGIN
SET @a = dbo.MD5_AddUnsigned(@a, dbo.MD5_AddUnsigned(dbo.MD5_AddUnsigned(dbo.MD5_H(@b, @c, @d), @x), @ac))
SET @a = dbo.MD5_RotateLeft(@a, @s)
SET @a = dbo.MD5_AddUnsigned(@a, @b)
RETURN(@a)
END
GO

IF EXISTS(SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[MD5_II]') AND xtype IN(N'FN', N'IF', N'TF'))
DROP FUNCTION [dbo].[MD5_II]
GO
/*****************************************************************************
* Name: MD5_II
* Description: MD5_II
*****************************************************************************/
CREATE FUNCTION dbo.MD5_II(
@a INT
,@b INT
,@c INT
,@d INT
,@x INT
,@s INT
,@ac INT
)
RETURNS INT
WITH ENCRYPTION
AS
BEGIN
SET @a = dbo.MD5_AddUnsigned(@a, dbo.MD5_AddUnsigned(dbo.MD5_AddUnsigned(dbo.MD5_I(@b, @c, @d), @x), @ac))
SET @a = dbo.MD5_RotateLeft(@a, @s)
SET @a = dbo.MD5_AddUnsigned(@a, @b)
RETURN(@a)
END


改变自增字段的初始值
1。identity 函数中不能用变量作参数
declare @a int
select @a=190
select identity(int,@a,1) as id,* into #temp from tablename
select * from #temp
上述不正确，可以用如下方法
declare @a int
set @a=190
//首先把结构赋值过去
select identity(int,1,1) as id,* into #temp1from tablename where 1=2
//改变初始值
dbcc checkident(#temp1,reseed,@a)
insert into #temp1 select * from tablename
select * from #temp1
2。要想让自增字段从1开始，可以
dbcc checkident(tablename,reseed,0)


数据分组
1。选前几条数据

select top 10 orderid,amt=unitprice*quantity
from ordredetails
order by amt desc
2.利用group by 子句对select命令所选数据分组。分组后可以显示出来
group by是除了不含汇总函数（sum,avg,count,min,max等以外的列)
select prod_id,sum(qty*unit_price) tot_amt
from sale_item
group by prod_id
order by tot_amt desc
3.group by 子句还可以加 with cube语句，加上with cube语句，其返回的数据除了和没有with cube语句一样外，，另外它会得到一些加总列。
select sup_id,prod_id ,sum(qty*unit_price),sum(qty) tot_amt
from sale_item
groupby sup_id,prod_id with cube
将有对应每个sup_id的加总列，每个prod_id 的加总列，和null，null全部的加总列
4.with cube语句换成with rollup ,则只返回最高层次的group列（最靠近group by的列的加总）
上面的例子，只返回sup_id的加总列，每个sup_id后有个加总列
5。使用grouping区分空值
cube操作生成的空值null带来一个问题，即如何区分cube操作生成的null值和实际数据中返回的null值。这个问题，可以用grouping函数解决。如果列中的值来自事实数据，则grouping函数返回0，如果列中的值是cube操作所生成的null，则返回1。在cube操作中，所生成的null代表全体值。可将select语句写成使用grouping函数将null替换为字符串all。
因为事实数据中的null表名数据值未知，所以select语句还可以用字符串unknow替代来自事实数据的null。
select productid,grouping(productid),orderid,grouping(orderid),sum(quantity)
from order
group by productid,orderid
with cube
order by productid,orderid
6.group by 子句经常会和having子句搭配，用来找出每个组别中满足指定条件的数据，以下命令由产品文件中找出代码重复者，也就是同一型产品有两家以上的供应商。
select prod_id,count(*) from stock
group by prod_id
having count(*)>1
order by prod_id


 触摸Null(ccat)
http://www.csdn.net/develop/read_article.asp?id=14195

我们初步见识了NULL这个不可思议的小东西。今天，我尽可能详细的介绍一下它。依照惯例，这是一次尽量浅显但并不严谨的讨论，甚至可能内容也不那么严肃。我的目的在于帮助读者更轻松地工作，并且有兴趣对数据库进一步的学习。从另一方面讲，我相信自己论点中的错误，肯定会有其他人也在犯，所以请发现不妥的朋友一定要公开指出。这样，才有助于我和我的读者朋友们进步。衷心感谢每一个提出批评和指正的朋友，特别是公开提出批评和指正的朋友们。特别感谢sunshine19，专程发来E-mail，指出了《SQL Story》 的种种不足，并提出了宝贵意见。我期望他继续关心这个专题，并期望他为我们带来优秀的作品。

无可回避的不存在

例：双人房间。

假设某个学校，它的单身职工公寓中，每个房间有两个床位。这样做有很多好处，比如安全，都是半大不小的年轻人，不管男孩女孩，有个伴一起住总让人放心些，互相也有个照应；如果有谁结了婚，由于每个房间最多住两个人，还可以比较容易地找出一间，先给他们安个家，慢慢等着分房。这些事当然不要我们程序员多操心（除非我们就住在里面）。现在要关心的是，有人做了一个公寓管理数据库。其中的房间管理表这样子设计：

CREATE TABLE ROOMS(

ROOMID CHAR(5),

MASTERID CHAR(10) ,

SECONDID CHAR(10),

CONSTRAINT PK_ROOM PRIMARY KEY(ROOMID),

CONSTRAINT FK_MASTER FOREIGN KEY (MASTERID) REFERENCES LIVERS(ID),

CONSTRAINT FK_SECOND FOREIGN KEY (SECONDID) REFERENCES LIVERS(ID)

)

简单介绍一下，管理员为了方便管理（比如收个水电费什么的），要求每一个住人的房间有一个房主；两个人更详细的信息保存在了 LIVERS表中，所以我们看到有两个外键约束。当然，这个设计并非无懈可击，不过它也自有它的理由，在这里我们先不讨论了。

这里要关注的是，房客少于两个人的房间，我们该在空出来的地方填什么？有一个方法，就是填入一个空字符串，可这样一来，就等于我们默认了存在一个ID号为空字符串的住户存在。因为空字符串也是字符串嘛。两个外键约束也说明了这一点。事实上，在类似这样的设计中，我就见过一些例子，其作者为了避免无可引用的情况，凭空造出一个不存在的信息，做为数据库结构的一部分。具体到这个例子，可能会有人真的用一个空字符串或写一个“NONE”什么的，写在LIVERS表中，表示没有人居住。通常这会引起很多问题，比如我们可能要加一个约束，使得一个人不能出现在ROOMS表中两次。可现在这个虚拟出来表示没有人的房客怎么办？可有很多位置都没有人啊，比如只要有一个空房，就会在这一条记录上有两个“NONE”！在更离奇（但在某些应用中绝非不可能）的情况下，有一个房客，他的ID就是“NONE”，如何？？而且如果极端情况下，房客全搬出去了，怎么办？想要“DELET FROM LIVERS”都不行，“NONE” 可删不掉啊。当你工作在这样一个数据库中，得时时提醒自己不要得罪了这个不存在的家伙。要记住，他没有性别，所以男女职工的房间他都要住；别人一个人只能睡一张床，他却可以要所有空闲的；别人都搬出去后，他老人家一个人占了所有的房间，我们的法律规章对他全没有意义。这种特殊值给我们带来的最大问题，就是把信息和数据库结构搅在了一起。也就是说，关系和关系的模式混为一谈了。如果一个数据库中有上几个这种表，谁都会疯的。

这就是NULL的意义，当你把一个数据定义为NULL时，就等于告诉系统，这个数据不存在，或未知。不要管它了，它的内容没有意义（当然，这本身也是一种意义）。在房间中有一个NULL，只表示没有人，而不会被认为是一个叫“没有人”的人住在里面。更不会有一个霸占了所有房间的恶客存在。要清空LIVERS，那就清吧，只要你设置了级联约束，就不会有任何离奇的事出现。唯一的后果只是一个无人居住的公寓而已，这不正是我们所要的吗？

NULL是真实存在的，尽管从某种意义上讲，我们无法解释它的存在。它是关系世界的黑洞。也许会让一些人不舒服，但回避它只会给我们带来更多的麻烦。

看不见摸不到

在《SQL-3参考大全》中，作者这样解释NULL的含义：未知或未定义。对NULL来说，有很多有趣的特性。

NULL是一个数据值，而且它属于一个域（？）。是的，例如一个字符串字段，其中的空值只能是一个字符串。尽管它的内容没有定义，或者未知，但它是字符串，这一点无可置疑。

NULL不是非法数据，这一点SQL-3 标准也说的很清楚。我们没有办法保存零分之一，但可以保存空值。虽然它是空值，是未定义，是未知，可它也的确是 一个合法的信息。

运算黑洞：对于NULL，一般的运算都会返回NULL。比如加减乘除，这简直就是一个黑洞一样。永远不会有什么数据等于NULL。当然，1不等于NULL，2也一样。可是，NULL也不等于NULL。说一个NULL等于NULL是错误的。所以我们只能比较它“是”或“不是”。为了避免混乱，SQL-3标准有一些约定。比如表达式 x=NULL，结果应当是UNKOWN 。 而表达式“x is NULL”，就得看情况，如果x是NULL或 False，就返回Ture（？）；x是非NULL，返回False。有点奇怪是不是，它基于NULL不等于NULL。 这个规则的确为SQL标准所支持，遇到这样的数据库系统，可不要感到惊讶。

三值逻辑：《鹿鼎记》中的韦小宝，整人的秘诀之一就是“我问你话，是就点头，不是就摇头，不许你出声！”的确，通常我们的逻辑观点，总是基于这种二值逻辑体系，对就是对，错就是错。可在关系理论中，没有这么简单。有一种没有绝对是非的情况存在：NULL。这就是关系理论的三值逻辑。

还有一些常见的与NULL相关的情况。比如，统计函数（也有的文档称之为聚集函数、集函数）通常会忽略NULL。不过，假设有这样一个表T：

C

-----

1

2

NULL

NULL

我们分别执行两个查询：SELECT COUNT(*) FROM T和SELECT COUNT(C) FROM T， 猜猜会有什么不同？起初，我以为两个结果应当一样。结果，前一个是4，后一个是2。前一个等于4，显然基于NULL也是数据这个事实；而后一个结果为2，是由于统计C列时，COUNT忽略了NULL。

SQL-3标准中，搜索条件（WHERE和HAVING）只接受TRUE，而约束却只拒绝FALSE，二者对NULL的接受态度相反。所以我们前面见到的ROOMS表才可以把空值写入人员字段。而在排序时，却又没什么规律。SQL标准只作出了一个补充定义，所以每个DBMS的处理 ORDER BY的方法并不相同，当然NULL不是高于所有值就是低于所有值。

在《SQL-3参考大全》中介绍了更详细的关于NULL的内容。另外，书中还介绍了两位数据库专家C.J.Date和E.f.Codd关于NULL的不同论点（E.F.Codd甚至希望有四值逻辑）有兴趣的朋友可以找来一读。在实践中，我们还会遇到一些有趣的事，特别是联接查询中， NULL让我们的人生变得 “丰富多彩”。

由于种种原因，近几天我可能会放慢写作速度，但这并不代表我会放弃。在休整和充电之后，我会回到我所擅长的实战领域。再次感谢每一位关心《SQL Story》的读者。


union (all)
利用union 将多个select 的结果结合起来.union命令将来自不同select命令所生成的数据混合在一起，并将重复的数据删除仅留下一条，也就是数学上的并集关系。
如果你想让重复的数据也留下时，可以使用union all取代原来的union命令

1.select prod_id ,sup_id
from sale_item
where order_no=10001
union
select prod_id,sup_id
from purchase
where pur_no=50001
order by 1,2
2.在sqlserver方面可以用case实现的，但在access里，只能用iif或union
select a.rmpwr,a.buypwr,yd-case when b.dl>0 then a.yd+b.rmpwr-b.bdl
else a.yd-b.yd end
from (select mid,rmpwr,buypwr,(buypwr-rmpwr)as yd from main where month(dt)=2)a,(select mid,rmpwr,buypwr,(buypwr-rmpwr)as yd,bdl from main where month(dt)=1 b where a.mid=b.mid
在本机数据库中a,b同上
select a.ydl+b.rmpwr-b.ydl as yd from a,b where a.mid=b.mid and b.bdl>0
union all
select a.yd-b.yd from a,b where a.mid=b.mid and isnull(b.bdl,-1)<=0


使用派生表
1.此示例使用派生表和紧跟 FROM 子句的 SELECT 语句，返回所有作者的姓名以及所著书的标题的书号。

USE pubs
SELECT RTRIM(a.au_fname) + '''' '''' + LTRIM(a.au_lname) AS Name, d1.title_id
FROM authors a, (SELECT title_id, au_id FROM titleauthor) AS d1
WHERE a.au_id = d1.au_id
ORDER BY a.au_lname, a.au_fname
2。相当于 case的
select distinct (ww.dates),(select price from adt1 b where id=1 and ww.dates=b.dates)as w1,(select price from adt1 c where id=2 and ww.dates=c.dates)as w2,(select price from adt1 d where id=3 and ww.dates=d.dates)as w3
from adt1 ww
order by ww.dates
3。select a.dates,a.price as w1,b.price as w2,c.price as w3
from (select * from tablename where id=1)as a,
(select * from tablename where id=2 )as b,
(select * from tablename where id=3) as c
where a.dates=b.dates and b.dates=c.dates
order by a.dates


 join 方式
（1) cross join
参与select语句所有表的的所有行的笛卡尔乘积
select au_lname ,title
from authors cross join titiles

outer join 对参与join的两个表有主从之分，处理方式以主表的每条数据去match 从属表的列，合乎条件的数据是我们所要的答案，不合乎条件的也是我们要的答案，只不过哪些从属表选取的列将被添上null。
(2) left join
左边的为主表，右边为从属表
select a.cust_id ,b.order_date,b.tot_ant
from customer a left join sales b
on (a.cust_id =b.cust_id and b.order_date>''1996/10/15'')
可以写为
select a.cust_id,b.order_date,b.tot_ant
from custom a
left join (select * from sales where order_date>''1996/10/15'') b
on a.cust_id =b.cust_id
(3) right join
左边的表为从属表，右边的表为主表
(4) self join
self join 常用在同一表内不同数据间对同一列的比较
select a.emp_no,a.emp_name,b.emp_no,b.emp_name,a.date_hired
from employee a
join employee b
on (a.emp_no!=b.emp_no and a.date_hired=b.date_hired)
order by a.date_hired
这样会重复数据，只要加上一句 and a.emp_name>b.emp_name
(5) full join
不仅列出符合条件的数据，两边未符合join条件的数据也会一并列出。哪些未符合join条件的数据如果在select列中无法得到对应的值则填上null
select a.cust_id,b.tot_amt
from customer a full join sales b
on a.cust_id=b.cust_id
有表
id ic name amount
I * *
c
i
c
i
i
要求结果为
ic name amount ic name amount
i c
i c
i
i
select aaa.*,bbb.*
from ( select (select count(id) from aa as b where (b.id<a.id) and (ic=''i'')) as newid, * from aa a where ic=''i'') aaa
full join
(select (select count(id) from aa as b where b.id<a.id and ic=''c'') as newid,* from
aa a where ic=''c'') bbb
on aaa.newid=bbb.newid
order by aaa.name
6.使用 HASH 和 MERGE 联接提示
此示例在 authors、titleauthors 和 titles 表之间建立三表联接，以生成一个作者及其著作的列表。查询优化器使用 MERGE 联接将 authors 和 titleauthors (A x TA) 联接在一起。然后，将 authors 和 titleauthors MERGE 联接 (A x TA) 的结果与 titles 表进行 HASH 联结以生成 (A x TA) x T。

重要 指定联接提示后，要执行 INNER JOIN 时 INNER 关键字不再为可选，而必须显式说明。

USE pubs
SELECT SUBSTRING((RTRIM(a.au_fname) + '' '' + LTRIM(a.au_lname)), 1, 25)
AS Name, SUBSTRING(t.title, 1, 20) AS Title
FROM authors a INNER MERGE JOIN titleauthor ta
ON a.au_id = ta.au_id INNER HASH JOIN titles t
ON t.title_id = ta.title_id
ORDER BY au_lname ASC, au_fname ASC

下面是结果集：

Warning: The join order has been enforced because a local join hint is used.
Name Title
------------------------- --------------------
Abraham Bennet The Busy Executive''s
Reginald Blotchet-Halls Fifty Years in Bucki
Cheryl Carson But Is It User Frien
Michel DeFrance The Gourmet Microwav
Innes del Castillo Silicon Valley Gastr
... ...
Johnson White Prolonged Data Depri
Akiko Yokomoto Sushi, Anyone?

(25 row(s) affected)


数据转换
CAST 和 CONVERT
将某种数据类型的表达式显式转换为另一种数据类型。CAST 和 CONVERT 提供相似的功能。

语法
使用 CAST：

CAST ( expression AS data_type )

使用 CONVERT：

CONVERT (data_type[(length)], expression [, style])
1。2222222222转换为16位的84746B8E,取后面6位746B8E，然后再转换为
7629710
select 22222222-convert(bigint,2222222222/convert(int,0x1000000)*
convert(int,0x1000000)

2。SELECT SUBSTRING(title, 1, 30) AS Title, ytd_sales
FROM titles
WHERE CAST(ytd_sales AS char(20)) LIKE ''''3%''''
3.显示时加百分号
select cast(field1/field2 as varchar(20))+''''%''''

4.SELECT CAST(ROUND(ytd_sales/price, 0) AS int) AS ''''Copies''''
FROM titles

5.select convert(type,case .......when....end)

6>密码有大小写的比较
select *
from tablename
where cast(ma as varbinary)=cast(:para as varbinary)

7.日期转换
select convert(char(10),getdate(),101) //102,121
101 04/18/2003
102 2003.04.18
121 2003-04-18


acess 技巧集（ysai)
http://www.delphibbs.com/delphibbs/dispq.asp?lid=1601151
ACCESS技巧集
作者:ysai
转载请保持文章完整并标明出处

1.DELPHI中操作ACCESS数据库(建立.mdb文件,压缩数据库)
以下代码在WIN2K,D6,MDAC2.6下测试通过,
编译好的程序在WIN98第二版无ACCESS环境下运行成功.
//声明连接字符串
Const
SConnectionString = 'Provider=Microsoft.Jet.OLEDB.4.0;Data Source=%s;'
+'Jet OLEDB:Database Password=%s;';

//=============================================================================
// Procedure: GetTempPathFileName
// Author : ysai
// Date : 2003-01-27
// Arguments: (None)
// Result : string
//=============================================================================
function GetTempPathFileName():string;
//取得临时文件名
var
SPath,Sfile:array [0..254] of char;
begin
GetTempPath(254,SPath);
GetTempFileName(SPath,'~SM',0,SFile);
result:=SFile;
DeleteFile(result);
end;

//=============================================================================
// Procedure: CreateAccessFile
// Author : ysai
// Date : 2003-01-27
// Arguments: FileName:String;PassWord:string=''
// Result : boolean
//=============================================================================
function CreateAccessFile(FileName:String;PassWord:string=''):boolean;
//建立Access文件，如果文件存在则失败
var
STempFileName:string;
vCatalog:OleVariant;
begin
STempFileName:=GetTempPathFileName;
try
vCatalog:=CreateOleObject('ADOX.Catalog');
vCatalog.Create(format(SConnectionString,[STempFileName,PassWord]));
result:=CopyFile(PChar(STempFileName),PChar(FileName),True);
DeleteFile(STempFileName);
except
result:=false;
end;
end;

//=============================================================================
// Procedure: CompactDatabase
// Author : ysai
// Date : 2003-01-27
// Arguments: AFileName,APassWord:string
// Result : boolean
//=============================================================================
function CompactDatabase(AFileName,APassWord:string):boolean;
//压缩与修复数据库,覆盖源文件
var
STempFileName:string;
vJE:OleVariant;
begin
STempFileName:=GetTempPathFileName;
try
vJE:=CreateOleObject('JRO.JetEngine');
vJE.CompactDatabase(format(SConnectionString,[AFileName,APassWord]),
format(SConnectionString,[STempFileName,APassWord]));
result:=CopyFile(PChar(STempFileName),PChar(AFileName),false);
DeleteFile(STempFileName);
except
result:=false;
end;
end;

2.ACCESS中使用SQL语句应注意的地方及几点技巧
以下SQL语句在ACCESS XP的查询中测试通过
建表:
Create Table Tab1 (
ID Counter,
Name string,
Age integer,
[Date] DateTime);
技巧:
自增字段用 Counter 声明.
字段名为关键字的字段用方括号[]括起来,数字作为字段名也可行.

建立索引:
下面的语句在Tab1的Date列上建立可重复索引
Create Index iDate ON Tab1 ([Date]);
完成后ACCESS中字段Date索引属性显示为 - 有(有重复).
下面的语句在Tab1的Name列上建立不可重复索引
Create Unique Index iName ON Tab1 (Name);
完成后ACCESS中字段Name索引属性显示为 - 有(无重复).
下面的语句删除刚才建立的两个索引
Drop Index iDate ON Tab1;
Drop Index iName ON Tab1;

ACCESS与SQLSERVER中的UPDATE语句对比:
SQLSERVER中更新多表的UPDATE语句:
UPDATE Tab1
SET a.Name = b.Name
FROM Tab1 a,Tab2 b
WHERE a.ID = b.ID;
同样功能的SQL语句在ACCESS中应该是
UPDATE Tab1 a,Tab2 b
SET a.Name = b.Name
WHERE a.ID = b.ID;
即:ACCESS中的UPDATE语句没有FROM子句,所有引用的表都列在UPDATE关键字后.
上例中如果Tab2可以不是一个表,而是一个查询,例:
UPDATE Tab1 a,(Select ID,Name From Tab2) b
SET a.Name = b.Name
WHERE a.ID = b.ID;

访问多个不同的ACCESS数据库-在SQL中使用In子句:
Select a.*,b.* From Tab1 a,Tab2 b In 'db2.mdb' Where a.ID=b.ID;
上面的SQL语句查询出当前数据库中Tab1和db2.mdb(当前文件夹中)中Tab2以ID为关联的所有记录.
缺点-外部数据库不能带密码.

在ACCESS中访问其它ODBC数据源
下例在ACCESS中查询SQLSERVER中的数据
SELECT * FROM Tab1 IN [ODBC]
[ODBC;Driver=SQL Server;UID=sa;PWD=;Server=127.0.0.1;DataBase=Demo;]
外部数据源连接属性的完整参数是:
[ODBC;DRIVER=driver;SERVER=server;DATABASE=database;UID=user;PWD=password;]
其中的DRIVER=driver可以在注册表中的
HKEY_LOCAL_MACHINE\SOFTWARE\ODBC\ODBCINST.INI\
中找到

ACCESS支持子查询

ACCESS支持外连接,但不包括完整外部联接,如支持
LEFT JOIN 或 RIGHT JOIN
但不支持
FULL OUTER JOIN 或 FULL JOIN

ACCESS中的日期查询
注意:ACCESS中的日期时间分隔符是#而不是引号
Select * From Tab1 Where [Date]>#2002-1-1#;
在DELPHI中我这样用
SQL.Add(Format(
'Select * From Tab1 Where [Date]>#%s#;',
[DateToStr(Date)]));

ACCESS中的字符串可以用双引号分隔,但SQLSERVER不认,所以为了迁移方便和兼容,
建议用单引号作为字符串分隔符


 子查询
1。select ... where 列或运算式 比较运算运算【any|all](子查询）
只要主查询中列或运算式与子查询所得结果中任一（any）或全部（all）数据符合比较条件的话则主查询的结果为我们要的数据

选出不同的人金额最高的订单
select * from sales a
where tomat=(select max(totmat) from sales where name=a.name)

select sale_id,tot_amt
from sales
where tot_amt>any(select tot_amt from sales where sale_id='e0013'and 'order_date='1996/11/10')

2。select ...where 列或运算式[not] in (子查询)
只要主查询中列或运算式是在（不在）子查询所得结果列表中的话，则主查询的结果为我们要的数据
select sales_id,tot_amt
from sales
where sale _id in(select sale_id from employee where sex='F')
3.select ...where [not] exists ( 子查询）
子查询的结果至少存在一条数据时，则主查询的结果为我们要的数据。（exists）或自查询的结果找不到数据时，则主查询的结果为我们要的数据(not exists)
我们经常查询的两个表有多少重复的记录就用这个
以下范例让你找出滞销的产品，也就是尚未有任何销售记录的库存产品。此范例主要是查询以库文件中的每一条产品代码到销售明细表中去查询，如果查询不到任何一条，表示该产品未曾卖出任何一件。
select * from stock a
where not exists(select * from sale_item b
where a.prod_id=b.prod_id and a.stup_id=b.stup_id)

select emp_no,emp_name from employee
from employee a
where (select sum(tot_amt) from sales b where a.sale_id=b.emp_no)<200000


使用约束
1.使用 primary key 约束
其值能唯一的标识表中的每一行。这样的一列或多列成为表的主键，通过它可强制表的实体完整性。

job_id int primary key clustered

emp_id empid constraint pk_emp_id primary key nonclustered
2.使用foreign key 约束
约束引用其他的表
job_id samllint not null references jobs(job_id)

foreign key(job_id) references jobs(job_id)

constraint fk_sales foreign key(stor_id,orde_num,title_id)
references sales(stor_id,ord_num,title_id)
3.使用unique 约束
unqiue约束用于强制非主键列的唯一性，允许存在空值（应该只有一个）
person varchar(30) null unique nonclustered

constraint u_store unique nonclustered(stor_name,city)
4.使用default定义
使用insert和update语句时，如果没有提供值，则使用默认值。
提供了默认值， 用dbgrid 编辑必须在onnewrecord 事件加上默认值的赋值，否则提示错误
‘row can not be located for updating.some values has been changed since it was last read '
default(getdate())
创建一个产品价格表，并且设置产品的改价者为当时增修改数据的用户
create table price
(
prod_id char(5),
sup_id char(5),
unit_price money,
modifier char(5)
modi_date datetime default getdate(),
primary key(prod_id,sup_id)
default user for modifier
)
5.使用check约束

check(min_lvl>=10)
check(max_lvl<=250)

constraint ck_emp_id check (emp_id like '[a-z][a-z][a-z][1-9][0-9][0-9][0-9][0-9]'
or emp_id like [a-z][a-z][1-9][0-9][0-9][0-9][0-9]')

check (pub_id in ('1389','0736','0877') or pub_id like'99[0-9][0-9]')


case 的用法
A. 使用带有简单 CASE 函数的 SELECT 语句
在 SELECT 语句中，简单 CASE 函数仅检查是否相等，而不进行其它比较。下面的示例使用 CASE 函数更改图书分类显示，以使其更易于理解。

USE pubs
GO
SELECT Category =
CASE type
WHEN ''''''''popular_comp'''''''' THEN ''''''''Popular Computing''''''''
WHEN ''''''''mod_cook'''''''' THEN ''''''''Modern Cooking''''''''
WHEN ''''''''business'''''''' THEN ''''''''Business''''''''
WHEN ''''''''psychology'''''''' THEN ''''''''Psychology''''''''
WHEN ''''''''trad_cook'''''''' THEN ''''''''Traditional Cooking''''''''
ELSE ''''''''Not yet categorized''''''''
END,
CAST(title AS varchar(25)) AS ''''''''Shortened Title'''''''',
price AS Price
FROM titles
WHERE price IS NOT NULL
ORDER BY type, price
COMPUTE AVG(price) BY type

B. 使用带有 SUBSTRING 和 SELECT 的 CASE 函数
下面的示例使用 CASE 和 THEN 生成一个有关作者、图书标识号和每个作者所著图书类型的列表。

USE pubs
SELECT SUBSTRING((RTRIM(a.au_fname) + '''''''' ''''''''+
RTRIM(a.au_lname) + '''''''' ''''''''), 1, 25) AS Name, a.au_id, ta.title_id,
Type =
CASE
WHEN SUBSTRING(ta.title_id, 1, 2) = ''''''''BU'''''''' THEN ''''''''Business''''''''
WHEN SUBSTRING(ta.title_id, 1, 2) = ''''''''MC'''''''' THEN ''''''''Modern Cooking''''''''
WHEN SUBSTRING(ta.title_id, 1, 2) = ''''''''PC'''''''' THEN ''''''''Popular Computing''''''''
WHEN SUBSTRING(ta.title_id, 1, 2) = ''''''''PS'''''''' THEN ''''''''Psychology''''''''
WHEN SUBSTRING(ta.title_id, 1, 2) = ''''''''TC'''''''' THEN ''''''''Traditional Cooking''''''''
END
FROM titleauthor ta JOIN authors a ON ta.au_id = a.au_id
C. 结合sum使用,实现统计和已知的有限的行列转换
例如物品分为 四类
物品号 物品类别 物品性能
1 cpu 1
2 cpu 2
2 cpu 2
3 memory 3

select 物品类别，count(*) ,sum(case 物品性能 when 1 then 1 else 0 end),
sum(case 物品性能 when 2 then 1 else 0 end),
sum(case 物品性能 when 3 then 1 else 0 end)
from tablename
group by 物品类别

D:归类省略显示

cj:
id dwid name cj
1 1 aa 70
2 1 bb 71
3 1 cc 72
4 2 dd 73
5 2 ee 74
6 2 ff 75

dw:
dwid name
1 单位1
2 单位2

结果显示：

单位1 aa 70
bb 71
cc 72
单位2 dd 73
ee 74
ff 75
select case when (select count(id) from cj where dwid=b.dwid and id<=b.id)=1
then name else '''' end,b.name,b.cj
from dw a left join cj b
on a.dwid=b.dwid
order by b.dwid,b.id


生成测试数据－1－T－SQL系列
Transact-SQL编程之谜系列（生成测试数据－1）

在xyz表中创建100行

--Method 1. simple DEFAULT values on table

CREATE TABLE xyz
(
col1 int PRIMARY KEY IDENTITY(1,1) NOT NULL,
col2 int NOT NULL DEFAULT 999,
col3 char(10) NOT NULL DEFAULT ''ABCDEFGHIJK''
)
GO

DECLARE @counter int
SET @counter=1
WHILE(@counter<=100)
BEGIN
INSERT xyz DEFAULT valueS
SET @counter=@counter+1
END

SELECT * FROM
xyz


 特殊案例（quicksilver）
http://www.delphibbs.com/delphibbs/dispq.asp?lid=1399372
有三个表：a,b,c
a: b: c:
a1 a2 b1 b2 c1 c2
----------- ---------------- ------------------
1 a1 1 b1_1 1 c1_1
2 a2 1 b1_2 1 c1_2
3 a3 1 b1_3 2 c2_1
4 a4 2 b2_1 2 c2_2
2 c2_3
3 c3_1
现想得出如下表：
a1 a2 b2 c2
----------------------
1 a1 b1_1 c1_1
1 a1 b1_2 c1_2
1 a1 b1_3
2 a2 b2_1 c2_1
2 a2 c2_2
2 a2 c2_3
3 a3 c3_1

select identity(int,1,1) as tmp,b1,b2
into #temp1
from b
order by b1,b2
go

select identity(int,1,1) as tmp,c1,c2
into #temp2
from c
order by c1,c2
go

Select BB.b1,BB.b2,CC.c1,CC.c2
Into #temp3
From (select b1,b2,(select count(*)+1 from #temp1 where tmp<AA.tmp and b1=AA.b1) tmpid from #temp1 AA) BB
Full OUTER JOIN
(select c1,c2,(select count(*)+1 from #temp2 where tmp<AA.tmp and c1=AA.c1) tmpid from #temp2 AA) CC
ON BB.b1=CC.c1 and BB.tmpid=CC.tmpid
go

Select a1,a2,b2,c2
From A AA
Full outer join
#temp3 BB
On AA.a1=BB.b1 or AA.a1=BB.c1
order by a1
go


经典排序案例
http://www.delphibbs.com/delphibbs/dispq.asp?lid=1016377
1。我有一张数据表，结构如下：
create Table List
(
name CHAR(20),
local CHAR(20),
age CHAR(20)
)

我想要返回的结果集格式为：
按地区排序，但是地区按枚举输出，
每个地区显示一条记录，按地区循环显示，下面只列出了3个地方作例子。
（各地区人数不定也不等，如果该地方没有人可以填充了，就跳过去不显示）
如：
name local age
张三 河北 10
李四 河南 20
王五 山东 18

马六 河北 16
刘七 河南 22
赵八 山东 25
. . .
. . .
. . .
Select Name,Local,Age,
XH=(Select Count(*) from List A where A.Local=B.Local and A.Name<B.Name)
from List B order by XH,Local

2。人員名稱 外出時間 外出趟次
張三 2000-01-01 10:01:01
張三 2000-01-01 13:01:01
張三 2000-01-01 12:01:01
張三 2000-01-02 09:01:01
張三 2000-01-02 15:01:01
張三 2000-01-02 16:01:01
李四 2000-01-01 10:01:01
李四 2000-01-01 11:01:01

目標要求:
人員名稱 外出時間 外出趟次 //記錄某人在某天的第幾次外出
張三 2000-01-01 10:01:01 1
張三 2000-01-01 13:01:01 3
張三 2000-01-01 12:01:01 2
張三 2000-01-02 09:01:01 1
張三 2000-01-02 15:01:01 2
張三 2000-01-02 16:01:01 3
李四 2000-01-01 10:01:01 1
李四 2000-01-01 11:01:01 2

select a.name,a.dt,(select count(name) from tablename
where name=a.name and year(dt)=year(a.dt) and month(a.dt) and day(dt)=day(a.dt) and dt<=a.dt) as ncount
from tablename a
order by a.dt

3.ID 名称
1 B
2 A
3 C
4 B
5 A
6 B
其中ID是主键,现在希望通过SQL查询得到以下结果
ID 名称 次数
1 B 1
2 A 1
3 C 1
4 B 2
5 A 2
6 B 3
其中"次数"是指以ID为顺序,名称是第几次出现。
望高手相助，200分相赠！
Select id,名称,
(select count(id) from table1 where 名称=AAA.名称 and id<=AAA.id) as 次数
From table1 AAA


生成测试数据－2－T－SQl系列
（2） RAND( ) 的一般用法

生成在一定范围的随机数

-- 0~9999
(CONVERT(int,RAND()*100000)%10000)

-- -9999~9999
CASE
WHEN (CONVERT(int,RAND()*100)%2=1 THEN
(CONVERT(int,RAND()*100000)%10000*-1)
ELSE (CONVERT(int,RAND()*100000)%10000)
END

-- A~Z
CHAR((CONVERT(int,RAND()*1000)%26)+65) --65 is A


生成测试数据－3－T－SQL系列
（3） 若想确认每次执行RAND()函数是否产生不同的结果，应该为RAND()指定种子，即把一个整数置于圆括号中。可以用@@spid ，即新建表的object_id，或其它“几乎随机”的整数作为种子值。

IF (ISNULL(OBJECT_ID(''random_data''),0))>0
DROP TABLE random_data
GO

CREATE TABLE random_data
(
COL1 int PRIMARY KEY,
col2 int,
col3 char(15)
)
GO

DECLARE @counter int,@col2 int,@col3 char(15)
/*Insert 1000 rows of date */
--Seed Random generator
SELECT @counter=0,@col2=RAND(@@spid+cpu+physical_io)
FROM master..sysprocesses WHERE spid=@@spid

WHILE (@counter<1000)
BEGIN
SELECT @counter=@counter+10, --Sequence numbers by 10
@col2=
CASE --Random integer between -9999 and 9999
WHEN CONVERT(int,RAND()*100)%2=1
THEN (CONVERT(int,RAND()*100000)%10000*-1)
ELSE CONVERT(int,RAND()*100000)%10000
END,
@col3=
CHAR((CONVERT(int,RAND()*1000)%26)+65) --65 is A
+CHAR((CONVERT(int,RAND()*1000)%26)+65)
+CHAR((CONVERT(int,RAND()*1000)%26)+65)
+CHAR((CONVERT(int,RAND()*1000)%26)+65)
+REPLICATE(CHAR((CONVERT(int,RAND()*1000)%26)+65),11)

INSERT random_data valueS(@counter,@col2,@col3)
END
GO

SELECT * FROM random_data WHERE col1<200

用另一种方法实现：

CREATE TABLE random_data
(
COL1 int PRIMARY KEY IDENTITY(10,10) NOT NULL,
col2 int NOT NULL DEFAULT CASE
--Random integer between -9999 and 9999
WHEN CONVERT(int,RAND()*100)%2=1
THEN (CONVERT(int,RAND()*100000)%10000*-1)
ELSE CONVERT(int,RAND()*100000)%10000
END,
col3 char(15) NOT NULL DEFAULT
CHAR((CONVERT(int,RAND()*1000)%26)+65) --65 is A
+CHAR((CONVERT(int,RAND()*1000)%26)+65)
+CHAR((CONVERT(int,RAND()*1000)%26)+65)
+CHAR((CONVERT(int,RAND()*1000)%26)+65)
+REPLICATE(CHAR((CONVERT(int,RAND()*1000)%26)+65),11)
)
GO

DECLARE @counter int
SELECT @counter=1

WHILE (@counter<1000)
BEGIN
INSERT random_data DEFAULT valueS
SET @counter=@counter+1
END
GO

SELECT * FROM random_data WHERE col1<200


获取排队顺序－1－T－SQL系列
引言，基础：

TOP 字句允许指定 WITH TIES。（多行同位）

SELECT TOP 5 WITH TIES title_id,price,title_name
FROM title
ORDER BY price DESC

结果：

title_id price title_name
-------- --------------------- ------------------------------
Tt005 80.0000 Title_Name_5
Tt013 70.0000 Title_Name_13
Tt012 65.0000 Title_Name_12
Tt010 55.0000 Title_Name_10
Tt002 50.0000 Title_Name_2
Tt004 50.0000 Title_Name_4
Tt008 50.0000 Title_Name_8
Tt014 50.0000 Title_Name_14

(8 row(s) affected)

方法一：标准的 SQL 方法：利用视图
对于大表来说，性能将显著降低，因为对每一行都要扫描一次该表。

CREATE VIEW ranked_sales(rank,title_id,price,title_name)
AS
SELECT
(SELECT COUNT(DISTINCT T2.price) FROM title AS T2
WHERE T2.price>=T1.price) AS rank,
title_id,
price,
title_name
FROM title AS T1 WHERE price IS NOT NULL
GO

SELECT * FROM ranked_sales WHERE rank<=10 ORDER BY rank
GO

结果：
rank title_id price title_name
----------- -------- --------------------- ------------------------------
1 Tt005 80.0000 Title_Name_5
2 Tt013 70.0000 Title_Name_13
3 Tt012 65.0000 Title_Name_12
4 Tt010 55.0000 Title_Name_10
5 Tt002 50.0000 Title_Name_2
5 Tt004 50.0000 Title_Name_4
5 Tt008 50.0000 Title_Name_8
5 Tt014 50.0000 Title_Name_14
6 Tt001 40.0000 Title_Name_1
7 Tt006 39.0000 Title_Name_6
8 Tt007 38.0000 Title_Name_7
9 Tt009 35.0000 Title_Name_9
10 Tt011 33.0000 Title_Name_11

(13 row(s) affected)


获取排队顺序－2－T－SQL系列
方法二：标准的 SQL 方法：不利用视图
用导出表代替了视图，性能与方法一相当，没有改善。结果也一样。

SELECT rank,title_id,price,title_name
FROM(SELECT
T1.title_id,
price,
T1.title_name,
(SELECT COUNT(DISTINCT T2.price) FROM title AS T2
WHERE T1.price<=T2.price) AS rank
FROM title AS T1) AS X
WHERE price IS NOT NULL
ORDER BY rank

结果同上。


获取排队顺序－3－T－SQL系列
方法三：利用临时表（带有“一致性”列而没有多行同位）
速度较快。的不区分“多行同位”值。

CREATE TABLE #ranked_order
(
rank int IDENTITY NOT NULL PRIMARY KEY,
title_id char(6) NOT NULL,
price int NOT NULL,
title_name varchar(30) NOT NULL
)
GO

INSERT #ranked_order
SELECT title_id,price,title_name FROM title
WHERE price IS NOT NULL ORDER BY price DESC

SELECT * FROM #ranked_order

DROP TABLE #ranked_order

结果：

(16 row(s) affected)

rank title_id price title_name
----------- -------- ----------- ------------------------------
1 Tt005 80 Title_Name_5
2 Tt013 70 Title_Name_13
3 Tt012 65 Title_Name_12
4 Tt010 55 Title_Name_10
5 Tt002 50 Title_Name_2
6 Tt004 50 Title_Name_4
7 Tt008 50 Title_Name_8
8 Tt014 50 Title_Name_14
9 Tt001 40 Title_Name_1
10 Tt006 39 Title_Name_6
11 Tt007 38 Title_Name_7
12 Tt009 35 Title_Name_9
13 Tt011 33 Title_Name_11
14 Tt016 30 Title_Name_16
15 Tt015 25 Title_Name_15
16 Tt003 20 Title_Name_3

(16 row(s) affected)


获取排队顺序－4－T－SQL系列
方法四：利用临时表（带有“一致性”列和“多行同位”）

CREATE TABLE #ranked_order
(
rank int IDENTITY NOT NULL,
title_id char(6) NOT NULL,
price int NOT NULL,
title_name varchar(30) NOT NULL
)
GO

INSERT #ranked_order
SELECT title_id,price,title_name FROM title
WHERE price IS NOT NULL ORDER BY price DESC

SELECT B.rank,A.title_id,B.price,A.title_name
FROM(
SELECT MIN(T2.rank) AS rank,T2.price FROM #ranked_order AS T2
GROUP BY T2.price) AS B,
#ranked_order AS A
WHERE A.price=B.price
ORDER BY B.rank

DROP TABLE #ranked_order

结果：

(16 row(s) affected)

rank title_id price title_name
----------- -------- ----------- ------------------------------
1 Tt005 80 Title_Name_5
2 Tt013 70 Title_Name_13
3 Tt012 65 Title_Name_12
4 Tt010 55 Title_Name_10
5 Tt002 50 Title_Name_2
5 Tt004 50 Title_Name_4
5 Tt008 50 Title_Name_8
5 Tt014 50 Title_Name_14
9 Tt001 40 Title_Name_1
10 Tt006 39 Title_Name_6
11 Tt007 38 Title_Name_7
12 Tt009 35 Title_Name_9
13 Tt011 33 Title_Name_11
14 Tt016 30 Title_Name_16
15 Tt015 25 Title_Name_15
16 Tt003 20 Title_Name_3

(16 row(s) affected)

指明有多少值处于“多行同位”状态。

CREATE TABLE #ranked_order
(
rank int IDENTITY NOT NULL,
title_id char(6) NOT NULL,
price int NOT NULL,
title_name varchar(30) NOT NULL
)
GO

INSERT #ranked_order
SELECT title_id,price,title_name FROM title
WHERE price IS NOT NULL ORDER BY price DESC

SELECT B.rank,
CASE B.number_tied
WHEN 1 THEN ''''
ELSE ''(''+CONVERT(VARCHAR(10),number_tied)+'' Way Tie)''
END AS tie,
A.title_id,
B.price,
A.title_name
FROM(
SELECT MIN(T2.rank) AS rank,COUNT(*) AS number_tied,T2.price
FROM #ranked_order AS T2
GROUP BY T2.price) AS B,#ranked_order AS A
WHERE A.price=B.price
ORDER BY B.rank

DROP TABLE #ranked_order

结果：

(16 row(s) affected)

rank tie title_id price title_name
----------- -------------------- -------- ----------- ------------------------------
1 Tt005 80 Title_Name_5
2 Tt013 70 Title_Name_13
3 Tt012 65 Title_Name_12
4 Tt010 55 Title_Name_10
5 (4 Way Tie) Tt002 50 Title_Name_2
5 (4 Way Tie) Tt004 50 Title_Name_4
5 (4 Way Tie) Tt008 50 Title_Name_8
5 (4 Way Tie) Tt014 50 Title_Name_14
9 Tt001 40 Title_Name_1
10 Tt006 39 Title_Name_6
11 Tt007 38 Title_Name_7
12 Tt009 35 Title_Name_9
13 Tt011 33 Title_Name_11
14 Tt016 30 Title_Name_16
15 Tt015 25 Title_Name_15
16 Tt003 20 Title_Name_3

(16 row(s) affected)


获取排队顺序－5－T－SQL系列
方法五：利用游标

SELECT语句（集合操作）可能需要相关子查询或自联结来解决，这样就需要多次访问数据。这类问题特别适合采用一种方法来解决，因为采用游标法时，只需扫描一次数据。对于一些时序操作，游标法比单个SELECT语句效率高。（但利用游标不如利用临时表速度快）。
处理“多行同位”的三个原则：
1、 对“多行同位”指定重复的等级值，并且下一个不重复的等级值递增1。
2、 各行（包括“多行同位”）的等级值都唯一。
3、 指定重复的等级值，但是下一个不重复的等级值由它在等级中的实际位置决定，而非简单地递增1。

方法五A：利用游标（对“多行同位”应用“规则1”）

DECLARE @rank int,@title_id char(6),@price int,
@title_name varchar(30),@last_rank int,
@last_prace money,@counter int
SELECT @rank=1,@last_rank=1,@last_prace=0,@counter=1
DECLARE rank_cursor CURSOR FOR
SELECT title_id,price,title_name
FROM title WHERE price IS NOT NULL
ORDER BY price DESC
OPEN rank_cursor
FETCH NEXT FROM rank_cursor INTO @title_id,@price,@title_name
WHILE (@@FETCH_STATUS<>-1)
BEGIN
IF (@counter=1) --For first row,just display values
--and set last values
BEGIN
SELECT rank=@rank,title_id=@title_id,
price=@price,title_name=@title_name
END
ELSE
BEGIN
--If current PRICE the same as last,assign the same rank
IF (@price=@last_prace)
SELECT rank=@last_rank,title_id=@title_id,
price=@price,title_name=@title_name
ELSE --Otherwise,increment the rank
BEGIN
SELECT rank=@last_rank+1,title_id=@title_id,
price=@price,title_name=@title_name
SELECT @rank=@last_rank+1
END
END
--Set values to current row
SELECT @counter=@counter+1,@last_rank=@rank,
@last_prace=@price
FETCH NEXT FROM rank_cursor INTO @title_id,@price,@title_name
END

CLOSE rank_cursor
DEALLOCATE rank_cursor

结果：
rank title_id price title_name
----------- -------- ----------- ------------------------------
1 Tt005 80 Title_Name_5
2 Tt013 70 Title_Name_13
3 Tt012 65 Title_Name_12
4 Tt010 55 Title_Name_10
5 Tt002 50 Title_Name_2
5 Tt004 50 Title_Name_4
5 Tt008 50 Title_Name_8
5 Tt014 50 Title_Name_14
6 Tt001 40 Title_Name_1
7 Tt006 39 Title_Name_6
8 Tt007 38 Title_Name_7
9 Tt009 35 Title_Name_9
10 Tt011 33 Title_Name_11
11 Tt016 30 Title_Name_16
12 Tt015 25 Title_Name_15
13 Tt003 20 Title_Name_3

方法五B：利用游标（对“多行同位”应用“规则2”）

DECLARE @rank int,@title_id char(6),@price int,
@title_name varchar(30)
SELECT @rank=1
DECLARE rank_cursor CURSOR FOR
SELECT title_id,price,title_name
FROM title WHERE price IS NOT NULL
ORDER BY price DESC
OPEN rank_cursor
FETCH NEXT FROM rank_cursor INTO @title_id,@price,@title_name
WHILE (@@FETCH_STATUS<>-1)
BEGIN
SELECT rank=@rank,title_id=@title_id,
price=@price,title_name=@title_name
SELECT @rank=@rank+1
FETCH NEXT FROM rank_cursor INTO @title_id,@price,@title_name
END

CLOSE rank_cursor
DEALLOCATE rank_cursor

结果：
rank title_id price title_name
----------- -------- ----------- ------------------------------
1 Tt005 80 Title_Name_5
2 Tt013 70 Title_Name_13
3 Tt012 65 Title_Name_12
4 Tt010 55 Title_Name_10
5 Tt002 50 Title_Name_2
6 Tt004 50 Title_Name_4
7 Tt008 50 Title_Name_8
8 Tt014 50 Title_Name_14
9 Tt001 40 Title_Name_1
10 Tt006 39 Title_Name_6
11 Tt007 38 Title_Name_7
12 Tt009 35 Title_Name_9
13 Tt011 33 Title_Name_11
14 Tt016 30 Title_Name_16
15 Tt015 25 Title_Name_15
16 Tt003 20 Title_Name_3

方法五C：利用游标（对“多行同位”应用“规则3”）

DECLARE @rank int,@title_id char(6),@price int,
@title_name varchar(30),@last_rank int,
@last_prace money,@counter int
SELECT @rank=1,@last_rank=1,@last_prace=0,@counter=1
DECLARE rank_cursor CURSOR FOR
SELECT title_id,price,title_name
FROM title WHERE price IS NOT NULL
ORDER BY price DESC
OPEN rank_cursor
FETCH NEXT FROM rank_cursor INTO @title_id,@price,@title_name
WHILE (@@FETCH_STATUS<>-1)
BEGIN
IF (@counter=1) --For first row,just display values
--and set last values
BEGIN
SELECT rank=@rank,title_id=@title_id,
price=@price,title_name=@title_name
END
ELSE
BEGIN
--If current PRICE the same as last,assign the same rank
IF (@price=@last_prace)
SELECT rank=@last_rank,title_id=@title_id,
price=@price,title_name=@title_name
ELSE --Otherwise,set the rank to the overrall
--counter of how many rows have been visited
BEGIN
SELECT @rank=@counter
SELECT rank=@rank,title_id=@title_id,
price=@price,title_name=@title_name
END
END
--Set values to current row
SELECT @counter=@counter+1,@last_rank=@rank,
@last_prace=@price
FETCH NEXT FROM rank_cursor INTO @title_id,@price,@title_name
END

CLOSE rank_cursor
DEALLOCATE rank_cursor

结果：
rank title_id price title_name
----------- -------- ----------- ------------------------------
1 Tt005 80 Title_Name_5
2 Tt013 70 Title_Name_13
3 Tt012 65 Title_Name_12
4 Tt010 55 Title_Name_10
5 Tt002 50 Title_Name_2
5 Tt004 50 Title_Name_4
5 Tt008 50 Title_Name_8
5 Tt014 50 Title_Name_14
9 Tt001 40 Title_Name_1
10 Tt006 39 Title_Name_6
11 Tt007 38 Title_Name_7
12 Tt009 35 Title_Name_9
13 Tt011 33 Title_Name_11
14 Tt016 30 Title_Name_16
15 Tt015 25 Title_Name_15
16 Tt003 20 Title_Name_3

由于游标法进行多个 FETCH 操作，所以它产生多个结果集。对于慢速网络会大幅降低性能。若临时表能满足对“多行同位”的处理要求，那么最好采用临时表，它只返回一个结果集。


查找不同时间段间的差异－1－T－SQL系列

基础准备：

用随机数生成表。

IF NULLIF(OBJECT_ID(''measurements''),0)>0
DROP TABLE measurements
GO

CREATE TABLE measurements
(
when_taken datetime NOT NULL,
temperature numeric(4,1) --(Fahrenherit)
)

CREATE CLUSTERED INDEX measurements_idx01
ON measurements(when_taken)
GO

DECLARE @counter int,@whendate datetime,@val numeric(4,1),
@randdiff smallint,@randmins smallint
SELECT @counter=1,@whendate=GETDATE(),@val=50.0
/*Insert 20 rows of data.Change constrant if you want more*/
WHILE(@counter<=20)
BEGIN
INSERT measurements valueS(@whendate,@val)
SELECT @randdiff=CASE
WHEN CONVERT(int,RAND()*100)%2=1 THEN
CONVERT(int,RAND()*100)%21*-1
ELSE CONVERT(int,RAND()*100)%21
END,
@randmins=CONVERT(int,RAND()*100000)%10080
--10080 (mins in a week)
SELECT @counter=@counter+1,
@whendate=DATEADD(mi,@randmins,GETDATE()),
@val=@val+@randdiff+RAND()
END

SELECT * FROM measurements

结果：
when_taken temperature
--------------------------- -----------
2002-01-30 20:45:42.610 50.0
2002-01-31 09:48:42.773 63.7
2002-01-31 17:28:42.820 18.0
。。。。。。

方法1：标准的SQL

可用视图或导出表

CREATE VIEW rankdates(when_taken,temperature,daterank)
AS
SELECT when_taken,temperature,
(SELECT COUNT(DISTINCT when_taken) FROM measurements AS T1
WHERE T1.when_taken<=T0.when_taken) AS rank
FROM measurements as T0
GO

SELECT * FROM rankdates ORDER BY daterank

结果：
when_taken temperature daterank
--------------------------- ----------- -----------
2002-01-30 20:45:42.610 50.0 1
2002-01-31 09:48:42.773 63.7 2
2002-01-31 17:28:42.820 18.0 3
。。。。。。

SELECT
P1_WHEN=V1.when_taken,P2_WHEN=V2.when_taken,
P1=V1.temperature,P2=v2.temperature,
DIFF=(v2.temperature-v1.temperature)
FROM rankdates AS V1 LEFT JOIN rankdates AS V2
ON(V2.daterank=V1.daterank+1)
GO

结果：
P1_WHEN P2_WHEN P1 P2 DIFF
--------------------------- --------------------------- ------ ------ --------
2002-01-30 20:45:42.610 2002-01-31 09:48:42.773 50.0 63.7 13.7
2002-01-31 09:48:42.773 2002-01-31 17:28:42.820 63.7 18.0 -45.7
2002-01-31 17:28:42.820 2002-01-31 17:41:42.780 18.0 50.1 32.1
。。。。。。


查找不同时间段间的差异－2－T－SQL系列

方法2：先具体化等级，然后自联结
标准的SQL方法进行了84次的逻辑读；而临时表方法在创建表时只进行了40次逻辑读，然后用52个逻辑读来访问它。由于涉及到的数据如此少，所以这两种方法都只发生一次物理读。

CREATE TABLE #rankdates(
when_taken datetime,
temperature numeric(4,1),
daterank int IDENTITY PRIMARY KEY)
GO

INSERT #rankdates(when_taken,temperature)
SELECT when_taken,temperature
FROM measurements
ORDER BY when_taken ASC
GO

SELECT
P1_WHEN=V1.when_taken,P2_WHEN=V2.when_taken,
P1=V1.temperature,P2=v2.temperature,
DIFF=(v2.temperature-v1.temperature)
FROM #rankdates AS V1 LEFT JOIN #rankdates AS V2
ON(V2.daterank=V1.daterank+1)
GO

DROP TABLE #rankdates

结果同上。

[此贴子已经被作者于2003-4-18 13:27:53编辑过]

----------------------------------------------


累积求和问题－T－SQL系列

累积求和问题

有一表： col1 value running_tot
1 10 0 （10）
2 15 0 （25）
3 50 0 （75）
……

若要将value列累积求和的结果放到running_tot 列，可以这样：

DECLARE @run_total int
SELECT @run_total=0
UPDATE running_tot
SELECT @run_total=running_tot=run_total+value
FROM running_tot

这是一种高效的方法，使用了UPDATE语句中不太常用的特性，这种方法只使用一次逻辑读来计算所有的值。（任何逻辑读的次数都不可以少于1次）。可以把这个过程看作从右往左发生。


 每隔n行的采样问题－T－SQL系列

每隔n行的采样问题

可以用临时表，以5取模；也可以通过游标法，使用FETCH RELATIVE 5 。


用于数据擦除的相关方法－T－SQL系列

用于数据擦除的相关方法

CREATE TABLE #match_cols
(
row_num int,
col2 char(1),
col3 char(1)
)
GO

CREATE UNIQUE INDEX #match_col2_idx
ON #match_cols(col2,col3)
WITH IGNORE_DUP_KEY
GO

--先检查一下match_cols有很多重复的行
SELECT "COUNT(*)"=COUNT(*),col2,col3
FROM match_cols
GROUP BY col2,col3

INSERT #match_cols
SELECT * FROM match_cols

--#match_cols没有重复的行
SELECT "COUNT(*)"=COUNT(*),col2,col3
FROM #match_cols
GROUP BY col2,col3


 保存计算列－T－SQL系列

保存计算列

CREATE TABLE customer_sx
(cust_name char(20),
soundex_value as SOUNDEX(cust_name)
)

上表中的soundex_value是计算列，它是一个虚拟列，在访问它时才计算它。因此不能被索引。

用触发器来解决：

--更改表
CREATE TABLE customer_sx
(cust_name char(20),
soundex_value char(4) null
)

--创建群集索引
CREATE CLUSTERED INDEX sounds_like
ON customer_sx(soundex_value)
GO

--触发器
CREATE TRIGGER maintain_soundex ON c
FOR INSERT,UPDATE
AS
UPDATE customer_sx
SET soundex_value=SOUNDEX(inserted.cust_name)
FROM customer_sx,inserted
WHERE customer_sx.cust_name=inserted.cust_name


用“单位矩阵”解决“交叉表”问题－T－SQL系列

用“单位矩阵”解决“交叉表”问题

构造如下“单位矩阵”：表pivot

Qtr Q1 Q2 Q3 Q4
1 1 0 0 0
2 0 1 0 0
3 0 0 1 0
4 0 0 0 1

SELECT year,
q1=SUM(value*Q1),
q2=SUM(value*Q2),
q3=SUM(value*Q3),
q4=SUM(value*Q4),
total=SUM(value)
FROM cross_tab JOIN pivot
ON (cross_tab.qrt=pivot.qrt)
GROUP BY year

它的效率没有使用CASE和导出表高，不使用CASE时，在应该是最好的方法。

本例请同事参考CASE或导出表的解决方法和效率。


提高效率1法：用选择代替迭代－T-SQL系列

用选择代替迭代

用循环计算三角函数如下：

DECLARE @angle smallint
SELECT @angle=0
WHILE(@angle<=180)
BEGIN
SELECT ANGLE=@angle,
SINE=STR(SIN(@angle),7,4),
COSINE=STR(COS(@angle),7,4),
TANGENT=STR(TAN(@angle),7,4)

SELECT @angle=@angle+10
END
用这种方法，每一行都有自己的结果集，这样会降低程序的效率。

用选择代替迭代：

--创建序数表
CREATE TABLE seq_num
(seq_num INT PRIMARY KEY NOT NULL)

--在序数表中加序数-500~500
DECLARE @counter int
SELECT @counter=-500
WHILE(@counter<=500)
BEGIN
INSERT seq_num valueS(@counter)
SELECT @counter=@counter+1
END

--用选择和联结seq_num
SELECT ANGLE=seq_num*10,
SINE=STR(SIN(seq_num*10),7,4),
COSINE=STR(COS(seq_num*10),7,4),
TANGENT=STR(TAN(seq_num*10),7,4)
FROM seq_num
WHERE seq_num BETWEEN 0 AND 18

这种方法比迭代快很多，它返回一个结果集。


获取表中记录的总行数，等－T-SQL系列

（1）、获取表中记录的总行数

SELECT COUNT(*) FROM seq_num

--更快的方法，只能取得表的总行数
SELECT ROWS
FROM sysindexes
WHERE id=OBJECT_ID("seq_num")
AND indid<2

（2）、将数据放到WEB页上

用专门的存储过程sp_makewebtask（它也内含了扩展存储过程xp_makewebtask）。

sp_makewebtask @outputfile="c:\tmp\pub_info.htm",
@query="SELECT pub_id,pr_info,pub_id,logo,pub_id FROM pub_info",
@blobfmt="%2%FILE=c:\tmp\pubtxt_.html%4%FILE=c:\tmp\pubing_.gif"

[此贴子已经被


查找不同时间段间的差异－3－T-SQL系列

方法3：使用游标

这是一种时序操作，所以游标的效果会很好。

DECLARE @lastval int,@currdate datetime,
@currval numeric(4,1),@counter int
SELECT @counter=1
DECLARE diffprev CURSOR
FOR SELECT when_taken,temperature FROM measurements
ORDER BY when_taken ASC
OPEN diffprev
FETCH NEXT FROM diffprev INTO @currdate,@currval
WHILE(@@FETCH_STATUS<>-1)
BEGIN
IF(@counter=1) --For first row,just get value and return
SELECT WHEN_TAKEN=@currdate,"CURR_value"=@currval,
RANK=@counter,"PREV_value"=NULL,
"DIFF"=NULL
ELSE
SELECT WHEN_TAKEN=@currdate,"CURR_value"=@currval,
RANK=@counter,"PREV_value"=@lastval,
"DIFF"=@currval-@lastval
SELECT @lastval=@currval,@counter=@counter+1
FETCH NEXT FROM diffprev INTO @currdate,@currval
END

CLOSE diffprev
DEALLOCATE diffprev
GO


找含匹配列的行－T-SQL系列

查找含匹配列的行

//--为col2与col3查找出重复的行

//match_cols表

row_no col2 col3
------ ---------- ----------
1 c d
2 a a
3 a d
4 c b
5 c c
6 b c
7 c a
8 c b
9 c d
10 d d

(10 row(s) affected)

方法一：自联结法

对于到大量数据，它的效率不高。

SELECT DISTINCT A.row_no,A.col2,A.col3
FROM match_cols A,match_cols B
WHERE A.col2=B.col2 AND A.col3=B.col3
AND A.row_no<>B.row_no
ORDER BY A.col2,A.col3

方法二：相关子查询

SELECT A.row_no,A.col2,A.col3
FROM match_cols A
WHERE EXISTS(SELECT B.col2,B.col3
FROM match_cols AS B
WHERE A.col2=B.col2 AND A.col3=B.col3
GROUP BY B.col2,B.col3
HAVING COUNT(*)>1)
ORDER BY A.col2,A.col3

方法三：具体化非唯一的值，然后联结到结果

SELECT col2,col3 INTO #mytemp
FROM match_cols
GROUP BY col2,col3 HAVING COUNT(*)>1

SELECT #mytemp.col2,#mytemp.col3,#mytemp.row_num
FROM #mytemp
JOIN match_cols
ON(#mytemp.col2=match_cols.col2 AND
#mytemp.col3=match_cols.col3)
ORDER BY 1,2,3

这种方法速度快。因只有16种可能的组合，因此只有16种出现重复的方式，该临时表也就只包含16行。该临时表就成为该联结的外表。然后对这16行中的每一行扫描（利用索引）一次主表match_cols。这样就不象前面介绍的方法中那样，扫描次数高达5000，而是只扫描16次。这种方法是到现在为止最快的。纯SQL查询最适合这类工作，效率也高。

方法四：使用导出表
使用导出表的效果相当好，处理方法与临时表几乎完全相同。

SELECT A.row_num,A.col2,A.col3
FROM match_cols AS A
JOIN
(SELECT col2,col3 FROM match_cols AS B
GROUP BY col2,col3 HAVING COUNT(*)>1) AS B
ON(A.col2=B.col2 AND A.col3=B.col3)
ORDER BY A.col2,A.col3,A.row_num

********************************
row_no col2 col3
------ ---------- ----------
4 c b
8 c b
1 c d
9 c d

(4 row(s) affected)


Database Backup
Database Backup Script

Here's a small script that will perform a backup of all the databases into a specified directory (d:\backup testing by default). The script needs to be run in master database and has been tested both in SQL Server 7.0 and 2000.

Author: C.S. Senthil Kumar

declare @dbname as varchar(40)
declare @msgdb as varchar(40)
declare @dbbkpname as varchar(40)
declare rs_cursor CURSOR for select name from master.dbo.sysdatabases
open rs_cursor
Fetch next from rs_cursor into @dbname
IF @@FETCH_STATUS <> 0
PRINT " <<db>>"
WHILE @@FETCH_STATUS = 0
BEGIN
select @msgdb= "database backup on progress: " + @dbname
PRINT @msgdb
select @dbbkpname='d:\backup testing\' + @dbname + '.dat'
backup database @dbname to disk=@dbbkpname
FETCH NEXT FROM rs_cursor INTO @dbname
END
CLOSE rs_cursor
deallocate rs_cursor


 关于恢复SQL Server 7.0系统数据库的几点心得
关于系统数据库的恢复总结如下:
在SQL Server数据库中，系统信息存储在系统数据库中，主要的系统数据库包括：
master-从整体上控制用户数据库和SQL Server操作，在创建了任何用户定义的
对象后，都要备份它
model-为新数据库提供模版和原型
msdb-包含了有关作业、报警及操作员等信息
如果包含系统数据库的介质变了，那么必须重建系统数据库，如果你仍然可以启动
SQL Server服务，则可以通过RESTORE语句从系统数据库的备份中恢复数据库。
如果master坏了，不能启动系统,可以按照下面步骤进行恢复
1 重建系统数据库 运行c:\mssql7\binn\rebuildm.exe，按照提示进行即可，过程中需
要
系统数据库样本的路径，可在安装光盘中找到；
2 重建系统数据库后，启动SQL Server服务，用系统数据库的备份恢复数据库就行了
通常恢复顺序为master->msdb->model
在恢复master的备份时要注意：必须在single user模式下进行，有以下几种方法进入单
用户模式：
1 可以在命令行模式下输入sqlservr -c -f -m或者输入sqlservr -m
其中：-c 可以缩短启动时间，SQL Server 不作为Windows NT的服务启动
-f 用最小配置启动SQL Server
-m 单用户模式启动SQL Server
2 可以在控制面板-服务-MSSQLServer的启动参数中输入-c -f -m或者输入-m,点击开始

3 还有一种更灵活的启动方法：用存在注册表里的启动参数启动
在MSSQLServer项下添加项SingleUser,具体内容如下所示：
HKEY_LOCAL_MACHINE
\Software
\Microsoft
\MSSQLServer
\SingleUser
\Parameters
SQLArg0 : REG_SZ : -dC:\MSSQL7\DATA\MASTER.DAT
SQLArg1 : REG_SZ : -eC:\MSSQL7\LOG\ERRORLOG
SQLArg2 : REG_SZ : -lC:\MSSQL7\DATA\MASTLOG.DAT
SQLArg3 : REG_SZ : -m
在命令行下输入SQLServr -c -sSingleUser,注意：必须是在命令行下
进入单用户模式后启动 Query Analyzer执行语句：
RESTORE DATABASE master form disk='c:\(具体的备份文件名）'
（我试着在Enterprise Manager中进行恢复操作，结果均失败）
以上几种方法我均做过具体验


 Web环境下中数据的磁带备份与恢复(转贴)
实现磁带备份数据的功能有两方面的困难：首先，MS SQL Server所提供的数据库的整体备份及恢复功能不能直接满足本系统要求的数据滚动备份。其次，需要解决如何在Web环境下实现磁带数据备份功能。
利用SQL中现有的数据库备份和恢复的命令以及NT中的IDC技术，实现SQL数据库中数据滚动备份到磁带的功能。本系统所实现功能既能保证近一段时间的数据总在数据库中，又能保证系统管理员可随时恢复备份数据，供用户查询。本文所实现的功能具有普遍的意义，特别适用于中小型企业开发基于Intranet技术的管理信息系统。
磁带数据备份及恢复的工作过程
为了充分利用SQL中现有的数据库备份与恢复功能，以降低实现磁带数据备份的代价，我们特地在硬盘上建立了一个与磁带的容量相当的数据库，我们称之为桥数据库，如FJJDBBK。可方便地实现数据的滚动备份了。其工作过程是：管理员把某月的数据从主数据库移到桥数据库中；然后用SQL的备份功能把桥数据库备份到磁带上。用户需要查看磁带中的历史数据时，系统又用SQL的恢复备份功能把磁带中的数据恢复到桥数据库中，用户直接在桥数据库中查阅历史数据。此外，在数据备份到磁带后，在进行下次数据备份前，必须清空备份数据库中的数据。为了提高工作效率，我们建立了一个空白桥数据库（没有数据时的桥数据库）。每次进行磁带备份数据之前，就用此空白数据库恢复桥数据库。空白数据库文件建立后要保存好，不可随便删除。
建立该备份空白数据库过程为：先建立主数据库FJJDBBK，然后在FJJDBBK中建立与当前数据库中一样结构的表。建立这些表结构时最好的方法是先用Enterprise Manager的Object菜单下的Generate SQL Scripts功能生成建立这些表的Scripts文件，然后选择FJJDBBK数据库，用Enterprise Manager的Tools菜单下的Query Analyzer功能运行该文件，就建立了这些所需表。显然，这时的FJJDBBK为空数据库。我们再利用备份命令或运行Tools菜单下的Backup/Restore功能备份该空数据库。
磁带数据备份及恢复的实现
在NT中，Web服务器IIS（Internet Information Server）提供了完善的访问SQL的技术IDC。IDC是一个DLL文件（HTTPODBC.DLL）,其实，它通过ODBC接口可访问各种数据库。在具体实现Web页面访问数据库时，需建立两种类型的文件：IDC文件（*.idc）和HTML模板文件（*.htx）。IDC文件用于控制数据库的访问。它一般由两部分组成：
.文件头　指明系统数据源，模板文件名，用户名及口令。有时还包括一些有关检索数据库方面的参数设置。
.文件主体　这部分内容以"SQLStatement："为起始标志，然后写操纵数据库的标准SQL语句。注意，每条语句以"+"号开头，它作为区分每条SQL语句的标志。需要强调的是，对于MS SQL数据库，IDC可用其任何标准的命令及函数。例如定义变量，控制数据流等。
IDC的处理流程如下：首先，Web服务器IIS分析浏览器传来的URL字符串。如果当前字符串以"*.idc"结尾，就将该IDC请求传给IDC接口模块，IDC将依次读取并解释IDC文件的内容，通过ODBC模块向数据库服务器发服务请求；数据库服务器将执行结果通过ODBC接口返回给IDC模块。接着IDC模块把结果插入指定的的模板文件，形成一个实际的HTML文件交给IIS；最后由IIS将该HTML返回浏览器。
在Web信息系统中要实现完整的磁带数据备份及恢复功能需要实现以下三个功能："数据备份"功能、"数据恢复"功能和"清理数据"功能。"数据备份"功能在保证系统管理员操作的前提下，根据用户输入的所要备份数据的起始日期和终止日期，从主数据库把要备份的数据转存到桥数据库中，然后运用SQL的DUMP命令实现该功能。"数据恢复"功能则在保证系统管理员操作的前提下，提供"恢复桥数据库"和"恢复空白桥数据库"的功能。只要运用SQL的LOAD命令就可实现上述功能；同样，"清理数据"功能也要保证在系统管理员操作的前提下，根据用户输入的所要删除数据的起始日期和终止日期，运用SQL中的DELETE命令删除当前数据库中数据。因此。为了"数据备份"功能和"清理数据"功能的顺利实施，要求每个表都有归档时间字段。
实现本系统磁带数据备份与恢复功能的部分IDC文件如下：
/*BKUP.IDC*/
Datasource: Web SQL
Template: BKUP.htx
Username: sa
Password: sa
SQLStatement:
+if %jb%=1
+/*判断系统维护员*/
+SELECT qsrq=convert(varchar(12),getdate(),1),
zzrq=convert(varchar(12),+dateadd(day,1,getdate()),1)
+/*预置起始日期和终止日期*/

/*BKUPCZ.IDC*/
Datasource: Web SQL
Template: BKUPCZ.htx
Username: sa
Password: sa
SQLStatement:
+INSERT FJJDBBK.DBO.JKLD SELECT * FROM FJJDB.DBO.JKLD
+where convert(varchar(12),gdsj,1)>=′%QSRQ%′
and convert(varchar(12),gdsj,1)< =′%ZZRQ%′
…………
+/*把当前数据库中选定的数据转存到桥数据库中*/
+DUMP DATABASE FJJDBBK TO TAPE = ′\\.\tape0′ WITH INIT, NOUNLOAD
+/*备份数据*/
+SELECT TS=′备份数据库操作已完成!′
/*HUIF.IDC*/
Datasource: Web SQL
Template: HUIF.htx
Username: sa
Password: sa
SQLStatement:
+if %jb%=1
+begin
+LOAD DATABASE FJJDBBK FROM TAPE=′\\.\TAPE0′ WITH
NOUNLOAD
+/*恢复备份数据*/
+SELECT TS=′恢复数据库操作已完成!′+/*
+end

/*QLSJKCZ.IDC*/
Datasource: Web SQL
Username: sa
Password: sa
Template: QLSJKCZ.htx
SQLStatement:
+delete FJJDB.DBO.JKLD
where convert(varchar(12),gdsj,1)>=′%QSRQ%′ and
convert(varchar(12),gdsj,1)<=′%ZZRQ%′
…………
+/*删除当前数据库中选定的数据*/
+SELECT TS=′删除操作已完成!′
    在本系统中，主数据库保证存储4个月数据。系统规定，系统管理员每月月初备份上月的数据到磁带。然后删除前第4个月的数据。实现了当前数据与历史数据的同步查阅，这是Web系统的一个显著的优点


BCP之妙用(adam.zhu)
BCP语句的作用：
BCP是SQL提供的进行数据传输的实用程序，这种语句提供了非常快的数据导入的方法。（当然7。0也有BCP的替代方法就是DTS，但是我们还是要来讲讲BCP。因为个人觉得它导入导出的速度非常的快。而且系统开销小，因为BCP是基于命令行的）
BCP支持的文件类型是ASCII文本文件，本地的SQL格式文件和UNICODE文件。所以大家不用考虑用BCP来导入EXCEL或者ACCESS。

执行了BCP后的语法如下：
Microsoft(R) Windows 98
(C)Copyright Microsoft Corp 1981-1999.
C:\>bcp
usage: C:\MSSQL7\BINN\BCP.EXE {database.owner.dbtable | query} {in | out | queryout | format} datafile
[-m maxerrors] [-f formatfile] [-e errfile]
[-F firstrow] [-L lastrow] [-b batchsize]
[-n native type] [-c character type] [-w wide character type]
[-N keep non-text native] [-6 6x file format] [-q quoted identifier]
[-C code page specifier] [-t field terminator] [-r row terminator]
[-i inputfile] [-o outfile] [-a packetsize]
[-S server name] [-U username] [-P password]
[-T trusted connection] [-v version] [-R regional enable]
[-k keep null values] [-E keep identity values]
[-h "load hints"]
语法说明如下：
database 需要访问的数据库名字
owner 所访问表或者视图的用户名字
dbtable 所访问的表或者视图
query 产生一个结果集的SQL语句，当然语句要用双引号引起来
in out queryout format 数据传输的方向。In表示导入，out表示导出。
Datafile 导入时的源数据文件名或者是导出生成的文件名字。
上面是BCP的语法说明，下面讲讲一些经常使用到的参数
[-f formatfile] 用来输入输出格式的文件名字，可以包含有路径
[-U username] 登陆数据库的表示符
[-P password] 登陆数据库的口令
[-S server name] 数据库的名字，如果是两台服务器之间导入导出必须要填写
[-c character type] 字符数据模式，在使用了该模式以后就不需要使用-f这个参数
其他的参数可以在SQL的帮助中找到，我就不详细介绍了。
操作的模式：
在使用BCP导入数据时，有两种模式：快速和慢速模式。快速模式绕过事务日志，慢速模式将所有数据都记录在事务日志中。
在使用BCP之前为了能够运行在快速模式下，你首先需要把数据库的SELECT INTO/BULK COPY设置成TURE。
通过BCP直接向数据库导入数据时，需要考虑你的导入的表中的触发器，规则，索引等。BCP对触发器和规则是不强制处理的。


SQL脚本生成的一些BUG(chair3)
SQL脚本生成的一些BUG：！！（原创作品）

原创： chair3.2002.1.18
email： chair3@sina.com

微软的SQL脚本生成令我伤透了心……我一直以为是我的程序上的问题，或者我操作上的失误，
并且，客服务人员屡屡埋怨我的程序bug，多次测试之后，靠，原来都是微软惹的祸……

Sql Server 的脚本生成有不少漏洞，经常由它生成的脚本运行起来却有错误。下面举例说明：

1、并没有根据sysdenpends的依赖关系生成SQL代码，而是根据“优先级”（呵呵，所谓的优先级）来生成。
比如：他认为view的优先级就要比function高。
那么，我写了下面的测试程序，形成如下的依赖关系：fnT1 <-- vwT1 <-- fnT2
就是，view vwT1处于依赖的中间。
------------------------------------
Create function fnT1()
Returns Integer
As
begin
Return 123
end
go

Create view vwT1
As
Select aa=dbo.fnT1()

Go

Create function fnT2()
Returns Table
As
Return (Select * From vwT1)
Go
-------------------------------------
运行到数据库之后，用Enterprise生成SQL代码。（选项不一样，会有所不同，我没有选数据库和用户的）
-----------------------------------------------------------------------------------
if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[fnT1]') and xtype in (N'FN', N'IF', N'TF'))
drop function [dbo].[fnT1]
GO

if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[fnT2]') and xtype in (N'FN', N'IF', N'TF'))
drop function [dbo].[fnT2]
GO

if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[vwT1]') and OBJECTPROPERTY(id, N'IsView') = 1)
drop view [dbo].[vwT1]
GO

SET QUOTED_IDENTIFIER ON
GO
SET ANSI_NULLS ON
GO

Create view vwT1
As
Select aa=dbo.fnT1()

GO
SET QUOTED_IDENTIFIER OFF
GO
SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO
SET ANSI_NULLS ON
GO

Create function fnT1()
Returns Integer
As
begin
Return 123
end

GO
SET QUOTED_IDENTIFIER OFF
GO
SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO
SET ANSI_NULLS ON
GO

Create function fnT2()
Returns Table
As
Return (Select * From vwT1)

GO
SET QUOTED_IDENTIFIER OFF
GO
SET ANSI_NULLS ON
GO
-----------------------------------------------------------------------------
呵呵，一眼你就可以看出来了，建立view要比建立function先。而不是根据依赖关系建立……
毫无疑问，将会得到如下的错误：（这个错误可真严重！害得我好惨……）
---------------------------------------------------
服务器: 消息 208，级别 16，状态 1，过程 vwT1，行 4
对象名 'dbo.fnT1' 无效。
服务器: 消息 208，级别 16，状态 1，过程 fnT2，行 5
对象名 'vwT1' 无效。
---------------------------------------------------
2、作业脚本。

这个我就不说了，bug还不是很严重，主要是中文“--”注释符的问题，英文版我没有测试过，不过猜想应当没有这个bug。
大家可以试试看。

3、还有一个SP的问题。

大家看过我的精华里面有spGetIDStr和spAnalyseStrList了吧，关系是后者依赖于前者。可是spGetIDStr我并没有调用任何的表。
因此，每当运行Sql Server生成的脚本的时候，总是报告（大概是这样的信息）：
------------------------------------------------------------------------
spGetIDStr并不存在，无法在sysdepends里建立依赖关系，存储过程spAnalyseStrList仍然建立。
--------------------------------------------------------------------------
无论我手工修改他的建立顺序还是什么的，用它生成的脚本就是有错。呵呵，这个破微软！
这里，再看看第三个bug，看我下面的测试程序：
（原理：当sp没有对表或视图等数据库对象有依赖关系的时候，sp被别的sp引用的时候也将无法建立依赖关系）
形成依赖关系：spB1 <--- spA1
------------------------------------------------------------------------
Create Proc spB1
As
Return 11

Go

Create proc spA1
As
Begin
Declare @i int
Exec @i=spB1
Return @i*2
End
Go
-------------------------------------------------------------------------
生成的脚本就为：
-------------------------------------------------------------------------
if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[spA1]') and OBJECTPROPERTY(id, N'IsProcedure') = 1)
drop procedure [dbo].[spA1]
GO

if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[spB1]') and OBJECTPROPERTY(id, N'IsProcedure') = 1)
drop procedure [dbo].[spB1]
GO

SET QUOTED_IDENTIFIER ON
GO
SET ANSI_NULLS ON
GO

Create proc spA1
As
Begin
Declare @i int
Exec @i=spB1
Return @i*2
End

GO
SET QUOTED_IDENTIFIER OFF
GO
SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO
SET ANSI_NULLS ON
GO

Create Proc spB1
As
Return 11

GO
SET QUOTED_IDENTIFIER OFF
GO
SET ANSI_NULLS ON
GO

-------------------------------------------------------------------------
嘻嘻。大家这回应当明白我故意命名为spA1,spB1缘故了。因为如果没有依赖关系的话，A自然排在B前面，你看微软可不就是这么做的么？
呵呵，发现微软的工程师有时候也好笨……：），你看spA1就建立在spB1的前面，而不是它的实际关系（她根本没有在sysdepends里面写）
幸好这个bug还不是很大，怎么说SP还是建立了起来。

各位有兴趣的可以试试别的，比如sp与fn，sp Group等等，我懒：）。


数据库文件恢复技术(yzhshi)
http://www.delphibbs.com/delphibbs/dispq.asp?lid=1670844
center][h3]SQL Server数据库文件恢复技术[/h3]
yzhshi(yzhshi@etang.com)
[/center]
SQL Server数据库备份有两种方式，一种是使用BACKUP DATABASE将数据库文件备份出去，另外一种就是直接拷贝数据库文件mdf和日志文件ldf的方式。下面将主要讨论一下后者的备份与恢复。本文假定您能熟练使用SQL Server Enterprise Manager(SQL Server企业管理器)和SQL Server Quwey Analyser(SQL Server查询分析器)
1、正常的备份、恢复方式
正常方式下，我们要备份一个数据库，首先要先将该数据库从运行的数据服务器中断开，或者停掉整个数据库服务器，然后复制文件。
卸下数据库的命令：Sp_detach_db 数据库名
连接数据库的命令：Sp_attach_db或者sp_attach_single_file_db
s_attach_db [@dbname =] ′dbname′, [@filename1 =] ′filename_n′ [,...16]
sp_attach_single_file_db [@dbname =] ′dbname′, [@physname =] ′physical_name′
使用此方法可以正确恢复SQL Sever7.0和SQL Server 2000的数据库文件，要点是备份的时候一定要将mdf和ldf两个文件都备份下来，mdf文件是数据库数据文件，ldf是数据库日志文件。
例子：
假设数据库为test，其数据文件为test_data.mdf，日志文件为test_log.ldf。下面我们讨论一下如何备份、恢复该数据库。
卸下数据库：sp_detach_db 'test'
连接数据库：sp_attach_db 'test','C:\Program Files\Microsoft SQL Server\MSSQL\Data\test_data.mdf','C:\Program Files\Microsoft SQL Server\MSSQL\Data\test_log.ldf'
sp_attach_single_file_db 'test','C:\Program Files\Microsoft SQL Server\MSSQL\Data\test_data.mdf'
2、只有mdf文件的恢复技术
由于种种原因，我们如果当时仅仅备份了mdf文件，那么恢复起来就是一件很麻烦的事情了。
如果您的mdf文件是当前数据库产生的，那么很侥幸，也许你使用sp_attach_db或者sp_attach_single_file_db可以恢复数据库，但是会出现类似下面的提示信息
[i][brown]设备激活错误。物理文件名 'C:\Program Files\Microsoft SQL Server\MSSQL\data\test_Log.LDF' 可能有误。
已创建名为 'C:\Program Files\Microsoft SQL Server\MSSQL\Data\test_log.LDF' 的新日志文件。[/brown][/i]
但是，如果您的数据库文件是从其他计算机上复制过来的，那么很不幸，也许上述办法就行不通了。你也许会得到类似下面的错误信息
[i][brown]服务器: 消息 1813，级别 16，状态 2，行 1
未能打开新数据库 'test'。CREATE DATABASE 将终止。
设备激活错误。物理文件名 'd:\test_log.LDF' 可能有误。[/brown][/i]
怎么办呢？别着急，下面我们举例说明恢复办法。
A．我们使用默认方式建立一个供恢复使用的数据库(如test)。可以在SQL Server Enterprise Manager里面建立。
B．停掉数据库服务器。
C．将刚才生成的数据库的日志文件test_log.ldf删除，用要恢复的数据库mdf文件覆盖刚才生成的数据库数据文件test_data.mdf。
D．启动数据库服务器。此时会看到数据库test的状态为“置疑”。这时候不能对此数据库进行任何操作。
E．设置数据库允许直接操作系统表。此操作可以在SQL Server Enterprise Manager里面选择数据库服务器，按右键，选择“属性”，在“服务器设置”页面中将“允许对系统目录直接修改”一项选中。也可以使用如下语句来实现。
[i][blue]use master
go
sp_configure 'allow updates',1
go
reconfigure with override
go[/blue][/i]
F．设置test为紧急修复模式
[i][blue]update sysdatabases set status=-32768 where dbid=DB_ID('test')[/blue][/i]
此时可以在SQL Server Enterprise Manager里面看到该数据库处于“只读\置疑\脱机\紧急模式”可以看到数据库里面的表，但是仅仅有系统表
G．下面执行真正的恢复操作，重建数据库日志文件
[i][blue]dbcc rebuild_log('test','C:\Program Files\Microsoft SQL Server\MSSQL\Data\test_log.ldf')[/blue][/i]
执行过程中，如果遇到下列提示信息：
[i][brown]服务器: 消息 5030，级别 16，状态 1，行 1
未能排它地锁定数据库以执行该操作。
DBCC 执行完毕。如果 DBCC 输出了错误信息，请与系统管理员联系。[brown][/i]
说明您的其他程序正在使用该数据库，如果刚才您在F步骤中使用SQL Server Enterprise Manager打开了test库的系统表，那么退出SQL Server Enterprise Manager就可以了。
正确执行完成的提示应该类似于：
[i][brown]警告: 数据库 'test' 的日志已重建。已失去事务的一致性。应运行 DBCC CHECKDB 以验证物理一致性。将必须重置数据库选项，并且可能需要删除多余的日志文件。
DBCC 执行完毕。如果 DBCC 输出了错误信息，请与系统管理员联系。[/brown][/i]
此时打开在SQL Server Enterprise Manager里面会看到数据库的状态为“只供DBO使用”。此时可以访问数据库里面的用户表了。
H．验证数据库一致性（可省略）
[i][blue]dbcc checkdb('test')[/blue][/i]
一般执行结果如下：
[i][brown]CHECKDB 发现了 0 个分配错误和 0 个一致性错误（在数据库 'test' 中）。
DBCC 执行完毕。如果 DBCC 输出了错误信息，请与系统管理员联系。[/brown][/i]
I．设置数据库为正常状态
[i][blue]sp_dboption 'test','dbo use only','false'[/blue][/i]
如果没有出错，那么恭喜，现在就可以正常的使用恢复后的数据库啦。
J．最后一步，我们要将步骤E中设置的“允许对系统目录直接修改”一项恢复。因为平时直接操作系统表是一件比较危险的事情。当然，我们可以在SQL Server Enterprise Manager里面恢复，也可以使用如下语句完成
[i][blue]sp_configure 'allow updates',0
go
reconfigure with override
go


Two Database Backup
Two Database Backup Scripts

Here are two scripts designed for backup purposes -- the first returns the last date a database was backed up and the second reports on backup activity for all databases, sorted by date of backup.

The Get Last Backup script allows you to report the last date of backup for all databases on a server, or you can pass in a specific database name and the script will report the date the database was last backed up.

In addition to providing all database backup activity, the Get Backup Info script will also allow you to filter by a specific database name as well.

Author: TJay Belt

/**********************************************************
sp_GetBackupInfo
**********************************************************/

if
exists
(
select * from SysObjects
where ID = Object_ID('sp_GetBackupInfo')
and ObjectProperty(ID, 'IsProcedure') = 1
)
begin
drop procedure sp_GetBackupInfo
end
go

create procedure sp_GetBackupInfo
@Name varchar(100) = '%'
with Encryption
as
set NoCount on

declare
@result int

--try

select
(substring ( database_name, 1, 32)) as Database_Name,
abs(DateDiff(day, GetDate(), backup_finish_date)) as DaysSinceBackup,
backup_finish_date
from msdb.dbo.backupset
where Database_Name like @Name
order by Database_Name, backup_finish_date desc

--finally
SuccessProc:
return 0 /* success */

--except
ErrorProc:
return 1 /* failure */
--end
go

grant execute on sp_GetBackupInfo to Public
go

/*

sp_GetBackupInfo 'hgp'

*/

/**********************************************************
sp_GetLastBackup
**********************************************************/

if
exists
(
select * from SysObjects
where ID = Object_ID('sp_GetLastBackup')
and ObjectProperty(ID, 'IsProcedure') = 1
)
begin
drop procedure sp_GetLastBackup
end
go

create procedure sp_GetLastBackup
@Name varchar(100) = '%'
with Encryption
as
set NoCount on

declare
@result int

--try

select
(substring ( database_name, 1, 32)) as Database_Name,
abs(DateDiff(day, GetDate(), Max(backup_finish_date))) as DaysSinceBackup,
Max(backup_finish_date)
from msdb.dbo.backupset
where Database_Name like @Name
group by Database_Name
order by Database_Name

--finally
SuccessProc:
return 0 /* success */

--except
ErrorProc:
return 1 /* failure */
--end
go

grant execute on sp_GetLastBackup to Public
go

/*

sp_GetLastBackup 'hgp'

*/


Data Retrieval
Data Retrieval Script

This stored procedure is a refinement of work previously done by Sumit Dhingra. It takes a string argument of fields delimited by the 3rd argument character, or a comma if the 3rd argument is missing, and applies each field to the second argument which may be either a SQL statement or, more usually, a stored procedure.

Example:

sp_ForEach "table1,table2,table3","sp_spaceused"

Author: Tony Zackin

if exists (select name from sysobjects where name = "sp_ForEach" and type =
"P")
drop proc sp_ForEach
go

/*--------------------------------------------------------------------------
------------------------------------
Based on code created by Sumit Dhingra

This proc takes a string argument of fields delimited by the 3rd argument
character,
or a comma if the 3rd argument is missing, and applies each field to the
second
argument which may be either a SQL statement or, more usually, a stored
procedure.

Example: sp_ForEach "table1,table2,table3","sp_spaceused"

----------------------------------------------------------------------------
----------------------------------*/

CREATE PROCEDURE sp_ForEach
@ParamStr Varchar(1000), -- delimited string
@cmd Varchar(500), -- command to issue for each
substring
@delim Char(1) = "," -- default delimiter
AS

SET NOCOUNT ON

/* Declare Variables */
DECLARE @Field Varchar(100)
DECLARE @ctr Int
DECLARE @posctr Int
DECLARE @commaPos Int
DECLARE @paramLen Int
DECLARE @findposinparamstr Varchar(200)

/* Initialize the Variables */
SET @paramLen = len(@ParamStr)
SET @findposinparamstr = @ParamStr
SET @posctr = 1
SET @ctr = 0

/* Loop through the Comma Delimited String and pick up the individual values
(Fields) */
WHILE @posctr < @paramLen
BEGIN
SET @commaPos = patindex('%'+@delim+'%',@findposinparamstr)

IF (@commaPos <> 0)
BEGIN
SET @Field = ltrim(substring(@ParamStr,@posctr,@commaPos-1))
exec (@cmd+" "+@Field)
END
ELSE /* @commaPos <> 0 */
Break

SET @posctr = @posctr + @commaPos -- Set this
to starting position for next element.
SET @findposinparamstr = substring(@findposinparamstr,(@commapos +
1),(len(@findposinparamstr)-@commapos))
SET @ctr = @ctr + 1
END /* End of While.... */

/* Get the last item from the string */
SET @Field = ltrim(@findposinparamstr)
exec (@cmd+" "+@Field)

SET NOCOUNT OFF
Return(0)
GO

/*--------------------------------------------------------------------------
------------------------------------
The following is a somewhat contrived example showing how this could be used
using a non-comma delimiter:

if exists (select name from sysobjects where name =
"sp_ExpensesForNameAndDeptByDate" and type = "P")
drop proc sp_ExpensesForNameAndDeptByDate
go

create proc sp_ExpensesForNameAndDeptByDate @parms varchar(500)
as
/*
This proc retrieves expense data for a name in a specific department for
a date range. It enables the user to specify all arguments as a single
string delimited by embedded commas. Multiple queries may be issued with
one call by separating separate argument strings by a semi-colon.

Example (whitespace is provided for readability only):
sp_ExpensesForNameAndDeptByDate
"'Joe','466','20020101','20020131';
'Joe','591','20020201','20020228';
'Sam','972','20020101','20020131'"
*//*

exec sp_ForEach @parms,"sp_ExpensesForNameAndDeptByDate2",";"

----------------------------------------------------------------------------
----------------------------------

if exists (select name from sysobjects where name =
"sp_ExpensesForNameAndDeptByDate2" and type = "P")
drop proc sp_ExpensesForNameAndDeptByDate2
go

create proc sp_ExpensesForNameAndDeptByDate2 @name varchar(20), @dept
varchar(20), @d1 char(8), @d2 char(8)
as
declare @statement nvarchar(1000)
select @statement=
"select d.name, d.mgr, p.name, p.date, p.expenses"+
"from Departments d "+
"join Personnel p on d.name = p.dept "+
"where p.date between @lowdate and @highdate "+
"and p.name = @name "+
"and d.dept = @dept"

exec sp_executesql @statement,
N'@lowdate char(8),@highdate char(8),@name varchar(20),@dept
varchar(20)',
@d1,@d2,@name,@dept
*/


Easily BCP In and Out Data
Easily BCP In and Out Data

sp_DBABCPOut.sql Exports user data from a database, and sp_DBABCPIn.sql Imports data into a database. They use the - T ( Trusted connection ) to run the BCP as the work is being done with xp_cmdshell which we limit execute permission to.

With the export script, sp_DBABCPOut.sql, pass in the Database name, the server name and it will BCP out all the user tables, to either a default location or a user specific location. The import Script sp_DBABCPIn.sql , works pretty much the same way. Specify the Database, the Server name and it will Import the all the user data from either a default location or a directory you define. The Data files are in the format of Database name_Tablename.dat. The other files produced are an Error File ( *.err ) and some BCP Transfer Statistics ( *.out )

Author: Eddie Deeney

Create Procedure sp_DBABCPIn @db varchar(10), -- Database Name
@srv varchar(10), -- Server Name
@path varchar(10) = 'C:\BCP\' -- Import Directory

AS

/**********************************************************************************************************
* Procedure: sp_DBABCPIn
*
* Purpose: BCP's data from a specified location into a specified database on
* a given server
*
* Inputs: Database Name, Server Name, Output Path (Optional)
* Assumes: There exists a directory called C:\BCP, if no output directory is given
*
* Output: 3 files per table :- <database>_<table>.dat ( BCP'ed output Data file )
* <database>_<table>.out ( BCP Statical info )
* <database>_<table>.err ( Error File )
* --------------------------------------------------------------------------------------------
*/

declare @tbl varchar(255),
@next_tbl varchar(255),
@cmd Varchar(1000)

exec ('declare d cursor for SELECT name FROM ' + @db + '..sysobjects WHERE type = ' + '''u''')

open d
fetch next from d into @tbl
SET NOCOUNT ON

while (@@FETCH_STATUS<>-1) begin

select @cmd = 'bcp '+ @db+ '..' + @tbl + ' in ' + @path + @db +'_'+ @tbl +'.dat' +
' /e ' + @path + @db +'_'+ @tbl +'.err' + ' /n /E /o ' + @path + @db +'_'+ @tbl +'.out' +
' /T /S ' + @srv

execute master..xp_cmdshell @cmd
Print 'BCP the ' + @tbl + ' Table'

fetch next from d into @tbl

End
close d
deallocate d

点击查看用户来源及管理<br>发贴IP：61.48.16.68 2003-6-12 8:36:21
编辑这个贴子 鲜花(0) 鸡蛋(0)
ugvanxk 帅哥哟，在线，有人找我吗？

等级：版主
文章：251
积分：653
注册：2002-11-2
搜索ugvanxk在『数 据 库』的所有贴子 第2楼
发贴心情
Create Procedure sp_DBABCPOut @db varchar(10), -- Table Name
@srv varchar(10), -- Server Name
@path varchar(10) = 'C:\BCP\' -- Output Directory

AS

/*******************************************************************************************
* Procedure: sp_DBABCPOut
*
* Purpose: BCP ALL user data from a specified database and
* specified server to given location
*
* Inputs: Database Name, Server Name, Output Path (Optional)
* Assumes: There exists a directory called C:\BCP, if no output directory is given
*
* Output: 3 files per table :- <database>_<table>.dat ( Data file )
* <database>_<table>.out ( BCP Statical info )
* <database>_<table>.err ( Error File )
*
* --------------------------------------------------------------------------------------------
*/

declare @tbl varchar(255),
@next_tbl varchar(255),
@cmd Varchar(1000)

exec ('declare d cursor for SELECT name FROM ' + @db + '..sysobjects WHERE type = ' + '''u''')

open d
fetch next from d into @tbl
SET NOCOUNT ON

while (@@FETCH_STATUS<>-1) begin

select @cmd = 'bcp '+ @db+ '..' + @tbl + ' out ' + @path + @db +'_'+ @tbl +'.dat' +
' /e ' + @path + @db +'_'+ @tbl + '.err' + ' /n /E /o ' + @path + @db +'_'+ @tbl +'.out' +
' /T /S ' + @srv

execute master..xp_cmdshell @cmd
Print 'BCP the ' + @tbl + ' Table'

fetch next from d into @tbl

End
close d
deallocate d


Load data from text file
Load data from text file with variable columns
Script Rating Total number of votes [6]
By: Edgardo
This script loads from a formatted text file (E.g. tab delimited) with variable number of columns using BULK INSERT. For instance, the file may have any number of?columns and next load (seconds later) the same file may have a different number of columns.

This script reads the first line (column names) extracting the names and using them to create a table dynamically appending the time stamp string to the table name (to ensure uniqueness). After executing the CREATE TABLE directive, and creating a new table, it will execute the second BULK INSERT to load the data to the new table.

The table definition uses varchar(1000) for all the columns to make the load easier. I would suggest to perform data validation the load is complete.

create table ##tbl (line varchar(1000))

bulk insert ##tbl
from '\\wsana157\c$\tbl.txt'
with (FIRSTROW=1)

--select top 1 Replace(Line, char(9), ',') from ##tbl

declare @tempstr varchar(1000)
declare @col varchar(1000)
declare @createtbl varchar (1000)
declare @loadtbl varchar (1000)
declare @newtblname varchar(1000)

/* ====================================================================

Generates a unique identifier for all the Event Log entries

====================================================================*/

declare @month char(2)
declare @date char(2)
declare @year char(4)
declare @hour char(2)
declare @min char(2)
declare @sec char(2)
declare @msec char(3)

set @month = DATEPART(m, getdate())
if LEN(RTRIM(DATEPART(m, getdate()))) = 1
set @month = '0'+RTRIM(DATEPART(m, getdate()))
set @date = DATEPART(d, getdate())
if LEN(RTRIM(DATEPART(d, getdate()))) = 1
set @date = '0'+RTRIM(DATEPART(d, getdate()))
set @year = DATEPART(yyyy, getdate())
set @hour = DATEPART(hh, getdate())
if LEN(RTRIM(DATEPART(hh, getdate()))) < 2
set @hour = '0'+RTRIM(DATEPART(hh, getdate()))
set @min = DATEPART(mi, getdate())
if LEN(RTRIM(DATEPART(mi, getdate()))) < 2
set @min = '0'+RTRIM(DATEPART(mi, getdate()))
set @sec = DATEPART(ss, getdate())
if LEN(RTRIM(DATEPART(ss, getdate()))) < 2
set @sec = '0'+RTRIM(DATEPART(ss, getdate()))
set @msec = DATEPART(ms, getdate())
if LEN(RTRIM(DATEPART(ms, getdate()))) < 3
begin
if LEN(RTRIM(DATEPART(ms, getdate()))) = 2
set @msec = '0'+RTRIM(DATEPART(ms, getdate()))
if LEN(RTRIM(DATEPART(ms, getdate()))) = 1
set @msec = '00'+RTRIM(DATEPART(ms, getdate()))
end

set @col = ''
set @tempstr = (select top 1 RTRIM(Replace(Line, char(9), ',')) from ##tbl)

while CHARINDEX(',',@tempstr) > 0
begin
set @col = @col+SUBSTRING(@tempstr, 1, CHARINDEX(',',@tempstr)-1)+' varchar(1000),'
set @tempstr = SUBSTRING(@tempstr, CHARINDEX(',',@tempstr)+1, len(@tempstr))
end

set @col = @col+@tempstr+' varchar(1000))'

set @createtbl = 'create table load_'+RTRIM(@month+@date+@year+@hour+@min+@sec)+' ('+@col
set @newtblname = 'load_'+RTRIM(@month+@date+@year+@hour+@min+@sec)

--select @createtbl

drop table ##tbl

exec (@createtbl)

--select @newtblname

set @loadtbl = 'bulk insert '+@newtblname+' from '+char(39)+'\\wsana157\c$\tbl.txt'+char(39)+' with (FIRSTROW=2)'

--select @loadtbl

exec (@loadtbl)


Creating a view between servers
I have two MS SQL Servers 2000 on two machines. Is there a way I can create a view table in ServerA, which would look at ServerB, using a ServerA user, who does not exists in ServerB.
This question posed on 26 February 2002

Ok, lets go step by step. We're going to use Transact SQL language to do this, so go to the Query Analyzer and login to the Local Server (ServerA).

First Link the ServerB to ServerA:
EXEC sp_addlinkedserver 'ServerB', 'SQL Server'
Then establish the user that we'll use to connect from ServerA to ServerB. For that task we'll use a valid account in ServerA (with the appropriate permissions) and map it to a valid account in ServerB. That account must have permission to access the source table for the view in ServerA:
EXEC sp_addlinkedsrvlogin
@rmtsrvname = 'ServerB',
@useself = 'false',
@locallogin = 'YourLocalUserFromA',
@rmtuser = 'YourRemoteUserFromB',
@rmtpassword = 'PasswordOf_rmtuser'
Log in to ServerA with the account that we mapped to ServerB and create the view:
CREATE VIEW dbo.MyView
AS
SELECT Field1, Field2, FieldN
FROM ServerB.RemoteDB.dbo.RemoteTable
GO
Start using it, and good luck:
SELECT * FROM MyView


实现上下文相关视图
一、 引言
在数据库应用的开发过程中，我们会经常碰到类似这样的查询语句用来查询某个业务员本人某天的业务记录：
SELECT * FROM DailyBusiness WHERE ClerkId = 'a001'AND OccurDate = '2002-5-1'
同时，出于业务的一些特殊要求，本人只能查看自己的业务数据，或者某人只能查看某日的数据等等。一般，我们用到的方法是在客户端程序中写类似上面语句的查询代码。
但有没有一种方法，实现这种智能的视图，会根据当前的上下文只显示必要的数据集合呢？这样不但可以降低变成的工作量，并且可以提高程序的可维护性。
因为不必要再重复大量的书写WHERE子句，其次前后台（前台应用程序、后台数据库），前台各个模块中，使用到该数据集合的地方，因为业务逻辑的变化等原因，需要调整该查询语句，那么我们只要简单地修改该上下文视图，而没有必要去修改使用该查询的每个模块。
二、 实现方法
情况1：如果仅仅是同登录用户的登录Id有关（这里的Id是后台SQL Server数据库的登录帐号，也就是上面的ClerkId就是这个登录帐号），那么我么可以简单地使用以下方式来实现：
CREATE VIEW v_ClerkDailyBusiness
AS
SELECT a.* FROM DailyBusiness a, Master.dbo.sysproceeses b
WHERE a.ClerkId = b.loginname and b.spid = @@spid.

但这种情况一般比较少。另外，有一个缺点就是只能实现有限的上下文相关，灵活性不够。
情况2：许多数据库应用系统一般都只建立少数几个数据库登录帐号，对于应用系统的权限，一般有程序进行控制，那么根据登录帐号来实现，就显得力不从心，因此，我们必须采取其他方法来实现。
SQL Server提供了一个设置连接上下文的功能，即SET CONTEXT_INFO可以为当前的数据库连接设置最大达到128字节的上下文信息，并可以通过查询sysprocesses表获取当前的上下文信息。因此，我们可以初步设想通过SET CONTEXT_INFO设置一些上下文信息，然后再将上下文信息作为上下文相关视图的查询条件，既可以实现。
但是，这里有两个需要解决的问题：
1． 什么时候设置上下文信息
2． 取出上下文信息，并作为查询的条件时，由于需要进行数据转换，免不了使用系统函数，SQL Server就无法对该查询进行优化，如果数据量较大，因此会遇到性能问题。
对于第一个问题，如果是跟用户相关的视图，那么用户登录的时候是最好的设置时机。写上一个用于登录的存储过程，一方面验证用户的口令等信息，另一方面可以调用SET CONTEXT_INFO设置用户的登录Id(这里的Id是数据库应用系统的Id，而不是SQL Server的登录帐号)。
如果是像财务如选择帐套之类的操作，我们可以在选择帐套时，设置当前的上下文为当前的帐套。
由于SET CONTEXT_INFO允许设置最大达到128字节的信息，因此，我们可以进行组合，包含多个上下文信息，如前2个字节为用户Id，后两个字节为帐套号，再接下去是指定的日期等等。
对于第二个问题：首先我们分析一下产生性能问题的原因。
假设我们设置了用户Id上下文，下面是建立视图的脚本如下：
CREATE VIEW v_DailyClerkBusiness
AS
SELECT a.* FROM DailyBusiness a, MASTER.DBO.SysProceeses b
WHERE a.ClerkId = Convert(CHAR(20), b.context_info)
AND b.spid = @@spId
GO
在做这个查询的时候，因为用到了函数，SQL Server 就无法决定使用DailyBusiness的索引，因此一旦数据量比较大的时候，就会产生严重的性能问题。
那么，解决问题的关键是在做查询的时候，再条件字句中不要使用到Convert之类的影响到SQL Server无法进行查询优化的任何函数。
还好，SQL Server 2000为我们提供了用户函数，并且让我们可以通过用户函数返回一个表变量。因此，我们可以先建立一个进行查询的用户函数，然后再创建以该用户函数为数据源的视图,脚本如下：
CREATE FUNCTION dbo.fn_DailyClerkBusiness()
RETURNS @retVal TABLE
( ClerkId CHAR(20),
OccurDate DATETIME,
……
)
AS
BEGIN
DECLARE @sClerkId CHAR(20)
SELECT @sClerkId = CONVERT(CHAR(20), context_info)
FROM MASTER.dbo.sysprocesses
WHERE spid = @@spid

INSERT INTO @retVal
SELECT ClerkId, OccurDate, …
FROM DailyBusiness
WHERE ClerkId = @sClerkId
RETURN
END
GO
CREATE VIEW v_ClerkDailyBusiness
AS
SELECT * FROM dbo.fn_ClerkDailyBusiness()
GO

通过这种方式，虽然SQL Server在调用用户函数的时候将数据集放到了临时表中，但是由于SQL Server能够对该查询进行优化，因此较之第一种方式还是能大大提高查询的性能。当然，凡事有利必有弊，后者只能作为只读视图，不能进行更新操


TRUNCATE all table
Declare @t varchar (1024)
Declare tbl_cur cursor for
select TABLE_NAME from INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE'

OPEN tbl_cur

FETCH NEXT from tbl_cur INTO @t

WHILE @@FETCH_STATUS = 0
BEGIN
EXEC ('TRUNCATE TABLE '+ @t)
FETCH NEXT from tbl_cur INTO @t
END

CLOSE tbl_cur
DEALLOCATE tbl_Cur


返回游标作为参数
用户必须指定varing 和output
create proc procauthornames
@curauthornames cusor varying output
as
set @curauthornames=cursor for
select au_fname+' '+au_lanme
from authors
open @curauthornames
利用返回的游标
declare @curauthors cursor
declare @varcharauthornames varchar(64)
exec procauthornames @curauthors output
fetch next from @curauthors into @varcharauthornames
while (@@fetch_status=0)
begin
print @varcharauthornames
fetch next from @curauthors into @varcharauthornames
end
close @curauthors
deallocate @curauthors


游标的应该与不应该(ccat)
http://www.csdn.net/develop/read_article.asp?id=14652
游标概观

相信很多Delphi程序员都写过这样的代码：

...

begin

MyDataSet.Open;

MyDataSet.Frist;

while not ( MyDataSet.BOF or MyDataSet.EOF) do

begin

...

end;

MyDataSet.Close;

end;

...

很久以来，我们习惯了用这样的代码对数据库返回的数据进行逐行操作。在用客户端程序的代码打开数据集之前，我们把它当做是一个无序集合。不过，在需要时，我们在服务器端就可以直接以行操作形式处理数据集，这就是游标。

游标的的使用方法类似前面的Delphi代码，通常有如下四步：

一、声明游标：Declare Cursor，这一过程在前面的代码中没有体现出来。然而我们都知道，在使用一个数据集之前，我们总要定义它的种种属性，比如数据源、SQL语句、打开方式等等。在游标中，数据源一般是不用指定了（因为就在当前数据库中，MS SQL Server中也可以通过SQL语言来读取异构数据源）。不过我们要为它指定一个数据集，还可以为它指定各种打开方式的设置，比如是否允许写操作，是否可以随机读取等等。一般来说，数据库系统默认的游标是只读、单向、逐行读取的。

二、打开游标：Open Cursor，一个Delphi（或其它开发平台）的数据集组件，在指定了各种必要属性后，还一定要用一个打开指令（如前面的MyDataSet.Open）来打开它，才能得到我们所需要的数据集，对于游标，我们也一样需要一个Open指令来打开它，才能使用。

三、操作数据：这一步骤通常包括移动当前游标、读取当前数据、操作代码三部分。前面设置的游标属性，很大一部分是关于这里面所能进行什么样操作的。比如，对于双向游标，我们可以向前或向后一行，而随机游标，我们甚至可以以随机方式指定游标的操作位置，但最常见的游标，是单向、只读的那一种。而对于当前行数据，我们能常是通过定义一些变量来读取，或移动到适当位置再进行写操作，这一点和一般的开发工具是一样的。至于操作代码，这虽然不是SQL的强项，但一般的数据库系统也提供基本的过程化编码能力，可以让我们完成操作。

四、关闭结果集：Close Cursor 做事要有始有终，在Delphi中，如果忘了关闭自己打开的数据集，会带来很多麻烦，而在数据库系统中，如果打开一个游标没有关上（想想吧，游标操作是要对数据上锁的），如果有很多用户都在执行这段有问题的代码……所以，在游标的标准语法中，有明确的语句，用来关闭数据集，并释放所占用的各种资源。这一点更像是OO语言中的析构函数，比Delphi的数据集组件的Close方法要做的事情要多一些。

以上各个步骤，SQL标准都约定了相应的实现代码。但具体到各个DBMS平台的实现，却是大同小异。问题就在于这一点不同之处足以把人烦死。所以，我在这里不具体写出实现方法。读者完全可以查阅自己使用的系统所带的帮助文件，看看自己用的数据库是如何实现的游标，实现了哪些功能。

不合理的存在

我们可以看到，游标与SQL语言的其它部分有相当大的差别。它的实现和操作很复杂，而且由于要逐行操作，完成同样功能的情况下，它通常比集合操作要慢。差距会有多大呢？举一个极端的例子：我曾经试验用游标给一个表填充行号，结果执行了十二个小时都没有完，而同样的操作，用前面文章提到过的不等联接，只要不到三秒钟。我保证不是所有情况下差距都会有这么大，但这种情况的确存在。特别是当大量并发任务存在时，这种长时间的锁定是很危险的。复杂和低效，是游标的最大缺点，仅仅为这两点，就足以让我们对它抱有一种谨慎的态度。而且，一般来说，需要用游标进行的操作，都可以在客户端完成（能过所谓的宿主语言，Host Language）。

也许，在极端的关系模型拥护者看来，游标是一个丑恶的存在。在一个完美、优雅，以无序的集合来管理信息的体系中，我们为什么要安置一个以有序方式逐行操作信息的游标呢？然而，正如《龙枪编年史》中，伊斯塔城神圣的帕拉丁神殿，却游荡着有史以来最黑暗的黑衣法师费斯坦但提勒斯，在庞大严谨的关系数据库中，有游标这样的另类存在。这是创造关系世界的众神之旨意，自有其道理。

存在即合理

游标虽有如此的缺憾，但它也有存在的价值。首先，当需要有序操作的数据集很大时，特别是最终的运算结果相对很小时，如果还要发到前台做，对网络资源的浪费就太大了。而且，一个很大的数据集传过来，宿主语言也不一定能支持这么庞大的数据结构（比如Delphi的VCL容器在这方面就倍受指责），这一点也限制了我们用宿主语言来扩充系统功能 （比如MS SQL Server和Interbase本来可以写扩展函数和扩展存储过程）。另外，如果要大量的逐行的写操作，与前台交互通常效率更低。游标的确不是好方法，但没有更好的方法时，它就是最好的方法。再就是以我的经验，以脚本写就的游标要比宿主语言编译后的二制代码的可维护性和可调试性要强。

俗世之中，是没有绝对的黑暗和光明的。关键，在于我们是否正确的利用它。

正确使用游标

游标本身没有所谓的对错，但在使用它时，我们应先三思而后行。

很多时候，游标未必是你想像中的唯一方法。我见过太多的游标脚本，本来都可以用更简洁高效的结构化操作完成。只要简单语句可以达到同样的效果，就不要用游标。《程序员》上读到过一句话：Simple is smart。这是软件开发的真理。

游标中，显然只读、单向的游标速度最快，而且也不容易造成死锁，尽可能用它吧。

在游标使用的表上，建立适当的索引，这么做带来的效率提高会比一般的SQL语句更明显，尤其是执行写操作的游标。

游标操作的结果集，要尽可能的小。

如果游标代码中有大量的运算，那么考虑是不是把它分散开，放到其它服务器或客户端。

对游标代码要进行充分的测试和验证，再投入使用，尤其是优化程序和稳定性。这方面不能相信系统。比如你写一个游标，每读一行把一个变量累加一，系统永远也不会主动把它优化成count(*)。

有些系统可以把当前事务打开的游标保持到以后的事务中，直到显示地关闭它。不过最好不要随便使用它。这个功能当然看起来很酷，不过滥用它会给我们带来无穷无尽的麻烦。你真的需要这种功能吗？

适当的时候，把它写成扩展存储过程或扩展存储函数，以二进制代码的形式链接进数据库系统。这样做的缺点是失去了灵活性，换来的是效率的提升。

附：

以前一直不会在Interbase的ISQL中输入多条成批执行的语句，所以什么存储过程、触发器、甚至于游标，都建不起来。直到有一天，注意了一下，发现在ISQL中是这样做的：

set term^;

...

^

...

^

...

set term;^

从第一行set term^;开始，ISQL会把用^分隔开的语句成组地发至后台执行，直至set term;^为止。有点像MS SQL Server的查询分析器的“GO”。这样，我们就可以自如地用ISQL编写脚本了。


Loop without using a cursor
Loop through records without using a cursor
Script Rating Total number of votes [74]
By: David.Poole
I sometimes have to loop through records in a database and perform a specific action on the value that is returned. For example, In the script below I loop through the user tables in sysobjects and simply print them out. This technique is useful when dropping all indices/triggers on a particular table, or adding WITH ENCRYPTION to stored procedures on a live server. Another use is on development servers where the table structure changes and you want to recompile all stored procedures and refresh all views.

DECLARE @objName VARCHAR(50)
SET @objName = ''

WHILE @objName IS NOT NULL
BEGIN
SELECT @objName = MIN( Name )
FROM SysObjects
WHERE Type='U' AND
Name > @objName

IF @objName IS NOT NULL
BEGIN
-- Insert code to do stuff here.
PRINT @objName
END
END

* * *
Example to recompile all stored procs and refresh all views.

DECLARE @objName VARCHAR(50)
SET @objName = ''

WHILE @objName IS NOT NULL
BEGIN
SELECT @objName = MIN( Name )
FROM SysObjects
WHERE Type='V' AND
Name > @objName

IF @objName IS NOT NULL
exec sp_refreshview @objName END
SET @objName = ''

WHILE @objName IS NOT NULL
BEGIN
SELECT @objName = MIN( Name )
FROM SysObjects
WHERE Type='U' AND
Name > @objName

IF @objName IS NOT NULL
exec sp_recompile @objName END


去除外键的存储过程
create proce prdropforeignkeys
@chvreferencedtable varchar(30)
as
declare @chvtablewithforeignkey varchar(30),
@chvforeignkey varchar(30),
@chvsql varchar(255)
declare cufks cursor for
select tb.name,fk.name from
((sysobject tb inner join sysreferences r on tb.id=r.fkeyid)
inner join sysobjects fk on r.constid=fk.id)
inner join sysobjects refd on refd_tb.id=r.rkeyid)
where refd_tb.name=@chvreferencedtable
open cufks
fetch next from cufks into @chvtablewithforeignkey,@chvforeignkey

while (@@fetch_status<>-1)
begin
select @chvsql='alter table'+@chvtablewithforeignkey+
'drop constraint '+@chvforeignkey
exec(@chvsql)
fetch next from cufks into @chvtablewithforeignkey,@chvforeignkey
end
deallocate cufks


 Creating a Script from a Stored Procedure
A simple task, I thought, but it took me to some interesting places. The method is broadly this:

1) Create an instance of SQL-DMO SQL Server, and use the script method to save the create table text in a file.
2) Get the text from the file into a sp variable.
3) Delete the text file.

Here are the details of the method, and a summary which puts it all together:

1) Create an instance of SQL-DMO SQL Server, and use the script method to save the create table text in a file. Here's the usage:

exec run_script 'my_server', 'my_database', 'my_table', 74077, 'my_path_name'

And here's the sp...

CREATE proc run_script
@server varchar(100),
@database_name varchar(100),
@table_name varchar(100),
@script_id int,
@path_name varchar(200) as

--runs a sql server script and outputs it to a file.

declare @i int
declare @object int
declare @return varchar(200)
declare @q varchar(200)
declare @is_error bit

set @is_error = 0

--create sql server object
EXEC @i = sp_OACreate 'SQLDMO.SQLServer', @object OUT
IF NOT @i = 0 EXEC sp_OAGetErrorInfo @object

--connect to sql server using windows nt and verify the connection EXEC
@i = sp_OASetProperty @object, 'LoginSecure', 1
IF NOT @i = 0 EXEC sp_OAGetErrorInfo @object
EXEC @i = sp_OAMethod @object, 'Connect', NULL, @server
IF NOT @i = 0 EXEC sp_OAGetErrorInfo @object
EXEC @i = sp_OAMethod @object, 'VerifyConnection', @return OUT
IF NOT @i = 0 EXEC sp_OAGetErrorInfo @object

--run the script
SET @q = 'Databases("' + @database_name + '").Tables("' + @table_name
+
'").Script(' + cast(@script_id as varchar(10)) + ', ' + @path_name +
')'
IF NOT @i = 0 begin EXEC sp_OAGetErrorInfo @object set @is_error = 1
end
EXEC @i = sp_OAMethod @object, @q, @return OUT
IF NOT @i = 0 begin EXEC sp_OAGetErrorInfo @object set @is_error = 1
end

--destroy sql server object
EXEC @i = sp_OADestroy @object
IF NOT @i = 0 EXEC sp_OAGetErrorInfo @object

return @is_error
GO

2) Get the text from the file into a sp variable. My first try was to use the FileSystemObject...

CREATE proc get_from_file @file_output varchar(8000) output,
@path_name
varchar(200) as
--outputs all the text of a file concatenated into a single string.
--Note - 255 character limitation.

DECLARE @file_output varchar(8000)
DECLARE @fso int
DECLARE @ts int
DECLARE @i int

EXEC @i = sp_OACreate 'Scripting.FileSystemObject', @fso OUT
IF NOT @i = 0 EXEC sp_OAGetErrorInfo @fso
EXEC @i = sp_OAMethod @fso, 'OpenTextFile', @ts out, @path_name
IF NOT @i = 0 EXEC sp_OAGetErrorInfo @fso
EXEC @i = sp_OAMethod @ts, 'ReadAll', @file_output out
IF NOT @i = 0 EXEC sp_OAGetErrorInfo @ts
EXEC @i = sp_OADestroy @ts
IF NOT @i = 0 EXEC sp_OAGetErrorInfo @ts
EXEC @i = sp_OADestroy @fso
IF NOT @i = 0 EXEC sp_OAGetErrorInfo @fso
GO

This, however, has a 255 character limitation - so it was back to the drawing board. I don't much like it, but I came up with this...

declare @file_output varchar(8000)
exec get_from_file @file_output output, 'my_path_name'
select @file_output

And here's the sp (with a simple supporting sp below it)...

CREATE proc get_from_file @file_output varchar(8000) output,
@path_name
varchar(200) as
--outputs all the text of a file concatenated into a single string.

set nocount on

--get_unique_name for temporary table
declare @unique_table_name varchar(100)
exec get_unique_name @unique_table_name output
set @unique_table_name = '##' + @unique_table_name

--create concatenated string and puts it into the table
exec('
create table #t1 (c1 varchar(8000))
bulk insert #t1 from ''' + @path_name + '''
declare @s varchar(8000)
set @s = ''''
select @s = @s + isnull(c1, '''') + char(13) from #t1
select c1 = @s into ' + @unique_table_name
)

--output the single value in the table to our output variable
declare @q nvarchar(100)
set @q = 'select @p1 = c1 from ' + @unique_table_name
exec sp_executesql @q, N'@P1 varchar(8000) output', @file_output
output

--drop our temporary table
exec ('drop table ' + @unique_table_name)

set nocount off
GO

Supporting sp...

CREATE proc get_unique_name @output varchar(50) output as

--outputs a unique name based on the current user and the precise time the sp is run.
--can be used for table names / file names etc.

select @output =
replace(system_user, '\', '_') + '_' +
cast(datepart(yyyy, getdate()) as varchar(4)) + '_' +
cast(datepart(mm, getdate()) as varchar(2)) + '_' +
cast(datepart(dd, getdate()) as varchar(2)) + '_' +
cast(datepart(hh, getdate()) as varchar(2)) + '_' +
cast(datepart(mi, getdate()) as varchar(2)) + '_' +
cast(datepart(ss, getdate()) as varchar(2)) + '_' +
cast(datepart(ms, getdate()) as varchar(3))
GO

3) Delete the text file. This uses a familiar method. This time there are no limitations. Here's the usage...

exec delete_file 'my_path_name'

And here's the sp...

CREATE proc delete_file @path_name varchar(200) as
--deletes a file

DECLARE @object int
DECLARE @i int

EXEC @i = sp_OACreate 'Scripting.FileSystemObject', @object OUT
IF NOT @i = 0 EXEC sp_OAGetErrorInfo @object
EXEC @i = sp_OAMethod @object, 'DeleteFile', null, @FileSpec =
@path_name
IF NOT @i = 0 EXEC sp_OAGetErrorInfo @object
EXEC @i = sp_OADestroy @object
IF NOT @i = 0 EXEC sp_OAGetErrorInfo @object
GO

Putting it all together - here's the usage...

declare @object_text varchar(8000)
exec get_create_table_script @object_text output, 'my_server',
'my_database', 'my_table'
select @object_text

And here's the sp...

CREATE proc get_create_table_script
@create_table_script varchar(8000) output,
@server varchar(100),
@database_name varchar(100),
@table_name varchar(100) as

--outputs a create table script for a sql table. To do this, it runs a script to put it into a file, then gets it from the file and deletes the file

declare @return int

--get path name of temporary sql file
declare @path_name varchar(100)
exec get_unique_name @path_name output
set @path_name = '\\' + @server + '\c$\' + @path_name + '.sql'

--create the 'create table' script and put it into sql file
exec @return = run_script @server, @database_name, @table_name, 74077,
@path_name

--return if above step errored.
if @return = 1 return

--get script results from sql file into output variable
exec get_from_file @create_table_script output, @path_name

--delete temporary sql file
exec delete_file @path_name
GO

And there's your final stored procedure which does what we set out to do.


MSSQL Stored Procedure PRINT Statements with ADO
MS SQL Server 7 Stored Procedure PRINT Statements with ADO - by Borland Developer Support Staff

Abstract: How to access the message from an MS SQL Server 7 Stored Procedure
MS SQL Server stored procedures use a number of ways of communicating information back to the user. One of these is the PRINT statement. This statement is typically used for informational type messages. If the intention is to raise an exception in the calling application, it is preferable to use the RAISERROR statement.

Many users want to use the PRINT statement, however, and it has not been possible to access informational type messages with the BDE - only RAISERROR was supported, and then only those errors of sufficient severity to raise an exception.

With ADO it is possible to access PRINT statement messages. ADO considers these messages to be errors of severity 0 - too low to raise an exception - but nevertheless the messages are written to ADO's errors collection, which, in Delphi, is a property of the TADOConnection component.

Another fact worth pointing out is that Print statement messages are only placed in the Errors collection if the ADO command is executed with the eoExecuteNoRecords Option set to True.

How do we retrieve this message in practice?

Consider the following Stored Procedure:

create PROCEDURE sp_show_test (@param1 char(30)) as
begin
If (@param1 = "Some value")
Select * from dbo.authors
else
begin
print "Illegal value : "+@param1
return 25
end
end

This will return a result set if the input parameter is set to "Some value", otherwise it will issue the "Illegal value" message and return a user defined return code of 25.

How do we deal with this in Delphi? If we just use a TADODataSet component, we risk getting an exception raised when a result set is not returned by the stored procedure. It is therefore better to use a TADOCommand component AND a TADODataSet. The Execute method of a TADOCommand can return an ADO _RecordSet object, which we can assign to the RecordSet property of the TADODataSet. If the stored procedure does not return a result set, the _RecordSet is still returned but in a closed state, which we can test for.

Here is a simple Delphi unit that allows us to see the Print statement's message. In addition to the three ADO components listed above this code requires a TEdit and TButton, and 'ADOInt' should be added to your uses clause:

procedure TForm1.Button1Click(Sender: TObject);
var
MyReturn : Integer;
looper : Integer;
MyRecordSet : _RecordSet;
begin
ADOCommand1.Prepared := True;
ADOCommand1.Parameters.ParamByName('@param1').value := Edit1.Text;
MyRecordSet := ADOCommand1.Execute;
if MyRecordSet.State = adStateOpen then
begin
ADODataSet1.Recordset := MyRecordSet;
ADODataSet1.Open;
end
else
begin
ADOCommand1.ExecuteOptions := [eoExecuteNoRecords];
ADOCommand1.Execute;
MyReturn := ADOCommand1.Parameters.ParamByName('RETURN_value').value;
for looper := 0 to ADOConnection1.Errors.Count - 1 do
begin
ShowMessage(Format('Stored Procedure %s Failed with Return Code %d and Message "%s"',
[ADOCommand1.CommandText,MyReturn,
TrimRight(ADOConnection1.Errors[looper].Description)]));
end;
end;
end;

As mentioned, when we execute the procedure the first time, we are expecting a result set to be returned. This will mean that the Errors collection will not be filled. If the result set has not been returned, we must execute the procedure a second time with the eoExecuteNoRecords option set.


存储过程。

存储过程是数据库编程里面最重要的表现方式了。

呵呵，这里我要提到上次说道的：我拒绝使用触发器。这里我要开始猛批一顿触发器了。

在SQL 2000里，说实话，我实在找不出触发器可以存在的理由。回忆一下：触发器是一种特殊的存储过程。它在一定的事件（Insert,Update,Delete 等）里自动执行。我建议使用sp和级联来代替触发器。

在SQL 7 里面，触发器通常用于更新、或删除相关表的数据，以维护数据的完整。SQL 7里面，没有级联删除和级联修改的功能。 只能建立起关系。既然SQL 2000里面提供了级联，那么触发器就没有很好的存在理由。更多的情况下是作为一个向下兼容的技术而存在。

当然，也有人喜欢把触发器作为处理数据逻辑，甚至是业务逻辑的自动存储过程。 这种方法并不足取。这里列举以下使用触发器的一些坏处：

a、“地下”运行 。
触发器没有很好的调试、管理环境。调试一段触发器，要比调试一段sp更耗费时间与精力。

b、类似于goto语句。（过分自由的另外一个说法是：无政府主义！）
一个表，可以写入多个触发器，包括同样for Update的10个触发器！同样for Delete的10个触发器。也就是说，你每次要对这个表进行写操作的时候，你要一个一个检查你的触发器，看看他们是做什么的，有没有冲突。
或许，你会很牛B的对我说：我不会做那么傻B的事情，我记得住我做了些什么！3个月以后呢？10个月以后呢？你还会对我说你记得住么？
c、嵌套触发器、递归触发器
你敢说你这么多的触发器中不会存在Table1更新了Table2表，从而触发Table2表更新TAble3，TAble3的触发器再次触发Table1更新Table2…… ？？
或许还会发生这种情况：你的程序更新了Table1.Fd1，触发器立马更新Table1.fd1，再次触发事件，触发器再次更新Table1.fd1……

当然，SQL Server可以设置和避免应用程序进入死循环，可是，得到的结果，或许就不是你想要的。

……
我想不出触发器更多的坏处了，因为我早就抛弃了它。算了，不批它了，酸是各人爱好把！我建议使用完全存储过程来实现数据逻辑和事务逻辑！

先讲讲sp的编写格式（我个人的编程习惯）。良好的习惯有助于日后的维护。

Create Proc spBuyBook( --@@存储过程头，包括名字、参数、说明文档
@iBookID int, --书的ID --@@参数
@iOperatorID int --操作员ID
)
------------------------------------------------------- @@说明文档
--Name : spBuyBook @@名字
--func : 购买一本书的业务逻辑 @@存储过程的功能
--Return: 0,正确;-1,没找到该书;-2,更新Book表出错;-3..... @@返回值解释
--Use : spDoSomething,spDoSomething2.... @@引用了那些外部程序，比如sp,fn,vw等
--User : 懒虫 @@该存储过程的使用者
--Author: 懒虫 # SapphireStudio (www.chair3.com) @@作者
--Date : 2003-5-4 @@最后更新日期
--Memo : 临时写写的，给大家作个Sample。没有调试阿。 @@备注
-------------------------------------------------------
As --@@程序开始
begin

Begin Tran --@@激活事务
Exec spDoSomething --@@调用其他sp
if @@Error<>0 --@@判断是否错误
begin
Rollback Tran --@@回滚事务
RaisError ('SQL SERVER,spBuyBook: 调用spDoSomeThing发生错误。', 16, 1) with Log --@@记录日志
Return -1 --@@返回错误号
end

.... --更多其他代码

Commit Tran --@@提交事务
end

妈 的我怎么这么背啊我？？什么时候不死机，偏偏在这时！！丢了不少……：（：（
下面默哀3分钟……

1……
2……
3……

好了，继续！回忆刚才写的内容ing ……

AA、存储过程的几个要素： a. 参数 b.变量 c.语句 d.返回值 e.管理存储过程
BB、更高级的编程要素： a.系统存储过程 b.系统表 c.异常处理 d.临时表 e.动态SQL f.扩展存储过程 g.DBCC命令

AA.a 参数： 知识要点包括：输入参数，输出参数，参数默认值

Sample:

Create Proc spTest(
@i int =0 , --输入参数
@o int output --输出参数
)
As
Set @o=@i*2 --对输出参数付值

Use the Sample:

Declare @o int
Exec spTest 33,@o output
Select @o --此时@o应该等于33*2=66。

----------------------------------------------------------------------
以上代码没有测试，顺手写写的。希望不会出错：）
--懒虫 # SapphireStudio

精彩世界，尽在3腿软件网(www.chair3.com)！！
-----------------------------------------------------------------------
AA.b 变量：AA.a中已经有声明变量的例子了，就是Declare @o int
AA.c 语句：在Sql Server 中，如果仅仅使用标准SQL语句将是不可想象的，通常认为，标准的SQL 语句就那么几条，如：
Select, Update, Delete
因此，我们需要引入更多更强大的功能，那就是T-SQL语句：

赋值语句：Set
循环语句：While
分支语句：if , Case ( Case语句不能单独使用，与一般高级语言的不同）

一起举个例子吧：
Sample :

Declare @i int
Set @i=0

While @i<100
begin

if @i<=20
begin

Select Case Cast(@i As Float)/2 When (@i/2) then Cast(@i As varchar(3)) + '是双数'
else Cast(@i As varchar(3)) + '是单数'

end

end

Set @i=@i+1
end

----------------------------------------------------------------------
以上代码判断20之内的单数与双数。
--懒虫 # SapphireStudio
精彩世界，尽在3腿软件网(www.chair3.com)！！
-----------------------------------------------------------------------
AA.d 返回值
Sample:

Create Proc spTest2
As
Return 22

Use the Sample
Declare @i int
Exec @i=spTest2
Select @i

AA.e 管理存储过程: 创建，修改，删除。
分别为：
Create Proc ... , Alter Proc ... , Drop Proc ...

BB、更高级的编程要素： a.系统存储过程 b.系统表 c.异常处理 d.临时表 e.动态SQL f.扩展存储过程 g.DBCC命令

哈哈，以下课程收费！！（玩笑，实际上打算放到后面去讲了。）

3、函数。

函数是SQL 2000的新功能。一般的编程语言都有函数，我就不用解释函数是什么东东了。：）
或许不少朋友会问：我用存储过程不就可以了么，我为什么要使用函数？

这里特别指出的一点：fn可以嵌套在Select语句中使用，而sp不可以。

这里不打算大批特批一番游标了，当然，在我的程序里面，基本抛弃了游标（这里特别说明，是“基本”！因为还是有很多地方费用导游表不可的。），转而采用了fn。游标太消耗资源了。受不了……我快要感动得要流泪了…

fn其实要比sp要简单得多。因为它的不确定性，从而也使他受到了不少的限制。
举个函数的小粒子：

Create Function fnTest ( @i int )
Returns bit
As
begin
Declare @b bit
if (Cast(@i As Float)/2)=(@i/2)
Set @b= 1
else
Set @b= 0

Return @b

end

----------------------------------------------------------------------
以上代码判断@i是单数还是双数。
--懒虫 # SapphireStudio
精彩世界，尽在3腿软件网(www.chair3.com)！！
-----------------------------------------------------------------------

Use the Sample:

Create Table #TT( fd1 int)
Declare @i int
Set @i=0
While @i<=20
begin
Insert Into #tt values(@i)
Set @i=@i+1
end

Select fd1,
'是否双数'=dbo.fnTest(fd1) --在这里调用了函数，注意哈：函数之前一定要加上他的owner.
From #tt

Drop Table #tt

----------------------------------------------------------------------
以上代码虚拟一段数据，然后判断数据表中是单数还是双数。
--懒虫 # SapphireStudio
精彩世界，尽在3腿软件网(www.chair3.com)！！
-----------------------------------------------------------------------

有了sp的编程基础，写fn也就不是什么很难的事情了。刚才我提到了，fn受到限制颇多，这里稍稍列举：

chair1. 只能调用确定性函数，不可以调用不确定函数。 比如，不可以调用GetDate()，以及自己定义的不确定性函数。
chair2. 不可以使用动态SQL 。如：Execute, sp_ExecuteSQL （这是我最痛苦的事情了，痛哭中……）
chair3. 不可以调用扩展存储过程
chair4. 不可以调用Update语句对表进行更新
chair5. 不可以在函数内部创建表(Create TAble )，修改表（Alter TAble）

等等……头脑发昏中……反正稍微一些不可预测后果，无法返回后果的都不能用。

5.事务

什么叫事务？ 这些就是数据库特有的术语了。懒虫在这里口头解释：就是把多件事情当做一件事情来处理。也就是大家同在一条船上，要活一起活，要over一起over ！

我为什么要使用事务？ 俺这里再举个很俗很俗的例子：

俺到银行存钱，于是有这么几个步骤：
1、把钱交给工作人员；2、工作人员填单；3、将单子给我签字；4、工作人员确认并输入电脑。

要是，要是我把钱交给工作人员之后，进行到3我签字了。那哥们突然心脏病发作，over掉了，那，我的钱还没有输入电脑，但我却交了钱又签字确认了，而并没有其他任何记录。我岂不是要亏死了？？？我的血汗钱啊！赶紧退给我！！

于是，在数据库里产生了这么一个术语：事务(Transaction)，也就是要么成功，要么失败，并恢复原状。

还是写程序把：

Create Proc sp我去存款(@M Money , @iOperator Int)
As
Begin
Declare @i int

Begin Tran --激活事务
Exec @i=sp交钱 @m,@iOperator
if @i<>0 --这里一般用系统错误号 @@Error。 我这里为了举例子没有用到。需要根据实际情况。
begin
Rollback Tran --回滚事务
RaisError ('银行的窗口太少了，我懒得排队，不交了！：（ ', 16, 1) with Log --记录日志
Return -1 --返回错误号
end

Exec @i=sp填单 @m,@iOperator
if @i<>0
begin
Rollback Tran --回滚事务
RaisError ('银行的哥们打印机出了点毛病，打印不出单子来，把钱退回来给我吧？？', 16, 1) with Log
Return -2
end

Exec @i=sp签字 @m
if @i<>0
begin
Rollback Tran --回滚事务
RaisError ('我 靠？什么烂银行，换了3支笔都写不出水来！！老子不存了！！不签！', 16, 1) with Log
Return -3
end

Exec @i=sp输入电脑 @m,@iOperator
if @i<>0
begin
Rollback Tran --回滚事务
RaisError ('什么意思？磁盘空间已满？好了好了，把钱给我，我到旁边的这家银行！', 16, 1) with Log
Return -4
end

Commit Tran --提交事务
Return 0
End

----------------------------------------------------------------------
以上是伪代码，模拟我去存款的过程。
--懒虫 # SapphireStudio
精彩世界，尽在3腿软件网(www.chair3.com)！！
-----------------------------------------------------------------------

事务的几个要点 Begin Tran , @@Error（我这里没有用到，见上面的注释） , Rollback Tran , Commit Tran。
另：事务可以嵌套使用。这个时候需要命名。请参见sql server online help 。


存储过程重新编译的疑难解答(一)--MS(GHOST)
INF： 存储过程重新编译的疑难解答
这篇文章中的信息适用于:
Microsoft SQL Server 7.0
概要
本文介绍应用程序在使用 Microsoft SQL Server 时可能遇到的一种特定类型的性能问题： ?′￡?存储过程的运行时重新编译。 如果您正在解决某个性能问题，但还不能确定这是否是问题的真正原因，请在继续本文之前参考 Microsoft Knowledge Base 中的下列文章：

Q224587 INF： 解决 SQL Server 中的应用程序性能问题
本文假设通过参阅以上文章缩小了引起问题的范围，并且捕获了对特定事件和其中详细数据列的 SQL Server 事件探查器跟踪。
更多信息
在 SQL Server 7.0 中创建存储过程时，过程的文本存储在 syscomments 系统表中。 在用户执行过程时，如果过程还不在高速缓存中，SQL Server 将从 syscomments（在早期版的 SQL Server 中为 sysprocedures）中加载过程，并编译查询计划。 已编译的计划保存在高速缓存中，并在以后调用存储过程时再次使用，直到发生某个操作使计划无效并强制进行重新编译为止。 下列操作可能会引起存储过程计划的重新编译。
在 CREATE PROCEDURE 或 EXECUTE 语句中使用 WITH RECOMPILE 子句。
对任何过程所引用对象的架构进行更改，包括添加或取消约束、默认值或规则。
对过程所引用的表运行 sp_recompile。
恢复包含过程或过程引用的任意对象的数据库（如果执行的是跨数据库操作）。
太多的服务器活动引起计划在高速缓存中超时。
重新编译存储过程的所有这些原因在早期版本中确实存在，并导致在开始执行过程前重新编译过程。 在 SQL Server 7.0 中，引入了一个新功能，该功能使得可以在执行期间重新编译存储过程。 这种新功能可保证优化器对过程内的每个特定语句总是有最可行的计划。 下列事件可能会引起存储过程的运行时重新编译：
存储过程引用的表中的大部分数据发生了更改。
过程交错执行数据定义语言 (DDL) 和数据操作语言 (DML) 操作。
过程在临时表上执行特定的操作。
上述所有情况的原因将在本文下面给予进一步的详细讨论。

在某些情况下，重新编译存储过程的开销远远大于所带来好处，对于大型过程尤其如此。 切记：启动重新编译时，整个批处理或过程均进行重新编译。 这意味着性能的下降直接与过程或批处理的大小成比例。 有关该主题的更多信息，请参阅 SQL Server 7.0 Books Online 中的“Transact-SQL Tips”主题。

本文下面的内容将集中在如何识别引起运行时存储过程重新编译的原因，并讨论可以用来防止重新编译的方法。

最优准则

当作为非所有者执行某个过程时，最好完全限定存储过程名称。 这样做会更明确且更易于当前用户重新使用现有执行计划。 例如，如果一个非数据库所有者 (dbo) 用户执行了数据库所有者 (dbo) 所拥有的 pubs 数据库中的存储过程（在本例中为 myProc），则应当使用下面的语句：
exec pubs.dbo.myProc
而不要使用下面的语句：
exec myProc
这种技术从编码和维护的角度，消除了不同所有者使用不同过程版本的混乱状况，同时也允许 SQL Server 更直接地访问特定过程的执行计划。
识别和解决问题

如果还没有这么做，要得到详细的信息，请参考 Microsoft Knowledge Base 中关于捕获“事件探查器”数据帮助分析系统性能的文章：
Q224587 INF： 解决 SQL Server 中的应用程序性能问题
查看“事件探查器”数据

SQL Server 事件探查器包括 SP:Recompile 事件，可以利用这个事件监控重新编译发生的次数。 只要在执行期间发生存储过程重新编译，就会发生 SP:Recompile 事件。
根据 Event Class 对“事件探查器”跟踪进行分组：

在文件菜单上，单击属性。
在 Data Columns 选项卡上，使用向上按钮移动 Groups 标题下的 Event Class 及 Text（Event Class 在先）。 使用向下按钮删除组标题下的所有其它列。
单击确定。
检查 SP:Recompile 事件个数。

可以展开 SP:Recompile 组以查看每次发生重新编译时的详细信息。 事件的 Text 列指出重新编译的存储过程名称。 如果多个过程正在引发重新编译事件，则按发生的次数进行存储?￡ 如果有大量的 SP:Recompile 事件，而且此时 CPU 占用率很高，则集中解决重新编译次数最多的过程。 请注意特定存储过程的一个实例的 SP:Recompile 事件的系统进程 ID (SPID) 和“启动时间”(Start Time)，并按照下面的步骤执行。如果没有看到任何 SP:Recompile 事件，但是仍然遇到性能问题，请参见 Microsoft Knowledge Base 中的下列文章：
Q224587 INF： 解决 SQL Server 中的应用程序性能问题
找出引发重新编译事件的语句

在文件菜单上，单击属性。
在 Data Columns 选项卡上，使用向下按钮删除 Groups 标题下的所有其它列。
在 Events 选项卡上，删除除 SP:Starting、SP:StmtStarting、SP:Recompile 和 SP:Completed 之外的所有其它事件。 如果不捕获 SP:StmtStarting 事件，可以替换为 SP:StmtCompleted 事件，但不能同时包含这两个事件，因为这样会将需要查询的信息量加倍。
如果已经识别出要检查的存储过程重新编译的特定实例，则通过使用 Filters 选项卡，可以将查看的数据限定为事件发生时的特定 SPID 和时间范围。
单击确定。

SP:Recompile 事件将在发生存储过程语句引起重新编译的 SP:StmtStarted 事件之后立即发生。 重新编译事件完成后，将会看到 SP:StmtStarted 事件重新出现，表明正在使用新生成的计划执行语句。

请考虑下列示例：
use pubsgodrop procedure RecompProc gocreate procedure RecompProc ascreate table #t (a int)select * from #tgoexec RecompProc
如果在 Query Analyzer 中执行该代码并在“事件探查器”跟踪中查看上述事件，将看到下列序列：

SP:Starting RecompProc
SP:StmtStarting create table #t (a int)
SP:StmtStarting select * from #t
SP:Recompile RecompProc
SP:StmtStarting select * from #t
SP:Completed RecompProc

您可以立即指出引起重新编译的语句是：

select * from #t
因为它出现在 SP:Recompile 事件的前后。

如果您仅捕获 SP:StmtCompleted 事件，而没有捕获 SP:StmtStarting 事件，SP:Recompile 事件将直接出现在引发该事件的语句之前，如下所示：

SP:Starting RecompProc
SP:Recompile RecompProc
SP:StmtCompleted select * from #t
SP:Completed RecompProc

可以看到 SP:Recompile 事件在 select * from #t 语句的 SP:StmtCompleted 事件之前发生，从而引起了重新编译。 这是有道理的，因为直到为重新编译生成了新查询计划之后，才能完成该语句。 本文的其余示例均使用 SP:StmtStarting 事件。 如果您仅捕获 SP:StmtCompleted 事件，切记查看 SP:Recompile 之后的语句，如上所述。

注意：如果多次执行这个特定存储过程，SQL Server 将重用该过程的现有计划。 您只在第一次执行过程时或在每次执行脚本时删除并重新创建过程时，才会看到重新编译事件。 在本文“由于交错执行数据定义语句 (DDL) 和数据操作语句 (DML) 操作引起重新编译”部分讨论这一特定情况下引起的重新编译的原因；这只是一个简单的示例以演示如何找到引起重新编译的语句。
由于行修改引起的重新编译

自从生成原始查询计划以来，如果由存储过程所引用的表中的大多数数据已发生更改，SQL Server 将重新编译存储过程以确保它有一个基于最新统计数据的计划。 例如，请考虑下列存储过程￡o
drop procedure RowModifications gocreate procedure RowModifications as-- assume SomeTable exists with the same definition as #t, -- and has over 1000 rowscreate table #t (a int, b char(10))select * from #tinsert #t select * from SomeTableselect count(*) from #t where a = 37goexec RowModificationsexec RowModifications
对于 RowModifications 过程的第二次执行，在“事件探查器”中将看到下列事件：

SP:Starting RowModifications
SP:StmtStarting create table #t (a int, b char(10))
SP:StmtStarting select * from #t
SP:StmtStarting insert #t select * from SomeTable
SP:StmtStarting select count(*) from #t where a = 37
SP:Recompile RowModifications
Auto-UpdateStats a
SP:StmtStarting select count(*) from #t where a = 37
SP:Completed RowModifications

备注：第一个执行过程也将显示 select * from #t 语句的 SP:Recompile 事件。 在这种特定情况下，重新编译的原因将在本文的“由于交错执行数据定义语句 (DDL) 和数据操作语句 (DML) 操作引起重新编译”部分进行讨论。 对于该示例，请注意如上所示的 SP:Recompile，因为每次执行过程时都发生该事件。

在本例中，由于表创建以来行数已发生更改，select count(*) from #t where a = 37 语句引起了过程的重新编译。 Auto-UpdateStats 事件的出现证实了重新编译是由于行修改引起的。 Text 列指出修改其统计数字的列。

创建 #t 表时，行数为零。 原始 select * from #t 语句的计划是用该行计数产生的，对于 select count (*) 查询的计划也一样。 但是在执行 select count(*) 前，1,000 行新行插入到 #t 表中。 由于大多数数据已更改，优化器将重新编译过程以确保为语句选择了效率最高的计划。 这个重新编译在每次执行存储过程期间都发生，因为通常认为插入 1,000 行对于保证产生重新编译足够了。

SQL Server 用来确定计划是否应重新编译的算法与用于自动更新统计的算法相同，如在下列 Microsoft Knowledge Base 文章所描述的：

Q195565 INF： How SQL Server 7.0 Autostats Work
在上述示例中，存储过程足够小，所以重新编译不会对性能有明显的影响。 但是，当有大型存储过程时，如果它执行类似的活动导致多次进行重新编译，可能会注意到性能下降。

使用以下方法消除由于行修改引起的重新编译：
使用 sp_executesql 执行语句。
这是首选的方法。 使用 sp_executesql 存储过程执行的语句并没有作为存储过程计划的一部分进行编译。 因此，执行该语句时，SQL Server 将可以随意选择使用语句高速缓存中的现有计划，或者在运行时创建新的计划。 不管哪一种情况下，正在调用的存储过程的计划不会受到影响，因而无须进行重新编译。

EXECUTE 语句有同样的效果；但是并不推荐使用它。 使用 EXECUTE 语句的效率没有使用 sp_executesql 的高，因为它不允许进行参数化查询。

上述所给出的 RowModifications 过程可以使用 sp_executesql 进行编写，如下所示：

drop procedure RowModifications2gocreate procedure RowModifications2 asset nocount on-- assume SomeTable exists with the same definition as #t, -- and has over 1000 rowscreate table #t (a int, b char(10))select * from #tinsert #t select * from SomeTableexec sp_executesql N''select count(*) from #t where a = @a'', N''@a int'', @a = 37goexec RowModifications2exec RowModifications2

如果是第二次执行 RowModifications2 过程，则在“事件探查器”中看到下列事件：

SP:Starting RowModifications2
SP:StmtStarting create table #t (a int, b char(10))
SP:StmtStarting select * from #t
SP:StmtStarting insert #t select * from SomeTable
SP:StmtStarting exec sp_executesql N''select count(*) from #t where a = @a'', N''@a int'', @a = 37
SP:Starting
SP:StmtStarting select count(*) from #t where a = @a
Auto-UpdateStats a
SP:StmtStarting select count(*) from #t where a = @a
SP:Completed
SP:Completed RowModifications2

注意 RowModifications2 过程没有 SP:Recompile 事件。 sp_executesql 调用环境具有从 SP:Starting 到 SP:Completed 的完整事件，同时 a 列有 Auto-UpdateStats 事件。 但是，由于该调用超出存储过程的环境，在这种情况下无须对 RowModifications2 过程进行重新编译。

有关使用 sp_executesql 存储过程的详细信息，请参见 SQL Server 7.0 Books Online 中的“sp_executesql (T-SQL)”和“Using sp_executesql”。
使用子过程执行引起重新编译的语句。
在这种情况下，该语句可能还会引起重新编译，但不是重新编译大型调用存储过程，而只是重新编译小型子过程。
使用 KEEP PLAN 选项。
临时表对于重新编译有特殊规则，在某些情况下，这些规则比默认的重新编译算法要严格得多。 可以使用 KEEP PLAN 选项将临时表的阈值放宽到默认算法。 有关其它信息，请参阅本文的“使用 KEEP PLAN 选项避免重新编译”部分。
备注：RowModifications 过程是由于行修改需要重新编译过程的一个非常简化的示例。 请查看以下有关该示例的警告信息：

尽管示例使用的是临时表，这种状况也适用于引用永久表的存储过程。 如果引用表中的大量数据在生成查询计划以来已被更改，则重新编译存储过程。 在本文的“使用 KEEP PLAN 选项避免重新编译”部分，讨论如何在重新编译中考虑临时表的差别。
当在临时表 #t 上第一次执行 SELECT 操作时，上述两个过程的第一次执行也会引起重新编译。进行重新编译的原因将在本文的“由于交错执行数据定义语句 (DDL) 和数据操作语句 (DML) 操作引起重新编译”部分进行讨论。
在本示例中使用 select count(*) from #t 语句，而不是使用简单的 select * from #t 语句。 为避免过多的重新编译，SQL Server 不考虑因行修改重新编译“非常小的计划”（如 select * from a table）。


存储过程重新编译的疑难解答(二)--MS(ghost)
由于交错执行数据定义语句 (DDL) 和数据操作语句 (DML) 操作引起重新编译

如果 DDL 操作是在一个过程或批处理内执行的，则过程或批处理在遇到随之发生的影响 DDL 中涉及的表的第一个 DML 操作时将进行重新编译。

请考虑下列示例存储过程：
drop procedure Interleave gocreate procedure Interleave as-- DDLcreate table #t1 (a int)-- DMLselect * from #t1-- DDLcreate index idx_#t1 on #t1(a)-- DMLselect * from #t1-- DDLcreate table #t2 (a int)-- DMLselect * from #t2goexec Interleave
如果在 Query Analyzer 中执行该代码并在“事件探查器”跟踪中查看上述事件，将看到下列序列：

SP:Starting Interleave
SP:StmtStarting create table #t1 (a int)
SP:StmtStarting select * from #t1
SP:Recompile Interleave
SP:StmtStarting select * from #t1
SP:StmtStarting create index idx_#t1 on #t1(a)
SP:StmtStarting select * from #t1
SP:Recompile Interleave
SP:StmtStarting select * from #t1
SP:StmtStarting create table #t2 (a int)
SP:StmtStarting select * from #t2
SP:Recompile Interleave
SP:StmtStarting select * from #t2
SP:Completed Interleave

在这种情况下，存储过程在执行期间重新编译三次。 要了解发生这种情况的原因，请考虑优化器为存储过程开发计划的方式：
在过程的初始编译过程中，并不存在临时表 #t1 和 #t2。 因此，没有创建引用这些表的查询的计划。 这些计划必须是在执行期间生成的。
在过程第一次执行时，第一步是创建表 #t1。 第二步是从表 #t1 中进行选择，该选择还没有计划。 因此，此时重新编译过程来为 SELECT 语句生成计划。 为表 #t1 中的当前选择生成计划，也为创建索引后表 #t1 中的选择生成计划。 没有为表 #t2 中的选择生成计划，因为表 #t2 还不存在。
下一步是对表 #t1 创建索引。 随后，在表 #t1 上执行另一个选择，现在它有来自第一次重新编译的一个计划。 但是，因为表 #t1 的架构自从生成计划后已更改，因此必须再次重新编译过程来为表 #t1 中的选择生成新计划。 由于表 #t2 还不存在，还不可以为表 #t2 中的选择生成计划。
下一步，创建表 #t2，并执行表 #t2 中的选择。 由于没有用于该语句的计划，最终一次重新编译过程。
在每次执行存储过程时都发生这些重新编译。 要减少重新编译的次数，请将过程修改为先执行所有 DDL 操作，然后接着执行 DML 操作，如下所示：

drop procedure NoInterleave gocreate procedure NoInterleave as-- All DDL firstcreate table #t1 (a int)create index idx_#t1 on #t1(a)create table #t2 (a int)-- Then DML select * from #t1select * from #t1select * from #t2goexec NoInterleave exec NoInterleave
NoInterleave 过程的第一次执行将在“事件探查器”中显示下列事件：

SP:Starting NoInterleave
SP:StmtStarting create table #t1 (a int)
SP:StmtStarting create index idx_#t1 on #t1(a)
SP:StmtStarting create table #t2 (a int)
SP:StmtStarting select * from #t1
SP:Recompile NoInterleave
SP:StmtStarting select * from #t1
SP:StmtStarting select * from #t1
SP:StmtStarting select * from #t2
SP:Completed NoInterleave

在这种情况下，所有 DDL 语句是先完成的。 优化器如下编译该过程：
在过程的初始编译过程中，并不存在临时表 #t1 和 #t2。 因此，没有创建引用这些表的查询的计划。 这些计划必须是在执行期间生成的。
过程执行的第一个步骤是 DDL 操作，创建表 #t1 和 #t2，及表 #t1 上的索引。
下一步是从表 #t1 中进行第一次选择。 由于该 SELECT 语句没有可用计划，将重新编译过程。 由于所有对象均存在，此时将为过程中的所有 SELECT 语句生成计划。
使用生成的计划执行过程的剩余部分。 由于没有对引用对象进行更改，因此无须进一步重新编译过程。
备注： 第二次执行和后续执行将利用现有查询计划和高速缓存，而且根本不会引起重新编译。 频繁使用临时表的过程应加以修改，以确保所有 DDL 语句均在该过程的开始部分。
由于某些临时表操作引起的重新编译

在存储过程中使用临时表可能会导致在每次执行过程时重新编译存储过程。

为避免这种情况，请更改存储过程以符合下列要求：
所有包含临时表名称的语句都引用在同一存储过程创建的临时表，而不是在正在调用或已调用的存储过程中创建的临时表，或在 EXECUTE 语句所执行的字符串或 sp_executesql 存储过程中创建的临时表。
包含临时表名称的所有语句句法上出现在存储过程或触发器中临时表的执行 SELECT 语句。
没有其 SELECT 语句引用临时表的 DECLARE CURSOR 语句。
包含任意临时表名称的所有语句出现在引用临时表的任何 DROP TABLE 语句之前。

在存储过程中创建的临时表不需要 DROP TABLE 语句。 在过程执行完毕时，这些表自动消失。
创建临时表（如 CREATE TABLE 或 SELECT... INTO）的语句不出现在流控制语句如 IF... ELSE 或 WHILE 中。
使用 KEEP PLAN 选项避免重新编译

在存储过程中使用临时表使得查询优化程序变得有些复杂。 表的行计数和统计信息在存储过程执行的整个期间变化非常大。 为确保优化器在所有涉及临时表的情况下使用最优计划，已开发了一种特殊算法，该算法对于重新编译更具有主动性。 该算法的原理是：如果对存储过程创建的临时表已更改六次以上，将在下一个语句引用临时表时重新编译过程。

请考虑下列示例：
drop procedure useKeepPlan gocreate procedure useKeepPlan ascreate table #t (a int, b char(3))select * from #t-- Make greater than 6 changes to #tinsert #t values (1, 'abc')insert #t values (2, 'abc')insert #t values (3, 'abc')insert #t values (4, 'abc')insert #t values (5, 'abc')insert #t values (6, 'abc')insert #t values (7, 'abc')-- Now reference #tselect count(*) from #t --option (KEEP PLAN)goexec useKeepPlanexec useKeepPlan
在这种情况下，第二次执行时在“事件查看器”中将看到下列事件：

SP:Starting useKeepPlan
SP:StmtStarting create table #t (a int)
SP:StmtStarting - The seven insert statements -
SP:StmtStarting select count(*) from #t1
SP:Recompile useKeepPlan
SP:StmtStarting select count(*) from #t1
SP:Completed useKeepPlan

对临时表 #t 进行第七次更改之后，当进行选择时将重新编译过程。

当对临时表数据分布进行更改会极大地影响引用它的语句的最优查询计划时，主动进行重新编译是非常有帮助的。 不过，在频繁修改临时表的大型过程的情况下，但并不到值得注意的程度，重新编译将导致整体性能更低。 在这种情况下引入 SELECT 语句的 KEEP PLAN 选项。

KEEP PLAN 消除了由于过程中有六次以上的临时表更改而引发的存储过程重新编译，并还原回标准算法，有关由于行修改而导致重新编译的内容在本文上述“由于行修改导致重新编译”部分给予讨论。 总而言之，KEEP PLAN 不会阻止重新编译，它只是阻止由于在过程中对引用表的更改超过六次而引发的那些重新编译。 在上述示例中，如果在存储过程的“option (KEEP PLAN)”行删除注释，将不会生成 SP:Recompile。

如果从上述代码的“option (KEEP PLAN)”行删除注释并执行它，将在“事件探查器”中将看到下列事件：

SP:Starting useKeepPlan
SP:StmtStarting create table #t (a int)
SP:StmtStarting - The seven insert statements -
SP:StmtStarting select count(*) from #t1 option (KEEP PLAN)
SP:Completed useKeepPlan

注意没有 SP:Recompile 事件。


存储过程编写经验和优化措施(water_chestnut)
Stored Procedure（存储过程）编写经验和优化措施

一、前言：在经过一段时间的存储过程开发之后，写下了一些开发时候的小结和经验与大家共享，希望对大家有益，主要是针对Sybase和SQL Server数据库，但其它数据库应该有一些共性。

二、适合读者对象：数据库开发程序员，数据库的数据量很多，涉及到对SP（存储过程）的优化的项目开发人员，对数据库有浓厚兴趣的人。

三、介绍：在数据库的开发过程中，经常会遇到复杂的业务逻辑和对数据库的操作，这个时候就会用SP来封装数据库操作。如果项目的SP较多，书写又没有一定的规范，将会影响以后的系统维护困难和大SP逻辑的难以理解，另外如果数据库的数据量大或者项目对SP的性能要求很，就会遇到优化的问题，否则速度有可能很慢，经过亲身经验，一个经过优化过的SP要比一个性能差的SP的效率甚至高几百倍。

四、 内容：

1、开发人员如果用到其他库的Table或View，务必在当前库中建立View来实现跨库操作，最好不要直接使用“databse.dbo.table_name”，因为sp_depends不能显示出该SP所使用的跨库table或view，不方便校验。

2、开发人员在提交SP前，必须已经使用set showplan on分析过查询计划，做过自身的查询优化检查。

3、高程序运行效率，优化应用程序，在SP编写过程中应该注意以下几点：

a) SQL的使用规范：

i. 尽量避免大事务操作，慎用holdlock子句，提高系统并发能力。

ii. 尽量避免反复访问同一张或几张表，尤其是数据量较大的表，可以考虑先根据条件提取数据到临时表中，然后再做连接。

iii. 尽量避免使用游标，因为游标的效率较差，如果游标操作的数据超过1万行，那么就应该改写；如果使用了游标，就要尽量避免在游标循环中再进行表连接的操作。

iv. 注意where字句写法，必须考虑语句顺序，应该根据索引顺序、范围大小来确定条件子句的前后顺序，尽可能的让字段顺序与索引顺序相一致，范围从大到小。

v. 不要在where子句中的“=”左边进行函数、算术运算或其他表达式运算，否则系统将可能无法正确使用索引。

vi. 尽量使用exists代替select count(1)来判断是否存在记录，count函数只有在统计表中所有行数时使用，而且count(1)比count(*)更有效率。

vii. 尽量使用“>=”，不要使用“>”。

viii. 注意一些or子句和union子句之间的替换

ix. 注意表之间连接的数据类型，避免不同类型数据之间的连接。

x. 注意存储过程中参数和数据类型的关系。

xi. 注意insert、update操作的数据量，防止与其他应用冲突。如果数据量超过200个数据页面（400k），那么系统将会进行锁升级，页级锁会升级成表级锁。

b) 索引的使用规范：

i. 索引的创建要与应用结合考虑，建议大的OLTP表不要超过6个索引。

ii. 尽可能的使用索引字段作为查询条件，尤其是聚簇索引，必要时可以通过index index_name来强制指定索引

iii. 避免对大表查询时进行table scan，必要时考虑新建索引。

iv. 在使用索引字段作为条件时，如果该索引是联合索引，那么必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引，否则该索引将不会被使用。

v. 要注意索引的维护，周期性重建索引，重新编译存储过程。

c) tempdb的使用规范：

i. 尽量避免使用distinct、order by、group by、having、join、cumpute，因为这些语句会加重tempdb的负担。

ii. 避免频繁创建和删除临时表，减少系统表资源的消耗。

iii. 在新建临时表时，如果一次性插入数据量很大，那么可以使用select into代替create table，避免log，提高速度；如果数据量不大，为了缓和系统表的资源，建议先create table，然后insert。

iv. 如果临时表的数据量较大，需要建立索引，那么应该将创建临时表和建立索引的过程放在单独一个子存储过程中，这样才能保证系统能够很好的使用到该临时表的索引。

v. 如果使用到了临时表，在存储过程的最后务必将所有的临时表显式删除，先truncate table，然后drop table，这样可以避免系统表的较长时间锁定。

vi. 慎用大的临时表与其他大表的连接查询和修改，减低系统表负担，因为这种操作会在一条语句中多次使用tempdb的系统表。

d) 合理的算法使用：

根据上面已提到的SQL优化技术和ASE Tuning手册中的SQL优化内容,结合实际应用,采用多种算法进行比较,以获得消耗资源最少、效率最高的方法。具体可用ASE调优命令：set statistics io on, set statistics time on , set showplan on 等。


数据库查询优化
数据库系统是管理信息系统的核心，基于数据库的联机事务处理（OLTP）以及联机分析处理(OLAP)是银行、企业、政府等部门最为重要的计算机应用之一。从大多数系统的应用实例来看，查询操作在各种数据库操作中所占据的比重最大，而查询操作所基于的SELECT语句在SQL语句中又是代价最大的语句。举例来说，如果数据的量积累到一定的程度，比如一个银行的账户数据库表信息积累到上百万甚至上千万条记录，全表扫描一次往往需要数十分钟，甚至数小时。如果采用比全表扫描更好的查询策略，往往可以使查询时间降为几分钟，由此可见查询优化技术的重要性。
笔者在应用项目的实施中发现，许多程序员在利用一些前端数据库开发工具（如PowerBuilder、Delphi等）开发数据库应用程序时，只注重用户界面的华丽，并不重视查询语句的效率问题，导致所开发出来的应用系统效率低下，资源浪费严重。因此，如何设计高效合理的查询语句就显得非常重要。本文以应用实例为基础，结合数据库理论，介绍查询优化技术在现实系统中的运用。
分析问题
许多程序员认为查询优化是DBMS（数据库管理系统）的任务，与程序员所编写的SQL语句关系不大，这是错误的。一个好的查询计划往往可以使程序性能提高数十倍。查询计划是用户所提交的SQL语句的集合，查询规划是经过优化处理之后所产生的语句集合。DBMS处理查询计划的过程是这样的：在做完查询语句的词法、语法检查之后，将语句提交给DBMS的查询优化器，优化器做完代数优化和存取路径的优化之后，由预编译模块对语句进行处理并生成查询规划，然后在合适的时间提交给系统处理执行，最后将执行结果返回给用户。在实际的数据库产品(如Oracle、Sybase等)的高版本中都是采用基于代价的优化方法，这种优化能根据从系统字典表所得到的信息来估计不同的查询规划的代价，然后选择一个较优的规划。虽然现在的数据库产品在查询优化方面已经做得越来越好，但由用户提交的SQL语句是系统优化的基础，很难设想一个原本糟糕的查询计划经过系统的优化之后会变得高效，因此用户所写语句的优劣至关重要。系统所做查询优化我们暂不讨论，下面重点说明改善用户查询计划的解决方案。
解决问题
下面以关系数据库系统Informix为例，介绍改善用户查询计划的方法。
1．合理使用索引
索引是数据库中重要的数据结构，它的根本目的就是为了提高查询效率。现在大多数的数据库产品都采用IBM最先提出的ISAM索引结构。索引的使用要恰到好处，其使用原则如下：
●在经常进行连接，但是没有指定为外键的列上建立索引，而不经常连接的字段则由优化器自动生成索引。
●在频繁进行排序或分组（即进行group by或order by操作）的列上建立索引。
●在条件表达式中经常用到的不同值较多的列上建立检索，在不同值少的列上不要建立索引。比如在雇员表的“性别”列上只有“男”与“女”两个不同值，因此就无必要建立索引。如果建立索引不但不会提高查询效率，反而会严重降低更新速度。
●如果待排序的列有多个，可以在这些列上建立复合索引（compound index）。
●使用系统工具。如Informix数据库有一个tbcheck工具，可以在可疑的索引上进行检查。在一些数据库服务器上，索引可能失效或者因为频繁操作而使得读取效率降低，如果一个使用索引的查询不明不白地慢下来，可以试着用tbcheck工具检查索引的完整性，必要时进行修复。另外，当数据库表更新大量数据后，删除并重建索引可以提高查询速度。
2．避免或简化排序
应当简化或避免对大型表进行重复的排序。当能够利用索引自动以适当的次序产生输出时，优化器就避免了排序的步骤。以下是一些影响因素：
●索引中不包括一个或几个待排序的列；
●group by或order by子句中列的次序与索引的次序不一样；
●排序的列来自不同的表。
为了避免不必要的排序，就要正确地增建索引，合理地合并数据库表（尽管有时可能影响表的规范化，但相对于效率的提高是值得的）。如果排序不可避免，那么应当试图简化它，如缩小排序的列的范围等。
3．消除对大型表行数据的顺序存取
在嵌套查询中，对表的顺序存取对查询效率可能产生致命的影响。比如采用顺序存取策略，一个嵌套3层的查询，如果每层都查询1000行，那么这个查询就要查询10亿行数据。避免这种情况的主要方法就是对连接的列进行索引。例如，两个表：学生表（学号、姓名、年龄……）和选课表（学号、课程号、成绩）。如果两个表要做连接，就要在“学号”这个连接字段上建立索引。
还可以使用并集来避免顺序存取。尽管在所有的检查列上都有索引，但某些形式的where子句强迫优化器使用顺序存取。下面的查询将强迫对orders表执行顺序操作：
SELECT ＊ FROM orders WHERE (customer_num=104 AND order_num>1001) OR order_num=1008
虽然在customer_num和order_num上建有索引，但是在上面的语句中优化器还是使用顺序存取路径扫描整个表。因为这个语句要检索的是分离的行的集合，所以应该改为如下语句：
SELECT ＊ FROM orders WHERE customer_num=104 AND order_num>1001
UNION
SELECT ＊ FROM orders WHERE order_num=1008
这样就能利用索引路径处理查询。
4．避免相关子查询
一个列的标签同时在主查询和where子句中的查询中出现，那么很可能当主查询中的列值改变之后，子查询必须重新查询一次。查询嵌套层次越多，效率越低，因此应当尽量避免子查询。如果子查询不可避免，那么要在子查询中过滤掉尽可能多的行。
5．避免困难的正规表达式
MATCHES和LIKE关键字支持通配符匹配，技术上叫正规表达式。但这种匹配特别耗费时间。例如：SELECT ＊ FROM customer WHERE zipcode LIKE “98_ _ _”
即使在zipcode字段上建立了索引，在这种情况下也还是采用顺序扫描的方式。如果把语句改为SELECT ＊ FROM customer WHERE zipcode >“98000”，在执行查询时就会利用索引来查询，显然会大大提高速度。
另外，还要避免非开始的子串。例如语句：SELECT ＊ FROM customer WHERE zipcode[2，3] >“80”，在where子句中采用了非开始子串，因而这个语句也不会使用索引。
6．使用临时表加速查询
把表的一个子集进行排序并创建临时表，有时能加速查询。它有助于避免多重排序操作，而且在其他方面还能简化优化器的工作。例如：
SELECT cust.name，rcvbles.balance，……other columns
FROM cust，rcvbles
WHERE cust.customer_id = rcvlbes.customer_id
AND rcvblls.balance>0
AND cust.postcode>“98000”
ORDER BY cust.name
如果这个查询要被执行多次而不止一次，可以把所有未付款的客户找出来放在一个临时文件中，并按客户的名字进行排序：
SELECT cust.name，rcvbles.balance，……other columns
FROM cust，rcvbles
WHERE cust.customer_id = rcvlbes.customer_id
AND rcvblls.balance>0
ORDER BY cust.name
INTO TEMP cust_with_balance
然后以下面的方式在临时表中查询：
SELECT ＊ FROM cust_with_balance
WHERE postcode>“98000”
临时表中的行要比主表中的行少，而且物理顺序就是所要求的顺序，减少了磁盘I/O，所以查询工作量可以得到大幅减少。
注意：临时表创建后不会反映主表的修改。在主表中数据频繁修改的情况下，注意不要丢失数据。
7．用排序来取代非顺序存取
非顺序磁盘存取是最慢的操作，表现在磁盘存取臂的来回移动。SQL语句隐藏了这一情况，使得我们在写应用程序时很容易写出要求存取大量非顺序页的查询。
有些时候，用数据库的排序能力来替代非顺序的存取能改进查询。

实例分析

下面我们举一个制造公司的例子来说明如何进行查询优化。制造公司数据库中包括3个表，模式如下所示：
1．part表
零件号零件描述其他列
（part_num）（part_desc）（other column）
102，032Seageat 30G disk……
500，049Novel 10M network card……
……
2．vendor表
厂商号厂商名其他列
（vendor _num）（vendor_name） （other column）
910，257Seageat Corp……
523，045IBM Corp……
……
3．parven表
零件号厂商号零件数量
（part_num）（vendor_num）（part_amount）
102，032910，2573,450,000
234，423321，0014，000，000
……
下面的查询将在这些表上定期运行，并产生关于所有零件数量的报表：
SELECT part_desc，vendor_name，part_amount
FROM part，vendor，parven
WHERE part.part_num=parven.part_num
AND parven.vendor_num = vendor.vendor_num
ORDER BY part.part_num
如果不建立索引，上述查询代码的开销将十分巨大。为此，我们在零件号和厂商号上建立索引。索引的建立避免了在嵌套中反复扫描。关于表与索引的统计信息如下：
表行尺寸行数量每页行数量数据页数量
（table）(row size)（Row count）（Rows/Pages）（Data Pages）
part15010，00025400
Vendor1501，000 2540
Parven13 15，000300 50
索引键尺寸每页键数量页面数量
（Indexes）(Key Size)（Keys/Page)(Leaf Pages）
part450020
Vendor45002
Parven825060
看起来是个相对简单的3表连接，但是其查询开销是很大的。通过查看系统表可以看到，在part_num上和vendor_num上有簇索引，因此索引是按照物理顺序存放的。parven表没有特定的存放次序。这些表的大小说明从缓冲页中非顺序存取的成功率很小。此语句的优化查询规划是：首先从part中顺序读取400页，然后再对parven表非顺序存取1万次，每次2页（一个索引页、一个数据页），总计2万个磁盘页，最后对vendor表非顺序存取1.5万次，合3万个磁盘页。可以看出在这个索引好的连接上花费的磁盘存取为5.04万次。
实际上，我们可以通过使用临时表分3个步骤来提高查询效率：
1．从parven表中按vendor_num的次序读数据：
SELECT part_num，vendor_num，price
FROM parven
ORDER BY vendor_num
INTO temp pv_by_vn
这个语句顺序读parven（50页），写一个临时表（50页），并排序。假定排序的开销为200页，总共是300页。
2．把临时表和vendor表连接，把结果输出到一个临时表，并按part_num排序：
SELECT pv_by_vn，＊ vendor.vendor_num
FROM pv_by_vn，vendor
WHERE pv_by_vn.vendor_num=vendor.vendor_num
ORDER BY pv_by_vn.part_num
INTO TMP pvvn_by_pn
DROP TABLE pv_by_vn
这个查询读取pv_by_vn(50页)，它通过索引存取vendor表1.5万次，但由于按vendor_num次序排列，实际上只是通过索引顺序地读vendor表（40＋2=42页），输出的表每页约95行，共160页。写并存取这些页引发5＊160=800次的读写，索引共读写892页。
3．把输出和part连接得到最后的结果：
SELECT pvvn_by_pn.＊，part.part_desc
FROM pvvn_by_pn，part
WHERE pvvn_by_pn.part_num=part.part_num
DROP TABLE pvvn_by_pn
这样，查询顺序地读pvvn_by_pn(160页)，通过索引读part表1.5万次，由于建有索引，所以实际上进行1772次磁盘读写，优化比例为30∶1。笔者在Informix Dynamic Sever上做同样的实验，发现在时间耗费上的优化比例为5∶1(如果增加数据量，比例可能会更大)。

小结

20％的代码用去了80％的时间，这是程序设计中的一个著名定律，在数据库应用程序中也同样如此。我们的优化要抓住关键问题，对于数据库应用程序来说，重点在于SQL的执行效率。查询优化的重点环节是使得数据库服务器少从磁盘中读数据以及顺序读页而不是非顺序读页。


 未公布的访问注册表的扩展存储过程(旭子)
SQL Server中包含了几个可以访问系统注册表的扩展存储过程.但实际上这几个扩展存储过程是未公开的,从sql server 7.0就有了,
在SQL server 2000中仍然保留,他们以后可能会删除.但是这几个存储过程却提供了在当前的SQL Server版本中访问系统注册表的能力,
而且很多人利用SQL Server来进行攻击系统时,往往都会用到这几个扩展存储过程.所以最好在SQL Server中禁用他们.

xp_regenumvalues 以多个记录集方式返回所有键值
使用方法:
xp_regenumvalues 注册表根键, 子键
比如说,想看看HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run 的所有键值:
use master
exec xp_regenumvalues 'HKEY_LOCAL_MACHINE','SOFTWARE\Microsoft\Windows\CurrentVersion\Run'

xp_regread 返回制定键的值
使用方法:
xp_regread 根键,子键,键值名
use master
exec xp_regread 'HKEY_LOCAL_MACHINE','SOFTWARE\Microsoft\Windows\CurrentVersion','CommonFilesDir'

xp_regwrite 写入注册表
使用方法:
xp_regwrite 根键,子键, 值名, 值类型, 值
use master
exec xp_regwrite 'HKEY_LOCAL_MACHINE','SOFTWARE\Microsoft\Windows\CurrentVersion','TestvalueName','reg_sz','hello'
注意值类型有2种REG_SZ 表示字符型,REG_DWORD 表示整型

xp_regdeletevalue 删除某个值
使用方法:
xp_regdeletevalue 根键,子键,值名
use master
exec xp_regdeletevalue 'HKEY_LOCAL_MACHINE','SOFTWARE\Microsoft\Windows\CurrentVersion','TestvalueName'

xp_regdeletekey 删除键,包括该键下所有值

使用方法:
use master
xp_regdeletekey 'HKEY_LOCAL_MACHINE','SOFTWARE\Microsoft\Windows\CurrentVersion\Testkey'


存储过程
1。返回不同的值，区别错在什么地方
create proc procaddperson
(@userid vchar(40),@name varchar(40),@titile varchar(40),@password varchar(40))
as
begin
if exists(select * from tableperson where userid=@userid)
begin
rollback tran
return 10 ---人已存在
end
insert into tableperson(userid,name,title,password)
values(@userid,@name,@title,convert(varbinary(300),@password))
if @@error<>0
begin
rollback tran
return 20 --意外错误
end else
begin
commit tran
return 1
end
2.取得自增编号，以便利用
在 insert 后
select @@identify
3.创建临时表
create procedure sumlogin
as
declare @count int
set @count=0
create table #temp
(spid smallint,
ecid smallint,
status nchar(30),
loginname nchar(128),
hostname nchar(128),
blk char(5),
dbname nchar(128),
cmd nchar(16)
)
insert into #temp exec sp_who
select @count=count(*) from #temp where upper(dbname)=upper(''fmeca'') and
status=''sleeping''
return @count
4.当存储过程返回表时，调用要用 open而不要用execsql
5。更新字段，当调用存储过程更新字段，而字段数目又不确定时
有的字段要更新，有的要保留，有时要全部更新，每次更新的不一定时
create proc updatetable (@f1 varchar(100),@f2 varchar(100)....)
as
update tabelname
set
field1=isnull(@f1,filed1),
field2=isnull(@f2,field2)
.....
from tablename
当不想更新时就传null对应相应的字段。
6.select ,update 不能直接调用存储过程，insert可以，见前面的例子
7。使用带一个变量的 EXECUTE ''tsql_string'' 语句
这个例子显示 EXECUTE 语句如何处理动态生成的、含有变量的字符串。这个例子创建 tables_cursor 游标来保存所有用户定义表 (type = U) 的列表。
说明 此例子只用作举例。
DECLARE tables_cursor CURSOR
FOR
SELECT name FROM sysobjects WHERE type = ''U''
OPEN tables_cursor
DECLARE @tablename sysname
FETCH NEXT FROM tables_cursor INTO @tablename
WHILE (@@FETCH_STATUS <> -1)
BEGIN
/* A @@FETCH_STATUS of -2 means that the row has been deleted.
There is no need to test for this because this loop drops all
user-defined tables. */.
EXEC (''DROP TABLE '' + @tablename)
FETCH NEXT FROM tables_cursor INTO @tablename
END
PRINT ''All user-defined tables have been dropped from the database.''
DEALLOCATE tables_cursor
8.访问不同的表
create proc dynamictable @tablename varchar(50)
as
declare @ssql varchar(200)
set @ssql=''select * from ''+@tablename
exec(@ssql)
9.使用带远程存储过程的 EXECUTE 语句
这个例子在远程服务器 SQLSERVER1 上执行 checkcontract 存储过程，在 @retstat 中保存返回状态，说明运行成功或失败。

DECLARE @retstat int
EXECUTE @retstat = SQLSERVER1.pubs.dbo.checkcontract ''409-56-4008''

10.使用带扩展存储过程的 EXECUTE 语句
下例使用 xp_cmdshell 扩展存储过程列出文件扩展名为 .exe 的所有文件的目录。

USE master
EXECUTE xp_cmdshell ''dir *.exe''

11.使用带一个存储过程变量的 EXECUTE 语句
这个例子创建一个代表存储过程名称的变量。

DECLARE @proc_name varchar(30)
SET @proc_name = ''sp_who''
EXEC @proc_name

12.使用带 DEFAULT 的 EXECUTE 语句
这个例子创建了一个存储过程，过程中第一个和第三个参数为默认值。当运行该过程时，如果调用时没有传递值或者指定了默认值，这些默认值就会赋给第一个和第三个参数。注意 DEFAULT 关键字有多种使用方法。

USE pubs
IF EXISTS (SELECT name FROM sysobjects
WHERE name = ''proc_calculate_taxes'' AND type = ''P'')
DROP PROCEDURE proc_calculate_taxes
GO
-- Create the stored procedure.
CREATE PROCEDURE proc_calculate_taxes (@p1 smallint = 42, @p2 char(1),
@p3 varchar(8) = ''CAR'')
AS
SELECT *
FROM mytable
13.使用多个参数与一个输出参数
[B] 存储过程的return好像只能返回整数，要想返回浮点数，要用output[/B]
这个例子执行 roy_check 存储过程，传递三个参数。第三个参数 @pc 是输出参数。过程 执行完后，返回变量可以从变量 @percent 得到。

说明 roy_check 存储过程只是用作举例，pubs 数据库中并没有此过程。
DECLARE @percent int
EXECUTE roy_check ''BU1032'', 1050, @pc = @percent OUTPUT
SET Percent = @percent


Disabling or Enabling All Constraints
Disabling or Enabling All Constraints of a Table or Database
By Eli Leiba

These procedures are very important to use when you need to load a bulk of data that you know is correct but when you do not need the foreign keys and check constraints to interfere while you are loading the data. This is also the case when you want to change key values and don't want the constraints to get in your way. The following procedure will disable/enable all constraints for a given parametric table.

-- PROCEDURE to disable/enable all constraints on a table

CREATE PROCEDURE sp_ConstraintState
@TblName VARCHAR(128),
@State BIT = 1
AS
DECLARE @SQLState VARCHAR(500)
IF @State = 0
BEGIN
SET @SQLState = 'ALTER TABLE '+ @TblName + ' NOCHECK CONSTRAINT ALL'
END
ELSE
BEGIN
SET @SQLState = 'ALTER TABLE ' + @TblName + ' CHECK CONSTRAINT ALL'
END
EXEC (@SQLState)
go

-- For example: exec sp_ConstraintState 'products',0
will disable all constraints on the products table in northwind

-- PROCEDURE to disable/enable all constraints in an entire databse (using sp_constraintState)

Create proc sp_disable_all_const_in_db
as
'exec sp_MsForEachTable 'sp_ConstraintState ''?'',0
go
to disbale all constraints in the database

EXEC sp_disable_all_const_in_db


GetDate Function and Stored Procedure
GetDate Function and Stored Procedure

>>Script Language and Platform: MS SQL 2000 ,MS SQL 7.0
Get a specific weekday of a month by passing any date.

--Usage explanation for Function Getting Second Tuesday of May/2003 by executing select dbo.UDF_getdate(2,3,'5/26/2003')

--Usage explanation for Stored Procedure Getting the fifth Friday of May/2003 by excuting exec usp_getdate 5,6,'5/1/2003'

--Both Function and Stored procedure can detect error inserts.

Author: Claire Hsu

/*
* Script Language and Platform:MS SQL 2000
* Objecttive: Get specific weekday of a month by passing any date
* Usage:Getting Second Tuesday of May/2003 by passing
* select dbo.UDF_getdate(2,3,'5/26/2003')
* Author:Claire Hsu
* Date :2003/5/26
* Email:messageclaire@yahoo.com
* Description:This script contains both funciton and procedure.
* You could apply accordingly
*/

--Here is the code for Function
-------------------------------------------------------------------------------
create function dbo.UDF_getdate (@week int,@dw int,@dayw1 varchar(25))
returns datetime
as
begin

declare @v1 int
declare @v2 int
declare @dayw datetime

if isdate(@dayw1) <> 1
begin
set @dayw = null
end

else
begin
set @dayw = convert(datetime,@dayw1 )
if @dw >8 or @dw <=0 or @week >=6 or @week <=0
begin
set @dayw = null
end

else
begin
set @v1 = month(@dayw)
set @dayw = cast(convert(char,@v1)+'/1/'+convert(char,year(@dayw)) as datetime)
set @v2 = datepart(dw,@dayw)

if @v2 >@dw
begin
set @dayw = dateadd(d,@week*7-(@v2-@dw),@dayw)
end

if @v2<=@dw
begin
set @dayw = dateadd(d,(@week-1)*7+(@dw-@v2),@dayw)
end

if month(@dayw)>@v1
begin
set @dayw = null
end

else
begin
set @dayw = @dayw
end
end
end
return(@dayw)
end

--Usage on single select statement
--Example1
--The following statement gives you the Second Sunday of May/2003
--select dbo.UDF_getdate(2,1,'5/35/2003')

--Example2
--The following statement gives you the Third Wednesday of Feb/2003
--select dbo.UDF_getdate(3,4,'2/14/2003')

--Example3
--The following statement gives you the Forth Monday of Dec/2003
--select dbo.UDF_getdate(4,2,'12/25/2003')

--Usage on table
--The following statement gives you the reuslt for the whole table
--select dbo.UDF_getdate(week,weekday,datetime_filed_column) from table_name
----------------------------------------------------------------------------
----------------------------------------------------------------------------

--Here is the code for Stored Procedure
create proc USP_getdate @week int,@dw int,@dayw1 varchar(25)
as
declare @v1 int
declare @v2 int
declare @dayw datetime

if isdate(@dayw1) <> 1
begin
raiserror("Out-of-Range datetime value",16,1)
end
else

begin
set @dayw = convert(datetime,@dayw1 )
if @dw >8 or @dw <=0 or @week >=6 or @week <=0
begin
raiserror("Your insert in invalid wither in week or day of the week",16,1)
end

else
begin
set @v1 = month(@dayw)
set @dayw = cast(convert(char,@v1)+'/1/'+convert(char,year(@dayw)) as datetime)
set @v2 = datepart(dw,@dayw)

if @v2 >@dw
set @dayw = dateadd(d,@week*7-(@v2-@dw),@dayw)

if @v2<=@dw
set @dayw = dateadd(d,(@week-1)*7+(@dw-@v2),@dayw)

if month(@dayw)>@v1
raiserror("Your insert is invalid in week ,the week should be
less or equal to 4",16,1)

else
select @dayw
end
end

--Usage
--exec usp_getdate 2,5,'6/23/2003' The second Thursday of Jun/2003
--exec usp_getdate 5,6,'5/1/2003' The fifth Friday of May/2003


Stored Procedure NetSend
Stored Procedure NetSend

>>Script Language and Platform: MS SQL 7.0 and MS SQL 2000
This stored procedure can be used by database administrators to inform users before breaking the user's conenction to the database prior to doing a restore or upgrade. Previously, users were notified by individual email or a phone call. Now, using this single command, every user working on the database can receive important messages from the database administrator in one second.

Author: Claire Hsu
Create procedure netsend @dbname varchar(50)
As

--Script Language and Platform: MS SQL 7.0 and MS SQL 2000
--Objecttive: Before restoreing,upgrading database,database administrator is responsible to
--inform all the users in that database that they are going to be disconnected and remind
--them to save their works.
--exec netsend 'xyz'--xyz is the database name
--Created by :Claire Hsu 2003/4/19
--Email:messageclaire@yahoo.com

create table table1000(msg varchar(500))
insert into table1000
select "net send "+ltrim(rtrim(y.hostname))+ ' "we will log you out in 2 min ,please save your work"' from master.dbo.sysprocesses y,master.dbo.sysdatabases x
where x.dbid = y.dbid and x.name = @dbname

declare @msgs varchar(500)
declare cur1 cursor for select msg from table1000
open cur1
fetch next from cur1 into @msgs
while @@fetch_status = 0
begin
exec master.dbo.xp_cmdshell @msgs
fetch next from cur1 into @msgs
end
close cur1
deallocate cur1
drop table table1000

--Usage
--exec netsend 'xyz'


Phone Number Convertor
Phone Number Convertor

>>Script Language and Platform: MS SQL 7.0 and MS SQL 2000
--Objective: To format the phone number and to convert Alphabetic telephone number to Numeric
--eg: exec telephone '900-call-cloe' will display '(900)225-5256' and
--exec telephone '609@@34$$$6068D' will display (609)346-0683

Author: Claire Hsu

create procedure telephone @phone varchar(100) as

declare @length int
declare @count int
declare @digit varchar(1)
declare @string varchar(100)
declare @ascii int

set @string = ''
set @count = 1
set @length = len(@phone)
while @count <=@length
begin
set @digit = substring(@phone, @count, 1)
set @ascii = ascii(@digit)
if @ascii >=48 and @ascii <=57
begin
set @string = @string + @digit
end
if (@ascii >=97 and @ascii <=122) or (@ascii >=65 and @ascii <=90)
begin
if @digit in ('a','b','c')
begin
set @digit = 2
set @string = @string + @digit
end
if @digit in ('d','e','f')
begin
set @digit = 3
set @string = @string + @digit
end
if @digit in ('g','h','i')
begin
set @digit = 4
set @string = @string + @digit
&n!
bsp;end
if @digit in ('j','k','l')
begin
set @digit = 5
set @string = @string + @digit
end
if @digit in ('m','n','o')
begin
set @digit = 6
set @string = @string + @digit
end
if @digit in ('p','q','r','s')
begin
set @digit = 7
set @string = @string + @digit
end
if @digit in ('t','u','v')
begin
set @digit = 8
set @string = @string + @digit
end
if @digit in ('w','x','y','z')
begin
set @digit = 9
set @string = @string + @digit
end

end

set @count = @count+1
end
set @string = substring (@string,1,10)

set @string =
"("+substring(@string,1,3)+")"+substring(@string,4,3)+"-"+substring(@string,7,4)
print @string

--Usage
--exec telephone '(609ktln1w1!@#@#@$##%$#$^%$^%$&)'
--exec telephone '((888-!@##$#%#$%#$%XXXX-xx'
--exec telephone '800-Aaa-XXxx'
--exec telephone '609@@34$$$6068D'
--exec telephone '900-call-cloe'


Sending Email in Procedure
Sending Email in Your Own Store Procedure

>>Script Language and Platform: SQL Server 2000
This SQL code can be ran from the SQL Editor or included in a stored procedure which can run as a job in SQL Server 2000 to send automated email when a certain event occurs. (Management | SQL Server Agent | jobs ) You need to have SMTP running on the Server.

Author: Mario Levesque

DECLARE @SenderAddress varchar(100)
DECLARE @RecipientAddress varchar(100)
DECLARE @Subject varchar(200)
DECLARE @Body varchar(8000)
DECLARE @oMail int --Object reference
DECLARE @resultcode int

SET @SenderAddress = 'someone@someisp.com'
SET @RecipientAddress= 'someone@someisp.net'
SELECT @Subject = 'subject of email for today ' + CAST(getdate() AS varchar(12))
SET @Body = 'This is the body of my email'

EXEC @resultcode = sp_OACreate 'CDONTS.NewMail', @oMail OUT
IF @resultcode = 0
BEGIN
EXEC @resultcode = sp_OASetProperty @oMail, 'BodyFormat', 0
EXEC @resultcode = sp_OASetProperty @oMail, 'MailFormat', 0
EXEC @resultcode = sp_OASetProperty @oMail, 'Importance', 1
EXEC @resultcode = sp_OASetProperty @oMail, 'From',
@SenderAddress
EXEC @resultcode = sp_OASetProperty @oMail, 'To',
@RecipientAddress
EXEC @resultcode = sp_OASetProperty @oMail, 'Subject',
@Subject
EXEC @resultcode = sp_OASetProperty @oMail, 'Body', @Body
EXEC @resultcode = sp_OAMethod @oMail, 'Send', NULL
EXEC sp_OADestroy @oMail
END


compare table
create procedure sp_Compare (
@TableA varchar(50),
@TableB varchar(50),
@ExcludeFields varchar(255),
@JoinFields varchar(255),
@CompType int = 0,
@debug bit = 0
)
as
begin
--Author: Subhan Munshi
--Date: 02/04/2003
--Comments: This sp can compare two tables with the same schema
-- and determine which records are different
--Usage: The following example assumes you have created this sp in Pubs and
have created a Titles2 table similar to Titles and changed some data
-- exec sp_Compare 'Titles','Titles2','Notes,PubDate','Title_Id'

--CompType 1 = Records that exist in A but not in B
-- 2 = Records that exist in B but not in A
-- 3 = Records that exist in both A and B but one or more columns compared
have different value
-- 4 = Records that exist in both A and B but all columns compared have
same value
-- 0 = Do all 4 comparisons

set nocount on
declare @SQL varchar(8000),
@select1 varchar(4000),
@from1 varchar(4000),
@where1 varchar(4000),
@select2 varchar(4000),
@from2 varchar(4000),
@where2 varchar(4000),
@select3 varchar(4000),
@from3 varchar(4000),
@where3 varchar(4000),
@select4 varchar(4000),
@from4 varchar(4000),
@where4 varchar(4000),
@ColName varchar(50),
@field varchar(50),
@len int,
@start int

--Parse Exclude Fields and populate a temp table
create table #ExcludeFields(field varchar(50))
select @start = 1
select @len = Charindex(',',@ExcludeFields,@start)-1
while @len<> -1
begin
select @field = substring(@ExcludeFields,@start,@len)
insert into #ExcludeFields(field) values ('['+@field+']')
select @ExcludeFields = substring(@ExcludeFields,@len +
2,len(@ExcludeFields))
select @len = Charindex(',',@ExcludeFields,@start)-1
end
insert into #ExcludeFields(field) values ('['+@Excludefields+']')

--Parse join fields and populate a temp table
create table #JoinFields(field varchar(50))
select @start = 1
select @len = Charindex(',',@JoinFields,@start)-1
while @len<> -1
begin
select @field = substring(@JoinFields,@start,@len)
insert into #JoinFields(field) values ('['+@field+']')
select @JoinFields = substring(@JoinFields,@len + 2,len(@JoinFields))
select @len = Charindex(',',@JoinFields,@start)-1
end
insert into #JoinFields(field) values ('['+@JoinFields+']')

--Get a list of columns name in a temp table
select ColName = '[' + rtrim(sc.Name) + ']'
into #Columns
from syscolumns sc inner join sysobjects so on sc.id = so.id
where so.Name = @TableA
order by colid

--Initialize select from and where clause
set @select1 = 'select '
set @select2 = 'select '
set @select3 = 'select '
set @select4 = 'select '

set @from1 = 'from ' + @TableA + ' A left outer join ' + @TableB + ' B on '
set @from2 = 'from ' + @TableA + ' A right outer join ' + @TableB + ' B on
'
set @from3 = 'from ' + @TableA + ' A inner join ' + @TableB + ' B on '
set @from4 = 'from ' + @TableA + ' A inner join ' + @TableB + ' B on '

set @where1 = 'where '
set @where2 = 'where '
set @where3 = 'where '
set @where4 = 'where '

--Build select from and where clauses
declare cBuildSQL cursor for
select ColName
from #Columns

open cBuildSQL
fetch next from cBuildSQL into @ColName
while @@fetch_status = 0
begin
--select clauses
set @ColName = left(@ColName,len(@ColName)-1)
--select 1
select @select1 = @select1 + @ColName + '_A] = A.' + @ColName + '], '

--select 2
select @select2 = @select2 + @ColName + '_B] = B.' + @ColName + '], '

--select 3
select @select3 = @select3 + @ColName + '_A] = A.' + @ColName + '], '
select @select3 = @select3 + @ColName + '_B] = B.' + @ColName + '], '

--select 4
select @select4 = @select4 + @ColName + '_A] = A.' + @ColName + '], '
select @select4 = @select4 + @ColName + '_B] = B.' + @ColName + '], '

set @ColName = @ColName + ']'
--from clause - Only include the fields in the Join list
if @ColName in(select field from #JoinFields)
begin
select @from1 = @from1 + 'A.' + @ColName + ' = B.' + @ColName + ' and '
select @from2 = @from2 + 'A.' + @ColName + ' = B.' + @ColName + ' and '
select @from3 = @from3 + 'A.' + @ColName + ' = B.' + @ColName + ' and '
select @from4 = @from4 + 'A.' + @ColName + ' = B.' + @ColName + ' and '
end

--where clause - Do not include the fields in the exclusion list
if @ColName not in(select field from #ExcludeFields)
begin
if @ColName in(select field from #JoinFields)
begin
select @where1 = @where1 + 'B.' + @ColName + ' is null and '
select @where2 = @where2 + 'A.' + @ColName + ' is null and '
end
else
begin
select @where3 = @where3 + 'A.' + @ColName + ' <> ' + 'B.' + @ColName +
' or '
select @where4 = @where4 + 'A.' + @ColName + ' = ' + 'B.' + @ColName + '
and '
end
end

fetch next from cBuildSQL into @ColName

end
deallocate cBuildSQL

--get rid of trailing ',' from select clause, 'and' from from clause and
'or' from where clause
set @select1 = left(@select1,len(@select1)-1)
set @select2 = left(@select2,len(@select2)-1)
set @select3 = left(@select3,len(@select3)-1)
set @select4 = left(@select4,len(@select4)-1)

set @from1 = left(@from1,len(@from1)-4)
set @from2 = left(@from2,len(@from2)-4)
set @from3 = left(@from3,len(@from3)-4)
set @from4 = left(@from4,len(@from4)-4)

set @where1 = left(@where1,len(@where1) - 4)
set @where2 = left(@where2,len(@where2) - 4)
set @where3 = left(@where3,len(@where3) - 3)
set @where4 = left(@where4,len(@where4) - 4)

if @debug = 1
begin
select field from #ExcludeFields
select field from #JoinFields

select select1 = @select1
select select2 = @select2
select select3 = @select3
select select4 = @select4

select from1 = @from1
select from2 = @from2
select from3 = @from3
select from4 = @from4

select where1 = @where1
select where2 = @where2
select where3 = @where3
select where4 = @where4
end

if @CompType = 0
begin
select Message = 'The following are the records that exist in table A but
not in table B'
exec (@select1 + ' ' + @from1 + ' ' + @where1)

select Message = 'The following are the records that exist in table B but
not in table A'
exec (@select2 + ' ' + @from2 + ' ' + @where2)

select Message = 'The following are the records that exist in both table A
and table B but one or more columns compared are different'
exec (@select3 + ' ' + @from3 + ' ' + @where3)

select Message = 'The following are the records that exist in both table A
and table B and all the columns compared are same'
exec (@select4 + ' ' + @from4 + ' ' + @where4)
end

if @CompType = 1
begin
select Message = 'The following are the records that exist in table A but
not in table B'
exec (@select1 + ' ' + @from1 + ' ' + @where1)
end

if @CompType = 2
begin
select Message = 'The following are the records that exist in table B but
not in table A'
exec (@select2 + ' ' + @from2 + ' ' + @where2)
end

if @CompType = 3
begin
select Message = 'The following are the records that exist in both table A
and table B but one or more columns compared are different'
exec (@select3 + ' ' + @from3 + ' ' + @where3)
end

if @CompType = 4
begin
select Message = 'The following are the records that exist in both table A
and table B and all the columns compared are same'
exec (@select4 + ' ' + @from4 + ' ' + @where4)
end

drop table #columns,#ExcludeFields,#JoinFields
end

点击查看用户来源及管理<br>发贴IP：61.48.16.211 2003-5-31 10:44:26
编辑这个贴子 鲜花(0) 鸡蛋(0)
ugvanxk 帅哥哟，在线，有人找我吗？

等级：版主
文章：189
积分：494
注册：2002-11-2
搜索ugvanxk在『数 据 库』的所有贴子 第2楼
发贴心情
This sp can compare two tables with the same structure.

Parameter Description:
1. @TableA: Name of first table
2. @TableB: Name of second table
3. @ExcludeFields: Comma delimited list of column names that are insignificant in the table comparison for example audit fields, notes fields, etc.
4. @JoinFields: Comma delimited list of column names that would be used to join the two tables.
5. @CompType:
1 = Records that exist in A but not in B
2 = Records that exist in B but not in A
3 = Records that exist in both A and B but one or more columns compared have different values
4 = Records that exist in both A and B but all columns compared have same value
0 = (Default)Do all 4 comparisons
6. @debug: Set this parameter to 1 to see intermediate results

Usage:
To see it work create this stored procedure in pubs database. Then create a duplicate of Titles table and call it Titles2. Add, delete and modify some records in the Titles2 table then compare the two tables using sp_compare as follows.

exec sp_Compare ''Titles'',''Titles2'',''Notes,PubDate'',''Title_Id'', 0

Author: Subhan P. Munshi


Kill only the top blocking spid
Kill only the top blocking spid

>>Script Language and Platform: Microsoft SQL Server
This script identifies the blocking locks at the top of the blocking chain and kills them. Specifically, it reports what the top blocking spids are doing, kills them, waits three seconds and then reports on current blocking status.

Author: Cameron Michelis
/*
block-sniper.sql
cjm@integer.org
http://www.Integer.org

Function: This script identifies the blocking locks at the top of the blocking chain and kills them.
Specifically, it reports what the top blocking spids are doing, kills them, waits three seconds and then reports on current blocking status.

Questions: See header above.
Updates: http://www.integer.org/cjm/files/block-sniper.sql

copyright (C) 2003 Cameron Michelis copying and redistribution of this file is permitted provided this notice and the above comments are preserved.

*/

use master
go
select getdate()
go
-- Report on blocking spids to be terminated.
select blocked as "Blocker"
,count(*) as "Victim Count"
from master..sysprocesses
where blocked > 0
group by blocked

go
DECLARE @blocker varchar(75)
DECLARE blocker_cursor CURSOR FOR select distinct blocked from sysprocesses where blocked !=0

OPEN blocker_cursor

FETCH NEXT FROM blocker_cursor INTO @blocker

WHILE (@@fetch_status <> -1)

BEGIN
IF (@@fetch_status = -2)
BEGIN
FETCH NEXT FROM blocker_cursor INTO @blocker
CONTINUE
END

-- Only kill process if it is the leader and more than 5 seconds old.
IF @blocker not in (select spid from sysprocesses where blocked != 0)
AND (select datediff(ss,last_batch,getdate()) FROM sysprocesses where blocked = @blocker) > 5

BEGIN
print 'killing head blocker, spid #' + @blocker + ' who is blocking connections with this command: '
print ''
exec ('dbcc inputbuffer (' + @blocker + ')')
exec ('kill ' + @blocker ) -- kill
FETCH NEXT FROM blocker_cursor INTO @blocker

-- Display Remaining Blocking Spids
waitfor delay '00:00:03' --wait three seconds for the blocks to clear, then display status
print ''
print 'Remaining Blockers...'
print ''
select blocked as "Blocker"
,count(*) as "Victim Count"
from master..sysprocesses
where blocked > 0
group by blocked

END

ELSE
FETCH NEXT FROM blocker_cursor INTO @blocker
END
DEALLOCATE blocker_cursor
print 'Process Complete...'

点击查看用户来源及管理<br>发贴IP：61.48.16.211 2003-5-31 10:13:28
编辑这个贴子 鲜花(0) 鸡蛋(0)
ugvanxk 帅哥哟，在线，有人找我吗？

等级：版主
文章：189
积分：494
注册：2002-11-2
搜索ugvanxk在『数 据 库』的所有贴子 第2楼
发贴心情
Kill all DB Connections/SPIDS

>>Script Language and Platform: SQL Server 2000
--No cursor kill for SPID/Connections

--Returns No. of connections killed

--
--Usage usp_killDBConnections @DBName=''NorthWind''
------------------------------------------------------------------------
-- This procedure will help to kill all connections to a given databse
/****CREATED BY AJITH DHARWAR****/
/****QUESTIONS, PLEASE EMAIL akdharwar@yahoo.com****/
/*** This procedure will accept a DB name and drop
all connections to the DB. ***/
-------------------------------------------------------------------------
CREATE PROCEDURE usp_killDBConnections @DBName varchar(50), @withmsg bit=1
AS
SET NOCOUNT ON
DECLARE @spidstr varchar(8000)
DECLARE @ConnKilled smallint
SET @ConnKilled=0
SET @spidstr = ''

IF db_id(@DBName) < 4
BEGIN
PRINT 'Connections to system databases cannot be killed'
RETURN
END

SELECT @spidstr=coalesce(@spidstr,',' )+'kill '+convert(varchar, spid)+ '; '
FROM master..sysprocesses WHERE dbid=db_id(@DBName)

IF LEN(@spidstr) > 0
BEGIN
EXEC(@spidstr)

SELECT @ConnKilled = COUNT(1)
FROM master..sysprocesses WHERE dbid=db_id(@DBName)

END

IF @withmsg =1
PRINT CONVERT(VARCHAR(10), @ConnKilled) + ' Connection(s) killed for DB ' + @DBName
GO


Delete Old Files
Delete Old Files

>>Script Language and Platform: SQL Server 7.0 and SQL Server 2000
This procedure deletes files that are older than a certain period from a folder.

Author: MAK [Muthusamy Anantha Kumar]
Create procedure USP_DelOldFiles @path varchar(25),@duration int
as
--Objective: To delete files older than certain period from a folder
--Usage example:
--Exec USP_DelOldFiles 'c:\test',30 -- which deletes files older than todaydate-30
--Created by :MAK
--Created date: Jan 7,2003
--OS: windows 2000
declare @myquery varchar(1000)
declare @query varchar(1000)
declare @name varchar(100)
set @myquery = "exec master.dbo.xp_cmdshell 'dir "+ ltrim(rtrim(@path)) + "\*.* /a/od'"
print @query

create table #Filenames (id int identity(1,1) ,name varchar(100))

insert #Filenames(name)
exec (@Myquery)
delete from #Filenames where substring(name,3,1) <> '/' or name is null or
substring(name,25,1) ='<'

Declare mycursor cursor for
select name from #Filenames where
convert(datetime,left(name,10)) <= getdate()-@duration
open mycursor

fetch next from mycursor into @name
while (@@fetch_status =0)
begin
set @query = 'exec master.dbo.xp_cmdshell "del '+@path+'\'+ ltrim(rtrim(substring(@name,40,59)))+'"'
--print @query
exec (@query)
fetch next from mycursor into @name
end
close mycursor
deallocate mycursor

drop table #Filenames


Case Sensitive Char Index Function
Case Sensitive Char Index Function

>>Script Language and Platform: SQL Server
This function returns the position of a character in a given string.

Author: STLSegwayAdmin

select dbo.udf_CharIndex('A', 'The Segway HT is Awesome!')
Script Code: SET QUOTED_IDENTIFIER ON
GO
SET ANSI_NULLS ON
GO
CREATE FUNCTION udf_CharIndex(@sChar CHAR(1), @strKey VARCHAR(94))
RETURNS INT
AS
BEGIN
/***
*
* Date: 01/22/2002
* Auth: Mike McWilliams
* Desc: Returns the position of the character with case-sensitivity
*
* @strKey is set at a length of 94 (the length of all typeable keys); but
* you can set this to any value. This only will return the first occurance
***/

DECLARE @nPOS INT
DECLARE @nRet INT

SET @nRet = -1
SET @nPOS = 1

WHILE @nPOS < LEN(@strKey) + 1
BEGIN
IF ASCII(@sChar) = ASCII(SUBSTRING(@strKey, @nPos,1))
BEGIN
SET @nRet = @nPos
BREAK
END
ELSE
SET @nPos = @nPos + 1

END

RETURN(@nRet)
END
GO
SET QUOTED_IDENTIFIER OFF
GO
SET ANSI_NULLS ON
GO


Most Legible Date Format Function
Most Legible Date Format Function

>>Script Language and Platform: MS SQL 2000
Datetime coversion is always a headache for SQL technical users. Through this function, you can get the most legible way of representing the date. It can be applied on any single day, any table with datetime field columns.

Author: Claire Hsu

create function dbo.date(@date datetime)
returns varchar(20)
As

--Script Language and Platform:MS SQL 2000
--Objecttive: New way of displaying date.This function can
--give you the most legible way to present a date
--Usage Ex: select dbo.date(DOB) from customers
--Author:Claire Hsu
--Date :2003/4/30
--Email:messageclaire@yahoo.com

begin
declare @string varchar(20)
set @string =
(select left(datename(month,@date),3)+"
"+convert(varchar(2),day(@date))+(case
when right(day(@date),1)=1 and day(@date)<>11 then 'st'
when right(day(@date),1)=2 and day(@date)<>12 then 'nd'
when right(day(@date),1)=3 and day(@date)<>13 then 'rd'
else 'th' end)+"'"+datename(year,@date))

return (@string)
end

--Usage
--select dbo.date(datetime_filed_column) from table_name
--select dbo.date('2/24/2001') as DATE

/*This is the result display

Jan 1st'2001

Mar 3rd'2003

Mar 15th'2002
*/


Get Last Friday
Function and Procedure : Get Last Friday

>>Script Language and Platform: SQL Server 2000 ,SQL Server 7.0
Given any date,this function/procedure will accurately return the date of the previous Friday.

This can be used in DTS ,Job or other query purpose.It can be used to get data of last Friday for statistic analysis or data comparison.

Author: Claire Hsu

/*
* Script Language and Platform:MS SQL 2000,MS SQL 7.0
* Objecttive: Simple scrip to return last friday
* Author:Claire Hsu
* Date :2003/5/11
* Email:messageclaire@yahoo.com
* Description:This script contains both funciton and procedure.
You could apply accordingly
*/

--Queried in Function
Create function dbo.getfri (@day datetime)
returns datetime
as
begin
declare @output datetime
if datepart(dw,@day) <> 7
set @output = dateadd(d,(-1)*(datepart(dw,@day)+1),@day)
else
set @output = dateadd(d,-1,@day)
return @output
end

--Usage1:Used in table
--Example:Table Schedule with 2 columns date1 and date2
--select dbo.getfri(date1),dbo.getfri(date2) from Schedule

--Usage2:Used in select statement
--select dbo.getfri(getdate())
--select dbo.getfri('5/11/2003')

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

--Queried as procedure
create procedure getfriday @day datetime
as
declare @output datetime
if datepart(dw,@day) <> 7
set @output = dateadd(d,(-1)*(datepart(dw,@day)+1),@day)
else
set @output = dateadd(d,-1,@day)
select @output

--Usage
--exec getfriday '2/3/2003'


Remove all freespace
Remove all freespace from all databases and truncate all transaction logs

>>Script Language and Platform: Microsoft SQL Server
For each user database,this script will remove all the freespace and truncate the transaction log.

Instructions: Run against master, the script will use the system catalog to produce a list of databases If you want to exclude a database add the excluded db name to the 'not in' list below.

Author: Cameron Michelis

/*

Function: For each user database remove all the freespace and truncate the transaction log.

Instructions: Run against master, the script will use the system catalog to produce a list of databases.
If you want to excluce a database add the excluded db name to the 'not in' list below.

cjm@integer.org
http://www.integer.org

Copyright (C) 2003 Cameron Michelis copying and redistribution of this file is permitted
provided this notice and the above comments are preserved.
*/

Set quoted_identifier off
use master
go

DECLARE @dataname varchar(30)
DECLARE @dataname_header varchar(75)
DECLARE datanames_cursor CURSOR FOR SELECT name FROM sysdatabases
WHERE name not in ('master', 'pubs', 'tempdb', 'model', 'northwind')

OPEN datanames_cursor

FETCH NEXT FROM datanames_cursor INTO @dataname

WHILE (@@fetch_status <> -1)
BEGIN
IF (@@fetch_status = -2)
BEGIN
FETCH NEXT FROM datanames_cursor INTO @dataname
CONTINUE
END
SELECT @dataname_header = "Database " + RTRIM(UPPER(@dataname))
PRINT " "
PRINT @dataname_header
EXEC("BACKUP LOG " + @dataname + " WITH TRUNCATE_ONLY")
EXEC("DBCC SHRINKDATABASE (" + @dataname + ",TRUNCATEONLY)")
FETCH NEXT FROM datanames_cursor INTO @dataname
END

DEALLOCATE datanames_cursor
PRINT ""
PRINT " "
PRINT "Free space removed and transaction log truncated for each user database"


 List objects from databases
User Stored Procedure - List objects from databases

>>Script Language and Platform: MS SQL 2000/MS SQL 7.0
This SP lists all of the objects from either all databases or from certain databases.

There are several special advantages of this stored procedure.

Without passing any database name or passing null value,this SP lists all the objects in every database
Without giving the exact database names,this SP automatically offers wildcard function to provide users all the possible databases they are looking for.
It can accept database name multiply like [Finance project plan] , [master] ,msdb
It can also accept combinational databases name ex.''[master],msdb,[Finance project plan]'',which can be passed at the same time
It also provide naming convention,user no need of checking which letter represents what object type

Author: Claire Hsu

Create proc USP_listobject @dbname varchar(200)='%' as
--Script Language and Platform:MS SQL 2000
--Objective: List all the objects in all the databases,certain databases
--Author: Claire Hsu
--Objective: To display all the objects from the list of databases
--Date: May 20,2003
--Email: messageclaire@yahoo.com
set nocount on
set @dbname = replace(@dbname,'[','')
set @dbname = replace(@dbname,']','')
declare @string varchar(50)
declare @db varchar(50)
Create table #object_dis (name sysname,xtype char(20),DBname varchar(200))
declare cur1 cursor for select '['+name+']' from sysdatabases
open cur1
fetch next from cur1 into @db
while @@fetch_status = 0
begin
set @string = 'use '+@db
exec(@string)
set @db = replace(@db,'[','')
set @db = replace(@db,']','')
insert into #object_dis select name,xtype,@db as dbname from sysobjects

fetch next from cur1 into @db
end
if @dbname <>'%' and @dbname <>''
begin
set @dbname = replace(@dbname,',','%" or dbname like "')
exec('
select name,
(case xtype when "c" then "CHECK constraint" when "D" then "Default" when "F" then "FOREIGN KEY"
when "FN" then "Scalar function" when "IF" then "Inlined table-function" when "K" then "Primary Key"
when "L" then "Log" when "P" then "Stored procedure" when "R" then "Rule" when "RF" then "Replication filter stored procedure"
when "PK" then "PRIMARY KEY" when "S" then "System table" when "TF" then "Table function" when "TR" then "Trigger" when "U" then "User table"
when "UQ" then "UNIQUE constraint" when "U" then "User Table"
when "V" then "VIEW" when "X" then "Extended stored procedure" end) as "xtype"
,dbname
from #object_dis where dbname like "'+@dbname+'%" group by dbname,xtype,name ')
end
if @dbname = '%' or @dbname = ''
exec('select name,
(case xtype when "c" then "CHECK constraint" when "D" then "Default" when "F" then "FOREIGN KEY"
when "FN" then "Scalar function" when "IF" then "Inlined table-function" when "K" then "Primary Key"
when "L" then "Log" when "P" then "Stored procedure" when "R" then "Rule" when "RF" then "Replication filter stored procedure"
when "PK" then "PRIMARY KEY" when "S" then "System table" when "TF" then "Table function" when "TR" then "Trigger" when "U" then "User table"
when "UQ" then "UNIQUE constraint" when "U" then "User Table"
when "V" then "VIEW" when "X" then "Extended stored procedure" end) as "xtype",
dbname from #object_dis where dbname like "%" group by dbname,xtype,name')
drop table #object_dis
close cur1
deallocate cur1

--Usage
--exec USP_listobject
--exec USP_listobject''
--exec USP_listobject 'msdb'
--exec USP_listobject '[ma]'
--exec USP_listobject 'ma'
--exec USP_listobject 'ma'
--exec USP_listobject 'claire,ma'
--exec USP_listobject '[claire],ma'
--exec USP_listobject '[I am ],master'
--exec USP_listobject '[I am ],[ms]'


Word Wrapping with t-sql
Word wrapping with T-SQL
Andrew Novick
15 Dec 2002
Rating: -4.00- (out of 5)

Word wrapping isn't the kind of task that is done in T-SQL very often. That's a good thing. The SQL Server engine isn't really the right location for formatting functions like wrapping. However, there are times when there isn't a suitable front end that can perform the word wrapping task and it has to be done by the database. For example, when you're generating reports for DBAs that are only run from SQL Query Analyzer.

Here is a UDF, called udf_TxtN_Wrap, that wraps a nvarchar string at a given line length. The list of word separators and the line terminator are parameters to the function. Here's the CREATE FUNCTION script:

CREATE FUNCTION dbo.udf_TxtN_Wrap (

@TxtN2Wrap nvarchar(4000) -- Input text to be wrapped
, @nMaxLineLen int -- # Characters allowed on a line.
, @Separators nvarchar(255) = ' ' -- List of characters
-- that separate words @TxtN2Wrap. Space is
-- assumed to be a separator, even if it's not
-- included in this parameter.
, @LineTerminator nvarchar(255) -- Char used to start a
-- new line. If NULL, NCHAR(10) is used.
)

RETURNS nvarchar(4000) -- The result string wrapped.
/*
* This function word wraps a nvarchar string at a given
* length. There can be several word separating characters.
* Space is always added as a separator.
*
* Attribution: loosely based on a wrapping function in
* VBScript from ASP101.com
*
* Common Usage:
select dbo.udf_TxtN_Wrap (REPLICATE ('12345 ', 200)
, 58, N' ', NULL) as [Wrapped Text]
select dbo.udf_TxtN_Wrap ('123457890', 4, N' ', NULL)
as [Wrapped at 4 with World longer than line]
* ? Copyright 2002 Andrew Novick [image] http://www.NovickSoftware.com
* You may use this function in any of your SQL Server databases
* including databases that you sell, so long as they contain
* other unrelated database objects. You may not publish this
* UDF either in print or electronically.
****************************************************************/

AS BEGIN

DECLARE @sOutput nvarchar(4000) -- working var for the result
DECLARE @nInputLen int -- Length of input
DECLARE @nCurPos int -- Current Position
DECLARE @nCurLineStart int -- current line starts
DECLARE @nPosOfLastSeparator int -- Last Separator seen

-- Save the length of the input
SET @nInputLen = Len(@TxtN2Wrap)

-- Start both character pointers at the beginning
SELECT @nCurPos = 1
, @nCurLineStart = 1
, @nPosOfLastSeparator = 0

SELECT @sOutput = '' -- empty string, not null

-- Check the parameters form some of our requirements
IF @LineTerminator is NULL SET @LineTerminator = NCHAR(10)
-- Cannot use this expression as a Default

IF CHARINDEX (N' ', @Separators) = 0 -- Space is
SET @Separators = N' ' + @Separators -- Required

-- Loop through all characters of the input
WHILE @nCurPos < @nInputLen BEGIN

-- Make note of the last separator for use later.
If CHARINDEX(SUBSTRING(@TxtN2Wrap, @nCurPos, 1)
, @Separators, 1) > 0 BEGIN
SET @nPosOfLastSeparator = @nCurPos
End -- If

-- Once we have enough for a line, go back to
-- the last separator we saw and end the line there.
IF @nCurPos >= @nCurLineStart + @nMaxLineLen - 1 BEGIN

if @nPosOfLastSeparator = 0 BEGIN
-- Cases where a word is longer than the line length

-- Append the new to the result
Set @sOutput = @sOutput
+ SUBSTRING(@TxtN2Wrap, @nCurLineStart, @nMaxLineLen)
+ @LineTerminator
SET @nCurLineStart = @nCurLineStart + @nMaxLineLen

END
ELSE BEGIN
-- Append this new line to the result
SET @sOutput = @sOutput
+ RTRIM(LTRIM(
SUBSTRING(@TxtN2Wrap
, @nCurLineStart
, @nPosOfLastSeparator
- @nCurLineStart + 1)
))
+ @LineTerminator

-- Reset the next line's starting point to the
-- point used for the last one's end + 1.
SELECT @nCurLineStart = @nPosOfLastSeparator + 1
, @nPosOfLastSeparator = 0 -- Don't have one now.

END

-- Remove any leading separators from the new line.
While CHARINDEX(SUBSTRING(@TxtN2Wrap
, @nCurLineStart, 1)
, @Separators, 1) > 0 BEGIN
SELECT @nCurLineStart = @nCurLineStart + 1
, @nCurPos = @nCurLineStart + 1
END -- While
END -- IF

-- Increment our current position.
SET @nCurPos = @nCurPos + 1
END -- While

-- If the loop ends before we add all the text, add it now.
SET @sOutput = @sOutput + RTRIM(LTrim(SUBSTRING(@TxtN2Wrap
, @nCurLineStart
, @nInputLen
- @nCurLineStart+1)
))

RETURN @sOutput
End -- Function

GRANT EXECUTE ON [dbo].[udf_TxtN_Wrap] TO [Public]
GO

Before you run udf_TxtN_Wrap, you should be aware of an few important features of the SQL Query Analyzer (QA). QA limits the length of every output column to a length set in one of its options. Use the menu Tools->Options and set the "Maximum characters per column:" field to 8192. That's the largest's number it will allow. Also, wrapped text doesn't show up if you send your results to grid. It's only works when the results go to "text" or to a file. Use the menu Query->Results in Text to set textual output.

Now that you've created the function and set the size of an output column, let's test it out:

select dbo.udf_TxtN_Wrap (REPLICATE(N'123456789 ', 20)
, 60, N' ', NULL) as [Wrapped Nubmers at 60]
GO

Here are the results:

Wrapped Nubmers at 60
----------------------------------------------------------------
123456789 123456789 123456789 123456789 123456789 123456789
123456789 123456789 123456789 123456789 123456789 123456789
123456789 123456789 123456789 123456789 123456789 123456789
123456789 123456789

Of course, you'll usually be using it on some type of string column and not on a bunch of numbers, but you get the idea. udf_TxtN_Wrap comes in handy when you have no choice but to wrap text in Query Analyzer.

About the Author

Andrew Novick is a 22-year industry veteran and principal of the New England based consulting company, Novick Software. He has recently co-authored SQL Server 2000 XML Distilled, which was published by Curlingstone in October 2002. As a long time project manager, consultant, and programmer Andy has designed and built applications for the financial services, retail, transportation, telecommunications, and real estate industries. He specializes in building systems using the Microsoft tools, usually SQL Server, Visual Basic, ASP, XML, and more recently .Net.


Finding the Nth Maximal Value
Finding the Nth Maximal value
By Eli Leiba

This procedure gets a tablename, column and an integer number N as parameters and finds the Nth maximum value of the column's value in a table.

For example: running it with 'products', 'UnitPrice' , 13 , @res

will get the 13TH largest value of unitprice from products is no such N exist an error message is printed.

Procedure code and usage is listed below:
=====================================
create proc max_nth_value (@tablename varchar(50),
@column varchar(50),
@n int,
@res decimal (10,4) OUTPUT)
AS
set nocount on
declare @sqlStatment varchar(200)
set @sqlStatment = 'select a.' + @column + ' from ' + @tableName + ' a ' +
'where ' + convert (varchar(10),@n) +
'=(select count(distinct ' + @column + ')' +
' from ' + @tableName + ' b ' +
' where ' + 'a.'+ @column + ' <= ' + 'b.' + @column + ')'
create table #tres (x decimal (10,4))
insert into #tres exec (@sqlStatment)
if @@rowcount = 0 print 'No value found!' else select @res = x from #tres
set nocount off
go

-- usage
declare @res decimal (10,4)
exec max_nth_value 'products','unitprice',8,@res OUTPUT
print @res


Script to Count Rows in SPs
Script to Count Rows in SPs

This stored procedure for SQL Server 7.0 and 2000 will return the number of rows for stored procedures. Setting the parameter to a stored procedure name will return the count of rows for that procedure. Omitting the parameter or setting it to NULL will return all the stored procedures in the database and the row count for each.

Updated on 3/12/02 to correct a typographical error -- the script contained an extra "and" that made it fail.

Author: Neil Mederich
USE master
go
IF EXISTS(SELECT * FROM sysobjects WHERE id = object_id('sp_getsprowcount') and objectproperty(id,'IsProcedure')=1)
drop proc sp_getSProwcount
go
CREATE procedure sp_getSProwcount @ProcName varchar(128)=NULL
as
-------------------------------------------------------------------
-- Author: Neil Mederich
-- Date: 2/6/2002
--Purpose: Provide row counts for stored procedures
--Remarks: Will not provide row count for encrypted procedures
-------------------------------------------------------------------
SET nocount on

DECLARE @strSQL varchar(350)
DECLARE @dbname sysname

CREATE TABLE #spcount (sptext varchar(255))

IF @ProcName IS NOT NULL
BEGIN
IF object_id(@ProcName) is null
BEGIN
SELECT @dbname = db_name()
RAISERROR(15009,-1,-1,@ProcName,@dbname)
RETURN (1)
END

SET @strSQL = 'insert #spcount EXECUTE sp_helptext ''' + @ProcName + ''''
EXECUTE(@strsql)

SELECT @ProcName as spname,
count(*) as [rows]
FROM #spcount

RETURN(0)
END

SELECT b.[name] + '.' + a.[name] as spname,
0 as [rows]
INTO #one
FROM sysobjects a INNER JOIN sysusers b
ON a.uid = b.uid
WHERE xtype = 'p'

DECLARE @x varchar(257)
DECLARE @count int

DECLARE cone CURSOR FOR
SELECT spname
FROM #one

OPEN cone

FETCH NEXT FROM cone INTO @x

WHILE @@FETCH_status=0
BEGIN
TRUNCATE TABLE #spcount
SET @strSQL = 'insert #spcount EXECUTE sp_helptext ''' + @x + ''''
EXECUTE(@strsql)
SELECT @count = count(*) FROM #spcount
UPDATE #one SET [rows] = @count WHERE spname = @x
FETCH NEXT FROM cone INTO @x
END
CLOSE cone
DEALLOCATE cone

SELECT * FROM #one
go


看了一篇关于动态生成的自动编号的文章，我觉得过于的复杂,有很多的地方都可以用SQLServer提供的函数替换。以下是我在和同事一起的工作中写成的，简单可行。

说明:
档案号的格式: 四位年+两位月+两位日+四位档案号
即:200202260001
档案编号表:记录当前最新的档案编号,只有一个记录.为了防止记录 出现重复的情况.
CREATE PROCEDURE prGetNo --产生按年月日排列的档案号
@No varchar(12) output ---为产生的档案号
AS
declare @Year Int,
@Month int,
@Day int,
@Temp_No varchar(12),
@NeedNo varchar(4) --档案号
BEGIN
select @year=Year(GetDate())
select @Month=Month(GetDate())
select @Day=Day(GetDate())
select @No=Str(@Year,4,0)+
(select
case
when @Month>=10 then Str(@Month,2,0)
when @Month<10 Then '0'+Str(@Month,1,0)
end)+
(select
case
when @Day>=10 then Str(@Day,2,0)
when @Day<10 Then '0'+Str(@Day,1,0)
end)
SELECT @Temp_No = 档案编号 FROM 档案编号表
IF @No <> SUBSTRING( @Temp_No,1,8)
SELECT @No = @No+'0001'
ELSE
BEGIN
SELECT @RandNo = STR(CONVERT(int,(SUBSTRING( @Temp_No ,9,4))+1),4,0)
SELECT @RandNo = REPLACE( @RandNo,' ','0')
SELECT @No = @No + @RandNo
END
UPDATE 档案编号表
SET 档案编号 = @No
END
END


RC4 Encryption/Decryption
Script to Perform RC4 Encryption/Decryption

This stored procedure will perform RC4 encryption/decryption on strings up to 255 characters in length. The procedure works as is on SQL 7 and 2000; in order to run on SQL 6.5, you will need to change the SET statements to SELECT statements.

Author: Brad Baker

/*
* Name:
* uspGetProductCode
*
* Purpose:
* This procedure uses RC4 to encrypt/decrypt the first parameter
* using the second parameter in the encryption key.
*
* Input Parameters:
* @product varchar(255) - The string to be encrypted/decrypted.
* @productCode varchar(32) - The string key used to encrypt/decrypt.
*
* Output Parameters:
* @productName varchar(255) - The encrypted/decrypted string.
*
* Returns:
* none
*
* DependsOn:
* none
*
* Calls:
* none
*
* Effects:
* none
*
* CalledBy:
* n/a
*
* History:
* Date Developer Description
* ------------- ------------ --------------------------------------
* 01/22/2002 Brad Baker Created procedure.
*
* Notes:
* 1. RC4 is a stream cipher symmetric key algorithm.
* It was developed in 1987 by Ronald Rivest and kept as a trade secret by RSA Data Security.
* On September 9, 1994, the RC4 algorithm was anonymously posted on the Internet on the Cyperpunks?揳nonymous remailers?list.
*
* 2. RC4 uses a variable length key from 1 to 256 bytes to initialize a 256-byte state table.
* The state table is used for subsequent generation of pseudo-random bytes and then to generate a pseudo-random stream which is XORed
* with the plaintext to give the ciphertext.
* Each element in the state table is swapped at least once.
*
* 3. The RC4 key is often limited to 40 bits, because of export restrictions but it is sometimes used as a 128 bit key.
* It has the capability of using keys between 1 and 2048 bits.
* RC4 is used in many commercial software packages such as Lotus Notes and Oracle Secure SQL.
* It is also part of the Cellular Specification.
*
* 4. Procedure name and parameter names should be meaningless.
* Renaming this procedure RC4 would be very helpful to hackers.
*
* 5. This procedure should always be encrypted.
*
* 6. Since this procedure is used for both encryption and decryption,
* it is important to limit permissions to it.
*
* Example:
* DECLARE
* @encrypted varchar(255)
* EXEC uspGetProductCode 'mypassword', 'mykey', @encrypted OUTPUT
*/
ALTER PROCEDURE uspGetProductCode
(@product varchar(255),
@productCode varchar(32),
@productName varchar(255) OUTPUT)
WITH ENCRYPTION
AS
-- Eliminate rowcount messages.
SET NOCOUNT ON
-- Declare variables.
DECLARE
@state smallint,
@key smallint,
@tempSwap smallint,
@a smallint,
@b smallint,
@N smallint,
@temp smallint,
@i smallint,
@j smallint,
@k smallint,
@cipherby smallint,
@cipher varchar(255),
@code varchar(64)

-- The RC4 algorithm works in two phases, key setup and ciphering.
-- Key setup is the first and most difficult phase of this algorithm.
-- During a N-bit key setup (N being your key length),
-- the encryption key is used to generate an encrypting variable using two arrays,
-- state and key, and N-number of mixing operations.
-- These mixing operations consist of swapping bytes,
-- modulo operations, and other formulas.
-- A modulo operation is the process of yielding a remainder from division.
-- This implementation of RC4 uses temporary tables in place of arrays.
CREATE TABLE #Keys
(
ID smallint ,
state smallint ,
intKey smallint
)
-- Initialize variable values.
SET @code = @productcode + ',./;''p[\`0-=Z<?L:"P{|~!@#%^*(_'
SELECT
@N = Len(@code),
@a = 0
-- Initialize temp table with key and state values.
WHILE @a < 256
BEGIN
SELECT
@key = Ascii(Substring(@code, (@a%@N) + 1, 1)),
@state = @a
INSERT #Keys(ID, state, intKey) valueS(@a, @state, @key)
SET @a = @a + 1
END

-- Initialize variable values.
SELECT
@b = 0,
@a = 0
-- The state array now undergoes 256 mixing operations.
WHILE @a < 256
BEGIN
SELECT @b = (@b + state + intKey) % 256, @tempSwap = state
FROM #Keys
WHERE ID = @a
UPDATE #Keys
SET state = (SELECT state FROM #Keys WHERE ID = @b)
WHERE ID = @a
UPDATE #Keys
SET state = @tempSwap
WHERE ID = @b
SET @a = @a + 1
END

-- Initialize variable values.
SELECT
@i = 0,
@j = 0,
@a = 1,
@cipher = '',
@cipherby = 0

-- Once the encrypting variable is produced from the key setup,
-- it enters the ciphering phase,
-- where it is XORed with the plain text message to create and encrypted message.
-- XOR is the logical operation of comparing two binary bits.
-- If the bits are different, the result is 1.
-- If the bits are the same, the result is 0.
-- The string is decrypted by XORing the encrypted message with the same encrypting key.
WHILE @a < Len(@product) + 1
BEGIN
SET
SELECT
@i = (@i + 1) % 256,
@j = (@j + state) % 256,
@temp = state
FROM #Keys
WHERE ID = @i
UPDATE #Keys
SET state = (SELECT state FROM #Keys WHERE ID = @j)
WHERE ID = @i
UPDATE #Keys
SET state = @temp
WHERE ID = @j
SELECT @k = state
FROM #Keys
WHERE ID = (((SELECT state FROM #Keys WHERE ID = @i) + (SELECT state FROM #Keys WHERE ID = @j)) % 256)
SET @cipherby = Ascii(Substring(@product, @a, 1)) ^ @k
SET @cipher = @cipher + Char(@cipherby)
SET @a = @a + 1
END

-- Clean up.
DROP TABLE #Keys
-- Set ouput variable.
SET @productName = @cipher
SET NOCOUNT OFF


SET QUOTED_IDENTIFIER ON
GO
SET ANSI_NULLS ON
GO

if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[sp_drop_table]') and OBJECTPROPERTY(id, N'IsProcedure') = 1)
drop procedure [dbo].[sp_drop_table]
GO

CREATE PROCEDURE sp_drop_table
@table_name nvarchar(128) /***Table to drop***/
AS
/****CREATED BY JASON BOHANON****/
/****QUESTIONS, PLEASE EMAIL j_bohanon@msn.com****/

/*** This procedure will accept a table name and drop
all constraints on the table before executing
a DROP TABLE command. ***/
SET NOCOUNT ON
DECLARE
@alter_table1 nvarchar(4000),
@key_name nvarchar(128),
@child_table nvarchar(128),
@drop_table1 nvarchar(4000)

BEGIN /***CREATE CONSTRAINT DROP SCRIPTS***/
CREATE TABLE #tmp_drop_keys
(
key_name nvarchar(128) COLLATE database_default,
child_table nvarchar(128) COLLATE database_default,
used bit
)
INSERT INTO #tmp_drop_keys
(
key_name,
child_table,
used
)
SELECT
d.name,
c.name,
0
FROM
sysobjects a
JOIN sysforeignkeys b
on a.id = b.rkeyid
JOIN sysobjects c
on c.id = b.fkeyid
JOIN sysobjects d
on d.id = b.constid
WHERE
a.name = @table_name

WHILE EXISTS (SELECT * FROM #tmp_drop_keys WHERE used = 0)
BEGIN
SELECT TOP 1
@key_name = key_name,
@child_table = child_table
FROM
#tmp_drop_keys
WHERE
used = 0

SELECT @alter_table1 =
' ALTER TABLE ' + @child_table +
' DROP CONSTRAINT ' + @key_name

EXEC (@alter_table1)

UPDATE
#tmp_drop_keys
SET
used = 1
WHERE
key_name = @key_name
END
SELECT @drop_table1 = 'DROP TABLE ' + @table_name
EXEC (@drop_table1)

END

GO
SET QUOTED_IDENTIFIER OFF
GO
SET ANSI_NULLS ON
GO


Three General Purpose Database
Three General Purpose Database Scripts

Here are three general purpose scripts for degragmenting and speeding up large databases. You can schedule the SPs for periodic execution, but they should be run in absence of user activity on the database (might slow down users otherwise). The SPs were tested against SQL Server 7.0 but should also work on SQL Server 6.5 and 2000.

The scripts are as follows:

exec sp_CheckIdent -- checks identity colulmns for all user tables

go

exec sp_AdminReindexTables -- reindexes all user tables

go

exec sp_AdminRecompileStoredProc -- recompiles all SPs

go

The script has been updated as of 1/7/02 with corrections and user input added to the comments text. The corrections address scenarios when table names might have spaces or weird symbols in them.

Author: Alexei Akimov


Also they might be used after substantial data population/data scrubbing.

Execution should be done in absence of user activity on the DB (might slow down users).

-- exec sp_CheckIdent -- checks identity colulmns for all user tables
-- go
-- exec sp_AdminReindexTables -- reindexes all user tables
-- go
-- exec sp_AdminRecompileStoredProc -- recompiles all SPs
-- go

These SPs were tested against SQL Server 7.0 and work on production DBs on
a large Real Estate SQL/VB package. They should work also on 6.5 and 2000 SQL Server.

Input - cortesy of Christopher B. Snider:
If your tables contain spaces in table name or some weird symbols, you may try to:
enclose table name in square brackets like this:

exec ("dbcc checkident ([" + @TableName + "])")

Wrapping the table name in the brackets "[ ]" allows the odd names that
exist in some DBs.

I also discovered that the brackets do not work at all when the table name has parentheses
"( )" in it. I have some tables in a DB that was written in ACCESS and
migrated to SQL Server that contain the parens... I am still searching
for a better way of accessing those tables with the SP's, but the
sysnames table does not show the name with the parens.

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

CREATE PROCEDURE sp_AdminRecompileStoredProc
AS
BEGIN

/*********************************************************************************
Changes, starting from the latest:

SP written to recompile all user SPs in the current Database

You can schedule these SPs for periodic execution.
They might be useful for defragmenting and speeding up large DBs.
Also they might be used after substantial data population/data scrubbing.

Execution should be done in absence of user activity on the DB (might slow down users).

-- exec sp_CheckIdent -- checks identity colulmns for all user tables
-- go
-- exec sp_AdminReindexTables -- reindexes all user tables
-- go
-- exec sp_AdminRecompileStoredProc -- recompiles all SPs
-- go

These SPs were tested against SQL Server 7.0 and work on production DBs on
a large Real Estate SQL/VB package. They should work also on 6.5 and 2000 SQL Server.

Alexei Akimov alakimov@hotmail.com
Colliers International, SQL DB Design, VB, ASP, Crystal Development
tel: 416-777-9377, ext.332
P.S. I am looking for job in SQL Server/Oracle/VB DB Design development
in USA by TN1(Canadian citizen) or H1B Visa.
******************************************************************/

declare @continue integer,
@TableCounter integer,
@string varchar(80),
@TableName varchar(50),
@SQL varchar (255)

set nocount on

declare curTable cursor

for SELECT name
FROM sysobjects
WHERE type = 'U'
order by name
for read only

-- open cursor
open curTable

select
@continue = 1,
@TableCounter = 0,
@string = ""

while (@continue = 1)
begin

fetch next from curTable
into @TableName

if (@@FETCH_STATUS <> 0)
begin
--fetch failed; end loop
select @continue = 0
break
end --if fetchstatus <> 0

else -- continue next table
begin
--successful fetch
select @TableCounter = @TableCounter + 1

select @SQL = "sp_recompile " + @TableName

/********** For Debug
select "@SQL" = @SQL
***************************/

exec (@SQL)

end -- else successful fetch

end --while (@continue = 1)

-- final cleanup
close curTable
deallocate curTable

select "Total Number of Tables:" = @TableCounter

END

go

CREATE PROCEDURE sp_AdminReindexTables
AS
BEGIN

/*********************************************************************************
Changes, starting from the latest:

SP written to reindex all user tables in the current DB

You can schedule these SPs for periodic execution.
They might be useful for defragmenting and speeding up large DBs.
Also they might be used after substantial data population/data scrubbing.

Execution should be done in absence of user activity on the DB (might slow down users).

-- exec sp_CheckIdent -- checks identity colulmns for all user tables
-- go
-- exec sp_AdminReindexTables -- reindexes all user tables
-- go
-- exec sp_AdminRecompileStoredProc -- recompiles all SPs
-- go

These SPs were tested against SQL Server 7.0 and work on production DBs on
a large Real Estate SQL/VB package. They should work also on 6.5 and 2000 SQL Server.

Alexei Akimov alakimov@hotmail.com
Colliers International, SQL DB Design, VB, ASP, Crystal Development
tel: 416-777-9377, ext.332

P.S. I am looking for job in SQL Server/Oracle/VB DB Design development
in USA by TN1(Canadian citizen) or H1B Visa.

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

declare @continue integer,
@TableCounter integer,
@string varchar(80),
@TableName varchar(50),
@SQL varchar (255)

set nocount on

declare curTable cursor

for SELECT name
FROM sysobjects
WHERE type = 'U'

order by name
for read only

-- open cursor
open curTable

select
@continue = 1,
@TableCounter = 0,
@string = ""

while (@continue = 1)
begin

fetch next from curTable
into @TableName

if (@@FETCH_STATUS <> 0)
begin
--fetch failed; end loop
select @continue = 0
break
end --if fetchstatus <> 0

else -- continue next table
begin
--successful fetch
select @TableCounter = @TableCounter + 1

-- DBCC DBREINDEX (authors, '', 70)

select @SQL = "DBCC DBREINDEX (" + @TableName + ", '', 70) "

/********** For Debug
select "@SQL" = @SQL
***************************/

exec (@SQL)

end -- else successful fetch

end --while (@continue = 1)

-- final cleanup
close curTable
deallocate curTable

select "Total Number of Tables:" = @TableCounter

END

go

CREATE PROCEDURE sp_CheckIdent

AS
BEGIN

/******************************************************************
Changes, starting from the latest:

CheckIdent SP runs DBCC checkident command for all user tables;
Checking Identity for all User tables
You can schedule these SPs for periodic execution.
They might be useful for defragmenting and speeding up large DBs.
Also they might be used after substantial data population/data scrubbing.

Execution should be done in absence of user activity on the DB (might slow down users).

-- exec sp_CheckIdent -- checks identity colulmns for all user tables
-- go
-- exec sp_AdminReindexTables -- reindexes all user tables
-- go
-- exec sp_AdminRecompileStoredProc -- recompiles all SPs
-- go

These SPs were tested against SQL Server 7.0 and work on production DBs on
a large Real Estate SQL/VB package. They should work also on 6.5 and 2000 SQL Server.

Alexei Akimov alakimov@hotmail.com
Colliers International, SQL DB Design, VB, ASP, Crystal Development
tel: 416-777-9377, ext.332

P.S. I am looking for job in SQL Server/Oracle/VB DB Design development
in USA by TN1(Canadian citizen) or H1B Visa.

********************************************************************
--Below goes List of commonly used DBCC and information commands
-- exactly how they are spelled:

--dbcc checktable (tablename)
--dbcc checkident (tablename)
--dbcc memusage
--dbcc show_statistics (tablename, indexname)
--Update Statistics tablename -- not dbcc command, no brackets, no quotes,exactly like shown

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

Print "sp_CheckIdent"
Print "Checking Identity for all User Tables"
print "This may take up to 3-5 minutes"
Print "*******************************************************"

declare
@continue integer,
@TableCounter integer,
@string varchar(80),
@TableName varchar(50)

set nocount on

declare curTable cursor

for SELECT name
FROM sysobjects
WHERE type = 'U'
for read only

-- open cursor
open curTable

select
@continue = 1,
@TableCounter = 0,
@string = ""

Loop:
while (@continue = 1)
begin

fetch next from curTable
into @TableName

if (@@FETCH_STATUS <> 0)
begin
--fetch failed; end loop
select @continue = 0
break
end --if fetchstatus <> 0

else -- continue next table
begin
--successful fetch
select @TableCounter = @TableCounter + 1

select @string = "Checking identity of " + RTRIM(@TableName)
print @string

exec ("dbcc checkident (" + @TableName + ")")

end -- else successful fetch

end --while (@continue = 1)

EndLoop:

-- final cleanup
close curTable
deallocate curTable

print ""
print "******************************************************"
print "Check Identity had been done for all User-defined tables."
select "Total Number of Tables:" = @TableCounter

END

go


Display Current SQL Activity
Display Current SQL Activity

Here is a simple stored procedure for SQL Server 2000 (should work on SQL 7 as well) that lists users (like sp_who), locks (like sp_lock), and the input buffers of blocked and blocking users.

Author: Mitch van Huuksloot

use master
GO
if exists (select * from sysobjects where id = object_id('dbo.sp_activity') and sysstat & 0xf = 4)
drop procedure dbo.sp_activity
GO

CREATE PROCEDURE sp_activity AS

/* SP_ACTIVITY */
/* Author: Mitch van Huuksloot */
/* Date: April 30, 2001 */

set nocount on

select 'Activity on' = convert(char(19), getdate(), 20), Server = @@SERVERNAME

/* temp tables to hold more-or-less consistent sysprocesses/syslockinfo snapshots */

create table #info
(
spid smallint,
cmd char(16),
status char(10),
blocked smallint,
waittype binary(2),
waittime int,
lastwaittype char(20),
waitresource char(25),
dbname char(30),
loginname char(25),
hostname char(15),
cpu int,
physical_io int,
[memusage] int,
login_time char(19),
last_batch char(19),
open_tran smallint,
net_address char(12),
net_library char(12),
)

create table #locks
(
spid int,
resource char(32),
dbname char(30),
indid smallint,
indname char(30),
objid integer,
objectname char(30),
typeid tinyint,
type char(3),
mode char(12),
status char(10),
refcnt smallint,
ownertype char(12),
transid bigint
)

/* capture sysprocesses */
insert into #info
select p.spid,
convert(char(16), p.cmd),
convert(char(10), p.status),
p.blocked,
p.waittype,
p.waittime,
convert(char(20), p.lastwaittype),
convert(char(25), p.waitresource),
convert(char(30), d.name),
convert(char(25), p.loginame),
convert(char(15), p.hostname),
p.cpu,
p.physical_io,
p.memusage,
convert(char(19), p.login_time, 20),
convert(char(19), p.last_batch, 20),
p.open_tran,
convert(char(12), p.net_address),
convert(char(12), p.net_library)
from master.dbo.sysprocesses p (nolock), master.dbo.sysdatabases d (nolock)
where p.dbid = d.dbid

/* capture syslockinfo */
insert into #locks
select
L.req_spid,
convert(char(32), L.rsc_text),
convert(char(30), d.name),
L.rsc_indid,
SPACE(30),
L.rsc_objid,
SPACE(30),
L.rsc_type,
convert(char(3), v.name),
convert(char(12), v2.name),
convert(CHAR(10), v3.name),
L.req_refcnt smallint,
case L.req_ownertype when 1 then 'Transaction' when 2 then 'Cursor' when 3 then 'Session' when 4 then 'ExSession' else cast(L.req_ownertype as char(12)) end,
req_transactionID
from master..syslockinfo L (nolock), master..sysdatabases d (nolock),
master..spt_values v (nolock), master..spt_values v2 (nolock), master..spt_values v3 (nolock)
where L.rsc_dbid = d.dbid and
l.rsc_type=v.number and v.type='LR' and
(l.req_mode+1)=v2.number and v2.type='L' and
l.req_status=v3.number and v3.type='LS'

/* Show active processes from sysprocesses capture */

print ''
print 'Active SQL Server Processes'
print ''
select * from #info order by spid

/* Dump out block chain, if there is one */

declare @blkd int
select @blkd=count(spid) from #info where blocked = 0 and spid in (select distinct blocked from #info where blocked != 0)
if @blkd > 0
begin
print ''
select 'SPIDs at the head of blocking chains'=spid from #info where blocked = 0 and spid in (select distinct blocked from #info where blocked != 0)
print ''
end

/* Dump inputbuffers for each blocking process */

declare @spid smallint, @spidch char(5), @msg varchar(100)

declare c1 cursor for select distinct blocked from #info where blocked > 0 FOR READ ONLY
open c1
fetch c1 into @spid
while @@fetch_status >= 0
begin
select @spidch = convert(char(5), @spid)
print ''
select @msg = 'Blocking SPID ' + @spidch + ' input buffer capture'
print ''
print @msg
select @msg = 'dbcc inputbuffer(' + @spidch + ')'
execute(@msg)
fetch c1 into @spid
end
deallocate c1

/* Dump inputbuffers for each blocked process */

declare c1 cursor for select spid from #info where blocked > 0 FOR READ ONLY
open c1
fetch c1 into @spid
while @@fetch_status >= 0
begin
select @spidch = convert(char(5), @spid)
print ''
select @msg = 'Blocked SPID ' + @spidch + ' input buffer capture'
print ''
print @msg
select @msg = 'dbcc inputbuffer(' + @spidch + ')'
execute(@msg)
fetch c1 into @spid
end
deallocate c1

drop table #info -- we are finished with the sysprocesses capture

/* Update locks table with tablename, objectname, indexname from the appropriate database */

declare @dbname varchar(30),
@objid int,
@indid int,
@idch varchar(20),
@indch varchar(20),
@objname varchar(30),
@indexname varchar(30),
@stmt varchar(500)

declare c2 cursor for select distinct dbname, objid, indid from #locks where typeid between 4 and 9 for read only
open c2
fetch c2 into @dbname, @objid, @indid
while @@fetch_status >= 0
begin
select @idch=cast(@objid as varchar(20))
select @indch=cast(@indid as varchar(20))
if @indid <> 0
select @stmt = 'update #locks set objectname = cast(o.name as char(30)), indname=cast(i.name as char(30)) from #locks l, ' +
@dbname + '..sysobjects o (nolock), ' + @dbname + '..sysindexes i (nolock) where l.dbname = ' + '''' + @dbname + '''' +
' and l.objid = ' + @idch + ' and l.indid = ' + @indch + ' and o.id = ' + @idch + ' and i.id = ' + @idch + ' and i.indid = ' + @indch
else
select @stmt = 'update #locks set objectname = cast(o.name as char(30)) from #locks l, ' +
@dbname + '..sysobjects o (nolock) where l.dbname = ' + '''' + @dbname + '''' +
' and l.objid = ' + @idch + ' and l.indid = ' + @indch + ' and o.id = ' + @idch
execute(@stmt)
fetch c2 into @dbname, @objid, @indid
end
deallocate c2

/* Show lock information from syslocks capture */

print ''
print 'Locks'
print ''

select spid, type, mode, status, [database]=dbname, [index]=indname, [object]=objectname, resource, ownertype, "trans #"=transid, refcnt
from #locks order by spid, dbname, object, indname, resource, type, mode, status

drop table #locks -- drop syslockinfo capture
GO

exec sp_activity


未透露的存储过程
Introduction

An extended stored procedure (xp) is a dynamic link library that runs
directly in the address space of SQL Server and is programmed using
the SQL Server Open Data Services API. You can run extended stored
procedures from the Query Analyzer, for example, just as you would
normal stored procedures. Extended stored procedures are used to extend
the capabilities of SQL Server. You can take advantage of the many
extended stored procedures that come with SQL Server, or you can
write your own in a programming language such as C or C++.

In this article, I want to tell you about some useful undocumented
extended stored procedures. These extended stored procedures work
with SQL Server 7.0, as well as with SQL Server 2000.

sp_MSgetversion
This extended stored procedure can be used to get the current version
of Microsoft SQL Server.
To get the current SQL Server version, run

EXEC master..sp_MSgetversion

Note. A more common way to retrieve the current SQL Server version (this
way provides more information) is to use following SELECT statement:

select @@version

xp_dirtree
This extended stored procedure can be used to get a list of all the
folders for the folder named in the xp.
To get a list of all the folders in the C:\MSSQL7 folder, run:

EXEC master..xp_dirtree 'C:\MSSQL7'

xp_enum_oledb_providers
This extended stored procedure is used to list of all the available OLE DB
providers. It returns Provider Name, Parse Name and Provider Description.
To get a list of all OLE DB providers for your SQL Server, run:

EXEC master..xp_enum_oledb_providers

xp_enumcodepages
This extended stored procedure can be used to list of all code pages,
character sets and their description for your SQL Server.
To see this, list, run:

EXEC master..xp_enumcodepages

xp_enumdsn
This extended stored procedure returns a list of all System DSNs
and their description.
To get the list of System DSNs, run:

EXEC master..xp_enumdsn

xp_enumerrorlogs
This extended stored procedure returns the list of all error logs
with their last change date.
To get the list of error logs, run:

EXEC master..xp_enumerrorlogs

xp_enumgroups
This extended stored procedure returns the list of Windows NT groups
and their description.
To get the list of the Windows NT groups, run:

EXEC master..xp_enumgroups

xp_fileexist
You can use this extended stored procedure to determine whether a
particular file exists on the disk or not.

Syntax:

EXECUTE xp_fileexist filename [, file_exists INT OUTPUT]

For example, to check whether the file boot.ini exists on disk c: or not, run:

EXEC master..xp_fileexist 'c:\boot.ini'

xp_fixeddrives
This very useful extended stored procedure returns the list of all
hard drives and the amount of free space in Mb for each hard drive.

To see the list of drives, run:

EXEC master..xp_fixeddrives

xp_getnetname
This extended stored procedure returns the WINS name of the SQL Server
that you're connected to.

To view the name, run:

EXEC master..xp_getnetname

xp_readerrorlog
This extended stored procedure returns the content of the errorlog
file. You can find the errorlog file in the C:\MSSQL7\Log directory,
by default for SQL Server 7.0.

To see the text of the errorlog file, run:

EXEC master..xp_readerrorlog

xp_regdeletekey
This extended stored procedure will delete an entire key from the registry.
You should use it very carefully.

Syntax:

EXECUTE xp_regdeletekey [@rootkey=]'rootkey',
[@key=]'key'

For example, to delete the key 'SOFTWARE\Test' from 'HKEY_LOCAL_MACHINE', run:

EXEC master..xp_regdeletekey
@rootkey='HKEY_LOCAL_MACHINE',
@key='SOFTWARE\Test'

xp_regdeletevalue
This extended stored procedure will delete a particular value for
a key in the registry. You should use it very carefully.

Syntax:

EXECUTE xp_regdeletevalue [@rootkey=]'rootkey',
[@key=]'key',
[@value_name=]'value_name'

For example, to delete the value 'Testvalue' for the key 'SOFTWARE\Test'
from 'HKEY_LOCAL_MACHINE', run:

EXEC master..xp_regdeletevalue
@rootkey='HKEY_LOCAL_MACHINE',
@key='SOFTWARE\Test',
@value_name='Testvalue'

xp_regread
This extended stored procedure is used to read from the registry.

Syntax:

EXECUTE xp_regread [@rootkey=]'rootkey',
[@key=]'key'
[, [@value_name=]'value_name']
[, [@value=]@value OUTPUT]

For example, to read into the variable @test from the value 'Testvalue'
from the key 'SOFTWARE\Test' from the 'HKEY_LOCAL_MACHINE', run:

DECLARE @test varchar(20)
EXEC master..xp_regread @rootkey='HKEY_LOCAL_MACHINE',
@key='SOFTWARE\Test',
@value_name='Testvalue',
@value=@test OUTPUT
SELECT @test

xp_regwrite
This extended stored procedure is used to write to the registry.

Syntax:

EXECUTE xp_regwrite [@rootkey=]'rootkey',
[@key=]'key',
[@value_name=]'value_name',
[@type=]'type',
[@value=]'value'

For example, to write the variable 'Test' to the 'Testvalue' value,
key 'SOFTWARE\Test', 'HKEY_LOCAL_MACHINE', run:

EXEC master..xp_regwrite
@rootkey='HKEY_LOCAL_MACHINE',
@key='SOFTWARE\Test',
@value_name='Testvalue',
@type='REG_SZ',
@value='Test'

xp_subdirs
This extended stored procedure is used to get the list of folders for the
folder named in the xp. In comparison with xp_dirtree, xp_subdirs returns
only those directories whose depth = 1.

This is the example:

EXEC master..xp_subdirs 'C:\MSSQL7'

Note.Keep in mind that these undocumented extended stored procedures are not
officially supported by Microsoft, and that they may not be found in the
next version of SQL Server.


Procedure Parameters
Use this procedure to get parameters for your stored procedure with data type,length, parameter position and also the mode of parameter (Input or Output).
Can be used to populate the dynamic parameters for any procedure.

Mohit Nayyar
MCP (.Net), MCSD, MCDBA

/*
Use this procedure to get parameters for your stored procedure with data type,length, parameter position and also the mode of parameter (Input or Output).
Can be used to populate the dynamic parameters for any procedure.
*/

create procedure getParam @Proc_Name varchar(255)
as
--Just pass the procedure name to get all the parameters
select Specific_Name as 'Procedure_Name', Parameter_Name, Ordinal_Position as 'Param_Position', case Parameter_Mode when 'IN' then 'Input' else 'Output' end as 'Mode', Data_Type, Character_Maximum_Length 'Data_Length'
from information_schema.Parameters where specific_name = @Proc_Name
go

exec getParam 'dt_addtosourcecontrol'


Pass an Array into a Stored Procedure
Pass an Array into a Stored Procedure
Script Rating Total number of votes [19]
By: david.hardin
Have you ever wished T-SQL allowed you to pass an array into a stored procedure? Well the spListToTable stored procedure listed below accepts a delimeted string and turns it into a table. Code your stored procedure to accept a varchar, create a temporary table, call spListToTable, and use the populated temporary table as if it were an array of values.

CREATE PROCEDURE [spListToTable]
@vcList VARCHAR(8000),
@vcDelimiter VARCHAR(8000),
@TableName SYSNAME,
@ColumnName SYSNAME
AS
SET NOCOUNT ON

DECLARE @iPosStart INT,
@iPosEnd INT,
@iLenDelim INT,
@iExit INT,
@vcStr varchar(8000),
@vcSql varchar(8000)

SET @iPosStart = 1
SET @iPosEnd = 1
SET @iLenDelim = LEN(@vcDelimiter)

SET @vcSql = 'INSERT ' + @TableName + ' (' + @ColumnName + ') valueS ('''

SET @iExit = 0

WHILE @iExit = 0
BEGIN
SET @iPosEnd = CHARINDEX(@vcDelimiter, @vcList, @iPosStart)

IF @iPosEnd <= 0
BEGIN
SET @iPosEnd = LEN(@vcList) + 1
SET @iExit = 1
END

SET @vcStr = SUBSTRING(@vcList, @iPosStart, @iPosEnd - @iPosStart)

EXEC(@vcSql + @vcStr + ''')')

SET @iPosStart = @iPosEnd + @iLenDelim
END

RETURN 0
GO

-- You can use it like:

DECLARE @vcArray VARCHAR(255)
SELECT @vcArray = '1|2|3|4|5|6|7|8|9|10'

CREATE TABLE #values (v INT NOT NULL)

EXEC spListToTable @vcArray, '|', '#values', 'v'

CREATE TABLE #SomeTable (Status VARCHAR(10), value INT)

INSERT INTO #SomeTable (Status, value) SELECT 'New', v FROM #values

SELECT * FROM #SomeTable

DROP TABLE #values
DROP TABLE #SomeTable


解密存储过程，视图，触发器
此存储过程解密比较短的存储过程可以，运行前先备份存储过程，否则可能无法恢复。

Decrypt Stored Procedures, Views and Triggers
Script Rating Total number of votes [11]
By: jgama
This SP will decrypt Stored Procedures, Views or Triggers that were encrypted using "with encryption" There are 2 versions: one for SP''s only and the other one for SP''s, triggers and views version 1: INPUT: object name (stored procedure, view or trigger) version 2: INPUT: object name (stored procedure, view or trigger), object type(''T''-trigger, ''P''-stored procedure or ''V''-view) Original idea: shoeboy?Copyright ?1999-2002 SecurityFocus
Stored procedures coded by Joseph Gama ?

SET QUOTED_IDENTIFIER OFF
GO
SET ANSI_NULLS OFF
GO

CREATE PROCEDURE DECRYPT2K (@objName varchar(50), @type char(1) )
--INPUT: object name (stored procedure,
-- view or trigger), object type (''S''-store
-- d procedure, ''V''view or ''T''-trigger)
--Original idea: shoeboy <shoeboy@ade
-- quacy.org>
--Copyright ?1999-2002 SecurityFocus
--adapted by Joseph Gama
--Planet Source Code, my employer and my
-- self are not responsible for the use of
-- this code
--This code is provided as is and for ed
-- ucational purposes only
--Please test it and share your results
AS
DECLARE @a nvarchar(4000), @b nvarchar(4000), @c nvarchar(4000), @d nvarchar(4000), @i int, @t bigint, @tablename varchar(255), @trigtype varchar(6)
SET @type=UPPER(@type)
IF @type=''T''
BEGIN
SET @tablename=(SELECT sysobjects_1.name
FROM dbo.sysobjects INNER JOIN
dbo.sysobjects sysobjects_1 ON dbo.sysobjects.parent_obj = sysobjects_1.id
WHERE (dbo.sysobjects.type = ''TR'') AND (dbo.sysobjects.name = @objName))
SET @trigtype=(SELECT CASE WHEN dbo.sysobjects.deltrig > 0 THEN ''DELETE''
WHEN dbo.sysobjects.instrig > 0 THEN ''INSERT''
WHEN dbo.sysobjects.updtrig > 0 THEN ''UPDATE'' END
FROM dbo.sysobjects INNER JOIN
dbo.sysobjects sysobjects_1 ON dbo.sysobjects.parent_obj = sysobjects_1.id
WHERE (dbo.sysobjects.type = ''TR'') AND (dbo.sysobjects.name = @objName))
END
--get encrypted data
SET @a=(SELECT ctext FROM syscomments WHERE id = object_id(@objName))
SET @b=case @type
WHEN ''S'' THEN ''ALTER PROCEDURE ''+ @objName +'' WITH ENCRYPTION AS ''+REPLICATE(''-'', 4000-62)
WHEN ''V'' THEN ''ALTER VIEW ''+ @objName +'' WITH ENCRYPTION AS SELECT dbo.dtproperties.* FROM dbo.dtproperties''+REPLICATE(''-'', 4000-150)
WHEN ''T'' THEN ''ALTER TRIGGER ''+@objName+'' ON ''+ @tablename+'' WITH ENCRYPTION FOR ''+@trigtype+'' AS PRINT ''''a''''''+REPLICATE(''-'', 4000-150)
END
EXECUTE (@b)
--get encrypted bogus SP
SET @c=(SELECT ctext FROM syscomments WHERE id = object_id(@objName))
SET @b=case @type
WHEN ''S'' THEN ''CREATE PROCEDURE ''+ @objName +'' WITH ENCRYPTION AS ''+REPLICATE(''-'', 4000-62)
WHEN ''V'' THEN ''CREATE VIEW ''+ @objName +'' WITH ENCRYPTION AS SELECT dbo.dtproperties.* FROM dbo.dtproperties''+REPLICATE(''-'', 4000-150)
WHEN ''T'' THEN ''CREATE TRIGGER ''+@objName+'' ON ''+ @tablename+'' WITH ENCRYPTION FOR ''+@trigtype+'' AS PRINT ''''a''''''+REPLICATE(''-'', 4000-150)
END
--start counter
SET @i=1
--fill temporary variable
SET @d = replicate(N''A'', (datalength(@a) / 2))
--loop
WHILE @i<=datalength(@a)/2
BEGIN
--xor original+bogus+bogus encrypted
SET @d = stuff(@d, @i, 1,
NCHAR(UNICODE(substring(@a, @i, 1)) ^
(UNICODE(substring(@b, @i, 1)) ^
UNICODE(substring(@c, @i, 1)))))
SET @i=@i+1
END
--drop original SP
IF @type=''S''
EXECUTE (''drop PROCEDURE ''+ @objName)
ELSE
IF @type=''V''
EXECUTE (''drop VIEW ''+ @objName)
ELSE
IF @type=''T''
EXECUTE (''drop TRIGGER ''+ @objName)
--remove encryption
--try to preserve case
SET @d=REPLACE((@d),''WITH ENCRYPTION'', '''')
SET @d=REPLACE((@d),''With Encryption'', '''')
SET @d=REPLACE((@d),''with encryption'', '''')
IF CHARINDEX(''WITH ENCRYPTION'',UPPER(@d) )>0
SET @d=REPLACE(UPPER(@d),''WITH ENCRYPTION'', '''')
--replace SP
execute( @d)

GO
SET QUOTED_IDENTIFIER OFF
GO
SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER OFF
GO
SET ANSI_NULLS OFF
GO

CREATE PROCEDURE DECRYPTSP2K (@objName varchar(50))
--INPUT: object name (stored procedure,
--
-- view or trigger)
--Original idea: shoeboy <shoeboy@a
-- dequacy.org>
--Copyright ?1999-2002 SecurityFocus
--adapted by Joseph Gama
--Planet Source Code, my employer and my
--
-- self are not responsible for the use
-- of
-- this code
--This code is provided as is and for ed
--
-- ucational purposes only
--Please test it and share your results
AS
DECLARE @a nvarchar(4000), @b nvarchar(4000), @c nvarchar(4000), @d nvarchar(4000), @i int, @t bigint
--get encrypted data
SET @a=(SELECT ctext FROM syscomments WHERE id = object_id(@objName))
SET @b=''ALTER PROCEDURE ''+ @objName +'' WITH ENCRYPTION AS ''+REPLICATE(''-'', 4000-62)
EXECUTE (@b)
--get encrypted bogus SP
SET @c=(SELECT ctext FROM syscomments WHERE id = object_id(@objName))
SET @b=''CREATE PROCEDURE ''+ @objName +'' WITH ENCRYPTION AS ''+REPLICATE(''-'', 4000-62)
--start counter
SET @i=1
--fill temporary variable
SET @d = replicate(N''A'', (datalength(@a) / 2))
--loop
WHILE @i<=datalength(@a)/2
BEGIN
--xor original+bogus+bogus encrypted
SET @d = stuff(@d, @i, 1,
NCHAR(UNICODE(substring(@a, @i, 1)) ^
(UNICODE(substring(@b, @i, 1)) ^
UNICODE(substring(@c, @i, 1)))))
SET @i=@i+1
END
--drop original SP
EXECUTE (''drop PROCEDURE ''+ @objName)
--remove encryption
--try to preserve case
SET @d=REPLACE((@d),''WITH ENCRYPTION'', '''')
SET @d=REPLACE((@d),''With Encryption'', '''')
SET @d=REPLACE((@d),''with encryption'', '''')
IF CHARINDEX(''WITH ENCRYPTION'',UPPER(@d) )>0
SET @d=REPLACE(UPPER(@d),''WITH ENCRYPTION'', '''')
--replace SP
execute( @d)

GO
SET QUOTED_IDENTIFIER OFF
GO
SET ANSI_NULLS ON
GO


 Copy one procedure from one database to another
Copy one procedure from one database to another
Script Rating Total number of votes [4]
By: simonsabin
The procedure copies one stored procedure from one database to another one

USE master
GO
IF EXISTS (SELECT 1 FROM dbo.sysobjects WHERE name = 'sp_CopyStoredprocedure')
DROP PROCEDURE sp_CopyStoredprocedure
GO
CREATE PROCEDURE dbo.sp_CopyStoredprocedure
(
@procedure sysname
,@sourceDb sysname
,@targetdb sysname
)
AS
/*******************************************************************************

Written By : Simon Sabin
Date : 25 October 2002
Description : Copies a stored procedure from one database to another
:
History
Date Change
------------------------------------------------------------------------------
25/10/2002 Created
*******************************************************************************/

DECLARE @lines int
DECLARE @line int
DECLARE @vline varchar(4)
DECLARE @objectid varchar(10)

DECLARE @DECLARE varchar(8000)
DECLARE @SELECT varchar(8000)
DECLARE @EXECUTE varchar(8000)
DECLARE @PRINT varchar(8000)
DECLARE @SQL nvarchar(4000)

SET @SQL = 'USE ' + @sourcedb + '
SELECT @objectid = id, @lines = count(*)
FROM ' + @sourcedb + '.dbo.syscomments
WHERE id = object_id(@procedure)
GROUP BY id'

EXEC sp_executesql @SQL, N'@objectid int OUTPUT, @lines int OUTPUT, @procedure sysname', @objectid OUTPUT, @lines OUTPUT, @procedure
SELECT @objectid , @lines , @procedure, @SQL

SET @DECLARE = ''
SET @SELECT = ''
SET @EXECUTE = ''
SET @PRINT = ''

SET @line = 0
WHILE @line <= @lines
BEGIN
SET @vline = @line
SET @DECLARE = @DECLARE + char(10) + 'DECLARE @line' + @vline + ' nvarchar(4000)'
SET @SELECT = @SELECT + char(10) + 'SELECT @line' + @vline + ' = text FROM ' + @sourcedb + '.dbo.syscomments WHERE colid = ' + @vline + ' AND id = ' + @objectid
SET @EXECUTE = @EXECUTE + '+ @line' + @vline

SET @PRINT = @PRINT + char(10) + 'PRINT @line' + @vline

SET @line = @line + 1
END

SET @DECLARE = @DECLARE + CHAR(10)
SET @SELECT = @SELECT + CHAR(10)
SET @EXECUTE = 'USE ' + @targetDb + char(10) + 'EXECUTE (''''' + @EXECUTE + ')'

SET @SQL = 'IF EXISTS (SELECT 1 FROM ' + @targetDB + '.dbo.sysobjects WHERE name = @procedure )
DROP PROCEDURE ' + @procedure
exec sp_executesql @SQL, N'@procedure sysname ', @procedure

PRINT @DECLARE
PRINT @SELECT
PRINT @EXECUTE
PRINT @PRINT
EXECUTE (@DECLARE + @SELECT + @EXECUTE + @PRINT)

GO


使用SQL SERVER 2000的全文检索功能
http://www.csdn.net/develop/read_article.asp?id=17137
使用SQL SERVER 2000的全文检索功能

步骤：

1、 打开NorthWind数据库，右键Products表，选择“全文索引表”，“在表上定义全文索引”，为其创建全文目录。在“全文索引向导”中，选择你需要全文查询条件的列（可以是文本或图像），我们选择Productname和QuantityPerUnit列。然后下一步，输入一个新的目录proCatalog，点击下一步。在填充调度一项里，我们暂时不建立填充调度。接着“下一步”，全成全文索引向导。完成后，我们可以看到在“全文目录”里，多了一个全文目录proCatalog。右键proCatalog属性，可以看到其状态、表、调度情况。

2、 右键全文目录proCatalog， “启动完全填充”，系统会出现“全文目录填充成功启动”。

3、 这时，我们可以使用Transact-SQL进行全文检索了，我们使用CONTAINS、FREETEXT等谓词。如：

检索ProductName中，即包含b又包含c的所有列

SELECT *

FROM Products

WHERE CONTAINS(ProductName, '"c*" and "b*" ')

检索ProductName中，即包含chai chang tofu之一的所有列

SELECT *

FROM Products

WHERE FREETEXT(ProductName, 'chai chang tofu ')

我们还可以使用CONTAINSTABLE、FREETEXTTABLE等

4、 我们可以用.NET创建WEB或Form客户端，提供一个良好的全文检索界面，使自己的应用程序得到扩展

参考：
全文查询有关的系统存储过程

（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 字节），全文填充将无法继续进行。

如果进行索引的表有成千上万行，请将该表指定给其自己的全文目录。

应该考虑对其进行全文索引的表中发生的更改数以及表的行数。如果要更改的总行数，加上上次全文填充期间表中出现的行数达到成千上万行，请将该表指定给其自己的全文目录。

请参见

sp_fulltext_table

CONTAINS

一个谓词，用于搜索包含基于字符的数据类型的列，该列与单个词和短语，以及与另一个词一定范围之内的近似词精确或模糊（不太精确的）匹配或者加权匹配。CONTAINS 可以搜索：

词或短语。

词或短语的前缀。

另一个词附近的词。

由另一个词的词尾变化生成的词（例如，词 drive 是 drives、drove、driving 和 driven 词尾变化的词干）。

比另一个词具有更高加权的词。

语法
CONTAINS
( { column | * } , '< contains_search_condition >' )

FREETEXT

是个谓词，用于搜索含有基于字符的数据类型的列，其中的值符合在搜索条件中所指定文本的含义，但不符合表达方式。使用 FREETEXT 时，全文查询引擎内部将 freetext_string 拆分为若干个搜索词，并赋予每个词以不同的加权，然后查找匹配。

语法

FREETEXT ( { column | * } , 'freetext_string' )

CONTAINSTABLE 和 FREETEXTTABLE 函数用来指定返回每行的相对排名的全文查询。这两个函数与全文谓词 CONTAINS 和 FREETEXT 很相似，但是用法不同。

(此文部分资料来自MSDN)

Author:李洪根

E-MAIL:lihonggen0@163.com


基本知识
1. SQL Server7 的 DeskTop 版中没有全文本检索。
2. 一个表只能有一个全文本检索。
3. 被检索的表必须有单列的唯一索引。
4. 全文本的索引存储在文件系统中，而非数据库中。
5. 更新全文本索引的过程比常规索引要耗时，而且也不象常规索引那样可以由数据库系统立即更新。
6. 全文本索引包含在全文本目录( Full-Text Catalog )中，每个数据库可以包含一个或多个目录，但一个目录不能属于多个数据库。
7. 全文本检索只能在真正的表上创建，不能是视图，系统表，临时表。
8. 全文本检索会忽略某些噪音字( noise words)，比如英文的 a，the，and，中文的'和'，'是'等等。
9. 如果在查询中包含 noise words ，就会引发错误，在应用程序中应去除这些 noise words。

启动全文本检索服务。
方法A：在企业管理器中打开 Support Services 文件夹，在 Full-Text Search 的右键菜单中选择 Start。
方法B：在 SQL Server Service Manager 的 Services 下拉列表中选择 Microsoft Search，并单击 Start/Continue 按钮。
方法C：使用 net start mssearch 的命令行方式。
使用全文本检索向导( Full-Text Indexing Wizard )。
step1. 选择被检索的数据库，在 Tools 的菜单中，选择 Full-text Indexing，进入欢迎( Welcome )的屏幕，单击 next。
step2. 选择被检索的表，单击 next。
step3. 选择唯一索引，单击 next。
step4. 选择被索引的列，单击 Add，该列显示在右栏中。单击 next。
step5. 选择目录(选择已存在的目录，或创建新的目录)，单击 next。
step6. 选择或创建 population schedule(可选项)，单击 next。
step7. 单击 finish。
使用 SQL-DMO (以 VB 为例)
step1. 在工程的引用中选择 Microsoft SQLDMO Object Library。
step2. 创建 SQLServer 对象。
Dim objSQL As New SQLDMO.SQLServer
objSQL.Connect "localhost", "sa", ""
step3. 创建新的目录，并加入到被索引的数据库目录中。
Dim objCatalog As New SQLDMO.FullTextCatalog
'使 pubs 为全文本检索的数据库
objSQL.Databases("pubs").EnableFullTextCatalogs
'创建新的目录
objCatalog.Name = "ftcPubsTest"
'将新目录加入到目录集合中
objSQL.Databases("pubs").FullTextCatalogs.Add objCatalog
step4. 在表上创建全文本索引。
Dim objTable As New SQLDMO.Table
'指定被索引的表
Set objTable = objSQL.Databases("pubs").Tables("authors")
'指定目录名和唯一索引名
objTable.FullTextCatalogName = "ftcPubsTest"
objTable.UniqueIndexForFullText = "UPKCL_auidind"
objTable.FullTextIndex = True

'指定被索引的列
objTable.Columns("au_lname").FullTextIndex = True
objTable.Columns("au_fname").FullTextIndex = True

'激活该表上的全文本索引
objTable.FullTextIndexActive = True

step5. 启动全文本目录
objCatalog.Start SQLDMOFullText_Full
使用存储过程
step1. 使 pubs 为全文本检索的数据库
USE Pubs
go
sp_fulltext_database 'enable'

step2. 创建新的目录
sp_fulltext_catalog 'ftcPubsTest','create'
step3. 指定被索引的表
sp_fulltext_table 'authors','create','ftcPubsTest','UPKCL_auidind'
step4. 指定被索引的列
sp_fulltext_column 'authors','au_lname','add'
sp_fulltext_column 'authors','au_fname','add'
step5. 激活该表上的全文本索引
sp_fulltext_table 'authors','activate'
step6. 启动全文本目录
sp_fulltext_catalog 'ftcPubsTest','start_full'


CONTAINS 语法
我们通常在 WHERE 子句中使用 CONTAINS ，就象这样：SELECT * FROM table_name WHERE CONTAINS(fullText_column,'search contents')。

我们通过例子来学习，假设有表 students，其中的 address 是全文本检索的列。
1. 查询住址在北京的学生
SELECT student_id,student_name
FROM students
WHERE CONTAINS( address, 'beijing' )
remark: beijing是一个单词，要用单引号括起来。

2. 查询住址在河北省的学生
SELECT student_id,student_name
FROM students
WHERE CONTAINS( address, '"HEIBEI province"' )
remark: HEBEI province是一个词组，在单引号里还要用双引号括起来。

3. 查询住址在河北省或北京的学生
SELECT student_id,student_name
FROM students
WHERE CONTAINS( address, '"HEIBEI province" OR beijing' )
remark: 可以指定逻辑操作符(包括 AND ，AND NOT，OR )。

4. 查询有 '南京路' 字样的地址
SELECT student_id,student_name
FROM students
WHERE CONTAINS( address, 'nanjing NEAR road' )
remark: 上面的查询将返回包含 'nanjing road'，'nanjing east road'，'nanjing west road' 等字样的地址。
A NEAR B，就表示条件： A 靠近 B。

5. 查询以 '湖' 开头的地址
SELECT student_id,student_name
FROM students
WHERE CONTAINS( address, '"hu*"' )
remark: 上面的查询将返回包含 'hubei'，'hunan' 等字样的地址。
记住是 *，不是 %。

6. 类似加权的查询
SELECT student_id,student_name
FROM students
WHERE CONTAINS( address, 'ISABOUT (city weight (.8), county wright (.4))' )
remark: ISABOUT 是这种查询的关键字，weight 指定了一个介于 0~1之间的数，类似系数(我的理解)。表示不同条件有不同的侧重。

7. 单词的多态查询
SELECT student_id,student_name
FROM students
WHERE CONTAINS( address, 'FORMSOF (INFLECTIONAL,street)' )
remark: 查询将返回包含 'street'，'streets'等字样的地址。
对于动词将返回它的不同的时态，如：dry，将返回 dry，dried，drying 等等。

以上例子都使用英文，不使用中文是因为有的查询方式中文不支持，而且我的计算机是英文系统。


CONTAINSTABLE 语法
我们通常在 FROM 子句中使用 CONTAINSTABLE ，就象这样：SELECT * FROM table_name,CONTAINTABLE(fulltext_table,fullText_column,'search condition') WHERE ......。

CONTAINSTABLE 在查询方式上与 CONTAINS 几乎一样，所以就不用赘述了。CONTAINSTABLE 返回的是符合查询条件的表，在 SQL 语句中我们可以把它当作一个普通的表来使用。

我们看一个例子，比较这两种表的不同。
SELECT FT_TBL.student_name,FT_TBL.student_score,KEY_TBL.RANK
FROM report AS FT_TBL INNER JOIN
CONTAINSTABLE( student,address,
'ISABOUT (city weight (.8), county wright (.4))' ) AS KEY_TBL
ON FT_TBL.student_id = KEY_TBL.[KEY]
ORDER BY KEY_TBL.RANK
CONTAINSTABLE 返回的表包含有特殊的两列：KEY，RANK。
在第一部分里我们就强调了：被全文索引的表必须有唯一索引。这个唯一的索引列在返回的表中就成为 KEY。我们通常把它作为表连接的条件。

在某些网站搜索时，结果中会出现表示匹配程度的数字，RANK 与此类似。它的值在0~1000之间，标识每一行与查询条件的匹配程度，程度越高，RANK 的值大，通常情况下，按照 RANK 的降序排列。
FREETEXT 语法
FREETEXT 与 CONTAINS 类似，只是没有 CONTAINS 的精度高。在 CONTAINS 中，对查询条件的写法有很多要求，而 FREETEXT 就没有，可以是任意的单词，词组或句子。看下面的例子：
SELECT CategoryName
FROM Categories
WHERE FREETEXT (Description, 'sweetest candy bread and dry meat' )

FREETEXTTABLE 语法
和 CONTAINSTABLE 一样，FREETEXTTABLE 返回带有 KEY，RANK 的表。举例说明：
SELECT FT_TBL.CategoryName,
FT_TBL.Description,
KEY_TBL.RANK
FROM Categories AS FT_TBL INNER JOIN
FREETEXTTABLE(Categories, Description,
'sweetest candy bread and dry meat') AS KEY_TBL
ON FT_TBL.CategoryID = KEY_TBL.[KEY]
在 ASP 中使用全文本检索
Dim cnn
Dim rs
Dim strSQL

strSQL = "SELECT book_name " &_
"FROM books " &_
"WHERE CONTAINS( description, '" & Request("search_condition") & "' )"

Set cnn = Server.CreateObject("ADODB.Connection")
Set rs = Server.CreateObject("ADODB.RecordSet")

cnn.Open "provider=sqloledb;datasource=.;initial catalog=books;user id=sa;pasword=;"
rs.Open strSQL,cnn

上面的例子十分简单，仅为示意。只要掌握了 CONTAINS 和 CONTAINSTABLE 的语法，在使用上和一般的 ADO 查询一样


GetTbColValues
Version: SQL Server 7.0/2000
Created by: Alexander Chigrik

This stored procedure can be used to return all tables and table's
columns, which contain the particular text value. The GetTbColvalues
stored procedure will scan all tables in the current database to
search the particular text value in the char, nchar, varchar, nvarchar,
text, and ntext columns. You should pass the text value to search, as
in the example below:

EXEC GetTbColvalues 'John'

/*
Version: SQL Server 7.0/2000
Created by: Alexander Chigrik
http://www.MSSQLCity.com/ - all about MS SQL
(SQL Server Articles, FAQ, Scripts, Tips and Test Exams).

This stored procedure can be used to return all tables and table's
columns, which contain the particular text value. The GetTbColvalues
stored procedure will scan all tables in the current database to
search the particular text value in the char, nchar, varchar, nvarchar,
text and ntext columns. You should pass the text value to search, as
in the example below:

EXEC GetTbColvalues 'John'
*/

IF OBJECT_ID('GetTbColvalues') IS NOT NULL DROP PROC GetTbColvalues
GO

CREATE PROCEDURE GetTbColvalues (
@txtvalue varchar(128) = null
)
AS
DECLARE
@execstr varchar(1000),
@objectname sysname,
@colname sysname

SET NOCOUNT ON
IF @txtvalue IS NULL
BEGIN
RAISERROR ('You must specify the value to search', 16, 1)
RETURN
END

DECLARE tb_fetch_cursor CURSOR FOR
SELECT name FROM sysobjects WHERE type = 'U'
OPEN tb_fetch_cursor
FETCH NEXT FROM tb_fetch_cursor INTO @objectname
WHILE (@@fetch_status <> -1)
BEGIN
DECLARE col_fetch_cursor CURSOR FOR
SELECT name FROM syscolumns WHERE id = OBJECT_ID(@objectname) AND type IN
(SELECT type FROM systypes WHERE name = 'char' OR name = 'nchar' OR name = 'varchar'
OR name = 'nvarchar' OR name = 'text' OR name = 'ntext')
OPEN col_fetch_cursor
FETCH NEXT FROM col_fetch_cursor INTO @colname
WHILE (@@fetch_status <> -1)
BEGIN
SELECT @execstr = 'IF EXISTS (SELECT * FROM ' + @objectname + ' WHERE ' + @colname + ' like ''%' + @txtvalue + '%'') BEGIN SELECT ''' + @objectname + ''' as tbname, ''' + @colname + ''' as colname PRINT '''' END'
EXEC (@execstr)
FETCH NEXT FROM col_fetch_cursor INTO @colname
END
DEALLOCATE col_fetch_cursor
FETCH NEXT FROM tb_fetch_cursor INTO @objectname
END
DEALLOCATE tb_fetch_cursor
GO


GetSPTrigValues
Version: SQL Server 7.0/2000
Created by: Alexander Chigrik

This stored procedure can be used to return the names of each view,
rule, default, trigger, CHECK constraint, DEFAULT constraint, and
stored procedure, which contain the particular text value.
You should pass the database name and the text value to search,
as in the example below:

EXEC GetSPTrigvalues @dbname = 'pubs', @txtvalue = 'sales'

/*
Version: SQL Server 7.0/2000
Created by: Alexander Chigrik
http://www.MSSQLCity.com/ - all about MS SQL
(SQL Server Articles, FAQ, Scripts, Tips and Test Exams).

This stored procedure can be used to return the names of each view,
rule, default, trigger, CHECK constraint, DEFAULT constraint, and
stored procedure, which contain the particular text value.
You should pass the database name and the text value to search,
as in the example below
(if the database name was not specified, the current database will be used):

EXEC GetSPTrigvalues @dbname = 'pubs', @txtvalue = 'sales'
*/

IF OBJECT_ID('GetSPTrigvalues') IS NOT NULL DROP PROC GetSPTrigvalues
GO

CREATE PROCEDURE GetSPTrigvalues (
@dbname sysname = null,
@txtvalue varchar(128) = null
)
AS
DECLARE @execstr varchar(1000)

SET NOCOUNT ON
IF @txtvalue IS NULL
BEGIN
RAISERROR ('You must specify the value to search', 16, 1)
RETURN
END

IF @dbname IS NULL SET @dbname = DB_NAME()
SET @execstr = 'USE '+ @dbname + ' SELECT name as ObjectName, ObjectType = CASE type WHEN ''C'' THEN ''CHECK constraint'' WHEN ''D'' THEN ''Default or DEFAULT constraint'' WHEN ''F'' THEN ''FOREIGN KEY constraint'' WHEN ''K'' THEN ''PRIMARY KEY or UNIQUE constraint'' WHEN ''L'' THEN ''Log'' WHEN ''P'' THEN ''Stored procedure'' WHEN ''R'' THEN ''Rule'' WHEN ''RF'' THEN ''Stored procedure for replication'' WHEN ''S'' THEN ''System table'' WHEN ''TR'' THEN ''Trigger'' WHEN ''U'' THEN ''User table'' WHEN ''V'' THEN ''View'' WHEN ''X'' THEN ''Extended stored procedure'' END FROM sysobjects a JOIN syscomments b ON a.id = b.id AND text LIKE ''%' + @txtvalue + '%'''
EXEC (@execstr)
GO


交叉表
CREATE PROCEDURE prCrosstab
@chrRowHead char(30),/*表示列，在交叉表的结果中作为第一列出现*/
@chrColHead char(30),/*表示列，在交叉表的结果中该列中的数据被变换为新列名称*/
@chrvalue char(30),/*表示列，在该列中执行聚合函数*/
@chrSource char(30),/*源表或视图*/
@inyType tinyint=1,/*1-求和，2-平均值，3-最小值，4-最大值，5-计数*/
@inyGrouping tinyint=0/*1-工作日，2-年内的周数，3-月份，4-季度，5-年份*/
AS
/*过程变量*/
Declare
@chvRow varchar(255),
@chvCol varchar(255),
@chvVal varchar(255),
@chvType varchar(10),
@chvRowType varchar(10),
@chvColType varchar(255),
@chvTemp varchar(255),
@chvColTemp varchar(255),
@chvRowTemp varchar(255),
@intType int,
@intRowType int,
@intColType int,
@chvExec varchar(255),
@chvGroup varchar(255),
@fltTemp float,
@dtmTemp Datetime,
@insR smallint,
@intColumn int,
@intReturn int,
@intTemp int,
@intColNameLen int,
@intMaxRowHead int
Set NoCount On

/*检查数据源是否存在*/
if not Exists
(select * From sysobjects
where name=@chrSource and type in('v','u'))
Begin
Raiserror 51001 '数据源不存在'
Return -1
End

/*检查列是否存在*/
if not Exists
(select sc.name from syscolumns sc
join sysobjects so on sc.id=so.id
where so.name=@chrSource
and sc.name=@chrColHead)
Begin
Raiserror 51002 '无效 @chrColHead 名称'
Return -1
End

if not Exists
(select sc.name from syscolumns sc
join sysobjects so on sc.id=so.id
where so.name=@chrSource
and sc.name=@chrRowHead)
Begin
Raiserror 51002 '无效 @chrRowHead名称'
Return -1
End

if not Exists
(select sc.name from syscolumns sc
join sysobjects so on sc.id=so.id
where so.name=@chrSource
and sc.name=@chrvalue)
Begin
Raiserror 51002 '无效 @chrvalue 名称'
Return -1
End

/*检查聚合函数类型，是否是有效值*/
if @inyType<1 or @inyType>5
Begin
Raiserror 51000 '无效聚合函数类型'
Return -1
End

/*确定聚合函数类型*/
Select @chvType=
Case @inyType
when 1 then 'SUM'
when 2 then 'AVG'
when 3 then 'MAX'
when 4 then 'MIN'
when 5 then 'COUNT'
else 'SUM'
End

/*取得@chrvalue的数据类型*/
Select @chvTemp=t2.name
From sysobjects o
join syscolumns c on (o.id=c.id)
join systypes t1 on (t1.usertype=c.usertype)
join systypes t2 on (t1.type=t2.type)
where t2.usertype<100
and t2.usertype<>18
and t2.usertype<>80
and o.type in ('u','v')
and o.name=@chrSource
and c.name=@chrvalue

/*数据类型分类*/
Select @intTemp=
Case
when @chvTemp in ('int','smallint','tinyint','float','real','decimal','numeric','money','smallmoney') then 1
when @chvTemp in ('datetime','smalldatetime') then 3
when @chvTemp in ('bit','char','varchar') then 5
else 100
End

/*检查数据类型与聚合类型是否匹配*/
if @inyType<@intTemp
Begin
Raiserror 51020 '无效的数据类型 @chrvalue'
Return -1
End

/*转换成合适的数据类型*/
Select @chvColType=Rtrim(
Case @inyType
when 5 then 'int'
else
case
when @chvTemp in ('bit','char','varchar ') then 'int'
when @chvTemp in ('decimal','numeric') then 'float'
else @chvTemp
end
End)

/*确认数据分组是否有效*/
if @inyGrouping<0 or @inyGrouping>5
Begin
Raiserror 51010 '无效的数据分组'
Return -1
End

/*取得@chrColHead列的合法数据类型*/
Select @chvTemp=t2.name
From sysobjects o
join syscolumns c on (o.id=c.id)
join systypes t1 on (t1.usertype=c.usertype)
join systypes t2 on (t1.type=t2.type)
Where t2.usertype<100
and t2.usertype<>18
and t2.usertype<>80
and o.type in ('u','v')
and o.name=@chrSource
and c.name=@chrColHead

if upper(@chvTemp) not in ('CHAR','VARCHAR')
Select @intColType=1
else
Select @intColType=0

/*取得@chrRowHead的合法数据类型*/
Select @chvRowType=t2.name
From sysobjects o
join syscolumns c on (o.id=c.id)
join systypes t1 on (t1.usertype=c.usertype)
join systypes t2 on (t1.type=t2.type)
Where t2.usertype<100
and t2.usertype<>18
and t2.usertype<>80
and o.type in ('u','v')
and o.name=@chrSource
and c.name=@chrRowHead

if upper(@chvRowTemp) not in ('CHAR','VARCHAR')
Select @intRowType=1
else
Select @intRowType=0

/*检查组分类类型*/

Select @intTemp=
Case
when @chvTemp in ('int','smallint','tinyint','float','real','decimal','numeric','money','smallmoney') then 1
when @chvTemp in ('datetime','smalldatetime') then 3
when @chvTemp in ('bit','char','varchar') then 5
else 100
End

/*验证数据类型与日期分组类型的一致性*/
/*将来可扩充成其他数据类型分组*/
if (@intTemp=5 and @inyGrouping>0) or (@intTemp=1 and @inyGrouping>0) or (@intTemp=3 and @inyGrouping=0)
Begin
Raiserror 51030 '分组数据与分组类型不一致'
Return -1
End

/*安全性检查*/
/*此部分以后完成
if user_id()<>1
Begin
if (Select Count(distinct c.name)
From syscolumns c,sysobjects o,sysprotects p,sysusers u,master..spt_values v
Where c.name in (@chrColHead,@chrRowHead,@chrvalue)
*/

/*定义临时表*/
Create Table #colNames(colname varchar(255),colnumber int Null)
Create Table #rownames(rowname varchar(255) null)

/*创建colnames表*/
Select @chvExec='insert #colnames select col1,col2 from'
+'(select distinct col1='+
case @intTemp
when 3 then
case
when @inyGrouping in (1,3) then 'datename('+
case @inyGrouping
when 1 then 'weekday'
when 3 then 'month'
end +','+RTrim(@chrColHead)+')'
else
Case @inyGrouping
when 2 then '''Week'
when 4 then '''quarth'
when 5 then '''year'
end+'_''+'+'datename('+
case @inyGrouping
when 2 then 'week'
when 4 then 'quarth'
when 5 then'year'
end+','+RTrim(@chrColHead)+')'
end
else
case @intColType
when 1 then 'convert(varchar(255),'+RTrim(@chrColHead)+')'
else RTrim(@chrColHead)
end
end+',col2='+
case @intTemp
when 3 then 'datepart('+
case @inyGrouping
when 1 then 'weekday'
when 2 then 'week'
when 3 then 'month'
when 4 then 'quarter'
when 5 then 'year'
end+','+Rtrim(@chrColHead)+')'
else '0'
end+',col3='+
case @intTemp
when 3 then 'datepart('+
case @inyGrouping
when 1 then 'weekday'
when 2 then 'week'
when 3 then 'month'
when 4 then 'quarter'
when 5 then 'year'
end+','+RTrim(@chrColHead)+')'
else RTrim(@chrColHead)
end+' from '+RTrim(@chrSource)+')xyz order by col3'

--Print @chvExec
Exec(@chvExec)
--select * from #ColNames

/*检查列计数值*/
if (select Count(*) from #colnames)>1023
begin
drop table #colnames
raiserror 51004 'Distinct column count exceeded max of 1023.'
return -1
end

/*检验名称长度*/
if (Select max(DataLength(Rtrim(colname))-1) from #colnames)>29
Begin
Drop Table #colnames
RaisError 51050 'Column data length exceeded max of 30.'
Return -1
End

/*填写RowNames表*/
Select @chvExec='insert #rownames select distinct '+
Case @intRowtype
when 1 then 'convert(varchar255),'
else ''
end+Rtrim(@chrRowHead)+
Case @intRowType
when 1 then ')'
else ''
End+' from '+@chrSource

--Print @chvExec
Exec(@chvExec)

/*创建和修改crosstable*/
Select @intMaxRowHead=
(Select Max(DataLength(RTrim(rowname))) from #rownames)

/*创建Crosstable*/
/*定义Crosstable的RowHead字段*/
Create Table #crosstable(Rowhead varchar(255) null)
/*在Crosstable中加入列*/
Declare colname_cursor2 cursor for
select colname from #colnames
open colname_cursor2
Fetch colname_cursor2 into @chvCol
while @@fetch_status>=0
Begin
Select @chvColTemp=''
if @chvCol Like '%[^A-Z0-9]%'
Begin
Select @insR=1
While @insr<=DataLength(RTrim(@chvCol))
Begin
Select @chvColTemp=Rtrim(@chvColtemp)+
Case
when substring(@chvCol,@insR,1) Like '[A-Z0-9_]'
then substring(@chvCol,@insR,1)
Else ''
End
Select @insR=@insr+1
end
Select @chvCol=@chvColTemp
End
Select @chvExec='alter table #crosstable add '+
Case
when substring(@chvCol,1,1) Like '[^1234567890]' then @chvCol
else '_'+LTrim(@chvCol)
End
+' '+@chvColType+' null default (0)'

--Print @chvExec
Exec(@chvExec)
Fetch colname_cursor2 into @chvCol
End
Close colname_cursor2
Deallocate colname_cursor2

/*加入初始Crosstable数据*/
Select @chvExec='insert #crosstable(rowhead) select rowname from #rownames'
--Print @chvExec
Exec(@chvExec)

/*使用游标填写crosstable的剩余部分*/
/*创建游标*/
Select @chvExec='declare colname_cursor3 cursor for select '+
Case @intRowType
when 1 then 'convert(varchar(255),'+RTrim(@chrRowHead)+')'
Else RTrim(@chrRowHead)
End+','+
Case
when @intTemp=3 then
case
when @inyGrouping in (1,3) then 'Datename('+
case @inyGrouping
when 1 then 'weekday'
when 3 then 'month'
end+','+RTrim(@chrColHead)+')'
else
case @inyGrouping
when 2 then '''Week'
when 4 then '''Quarth'
when 5 then '''Year'
End+'_''+'+'datename('+
case @inyGrouping
when 2 then 'week'
when 4 then 'quarth'
when 5 then 'year'
end+','+RTrim(@chrColHead)+')'
End

Else
Case @intColType
When 1 then 'convert(varchar(255),'+RTrim(@chrColHead)+')'
Else RTrim(@chrColHead)
End
End+',total=Convert(varchar(255),'+RTrim(@chvType)+'('+RTrim(@chrvalue)+')) from '+
RTrim(@chrSource)+' group by '+RTRim(@chrRowHead)+','+
Case @intTemp
when 3 then
case
when @inyGrouping in (1,3) then 'Datename('+
case @inyGrouping
when 1 then 'weekday'
when 3 then 'month'
end+','+RTrim(@chrColHead)+')'
else
case @inyGrouping
when 2 then '''Week'
when 4 then '''Quarth'
when 5 then '''Year'
end+'_''+'+'datename('+
case @inyGrouping
when 2 then 'week'
when 4 then 'quarter'
when 5 then 'year'
end+','+RTrim(@chrColHead)+')'
end
else Rtrim(@chrColHead)
End
--Print @chvExec
Exec(@chvExec)

/*更新Crosstable表*/
Begin Tran
Open colname_cursor3
Fetch colname_cursor3 into @chvRow,@chvCol,@chvVal
while @@fetch_status>=0
Begin
Select @chvColTemp=''
if @chvCol Like '%[^A-Z0-9]%'
Begin
Select @insR=1
While @insR<=DataLength(RTRim(@chvCol))
Begin
Select @chvColTemp=RTRim(@chvColTemp)+
Case
When Substring(@chvCol,@insR,1) Like '[A-Z0-9_]' then Substring(@chvCol,@insR,1)
Else ' '
End
Select @insR=@insR+1
End
Select @chvCol=@chvColTemp
End
Select @chvExec='update #crosstable set '+
Case
when substring(@chvCol,1,1) Like '[^1234567890]' then @chvCol
Else '_'+LTrim(@chvCol)
End+'='+
Case
when @chvVal is Null then '0'
Else RTrim(@chvVal)
End+' where Rowhead='''+RTRim(@chvRow)
Select @chvRow=
Case
When @chvRow is Null Then 'NULL'
Else RTrim(@chvRow)
End
Select @chvRowTemp=''
if @chvRow Like'%'
Begin
Select @insR=1
While @insR<=DataLength(RTrim(@chvRowTemp))-1
Begin
Select @chvRowTemp=RTrim(@chvRowTemp)+
Case
When Substring(@chvRow,@insR,1) Like '[^'']' then Substring(@chvRow,@insR,1)
Else ' '' '' '
End
Select @insR=@insR+1
End
End
Select @chvRow=@chvRowTemp
Select @chvExec=@chvExec+@chvRow+''''
--Print @chvExec
Exec(@chvExec)
Fetch colname_cursor3 into @chvRow,@chvCol,@chvVal
End

Close colname_cursor3
Deallocate colname_cursor3
Commit Tran

Set NoCount off
Select @chvExec='Select * from #crosstable'
--Print @chvExec
Exec(@chvExec)

Drop Table #colnames
Drop Table #rownames
Drop Table #crosstable


1. exec procsstab 'stor_id','ord_date','qty','sales',1,1 --weekday
insert #colnames
select col1,col2 from
(select distinct col1=datename(weekday,ord_date),
col2=datepart(weekday,ord_date),col3=datepart(weekday,ord_date)
from sales)xyz
order by col3
2.exec proc 'stor_id','ord_date','qty','sales',1,4 --quarter
insert #colnames
select col1,col2
from (select distinct col1='quarter_'+datename(quarter,ord_date),
col2=datepart(quarter,ord_date),col3=datepart(quarter,ord_date)
from sales)xyz
order by col3
第二列是仅用于一个数值(0除外),该数值用于当交叉表在过程的后面部分被更新时查找该列.对于非日期型数据,该数值只简单的转换成char并进行比较.
第三列用于提供合适的分类.通过合适的数据类型上进行分组,这些列以合适的顺序被列举出,而不管原始数据类型是什么.


交叉制表--检查列的计数值和长度并加入行数据(9)
除了需要证实列的计数值和名称长度,如果一切正常用户还需要填写#rownames表
--check column count
if (select count(*) from #colnames)>1023
begin
drop table #colnames
raiserror 51004 'distinct column count exceeded max of 1023'
return -1
end
--verify colnames do not exceed max length
if (select max(datalength(rtrim(colname))-1) from #colnames)>29
begin
drop table #colnames
raiserror 51050 'column data length exceeded max of 30'
return -1
end
--if all is ok ,continue to add #rownames data
select @chvexec='insert #rownames select distinct '+
case @introwtype
when 1 then 'convert(varchar(255),' else ''
end +rtrim(@chrrowhead)+
case @introwtype
when 1 then ')'
else ''
end +'from '+@chrsource
--print @chvexec
exec(@chvexec)


 交叉制表--产生列头列表(7)
下面代码部分将创建列头列表和行头列表.代码首先定义两个临时表
create table #colnames(colname varchar(255) null,colnumber int null)
create table#rownames(rowname varchar(255) null)
根据源表的数据为所请求列创建一个insert语句.这个代码需要处理一些细节;日期分组,将非字符型数据转换为char型及根据原始数据类型进行分类.第一项用于处理为请求的参数建立一个合适的日期分组,第二项用于使数据可以用作交叉表中的一个列名称.最后一项在下面所列举的情况下使很必要的;数字型转换为字符型数据;当把这些数字作为字符进行分类时,顺序使用字典顺序(1,10,2,3,4,,,9)但用户期望的是正确的数字顺序
-- insert distinct column data into #colnames
select @chvexec=''insert #colnames select col1,col2 from ''
+''(select distinct col1=''+
case @inttemp
when 3 then
case when @inygrouping in(1,3) then ''datetime(''+
case @inygrouping when 1 then ''weekday''
when 3 then ''month''
end+'',''+rtrim(@chrcolhead)+'')''
else case @inygrouping
when 2 then ''''''week''
when 4 then ''''''quarter''
when 5 then ''''''year''
end+''-"+''+''+''datename(''+
case @inygrouping
when 2 then ''week''
when 4 then ''quarter''
when 5 then ''year''
end+'',''+rtrim(@chrcolhead)+'')''
end
else case @intcoltype
when 1 then convert(varchar(255),,''+rtrim(@chrcolhead)+'')''
else rtrim(@chrcolhead)
end
end+,col2=''+case @inttemp
when 3 then ''datepart(''+
case @inygrouping
when 1 then ''weekday''
when 2 then ''week''
when 3 then ''month''
when 4 then ''quarter''
when 5 then ''year''
end+'',''+rtrim(@chrcolhead)+'')''
else ''0''
end+'',col3=''+
case @inttemp
when 3 then ''datepart(''+
case @inygrouping
when 1 then ''weekday''
when 3 then ''month''
when 2 then ''week''
when 4 then ''quarter''
when 5 then ''year''
end+'',''+rtrim(@chrcolhead)+'')''
else rtrim(@chrcolhead)
end +''from ''+rtrim(@chrsource)+'') xyz order by col3''
exec(@chvexec)
在insert语句内部创建select语句包含三列(其中两列实际上被插入).为了进行存档,可使用导出表来创建一个三列的结果集;其中只选择两列进行插入.如果该列的列头数据类型是日期型,则将转换为datename,作为特殊的分组类型.如果datename函数返回一个数值类型数据,则数据分组的名称并添加一个下划线作为该数字的前缀(例如quarter_4)


交叉制表-检查列的安全性(6)
在查询用于sp_column_privileges 系统存储过程之后,对查询建立模型.一个相当复杂的查询用于检查具有选择权限的列的号码是否等于3.该查询中检查每个列的参数,并且给那些当前用户有选择权限的列返回一个值1.如果所有三列都是可以访问的,if语句成功执行并且过程继续执行下去.否则,产生一个错误并且过程继续执行.
--check for permission on source
if user_id()<>1
begin
if select count(distinct c.name)
from syscolumns c,sysobjects o,sysprotects p,sysusers u,master..spt_values v
where c.name in(@chrcolhead,@chrrowhead,@chrvalue)
and c.id=o.id and p.id=c.id and c.colid=v.number and v.type='p'
and o.id=object_id(@chrsource) and u.uid=user_id() or u.uid in
(select u1.uid from sysusers u1 where u1.gid=u1.uid and u1.gid in
(select u2.gid from sysusers u2 where u2.uid=user_id() or u2.uid=user_id('public'))))
and p.uid=u.uid
and p.action=193 and p.protecttype=205
and columns is not null and
case substring(p.columns,1,1)&1
when null then 255
when 0 then convert(tinyint,substring(p.columns,v.low,1))
else(~convert(tinyint,isnull(substring(p.columns,v.low,1),0)))
end &v.high<>0
and not exists
(select * from syscolumns c5,sysobjects o5,sysprotects p5,sysusers u5,master..spt_values v5
where c.name in(@chrcolhead,#@chrrowhead,@chrvalue)
and c5.colid=c.colid and c5.id=o5.id and p5.id=c5.id
and c5.colid=v5.number and v5.type='p'
and o5.id=object_id(@chrsource)
and u5.uid=user_id() or u5.uid in
(select u6.uid from sysusers u6 where u6.gid=u6.uid and u6.gid in
(select u7.gid from sysusers u7 where u7.uid=user_id() or u7.uid=user_id('public'))))
and p5.uid=u5.uid
and p5.action=193
and p5.protecttype=206
and p5.columns is not null
and case substring(p5.columns,1,1)&1
when null then 255
when 0 then convert(tinyint,substring(p5.columns,v5.low,1))
else (~convert (tinyint,isnull(substring(p5.columns,v5.low,1),0)))
end &v5.high<>0))<>3
begin
raiserror 51003 'permission denied on column'
return -1
end
第一个if语句将dbo排除在查找之外,对数据库拥有者作额外的处理是不必要的.
在这个语句中,列名数目的计数值是返回值.被查询的表都来自master数据库.
当前用户的id必须与sysuser中的uid 相匹配;或者与当前用户所属的任何组相匹配.
来自sysprotect的uid需要与来自sysusers的uid相匹配,并且要查找值为193的动作
(选择权限)和值为205的protecttype(授予权限)如果一个用户是一个组的成员,而不是public的成员,当被查询时,用户没有作为sysusers表中的public成员列举出--即使所有的用户总是public的一个成员.当检查一个用户的权限时,有必要检查public组.
spt_values表用于查找sysprotects表中columns列的bit位置,这个表的用户正为其检查权限的表.
来自columns列(二进制数据类型)的数据的第一个字节在case表达式中被使用.来自spt_values的低位值用于查找来自二进制数据的另一个字节.该值和高位值之间执行位于(and)运算;如果不为0,用户对指定的列有访问权限.注意spt_value表的number列与来自syscolumns的colid相匹配,并且其类型值为'p'(permission,权限),这将保证当检查权限时高位和低位值从属表中的某一列.
如果一切正常,查询返回3,表明用户在所有这三个列上具有选择权限.


交叉制表--列头和行头数据类型(5)
--get standard data type of @chrcolhead column
select @chvtemp=t2.name
from sysobjects o
join syscolumns c on (o.id=c.id)
join systypes t1 on (t1.usertype=c.usertype)
join systypes t2 on(t1.type=t2.type)
where t2.usertype<100 and t2.usertype<>18 and t2.usertype<>80
and o.type in(''''u'''',''''v'''')
and o.name=@chrsource and c.name=@chrcolhead

if upper(@chvtemp) not in(''''char'''',''''varchar'''')
select @intcoltype=1
else select @intcoltype=0
--get standard data type of @chrrowhead
select @chvrowtype=t2.name
from sysobjects o
join syscolumns c on (o.id=c.id)
join systypes t1 on (t1.usertype =c.usertype)
join systypes t2 on(t1.type=t2.type)
where t2.usertype<100 and t2.usertype<>18 and t2.usertype<>80
and o.type in(''''u'''',''''v'''') and o.name=@chrsource and c.name=@chrrowhead
if upper(@chvrowtype)not in (''''char'''',''''varchar'''')
select @introwtype=1
else select @introwtype=0
--categorize types for grouping check
select @inttemp=case
when @chvtemp in(''''int'''',''''smallint'''',''''tinyint'''',''''float'''',''''real'''',''''decimal'''',''''numeric'''',''''money'''',''''smallmoney'''') then 1
when @chvtemp in (''''datetime'''',''''smalldatetime'''' )then 3
when @chvtemp in (''''bit'''',''''char'''',''''varchar'''') then 5
else 100 end
--validate existing data type is consistant with selected grouping
if (@inttemp=5 and @ingrouping>0) or (@inttemp=1 and @inygrouping>0) or
(@inttemp=3 and @inygrouping=0)
begin
raiserror 51030 ''''crosstab grouping not valid with @chrcolhead definition.''''
return -1
end
最后的代码验证列数据类型与日期分组参数的一致性.如果列数据是一个日期,日期分组必须在1和5之间,否则日期分组必须为0.


 交叉制表-验证数据类型和聚合操作(4)
现在用户需要找到value列的数据类型并确认它对于所使用请求的聚合类型是有效的

--create typestr to hold aggregate name
select @chvtype=
case @inytype
when 1 then ''sum''
when 2 then ''avg''
when 3 then ''max''
when 4 then ''min''
when 5 then ''count''
else ''sum'' end
--get standard data type of @chrvalue column
select @chvtemp=t2.name
from sysobjects o
join syscolumns c on (o.id=c.id)
join systypes t1 on(t1.usertype=c.usertype)
join systypes t2 on(t1.type=t2.type)
where t2.usertype<100
and t2.usertype<>18
and t2.usertype<>80
and o.type in(''u'',''v'')
and o.name=@chrsource
and c.name=@chrvalue
--categorize type for aggregate check
select @inttemp=case
when @chvtemp in (''int'',''smallint'',''tinyint'',''float'',''real'',''decimal'',''numeric'',''money'',''smallmoney'') then 1
when @chvtemp in(''datetime'',''smalldatetime'') then 3
when @chvtemp in(''bit'',''char'',''varchar'') then 5
else 100
end
--validate existing data type is consistent with selected aggregate
if @inytype <@inttemp
begin
raiserror 51020 ''crosstab type not valid with @chrvalue definition''
return -1
end
--hold the data type for future use
select @chrcoltype=rtrim(case @inytype when 5 then ''int''
else case when @chvtemp in (''bit'',''char'',''varchar'') then ''int''
when @chvtemp in(''decimal'',''numeric'') then ''flaot''
else @chvtemp end
end)
--verfy grouping is valid for colhead
if @inygrouping<0 or @inygrouping>5
begin
raiserror 51010 ''invalid crosstab grouping''
return -1
end
数据类型可以分为1,3,5,100.聚合类型是否能有效使用value的数据类型.
数据类型.聚合操作和分类值的列表.每个数据类型支持任何处于同一级别或更高级别的聚合操作.因为当传递给过程时聚合操作已经被分配了一个值,所以数据类型被分组

分类 数据类型 聚合操作
1 numerics sum
2 avg
3 datetime min
4 max
5 其他所有类型 count

如果需要计数,用户将value列的数据类型转换为int.否则 bit.char和varchar值被转换为int
(因为只有计数操作可以在bit,char和varchar上执行),并且numeric和decimals被转换成float.


交叉制表-验证对象的存在(3)
第一步检查三个列名称和源表或视图的存在性之后需要确认聚合操作合适于value列的数据类型,最后,数据分组参数需要针对列头进行验证.
通过考察sysobjects 表可以验证源对象的存在性.所有提供的三个列分别被检查以查看他们对于提供的源对象在syscolumns表中是否存在.
--check if source exists
if not exists
(select * from sysobjects where name=@chrsource and type in(''v'',''u''))
begin
raiserror 51001 ''source does not exist.''
return -1
end
--check for column existence
if not exists
(select sc.name from syscolumns sc join sysobjects so on sc.id=so.id
where so.name=@chrsource and sc.name=@chrhead)
begin
raiserror 51002 ''invalid @chrcolhead name''
return -1
end
if not exists
(select sc.name from syscolumns sc join sysobjects so on sc.id=so.id
where so.name=@chrsource and sc.name=@chrrowhead)
begin
raiserror 51002 ''invalid @chrrowhead name''
return -1
end

if not exists
(select sc.name from syscolumns sc join sysobjects so on sc.id=so.id
where so.name=@chrsource and sc.name=@chrvalue)
begin
raiserror 51002 ''invalid @chrvalue name''
return -1
end
--verify type is valid 1(sum) 2(avg) etc...
if @inytype<1 or @inytype>5
-- if @inytype not between 1 and 5
begin
raiserror 51000 ''invalid crosstab type''
return -1
end


交叉制表的存储过程变量部分的定义(2)
create proc prcrosstab
@chrrowhead char(30),--表示列,在交叉制表的结果中作为第一列出现
@chrcolhead char(30),--表示列,在交叉制表的结果中该列中的数据被转换为新列名称
@chrvalue char(30),--表示列,在该列中执行聚合函数
@chrsource char(30),--源表或视图
@inytype tinyint=1,--1-求和2-平均值 3-最小值 4-最大值5-计数
@inygrouping tinyint=0--1工作日 1-年内的周数 3-月份 4-季度 5-年份
as
declare
@chvrow varchar(255),
@chvcol varchar(255),
@chvtype varchar(10),
@chvrowtype varchar(10),
@chvcoltype varchar(10),
@chvtemp varchar(255),
@chvcoltemp varchar(255),
@chvrowtemp varchar(255),
@inttype int,
@introwtype int,
@intcoltype int,
@chvexec varchar(255),
@chvgroup varchar(255),
@flttemp float,
@dtmtemp datetime,
@insR smallint,
@intcolumn int,
@intreturn int,
@inttemp int,
@intcolnamelen int,
@intmaxrowhead int

set nocount on


交叉制表(sqlserver7编程技术内幕)的必要考虑(1)
如何将数据返回给用户?如何在这个实际上表示行值的数值结构上创建列?如何对用户所要求的数据进行用户权限确认?从哪里开始入手?
1.确定返回什么数据作为交叉表的数据的结果.在这种情况下最好使用一个临时表.但是只使用临时表还不能完成任务.临时表在结构和内容上需要作太多的修改,并且只使用t-sql也不能完成任务.
需要使用一个永久表.对于所有的用户会如何呢?每个用户需要自己的表.当对数据进行交叉制表操作时多个登录机会会潜在的使用相同的表(被称为crosstable的表),从而存在潜在的冲突.
由于tsql的性质,交叉表不能用动态执行进行定义.这要通过随机生成交叉表的名称进行定义.没有找到更好的方法.
2.返回数据的方法建立以后,作一些实际的处理.参数由以下部分组成:一个标识行头的列,一个标识列头的列,一个聚合值的列以及源本身(一个表或一个视图)
3.来检查列和源对象的存在性.如果都存在,则建立两个临时表用来保存行数据和列数据.创建交叉表,从行临时列表取出行并利用列临时表中的数据来改变行.
数据一般不能用作列名称,因为它经常不符合sqlserver标识符的规则.检查数据并删除非法字符,以及如果打头的值为数字,则使用下划线将该值变换为前缀.
4.再下一步创建一个游标保存数据.当用户遍历游标行时,执行一个execute语句来更新交叉表的内容.如果这些完成,则返回交叉表的结果,删除所有的表并释放游标.


行转列的例子
http://www.delphibbs.com/delphibbs/dispq.asp?lid=1472876
表结构如下XX，YY，mData.表名就定为TableA 吧。XX,YY同时为关键字。
有如下的数据
XX YY mData
1 1 1
1 2 2
1 3 3
1.5 1 1.5
1.5 2 2.5
2 1 3
2 2 4
2 3 36

我要把上面的数据按XX坐标排列后，形成字符串，如果该坐标YY为空值，则用--代替。
上面的结果就是
XX 1 1.5 2
YY1 1 1.5 3
YY2 2 2.5 4
YY3 3 -- 36

注：上面的YY1，YY2，YY3一定是整数，但XX不一定。（YY1表示YY=1时，YY2表示YY=2）
我建的表中的字段都是varchar(10)，运行通过，不知合不合用
if exists(select * from sysobjects where type='P' and name='RowToCol')
drop procedure RowToCol
go
create procedure RowToCol as
begin
declare @create varchar(4000)
declare @ins varchar(1000)
select @create='create table #temp (columnXX varchar(10)',@ins='insert into #temp values(''XX'''
declare @XX varchar(10)
declare xx_cursor cursor for select distinct XX from TableA order by XX
open xx_cursor
fetch next from xx_cursor into @XX
while @@fetch_status = 0
begin
select @create=@create+',[column'+@XX+'] varchar(10) default(''--'')',@ins=@ins+','+@XX
fetch next from xx_cursor into @XX
end
close xx_cursor
deallocate xx_cursor
select @create=@create+')',@ins=@ins+')'

declare @YY varchar(10),@DATA varchar(10)
declare @OldYY varchar(10),@values varchar(1000)
declare yy_cursor cursor for select YY,XX,mData from TableA order by YY
open yy_cursor
fetch next from yy_cursor into @YY,@XX,@DATA
while @@fetch_status = 0
begin
if @oldYY=@YY
select @ins=@ins+',[column'+@XX+']', @values=@values+','+@DATA
else
begin
if @oldYY is not null
select @ins=@ins+')'+@values+')'+char(13)+'insert into #temp(columnXX,[column'+@XX+']',@values=' values(''YY'+@YY+''','+@DATA
else
select @ins=@ins+char(13)+'insert into #temp(columnXX,[column'+@XX+']',@values=' values(''YY'+@YY+''','+@DATA
end
select @oldYY=@YY
fetch next from yy_cursor into @YY,@XX,@DATA
end
close yy_cursor
deallocate yy_cursor
select @ins=@ins+')'+@values+')'
print @create
print @ins
exec(@create+@ins+'select * from #temp')


SQL实现交叉表的方法？
交叉一般来讲是分组统计的一种，形式更复杂，显示更清淅，但数据库本身并没有提供实现交叉表的功能，自己创建交叉表不仅要对过程、游标、临时表、动态SQL等非常熟悉，而且思路也要清淅，本例以PUBS.DBO.SALES表的数据做样本：
CREATE PROCEDURE UP_TEST(
@T1 VARCHAR(30),@T2 VARCHAR(30),
@T3 VARCHAR(30),@T4 VARCHAR(30)) AS
--T1 表名,T2,T3是交叉表的两上分类字段,T4是汇总字段
--T2是行字段，T3列字段
BEGIN
DECLARE @SQL VARCHAR(7999),@FIELD VARCHAR(30)
SELECT @SQL='SELECT DISTINCT '+@T3+' FROM '+@T1
CREATE TABLE #FIELD(FIELD VARCHAR(30))
--将列字段提取到临时表#FIELD中
INSERT INTO #FIELD EXEC(@SQL)
SELECT @SQL='CREATE TABLE CROSS_TEST('+@T2+' VARCHAR(30),'
DECLARE CUR_FIELD CURSOR LOCAL FOR SELECT * FROM #FIELD
OPEN CUR_FIELD
FETCH CUR_FIELD INTO @FIELD
WHILE @@FETCH_STATUS=0 BEGIN
SELECT @FIELD='['+@FIELD+']'
SELECT @SQL=@SQL+@FIELD+' DECIMAL(8,2) DEFAULT 0,'
FETCH CUR_FIELD INTO @FIELD
END
SELECT @SQL=LEFT(@SQL,LEN(@SQL)-1)+')'
--创建临时交叉表CROSS_TEST
EXEC(@SQL)
SELECT @SQL='INSERT INTO CROSS_TEST('+@T2+') SELECT DISTINCT '+@T2+' FROM '+@T1
--将行数据存入交叉表#CROSS_TEST
EXEC(@SQL)
--创建分组数据表TEMP
SELECT @SQL='CREATE TABLE TEMP('+@T2+' VARCHAR(30),'+@T3+' VARCHAR(30),'+@T4+' DECIMAL(8,2))'
EXEC(@SQL)
--将交叉汇总数据放入交叉表
SELECT @SQL='SELECT '+@T2+','+@T3+', SUM(QTY) QTY FROM '+@T1 +' GROUP BY '+@T2+','+@T3
INSERT INTO TEMP EXEC(@SQL)
--将汇总数据写入交叉表
DECLARE CUR_SUM CURSOR LOCAL FOR SELECT * FROM TEMP
DECLARE @F1 VARCHAR(30),@F2 VARCHAR(30),@QTY DECIMAL(8,2),@Q1 VARCHAR(30)
OPEN CUR_SUM
FETCH CUR_SUM INTO @F1,@F2,@QTY
WHILE @@FETCH_STATUS=0 BEGIN
SELECT @F2='['+@F2+']',@Q1=CAST(@QTY AS VARCHAR(30))
SELECT @SQL='UPDATE CROSS_TEST SET '+@F2+'='+@Q1+' WHERE '+@T2+'='''+@F1+''''
EXEC(@SQL)
FETCH CUR_SUM INTO @F1,@F2,@QTY
END
CLOSE CUR_SUM
SELECT * FROM CROSS_TEST
DROP TABLE TEMP
DROP TABLE CROSS_TEST
DROP TABLE #FIELD
END
--------------------------------------------------------
EXEC UP_TEST 'SALES','TITLE_ID','STOR_ID','QTY'
说明：字段加中括号为了处理字段中含有特殊字符，值得注意得是要实现交叉表的表必须有两个分类，本例只支持分类字段的数据类型是字符型的，最大的问题就是高亮显示这行的WHERE条件啦，字符类型字段查询时条件必须加单引号，如果是数值类型就可以直接写，所以数值类型的分类字段更容易实现一些，更可以融合在一个过程中。通常大家看到的交叉表都有行汇总与列汇总等信息，本例就没有实现，最后一点工作大家自己练练手吧。


 SQL语句实现横排？
有一个表：
BH GZLB JE
----------------------
001 A 100
001 B 150
001 C 110
002 A 99
002 B 180
002 C 150
003 A 160
003 B 170
003 C 130
用SQL语句如何将上面的表变成下面横向的排列方式呢。
BH A B C
-------------------
001 100 150 110
002 99 180 150
003 160 170 130
（GZLB 里面的字段是动态的，不是只有A,B,C 可能还有A,B,C,D,E...）

---------------------------------------------------------------

if exists(select name from sysobjects where name='tblA' and xtype='U')
drop table tblA

create table tblA(
BH char(3) not null,
GZLB varchar(3) not null,
JE int not null
)
go

insert tblA values('001', 'A', 100)
insert tblA values('001', 'B', 150)
insert tblA values('001', 'C', 110)
insert tblA values('002', 'A', 99)
insert tblA values('002', 'B', 180)
insert tblA values('002', 'C', 150)
insert tblA values('003', 'A', 160)
insert tblA values('003', 'B', 170)
insert tblA values('003', 'C', 130)

declare @sql varchar(600)
set @sql = 'select'
select distinct GZLB as 'GZLB' into tmp from tblA

select @sql = @sql +' max(case GZLB when '''+ GZLB +''' then JE else null end) as '''+ GZLB +''',' from tmp
set @sql = substring(@sql, 1, len(@sql)-1) +' from tblA group by BH'
exec(@sql)
---------------------------------------------------------------

用游标
declare @sqlstr varchar(2000)
declare @gzlb
select sqlstr='select bh'
declare cgzlb cursor for select gzlb from table
create table #a
(bh char(3))
open cgzlb
fetch next from cgzlb into @gzlb
while (fetch_status<>-1)
begin
exec('alter table #a add '+@gzlb +' char(1)')
exec('insert into #a (bh,'+@gzlb+' select bh,je from table where gzlb='''+@gzlb+'''')
select @sqlstr=@sqlstr+',sum(isnull('+@gzlb+'))'

fetch next from cgzlb into @gzlb
end
close cgzlb
deallocate cgzlb
select @sqlstr=@sqlstr+' from #a group by bh order by bh
execute(@sqlstr)

---------------------------------------------------------------

declare @sql varchar(6000)
set @sql = 'select '
select @sql = @sql +' sum(case GZLB when '''+ GZLB +''' then JE else null end) ['+ GZLB +'],' from (select distinct GZLB as GZLB from tblA) tmp
set @sql = substring(@sql, 1, len(@sql)-1) +' from tblA group by BH'
exec(@sql)


The SQL Server Black Box
Have you ever had the problem where a user ran a query against your SQL Serer and crashed it or made the server unusable since the CPU was spiked at 100%? A SQL Server black box is the equivalent of a flight data record. The black box records all queries being passed to your SQL Server and other useful information like errors. You can use this information to determine why your server crashed or what error occured right before your CPU was pegged.
A black box traps the following information:

Errors and warnings (Attention and Exception)
Stored procedure execution (RPC: Starting)
T-SQL execution (SQL: BatchStarting)
For those given events, the following information is trapped:

Query or error that was executed
Date and time executed
The user that executed the query or sproc
Database the event occured in
The server or workstation that sent the query or caused the error
Application name that performed the query
The black box writes to the file in 128K chunks. In other words, when you start the black box, it will appear as if the file has 0K until it has 128K worth of data to write. This architecture makes it a very efficient process and where it uses minimal CPU utilization. Because of that, you can run it for extended periods of time and not worry about it killing your server's performance. If you stop SQL Server, the SQL Server will write whatever data it has in cache to the trace file.
The file is written out to the data directory by default (\Program Files\Microsoft SQL Server\MSSQL\data) and is called blackbox.trc. As the file grows to 5MB, it rolls over to a new file. A new file will be created also occurs if you start and stop SQL Server, overwriting your old file. Make sure that you occasionally go through and delete the old trace file that may've rolled over.

Now that you know what a black box is, how do you start one? To start one, you have to use the sp_trace_create system stored procedure. You set the trace type to the special server-side trace status of 8 to make it a black box trace file. The below stored procedure will create a black box stored procedure called startblackbox that can be used to start the black box on demand. I have optionally used the sp_procoption stored procedure to start the black box when SQL Server starts.

-----------------------------------------------------------------------------------
USE master
GO

CREATE PROC startblackbox
AS

declare @TraceID int

exec sp_trace_create @TraceID output, 8
exec sp_trace_setstatus @traceID, 1

RETURN
go
--Optional part of syntax to make the black box start on the startup of SQL Server
exec sp_procoption startblackbox, 'startup', 'on'
go
-----------------------------------------------------------------------------------

Now that you have the stored procedure, give it a shot on a development server! You can use the output in many ways. For example, you can use the output for Index Tuning Wizard input to tune your server. The main reason for this trace is to send to Microsoft product support or for debugging yoursel


Utilizing fn_virtualfilestats
Utilizing ::fn_virtualfilestats - Example

In this example, we will attempt to get some statistical information on file-group utilisation to assist us with IO monitoring. The DBA should re-read the article “How to Diagnose and Fix Wait Stats” by Cathan Kirkwood before attempting to analyse the statistics returned for your particular database.

The script below is very simple and is a starting point for a single stored procedure you can build in the future. The paradigm itself is simple enough. We will store every N seconds the results from ::fn_virtualfilestats into a working table. We will then get the difference between the previous scan for each file-group within the selected database. From here we will utilise Analysis Services to build a cube and query the results through the Excel pivot control.

The script I used is shown below; remembering that I was extremely lazy (hang on, make that “busy”) and did not write an generic routine. The routine was run via the tempdb database.

-- We have pre-queried sysdatabases and filegroups and will be monitoring as follows:

-- dbid = 5

-- 1 = system

-- 2 = log

-- 3 = data

-- 4 = index

-- 5 = audit

-- don’t create it in the DB we are monitoring

drop table tempdb.ck_filestats

-- table to store out statistic data

create table ck_filestats (

id int identity(1,1) clustered index,

dbid int,

dbname varchar(50),

fileid int,

[filegroup] varchar(150),

timestmp bigint,

numreads bigint,

numreads_diff bigint,

numwrites bigint,

numwrites_diff bigint,

bytesread bigint,

bytesread_diff bigint,

byteswrite bigint,

byteswrite_diff bigint,

iostallms bigint,

iostallms_diff bigint,

statstime datetime default getdate()

)

begin

declare @aa int

set @aa= 1440 -- approx 4hrs of data collection every 10 seconds

while @aa > 1 begin

waitfor delay '00:00:10' -- 10 sec delay per looper

-- get stats for file 1

insert into ck_filestats (dbid, fileid, timestmp, numreads, numwrites, bytesread, byteswrite, iostallms) select * from ::fn_virtualfilestats(5,1)

update ck_filestats

set numreads_diff = ck_filestats.numreads - (select numreads from ck_filestats B where B.id = (select max(id) from ck_filestats C where ck_filestats.dbid = C.dbid and ck_filestats.fileid = C.fileid and C.id <> ck_filestats.id)),

numwrites_diff = ck_filestats.numwrites - (select numwrites from ck_filestats B
where B.id = (select max(id) from ck_filestats C where ck_filestats.dbid = C.dbid and ck_filestats.fileid = C.fileid and C.id <> ck_filestats.id)),

bytesread_diff = ck_filestats.bytesread - (select bytesread from ck_filestats B where B.id = (select max(id) from ck_filestats C where ck_filestats.dbid = C.dbid and ck_filestats.fileid = C.fileid and C.id <> ck_filestats.id)),

byteswrite_diff = ck_filestats.byteswrite - (select byteswrite from ck_filestats B where B.id = (select max(id) from ck_filestats C where ck_filestats.dbid = C.dbid and ck_filestats.fileid = C.fileid and C.id <> ck_filestats.id)),

iostallms_diff = ck_filestats.iostallms - (select iostallms from ck_filestats B where B.id = (select max(id) from ck_filestats C where ck_filestats.dbid = C.dbid and ck_filestats.fileid = C.fileid and C.id <> ck_filestats.id))

where dbid = 5 and fileid = 1 and id = (select max(id) from ck_filestats C where ck_filestats.dbid = C.dbid and ck_filestats.fileid = C.fileid)

-- get stats for file 2

insert into ck_filestats (dbid, fileid, timestmp, numreads, numwrites, bytesread, byteswrite, iostallms) select * from ::fn_virtualfilestats(5,2)

update ck_filestats

set numreads_diff = ck_filestats.numreads - (select numreads from ck_filestats B where B.id = (select max(id) from ck_filestats C where ck_filestats.dbid = C.dbid and ck_filestats.fileid = C.fileid and C.id <> ck_filestats.id)),

numwrites_diff = ck_filestats.numwrites - (select numwrites from ck_filestats B where B.id = (select max(id) from ck_filestats C where ck_filestats.dbid = C.dbid and ck_filestats.fileid = C.fileid and C.id <> ck_filestats.id)),

bytesread_diff = ck_filestats.bytesread - (select bytesread from ck_filestats B where B.id = (select max(id) from ck_filestats C where ck_filestats.dbid = C.dbid and ck_filestats.fileid = C.fileid and C.id <> ck_filestats.id)),

byteswrite_diff = ck_filestats.byteswrite - (select byteswrite from ck_filestats B where B.id = (select max(id) from ck_filestats C where ck_filestats.dbid = C.dbid and ck_filestats.fileid = C.fileid and C.id <> ck_filestats.id)),

iostallms_diff = ck_filestats.iostallms - (select iostallms from ck_filestats B where B.id = (select max(id) from ck_filestats C where ck_filestats.dbid = C.dbid and ck_filestats.fileid = C.fileid and C.id <> ck_filestats.id))

where dbid = 5 and fileid = 2 and id = (select max(id) from ck_filestats C where ck_filestats.dbid = C.dbid and ck_filestats.fileid = C.fileid)

-- get stats for file 3

insert into ck_filestats (dbid, fileid, timestmp, numreads, numwrites, bytesread, byteswrite, iostallms) select * from ::fn_virtualfilestats(5,3)

update ck_filestats

set numreads_diff = ck_filestats.numreads - (select numreads from ck_filestats B where B.id = (select max(id) from ck_filestats C where ck_filestats.dbid = C.dbid and ck_filestats.fileid = C.fileid and C.id <> ck_filestats.id)),

numwrites_diff = ck_filestats.numwrites - (select numwrites from ck_filestats B where B.id = (select max(id) from ck_filestats C where ck_filestats.dbid = C.dbid and ck_filestats.fileid = C.fileid and C.id <> ck_filestats.id)),

bytesread_diff = ck_filestats.bytesread - (select bytesread from ck_filestats B where B.id = (select max(id) from ck_filestats C where ck_filestats.dbid = C.dbid and ck_filestats.fileid = C.fileid and C.id <> ck_filestats.id)),

byteswrite_diff = ck_filestats.byteswrite - (select byteswrite from ck_filestats B where B.id = (select max(id) from ck_filestats C where ck_filestats.dbid = C.dbid and ck_filestats.fileid = C.fileid and C.id <> ck_filestats.id)),

iostallms_diff = ck_filestats.iostallms - (select iostallms from ck_filestats B where B.id = (select max(id) from ck_filestats C where ck_filestats.dbid = C.dbid and ck_filestats.fileid = C.fileid and C.id <> ck_filestats.id))

where dbid = 5 and fileid = 3 and id = (select max(id) from ck_filestats C where ck_filestats.dbid = C.dbid and ck_filestats.fileid = C.fileid)

-- get stats for file 4

insert into ck_filestats (dbid, fileid, timestmp, numreads, numwrites, bytesread, byteswrite, iostallms) select * from ::fn_virtualfilestats(5,4)

update ck_filestats

set numreads_diff = ck_filestats.numreads - (select numreads from ck_filestats B where B.id = (select max(id) from ck_filestats C where ck_filestats.dbid = C.dbid and ck_filestats.fileid = C.fileid and C.id <> ck_filestats.id)),

numwrites_diff = ck_filestats.numwrites - (select numwrites from ck_filestats B where B.id = (select max(id) from ck_filestats C where ck_filestats.dbid = C.dbid and ck_filestats.fileid = C.fileid and C.id <> ck_filestats.id)),

bytesread_diff = ck_filestats.bytesread - (select bytesread from ck_filestats B where B.id = (select max(id) from ck_filestats C where ck_filestats.dbid = C.dbid and ck_filestats.fileid = C.fileid and C.id <> ck_filestats.id)),

byteswrite_diff = ck_filestats.byteswrite - (select byteswrite from ck_filestats B where B.id = (select max(id) from ck_filestats C where ck_filestats.dbid = C.dbid and ck_filestats.fileid = C.fileid and C.id <> ck_filestats.id)),

iostallms_diff = ck_filestats.iostallms - (select iostallms from ck_filestats B where B.id = (select max(id) from ck_filestats C where ck_filestats.dbid = C.dbid and ck_filestats.fileid = C.fileid and C.id <> ck_filestats.id))

where dbid = 5 and fileid = 4 and id = (select max(id) from ck_filestats C where ck_filestats.dbid = C.dbid and ck_filestats.fileid = C.fileid)

-- get stats for file 5

insert into ck_filestats (dbid, fileid, timestmp, numreads, numwrites, bytesread, byteswrite, iostallms) select * from ::fn_virtualfilestats(5,5)

update ck_filestats

set numreads_diff = ck_filestats.numreads - (select numreads from ck_filestats B where B.id = (select max(id) from ck_filestats C where ck_filestats.dbid = C.dbid and ck_filestats.fileid = C.fileid and C.id <> ck_filestats.id)),

numwrites_diff = ck_filestats.numwrites - (select numwrites from ck_filestats B where B.id = (select max(id) from ck_filestats C where ck_filestats.dbid = C.dbid and ck_filestats.fileid = C.fileid and C.id <> ck_filestats.id)),

bytesread_diff = ck_filestats.bytesread - (select bytesread from ck_filestats B where B.id = (select max(id) from ck_filestats C where ck_filestats.dbid = C.dbid and ck_filestats.fileid = C.fileid and C.id <> ck_filestats.id)),

byteswrite_diff = ck_filestats.byteswrite - (select byteswrite from ck_filestats B where B.id = (select max(id) from ck_filestats C where ck_filestats.dbid = C.dbid and ck_filestats.fileid = C.fileid and C.id <> ck_filestats.id)),

iostallms_diff = ck_filestats.iostallms - (select iostallms from ck_filestats B where B.id = (select max(id) from ck_filestats C where ck_filestats.dbid = C.dbid and ck_filestats.fileid = C.fileid and C.id <> ck_filestats.id))

where dbid = 5 and fileid = 5 and id = (select max(id) from ck_filestats C where ck_filestats.dbid = C.dbid and ck_filestats.fileid = C.fileid)

set @aa = @aa - 1

end

-- Manually upate the table, can be simply done by quering the sys tables

update ck_filestats set dbname = 'MyDB’

update ck_filestats set [filegroup] = 'SYSTEM' where fileid = 1

update ck_filestats set [filegroup] = 'LOG' where fileid = 2

update ck_filestats set [filegroup] = 'CORPSYS' where fileid = 3

update ck_filestats set [filegroup] = 'DATA' where fileid = 4

update ck_filestats set [filegroup] = 'AUDIT' where fileid = 5

end

Next we create a very simple OLAP cube. Once created, we will utilise the pivot table control in Excel to analyse the results.

The ck_filestats table will be the fact table and will also drive the dimensions for the cube. The measures will be the _diff columns. The dimensions will include:

a) time – broken down to hour and minute - (based on the statstime column)

b) database – not really required as we only have one - (based on dbname column)

c) file group – (based on [filegroup] column)

To create the cube, run query analyser, select the foodmart sample cube database, and run the new cube wizard.


取得SQLServer字段的描述内容
fn_listextendedproperty
返回数据库对象的扩展属性值。

语法
fn_listextendedproperty (
{ default | [ @name = ] 'property_name' | NULL }
, { default | [ @level0type = ] 'level0_object_type' | NULL }
, { default | [ @level0name = ] 'level0_object_name' | NULL }
, { default | [ @level1type = ] 'level1_object_type' | NULL }
, { default | [ @level1name = ] 'level1_object_name' | NULL }
, { default | [ @level2type = ] 'level2_object_type' | NULL }
, { default | [ @level2name = ] 'level2_object_name' | NULL }
)

参数
{default|[@name =] 'property_name'|NULL}

是属性的名称。property_name 为 sysname 类型。有效的输入为 default、NULL 或属性名。

{default|[@level0type =] 'level0_object_type'|NULL}

用户或用户定义类型。level0_object_type 的数据类型为 varchar(128)，其默认值为 NULL。有效的输入是 USER、TYPE、default 和 NULL。

{default|[@level0name =] 'level0_object_name'|NULL}

指定的 0 级对象类型的名称。level0_object_name 的数据类型为 sysname，其默认值为 NULL。有效的输入为 default、NULL 或对象名。

{default|[@level1type =] 'level1_object_type'|NULL}

1 级对象的类型。level1_object_type 的数据类型为 varchar(128)，其默认值为 NULL。有效的输入为 TABLE、VIEW、PROCEDURE、FUNCTION、DEFAULT、RULE、default 和 NULL。

说明 默认映射 NULL，而"default"映射对象类型 DEFAULT。

{default|[@level1name =] 'level1_object_name'|NULL}

指定的 1 级对象类型的名称。level1_object_name 的数据类型为 sysname，其默认值为 NULL。有效的输入为 default、NULL 或对象名。

{default|[@level2type =] 'level2_object_type'|NULL}

2 级对象的类型。level2_object_type 的数据类型为 varchar(128)，其默认值为 NULL。有效的输入为 COLUMN、PARAMETER、INDEX、CONSTRAINT、TRIGGER、DEFAULT、default（映射 NULL）和 NULL。

{default|[@level2name =] 'level2_object_name'|NULL}

指定的 2 级对象类型的名称。level2_object_name 的数据类型为 sysname，其默认值为 NULL。有效的输入为 default、NULL 或对象名。

返回表
下面是 fn_listextendedproperty 返回的表的格式。

列名 数据类型
objtype sysname
objname sysname
name sysname
value sql_variant

如果返回的表为空，可能对象没有扩展属性或用户没有列出对象扩展属性的权限。

注释
系统对象不允许有扩展属性。

如果 property_name 的值为 NULL 或 default，fn_listextendedproperty 返回对象的所有属性。

如果指定了对象类型且相应的对象名的值为 NULL 或 default，则 fn_listextendedproperty 返回指定类型的所有对象的所有扩展属性。

对象是按级别区分的，0 级为最高，2 级为最低。如果指定了较低级别的对象（1 或 2 级）类型和名称，则父对象类型和名称的值不能为 NULL 或 default。否则，函数将返回错误。

用于列出某些级别对象类型的扩展属性的权限有所不同。

对于级别 0 的对象，如果某用户为级别 0 的名称中所标识的用户，或者是 db_owner 和 db_ddladmin 固定数据库角色的成员，则该用户可以列出指定"user"类型的扩展属性。

所有用户可使用 0 级对象类型"type"列出扩展属性。

对于 1 级对象，如果用户为对象的所有者或用户对于对象有权限，该用户可以用任何有效的类型值列出扩展属性。

对于 2 级对象，如果当前用户对父对象（1 级和 0级）有任何权限，该用户可以用任何有效的类型值列出扩展属性。
示例
此示例列出数据库的所有扩展属性。

SELECT *
FROM ::fn_listextendedproperty(NULL, NULL, NULL, NULL, NULL, NULL, NULL)

-或-

SELECT *
FROM ::fn_listextendedproperty(default, default, default, default, default, default, default)

此示例列出表"T1"中所有列的所有扩展属性。

CREATE table T1 (id int , name char (20))

EXEC sp_addextendedproperty 'caption', 'Employee ID', 'user', dbo, 'table', 'T1', 'column', id

EXEC sp_addextendedproperty 'caption', 'Employee Name', 'user', dbo, 'table', 'T1', 'column', name

SELECT *
FROM ::fn_listextendedproperty (NULL, 'user', 'dbo', 'table', 'T1', 'column', default)

下面是结果集：

objtype objname name value
COLUMN id caption Employee ID
COLUMN name caption Employee N


谈谈MSSQl锁机制
主　　题： ******浅谈MS-SQL锁机制*********
作　　者： happydreamer (小黑)
等　　级：
信 誉 值： 174
所属论坛： MS-SQL Server 基础类
问题点数： 200
回复次数： 19
发表时间： 2003-5-19 10:23:06

锁的概述

一. 为什么要引入锁

多个用户同时对数据库的并发操作时会带来以下数据不一致的问题:

丢失更新
A,B两个用户读同一数据并进行修改,其中一个用户的修改结果破坏了另一个修改的结果,比如订票系统

脏读
A用户修改了数据,随后B用户又读出该数据,但A用户因为某些原因取消了对数据的修改,数据恢复原值,此时B得到的数据就与数据库内的数据产生了不一致

不可重复读
A用户读取数据,随后B用户读出该数据并修改,此时A用户再读取数据时发现前后两次的值不一致

并发控制的主要方法是封锁,锁就是在一段时间内禁止用户做某些操作以避免产生数据不一致

二 锁的分类

锁的类别有两种分法：
1. 从数据库系统的角度来看：分为独占锁（即排它锁），共享锁和更新锁

MS-SQL Server 使用以下资源锁模式。

锁模式 描述
共享 (S) 用于不更改或不更新数据的操作（只读操作），如 SELECT 语句。
更新 (U) 用于可更新的资源中。防止当多个会话在读取、锁定以及随后可能进行的资源更新时发生常见形式的死锁。
排它 (X) 用于数据修改操作，例如 INSERT、UPDATE 或 DELETE。确保不会同时同一资源进行多重更新。
意向锁 用于建立锁的层次结构。意向锁的类型为：意向共享 (IS)、意向排它 (IX) 以及与意向排它共享 (SIX)。
架构锁 在执行依赖于表架构的操作时使用。架构锁的类型为：架构修改 (Sch-M) 和架构稳定性 (Sch-S)。
大容量更新 (BU) 向表中大容量复制数据并指定了 TABLOCK 提示时使用。

共享锁
共享 (S) 锁允许并发事务读取 (SELECT) 一个资源。资源上存在共享 (S) 锁时，任何其它事务都不能修改数据。一旦已经读取数据，便立即释放资源上的共享 (S) 锁，除非将事务隔离级别设置为可重复读或更高级别，或者在事务生存周期内用锁定提示保留共享 (S) 锁。
更新锁
更新 (U) 锁可以防止通常形式的死锁。一般更新模式由一个事务组成，此事务读取记录，获取资源（页或行）的共享 (S) 锁，然后修改行，此操作要求锁转换为排它 (X) 锁。如果两个事务获得了资源上的共享模式锁，然后试图同时更新数据，则一个事务尝试将锁转换为排它 (X) 锁。共享模式到排它锁的转换必须等待一段时间，因为一个事务的排它锁与其它事务的共享模式锁不兼容；发生锁等待。第二个事务试图获取排它 (X) 锁以进行更新。由于两个事务都要转换为排它 (X) 锁，并且每个事务都等待另一个事务释放共享模式锁，因此发生死锁。

若要避免这种潜在的死锁问题，请使用更新 (U) 锁。一次只有一个事务可以获得资源的更新 (U) 锁。如果事务修改资源，则更新 (U) 锁转换为排它 (X) 锁。否则，锁转换为共享锁。

排它锁
排它 (X) 锁可以防止并发事务对资源进行访问。其它事务不能读取或修改排它 (X) 锁锁定的数据。

意向锁
意向锁表示 SQL Server 需要在层次结构中的某些底层资源上获取共享 (S) 锁或排它 (X) 锁。例如，放置在表级的共享意向锁表示事务打算在表中的页或行上放置共享 (S) 锁。在表级设置意向锁可防止另一个事务随后在包含那一页的表上获取排它 (X) 锁。意向锁可以提高性能，因为 SQL Server 仅在表级检查意向锁来确定事务是否可以安全地获取该表上的锁。而无须检查表中的每行或每页上的锁以确定事务是否可以锁定整个表。

意向锁包括意向共享 (IS)、意向排它 (IX) 以及与意向排它共享 (SIX)。

锁模式 描述
意向共享 (IS) 通过在各资源上放置 S 锁，表明事务的意向是读取层次结构中的部分（而不是全部）底层资源。
意向排它 (IX) 通过在各资源上放置 X 锁，表明事务的意向是修改层次结构中的部分（而不是全部）底层资源。IX 是 IS 的超集。
与意向排它共享 (SIX) 通过在各资源上放置 IX 锁，表明事务的意向是读取层次结构中的全部底层资源并修改部分（而不是全部）底层资源。允许顶层资源上的并发 IS 锁。例如，表的 SIX 锁在表上放置一个 SIX 锁（允许并发 IS 锁），在当前所修改页上放置 IX 锁（在已修改行上放置 X 锁）。虽然每个资源在一段时间内只能有一个 SIX 锁，以防止其它事务对资源进行更新，但是其它事务可以通过获取表级的 IS 锁来读取层次结构中的底层资源。

独占锁：只允许进行锁定操作的程序使用，其他任何对他的操作均不会被接受。执行数据更新命令时，SQL Server会自动使用独占锁。当对象上有其他锁存在时，无法对其加独占锁。
共享锁：共享锁锁定的资源可以被其他用户读取，但其他用户无法修改它，在执行Select时，SQL Server会对对象加共享锁。
更新锁：当SQL Server准备更新数据时，它首先对数据对象作更新锁锁定，这样数据将不能被修改，但可以读取。等到SQL Server确定要进行更新数据操作时，他会自动将更新锁换为独占锁，当对象上有其他锁存在时，无法对其加更新锁。
2. 从程序员的角度看：分为乐观锁和悲观锁。
乐观锁：完全依靠数据库来管理锁的工作。
悲观锁：程序员自己管理数据或对象上的锁处理。

MS-SQLSERVER 使用锁在多个同时在数据库内执行修改的用户间实现悲观并发控制

三 锁的粒度
锁粒度是被封锁目标的大小,封锁粒度小则并发性高,但开销大,封锁粒度大则并发性低但开销小

SQL Server支持的锁粒度可以分为为行、页、键、键范围、索引、表或数据库获取锁

资源 描述
RID 行标识符。用于单独锁定表中的一行。
键 索引中的行锁。用于保护可串行事务中的键范围。
页 8 千字节 (KB) 的数据页或索引页。
扩展盘区 相邻的八个数据页或索引页构成的一组。
表 包括所有数据和索引在内的整个表。
DB 数据库。

四 锁定时间的长短

锁保持的时间长度为保护所请求级别上的资源所需的时间长度。

用于保护读取操作的共享锁的保持时间取决于事务隔离级别。采用 READ COMMITTED 的默认事务隔离级别时，只在读取页的期间内控制共享锁。在扫描中，直到在扫描内的下一页上获取锁时才释放锁。如果指定 HOLDLOCK 提示或者将事务隔离级别设置为 REPEATABLE READ 或 SERIALIZABLE，则直到事务结束才释放锁。

根据为游标设置的并发选项，游标可以获取共享模式的滚动锁以保护提取。当需要滚动锁时，直到下一次提取或关闭游标（以先发生者为准）时才释放滚动锁。但是，如果指定 HOLDLOCK，则直到事务结束才释放滚动锁。

用于保护更新的排它锁将直到事务结束才释放。
如果一个连接试图获取一个锁，而该锁与另一个连接所控制的锁冲突，则试图获取锁的连接将一直阻塞到：

将冲突锁释放而且连接获取了所请求的锁。

连接的超时间隔已到期。默认情况下没有超时间隔，但是一些应用程序设置超时间隔以防止无限期等待


孤立用户问题
把数据库备份还原到另一个服务器时，可能会遇到孤立用户的问题。下面的方案显示并解决了这个问题：

通过执行 sp_addlogin，把登录 janetl 改名为 dbo。
sp_addlogin 'janetl', 'dbo'

备份数据库。在本例中，备份 Northwind。
BACKUP DATABASE Northwind
TO DISK = 'c:\mssql\backup\northwnd'

除去刚刚备份的数据库。
DROP DATABASE Northwind

除去登录。
sp_droplogin 'janetl'

还原备份的数据库。
RESTORE DATABASE Northwind
FROM DISK = 'c:\mssql\backup\northwnd'

select * from sysusers

janetl 登录不能访问 Northwind 数据库，除非允许 guest 登录。尽管 janetl 登录已经删除，它仍然（作为一个孤立行）显示在 sysusers 表中：

USE Northwind
SELECT *
FROM sysusers
WHERE name = 'janetl'

解决孤立用户问题

用 sp_addlogin 添加一个临时登录。为孤立用户指定安全标识符 (SID)（从 sysusers）。
sp_addlogin @loginame = 'nancyd',
@sid = 0x32C864A70427D211B4DD00104B9E8A00

用 sp_dropalias 除去属于别名 SID 的临时别名。
sp_dropalias 'nancyd'

用 sp_dropuser 除去原始用户（即现在的孤立用户）。
sp_dropuser 'janetl'

用 sp_dropuser 除去原始登录。
sp_droplogin 'nancyd'


Have you heard about orphan SQL Server users? If not, an orphan user is a user in a SQL Server database that is not associated with a SQL Server login. Orphan users are created when a database backup from one server is restored on another server. There are a number of articles that have been written, and lots of topics on discussion boards about reconnecting orphan users with logins, but few, if any, regarding removing orphan users. Therefore, this article deals with how to identify which databases have orphan users, and how to remove the identified orphan users.

Just because you are moving a database from one server to another does not mean you also want to move all of the users associated with the database. In some cases, you may not want to move any, and in other cases, you might want to move only a few. For this discussion, let's say you have already identified and reconnected all of the orphan users you plan to keep. For all of the other orphan users you did not reconnect, I will show you how to identify and remove them.

Identifying Orphan Users
SQL Server provides a SP to help you identify orphan users that were originally associated with SQL Server Authenticated logins. The SP is called sp_change_users_login. However, SQL Server does not provide a mechanism to identify orphan users that where originally associated with Windows authenticated users or groups. The key to removing users is being able to identify them. The following code can be run against any database to identify all of the orphan users regardless of whether they are associated with a SQL Server or Windows authenticated user and/or a Windows group.

select u.name from master..syslogins l right join
sysusers u on l.sid = u.sid
where l.sid is null and issqlrole <> 1 and isapprole <> 1
and (u.name <> 'INFORMATION_SCHEMA' and u.name <> 'guest'
and u.name <> 'system_function_schema')

Removing Orphan Users
Once you have identified orphan users it is extremely simple to remove them. You remove them by using the sp_revokeuser SP. Here is an example that removes the database users 'USERX', from the current database in use.

exec sp_revokelogin 'USERX'
It seems fairly simple to do this for a few users and databases. However, if you have a large number of orphan users and databases, I'm sure you would not like to do this by hand. At least, I didn't want to do this manually. Because I like to automate repetitive manual tasks, I developed a stored procedure (SP) named "usp_remove_orphan_users" to accomplish identifying and removing orphan users. The code for this SP can be found at the end of this article.

This SP first determines which databases have orphan users. For each database that has orphans, it removes them one at a time. If an orphan user is the database owner then the "sp_changedbowner" SP is used to change the owner to "SA" before the orphan user is removed. The SP does not really remove the users, or change the database owners, but instead it just generates the code to remove the users. This allows you to review the code and determine if you want to remove all users, or only a select set of orphan users.

Conclusion
You can leave the orphan database users in a database if you want. Although they are excess baggage, that comes along with a database restore. In addition, they provide a small security risk if some newly defined login is unintentionally associated with an orphan user, allowing the new login to gain unauthorized database access. It is best to remove un-necessary orphan users to provide a clean, uncluttered database environment. This script provides an easy method to identify and remove unneeded orphan users. Therefore, this SP can be a valuable tool, to be used as part of your database restore process, should you desire to remove orphan users.


create proc usp_remove_orphan_users as

-- Written by: Gregory A. Larsen
-- Script to modify database owner, and remove all users that
-- are not mapped to logins.

set nocount on

-- Section 1: Create temporary table to hold databases to process

-- drop table if it already exists
if (select object_id('tempdb..##dbnames')) is not null
drop table ##dbnames

-- Create table to hold databases to process
create table ##dbnames (dbname varchar(128))

-- Section 2: Determine what databases have orphan users
exec master.dbo.sp_MSforeachdb 'insert into ##dbnames select ''?'' from master..syslogins l right join ?..sysusers u
on l.sid = u.sid
where l.sid is null and issqlrole <> 1 and isapprole <> 1
and (u.name <> ''INFORMATION_SCHEMA'' and u.name <> ''guest'' and u.name <> ''system_function_schema'')
having count(*) > 0'

-- Section 3: Create local variables needed
declare @CNT int
declare @name char(128)
declare @sid varbinary(85)
declare @cmd nchar(4000)
declare @c int
declare @hexnum char(100)
declare @db varchar(100)

-- Section 5: Process through each database and remove orphan users
select @cnt=count(*) from ##DBNAMES
While @CNT > 0
begin

-- get the name of the top database
select top 1 @db=dbname from ##DBNAMES

-- delete top database
delete from ##DBNAMES where dbname = @db

-- Build and execute command to determine if DBO is not mapped to login
set @cmd = 'select @cnt = count(*) from master..syslogins l right join ' +
rtrim(@db) + '..sysusers u on l.sid = u.sid' +
' where l.sid is null and u.name = ''DBO'''
exec sp_executesql @cmd,N'@cnt int out',@cnt out

-- if DB is not mapped to login that exists map DBO to SA
if @cnt = 1
begin
print 'exec ' + @db + '..sp_changedbowner ''SA'''
-- exec sp_changedbowner 'SA'
end -- if @cnt = 1

-- drop table if it already exists
if (select object_id('tempdb..##orphans')) is not null
drop table ##orphans

-- Create table to hold orphan users
create table ##orphans (orphan varchar(128))

-- Build and execute command to get list of all orphan users (Windows and SQL Server)
-- for current database being processed
set @cmd = 'insert into ##orphans select u.name from master..syslogins l right join ' +
rtrim(@db) + '..sysusers u on l.sid = u.sid ' +
'where l.sid is null and issqlrole <> 1 and isapprole <> 1 ' +
'and (u.name <> ''INFORMATION_SCHEMA'' and u.name <> ''guest'' ' +
'and u.name <> ''system_function_schema'')'
exec (@cmd)

-- Are there orphans
select @cnt = count(*) from ##orphans

WHILE @cnt > 0
BEGIN

-- get top orphan
select top 1 @name= orphan from ##orphans

-- delete top orphan
delete from ##orphans where orphan = @name

-- Build command to drop user from database.
set @cmd = 'exec ' + rtrim(@db) + '..sp_revokedbaccess ''' + rtrim(@name) + ''''
print @cmd
--exec (@cmd)

-- are there orphans left
select @cnt = count(*) from ##orphans
end -- WHILE @cnt > 0

-- are the still databases to process
select @cnt=count(*) from ##dbnames

end -- while @cnt > 0

-- Remove temporary tables
drop table ##dbnames, ##orphans


installshield的数据库安装程序制作
Delphi具有强大的数据库应用程序开发功能，其Client／Sever版本可以开发一般的单机数据库应用系统、普通Client／Sever应用系统直至基于Web的多层应用，其中自然离不开Borland数据库引擎（BDE）的功劳。不过在没有安装过Delphi程序的计算机上运行数据库程序时，必须同时分发庞大的BDE文件，Borland建议用户用Delphi附带的InstallShield制作安装程序来解决这些问题。但怎样选择BDE文件才能使自己的安装程序更加精炼呢？我首先介绍一下其中几个必须的文件：

    Idapi32.dll：BDE基本函数库

    Blw32.dll：语言驱动函数库

    Idr20009.dll：错误信息库

    Fareast.bll：远东语言驱动程序

    Usa.bll：美国语言驱动程序

    以下文件为根据需要可选：
    如用Paradox，则应选择：Idpdx32.dll
    如用Dbase，则应选择：Iddbas32.dll
    如用Access，则应选择：Iddao32.dll
    如用Ascii，则应选择：Idasci32.dll
    如用TBatchMove，则应选择：Idbat32.dll
    如用Data Repostitory，则应选择：Iddr32.dll
    如用ODBC，则应选择：Idodbc32.dll
    如用BDE DataSet Provider DLL，则应选择：Idprov32.dll
    如用SQL查询，则应选择：Idsql32.dll

    下面我们就以用InstallShield来制作一个Paradox数据库应用系统的安装程序为例：
    1.启动程序设置有关文件目录路径，进入Setup Checklist；
    2.在Set the Visual Design中设置程序名、程序目录、安装程序主窗口等；
    3.在Specify Components and Files中设置Groups and Files项，在Program Files中加入用户所有程序文件和数据库表文件，以及下列文件：Idbat32.dll、Usa.btl、Idapi.cfg、Charset.cvb、Bantam.dll、BLW32.dll、Idapi32.dll、Fareast.btl、Idpdx32.dll、Idr20009.dll、Idsql32.dll
（以上文件必须含目录路径）；

    4.在Dialog Boxes中设置所需安装对话框；
    5.在Make Registry Changes中设置Keys项，在
HKEY_LOCAL_MACHINE中加入software\borland\blw32和software\borland\database engine两项，并且在blw32值中加入“Blapipath ‘以上文件目录’”、“LOCALE_LIB0 ‘以上文件目录’\Fareast.bll”和“LOCALE_LIB1 ‘以上文件目录’\Usa.bll”，在database engine值中加入“Dllpath ‘以上文件目录’”、“Configfile01 ‘以上文件目录’\Idapi.cfg”；

    6.在Specify Folder and Icons中根据实际要求设置有关项目；
    7.在Run Disk Builder中设置安装盘的有关信息；
    8.最后进行Test the Installation和Create Distribution Media即可完成安装程序的制作


用MSCS以及CA的SurviveIT实现过SQL的双机热备
(作者 _rambo)
Microsoft? SQL Server? 2000 故障转移群集允许系统自动故障服务器中的 SQL Server 实例处理转换到工作服务器，从而提高了服务器的可用性。例如，SQL Server 实例可以快速地将数据库服务还原到 Web 站点或企业网络，即使运行该实例的服务器发生故障。SQL Server 2000 基于 Windows NT? 4.0 和 Windows? 2000 中的 Microsoft 群集服务 (MSCS) 的故障转移群集功能实现故障转移群集。

SQL Server 2000 使用的 MSCS 故障转移群集类型由多台服务器计算机组成（在 Windows NT 4.0 上为两个，在 Windows 2000 Datacenter Server 上最多可以为四个），它们共享一套公用群集资源，如磁盘驱动器。群集中的每台服务器都称为一个节点。每台服务器（或节点）与网络连接，并且各节点间可以相互通讯。每一节点都运行同一版本的 MSCS。

在故障转移群集中，共享资源被收集到群集组内。例如，如果故障转移群集有四个共享磁盘驱动器，其中的两个驱动器可以收集到一个群集组内，而另两个驱动器可以收集到另一个群集组内。每一群集组都由故障转移群集中的某个节点拥有，不过这种所有权可以在节点间转让。

应用程序可以安装在故障转移群集中的节点上。这些应用程序通常是服务器应用程序或用户通过网络连接访问的分布式 COM 对象。应用程序的可执行文件和其它资源通常存储在节点所拥有的一个或多个群集组内。每一节点都可以在其上安装多个应用程序。

故障转移群集节点定期相互发送称为心跳信号消息的网络消息。如果 MSCS 软件检测到群集中某个节点丢失了心跳信号，则将该服务器视为故障服务器。MSCS 然后自动将该节点的群集组和应用程序资源转移给网络中的其它节点。当任何特定的节点发生故障时，群集管理员指定将群集组转移到的替换节点。于是，其它节点继续处理对故障服务器转移来的应用程序的用户网络请求。


sqlserver所在服务器修改密码
若因为Windows密码修改而造成Sql Server无法启动，
则需在windows的管理工具的[服务]中修改[MSSQLSERVER]的登陆帐户的密码为你的新密码
点击查看用户来源及管理<br>发贴IP：61.48.16.208 2003-4-20 19:36:51
鲜花(0) 鸡蛋(0)
ugvanxk 帅哥哟，离线，有人找我吗？

等级：版主
文章：168
积分：441
注册：2002-11-2
搜索ugvanxk在『数 据 库』的所有贴子 第2楼

在本版面出现这个问题的频率也算是很高的了，新手通常会比较多遇到这个问题
原因很简单，安装SQL Server时是使用默认登录用户来作为启动SQL Server服务的账号（对于自己使用的单机，通常就是administrator了），当该用户更改了用户名（如有人喜欢把administrator改成admin）或更改了其口令时，再次启动服务器，就出现“同于登录失败而无法启动服务”的错误了
知道了原因，解决方法也就很显然了
1，把用户口令改回原来的，再启动服务
2，使用控制面版服务管理器，找到MSSQLSERVER服务，更改启动账号信息，改成变更后的，再启动服务
3，也是推荐使用的方法，创建一个新用户，专门用于启动SQL Server服务，安装SQL Server时就使用该用户来启动SQL Server，这样就可以避免用于频繁更改administrator口令而带来的1069错误了。如果已经安装好SQL Server，也可以在控制面版服务管理器下更改MSSQLSERVER服务的服务账号信息，换成SQL服务专用的用户。

Rambo
4.15


SQLServer数据库服务器名称的获取
在窗口上放置一个listbox控件，命名为：listbox1，原代码如下所示：
procedure TForm1.FormCreate(Sender: TObject);
var
SQLServer:Variant;
ServerList:Variant;
i,nServers:integer;
begin
SQLServer := CreateOleObject('SQLDMO.Application');
ServerList:= SQLServer.ListAvailableSQLServers;
if serverlist.count<1 then
begin //如果服务器不存在，则退出系统
showmessage('服务器不存在，请检测服务器是否已经开机或者服务是否打开了');
application.Terminate;
end
else
begin
for i:=1 to serverlist.count do
listbox1.Items.Add(serverlist.item(i)) ;
showmessage(serverlist.item(1)+'服务器已经打开，可以正常使用本系统');
adoconnection1.ConnectionString:='Provider=SQLOLEDB.1;Persist Security info=False;UserID=sa;InitialCatalog=ideal_db;DataSource='+ServerList.Item(1);
end;
SQLServer:=NULL;
serverList:=NULL;
end;


关于SQLDMO的一点资料
编写数据库脚本
作者：Andrew Clinick

发表日期：2000 年 1 月 10 日

我在“If It Moves, Script It”（英文）这篇文章中曾谈到，如何使用“Windows Script Host(WSH)”( Windows 脚本主机) 管理 Windows 和 Windows 中的应用程序。文中的大多数示例都是基于管理 Windows 操作系统自身的，并不基于在该操作系统下运行的应用程序。为迎接新千年，我想我应该谈谈，如何在众多显露可脚本化接口的应用程序中使用脚本。这次只涉及“SQL Server”。在以后的几个月中，我将着重谈 Exchange、Office 和“系统管理服务器”。

--------------------------------------------------------------------------------
您可以通过使用“分布式管理对象”、“数据转换服务”和新的“SQL Server XML”实现，将脚本用于“SQL Server”。
--------------------------------------------------------------------------------

许多人都能够通过“Active Data Object(ADO)”和“Active Server Page (ASP)”技术访问数据库了。ADO 在帮助您查询和更新数据库方面做了大量的工作 — 但在备份（Y2K 问题带给我们的警惕）或是数据库之间的传输数据方面，其表现又如何呢？而这时就非涉及到 XML 不可了。

在此，我将告诉您如何通过使用 ADO 的伴随技术 － 特别是“分布式管理对象”、“数据转换服务”和新的“SQL Server XML”实现，将脚本用于“SQL Server”。

分布式管理对象
“分布式管理对象 (DMO)”是一组 COM 对象，它将“SQL Server”数据库和复制管理封装在一起。这意味着您可以编写一个 WSH 脚本，将特定表中的所有数据都复制到用制表符分隔的文件中，这有助于大量数据的移动。我之所以选择这个示例，是因为它的代码编写起来简单，但 DMO 允许您获取“SQL Server”中的每个对象，使您能够编写出一些非常优秀而有意义的管理脚本。

DMO 的关键是 SQLDMO.SQLServer 对象，它是基本的对象，它允许您连接到服务器并获取所有可用对象。在这种情况下，我将使用 Database 集合来选择数据库，然后从 table 集合访问要转储到文件的表。如果不提供数据库，将出现错误消息，并且脚本也就结束了。如果不提供表名，脚本将在数据库所有的表中循环，并导出非系统表。如果提供了数据库，它就导出该表。该示例虽然功能有限，但它为“SQL Server”提供了基于命令行的简单的导出实用程序，您可以以它为根据。

现在先看一段代码：

'声明与 sql 谈话时使用的变量
Dim oServer ' SQL Server 对象
Dim oDatabase ' 要使用的目标数据库
Dim oBCP ' BCP 对象
Dim nRows ' 从 bcp 返回的行数
dim table ' 表对象
on error resume next
' 第一个参数必须是数据库
if WScript.Arguments(0) = "" then
WScript.Echo "您没有提供要连接的数据库"
WScript.Quit
end if
' 创建 SQL DMO 的实例
Set oServer = CreateObject("SQLDMO.SQLServer")
' 创建 SQL DMO Bulkcopy 对象的实例
Set oBCP = CreateObject("SQLDMO.BulkCopy")
oServer.EnableBcp = True
' 登录到本地服务器
' 希望您已经更改了 sa 口令!!
oServer.Connect ".", "sa"
' 连接到提供的数据库
Set oDatabase = oServer.Databases(Wscript.Arguments(0))

' 将分隔符设置为逗号
oBCP.ColumnDelimiter = vbComma
' 将文件类型设置为以逗号分隔
oBCP.DataFileType = SQLDMODataFile_CommaDelimitedChar
oBCP.ImportRowsPerBatch = 1000
oBCP.MaximumErrorsBeforeAbort = 1
BCP.RowDelimiter = vbCrLf
oBCP.ServerBCPDataFileType = SQLDMOBCPDataFile_Char
oBCP.UseExistingConnection = True
' 如果未提供表，则转储所有的表
f wscript.Arguments(1) = "" then
for each table in oDatabase.tables
' 确保该表不是系统表
if table.systemobject = false then
oBCP.DataFilePath = table.name & ".csv"
nRows = table.ExportData(oBCP)
wscript.Echo nRows & " rows exported from " & table.name
end if
next
else
' 设置输出文件
oBCP.DataFilePath = wscript.Arguments(1) & ".csv"
nRows = oDatabase.Tables(wscript.arguments(1)).ExportData(oBCP)
wscript.Echo nRows & " rows exported from " & wscript.arguments(1)
end if
DMO 的全部内容要比本文介绍的多得多，但我希望本文能给您一些感性认识：用某些简单的 WSH 脚本和 DMO 可以做些什么。您可以下载我的示例（英文）。有关 DMO 的详细信息，请访问 [image] http://msdn.microsoft.com/library/psdk/sql/9_dmoc01.htm（英文）。

数据转换服务
导出到 Comma Separated 文件 (CSV)，可以作为将数据导出到 SQL 和从 SQL 导入的起点，但这不象是尖端科技（太过于 20 世纪了，您不觉得吗?）。“SQL Server 7.0”以“数据转换服务 (DTS)”的形式，提供了相当完善的导入和导出机制。幸运的是，脚本在“SQL Server”的这个新领域中仍有一席之地，因此，您可以用“Visual Basic(R) 脚本编辑 (VBScript)”、“JScript(R)”或“Perl”的技术来扩展 DTS 的能力。

DTS 的设置非常简单，特别是在使用“SQL ServerEnterprise Manager”的时候。在默认情况下，“SQL Server”有一个定义的文件夹，可存储任何转换，而且“Enterprise Manager”提供了创建和编辑 DTS 程序包的大的图形用户界面 (GUI)。在下面的示例中，我选择了已由 sqlexport.wsf 文件创建的 employees.csv 文件和“SQL Server”中的一个新表。DTA 程序包将创建该表，加载到 Text 文件中，然后运行某个脚本，将数据转换到“SQL Server”的表中。

DTS 程序包中的转换，使脚本在整个转换过程中保持运行状态。“SQL Enterprise Manager”提供的简单的脚本编辑器，有一个语法分析脚本按钮。在运行脚本之前，该按钮将警告您脚本中的错误。在转换过程中，该脚本使用 VBScript 的 Cint 功能，将 employeeid 和 deptid 的输入转换为 int，并将所有的电子邮件地址转换为小写字母。

'**********************************************************************
' Visual Basic 转换脚本
' 把每个源列复制到
' 目标列
'***********************************************************************
Function Main()
DTSDestination("EmployeeID") = cint(DTSSource("Col001"))
DTSDestination("FirstName") = DTSSource("Col002")
DTSDestination("LastName") = DTSSource("Col003")
DTSDestination("email") = lcase(DTSSource("Col004"))
DTSDestination("extension") = DTSSource("Col005")
DTSDestination("office") = DTSSource("Col006")
DTSDestination("DeptID") = cint(DTSSource("Col007"))
Main = DTSTransformStat_OK
End Function
用 DTS，能够采用某些完善的导入/导出机制，并使您能够在转换的任何一步中使用脚本。重要的是，要注意脚本可能不是操作数据的最佳方式 — 尤其是您的数据集很大的话。如果您有大量数据需要转换，而且性能也很重要，则您可能需要考虑使用 Visual Basic 或 C++ 来创建 COM 组件，然后从 DTS 内部调用该组件。也就是说，如果性能并不重要，并且要在数据导入/导出时对它进行转换，则脚本为您提供了实现这一点的灵活机制，并使您能够将所有代码存储到“SQL Server”数据库中，使部署变得相当简单。

关于 XML
目前，XML 看起来像是在应用程序之间共享数据的最佳工具，所以“SQL Server”的所有管理肯定都得到了 XML 的好处。编写提取数据库中所有数据的脚本，以及编写用编程的方法创建 XML 文档的脚本，都是可能的。但是，如果您只要查询“SQL Server”的话，最好使它在 XML 中返回数据，然后用脚本操作该 XML。“SQL Server”组最近发行了“SQL Server XML”技术的非正式版本，完全做到了这一点。

新的 XML 技术使用起来非常简单。实际上是对服务器发出 HTTP 请求，将查询传递给服务器，而服务器返回 XML。为了举例说明，我创建了简单的 WSH 脚本，它向本地机器查询 North Wind 数据库的雇员表中的所有数据。为访问 XML，该脚本使用了“Internet Explorer 5.01”附带的 XML 分析程序。此对象的美妙之处，在于它处理您所有的 HTTP 请求，并使您能够同步调用。因为您再也不用处理任何事件驱动的程序，所以，它对编写脚本很有帮助。

该脚本非常简单。它创建了 XML 分析程序的实例，使用分析程序打开 URL，然后将结果保存为 .xml 文件。只需五行的脚本，不错吧!

' 设置请求的 url
xmlpath = " [image] http://localhost/Northwind?sql=select+*+from+employees+for+xml+auto"
' 创建“XML 分析程序”的实例
Set myXMLDoc = CreateObject("Microsoft.XMLDOM")
' 不需要异步
myXMLDoc.async = false
' 加载该 URL
myXMLDoc.Load xmlpath
' 保存文档
myXMLDoc.save "employees.xml"
它的强大之处在于，现在可很容易地与服务器建立远程连接并转储数据；只要更改 URL，您早已做过了。此例告诉您如何导出，但是您可以轻松地写出导入例行程序，用 XML 分析程序在 XML 中反复操作并将值插入数据库中。

摘要
“SQL Server”提供全面的可编写脚本的机制，用于本地或远程管理数据库。DMO 和 DTS 已经上市（实际上，DMO 已发行了许多版本），所以您可以直接利用它们，使您的数据库管理任务自动化。Windows 2000 已与“Windows Script Host 2.0”一起上市，所以以上所有脚本的运行，都不受装有“SQL Server”的 Windows 2000 服务器的条件限制。“SQL Server”中新的 XML 技术使存取数据变得轻而易举，从而使编写“SQL Server”的脚本更加容易。有关“SQL Server”的详细信息，请访问 SQL Server Developer enter（英文）。


用COM自动服务扩展SQL Server功能
SQL Server的T-SQL编程语言在数据存储和恢复方面功能强大，但在与SQL Server数据库之外的系统交互方面则功能较弱。然而，我们可以通过SQL Server内置的COM自动操作环境来克服这个限制，SQL Server内置的COM自动操作环境可以使用户在存储过程中自动操作COM对象。在SQL Server 7.0和SQL Server 6.5中提供了7个扩展的存储过程，可以通过自己开发的或Office等现成的COM对象扩展SQL Server的功能。SQL Server还提供了一种错误处理机制，可以把出错信息写到SQL代理日志中。利用COM自动化操作服务，还可以把SQL Server与微软的Exchange Server、Index Server和其他可以通过COM自动化操作服务控制其他软件进行集成。

    SQL Server 6.5引进了对象自动操作环境，它最初被称作OLE。随着时间的变迁对象操作的名称也有所变化，然而与SQL Server 6.5相比，SQL Server 7.0中的自动操作环境没有改变，因此微软的文档中仍然把这一功能称作OLE操作而不是COM操作，在查阅SQL Server在线手册（BOL）时尤其需要注意这一点。下面我们来讨论如何使用SQL Server的COM自动操作存储过程以及COM自动操作如何帮助我们解决现实的编程问题。
COM操作的细节

    表1列出了SQL Server中的7个用于COM操作的扩展存储过程。当自动操作一个COM对象时，需要首先通过调用sp_OACreate建立一个COM对象的实例，然后通过一系列的sp_OAGetProperty、sp_OASetProperty和sp_OAMethod调用完成需要完成的任务，在完成对COM对象的操作后，还需要调用sp_OADestroy释放该对象。在详细地研究每个储存过程时，请注意二个很重要的问题。第一，必须提供调用的所有参数，因为自动操作功能不支持有名参数，如果不能使用一个详细的参数，需要向它传递一个NULL作为占位符；第二，每个调用返回一个整数类型的HRESULT，如果调用成功则该值为0。在后面，我们将讨论如何处理返回值为非0时的处理方法。

存储过程 描述
sp_OACreate 建立自动操作对象的一个实例
sp_OADestroy 释放一个对象的实例
sp_OAGetErrorInfo 从其他过程返回的HRESULT中获得错误描述信息
sp_OAGetProperty 把一个对象的属性存储在结果集或局部变量中
sp_OASetProperty 改变一个对象属性的值
sp_OAMethod 执行对象的方法，向方法传递参数，并得到返回值
sp_OAStop 关闭SQL Server的自动操作环境

      表 1: SQL Server的COM自动操作存储过程

    COM操作必须以调用sp_OACreate存储过程开始，语法格式如下所示：

      sp_OACreate progid | clsid, objecttoken OUT.PUT,

    [context:]

      第一个参数是程序ID（ProgID━━一个应用程序名.类名形式的字符串，例如Excel.Application，）或者一个类ID（CLSID━━一个nnnnnnnn-nnnn-nnnn-nnnn-nnnnnnnnnnnn形式的全球唯一的ID），它标明你希望创建实例的COM对象。在可能的情况下，我建议使用ProgID参数，因为它易于输入和记忆。你会发现，只有很少的希望自动操作的对象没有ProgID，如果偶尔碰上这样的对象，就只有使用CLSID了。第二个变量objecttoken也是一个整型变量，一个对象的标记是指向SQL Server创建的对象的句柄和指针，我们需要在随后的对对象的自动操作中使用这个返回的对象标记来确定这个对象。最后的context变量是可选的，可以强迫创建的对象使用某种自动操作的机制。如果其值为1，则要求对象在一个ActiveX DLL文件中；值为4，则要求对象在ActiveX EXE服务器中；如果是缺省的值5，则可以使用任一自动操作。在这里我们建议使用缺省的选项，而无须为context参数提供一个恰当的值。下面调用op_OACreate 的命令将创建一个微软的Excel程序的实例：

    Declare @Object int
    Declare @RetVal int

    Exec @RetVal=sp_OACreate 'Excel.Application',
    @Object OUTPUT

    在创建一个对象后，需要获取其一些属性。要得到这些属性，可以通过下面的语法调用存储过程

    sp_OAGetProperty：
    sp_OAGetProperty objecttoken, propertyname[, propertyvalue OUTPUT] [, index]

    第一个参数objecttoken的值就是由sp_OACreate返回的值，参数Propertyname是我们希望获取的属性。在获取这个值是有几种选择，如果该属性是一个单一的值，可以把它存储在一个变量中，或者把它作为一个单行、单字段的结果集；如果属性值是一个一维或二维的数组，则必须把它作为一个结果集；如果如果该属性的值是一个多于二维的数组，sp_OAGetProperty就不能返回它的值，会出现一个错误。要返回一个结果集，只须简单地不指定propertyvalue参数的值即可（如果需要它有一个值以便使用index参数，就把NULL赋给它好了。），否则的话，应该赋给propertyvalue一个适当的类型的值，并且一定要把该参数标记为OUTPUT。如果你访问的属性是一个集合，就需要使用index参数指定这个集合中一个特定的数字。如果一个对象的属性是另一个对象，就应该把这个对象存入一个整数型变量中，sp_OAGetProperty返回的也是一个对象标记，不过与sp_OACreate返回的并不相同。我们可以使用这个对象标记对存储过程返回的任何对象进行自动化操作。下面的命令调用sp_OAGetProperty把一个名字为DefaultFilePath的属性值存入变量@DFP中：

    Exec sp_OAGetProperty @Object, 'DefaultFilePath',
    @DFP OUTPUT

    可以通过如下格式使用sp_OASetProperty存储过程改变一个对象的属性值：

    sp_OASetProperty objecttoken, propertyname,newvalue [, index]

第一个参数objecttoken是由sp_OACreate返回的，参数Propertyname是要改变的对象的属性名字，Newvalue参数是想赋给属性的新变量，可以是一个变量或一个文字值。如果设定的属性值是作为一个集合的一个对象，可以使用可选的index参数来指定这个集合的一个特定的位置。下面的命令调用sp_OASetProperty把名字为FixedDecimalPlaces的属性设置为6：

    Exec sp_OASetProperty @Object, 'FixedDecimalPlaces', 6

    可以用下面的语法调用sp_OAMethod存储过程执行一个对象的方法：

    sp_OAMethod objecttoken, methodname [, returnvalue OUTPUT] [, ][@parametername =] parameter [OUTPUT]...]

Sp_OAMethod是最灵活的，因而也是最复杂的自动操作存储过程，我们甚至可以用它象调用一个方法那样调用一个属性，而且还能得到一个返回值，当然，我们也能使用sp_OAGetProperty来完成这一任务。该存储过程的第一个参数objecttoken是由sp_OACreate返回的对象标记，参数methodname是希望执行的方法的名字，如果该方法有返回值，则下一个参数returnvalue应当是一个包含该方法返回值的适当类型的变量；如果返回值是一个一维或二维的数组，则用NULL作为一个占位符，该过程将返回一个结果集。该存储过程不能返回一个超过二维的数组作为结果集合，在这种情况下，SQL Server就会出错。如果该方法没有返回类型，则使用NULL作为一个占位符。

    如果调用的方法需要参数，就需要在调用sp_OAMethod时提供这些参数。如果方法允许按顺序提供参数，则按要求的顺序列出每个参数，并用逗号分隔每个参数，还可以用变量或文字变量作为参数。如果需要使用有名参数，SQL Server也提供了相应的机制，只需使用

    @变量名=变量值

的形式列出所需的变量即可。需要注意的是不要因为有@前缀而把变量名当作局部变量，当调用存储过程sp_OAMethod时，SQL Server就会解析出@，因此，即使在调用的方法中有名字为HostName的参数时，仍然可以使用名字为@HostName的局部变量。

    下面是二个调用sp_OAMethod的例子。第一个例子调用一个名字为CentimetersToPoints的方法，它只接受在@CMVal变量中提供的一个参数，返回的值存储在变量@RetVal中。第二个例子调用一个名字为MailLogon的方法，它接受三个可选的变量，这个例子中根据名字接受二个变量，把Name设置为字符串"MyUserName"，把 Password设置为字符串"pwd"。

    Exec sp_OAMethod @Object, 'CentimetersToPoints',@RetVal OUTPUT, @CMVal

    Exec sp_OAMethod @Object, 'MailLogon', NULL,@Name='MyUserName', @Password='pwd'

不再使用一个对象后，需要通过下面的语法调用存储过程sp_OADestroy释放对该对象的引用：

    sp_OADestroy objecttoken

    调用sp_OADestroy存储过程可以释放由参数objecttoken指定的对象，同时还释放这个对象所使用的内存和其他资源。下面是一个调用sp_OADestroy的命令：

    Exec sp_OADestroy @Object

    需要注意的是，T-SQL中的数据类型与其他的编程语言并非是一一对应的，在调用一个需要特定的数据类型的方法时就可能出错。"数据类型转换"工具条可以将SQL Server的数据类型转换为Visual Basic的数据类型。

    错误处理

    象在前面提到的那样，如果对存储过程的调用成功了，则会返回一个为0的HRESULT值，其他的HRESULT值则意味着发生了错误。要判断一个非零的HRESULT值，可以把HRESULT值传递给sp_OAGetErrorInfo存储过程，其语法如下：

    sp_OAGetErrorInfo [objecttoken] [, source OUTPUT] [, description OUTPUT] [, helpfile OUTPUT] [, helpid OUTPUT]

    第一个参数objecttoken是由sp_OACreate返回的对象标记。下面的四个参数返回错误信息。Source是产生这一错误信息的应用程序或库，Description是该错误的描述，如果有帮助文件的话，则该Helpfile是帮助文件的路径。这三个参数都是有符号或无符号字符型数据，sp_OAGetErrorInfo会根据定义的变量的大小截取返回的值。最后一个参数helpid是特定错误在帮助文件中的索引号。下面的命令调用sp_OAGetErrorInfo以获得某一个错误的更详细的信息：

    Declare @Source varchar(100), @Description varchar(255), @HelpFile varchar(260),@HelpID int

    Exec sp_OAGetErrorInfo @Object, @Source OUTPUT, @Description OUTPUT, @HelpFile OUTPUT, @HelpID OUTPUT

    SQL Server在线手册还提供了一个有关sp_DisplayOAErrorInfo存储过程的例子，该存储过程可以调用sp_OAGetErrorInfo把返回的值组织成格式化的字符串，以便把该信息写入日志文件中。关于sp_DisplayOAErrorInfo的更详细的信息，请参阅工具条， 另外，调用sp_OAStop储存过程可以关闭SQL Server的COM自动操作环境，它无需任何参数。关闭自动操作环境在大多数情况下并非是必需的，第一次调用sp_OACreate时自动操作环境会自动开启，SQL Server关闭时自动操作环境也会自动关闭。如果一个存储过程正在对一个对象进行自动操作，而另一个过程调用sp_OAStop时就会出现错误，因此我们不建议在程序中调用sp_OAStop，只有在调试一个没有运行的过程时，才可以通过一个查询窗口调用它。

    在实际工作中使用COM自动操作

    至此，我们已经学习了如何使用每一个COM自动操作存储过程，我们现在来讨论一下一个综合应用它们的例子。程序清单1是一个名字为sp_OpenWordIfCoProcAvailable的过程，在这个过程中，我们用sp_OACreate创建了一个Microsoft Word的实例，然后使用sp_OAGetProperty来获取Word的MathCoProcessorAvailable属性，如果sp_OAGetProperty返回1，则sp_OpenWordIfCoProcAvailable向调用过程返回Word对象的对象标记；否则，sp_OpenWordIfCoProcAvailable关闭Word，并返回0。为了节省版面，我们只调用了出错处理过程一次，在实际应用中，应该在每次调用自动操作存储过程后都调用出错处理过程。注意，为对Word进行自动操作，应该在安装SQL Server的机器上安装Word。

    程序清单 1：自动操作Word的方法的例子

Create Procedure sp_OpenWordIfCoProcAvailable As

    Declare @Object int, @hr int, @RetVal int

       Exec @hr = sp_OACreate 'Word.Application', @Object OUTPUT

    If @hr <> 0

      BEGIN

        Exec sp_DisplayOAErrorInfo @Object, @hr

        Return 0

    END

    Exec @hr = sp_OAGetProperty @Object, 'MathCoProcessorAvailable', @RetVal

    If @hr=0

      BEGIN

        Exec @hr = sp_OAMethod @Object, 'Quit', 0

        Exec @hr = sp_OADestroy @Object

        Return 0

    END

    Exec @hr = sp_OAMethod @Object, 'Activate'

    Return @Object

    如果需要对一个使用Visual Basic编写的COM对象进行自动操作，调试它与SQL Server之间的互操作性是一件相当容易的事。我们需要在运行SQL Server的机器上安装有Visual Basic，在Visual Basic的编辑器中加载COM项目，设置一些断点，然后编译并运行该COM对象。在有存储过程对该对象进行自动操作时，在运行到一个断点时，编辑器就会自动切换到调试模式，我们就可以象调试其他的Visual Basic程序那样调试这个COM对象。如果要对调试过程实施更多的控制，可以使用T-SQL Debugger for VB插件，它能采用步进方式执行存储过程的代码。

    此外，在SQL Server中应用COM自动操作我们还能作什么呢？下面是我曾使用SQL Server强大的COM自动操作功能的实际例子。前不久，我需要从一个SQL Server存储过程中使用一个通过命名管道进行通讯，而SQL Server中没有提供通过编程方式打开和使用命名管道的机制，我正好有一个可以使用命名管道通讯的VB例和库，因此就把这个库文件作成一个类，并创建了一个ActiveX DLL文件，然后从存储过程中对DLL进行自动操作。就这样，我就可以在SQL Server中使用命名管道通讯了。

    另一次，我需要复制一些文件和数据库表。使用SQL Server的复制功能可以很方便地复制这些数据，但复制文件则要难得多，NT的目录同步功能很弱，不能满足要求。尽管我还可以把拷贝命令存到字符变量中，然后把变量传递给xp_cmdshell，但会遇到命令行长度的限制。更不方便的是，如果在拷贝过程中发生了错误，我不能很方便地判断错误发生在什么地方，因此，我就编写了一个ActiveX DLL，并通过自动操作它来处理文件的拷贝工作。

    还有一次，我需要在SQL Server 6.5和Index Server 2.0之间先执行连结后再完成查询任务，如果使用带ADO的Windows 2000 Indexing Services和SQL Server 7.0，完成这样的工作非常简单，但如果不是使用这些产品，则要困难得多。首先，需要编写一个可以执行Index Server查询对象ixsso.dll的ActiveX DLL，对它进行自动操作，从Index Server目录中获得信息，并通过一个方法将信息返回到存储过程中。然后把这些数据保存到一个临时表中，再对它进行联结操作。COM自动操作再一次帮我解决了问题。

    在存储过程中执行COM自动操作几乎可以使我们完成任何想完成的操作。SQL Server 2000中的COM自动操作没有什么变化，因此采用这种方法编写的代码在将来仍然可以使用下去。


设计数据库之经验谈 (coo_key)
数据库设计经验谈

数据库模型的设计是否合理会极大影响系统的使用性能。笔者依据多年来设计和使用数据库的经验，提出以下一些设计原则，供同仁们参考。

慎用游标（Cursor）

游标提供了对特定集合中逐行扫描的手段，一般使用游标来逐行遍历数据，根据取出数据条件的不同进行不同的操作。而对于多表和大表中定义的游标（大的数据集合）循环很容易使程序进入一个漫长的等待甚至死机，笔者在某市“住房公积金管理系统”进行日终账户滚积数计息处理时，对一个10万个账户的游标处理时导致程序进入了一个无限期的等待（后经测算需48小时才能完成）(硬件环境：Alpha/4000 128MB RAM ,SCO Unix ,Sybase 11.0)。经修改程序并改用UPDATE语句后，该处理过程得以在20分钟之内完成。示例如下：

Declare Mycursor cursor for select count—no from COUNT

Open Mycursor

Fetch Mycursor into @vcount—no

While (@@sqlstatus=0)

Begin

If @vcount—no=′　′ 条件1

操作1

If @vcount—no=′　′ 条件2

操作2

...

Fetch Mycursor into @vcount—no

End

  ...

改为

Update COUNT set 操作1 for 条件1

Update COUNT set 操作2 for 条件2

...

在某些必须使用游标的场合，可考虑将符合条件的数据行转入临时表中，再对临时表定义游标进行操作，这样，可使性能得到明显提高。笔者在某地市“电信收费系统”数据库后台程序设计中，对一个表（3万行中符合条件的30多行数据）进行游标操作(硬件环境：PC服务器，PⅡ266 64MB RAM ,Windows NT4.0 MS SQL Server 6.5)。

示例如下：

Create ＃tmp /＊ 定义临时表 ＊/

  ( 字段1

    字段2

      ... )

Insert into ＃tmp select ＊ from TOTAL where 条件

Declare Mycursor cursor for select ＊ from ＃tmp /＊对临时表定义游标＊/

  ...

索引(Index)的使用技巧

创建索引一般有两个目的：维护被索引列的惟一性和提供快速访问表中数据的策略。大型数据库有两种索引，即簇索引和非簇索引，一个没有簇索引的表是按堆结构存储数据，所有的数据均添加在表的尾部；而建立了簇索引的表，其数据在物理上会按照簇索引键的顺序存储，一个表只允许有一个簇索引，因此，根据B树结构，可以理解添加任何一种索引均能提高按索引列查询的速度，但与此同时会降低插入、更新、删除操作的性能，尤其是当填充因子（Fill Factor）较大时。所以对索引较多的表进行频繁的插入、更新、删除操作时，建表和索引时应设置较小的填充因子，以便在各数据页中留下较多的自由空间，减少页分割及重新组织的工作。

数据的一致性和完整性

为了保证数据库的一致性和完整性，设计人员往往会设计过多的表间关联（Relation），尽可能地降低数据冗余。表间关联是一种强制性措施，建立后，对父表（Parent Table）和子表(Child Table)的插入、更新、删除操作均要占用系统的开销，另外，最好不要用Identify 属性字段作为主键与子表关联。如果数据冗余低，数据的完整性容易得到保证，但增加了表间连接查询的操作。为了提高系统的响应时间，合理的数据冗余也是必要的。使用规则（Rule）和约束（Check）来防止系统操作人员误输入造成数据的错误是，设计人员的另一种常用手段，但是，不必要的规则和约束也会占用系统的不必要开销，需要注意的是，约束对数据的有效性验证要比规则快。所有这些，设计人员在设计阶段应根据系统操作的类型、频度加以均衡考虑。

事务的陷阱

事务是在一次性完成的一组操作。虽然这些操作是单个的操作，SQL Server能够保证这组操作要么全部都完成，要么一点儿都不做。正是大型数据库的这一特性，使得数据的完整性得到了极大的保证。

众所周知，SQL Server为每个独立的SQL语句都提供了隐含的事务控制，使得每个DML的数据操作得以完整提交或回滚，但是SQL Server还提供了显式事务控制语句，如：

BEGIN TRANSACTION 开始一个事务

COMMIT TRANSACTION 提交一个事务

ROLLBACK TRANSACTION 回滚一个事务

事务可以嵌套，可以通过全局变量@@trancount检索到连接的事务处理嵌套层次。要特别注意的是，每个显示或隐含的事物开始都使得该变量加1，每个事务的提交使该变量减1，每个事务的回滚都会使得该变量置0，而只有当该变量为0时的事务提交（最后一个提交语句时），才把物理数据写入磁盘。

数据类型的选择

数据类型的合理选择对于数据库的性能和操作具有很大的影响，有关这方面的书籍也有不少的阐述，笔者这里主要介绍几点经验：

1. Identify字段不要作为表的主键与其它表关联，这将会影响到该表的数据迁移。

2. Text 和Image字段属指针型数据，主要用来存放二进制大型对象（BLOB）。这类数据的操作相比其它数据类型较慢，因此要避开使用。

3. 日期型字段的优点是有众多的日期函数支持，因此，在日期的大小比较、加减操作上非常简单。但是，在按照日期作为条件的查询操作也要用函数，相比其它数据类型速度上就慢许多,因为用函数作为查询的条件时，服务器无法用先进的性能策略来优化查询而只能进行表扫描遍历每行。


使用MySql ODBC进行MYsql和MS sql7的数据转换(衰衰)
将Mysql数据库转换为MSsql的数据库，或者将Mssql数据库转换为Mysql的数据库,在NT环境下很多时候都会用到。使用MySql ODBC后就比较好办，可以使用MSSQL7的管理工具，也可以使用mysql的管理工具，更可以使用其它方的管理工具
这里介绍一个使用SQL7的MMC的方法 ，将MSsql7的数据转化为Mysql的数据库，将源和目的反之，就可以将Mysql的数据库转化为Mssql7的数据库。

1.安装Mysql的ODBC接口，可以在本站下载
2.建立Mysql的DSN，可以建系统DSN，这里命名testmysql,填写IP,dababase,用户名口令等项，完成
3.在SQL7的MMC中，选择要导出的数据库，右键选择All Tasks->Export Datas...
4.开始DTS Export Wizerd...,
Choose a Data Source:Microsoft OLE DB Privoder for SQL Server
server:你的MSsql7数据库的服务器
是否使用NT认证和用户名口令看你自己的了
最后选择一个Database,如:mynews(你自己要导到mysql中的数据库)
下一步
5.Choose a Destination:选Mysql
User/System DSN,如果建立过就选择，如果没有建立就新建。
6.Specify Table Copy or Query
Copy table(s) from the source database,从源数据库拷贝表开始
7.Select Source Table
选择要拷贝的表，如果不想仔细调整，就选择全部吧。
8.Run immediately,当然要立即执行，下一步再选完成，就开始转换。

这个转换有时一部分表可能要失败，双击失败的表格，看看什么原因，一般是SQL7的数据类型问题，作一些小的修改就应该可以了。
使用MySql ODBC后就比较好办，可以使用MSSQL7的管理工具，也可以使用mysql的管理工具，更可以使用其它方的管理工具
这里介绍一个使用SQL7的MMC的方法
1.安装Mysql的ODBC接口，可以在本站下载
2.建立Mysql的DSN，可以建系统DSN，这里命名testmysql,填写IP,dababase,用户名口令等项，完成
3.在SQL7的MMC中，选择要导出的数据库，右键选择All Tasks->Export Datas...
4.开始DTS Export Wizerd...,
Choose a Data Source:Microsoft OLE DB Privoder for SQL Server
server:你的MSsql7数据库的服务器
是否使用NT认证和用户名口令看你自己的了
最后选择一个Database,如:mynews(你自己要导到mysql中的数据库)
下一步
5.Choose a Destination:选Mysql
User/System DSN,如果建立过就选择，如果没有建立就新建。
6.Specify Table Copy or Query
Copy table(s) from the source database,从源数据库拷贝表开始
7.Select Source Table
选择要拷贝的表，如果不想仔细调整，就选择全部吧。
8.Run immediately,当然要立即执行，下一步再选完成，就开始转换。

这个转换有时一部分表可能要失败，双击失败的表格，看看什么原因，一般是SQL7的数据类型问题，作一些小的修改就应该可以了。


 INF：SQL Server 内存使用量(cityboy)
调试了两天，竟然在微软发现了这个，，，，

这篇文章中的信息适用于:
Microsoft SQL Server 2000（所有版本）
Microsoft SQL Server 7.0

本文的发布号曾为 CHS321363
概要在您启动 SQL Server 之后，SQL Server 内存使用量将会持续稳定上升，即使当服务器上活动很少时也不会下降。另外，任务管理器和性能监视器将显示计算机上可用的物理内存稳定下降，直到可用内存降到 4 至 10 MB 为止。

仅仅出现这种状态不表示内存泄漏。此行为是正常的，并且是 SQL Server 缓冲池的预期行为。

默认情况下，SQL Server 根据操作系统报告的物理内存加载动态增大和收缩其缓冲池（缓存）的大小。只要有足够的内存可用于防止内存页面交换（在 4 至 10 MB 之间），SQL Server 缓冲池就会继续增大。像在与 SQL Server 分配内存位于相同计算机上的其他进程一样，SQL Server 缓冲区管理器将在需要的时候释放内存。SQL Server 每秒可以释放和获取几兆字节的内存，从而使它可以快速适应内存分配变化。
更多信息您可以通过服务器内存最小值和服务器内存最大值配置选项设置 SQL Server 数据库引擎使用的内存（缓冲池）量的上下限。在设置服务器内存最小值和服务器内存最大值选项之前，请查阅以下 Microsoft 知识库文章中标题为"内存"一节中的参考信息：
319942 HOW TO：Determine Proper SQL Server Configuration Settings（确定正确的 SQL Server 配置设置）

请注意，服务器内存最大值选项只限制 SQL Server 缓冲池的大小。服务器内存最大值选项不限制剩余的未保留内存区域，SQL Server 准备将该区域分配给其他组件，例如扩展存储过程、COM 对象、以及非共享 DLL、EXE 和 MAPI 组件。由于前面的分配，SQL Server 专用字节超过服务器内存最大值配置是很正常的。有关此未保留内存区域中分配的其他信息，请单击下面的文章编号，以查看 Microsoft 知识库中相应的文章：

316749 PRB：在使用大量数据库时可能没有足够的虚拟内存

参考
SQL Server 联机图书；主题："服务器内存最小值和最大值的影响"；"内存体系结构"；"服务器内存选项"；"SQL Server 内存池"
最近更新: 2003-1-13
关键字 kbinfo KB321363


信息挖掘初步（ccat）
http://www.csdn.net/develop/read_article.asp?id=14081
写这本书的最初想法，来自于在第一个公司工作时，与同事的交流和学习。不过发布这本书的导火索，却在于一次在CSDN上读到一篇关于最新信息的报表问题的贴子。贴子中的问题可以用子查询和联接两种方式完成。由于条件所限，我不能详细解答，由此发贴的朋友不能理解我的本意，让我心生遗憾。所以决定将写书的想法付诸行动，并将这本书贴在CSDN上，与大家一起交流，共同进步。今天正好又见到类似于当日的问题，心生感触，决定在这里把它详细讨论一下。
在实际工作中，我们有时会需要建立数据表来存储变动的数据，并由这些数据统计出我们所需的信息。其中有一类问题的特点在于最终结果的过滤条件来自分组统计后的数据。这类应用常见于网站数据库、财会系统、实时系统、数据仓库与数据挖掘等。事实上，这种命题本身已经包括了数据挖掘。现在，我们看下面的例子

例4-4-1：最新报价

网友kikilyq问：

我有一个table:COMPUTER_PRICE，格式如下：

goods price dates

--------------------------------

HP电脑 20000 5.21

HP电脑 20050 5.23

NEC电脑 31200 5.3

NEC电脑 32000 5.5

查询结果要求：要查出每种电脑的最新价格；

上面表的结果为：

goods price dates

---------------------------------

HP电脑 20050 5.23

NEC电脑 32000 5.5

帮帮忙，搞定这个问题？

根据问题，我们先建立数据表，经分析，表中数据应由货物名和日期标示，所以设这两个字段为主关键字：

SQL Server版脚本如下

CREATE TABLE [dbo].[GOODS] (

[GOODS] [char] (10) COLLATE Chinese_PRC_CI_AS NOT NULL ,

[PRICE] [money] NOT NULL ,

[DATE_TIME] [datetime] NOT NULL ,

PRIMARY KEY (GOODS, DATE_TIME)

) ON [PRIMARY]

InterBase版脚本如下

CREATE TABLE GOODS

(

GOODS CHAR(10) NOT NULL,

PRICE NUMERIC(15, 4) NOT NULL,

DATE_TIME TIMESTAMP NOT NULL,

PRIMARY KEY (GOODS, DATE_TIME)

)

建立表后，请读者自行将数据插入。

这个问题中，最终报表中的电脑的价格取决于其后一次报价，也就是报价日期最新的那一行数据。典型地属于前面提到的类型。直觉来讲，我先试着选出每种电脑的最新报价日期，这个比较简单：

SELECT GOODS, MAX(DATE_TIME)

FROM GOODS

GROUP BY GOODS

返回结果

GOODS

---------- ------------------------------------------------------

HP 2002-05-23 00:00:00.000

NEC 2002-05-05 00:00:00.000

显然，只要把各品牌电脑在以上日期的报价显示出来，就是我们所要的结果了。那么直接这么写如何？

SELECT GOODS, PRICE, MAX(DATE_TIME)

FROM GOODS

GROUP BY GOODS

我想这个语句就不用试了，稍有经验的程序员会发现，PRICE列不在统计函数中，也不在GROUP BY 之列，数据库系统无法执行这样的语句。这样的列一定要从另一个数据集中取出，所以我首先想到的是自联接。不过相信大多数朋友会先想到子查询。现在我们先看看子查询如何做，毕竟这样比较直观。最偷懒的办法是直接把PRICE表达为一个插入的标量子查询：

SELECT L.GOODS,

(SELECT R.PRICE FROM GOODS R WHERE R.GOODS = L.GOODS AND R.DATE_TIME = MAX(L.DATE_TIME)) AS PRICE,

MAX(L.DATE_TIME) AS CURRENT_DATE_TIME

FROM GOODS L

GROUP BY L.GOODS

不过很遗憾，这个语句只能在SQL Server中执行，InterBase的提示是invalid column reference（无效的列引用）。不过换一个思路就可以写一个通用版。现在，我们对系统说，我要从表中取出部分行，每个品牌的电脑一行，其日期是这个牌子的最新报价日期：

SELECT L.GOODS, L.PRICE, L.DATE_TIME

FROM GOODS L

WHERE L.DATE_TIME =

(SELECT

MAX(R.DATE_TIME)

FROM GOODS R

WHERE R.GOODS = L.GOODS

GROUP BY R.GOODS)

这个

我想应该还可以写出几个不同的子查询变种，不过大同小异，就不一一尝试了。这个版本看来有些不可靠，因为主查询的WHERE条件中只有DATE_TIME，似乎不能准确地区分出每一行数据。不过放心，这里有一个“诡异”的相互引用，主查询的记录要满足日期等于子查询的返回值，而子查询的货物名（GOODS列）依赖于主查询的货物名（GOODS列）。这样，子查询会针对当前的品牌返回其正确日期，这是相关子查询的绝技，也是造成它在很多场合效率较差的原因。我对子查询的兴趣到此为止了，前面的文章中我说过，联接查询是一种很好的技术，那么这个查询有没有可能用联接来实现呢？前面对子查询的分析在这里会有助于我们的思考。现在我们如果有两个数据集，一个有最大日期，一个有价格，把它们一联接，不就可以了吗？这两个结果集就在上面的子查询中，现在的问题是我们如何把它们联接起来，显然，有一个联接条件是R.GOODS = L.GOODS ，这同时也确定了最终结果集的唯一标识之一——GOODS列，而日期列的过滤条件照搬L.DATE_TIME = MAX(R.DATE_TIME) ，加上GROUP BY结果集中的列，于是就有了：

SELECT L.GOODS, L.PRICE, L.DATE_TIME

FROM GOODS L

JOIN GOODS R

ON L.GOODS = R.GOODS

GROUP BY L.PRICE, L.GOODS, L.DATE_TIME

HAVING L.DATE_TIME = MAX(R.DATE_TIME)

有些朋友可能不明白这个GROUP BY 为何而来，简单说一下。我们做了一个GOODS表的自联接（两个数据集分别是L和R）后，会生成一个迪卡尔积。有些人称之为数据爆炸。不过把矿石炸开之后，倒方便我们在其中找金子了。用JOIN条件一联接，L数据集中的每一种品牌在R数据集中都对应上了自己的所有报价日期。现在我们要对R数据集分组，选出每一品牌的最新报价日期，以其过滤L数据集就应该用

GROUP BY R.DATE_TIME

HAVING L.DATE_TIME = MAX(R.DATE_TIME)

由于我们要取出L数据集中的三列，所以要把它们也列在GROUP BY 中，由于已经有了L.GOODS = R.GOODS 这个联接条件，R.GOODS 倒是可以不写在GROUP BY 中了。由于我在前面的文章中提到过的种种理由，我个人比较喜欢用联接。当然从效率方面讲，当表中每个分组的数据很多时，联接查询的效率会因巨大的迪卡尔积而被抵消，这时应用子查询（如果这是一个记录生产线即时工作情况的表，统计其最新的生产情况，通常就是这样）；而表中每个分组的数据很少，但数据组很多（如大型网上书店，用户可能是一个天文数字，相对来说每个用户买的书就不多了）时，子查询仍要为主查询的每一行反复遍历整个表，效率就太低了，而联接只要处理事先生成的迪卡尔积，以空间上的代价换来了时间上的优势，这时应当用联接查询。

以上各种方案的返回结果集都是：

GOODS PRICE DATE_TIME

---------- --------------------- ------------------------------------------------------

HP 20050.0000 2002-05-23 00:00:00.000

NEC 31000.0000 2002-05-5 00:00:00.000

除特别指明，以上脚本可在MS SQL Server7.0以上版本和InterBase6.0.1 以上版本通用。掌握以设计以上脚本的方法，可以广泛用于各种以统计数据为查询条件的SQL编程。由此开始，我们还可以就两个有趣的方面展开讨论：联接查询和数据分组。

附：本来这个联接查询脚本还应当贴在 kikilyq 的问题后面，不过由于我愚蠢地贴了两个不该贴的贴子（一个有错，一个贴重了），CSDN的网页不允许我再往 kikilyq 的贴子后跟贴了。请kikilyq来这里读吧。抱歉。

关于《SQL Story》：这本书意在提出一些常见的SQL编程问题的解决方案，总结出一些关系型数据库设计和SQL语言编程方面的模式，通过实际问题，帮助读者提高数据库编程的能力，本书还会涉猎关系代数的理论领域。我计划在CSDN上收集一些典型的SQL 编程问题，做为本书的重要内容。作者保证不剽窃任何人的劳动成果，所有解答及分析绝不假手于人。如有高手指点迷津，我一定会在文中明确指出并提出感谢。欢迎各位网友提供素材，在此先表示感激。另外我希望能有一位Oracle高手与我合作，完成这本书的Oracle部分。与我分享成功的喜悦和劳动的艰辛。

以下是我初步拟定的《SQL Story》 提纲，也就是说，现在各篇文章的例题届时会分门别类出现在这本书的合适位置，可能现在的序号编排会有变动。


可扩展设计(ccat)
http://www.csdn.net/develop/read_article.asp?id=14039
面向集合的结构化设计。这一点很多人都知道，可真正能够活用的就太少了。举一个简单的例子：
例1-3：有一个简单的数据表Orders，存储某商店的订单信息：
CREATE TABLE [dbo].[ORDERS] (
[ID] [int] IDENTITY (1, 1) NOT NULL ,
[CustomerID] [int] NOT NULL ,
[OrderDate] [datetime] NOT NULL
) ON [PRIMARY]
GO
CREATE CLUSTERED INDEX [CU_INX_OrderDate] ON [dbo].[ORDERS]([OrderDate]) WITH FILLFACTOR = 50 ON [PRIMARY]
GO
ALTER TABLE [dbo].[ORDERS] WITH NOCHECK ADD
CONSTRAINT [PK_ORDERS] PRIMARY KEY NONCLUSTERED([ID])
ON [PRIMARY]
GO
表中现在有以下数据：
ID CustomerID OrderDate
----------- ----------- ------------------------------------------------------
1 1 1999-1-4
2 10 1999-3-5
3 22 1999-5-2
4 2 1999-6-7
5 2 2000-3-6
7 101 2001-5-3
8 10 2001-6-5
6 101 2002-4-2
那么，我们如何生成一个1999-2002的年度订单数报表呢（四年只有8个订单？我为了演示方便才这样做的，这并不代表真实的情况：P）？现在，我给出实际报表的数据格式，读者们请先试一下这个语句的写法
CustomerID 1999 2000 2001 2002
-------------- ------ ------ ------ ------
1 1 0 0 0
2 1 1 0 0
10 1 0 1 0
22 1 0 0 0
101 0 0 1 1
最直观的想法，是在前台，用其它语言实现这一功能。不过有一个办法，可以用SQL语言来实现它。而且不一定比你想像的更复杂：
SELECT CustomerID,
SUM(CASE WHEN YEAR(isnull(OrderDate, 0)) = 1999 THEN 1 ELSE 0 END) AS "1999",
SUM(CASE WHEN YEAR(isnull(OrderDate, 0)) = 2000 THEN 1 ELSE 0 END) AS "2000",
SUM(CASE WHEN YEAR(isnull(OrderDate, 0)) = 2001 THEN 1 ELSE 0 END) AS "2001",
SUM(CASE WHEN YEAR(isnull(OrderDate, 0)) = 2002 THEN 1 ELSE 0 END) AS "2002"
FROM ORDERS
GROUP BY CustomerID
我想这时会有朋友提出InterBase不支持Case的问题。不过即使如此，我还是要向大家推荐这种写法。因为它优美、简洁，不仅我们读着好懂，还可以很方便地写出程序来自动生成它。事实上，Case关键字已是SQL标准之一，大势所趋，会有越来越多的数据库系统支持它的。
那么它又是怎么来的呢？我在设计这个语句时是这样的思路：
1、我们需要一个同时在时间和客户两个坐标轴上展开的报表；
2、纵向上，我们要为每一位客户建立一行数据，这个比较好办，我们首先确定了这个语句会有一个基本框架
SELECT CustomerID,
………………
FROM ORDERS
GROUP BY CustomerID
如果不区分年度，已下语句就是我们要的结果
SELECT CustomerID,
COUNT(ID) AS ORDERS_COUNT,
FROM ORDERS
GROUP BY CustomerID
3、设所有订单为一全集，那么这个集合的总数用以下语句来统计：
SELECT COUNT(ID) FROM ORDERS
横向上，我们为每一年度的订单数定义一列，以1999年为例，取年份为1999年的订单子集的元素数为
SELECT SUM(CASE WHEN YEAR(isnull(OrderDate, 0)) = 1999 THEN 1 ELSE 0 END) AS "1999"
FROM ORDERS
其它年份依此类推，我们得到每一年的订单数：
SELECT SUM(CASE WHEN YEAR(isnull(OrderDate, 0)) = 1999 THEN 1 ELSE 0 END) AS "1999",
SUM(CASE WHEN YEAR(isnull(OrderDate, 0)) = 2000 THEN 1 ELSE 0 END) AS "2000",
SUM(CASE WHEN YEAR(isnull(OrderDate, 0)) = 2001 THEN 1 ELSE 0 END) AS "2001",
SUM(CASE WHEN YEAR(isnull(OrderDate, 0)) = 2002 THEN 1 ELSE 0 END) AS "2002"
FROM ORDERS
其返回结果如下：
1999 2000 2001 2002
----------- ----------- ----------- -----------
4 1 2 1

(所影响的行数为 1 行)
4、顾及到关系型数据库“诡异”的NULL值问题后，综合2、3步，我们得出最终的语句：
SELECT CustomerID,
SUM(CASE WHEN YEAR(isnull(OrderDate, 0)) = 1999 THEN 1 ELSE 0 END) AS "1999",
SUM(CASE WHEN YEAR(isnull(OrderDate, 0)) = 2000 THEN 1 ELSE 0 END) AS "2000",
SUM(CASE WHEN YEAR(isnull(OrderDate, 0)) = 2001 THEN 1 ELSE 0 END) AS "2001",
SUM(CASE WHEN YEAR(isnull(OrderDate, 0)) = 2002 THEN 1 ELSE 0 END) AS "2002"
FROM ORDERS
GROUP BY CustomerID
现在这个报表结构清晰明白。扩展性极强。比如明年我们需要2003年的统计数据，只要再依葫芦画瓢，来一列
SUM(CASE WHEN YEAR(isnull(OrderDate, 0)) = 2003 THEN 1 ELSE 0 END) AS "2003"
加在最后就可以了，它是全集中的2003年数据的子集。还有，用来判断空值的isnull函数不一定所有的数据库都有，没关系，只要在Case的分支里加一行
WHEN OrderDate is NULL THEN 0
就可以了。基于这个思想，我们可以很容易地写出一个存储过程，只要给定起讫年份，就可以生成一个完整的年度报表。由于所有的运算都在服务器端运行，并且是随着数据检索一次就完成了。它的速度快于客户端的报表。而且传输的数据量也少，可以有效减轻网络负载。
在《SQL Server6.5技术内幕》中，有一个类似的例子。不过作者使用的语句结构比我的复杂，他的例子中，From关键字是从一个子查询导出表中选择的数据，这让我百思不得其解。也许6.5版的MS SQL Server还不支持我的写法，也许那样写性能更好。作者并没有说明，我也一直没有机会接触到MS SQL Server6.5。
对于InterBase，我还没有办法用足够优雅的语句生成这个报表。这主要是由于InterBase不支持Case。不过，如果你对语句的性能和美感要求不高的话，下面这个语句可以实现与以上的SQL Server版本相同的功能：
SELECT O.CUSTOMERID,
(SELECT COUNT(I.ID)
FROM ORDERS I
WHERE (I.CUSTOMERID = O.CUSTOMERID)
AND (EXTRACT(YEAR FROM I.ORDERDATE) = 1999))
as COUNT_1999,
(SELECT COUNT(I.ID)
FROM ORDERS I
WHERE (I.CUSTOMERID = O.CUSTOMERID)
AND (EXTRACT(YEAR FROM I.ORDERDATE) = 2000))
as COUNT_2000,
(SELECT COUNT(I.ID)
FROM ORDERS I
WHERE (I.CUSTOMERID = O.CUSTOMERID)
AND (EXTRACT(YEAR FROM I.ORDERDATE) = 2001))
as COUNT_2001,
(SELECT COUNT(I.ID)
FROM ORDERS I
WHERE (I.CUSTOMERID = O.CUSTOMERID)
AND (EXTRACT(YEAR FROM I.ORDERDATE) = 2002))
as COUNT_2002
FROM ORDERS O
GROUP BY O.CUSTOMERID
依照SQL Server版本，我们完成了InterBase版的年度报表。不同的是由于使用了相关子查询统计数据，它的效率会差一些(好在你不需要即时更新你的年度报表吧)。不过由于它同样是基于面向集合的设计构架，至少我们保证了它的可扩展性。只是很明显的，当子查询版本中增加一列年度统计，所带来的开销增长会比Case版本多很多。如果你对速度要求较高，还是在客户端另写程序生成吧。
InterBase数据库的用户会在这个示例中遇到很多不满意的地方：不支持自动标识列、没有聚簇索引、没有Case、没有……更可恨的是，这个数据系统的开放源码版本没有附带ODBC或ADO驱动，在得到一个免费的数据库系统后，我们却要为它花几十美元去买一套ODBC驱动？
不过，InterBase正在得到开放源码社区的支持，Borland公司也通过DBExpress和InterClient技术来为InterBase提供开放的接口（目前DBExpress驱动基本上也只存在于Borland昂贵的企业版开发工具中:(）。只要每一个InterBase的程序员和用户都为这个属于我们自己的软件做出贡献，它的前途还很光明。

面向集合的设计方法虽然只适用于特定的目标，并不是通用的软件设计方法。但也不是三言两语能说清的，以后的章节中，我们会一直实际这种设计方法，还会有专门的章节讨论这个问题。那时，我们的示例数据库也建设的比较完整了，我也许会给出更实用的年度订单统计报表。现在，我们先简单地总结一下：
1、定义我们要生成的结果集的结构；
2、找出结果集的数据来源，定义全集；
3、定义结果集的取值范围，定义所取的子集；
4、完成操作。


关系的真相(ccat)
http://www.csdn.net/develop/read_article.asp?id=14118
关系的真相

长期以来，我们习惯了称关系型数据库中的表为二维表。因为它有行和列，很容易我们就可以把它同一个二维平面联系起来，但事实上，这并非关系型数据库的初衷，也并非符合关系模型的设计。其实长久以来，我对此也只有一个很模糊的概念，对平面表的观点虽有怀疑，却一直无从验证。直到有一天，翻出一本老书——《关系数据库》（石树刚、郑振楣编著，清华大学出版社，1993年），这本老书没有什么流行的新噱头，却满满当当地净是数学理论。这本书读起来并不是很诱人，不过的确很严谨，澄清了我的很多不明之处。也激励我找来各种权威材料重头学起。薄薄一本书，定价只有9.9元，想想这应当是我在上学时，从旧书摊上买的，可能当时只花了不到五元钱。这肯定是我买的性价比最高的一本书了。

在此，我不想从别人书中抄出大段原话，拼出一篇文字，有这工夫还不如上网回贴子更有成就感，我宁愿将我的心得，用并不严密，但尽可能易懂的语言写下。让朋友们先对关系模型有个基本概念，使众多像我这样非科班出身的读者多少了解一下事实真相，不至于在工作中有所不便。如果你想真正掌握关系型数据库的数学模型，请读一读这本不流行的老书《关系数据库》。当然，还有很多新近的正规教材也写得很不错，比如现在在我手边的《DATABASE SYSTEM CONCEPTS》（机械工业出版社）和《SQL-3 参考大全》（机械工业出版社）。前两本写得更严谨一些，后一本是译得不太好，有些关键地方读着莫名其妙，不过总得来说还是本难得的好书。

言归正传，现在我们说说关系型数据库到底是怎么一回事。我们先看一个表：

X Y Z

------------------

0 0 0

1 1 1

0 0 1

0 1 1

……

这个表存储一个三维空间内的一些点。我们可以很清楚地看到，每一个完整的行，才代表一个点。仅定位某行某列，它并不能表达这个表所定义的信息结构。当我们向这个表中插入或删除数据，它仍代表三维空间内的点集。但如果我们增加一列T（time)呢？那一切全变了，它不再是三维空间了，现在，这个表中存储的是四维的信息了（读过相对论的朋友应当了解，相对论中的时空就是一个四维坐标系）。删除一列呢？比如Z列，现在我们面对的就是X-Y 平面了，这是一个二维坐标系。也就是说，在表中删除或增加或修改若干列，并不会改变这个表所代表的意义。而修改或增删列，就会改变表的结构，表所代表的意义也就不同于以前了。表的行和列，并不是平等的！我们不能简单地把它理解为一个平面！相反，我认为，将表的结构理解为一个代数空间更合适，这样，表中的每一行是这个代数空间中的一个点，那么当前表中的信息就形成了这个空间中的一个点集。当然，这个集合还可有其它的约束条件，下面我们从关系模型说起。

在严格意义上的关系模型中，一个关系模式，包括关系名、有限属性集、属性值域、属性列到值域的映射、完整性约束和属性间依赖。对应数据库中的结构，通常一个关系模式对应一个或多个表和视图。这些表和视图有其各自包括的列，而列有列名、列的数据类型、表和视图的主键约束、外键约束、检查、断言、触发器（在SQL Server2000中，已可以在视图中定义索引和触发器，再加上视图本身的 SQL脚本，视图以可以定义为一个完整的关系模式）。一个简单的关系模式可以只由一个表构成，而多个元组（表或视图）组成的关系，其相互的关联由外键和触发器来完成。在这个定义中，关系中的各个列（也就是说关系的模式）可以有很强的依赖性。比如某些列有主键约束，另一些列有引用外键。而行与行之间（也就是说关系之间）的依赖只存在于两种情况——外键和触发器。其中外键可以表达为模式上的（也就是列之间的）依赖。例如以下订单系统，它由客户表，订单表和

CREATE TABLE CUSTOMERS(

CUSTOMERID INT NOT NULL,

FIRSTNAME CHAR(20),

LASTNAME CHAR(20),

CITY VARCHAR(30),

CONSTRAINT PK_CUSTOMER PRIMARY KEY(CUSTOMERID)

)

CREATE TABLE GOODS(

ID INT NOT NULL,

NAME CHAR(20) NOT NULL,

NUMBER INT,

PRICE MONEY,

CONSTRAINT PK_GOOD PRIMARY KEY(ID))

CREATE TABLE ORDERS(

ORDERID INT NOT NULL,

CUSTOMERID INT NOT NULL,

ORDERDATE DATETIME,

CONSTRAINT PK_ORDER PRIMARY KEY(ORDERID),

CONSTRAINT FK_CUSTOMER FOREIGN KEY (CUSTOMERID)

REFERENCES CUSTOMERS(CUSTOMERID))

CREATE TABLE ITEMS(

ITEMSID INT NOT NULL,

ORDERID INT NOT NULL,

NUMBER INT NOT NULL

CONSTRAINT PK_ITEM PRIMARY KEY(ITEMSID),

CONSTRAINT FK_GOOD FOREIGN KEY (ITEMSID)

REFERENCES GOODS(ID),

CONSTRAINT FK_ORDER FOREIGN KEY (ORDERID)

REFERENCES ORDERS(ORDERID)

)

以上一表中，一位客户可以有多张订单，订单的订户依赖客户表。一张订单可以有多个条目，明细条目又依赖于订单和商品的存在。这四个表就构成一个关系模式，四个表都有其主键，表中的其它列依赖于主键列。表之间的外键引用则构成了表之间的依赖关系。由此联接起了整个模式。每一个完整的订单，包括ORDERS表中的订购信息和ITEMS表中的明细，形成一个关系。而一个用户和他的订单，又可以形成一种新的关系，这些关系的模式，可以由SQL语句来表达，这里不详细讲了，朋友们可以试一下，很简单的联接查询而已。我们可以看到，这个典型的关系中，信息的内容之复杂，远远超出了“几个二维平面表”所能定义的。“带有强约束条件关联的若干代数空间点集”可能更好一些。甚至，我们还可以在其上定义更强的约束，比如用一个触发器保证用户的订购数在某一范围内。当我们增删订单，存货甚至客户时，由于强有力的约束存在，保证了每个关系仍是完整的。可列的定义本身就是关系模式的一部分，如果我们改变或增删了列，修改的是整个模式。至少一个代数空间，被我们改变了。这也就是行和列的区别。当然，只定义一个表，不给它加以任何约束，在技术上是允许的，但这样的表不能称为一个关系。它甚至不能保证最基本的关系完整性。如以前的文章所示，这样的表轻易就可以插入重复数据，而这些不能互相区别的数据并不能给我们更多的信息。这种没有约束的表在实用中应当严格限制于临时表，不应在其它任何场合出现。

希望读了这篇文章的朋友，能够不再犯我当初常初的错误，建立一个又一个没有任何约束的表，存入不可靠的数据。我们应当把信息的结构和关系模型直接表达在数据库的设计中，这才是关系型数据库的意义所在。这一次几乎没有可执行的代码，可能读者们会有意见。不过真心祝大家能理解我的意思，在关系型数据库的世界中更轻松自如地工作。如果您有批评、表扬、指导，我在这里专心地听取，谢谢您的支持。我在书中专注于SQL Server 和InterBase，希望有Oracle或DB2等其它数据库系统的高手与我合作，完成本书的不同版本内容，与我分享劳动的艰辛和成功的喜悦。


不等连接(ccat)
http://www.csdn.net/develop/read_article.asp?id=14591
通常来说，SQL语言进行的都是无序操作。想要进行有序的处理，比如比较一个序列的前后项，必须要使用游标。但是，在有些场合下，可采用另一种方法，不用游标，一样能处理有序的信息，这就是不等联接。先看下面一个例子

前一阵， CSDN网友BuildIt来信，和我讨论了这样的问题：以下表HISTORY

CREATE TABLE [HISTORY] (

[TheDate] [datetime] NULL ,

[Quantity] [int] NULL

) ON [PRIMARY]

中存储的是一系列的历史数据，例如：

INSERT HISTORY valueS('2002-01-01 00:00:00.0',11)

GO

INSERT HISTORY valueS('2002-01-02 00:00:00.0',34)

GO

INSERT HISTORY valueS('2002-01-03 00:00:00.0',27)

GO

INSERT HISTORY valueS('2002-01-04 00:00:00.0',43)

GO

现在，我们要查询自起始日期至每一个日期的总量。也就是说，显示这样一个结果集：

TheDate Quantity q_sum

2002-01-01 00:00:00.0 11 11

2002-01-02 00:00:00.0 34 45

2002-01-03 00:00:00.0 27 72

2002-01-04 00:00:00.0 43 115

直观上来讲，我们可以在SELECT * FROM HISTORY ORDER BY TheDate上建一个游标，从第一条开始，每一条，加一次。那换个想法呢？如果我们建立这样一个结果集，让每一个日期限，都对应它当天的数量及所有在它之前的数量记录。那么我们就可以按这个日期分组，对数量进行求和。很显然，一个不等查询就这样形成了。我最初的写法有错误，以下是经 BuildIt 修改后最终的语句

select l.TheDate,

l.quantity,

sum(r.quantity) as q_sum

from HISTORY l

join HISTORY r

on l.TheDate >= r.TheDate

group by l.TheDate, l.quantity

order by l.TheDate

不等联接本身就不是一一对应，它的对应关系和顺序有着密切的关系。这也就是我们能够拿它来进行有序操作的原因。再给一个很自然的例子：

SELECT L.I, SUM(R.I)

FROM N L

JOIN N R

ON L.I >=R.I

GROUP BY L.I

表N只有一个整型列I，保存自然数列。所以，没什么神秘，这就是求自然数列的和。这里SUM(R.I)表示自然数列N从零至I的累加和，比前面的那个问题还要简单。不过显然这不是不等联接发挥威力的地方，因为它会形成一个巨大的三角形数据集，就像下面这样

1 1

2 1

2 2

3 1

3 2

3 3

...

当我对十六位整型的列表执行这个查询时，我的AthlonXP1700+/256MDDR的机器足足运行了近三十分钟，当我写下现在这段文字时，它返回了一个数据溢出错误。显然，对于这个查询，即使是十六位整数的列表，也太大了。我的建议是，仅当结果集无法用公式表达时，使用不等联接。像这个累加，我们早已有了成熟的公式，何必再让计算机傻算呢？用下面这个语句

SELECT I, ((1+I)*I)/2

FROM N

相比老老实实地累加，速度奇快。发现数据溢出时，连一秒钟都不到，可这台计算机就是想不到用这个方法，唉……

传说数学界一代宗师高斯小学的时候，他老师考过他这个问题。所以几乎所有的中国小学生，都被老师用这道题折磨过。好像老师们的目的就在于告诉我们，我们的智商比不上高斯。可我压根就没想和人家比啊……

上大学时，教我们第一本《数学分析》的范先令老师说计算机是傻子，我当时只是觉得好玩而已，今天算是见识了，看来在归纳总结的能力方面，计算机也就是我小学时的水平，永远也赶不上高斯上小学那会儿了。

不过，这种东西用于公式难以表达的地方，还是有意义的。比如我的一个朋友用不等联接写过一个素数筛子，很有趣。它虽说不会比我们用过程化的代码写出来的程序效率更高，但却能把筛法根本的精要表达的清清楚楚，也许以后我们研究数论，会用的上这种SQL风格的表示法呢。这位朋友教了我很多计算机方面的知识，出于对他的尊重，我不会抄录他的代码。不过这个语句本身并不复杂，相信朋友们想到用联接查询后，都一定写得出来，大家有兴趣的话，自己不妨试试。用它还可以实现其它的一些数列，以后我们再讨论几个。

不等联接还有一个用法，可以用它生成一个序号列，比如

SELECT COUNT(L.AFIELD) AS ID,

L.AFIELD

FROM MYTABLE L

JION MYTABLE R

ON L.AFIELD > R.AFIELD

GROUP BY L.AFIELD

AFIELD字段可以是字符串、日期，当然也可以是数值，反正可排序就行。这东西有点奇技淫巧的味道，数据量太大，就不好玩了，一般还是用物理行号的好，虽说不是SQL标准，但实用啊。这个例子我在MCDBA的复习题中见过（据说这道题考过），不过我的那位朋友自己就做出来过，大家可能也有独立实现过这一方法的吧。

不等查询的有序操作能力，显然来自联接字段的可排序和互异性，所以，最好不要在有重复值的字段上做不等联接（事实上，最好不要在有重复值的字段上做任何联接，除非你非常肯定你在干什么）。等值联接出现数据爆炸就够可怕的了，不等联接要是玩爆了……嘿嘿嘿……

想像一个等值联接中有一对重复值，可能出现两对重复结果。不过要是不等联接，就和重复的位置有关了。因为这是一个三角形，所以出现在最上面还好，要是出现在这个三角形的下部……

不等联接查询，显然是一个有力的工具，但它也是招来麻烦的快捷方式之一。有几个建议，是我的经验：
如果联接会生成很大的“三角形”，就不要用，试试子查询或哪怕游标；

生成的结果集相对于原表越小越好，尽可能地把无用的数据先过滤掉；

用不等联接进行数列计算会表达的很清楚（因为是非过程化的），但通常在效率上它没有什么优势，所以，平时玩玩可以，真用的话最好先考虑好；

还有就是不等联接不要轻易用在多重联接中，否则可能会引起杠杆作用。

祝大家在这个神奇的世界中旅行愉快！


系统表的一些例子
具体看sqlserver2000帮助 ，输入sys后列出相应的系统表
1.自动生成sql的数据字典
select name,sno='''',fieldname=''表名:''+name,type='''',length='''',xscale='''',ifnull=''''
into #tmp
from sysobjects where type=''U''
union all
select a.name,sno=b.colid,fieldname=b.name,
type=case b.xtype when 34 then ''image''
when 35 then ''text''
when 52 then ''smallint''
when 56 then ‘int''
when 61 then ''datetime''
when 62 then ''float''
when 108 then ''numeric''
when 167 then ''varchar''
when 175 then ''char''
when 231 then ''nvarchar'' end,
length =case when b.xtype=61 then 0 else b.xscale end,
ifnull=case when b.isnullable=0 then ''no'' else ''yes'' end
from sysobject a left join syscolumns b
on a.id=b.id where a.type=''u''
2.怎样取得数据库中所有表的索引名称及索引类型和相关索引字段（参考sp_helpindex)
select b.id,a.indid,a.groupid,a.name,(a.status&16) as clusted,(a.status&2) as unique,b.name as myname,d.name as colname
from sysindexes a.,sysobjects b,sysindexkeys c,syscolumns d
where a.indid>0 and a.inid<255 and(a.status&64)=0 and a.id=b.id
and a.id=c.id and a.id=d.id and c.colid=d.colid
and a.indid=c.indid and objectproperty(b.id,N''isusertable'')=1
order by b.name ,a.indid

3.如何在数据库里查找含有相同字段的表
select b.name as tablename,a.name as columnname
from syscolumns a inner join sysobjects b
on a.id=b.id and b.type=’U''
and a.name =''你的字段名称''
select o.name as tablename ,s1.name as colulmnname
from syscolumns s1,sysobjects o
where s1.id=o.id and o.type=''U''
and exists(select * from syscolumns s2
where s1.name=s2.name and s1.id<>s2.id)
4.利用系统视图可以更方便的查出一些想要的东西
在帮助里输入infomation_schema,然后就列出了所有的系统视图
select * from infomation_schema.tables
可以查出表和视图
select * from infomation_schema.views
可以列出系统视图和自定义视图
select * from infomation_schema.key_column_usage
列出表的名字，主键，外键所属表的列名
.....
因为包含太多，可以自己看帮助。
5.得到字段的描述属性
select * from ::fn_listextendedproperty(null，‘user'',''dbo'',''table'',''cab'',''column'',default)


异构数据库导数据（碧血剑)
http://www.delphibbs.com/delphibbs/dispq.asp?lid=1691966
看一下SQLServer联机帮助的
OpenDataSource和OpenRowSet函数

告诉你一个最快的方法，用SQLServer连接DBF
在SQLServer中执行
SELECT * into bmk
FROM OpenDataSource( 'Microsoft.Jet.OLEDB.4.0',
'Data Source="e:\share";User ID=Admin;Password=;Extended properties=dBase 5.0')...bmk
这样就可以把e:\share中的bmk.dbf表导入到Sqlserver中，
速度是最快的
把压箱底的方法都告诉大家
在Form上放一个ADOConnection,连结指向目标Access库
比如txt文件在c:\temp\aaaa.txt
就执行
ADOConnection.Connected := True;
ADOConnection.Execute('Select * Into abcd From [Text;Database=c:\temp].aaaa.txt');
就一切Ok了,这个方法一定行的，我好不容易才研究出来的
有了这两个例子，异构数据库之间导数据的问题就举一反三，迎刃而解了。

把Excel导入到Access中,同txt类似
select * into <table name> from [excel 8.0;database=<filename>].[<sheetname>$]

当然可以在程序中运行
SQL Server表导出和txt导入一个道理
基本上都是用Jet4.0 和 ODBC连结实现
先用用Jet4.0连结dbf,access,txt然后利用SQL语句导出导入
在此我就不详细说了，一句话，都可以的
列出连结串属性Extended properties,
DBase 5.0
DBASE IV
FoxPro 3.0
Paradox 7.X
Excel 8.0
text;HDR=YES;FMT=Delimited

Excel联接ADO串
Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\temp\a.xls;Extended
Properties="Excel 8.0;HDR=Yes;";Persist Security Info=False

SQLServer从Excel取数
SELECT *
FROM OpenDataSource('Microsoft.Jet.OLEDB.4.0','Data Source="C:\temp\b.xls";Extended Properties="Excel 5.0;HDR=Yes;";Persist Security Info=False')...sheet1$

txt导入SQLServer
select * from OpenRowset('MSDASQL', 'Driver={Microsoft Text Driver (*.txt; *.csv)};
DefaultDir=c:\temp;','select * from aaaa.txt')

c:\temp\aaa.dbf to Access,
ADOConnection 指向Access
语句是 select * into bbb from aaa in 'c:\temp' 'dbase 5.0;'

sql server导出到access
ADOConnection 指向Access
SELECT * into table FROM Tab1 IN [ODBC]
[ODBC;Driver=SQL Server;UID=sa;PWD=;Server=127.0.0.1;DataBase=Demo;]

sql server导出到excel和sql server导出到Access是一样的
ADOConnection 指向excel, 用Jet4.0 ,Extended properties设为Excel 8.0
SELECT * into table FROM Tab1 IN [ODBC]
[ODBC;Driver=SQL Server;UID=sa;PWD=;Server=127.0.0.1;DataBase=Demo;]


 'Could Not Allocate Space' 1105 Errors
SQL Server Agent - Creating an Alert for 'Could Not Allocate Space' 1105 Errors
By Alexzander Nepomnjashiy

This article is the third in my new series that focuses on alerts and errors within the SQL Server Agent subsystem. In this article I am going to describe how to create a specific alert for "Could Not Allocate Space" 1105 errors. I will be making reference to my previous article, which described creating custom alert procedures.

Let's begin. First, complete Steps 1 through 4 from my previous article, "SQL Server Agent: Creating Alert definition", making the following changes for step 4:

Start the SQL Server administrative console - Enterprise Manager.

Make a connection to a database server, drill down the SQL server objects to Alerts (your_database_server_name -> Management -> SQL Server Agent -> Alerts) and place a mouse cursor on it.

Select "New Alert..." in the context-dependent menu (accessed by right-clicking on the details pane).

In the "New Alert Properties - (your_database_server_name)" dialog window, define the following options:

On "General" bookmark:

Name = "Not enough free space for Data"
Type = "SQL Server event alert"
Error number = "1105"
Database name = "(All Databases)"
On "Response" bookmark:
Click "New operator..." button (we will discuss Operators in future articles)
In "New Operator Properties - (your_database_server_name)" dialog window, input DBA in the "Name" field and specify in the "Net send address" field the net send address of the newly created operator (in our case it should be the domain account name for the database administrator or his/her personal computer NetBIOS-name);
Test the notification functionality by pressing the "Test" button...shortly afterwards your database administrator should see a very strange message :-).
Press the "OK" button 2 times.

ATTENTION: The alert you have just created (i.e. an alert to notify you when free database space is less then 90%) will not fire unless the SQL Server Agent service has been started. The best practice is to always start SQL Server Agent with your database server. To ensure this, enable the "Auto-start service when OS starts" option for services SQLServerAgent in SQL Server Service Manager window (double click on the corresponding icon in the system tray).


Report Extended Properties Script

If you use SQL Server 2000's extended property features to document your database tables, this stored procedure can provide an easy way to extract the table and column-level descriptions for reporting purposes. This procedure can easily be modified to provide additional information to suit your needs. The script works only on SQL Server 2000.

Author: Kevin O'Quinn

IF EXISTS (select * from INFORMATION_SCHEMA.ROUTINES WHERE
ROUTINE_NAME='DataDictionaryTableReport' AND ROUTINE_TYPE='PROCEDURE')
DROP PROCEDURE DataDictionaryTableReport
GO

CREATE PROCEDURE DataDictionaryTableReport
@TABLE_NAME sysname = null,
@COLUMN_NAME sysname = null,
@TABLE_SCHEMA sysname = null
AS
BEGIN

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

Name:
-----
DataDictionaryTableReport

Purpose:
--------
If you use SQL Server 2000's extended property features to document your
database tables, this
stored procedure can provide an easy way to extract the table and
column-level descriptions for
reporting purposes. This procedure can easily be modified to provide
additional information to
suite your needs. Compile it to any SQL Server 2000 database.

Created By:
-----------
Kevin O'Quinn, 2002-04-19
kevin_oquinn@hotmail.com
Copyright ?2002. All rights reserved.

Tested on:
----------
SQL Server 2000; will not work on SQL Server 6/7

Background:
-----------
Using SQL Server 2000's "extended property" features, it is easy to document
your database objects
(i.e. create a data dictionary) directly inside the database itself.
Although this was available
in previous version of SQL Server, with SQL Server 2000 we now have far more
built-in support
for actually maintaining these comments. For example, you can simply open
up a table in Enterprise
Manager design mode, click on a column, and then enter your documentation
comments for the column
in the 'description' edit box in the bottom 'Columns' pane. Comments can be
made at the table-level
by clicking on the 'Table and Index Properties' button and then entering the
comments in the
'description' box on the Table tab.

You can use Query Analyzer's Object Browser to manage the extended
properties of any object (stored
procs, views, etc.) You can also enter descriptions via the underlying
system stored procedures
sp_addExtendedProperty, sp_updateExtendedProperty and
sp_dropExtendedProperty, and you can retrieve
this information via the system function fn_listExtendeProperty.

Additionally, it is worth noting that you can customize your extended
properties to maintain
multiple versions of your definitions. For example, you can maintain a
high-level business
description (i.e. end-user / management), as well as a low-level technical
description (i.e. for
programmers / DBAs). You can also use extended properties to store such
things as input masks,
formatting rules, captions, etc. However, this stored procedure assumes you
are simply maintaining
descriptions for a table and column-level data dictionary model using
Enterprise Manager's built-in
interface.

Reference Materials:
--------------------
In addition to referrring to the SQL Server Books Online, the following
articles provide good
information to help you understand how to use extended properties to
document your database:
http://msdn.microsoft.com/library/en-us/createdb/cm_8_des_08_7qb7.asp
http://msdn.microsoft.com/library/en-us/dnsqlpro02/html/sql02a10.asp?frame=true
http://www.devx.com/premier/mgznarch/vbpj/2001/05may01/sq0501/sq0501.asp
http://www.sqlmag.com/Articles/Index.cfm?ArticleID=22399
http://www.sql-server-performance.com/ac_undocumented_2000_system_tables.asp

Usage of this procedure:
------------------------
First, use your preferred method to enter the extended property descriptions
for your tables and
their columns. You can then use this stored procedure to produce a bulk
report of all your
tables, their columns, and the descriptions entered for each. You can also
call this procedure
to produce a report for a single table, or even a single column on a table.

For example, you can use this procedure to produce a full listing of each
table and its columns,
dump it into Excel, then use that as a fill-in-the-blank documentation
template. You could then
use a DTS package to pull the definitions out of the Excel spreadsheet and
put them back into
database's extended properties using the system functions.

Examples (using the PUBS database):
-------------------------------------------------------------
To see all tables and columns in the PUBS database:
EXEC DataDictionaryTableReport

To see all information on the Authors table:
EXEC DataDictionaryTableReport @TABLE_NAME='authors'

To see only information on the 'au_fname' column in the Authors table:
EXEC DataDictionaryTableReport @TABLE_NAME='authors',
@COLUMN_NAME='au_fname'

Modification Log:
-----------------
2002-04-22 KOQUINN: fixed bug where Views showed in result set; also added
@TABLE_SCHEMA argument
so that we can view users other than previously hard-coded 'dbo'

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

SET NOCOUNT ON

IF (@TABLE_SCHEMA IS NULL)
SET @TABLE_SCHEMA='dbo'

DECLARE @curTableName sysname -- cursor variable

-- create two table variables to store the result sets
-- Note: due to usage of the sql_variant data types, this will produce a
warning about row size
-- upon compilations. However, this can be ignored, as the condition
will never occur.

-- @defnBuildTbl stores all existing extended properties meeting the
criteria
DECLARE @defnBuildTbl TABLE (
tableName sysname NULL,
objtype sysname NULL,
objname sysname NULL,
[name] sysname NULL,
value sql_variant NULL)

-- @defnResultsTbl stores the end result set
DECLARE @defResultsTbl TABLE (
TABLE_NAME sysname NULL,
COLUMN_NAME sysname NULL,
ORDINAL_POSITION smallint NULL,
[Description] sql_variant NULL,
DATA_TYPE nvarchar(256),
CHARACTER_MAXIMUM_LENGTH int,
NUMERIC_PRECISION smallint,
NUMERIC_SCALE int,
IS_NULLABLE varchar(3) )

-- cursor through the system table list of tables;
-- if a tableName argument is passed in, only use that, otherwise look up
all tables
DECLARE cTables CURSOR FOR
SELECT TABLE_NAME
FROM INFORMATION_SCHEMA.TABLES -- preferable to use these views rather than
system tables
WHERE TABLE_SCHEMA=@TABLE_SCHEMA
AND TABLE_TYPE='BASE TABLE'
AND TABLE_NAME=COALESCE(@TABLE_NAME, TABLE_NAME)

OPEN cTables

FETCH NEXT FROM cTables
INTO @curTableName

WHILE @@FETCH_STATUS = 0
BEGIN

-- store listing of extended properties for this table, if any

INSERT @defnBuildTbl (tablename, objtype, objname, [name], value)
-- capture table comment, if any
SELECT @curTableName, *
FROM ::fn_listextendedproperty(default, 'user', @TABLE_SCHEMA, 'table',
@curTableName, default, default)
WHERE name='MS_Description' -- exclude 'caption' types
UNION
-- capture comments for any columns in the table
SELECT @curTableName, *
FROM ::fn_listextendedproperty (default, 'user', @TABLE_SCHEMA, 'table',
@curTableName, 'column', default)
WHERE name='MS_Description' -- exclude 'caption' types

FETCH NEXT FROM cTables
INTO @curTableName
END

CLOSE cTables
DEALLOCATE cTables

-- now compile the result set. We again select from the system tables to
get the
-- full list of tables and columns, then do an outer join against our
previous
-- result set. This is because we may not have comments in ever
table/column.
-- By doing this, we can generate a full template data dictionary that
includes
-- any comments we have entered, and blanks for any incomplete
documentation.

INSERT @defResultsTbl ( TABLE_NAME, COLUMN_NAME, ORDINAL_POSITION,
[Description], DATA_TYPE,
CHARACTER_MAXIMUM_LENGTH, NUMERIC_PRECISION, NUMERIC_SCALE, IS_NULLABLE )
SELECT t.TABLE_NAME,
t.COLUMN_NAME,
t.ORDINAL_POSITION,
[Description]=isNull(dt.value, dc.value),
t.DATA_TYPE,
t.CHARACTER_MAXIMUM_LENGTH,
t.NUMERIC_PRECISION,
t.NUMERIC_SCALE,
t.IS_NULLABLE
FROM ( -- generate a derived table that contains all tables and columns.
-- one row for each table-level entry...
SELECT
TABLE_NAME, COLUMN_NAME=NULL, DATA_TYPE=NULL,
CHARACTER_MAXIMUM_LENGTH=NULL, NUMERIC_PRECISION=NULL, NUMERIC_SCALE=NULL,
IS_NULLABLE=NULL, ORDINAL_POSITION=0
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA=@TABLE_SCHEMA
AND TABLE_TYPE='BASE TABLE'
UNION
-- ... and one row for each of the columns per table
SELECT c.TABLE_NAME, c.COLUMN_NAME, c.DATA_TYPE,
c.CHARACTER_MAXIMUM_LENGTH, c.NUMERIC_PRECISION, c.NUMERIC_SCALE,
c.IS_NULLABLE, c.ORDINAL_POSITION
FROM INFORMATION_SCHEMA.TABLES AS t
JOIN INFORMATION_SCHEMA.COLUMNS AS c ON
t.TABLE_CATALOG = c.TABLE_CATALOG
AND t.TABLE_SCHEMA = c.TABLE_SCHEMA
AND t.TABLE_NAME = c.TABLE_NAME
WHERE t.TABLE_SCHEMA=@TABLE_SCHEMA
AND t.TABLE_TYPE='BASE TABLE'
AND c.TABLE_SCHEMA=@TABLE_SCHEMA
AND c.COLUMN_NAME=COALESCE(@COLUMN_NAME, COLUMN_NAME) ) AS t
LEFT OUTER JOIN @defnBuildTbl AS dt ON -- any table-level comments, if any
dt.tableName = t.TABLE_NAME
AND dt.objName = t.TABLE_NAME
AND dt.objType = 'TABLE'
AND t.COLUMN_NAME IS NULL
LEFT OUTER JOIN @defnBuildTbl AS dc ON -- any column-level comments, if any
dc.tableName = t.TABLE_NAME
AND dc.ObjName = t.COLUMN_NAME
AND dc.objType = 'COLUMN'
WHERE t.TABLE_NAME=COALESCE(@TABLE_NAME, TABLE_NAME)

-- finally, return the result set; comment out any columns you don't
need/want
IF (@COLUMN_NAME IS NOT NULL) -- column-level
SELECT TABLE_NAME, COLUMN_NAME, ORDINAL_POSITION, [Description], DATA_TYPE,
CHARACTER_MAXIMUM_LENGTH, NUMERIC_PRECISION, NUMERIC_SCALE, IS_NULLABLE
FROM @defResultsTbl
WHERE COLUMN_NAME=@COLUMN_NAME

ELSE -- table-level
SELECT TABLE_NAME, COLUMN_NAME, ORDINAL_POSITION, [Description], DATA_TYPE,
CHARACTER_MAXIMUM_LENGTH, NUMERIC_PRECISION, NUMERIC_SCALE, IS_NULLABLE
FROM @defResultsTbl
ORDER BY TABLE_NAME, ORDINAL_POSITION

RETURN
End -- procedure
GO

GRANT ALL ON DataDictionaryTableReport TO public


Rename SQL Server Script
Rename SQL Server Script

This utility procedure will rename SQL Server to a given name, by default the WINS machine name. The script works on SQL Server 7.x and 2000.

Notes: 1. You will need to restart the SQL Server service in order for the new name to take effect. 2. Be careful using this procedure with named instances of SQL Server 2000.

Author: Yul Wasserman
Create proc uspRenameServer
@pNewName varchar(256)=null--If NULL we will attempt to rename server to the WINS machine name
/*
Purpose: renames SQL server.
Server: all
Database: DBAservice
Developed: Yul Wasserman 03/08/02
*/
AS
Declare @OldName varchar(256)
Declare @NewName varchar(256)
set @OldName=''
select @OldName=isnull(srvname,'') from master.dbo.sysservers where srvid=0
If @pNewName is NULL
Begin
create table #NName (NName varchar (256))
insert #NName exec master.dbo.xp_getnetname
select @NewName=Nname from #Nname
drop table #Nname
End
ELSE If @pNewName is not NULL
Begin
select @NewName=ltrim(rtrim(@pNewName))
End

If @OldName<>@NewName
BEGIN
IF @OldName <>''
BEGIN
print 'Attempting to drop server '+@OldName
Exec master.dbo.sp_dropserver @OldName
END
print 'Attempting to add server '+@NewName
Exec master.dbo.sp_addserver @NewName,'local'
END
If isnull(@@Servername,'')<>@NewName
Begin
Print 'Please shut down and restart SQL Server in order to complete renaming.'
End
Else If isnull(@@Servername,'')=@NewName
Begin
Print 'SQL Server is already named ' +@NewName
End


SQL Server 7.0数据库的六种数据移动方法
转贴自：2002-9-10 动网先锋

本人从事的工作是数据库管理员，要维护多台服务器中的数据库，经常把某台服务器中的某个数据库移动到另外一台服务器，对数据的移动有些心得体会，希望和大家共同交流。
1. 通过工具DTS的设计器进行导入或导出
DTS的设计器功能强大，支持多任务，也是可视化界面，容易操作，但知道的人一般不多，如果只是进行SQL Server数据库中部分表的移动，用这种方法最好，当然，也可以进行全部表的移动。在SQL Server Enterprise Manager中，展开服务器左边的+，选择数据库，右击，选择All tasks/Import Data...(或All tasks/Export Data...)，进入向导模式，按提示一步一步走就行了，里面分得很细，可以灵活的在不同数据源之间复制数据，很方便的。而且可以另存成DTS包，如果以后还有相同的复制任务，直接运行DTS包就行，省时省力。也可以直接打开DTS设计器，方法是展开服务器名称下面的Data Transformation Services，选Local Packages，在右边的窗口中右击，选New Package，就打开了DTS设计器。值得注意的是：如果源数据库要拷贝的表有外键，注意移动的顺序，有时要分批移动，否则外键主键，索引可能丢失，移动的时候选项旁边的提示说的很明白，或者一次性的复制到目标数据库中，再重新建立外键，主键，索引。
其实建立数据库时，建立外键，主键，索引的文件应该和建表文件分开，而且用的数据文件也分开，并分别放在不同的驱动器上，有利于数据库的优化。
2. 利用Bcp工具
这种工具虽然在SQL Server7的版本中不推荐使用，但许多数据库管理员仍很喜欢用它，尤其是用过SQL Server早期版本的人。Bcp有局限性，首先它的界面不是图形化的，其次它只是在SQL Server的表（视图）与文本文件之间进行复制，但它的优点是性能好，开销小，占用内存少，速度快。有兴趣的朋友可以查参考手册。
3. 利用备份和恢复
先对源数据库进行完全备份，备份到一个设备（device）上，然后把备份文件复制到目的服务器上（恢复的速度快），进行数据库的恢复操作，在恢复的数据库名中填上源数据库的名字（名字必须相同），选择强制型恢复（可以覆盖以前数据库的选项），在选择从设备中进行恢复，浏览时选中备份的文件就行了。这种方法可以完全恢复数据库，包括外键，主键，索引。
4. 直接拷贝数据文件
把数据库的数据文件（*.mdf）和日志文件（*.ldf）都拷贝到目的服务器，在SQL Server Query Analyzer中用语句进行恢复:
EXEC sp_attach_db @dbname = 'test',
@filename1 = 'd:\mssql7\data\test_data.mdf',
@filename2 = 'd:\mssql7\data\test_log.ldf'
这样就把test数据库附加到SQL Server中，可以照常使用。如果不想用原来的日志文件，可以用如下的命令：
EXEC sp_detach_db @dbname = 'test'
EXEC sp_attach_single_file_db @dbname = 'test',
@physname = 'd:\mssql7\data\test_data.mdf'
这个语句的作用是仅仅加载数据文件，日志文件可以由SQL Server数据库自动添加，但是原来的日志文件中记录的数据就丢失了。
5. 在应用程序中定制
可以在应用程序（PB、VB）中执行自己编写的程序，也可以在Query Analyzer中执行，这种方法比较灵活，其实是利用一个平台连接到数据库，在平台中用的主要时SQL语句，这种方法对数据库的影响小，但是如果用到远程链接服务器，要求网络之间的传输性能好，一般有两种语句：
1> select ... into new_tablename where ...
2> insert (into) old_tablename select ... from ... where ...
区别是前者把数据插入一个新表（先建立表，再插入数据），后者是把数据插入已经存在的一个表中，我个人喜欢后者，因为在编程的结构上，应用的范围上，第二条语句强于前者。
6. SQL Server的复制功能
SQL Server提供了强大的数据复制功能，也是最不易掌握的，具体应用请参考相关资料，值得注意的是要想成功进行数据的复制工作，有些条件是必不可少的：
1>SQL Server Agent必须启动，MSDTC必须启动。
2>所有要复制的表必须有主键。
3>如果表中有text或image数据类型，必须使用with log选项，不能使用with no_log选项。
另外max text repl size选项控制可以复制的文本和图像数据的最大规模，超过这个限制的操作将失败。
4>在要进行复制的计算机上，应该至少是隐含共享，即共享名是C$或D$…。
5>为SQL Server代理使用的Windows NT帐号不能是一个本地的系统帐号，因为本地的系统帐号不允许网络存取。
6>如果参与复制的服务器在另外的计算机域中，必须在这些域之间建立信任关系。


View Database Information
View Database Information Script

This stored procedure is designed to view database information in an active database -- i.e. database, tables, objects, users, jobs, etc. The script basically acts as a 'wrap-around' and enables one procedure to be used to view a wealth of database information. Tested on SQL Server 2000.

Accepts viewtype parameters of 'database', 'tables', 'objects', 'jobs', 'users' and 'alerts'. Type the database name and the parameter type, then execute the procedure.

Example:

exec usp_dbinfo '[database name]','[parameter type]'

Author: Lauryn Bradley
-- if sp exists, drop it
IF EXISTS ( SELECT *
FROM sysobjects
WHERE id = object_id(N'[dbo].[usp_DBinfo]')
and OBJECTPROPERTY(id, N'IsProcedure') = 1)

DROP PROCEDURE [dbo].[usp_dbinfo]
GO

SET QUOTED_IDENTIFIER OFF SET ANSI_NULLS ON
GO

-- create procedure usp_dbinfo with 2 input parameters
CREATE PROCEDURE usp_dbinfo @DBname sysname = null, @VType varchar(25)
AS
DECLARE
@TName varchar(50), -- Table name
@FName varchar(50), -- Field name
@Len varchar(30), -- Field length
@DType varchar(15), -- Data type
@Cmd varchar(30), -- Command
@Id varchar (20), -- Table object id
@Nulls varchar (20) -- Allows nulls

SET NOCOUNT ON

-- see if database exists
IF not exists ( SELECT name
FROM master.dbo.sysdatabases
WHERE (name = @DBname))
BEGIN

-- if not, raise error
RAISERROR(15010,-1,-1, @DBname)
RETURN (1)
END

-- print exec msg
PRINT 'Executing: usp_dbinfo for ' + UPPER(@DBname)+ ' ... '
PRINT ''

-- to view server jobs
IF @VType = 'jobs'
BEGIN
SELECT @Cmd = 'USE msdb exec sp_help_job'
EXEC (@Cmd)
RETURN (1)
END

-- to view server alerts
IF @VType = 'alerts'
BEGIN
SELECT @Cmd = 'USE msdb exec sp_help_alert'
EXEC (@Cmd)
RETURN (1)
END

-- to view database properties & space allocation info
IF @VType = 'database'
BEGIN
DBCC CHECKALLOC WITH ALL_ERRORMSGS
EXEC sp_helpdb @DBname
RETURN (1)
END

-- to view database objects
IF @VType = 'objects'
BEGIN
EXEC sp_help
RETURN (1)
END

-- to view database users
IF @VType = 'users'
BEGIN
EXEC sp_helpuser
RETURN (1)
END

-- to view database tables & info
IF @VType = 'tables'
BEGIN
PRINT '** ALL TABLE INFORMATION **'
END

-- declare main cursor to get first user table name from sysobjects
DECLARE TabNameCur CURSOR FOR

SELECT so.name
FROM sysobjects so
WHERE so.xtype = 'u'
ORDER BY name

-- open cursor and fetch table info data
OPEN TabNameCur FETCH TabNameCur INTO @TName

-- while fetch status is 0, fetch table info
WHILE @@FETCH_STATUS = 0
BEGIN

-- display space used for table
EXEC sp_spaceused @TName

PRINT '------------------- ' + @TName + ' -------------------------------'

-- declare cursor to get table info
DECLARE TableInfoCur CURSOR FOR

-- get all table information (linked to main cursor by @TabName)
SELECT DISTINCT sc.name, sc.length, st.name, sc.id, st.allownulls
FROM sysobjects so
INNER JOIN syscolumns sc ON so.id = sc.id
INNER JOIN systypes st ON sc.xtype = st.xtype

WHERE (so.xtype = 'U')
and st.name <> 'sysname'
and so.name = @TName

-- open tableinfo nested cursor to get table info
OPEN TableInfoCur

-- print item headers
PRINT ''
PRINT '' + CAST('NAME' as char(30)) + '' + CAST('OBJ ID' as char(7)) + ' ' +
CAST('NULLS?' as char(6)) + ' ' + CAST('TYPE' as char(10)) + ' ' + CAST('LEN' as char(3))
PRINT ''

-- fetch items into nested cursor
FETCH TableInfoCur INTO @FName, @Len , @DType, @Id, @Nulls

-- if fetch status less than 0, there are no user tables
IF @@FETCH_STATUS < 0
PRINT 'No User Tables'

-- while fetch status = 0, print table info
WHILE @@FETCH_STATUS = 0
BEGIN

-- print data
PRINT '' + CAST (@FName as char(30)) + CAST (@Id as char(6)) + ' ' + CAST (@Nulls as char(8))
+ '' + CAST (@DType as char(10)) + ' (' + rtrim(@Len)+ ') '

-- fetch next row of table data
FETCH NEXT FROM TableInfoCur
INTO @FName, @Len , @DType, @Id, @Nulls
END

-- close & deallocate nested cursor
CLOSE TableInfoCur DEALLOCATE TableInfoCur

-- get next database name from main cursor
FETCH NEXT FROM TabNameCur INTO @TName
END

-- close & deallocate main cursor
CLOSE TabNameCur DEALLOCATE TabNameCur

-- Author: Lauryn Bradley 02/02/02
-- Shows various info for active database
-- Accepts viewtype parameter of either:
-- 'database', 'tables', 'users', 'objects', 'alerts' or 'jobs'

-- EXEC usp_dbinfo 'database_name', 'viewtype_name'
GO


Inside SQL Server Maintenance Plans

by Andrzej Kozlowski

Like a lot of database administrators who manage many SQL Server databases, I have problems with maintenance and administration tasks. Some of these are massive jobs, which have to be created and maintained to be sure that databases are robust and reliable. One of the tools I use often to make my life a little easier is the Database Maintenance Wizard, which is used to create SQL Server maintenance plans.

While the Database Maintenance Wizard, and the plans it creates are generally good, they are not always perfect. In addition, what is going on behind the scenes when a maintenance plan is running is not so obvious. So what I recently did was to find out what was going on behind the scene using the SQL Server Profiler.

To capture the Profiler trace (using SQL Server 2000), I used the predefined Profiler template called SQLProfilerStandard, with two modifications, using:

  • RPC: Starting
  • SQL: BatchStarting

instead of:

  • RPC: Completed
  • SQL: BatchCompleted

All the other trace configuration items were left the same. Those two changes to the SQLProfilerStandard template allow us easily to capture the relevant trace data.

Profiling Integrity Maintenance Plan Options

Next, I created a Maintenance Plan using the following Integrity options (see figure as a reference point) in order to see what SQL Server statements were used.

Then, I started the Profiler trace and ran the job created by the Maintenance Plan to see what the results would be. I then went back and changed some of the settings in the above screen to see what happened as options were changed.

Below are the results, formatted into a table for easy viewing.

Which Integrity Settings Were Used | Profiler Trace Results | Xp_Sqlmaint Syntax
Check database integrity, include indexes, attempt to repair any minor problems | dbcc checkdb(Pubs, REPAIR_FAST) WITH NO_INFOMSGS | -CkDBRepair (not documented)
Check database integrity, include indexes | Use pubs dbcc checkdb WITH NO_INFOMSGS |
Check database integrity, exclude indexes | dbcc checkdb(Pubs, noindex) WITH NO_INFOMSGS | -CkDBNoIdx
Perform the above tests before backing up the database or transaction log |  | -BkUpOnlyIfClean (One of above CkDB depending on chosen settings)

Comments on this test run:

  1. Don抰 use the option "Attempt to repair any minor problems" which causes DBCC Checkdb to be run with the option REPAIR_FAST. This option performs minor repair actions without risk of data loss. However, the given database must be in single-user mode to use this value, which presents problems if this is a production database.
  2. If database is not in single-user mode, DBCC Checkdb with REPAIR_FAST, will not be run, unfortunately the text or html report files will still report a success.
  3. By checking the option "Perform these tests before backing up the database or transaction log," SQL server modifies the backup jobs (full and log backup) by adding two option to the respective maintenance plans:

    ?BkUpOnlyIfClean
    ?CkDB | CkDBNoIdx | CkDBRepair
  4. Don抰 use the above option if you are going to add transaction log backups to the same maintenance plan, as this will cause DBCC activity before each incremental backup, hurting performance
  5. "WITH NO_INFOMSGS" is used to suppress all informational messages

Profiling Optimization Maintenance Plan Options

In this step, I wanted to see happened under the covers when various Optimization options (see illustration below) from the Database Maintenance Wizard are traced with Profiler. As before, I will experiments with several different Optimization options, and run the resulting jobs manually in order to capture the traces.

Below are the results, formatted into a table for easy viewing.

Which Optimization Settings Were Used | Profiler Trace Results | Xp_Sqlmaint Syntax
Reorganize data and index pages, reorganize pages with the original amount of free space | dbcc dbreindex(dbo.Table1,'', 0, sorted_data_reorg) (for each table in database) | -RebldIdx 100
Reorganize data and index pages, change free space per page percentage to 30% | dbcc dbreindex(dbo.Table1', '', 70, sorted_data_reorg) (for each table in database) | -RebldIdx 30
Update the statistics used by the query optimizer, percentage of database to sample 30% | UPDATE STATISTICS dbo.table1 WITH all, SAMPLE 30 PERCENT (for each table in database) | -UpdOptiStats 30
Remove unused space from database files | DBCC shrinkdatabase(test, 10, TRUNCATEONLY ) | -RmUnusedSpace 2048 10

Comments on this test run:

  1. DBCC DBREINDEX uses free space as the inverse of the fill factor. So in the first row in the above table, free space = 0%, which is the same as a fill factor of 0. In second position we can see a value of 30% for free space, which is the same as a 70% fill factor.

  2. If you choose DBREINDEX for a database, an UPDATE STATISTIC is done automatically

  3. Even though the UPDATE STATISTIC command gives us the possibility to choose either a SAMPLE option of PERCENT or number of ROWS, the maintenance plan allows you only to specify PERCENT.

Profiling Other Maintenance Plan Options

I will not describe the database and log backup options of the Database Wizard because they are very straight-forward, and work like you would expect them to. However, I would like you to discuss some other maintenance plan options which are accessible only from xp_sqlmaint utility and not from the Maintenance Plan Wizard:

  • HtmlRpt "e:\mssql7\LOG\ak_test.html": Creates an HTML version of the database maintenance report.

  • DelHtmlRpt <time period>: Sets the retention cycle for HTML files, e.g. 3 WEEKS.

  • CkCat: Runs DBCC CHECKCATALOG.

  • CkAl, CkAlNoIdx: Runs DBCC NEWALLOC.

  • CkTxtAl: Runs DBCC TEXTALL.

Comments on These Commands:

  1. Be careful with the syntax of the above switches. They must be exactly as I listed above. For example if you type Htmlrpt (instead of HtmlRpt) the job will fail.

  2. Don抰 use NEWALLOC and TEXTALL. They are included for backward compatibility. CHECKALLOC replaced NEWALLOC and CHECKTABLE TEXTALL. It抯 better to use CHECKDB, which performs CHECKTABLE for each table in a database to check the integrity of the data, and CHECKALLOC to check allocation of all pages.

Maintenance Plan Tables

Now for some details on how maintenance plans are stored inside SQL Server. The more you know about this, the easier it will be for you to automate the gathering of useful information about your maintenance plans, if you so desire.

There are four tables in the msdb database which are store maintenance plan information:

  • sysdbmaintplan_databases: Contains one row for each database. Note that you can use special strings for group of databases like in the Database Maintenance Plan Wizard.

Sample Data From sysdbmaintplan_databases

plan_id | database_name
36D539DF-7DE3-11D6-9855-00508BB3C376 | WWW_ak
36D539E1-7DE3-11D6-9855-00508BB3C376 | All Databases
36D539E2-7DE3-11D6-9855-00508BB3C376 | All System Databases
36D539E3-7DE3-11D6-9855-00508BB3C376 | All User Databases

  • sysdbmaintplans: Contains one row for each maintenance plan.

Sample Data From sysdbmaintplans (partial table)

Plan_id | Plan_name | date _created | owner | max _history _rows
00000000-0000-0000-0000-000000000000 | All ad-hoc plans | 11/13/98 3:10 | REDMOND\_sqlbld | 0
36D539DF-7DE3-11D6-9855-00508BB3C376 | ak_test_www_ak | 11/14/98 3:10 | NT\TEST | 1000
36D539E1-7DE3-11D6-9855-00508BB3C376 | ak_test_all_db | 11/15/98 3:10 | NT\TEST | 1000
36D539E2-7DE3-11D6-9855-00508BB3C376 | ak_test_sys_db | 11/16/98 3:10 | NT\TEST | 1000
36D539E3-7DE3-11D6-9855-00508BB3C376 | ak_test_user_db | 11/17/98 | 3:10 NT\TEST | 1000

  • sysdbmaintplan_history: Maintain the history information for each maintenance plan.

  • sysdbmaintplan_jobs: Maintains the relationship between the maintenance plan id and the job id.

Comments on These System Tables:

Notice than there is one predefined maintenance plan named: "All ad-hoc plans" with a strange owner: "REDMOND\_sqlbld". It is used for all ad-hoc jobs using the xp_sqlmaint utility. The max_history_rows for this plan is set to 0, however there are history entries in the sysdbmaintplan_history table for it.

To demonstrate how this "special" entries works, I created a custom job with following command:

EXECUTE master.dbo.xp_sqlmaint N'-D WWW_ak -Rpt "e:\mssql7\LOG\www_ak.txt" -WriteHistory -CkDB '

After running it twice, here's what the history table looks like:

plan_id | plan _name | database _name | server _name | activity | duration
00000000-0000-0000-0000-000000000000 | All ad-hoc plans | WWW_ak | test | Check Data and Index Linkage | 77
00000000-0000-0000-0000-000000000000 | All ad-hoc plans | WWW_ak | test | Check Data and Index Linkage | 77

Creating a Custom Maintenance Plan

As you can see, the SQL Server Database Maintenance Plan Wizard doesn抰 allow us to include all the potentially useful options (HTML report, some DBCC stuff). As workaround, I propose that you follow one these two options when creating your own maintenance plan:

  1. Create each new maintenance plan using the wizard, then modify each job manually for those options not available directly from the wizard.

  2. Avoid the wizard completely, and create all jobs manually using the xp_sqlmaint utility.

I recommend the following strategy for SQL Server maintenance jobs:

  1. Create jobs (ad-hoc or using the wizard) for full backups of your databases with DBCC CHECKDB and CHECKCATALOG, and set up the option to perform the backup only if they didn抰 report any errors. Create this job for all databases which don抰 demand transaction log backups.

  2. Create separate jobs for only transaction log backups for all databases which need it.

  3. Create separate job(s) for data optimizations, depending on your demands.

  4. For all of your jobs, add the HTML report option and direct it to a share point for easy access with a browser.

All information in this article was provided to help you simplify the management of our SQL Server environment. Please send all comments and questions to: akozlowski@amadeus.net

Published with the express written permission of the author. Copyright 2002.


Print Table Hierarchy
Script to Print Table Hierarchy

This script will print tables according to their FK dependencies, making it helpful in determining the hierarchy of tables in a database.

Author: Uri Margalit
set nocount on

declare @Rows int,
@LevelID int
declare @Tables TABLE (
TableName varchar(200),
LevelID int)

insert into @Tables (TableName, LevelID)
select name, 1
from sysobjects
where xtype= 'U'

select @Rows = @@RowCount, @LevelID = 1

while @Rows > 1 begin
update @Tables
set LevelID = LevelID + 1
where TableName in (select distinct master.name
from
sysobjects master,

sysobjects ref,

sysreferences refkey
where
refkey.fkeyid = ref.id
and
refkey.rkeyid = master.id
and
ref.Name in (select distinct TableName from @Tables where LevelID =
@LevelID)
)
select @Rows = @@RowCount
select @LevelID = @LevelID + 1
end

select *
from @Tables
order by 2 desc,1


View Old Connections
Script to View Old Connections

This procedure accepts an integer parameter (as # of days) and will show all active SQL connections older than the number of days specified. The script has been tested in SQL Server 2000.

Example:

exec sp_connections '3'

Author: Lauryn Bradley

IF EXISTS (SELECT name
FROM sysobjects
WHERE name = N'sp_connections'
AND type = 'P')

DROP PROCEDURE sp_connections
GO

CREATE PROCEDURE sp_connections @days int AS
SELECT
DATEDIFF(day, last_batch, getdate()) AS Days,
sp.spid,
sp.nt_username as NTname,
sp.loginame,
sp.hostname,
sp.program_name,
sd.name as DBName,
sp.login_time,
sp.last_batch,
sp.status,
sp.dbid,
sp.cmd,
sp.lastwaittype

FROM sysdatabases sd RIGHT OUTER JOIN
sysprocesses sp ON sd.dbid = sp.dbid
WHERE DATEDIFF(day, last_batch, getdate())>= @days
and sd.name is not null

-- Lauryn Bradley 11/01/02
-- Gets active database connections older than number of days
-- specified. Accepts integer input parameter for number of days.

-- exec sp_connections '5'

GO


Database User Permissions
Database User Permissions Script

This stored procedure for SQL Server 7.0 and 2000 will return a user's permissions for a database object. It contains two null-able parameters: @user_name and @object_name. The parameter @user_name will accept a SQL Role name or a User name. The parameter @object_name will accept any type of database object name. The result set will return the types of permissions a user has on an object in the database. If the @user_name parameter contains a role name, it will also return the members of that role. If the @user_name parameter contains a user name, it will also return the roles that the user is a member of. Leaving one or more of the parameters null will return various types of results, experiment a bit and see if you find it useful.

Author: Jason Bohanon

SET QUOTED_IDENTIFIER ON
GO
SET ANSI_NULLS ON
GO

if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[sp_get_object_permissions]') and OBJECTPROPERTY(id, N'IsProcedure') = 1)
drop procedure [dbo].[sp_get_object_permissions]
GO

CREATE PROCEDURE sp_get_object_permissions
@user_name nvarchar(128) = null, /***User Name to check, if null then all users***/
@object_name nvarchar(128) = null /***Object Name to check, if null then all objects***/
AS

/****CREATED BY JASON BOHANON.
FOR QUESTIONS AND COMMENTS PLEASE CONTACT ME AT bohanon@qwest.net. ****/

SELECT
sobj.name as 'Object Name',
CASE WHEN sobj.xtype = 'U'
THEN 'USER TABLE'
WHEN sobj.xtype = 'P'
THEN 'STORED PROCEDURE'
WHEN sobj.xtype = 'V'
THEN 'VIEW'
WHEN sobj.xtype = 'FN'
THEN 'SCALAR FUNCTION'
WHEN sobj.xtype = 'TF'
THEN 'TABLE FUNCTION'
WHEN sobj.xtype = 'S'
THEN 'SYSTEM TABLE'
ELSE 'UNKNOWN'
END as 'Object Type',
susr.name as 'User',
CASE WHEN spro.action = 193
THEN 'SELECT'
WHEN spro.action = 195
THEN 'INSERT'
WHEN spro.action = 196
THEN 'DELETE'
WHEN spro.action = 197
THEN 'UPDATE'
WHEN spro.action = 224
THEN 'EXECUTE'
WHEN spro.action = 26
THEN 'REFERENCES'
ELSE 'UNKNOWN TYPE: ' + CAST(spro.action as nvarchar(128))
END as 'Permission Type'
FROM
sysprotects spro
JOIN
sysusers susr
on spro.uid = susr.uid
JOIN
sysobjects sobj
on sobj.id = spro.id
WHERE
susr.name like
CASE
WHEN @user_name IS NOT NULL
THEN @user_name
ELSE
'%'
END
AND
sobj.name like
CASE
WHEN @object_name IS NOT NULL
THEN @object_name
ELSE
'%'
END
ORDER BY
sobj.xtype,
susr.name,
sobj.name,
spro.action

/***IF SUPPLIED @user_name IS A ROLE, OR A USER THAT IS PART OF A ROLE***/
IF @user_name IS NOT NULL and @user_name <> 'public'
BEGIN---CHECK TO SEE IF @user_name IS A ROLE
IF (SELECT
issqlrole
FROM
sysusers
WHERE
name = @user_name) > 0

SELECT
susr2.name as 'Role Users'
FROM
sysusers susr1
JOIN
sysmembers smbr
on smbr.groupuid = susr1.uid
JOIN
sysusers susr2
on smbr.memberuid = susr2.uid
WHERE
susr1.name = @user_name
ELSE
SELECT
susr1.name as 'User a Member In Role'
FROM
sysusers susr1
JOIN
sysmembers smbr
on smbr.groupuid = susr1.uid
JOIN
sysusers susr2
on smbr.memberuid = susr2.uid
WHERE
susr2.name = @user_name
END ---END ROLE CHECK

GO
SET QUOTED_IDENTIFIER OFF
GO
SET ANSI_NULLS ON
GO


find_foreign_keys
find_foreign_keys.sql

In a heavily normalized database, it is often difficult to modify/drop tables when lots of foreign keys exist. This script will list all of the foreign keys in the database that reference a given table name. It will also generate scripts to drop the foreign keys to make dropping the parent table easier. Supply the parent table name in the @table_name parameter, then set the @generate_drop_scripts bit to 1 if you wish to PRINT the scripts to drop the constraints.

I hope you find this script useful -- it has helped me significantly cut down the time required to search the database for dependencies related to foreign key constraints.

Author: Jason Bohanon
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_NULLS ON
GO

/****** Object: Stored Procedure dbo.sp_FIND_FOREIGN_KEYS Script Date: 10/17/2001 3:17:55 PM ******/
if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[sp_FIND_FOREIGN_KEYS]') and OBJECTPROPERTY(id, N'IsProcedure') = 1)
drop procedure [dbo].[sp_FIND_FOREIGN_KEYS]
GO

CREATE PROCEDURE sp_FIND_FOREIGN_KEYS
@table_name nvarchar(128), /***Table to check for dependent constraints***/
@generate_drop_scripts bit = 0 /***SET to 1 to PRINT scripts***/
AS

/****CREATED BY JASON BOHANON****/
/****QUESTIONS, PLEASE EMAIL bohanon@qwest.net****/

SET NOCOUNT ON
DECLARE @alter_table1 nvarchar(4000),
@key_name nvarchar(128),
@child_table nvarchar(128)

SELECT
@table_name as 'Referenced Table',
f.name as 'Referenced Column',
c.name as 'Tables That Reference',
e.name as 'Referencing Column',
d.name as 'Foreign_Key_Name'
FROM sysobjects a
JOIN sysforeignkeys b
on a.id = b.rkeyid
JOIN sysobjects c
on c.id = b.fkeyid
JOIN sysobjects d
on d.id = b.constid
JOIN syscolumns f
on f.id = a.id
AND b.rkey = f.colid
JOIN syscolumns e
on c.id = e.id
AND b.fkey = e.colid
WHERE a.name = @table_name
ORDER BY c.name

IF @generate_drop_scripts = 1
BEGIN /***CREATE CONSTRAINT DROP SCRIPTS***/

CREATE TABLE #tmp_drop_keys
(key_name nvarchar(128), child_table nvarchar(128), used bit)

INSERT INTO #tmp_drop_keys
(key_name, child_table, used)
SELECT d.name ,c.name , 0
FROM sysobjects a
JOIN sysforeignkeys b
on a.id = b.rkeyid
JOIN sysobjects c
on c.id = b.fkeyid
JOIN sysobjects d
on d.id = b.constid
WHERE a.name = @table_name

WHILE EXISTS (SELECT * FROM #tmp_drop_keys WHERE used = 0)
BEGIN

SELECT TOP 1 @key_name = key_name, @child_table = child_table
FROM #tmp_drop_keys
WHERE used = 0

SELECT @alter_table1 = ' ALTER TABLE ' + @child_table +
' DROP CONSTRAINT ' + @key_name
PRINT ''
PRINT '------ALTERING TABLE ' + UPPER(@child_table) + '------'
PRINT @alter_table1
PRINT ''

UPDATE #tmp_drop_keys
SET used = 1
WHERE key_name = @key_name

END

END /***END CREATE CONSTRAINT DROP SCRIPTS***/

GO

SET QUOTED_IDENTIFIER OFF
GO
SET ANSI_NULLS ON
GO


sp_chowner
sp_chowner.sql

This is a stored procedure that can be run in any database to change all tables and views so that they're owned by dbo. It can be called like "exec sp_change_table_owner_to_dbo" and it will change all views and table to be owned by dbo or "exec sp_change_table_owner_to_dbo name" and it will change all views and tables owned by "name" to be owned by dbo.

style="font-family:Arial, Helvetica, serif; font-size: small;" The script needs to be compiled in the master database under a dbo account. It should work in SQL Server 7 and up.

Author: David Satz

SET QUOTED_IDENTIFIER OFF
GO
SET ANSI_NULLS ON
GO

if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[sp_change_table_owner_to_dbo]') and OBJECTPROPERTY(id, N'IsProcedure') = 1)
drop procedure [dbo].[sp_change_table_owner_to_dbo]
GO

Create Procedure dbo.sp_change_table_owner_to_dbo
@SpecificUser sysname = NULL
, @Debug char(1) = NULL
AS
DECLARE @sql VARCHAR(4000)

/*
Programmer : David Satz
Description: change all tables and views to be owned by dbo
Parameters : @SpecificUser - if you only want to change 1 users tables and views
Note: this only works in the current database
*/
SET NOCOUNT ON
SET XACT_ABORT OFF

CREATE TABLE #tables
( TABLE_SCHEMA sysname
, TABLE_NAME sysname)

SET @sql = "INSERT #tables SELECT TABLE_SCHEMA, TABLE_NAME " +
" FROM " + db_name() + ".INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA <> 'dbo'
AND TABLE_SCHEMA <> 'INFORMATION_SCHEMA'"

IF @SpecificUser IS NOT NULL
BEGIN
SET @sql = @sql + " AND TABLE_SCHEMA = '" + @SpecificUser + "'"
END

EXEC( @sql )

DECLARE curTables CURSOR
FOR
SELECT "exec sp_changeobjectowner '" + TABLE_SCHEMA + "." + TABLE_NAME + "', dbo"
FROM #tables
ORDER BY TABLE_NAME

OPEN curTables
FETCH curTables INTO @sql
WHILE @@FETCH_STATUS = 0
BEGIN
IF IsNull( @Debug, "N" ) = "Y"
SELECT @sql
ELSE
EXEC (@sql)

FETCH curTables INTO @sql
END

CLOSE curTables
DEALLOCATE curTables
DROP TABLE #tables
SET XACT_ABORT ON
GO

SET QUOTED_IDENTIFIER OFF
GO
SET ANSI_NULLS ON
GO

if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[sp_change_table_owner_to_dbo]') and OBJECTPROPERTY(id, N'IsProcedure') = 1)
GRANT EXEC ON dbo.sp_change_table_owner_to_dbo TO PUBLIC
GO


vsysShowPermissions
vsysShowPermissions.sql

This code presents you with a nice list of object permissions for every object in a database.

It seems to work fine under SQL 7.0, but I haven't yet researched those system tables for any differences as I'm about 3:1 6.5 vs 7.0.

Author: Wayne West

/* Microsoft SQL Server - Scripting */
/* Server: SQLSERVER */
/* Database: TADS */
/* Creation Date 8/29/00 10:11:42 AM */

/****** Object: View dbo.vsysShowPermissions Script Date: 8/29/00 10:11:42 AM ******/
if exists (select * from sysobjects where id = object_id('dbo.vsysShowPermissions') and sysstat & 0xf = 2)
drop view dbo.vsysShowPermissions
GO

/****** Object: View dbo.vsysShowPermissions Script Date: 8/29/00 10:11:42 AM ******/
Create view vsysShowPermissions
as
select ObjName = convert(char(30), o.name),
ObjType =
CASE o.type
when "C" then "Constraint"
when "D" then "Default"
when "F" then "Foreign Key"
when "K" then "Primary Key"
when "L" then "Log"
when "P" then "Procedure"
when "R" then "Rule"
when "RF" then "Replication SP"
when "S" then "SYSTEM TABLE"
when "TR" then "Trigger"
when "U" then "Table"
when "V" then "View"
when "X" then "Extended SP"
ELSE "*BAD OBJECT TYPE"
END,
UserName = convert(char(20),
case
when p.id is null then " (no users)"
when u.uid = u.gid then lower(u.name)
else upper(u.name)
end),
Permission = convert(char(20),
(CASE p.action
when 26 then "References"
when 193 then "Select"
when 195 then "Insert"
when 196 then "Delete"
when 197 then "Update"
when 198 then "Create Table"
when 203 then "Create Database"
when 207 then "Create View"
when 222 then "Create Procedure"
when 224 then "Execute"
when 228 then "Dump Database"
when 233 then "Create Default"
when 235 then "Dump Transaction"
when 236 then "Create Rule"
ELSE " (no permissions)"
END
+
CASE p.protecttype
when 205 then "/Grant"
when 206 then "/REVOKE"
ELSE ""
END))
-- , p.id, p.uid, p.protecttype, p.action
from sysobjects o
left join sysprotects p on o.id = p.id
left join sysusers u on p.uid = u.uid

GO


Return Table Counts Quickly
Return Table Counts Quickly

Returns rowcounts for all tables in the current database, or if @tablename is provided, for that table only. Note : this procedure makes use of sysindexes, so make sure all indexes are up-to-date

Author: Paul Silveira
if exists (select * from sysobjects where id = object_id('dbo.ps_rowcount') and sysstat & 0xf = 4)
drop procedure dbo.ps_rowcount
GO

create procedure ps_rowcount
@NamesOrCount char(1) = NULL

as
/**************************************************************************************************
description: returns rowcounts for all tables in the current database,
or if @tablename is provided, for that table only.

modified by: Paul Silveira psilveira@lojack.com

caveat: this procedure makes use of sysindexes,
so make sure all indexes are up-to-date

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

declare @dbname varchar(32)

set nocount on

IF UPPER(@NamesOrCount) = 'C'
BEGIN
SELECT o.name,
i.rows
FROM sysobjects o
INNER JOIN sysindexes i
ON (o.id = i.id)
WHERE o.type = 'u'
AND i.indid < 2
ORDER BY i.rows desc, o.name
END
ELSE IF UPPER(@NamesOrCount) = 'N'
BEGIN
SELECT o.name,
i.rows
FROM sysobjects o
INNER JOIN sysindexes i
ON (o.id = i.id)
WHERE o.type = 'u'
AND i.indid < 2
ORDER BY o.name, i.rows
END
ELSE
BEGIN
PRINT "No parameter was entered... C or N "
PRINT "Therefore the output will be the default, or Column Count"

SELECT o.name,
i.rows
FROM sysobjects o
INNER JOIN sysindexes i
ON (o.id = i.id)
WHERE o.type = 'u'

AND i.indid < 2
ORDER BY i.rows desc, o.name

END
GO


Get all Table Sizes
Get all Table Sizes

These script creates two procedures (usp_GetTableSizes and sp_SpaceUsed2) in the user database. The first procedure Usp_GetTableSizes reports each user table in the database along with it's data size and the number of data rows it has. Usp_GetTableSizes uses the second store procedure sp_spaceused2 to collect metadata. Sp_spaceused2 is a slightly modified version of the system stored procedure sp_spaceused. It returns tablenames that are longer than 20 characters. The sp_spaceused system stored procedure currently shows only the first 20 characters of a table name.

Author: Neil Mederich

-- Run this script in your user database
-- There are two procedure create statements in this script. The first one reports the size
-- of each user table in the current database. The second procedure provides support for
-- table names that are longer than 20 characters (a limitation of system stored procedure sp_spaceused)
-- It will support tablenames of 50 characters or less, which should be sufficient in most cases

if exists(select * from sysobjects where id=object_id('usp_gettablesizes') and objectproperty(id,'isprocedure')=1)
drop proc usp_gettablesizes
go

create procedure usp_GetTableSizes @sortby varchar(5)='rows',@updateusage varchar(5)='False'
as
/*
** Date: August 17, 2000
** Author: Neil Mederich
** Purpose: Report row counts and data size of user tables
** Input Parameter: @sortby = column to sort results by ('rows' or 'data' or 'name')
** Input Parameter: @updateusage = update statistics ('true' or 'false')
*/
set nocount on

declare @dependency char(1)
if exists(select * from sysobjects where id=object_id('sp_spaceused2') and objectproperty(id,'isprocedure')=1)
set @dependency='T'
else
set @dependency='F'

if @updateusage='True'
Begin
DBCC UPDATEUSAGE (0) with NO_INFOMSGS
End

create table #sdtt ([name] nvarchar(50),[rows] int,[reserved] varchar(18),
[data] varchar(18),[index_size] varchar(18),[unused] varchar(18))

select convert(varchar(50),a.name) as tablename into #sdt
from sysobjects a
where a.type='u' and objectproperty(a.id,'istable')=1

declare @x varchar(50), @y varchar(255)
declare cOne cursor for select distinct tablename from #sdt order by tablename
open cOne
fetch next from cOne into @x
While @@fetch_status=0
begin
if @dependency = 'T'
set @y = 'exec sp_spaceused2 @objname=' + @x
else
set @y = 'exec sp_spaceused @objname=' + @x

set @y = 'insert #sdtt ' + @y
execute(@y)
fetch next from cOne into @x
end
close cOne
deallocate cOne

declare @z varchar(255)
if @sortby='name'
set @z = 'select * from #sdtt order by ' + @sortby + ' asc'
else
set @z = 'select * from #sdtt order by ' + @sortby + ' desc'

exec (@z)

go

if exists(select * from sysobjects where id=object_id('sp_spaceused2') and objectproperty(id,'isprocedure')=1)
drop proc sp_spaceused2
go
CREATE procedure sp_spaceused2
/*
** The Original version (sp_spaceused) truncates table name at 20 characters.
** Sp_spaceused2 is a slightly modified version that allows the usp_GetTableSizes stored procedure to
** report on tables that have a name up to 50 characters in length.
** If all your user table names are 20 characters or less you can get by without adding this procedure.

*/
@objname nvarchar(776) = null, -- The object we want size on.
@updateusage varchar(5) = false -- Param. for specifying that
-- usage info. should be updated.
as

declare @id int -- The object id of @objname.
declare @type character(2) -- The object type.
declare @pages int -- Working variable for size calc.
declare @dbname sysname
declare @dbsize dec(15,0)
declare @bytesperpage dec(15,0)
declare @pagesperMB dec(15,0)

/*Create temp tables before any DML to ensure dynamic
** We need to create a temp table to do the calculation.
** reserved: sum(reserved) where indid in (0, 1, 255)
** data: sum(dpages) where indid < 2 + sum(used) where indid = 255 (text)
** indexp: sum(used) where indid in (0, 1, 255) - data
** unused: sum(reserved) - sum(used) where indid in (0, 1, 255)
*/
create table #spt_space
(
rows int null,
reserved dec(15) null,
data dec(15) null,
indexp dec(15) null,
unused dec(15) null
)

/*
** Check to see if user wants usages updated.
*/

if @updateusage is not null
begin
select @updateusage=lower(@updateusage)

if @updateusage not in ('true','false')
begin
raiserror(15143,-1,-1,@updateusage)
return(1)
end
end
/*
** Check to see that the objname is local.
*/
if @objname IS NOT NULL
begin

select @dbname = parsename(@objname, 3)

if @dbname is not null and @dbname <> db_name()
begin
raiserror(15250,-1,-1)
return (1)
end

if @dbname is null
select @dbname = db_name()

/*
** Try to find the object.
*/
select @id = null
select @id = id, @type = xtype
from sysobjects
where id = object_id(@objname)

/*
** Does the object exist?
*/
if @id is null
begin
raiserror(15009,-1,-1,@objname,@dbname)
return (1)
end

if not exists (select * from sysindexes
where @id = id and indid < 2)

if @type in ('P ','D ','R ','TR','C ','RF') --data stored in sysprocedures
begin
raiserror(15234,-1,-1)
return (1)
end
else if @type = 'V ' -- View => no physical data storage.
begin
raiserror(15235,-1,-1)
return (1)
end
else if @type in ('PK','UQ') -- no physical data storage. --?!?! too many similar messages
begin
raiserror(15064,-1,-1)
return (1)
end
else if @type = 'F ' -- FK => no physical data storage.
begin
raiserror(15275,-1,-1)
return (1)
end
end

/*
** Update usages if user specified to do so.
*/

if @updateusage = 'true'
begin
if @objname is null
dbcc updateusage(0) with no_infomsgs
else
dbcc updateusage(0,@objname) with no_infomsgs
print ' '
end

set nocount on

/*
** If @id is null, then we want summary data.
*/
/* Space used calculated in the following way
** @dbsize = Pages used
** @bytesperpage = d.low (where d = master.dbo.spt_values) is
** the # of bytes per page when d.type = 'E' and
** d.number = 1.
** Size = @dbsize * d.low / (1048576 (OR 1 MB))
*/
if @id is null
begin
select @dbsize = sum(convert(dec(15),size))
from dbo.sysfiles

select @bytesperpage = low
from master.dbo.spt_values
where number = 1
and type = 'E'
select @pagesperMB = 1048576 / @bytesperpage

select database_name = db_name(),
database_size =
ltrim(str(@dbsize / @pagesperMB,15,2) + ' MB'),
'unallocated space' =
ltrim(str((@dbsize -
(select sum(convert(dec(15),reserved))
from sysindexes
where indid in (0, 1, 255)
)) / @pagesperMB,15,2)+ ' MB')

print ' '
/*
** Now calculate the summary data.
** reserved: sum(reserved) where indid in (0, 1, 255)
*/
insert into #spt_space (reserved)
select sum(convert(dec(15),reserved))
from sysindexes
where indid in (0, 1, 255)

/*
** data: sum(dpages) where indid < 2
** + sum(used) where indid = 255 (text)
*/
select @pages = sum(convert(dec(15),dpages))
from sysindexes
where indid < 2
select @pages = @pages + isnull(sum(convert(dec(15),used)), 0)
from sysindexes
where indid = 255
update #spt_space
set data = @pages

/* index: sum(used) where indid in (0, 1, 255) - data */
update #spt_space
set indexp = (select sum(convert(dec(15),used))
from sysindexes
where indid in (0, 1, 255))
- data

/* unused: sum(reserved) - sum(used) where indid in (0, 1, 255) */
update #spt_space
set unused = reserved
- (select sum(convert(dec(15),used))
from sysindexes
where indid in (0, 1, 255))

select reserved = ltrim(str(reserved * d.low / 1024.,15,0) +
' ' + 'KB'),
data = ltrim(str(data * d.low / 1024.,15,0) +
' ' + 'KB'),
index_size = ltrim(str(indexp * d.low / 1024.,15,0) +
' ' + 'KB'),
unused = ltrim(str(unused * d.low / 1024.,15,0) +
' ' + 'KB')
from #spt_space, master.dbo.spt_values d
where d.number = 1
and d.type = 'E'
end

/*
** We want a particular object.
*/
else
begin
/*
** Now calculate the summary data.
** reserved: sum(reserved) where indid in (0, 1, 255)
*/
insert into #spt_space (reserved)
select sum(reserved)
from sysindexes
where indid in (0, 1, 255)
and id = @id

/*
** data: sum(dpages) where indid < 2
** + sum(used) where indid = 255 (text)
*/
select @pages = sum(dpages)
from sysindexes
where indid < 2
and id = @id
select @pages = @pages + isnull(sum(used), 0)
from sysindexes
where indid = 255
and id = @id
update #spt_space
set data = @pages

/* index: sum(used) where indid in (0, 1, 255) - data */
update #spt_space
set indexp = (select sum(used)
from sysindexes
where indid in (0, 1, 255)
and id = @id)
- data

/* unused: sum(reserved) - sum(used) where indid in (0, 1, 255) */
update #spt_space
set unused = reserved
- (select sum(used)
from sysindexes
where indid in (0, 1, 255)
and id = @id)
update #spt_space
set rows = i.rows
from sysindexes i
where i.indid < 2
and i.id = @id

-- Line below is the line that was changed to allow table names up to 50 characters
select name = substring(object_name(@id), 1, 50),
rows = convert(char(11), rows),
reserved = ltrim(str(reserved * d.low / 1024.,15,0) +
' ' + 'KB'),
data = ltrim(str(data * d.low / 1024.,15,0) +
' ' + 'KB'),
index_size = ltrim(str(indexp * d.low / 1024.,15,0) +
' ' + 'KB'),
unused = ltrim(str(unused * d.low / 1024.,15,0) +
' ' + 'KB')
from #spt_space, master.dbo.spt_values d
where d.number = 1
and d.type = 'E'
end

return (0)


Renaming the Logical Filename
Renaming the Logical Filename

This stored procedure was created to allow one to change logical file names of datbase files in a 7.0 database. After doing a restore with the move option, one can change the physical file path and physcal file name, but not the logical name. This posed a problem because some have internal standards for database names and for logical and physical file names.

Author: Joe Toscano

/************************************************************************************************************************
* sp_rename_logical_filename *
* *
* This stored procedure was created to allow one to change logical file names of datbase files in a 7.0 database. *
* After doing a restore with the move option, one can change the physical file path and physcal file name, but not *
* the logical name. This posed a problem because we (GMAC) have internal standards for database names and for *
* logical and physical file names. *
* *
* This stored procedure does change the configuration paramater 'allow updates' so it can directly update the *
* sysfiles1 table in the database in question. I thought it was very interesting that I was actually able to make *
* this change from within a storeded procedure. By the way, I've never run this on a production server ... yet. *
* *
* Usage: use database in question *
* go *
* exec sp_rename_logical_filename current_logical_name, new_name *
* *
* You will have to execute this stored procedure for every logical name you wish to change in the database. It was *
* interesting that even with 'allow updates' set, I could not update the sysfiles table ... so I settled for *
* updating sysfiles1 instead. From within the database, I used sp_helpfile and sp_helpdb to verify the changes. *
* *
* I'm looking for ideas to improve this stored procedure ... this is just a first run. Please feel free to *
* contact me with any ideas or constructive criticism. You could go nuts and add checks for all of the db *
* options and maybe only allow this if the db is in single user mode ? What if the db is marked suspect ? To be *
* more official, we could have more raiserrors and less prints. *
* *
* Original Author: Joe Toscano 6/1/2000 (jtoscano@ix.netcom.com) *
* Revision History: *
* *
************************************************************************************************************************/

if exists (select 1 from sysobjects where name = 'sp_rename_logical_filename' and type = 'P')
drop proc sp_rename_logical_filename
go

create proc sp_rename_logical_filename
@current_name varchar(64)= null,
@new_name varchar(64)= null
as

declare @msg varchar(200),
@cmd varchar(240),
@err int

set nocount on

if @current_name is null or @new_name is null
begin
select @msg = 'Usage: exec sp_rename_logical_filename ' + char(39)
+ 'current_name' + char(39) + ',' + char(39) + 'new_name' + char(39)
print @msg
print ''
print '(While in the database in question)'
return (1)
end

if db_name() ='master'
begin
print 'You must be in the database that contains the logical filenames you wish to modify ... not in master'
return (1)
end

/*
** Only the SA can execute sp_configure to change config parameters
*/
if (not is_srvrolemember('serveradmin') = 1)
begin
print 'In order to execute this stored procedure, you must be permitted to directly update system tables'
raiserror(15125,-1,-1)
return (1)
end
/*
* We are not going to let you be in a transaction ... at least for now.
*/

if @@trancount > 0
begin
raiserror(15002,-1,-1, 'sp_rename_logical_filename')
print 'Please perform a commit or rollback and then execute this stored procedure'
return(1)
end

/*
* Let's make sure the logical file name you specify exists
*/

if not exists (select 1 from sysfiles1 where name = @current_name)
begin
select @msg = 'Logical File Name: ' + @current_name + ', not found in ' + db_name() + '. Please check the name'
print @msg
return (1)
end

/*
* Change the option to allow updates to system tables ... this is a server wide setting ... that's a bit scarry!
*/

select @cmd = 'exec sp_configure '+ char(39) + 'allow updates' + char(39) + ', 1' + char(13)
exec (@cmd)

select @cmd = 'reconfigure WITH override'
exec (@cmd)

select @err = 0

update sysfiles1
set name = @new_name
where name = @current_name

if @@error <> 0
begin
print 'sp_rename_logical_filename failed: Unable to update sysfiles1 table with new logical name'
select @err = 1
end

/*
* Regardless of the status of our updates ... change the option to allow updates to system tables back to 0
*/

select @cmd = 'exec sp_configure '+ char(39) + 'allow updates' + char(39) + ', 0' + char(13)
exec (@cmd)

select @cmd = 'reconfigure WITH override'
exec (@cmd)

select @msg = 'sp_rename_logical_filename: Changed filename: ' + @current_name + ' to : ' + @new_name
print @msg

set nocount off

return @err

grant exec on sp_rename_logical_filename to public


SQL Server 体系结构的工作原理(soul)
数据库体系结构：存储引擎
2002-03-25 15:30:00· Cathan Cook· RealSKy

    摘要：本文深入探讨了 SQL Server 体系结构的工作原理。其中介绍了数据库引擎的增强功能及其使用技巧，并提供了相关信息的链接。深入了解 SQL Server 的引擎有助于数据库管理员（数据库系统工程师）在设计、构建或改进数据库系统时充分利用 SQL Server 的优势。虽然本文主要面向数据库专业人士，但也可用于教学或营销目的。

    一. 简介

    本文描述 Microsoft? SQL Server? 2000 中新增的存储引擎功能，并提供相关的使用技巧，同时探讨存储引擎的工作原理。初步掌握存储引擎的工作原理可使您最大限度地发挥 SQL Server 的性能。

    当今，人们的注意力集中在应用程序的高可扩展性方面。数据库的设计和实施周期不断缩短，而且由于开发需求的不断变更以及产品使用的增长，又使得数据库在不断地演变发展。为满足可扩展性、可用性和易于使用等方面的要求，需要有一种具有应变能力和灵活性的数据存储引擎。

    SQL Server 2000 的不同版本可支持不同规模的系统，其范围从用于 Pocket PC 的小型移动系统到运行在群集 Windows? 2000 Datacenter Server 上容量达数 TB 的高可用性事务处理或决策支持系统。所有这些系统都满足使命关键的业务系统所要求的灵活性、安全性和可靠性要求。

    由于存储引擎操作的智能化和自动化，因而可以在各种用途和规模的项目中部署 SQL Server 2000 应用程序。高度完善的体系结构改善了性能、可用性和可伸缩性。

    可用性

    由于在与物理文件进行交互时采用了新的算法，可靠性和并发能力得到了增强。这些算法减轻了日常维护工作，使您不必再运行数据库控制台命令 (DBCC)。然而，DBCC 仍旧可以使用，并且新增的 DBCC CHECK 命令的运行不会干扰联机处理工作。

    可扩展性

    存储子系统（由物理数据库文件及其在磁盘上的布局组成）既支持小型数据库，也支持超大型数据库。SQL Server 当前可支持最高达 64 GB 物理内存 (RAM) 和 32 颗处理器。

    易于使用

    增强的管理功能可帮助数据库管理员 (DBA) 实现服务器的自动管理和集中管理。这也使得对远程服务器和应用程序的维护变得容易，DBA 无需访问每个站点。由复杂算法管理的服务器配置可动态对服务器使用方案的变化作出反应，从而使 DBA 可以将精力集中在数据库管理和优化等任务上。

    二. 存储引擎的增强功能

    SQL Server 2000 的关系数据库服务器包括两个主要部分：关系引擎和存储引擎。两个引擎独立工作，它们通过本地数据访问组件（如 OLE DB）进行交互。关系引擎提供访问存储引擎的接口，而存储引擎由与基本数据库存储组件和功能进行交互的服务构成。

    存储引擎的主要任务包括：
    提供改善管理存储组件易用性的功能
    管理数据缓冲和对物理文件的所有 I/O 操作
    控制并发、管理事务、锁定和日志记录
    管理用于存储数据的文件和物理页
    恢复系统故障

    SQL Server 2000 中的存储引擎提供概念简单而实际操作比较灵活的新增功能，同时减少了详细规划容量和优化性能的工作。SQL Server 2000 可以对其环境作出反应，并准确而快速地适应数据库使用上的变化。这种技术上的突破已将数据库管理的重点转移到作为服务的数据简化上。SQL Server 2000 DBA 可以将注意力集中到设计一个可对数据流和数据使用作出响应的系统上，而不再需要将时间浪费在优化个别参数上。

2003-6-7 19:21:00
查看评语???

2003-6-7 19:23:01 　　SQL Server 2000 中的变化建立在体系结构的增强上。这种增强是在 SQL Server 7.0 中引入的，其目的是为后来的改进和创新提供基础。存储引擎小组的主要目的是减少花在定期优化服务器上的时间和精力。由于绝大多数的调优参数设置是基于数据库使用的，所以引擎使用自适应算法动态调整这些设置，使其适合数据库环境。调优参数现在可以按这种方式自动调整，而在早期版本中，它们需要不断调整和测试。您仍可以手动调整调优功能，但 SQL Server 2000 可以完成更多的工作。只有很少的 SQL Server 客户才需要对调优参数进行调整；这种调整工作需要进行仔细的测试，并且需要经验丰富的数据库管理员进行监督。

    下表总结了 SQL Server 2000 存储引擎的主要增强功能。本文的后面将对这些内容进行详细阐述。

功能 描述及益处
应用程序锁定管理器 如果需要控制对应用程序定义的资源（如表单）的并发访问，新增的存储过程允许您使用 SQL Server 的应用程序锁定管理器锁定这些资源。
数据库控制台命令 (DBCC) DBCC CHECK 命令可以在联机处理过程中运行，且不会中断更新。新增的功能允许校验物理页的一致性，以检测硬件引起的错误。在 SQL Server 2000 企业版中，DBCC 可以在多个处理器上以并行方式运行。
数据库选项 所有的数据库选项都可使用 ALTER DATABASE 进行修改。此功能简化了管理工作。
差异备份 在 SQL Server 2000 中，由于改进后的功能可以在更广的层次上跟踪数据库的更改，差异备份的速度更快。
动态调优 通过使用动态自适应算法，服务器可以自动调整以前是静态不变的配置设置。管理控制仍可用于管理系统范围的资源，但以后您不必使用它们。手动设置参数可以在它们的约束边界内动态调整。
行内文本 在包含较小且使用频繁的文本列的表中，较小的文本值可以与标准数据行存储在同一页中，而不必存储在文本值页中。如果表中包含这种被频繁访问的文本数据，此功能可减少大量磁盘 I/O 操作。
并行建立索引 在企业版中，索引建立过程自动使用为并行处理配置的所有处理器，减少了建立索引所需的时间；例如，在一台八处理器的服务器中，时间缩短到原来的六分之一。索引建立过程还可利用内存和 tempdb 中的可用资源。
预读索引 读取索引的功能得到增强，提高了扫描索引的性能。
重组索引 对 DBCC SHOWCONTIG 进行的改进提供了有关索引碎片的详细信息。新增的 DBCC 命令 INDEXDEFRAG 可联机重组索引页，且不会中断数据库服务，也不会导致数据库一致性或故障恢复方面的问题。
降序排列索引中的键列 索引中的各个键列可单独指定为升序或降序。
KILL 命令 此命令现在报告完成的进度。如果此命令正在等待另一个进程（例如回滚），则可以查看命令执行的进度。改进后的命令可以用于停止 Microsoft 分布式事务协调器 (MS DTC) 事务，而这些事务并不与特定会话相关联。
对高内存量的支持 Windows 2000 中的技术改进了使用大量内存的企业版系统的性能。通过使用 Windows 2000 的 AWE 扩展，SQL Server 2000 可至多支持 64 GB 物理内存 (RAM)。
锁定 改进后的锁定管理器可探测到其它资源（如线程和内存）的死锁情况。并发能力的改善同时也降低了死锁的发生，从而进一步加强了 SQL Server 2000 的可扩展性。
逻辑日志标记 Transact-SQL 命令可在日志中创建书签，使数据库可以恢复到书签所示的时点。此功能还可同步恢复用于同一应用程序的多个数据库。
联机索引重组 对 DBCC SHOWCONTIG 进行的改进提供了有关索引碎片的详细信息。新增的 DBCC 命令 INDEXDEFRAG 可联机重组索引页，且不会中断数据库服务，也不会导致数据库一致性或故障恢复方面的问题。
优化的预读 I/O 操作 对于扫描所涉及的每个文件，SQL Server 2000 都会同时发出多个连续的、预读读取操作。为提高性能，查询优化器在扫描表和索引时使用连续的预读 I/O 操作。
密码保护备份 可使用密码保护备份媒体和单独的备份。这样可以防止未授权的用户恢复备份并访问数据库。
故障恢复模式 通过使用故障恢复模式，可以选择数据库的日志记录级别。这样事务日志管理更加灵活。故障恢复模式可联机更改，以适应一天当中不断变化的数据库使用。
共享表扫描 在企业版中，对某个表的多次扫描可以利用其他进行中的对该表的扫描，减少了对磁盘的实际 I/O 操作。
收缩日志 收缩日志命令可在更多的情况下立即运行。即使不能立即收缩日志，SQL Server 也会提供建议性的反馈，说明在继续或完成收缩操作之前必需完成的操作。
快照备份 对第三方供应商提供的快照备份的支持进一步得到加强。快照备份采用存储技术，可以在几秒内备份或恢复整个数据库。如今，可以将这些备份与常规事务日志及差异备份相结合，为 OLTP 数据库提供完整的保护。此功能对于中型或大型数据库是非常有益的，因为在这种环境中可用性是非常重要的。
节省空间的空表和索引 SQL Server 2000 不为空表和索引分配磁盘页。SQL Server 7.0 会给空表和索引分配多达三个磁盘页。
前 n项排序 此新功能可优化前 n 项值的检索（例如，SELECT TOP 5 * FROM tablename）。
Xlock SQL Server 2000 提供这种新的 Transact-SQL 锁定提示。它可用于明确调用互斥的、事务级别的页或表锁。

    SQL Server 2000 中增加了许多功能，这些功能使数据交互更为有效，使管理更加灵活。以下部分将详细介绍这些增强功能，并提供相关的使用技巧。

点击查看用户来源及管理<br>发贴IP：61.48.16.230 2003-6-15 9:54:46
编辑这个贴子 鲜花(0) 鸡蛋(0)
ugvanxk 帅哥哟，在线，有人找我吗？

等级：版主
文章：262
积分：682
注册：2002-11-2
搜索ugvanxk在『数 据 库』的所有贴子 第2楼
发贴心情
三. 与数据进行交互

    在 SQL Server 2000 中，存储引擎的功能得到增强，在与数据进行交互时可提供更好的可扩展性及性能。了解这些增强的功能有助于更有效地使用 SQL Server。

    无论是通过用户界面还是自动执行的任务，数据交换都从查询开始。数据请求先被传递到关系引擎，然后关系引擎与存储引擎进行交互以获取数据，并将其传递给用户。无论是从用户还是 DBA 的角度来看，存储和关系引擎的功能是无法区分的。

    更有效地读取数据

    数据通过一系列事务在服务器和用户之间传递。应用程序或用户启动任务后，数据库将其传递给查询处理器进行处理，然后返回最终结果。查询处理器通过接收、解释和执行 SQL 语句来完成任务。

    例如，当用户会话发出 SELECT 语句时，将会执行以下步骤：

    关系引擎将语句进行编译和优化后，将其纳入执行计划（获取数据所需的一系列步骤）。然后，关系引擎运行执行计划。执行步骤包括通过存储引擎访问表和索引。

    关系引擎解释执行计划，调用存储引擎以收集所需的信息。

    关系引擎将存储引擎返回的所有数据组合到最终的结果集中，然后将结果集返回给用户。

    为提高此过程的性能，进行了两项改进。在 SQL Server 2000 中，关系引擎将核准查询谓词的工作交由存储引擎完成，这样在该过程中这些谓词能尽早得到处理，因而提高了存储和关系引擎之间数据交换的效率。此项改进使核准查询的效率显著提高。

    增强的 Top n 功能

    另一项改进是存储引擎处理从结果集中选择前 n 个记录的方式。在 SQL Server 2000 中，新的 Top n 引擎将分析类似以下语句的最佳操作路径：
SELECT top 5 * from orders order by date_ordered desc
在本例中，如果必须搜索整个表，则引擎会分析数据并只跟踪高速缓存中的前 n 项数值。这种方式将大幅提高上述 SELECT 语句的性能，因为只有前 n 项值需要排序，而非整个表。

    共享扫描

    在 SQL Server 2000 企业版中，两个或多个查询可共享正在进行的表扫描，此项功能可改善大型 SQL Server 2000 数据库的性能。例如，当查询使用无序扫描查询一个很大的表时，高速缓存中的页面将被清空，以便为流入数据腾出空间。如果另一个查询已经开始，对同一表的第二次扫描就会使磁盘 I/O 再次检索这些页面。在频繁进行表扫描的环境中，当两个查询搜索相同的数据页时，这将导致磁盘颠簸。

图 1：共享扫描效果

    优化的进程可减少由此类数据访问模式造成的磁盘 I/O 操作。对表的第一个无序扫描将从磁盘中读取数据；后续的对同一表的无序扫描不必再读取硬盘，而只需使用已在内存中的信息。参见图 1。在对同一个表同时进行多个扫描操作时，此同步过程可将性能提高至多八倍。此项改进的效果在大型决策支持查询中更加明显，因为整个表的大小远远大于高速缓存的大小）。

    当查询没有更有效的执行计划时，存储引擎将使用共享扫描功能协助查询。此功能的目的是提高频繁读取大型表的性能。当查询处理器确定最佳执行计划中包含表扫描时，将调用此功能。然而，尽管可以使用查询或索引优化强制进行共享扫描时，但强制进行表扫描并不会提高性能。此时使用状态良好的索引完成同样的工作效果不会差，而且可能会更好。

    并发

    为了在多个用户进行数据交互的同时维护事务的一致性，存储引擎会锁定资源以管理行、页、键、键范围、索引、表和数据库的依存性。通过在更改资源时将其锁定，引擎可防止多个用户同时更改同一数据。SQL Server 中的锁可在不同粒度级别上动态应用，以选择事务所需的限制最小的锁。

    在 SQL Server 2000 中，并发方面的改进进一步减少了死锁，避免了对资源不必要的锁定。例如，增强的锁定管理器可了解被竞用的其它资源（如线程和内存）。这种新的功能可帮助数据库管理员确定更广范围内的设计或硬件限制。

2003-6-7 19:30:13 锁定管理器中新增的 Transact-SQL 接口支持在编程代码中使用自定义的锁定逻辑。业务逻辑所需的锁可通过在 Transact-SQL 批处理中调用 sp_getapplock 命令来创建，这将允许您指定要锁定的应用程序定义的资源（例如锁定表单，而非数据行）、要使用的锁定模式、超时值以及锁的范围是事务还是会话。当使用新的应用程序锁管理器创建锁后，它们接受 SQL Server 的常规锁管理，如同它们是由存储引擎创建的一样，因此，不必担心当调用事务终止时，应用程序创建的锁仍处于打开状态。

    在 SQL Server 2000 中，用于获取锁的进程将考虑页中的数据是否都已提交。例如，若要对某个表运行 SELECT 语句，而该表中的数据在最近未发生变化（如 pubs 数据库中的表），则该进程不会产生锁，因为最近没有活动事务对表进行过更新。存储引擎是通过将数据页上的日志序列号与当前活动事务相比较来实现上述功能的。如果数据库中的绝大多数数据都早于最早的活动事务，则对于这样的数据库，这一功能将显著减少锁定操作，从而使性能大幅提高。

    在使用锁保护事务中数据的同时，另一个进程 latching 控制对物理页的访问。闩锁是一种非常轻型的、短期同步对象，它保护事务生存期内不需要锁定的操作。当存储引擎扫描某页时，它先锁住该页，读取行，将行返回给关系引擎，然后再解除对页面的锁定，以使其它进程可以访问同一数据。存储引擎使用称为 lazy latching 的进程优化对数据页的访问，即只在另一个活动进程请求某页时，才释放对该页的锁存。如果没有活动进程请求同一数据页，则在对该页的整个操作过程中，单个闩锁将始终有效。

    为改进系统的并发性能，应将精力集中在数据库系统的设计以及与其相关的代码对象上。SQL Server 2000 支持 TB 级的数据存储，其扩展能力可线性增长，且不受限制。数据库管理员的任务是管理数据库生命周期，即所有数据库组件（从代码到磁盘上的数据存储）的设计和优化周期，以确保设计始终满足服务级协议的要求。

    四. 表和索引

    在物理数据结构方面也进行了改进，提高了设计和维护的灵活性。

    随着表或索引的增长，SQL Server 以八个为一组分配新的数据页；这些数据页称为扩展。虽然 text、ntext 或 image 类型的列可存储在不同的页中，但一行数据不能超出一页，所以它只能拥有 8 KB 数据。拥有聚集索引的表按键的顺序存储在磁盘上。堆是不带聚集索引的表，它们是无序的。记录按插入的顺序存储。

    SQL Server 2000 支持索引视图，在其它数据库产品中常常称为实体视图。在某个视图上创建聚集索引时，该视图将从派生对象转为存储在数据库中的基本对象，并且其结构与带有聚集索引的表相同。索引视图可用于存储预先计算的值或复杂联接的结果，但前提是维护开销不能超过性能上的收益。在 SQL Server 2000 企业版中，只要索引视图可以优化查询计划，查询处理器就会自动使用它。对于很少更改但又经常作为复杂联接或计算查询组成部分的数据，索引视图可改善查询速度。

    行内文本

    行内文本可用于在主页面中存储小文本数据。例如，如果某个表中有一文本列，但文本值通常小到可与行中的其余内容放在同一普通页中，则可以在文本列中设置阈值。阈值用来确定可存储在主页面而非单独的文本页上的最大文本长度。如果大多数数据可放在主页面上，而只有小部分数据比较大，需要创建文本页，采取这种做法就可使性能获得提高。

    若要确定在何种情况下使用此新功能，则需要权衡存储密度（或每个数据页上存储的行数）以及 I/O 性能的改善。例如，某个表中的文本列用于存放注释。该列中有 20% 的文本值较长，而其它 80% 的文本值都小于 100 个字节。对于这种情形，似乎可以采用行内文本解决方案；但是，只有在这样的列中的数据被频繁访问时才应考虑使用行内文本。如果用户频繁访问此表，但只在进行特殊搜索时才查看此注释列，则使用行内文本未必是最好的做法。由于每页存储的行数少，所以存储密度会降低；并且由于表包含更多的页，所以表扫描响应时间也会增加。所以，实现行内文本的最好情况是，存在需要频繁访问的文本列，并且该列的许多值都小于 8 K，可以存储在行中。

    新增数据类型

    SQL Server 2000 引入了三种新的数据类型。bigint 是 8 字节整数类型。sql_variant 可以存储不同数据类型的数据值。第三种数据类型 table 可用于优化性能。Table 变量使 tempdb 的使用效率更高，并且比临时表更加快速。与其它变量一样，它们的作用范围是声明它们的批处理。table 变量的功能类似于临时表，但其性能要高于临时表或游标，并且可更加有效地利用服务器资源。通常，在创建与数据库交互的代码时，一定要考虑利用服务器上可用资源的最佳方法。

    索引

    通过使用索引，可以优化对数据的访问。因为是否建立索引取决于使用情况，所以不正确的索引是造成数据库缓慢最主要的原因。标准的索引维护工作应该包括周期性校验当前索引方案以及通过适当增删索引使其适应当前的系统使用情况。

    SQL Server 2000 中的几个新增功能使索引维护更加有效，索引管理更加容易。这些增强功能减少了磁盘 I/O 操作，从而增加了索引扫描的性能。在范围扫描可以使用辅助索引时，这一功能尤其有用。

2003-6-7 19:31:33 建立索引

    建立索引时，存储引擎对行进行采样，并计算使用服务器资源建立索引的最佳方法。通过使用选项，可以控制建立索引的方式，因而可选择控制系统资源的分配方式。可以使用这些选项，并结合您在特定数据库系统方面的知识，平衡对于整体系统的性能是至关重要的进程中的资源，从而使建立索引的操作对事务处理的影响最小。

资源 命令 选项 说明
内存 sp_configure（高级）
index create memory
指定建立索引操作可以使用的内存总量。
TempDB create index
sort_in_tempdb
从 tempdb 中分配在索引建立期间用于排序的磁盘空间。如果 tempdb 在单独的磁盘上，此命令会产生更高的 I/O 带宽；并且如果数据库所在空间不是非常连续，该命令还可以使索引页的布局在物理上更加连续。
CPU sp_configure（高级）
最大并行程度
限制在并行操作中（服务器范围）可使用的处理器(CPU) 的个数。

    大型系统的另一个可扩展性功能是并行索引建立。SQL Server 2000 企业版具有此功能。在发出单个 CREATE INDEX 语句时，此过程将被自动调用。存储引擎计算数据的要求，然后创建单独的线程，每个线程建立一段索引。

图 2：并行索引优化

    索引建立也可以使用共享表扫描，从而使这一过程进一步优化。

    整理索引碎片

    SQL Server 2000 支持联机索引重组，相比于以前的版本，这是一个非常大的进步。联机索引重组对事务的吞吐量影响非常小，并且可随时停止并重新启动，而不会影响其运行效果。索引重组操作按较小增量进行，并且可完全恢复。

    随着在表中插入、删除和更新信息，聚集和非聚集索引页最终将变得零碎，从而降低对数据的范围查询的效率。因此，定期整理索引碎片是非常有益的。可以使用 DBCC SHOWCONTIG（该命令在 SQL Server 2000 中已有所改进）分析并报告碎片。

    如果确定索引已变为碎片，就可以使用 DBCC INDEXDEFRAG 命令对其进行重组。该命令以逻辑键的顺序记录页，同时压缩可用空间，移动已建立的扩展中的行以满足填充因子设置。通过提高页面中内容的密度以减少数据扫描时读取的页数，从而提高读取性能。如果索引经常得到维护并且其分布不是完全散碎的，那么运行 DBCC INDEXDEFRAG 对联机性能的影响要远远小于重建索引。

    DBCC INDEXDEFRAG 是众多长期运行的管理操作中的一个，它们内部都使用短小的事务。这些短小的事务可最大限度提高服务器中的并发操作，允许操作停止而不影响工作，并且这些事务被全部记录以便在发生故障时进行恢复。

点击查看用户来源及管理<br>发贴IP：61.48.16.230 2003-6-15 9:55:32
编辑这个贴子
ugvanxk 帅哥哟，在线，有人找我吗？

等级：版主
文章：262
积分：682
注册：2002-11-2
搜索ugvanxk在『数 据 库』的所有贴子 第3楼
发贴心情
五. 日志记录和故障恢复

    事务日志是一个记录流，它记录了从数据库创建到当前时点对数据库所做的更改。每个记录的操作都创建一个日志记录。日志记录由事务生成，并在事务提交时写入磁盘。相反，被事务修改的数据页不会立即写入磁盘，而是先保留在 SQL Server 的缓冲区高速缓存中，稍后再写入磁盘。推迟将数据写入磁盘可最大限度地提高对数据页进行多路访问的性能，并避免中断扫描。在提交时强制将日志写入磁盘是为了确保在服务器关机时不会丢失已完成的工作。

    故障恢复可确保在将数据库变为联机状态之前保持其在事务上的一致性。如果数据库在事务上是一致的，则所有提交的工作都已生效，而任何未提交的工作都变为无效。日志总是定义数据库的正确视图。简而言之，故障恢复就是将数据与事务日志在某一给定时点保持一致的过程。

    当 SQL Server 启动时，当数据库被连接时，或在从备份恢复数据库的最后一步时，故障恢复将自动执行。在 SQL Server 启动时执行的故障恢复称为重新启动故障恢复或启动故障恢复。使用备份进行故障恢复通常是由于磁盘发生故障。此类故障恢复称为媒体故障恢复。

    重新启动故障恢复是自动进行的，通常可恢复到最近的时点。在使用备份进行故障恢复时，DBA 可以选择恢复到较早的时点。这种故障恢复需要满足一些限制条件。

    每当启动一个 SQL Server 实例时，启动故障恢复会自动运行，它将回滚上次关闭实例时尚未完成的所有事务。在使用备份进行故障恢复时，DBA 可以选择恢复到较早的时点。这种故障恢复需要满足一些限制条件。无论何种情况，故障恢复操作都基于此目标时点。

2003-6-7 19:32:58 故障恢复分为两个阶段：

    恢复所有更改，直到达到事务日志中的目标时点。

    撤消由在恢复停止点仍处于活动状态的事务所执行的所有操作。

    SQL Server 使用检查点加速重新启动故障恢复。检查点强制将当前缓冲区高速缓存中所有已修改的数据页保存到磁盘上。这将为故障恢复的恢复阶段创建一起点。由于检查点的开销非常大，所以 SQL Server 自动对检查点进行管理，以保证在尽量缩短重新启动所花时间的同时尽可能提高性能。

    在 SQL Server 2000 中，成功完成的写入必须可持久存储在磁盘中。如果使用写缓存磁盘存储，请与您的存储设备供应商联系，确认高速缓存是否容错。容错能力表示高速缓存可不受电源故障或操作员操作的影响。如果缓存没有容错能力，则应不使用。

    逻辑日志标记

    在 SQL Server 7.0 中，已经可以恢复到任何指定时点。如果出现硬件故障，则恢复过程是相当简单的。然而，对数据库的另一种威胁可能是输入了无效的数据，或者有效数据被用户操作所破坏。在这种情况下，需要确定问题发生的开始时间。在 SQL Server 7.0 中，解决这种问题的唯一方法是将日志恢复成数据库副本，直到问题重现，然后再对产品映像执行恢复操作，直到在所发现的问题出现时刻之前的时点。

    在 SQL Server 2000 中，可以在日志中标记事务。之后，如果需要恢复，就可以参考执行时使用的标记，而不必使用规定的时刻。为此，请使用 BEGIN TRANSACTION 的语句和 WITH MARK [说明] 子句。标记存储在 msdb 中。故障恢复可以包括包含标记的事务，也可以恰恰在包含标记的事务前停止。例如，如果某个进程以批处理方式运行并且更改了许多记录，那么可以使用此功能以确保，当进程运行在错误环境中时可以将数据回滚到执行命令的时点。

    标记名称不必唯一。若要指定所需的事务，请指定 datetime 值。该操作的语法为：
RESTORE LOG WITH [ STOPBEFOREMARK|STOPAFTERMARK ] = @TaggedTransaction AFTER @datetime
也可以对分布式事务使用标记（称为分布式标记），以支持将多个相关数据库恢复到事务上一致的状态。相关数据库可以位于 SQL Server 的同一或不同实例上。可以定期对一组数据库设置分布式标记（例如，每五分钟一次）。如果其中某个数据库的事务日志被损坏，则必须将这组数据库恢复到更早的时点。分布式标记可提供这一时点。使用分布式标记，就可以在对多个相关数据库进行备份时不用费心地确定备份的时刻。

    收缩事务日志

    SQL Server 7.0 中不能立即执行日志收缩操作。该操作被推迟到下一次备份或删节事务日志。这种方式使许多 SQL Server 7.0 客户非常烦恼。SQL Server 2000 可以立即收缩日志，并且可在日志备份后指出是否可以进行进一步收缩。这时，可以在日志备份完成后再次运行收缩命令。

    日志大小取决于当前故障恢复模式以及应用程序设计。如果发现需要定期收缩日志，请查明造成问题的原因。应该进一步调查日志添满的原因，而不要只是一味地使用收缩命令维护日志。

    故障恢复模式

    使用 SQL Server 2000 中增加的故障恢复模式可以方便到实施数据保护计划。这些模式都在性能、日志空间要求和媒体（磁盘）故障保护之间进行了取舍。共有三种模式，它们是：简单故障恢复、完全故障恢复和大容量记录。

    选择故障恢复模式时，应考虑数据库的使用情况和可用性要求，同时选择的模式应有助于确定适当的备份和恢复过程。这些故障恢复模式只适用于媒体故障恢复，即使用备份进行故障恢复。重新启动故障恢复所有提交的工作。

    故障恢复模式间的转换非常容易。例如，在大型数据库中，既可使用完全模式，也可以使用大容量记录模式，或同时使用这两种模式。可以在白天使用完全模式，而在夜晚或在包含大容量插入以及重建索引的数据装载过程中使用大容量记录模式。也可以在运行数据装载时切换到大容量记录模式，然后再切换回完全模式，运行事务日志备份，而且能够恢复到模式切换时点，而不必运行完全数据库备份。此功能可更有效地进行大容量处理操作；而所要做的只是将以前的事务日志进行备份。

    要更改故障恢复模式，请使用以下语法：
    ALTER DATABASE SET RECOVERY RecoveryModel

    简单故障恢复模式

    简单故障恢复模式通常需要很少的日志空间，但如果数据或日志文件被损坏，则它造成的潜在工作损失是最大的。因为在这种模式下只记录基本故障恢复所需的事件。使用简单故障恢复模式时，只能进行完全数据库备份和差异数据库备份。在出现故障时，必须重新完成自从上次备份后所有提交的工作。此模式对管理员是最简单的，但并不适用于关键性任务的应用程序，因为这种程序通常不允许丢失已提交的工作。

    此模式类似于 SQL Server 7.0 及以前版本中的 truncate log on checkpoint 选项。

2003-6-7 19:34:47 完全故障恢复模式

    在完全故障恢复模式中，所有一切都被记录。完全故障恢复模式提供了全面的保护，以防止损坏的数据文件对工作造成损失。如果事务日志被损坏，则从最近一次日志备份后提交的工作都将丢失，并且必须重新手动完成。

    即使使用完全故障恢复模式，也最好使用容错磁盘存储事务日志，以防止数据丢失。完全故障恢复模式还允许恢复到指定的时点。

    大容量记录故障恢复模式

    大容量记录故障恢复模式为大容量操作提供了最高的性能。而且，这些操作在该模式下占用的日志空间要小于在完全故障恢复模式下占用的空间。例如，新页的分配将被记录，而插入页中的数据则不被记录。在 SQL Server 2000 中，大容量操作由大容量装载（BCP 和 BULK INSERT，包括当他们在 DTS 包中运行时）、SELECT INTO、CREATE INDEX、WRITETEXT 和 UPDATETEXT 组成。

    与完全故障恢复模式相比，大容量记录故障恢复模式减少了对大容量操作的日志记录。请记住，在需要进行故障恢复时，如果日志被损坏或在最近一次日志备份后又进行了大容量操作，则在最后一次日志备份后对数据库进行的更改将会丢失。

    此模式不支持恢复到指定的时点，但它允许恢复到包含大容量更改的事务日志备份的末尾。使用大容量记录故障恢复模式进行的事务日志备份包含由大容量操作修改的扩展。此功能改善了对日志传送的支持，因为不用担心备份在大容量操作后会变为无效。SQL Server 维护映射以跟踪修改的数据扩展，这样做，可优化 SQL Server 用于标识更改的进程。

    改善的备份功能

    除引入故障恢复模式以简化常规数据保护外，SQL Server 2000 还改善了管理特性：快照技术、差异备份和安全性都已得到加强。

    事务日志备份链永远不会断开。在 SQL Server 7.0 中，某些操作（如向数据库中添加文件）会中断日志链，并且要求以后进行完全数据库备份。

    备份操作不会与应用程序或其他管理操作发生冲突。例如，备份可与大容量操作（如创建索引和批处理装载）同时进行。

    日志和文件备份可以同时进行。

    无论系统正在进行何种活动，SQL Server 2000 都对无人值守备份操作实现良好的支持。

    SQL Server 支持与独立硬件和软件供应商共同完成的快照备份和恢复技术。快照备份使得在进行备份时占用的系统资源最少，甚至可以不占用资源。这种技术对于中型或大型数据库尤其有益，因为在这种环境中可用性是非常重要的。这种技术的主要优势在于：

    可在非常短的时间（通常可以秒计）内创建备份，基本上不会对服务器造成任何影响。

    可以使用磁盘备份非常快地恢复数据库。

    另一台主机可创建备份，且不会对生产系统造成影响。

    可以立即创建生产数据库的副本，以用于报告或测试目的。

    快照备份和恢复技术是与第三方硬件和/或软件供应商协作共同完成的，这些供应商使用了 SQL Server 2000 为实现该技术而提供的特定功能。备份技术通常使用拆分磁盘镜像集的方法，创建要备份的数据的即时副本。在恢复时，原有数据就可立即投入使用。基本磁盘的同步是在后台进行的，因此几乎可以实现即时恢复。

    差异数据库备份的运行时间与上次完全备份后数据更改的总量成正比。数据更改越少，备份越快。SQL Server 2000 使用映射跟踪自最近一次数据库或文件备份后发生更改的数据扩展，以确保可更有效地定位这些扩展。此外，SQL Server 2000 支持文件差异备份。

    备份仍会收集自最近一次完全备份后对数据库进行的更改，运作方式与故障恢复相同。然而，这种备份是非常快的，因为它们只记录少部分更改过的信息，尤其是当数据库非常大而更改的数据又非常少时。

    为确保安全，可以使用密码保护备份媒体和备份集。这样就可防止未授权的用户在备份中添加数据或恢复数据库。

    六. 增强的管理功能

    在 SQL Server 2000 中，存储引擎的若干管理功能得到了加强。

    数据库验证

    DBCC 提供了各种管理能力，包括验证数据库一致性的 CHECK 命令。

    使用 SQL Server 7.0 和 SQL Server 2000 的经验表明，数据库的不一致性是由硬件问题引起的，但是数据库引擎或应用程序在正常操作中不一定能检测到这种问题。这种情况更可能出现在不经常访问的数据上。为解决这种问题，SQL Server 2000 引入一种检查模式 Physical_Only，可以探测到绝大部分由硬件引发的问题。探测过程非常快，速度与磁盘扫描速度相当，并且其对资源的消耗也很小。

    由于 SQL Server 存储引擎在基础体系结构上的改进（从 SQL Server 7.0 开始），已不必在常规维护时进行数据库验证。然而，Microsoft 仍然将数据库验证工具作为管理任务关键数据的重要组成部分。Microsoft 建议：

    根据对基本硬件（特别是磁盘子系统）的信心，不定期运行 Physical_Only 检查。

2003-6-7 19:36:16 在关键时刻，如在硬件或软件升级时，或怀疑出现任何问题时，进行完整数据库检查。

    Microsoft 不推荐在进行常规维护时执行完整数据库检查。

    SQL Server 2000 还在数据库验证方面作出了如下重大改进：

    默认情况下，检查可以在联机状态下完成。联机检查对事务工作负载的影响很小。这种影响的大小取决于系统负载、硬件配置和 tempdb 的速度。Microsoft 的实验结果表明，对于中等 OLTP 工作负载（50% 的 CPU 使用率），这种影响为 15% 到 20%。提供的 TABLOCK 选项会强制检查索取共享表锁，这可以使检查的运行速度更快，但会妨碍更新。

    检查操作在对称的多处理器 (SMP) 计算机中是以并行方式完成的，它受限于在该 SQL Server 实例中设置的最大并行程度。

    SQL Server 2000 检查命令继续支持 SQL Server 7.0 中引入的修复功能。在某些情形，脱机修复可以替代备份恢复。

    数据库状态控制

    SQL Server 2000 对 ALTER DATABASE 语句进行了改进，改进后的语句允许通过 Transact-SQL 对数据库状态实现更多的控制。现在，所有数据库选项都可通过 ALTER DATABASE 命令进行灵活修改；在以后的版本中，将不再更新 sp_dboption 和 databaseproperty()。Transact-SQL 命令 sp_helpdb 和 DatabasePropertyEx() 提供有关数据库状态的更多信息。

    下表列出了数据库状态选项。

选项类型 可用设置
用户权限 SINGLE_USERRESTRICTED_USERMULTI_USER
可用性 ONLINEOFFLINE
更新能力 READ_ONLYREAD_WRITE

    SQL Server 还根据数据库中的条件设置以下状态：复原 (restoring)、恢复 (recovering) 和待定 (suspect)。数据库选项可通过以下方式进行设置：ALTER DATABASE 语句的 SET 子句、sp_dboption 系统存储过程或 SQL Server Enterprise Manager（在某些情况下）。

    在数据库状态发生变化后，更改数据库状态的会话仍然保持连接，而与新状态不一致的会话可被终止，并且其事务将被回滚。会话终止选项如下：

    立即终止

    在指定时间后终止

    允许活动进程正常完成

    检查活动，如果发现活动用户会话，则忽略状态更改

    如下为语法的两个示例：

    alter database accting set read_only with rollback immediate
    alter database accting set single_user with rollback after 60 seconds

    系统进程 ID 和工作单元

    管理方面的另一个改进是 KILL 命令，该命令在停止进程时使用。改进后的 KILL 命令具有状态反馈。所以，如果要了解 KILL 命令的状态，请运行以下命令：

    KILL SPID WITH STATUSONLY

    在试图停止已由其它 KILL 命令停止的系统进程 ID (SPID) 时，系统将返回相同的状态信息。

    在 SQL Server 2000 中，MS DTC 事务可以在没有相关连接或 SPID 的情况下 存在。因此，在等待事务或工作单元完成之前，连接可由其他进程使用。当 MS DTC 事务管理器发送消息声明任务已完成时，您可以提交事务，也可以回滚事务。这就叫做一个工作单元 (UOW)，它是 MS DTC 用于事务的事务标识符。UOW 没有 SPID。

2003-6-7 19:37:22 动态调优

    在 SQL Server 2000 中，基于使用的性能优化是动态管理的，无需手动调整。静态参数已被去除，但仍然保留了对某些资源的管理控制（例如，设置 SQL Server 可以使用的最大内存数）。相对于根据平均值和估计值进行手工计算的系统，这种方法更加精确，反应更加快捷。这样，您可以将注意力集中在数据库管理的设计方面。传统的数据库系统需要大量的手工管理和调优工作。例如，为了根据使用情况来优化系统，DBA 必须监视系统，不断记录大量的统计数据，以便选择可提供最佳系统性能的静态设置。然后，DBA 要重新评估系统以确定新设置的效果，接着又从头开始调优过程。

    SQL Server 2000 在存储引擎中引入了动态算法，它可主动监视服务器的使用情况，并在内部调整设置。SQL Server 2000 中的动态反馈和分析可将设置保持在绝对优化值的 10% 以内（参见图 3），从而使系统的性能更优，适应性更强。

图 3：自适应算法优化

点击查看用户来源及管理<br>发贴IP：61.48.16.230 2003-6-15 9:56:29
编辑这个贴子
ugvanxk 帅哥哟，在线，有人找我吗？

等级：版主
文章：262
积分：682
注册：2002-11-2
搜索ugvanxk在『数 据 库』的所有贴子 第4楼
发贴心情
七. 数据存储组件

    SQL Server 2000 协同 Windows 2000 操作系统平衡所有可用 CPU 的工作量。如果正在运行一个特定的 SQL Server 实例，并且其他应用程序未占用相同的资源，请将处理器相关设置保持为默认值，以便使全部的处理器得到充分利用。SQL Server 可利用多个处理器上的并行处理能力执行查询、索引建立、DBCC 和其他操作。SQL Server 2000 标准版最多可支持四个处理器和 2 GB 物理内存 (RAM)。企业版提高到新的水平，支持多达 32 个处理器和 64 GB 物理内存 (RAM)。

    SQL Server 实例的主内存源称为它的内存池。在 SQL Server 实例中几乎所有使用内存的数据结构都是从内存池分配的。从内存池分配的对象示例包括缓冲区高速缓存（其中存储最近读取的数据）和过程高速缓存（其中存储最近的执行计划）。

    内存池中的分配是高度动态的。为优化性能，SQL Server 不断调整分配给不同区域的内存池大小。例如，当存储的执行计划的数量很少时，会通过将更多可用内存分配给数据高速缓存来调整内存池，从而优化资源的使用。

    SQL Server 2000 尽可能使用内存以减少磁盘 I/O。为此，SQL Server 在物理内存 (RAM) 中使用缓冲区高速缓存装载最近引用的数据，这样，这些数据可被重复使用。减少磁盘 I/O 和提供数据库系统速度的潜在方法是增加 SQL Server 可用的物理内存 (RAM)。

    通常，内存设置不需要任何调整。然而，在某些情况下可以对它们进行控制。例如，当在同一服务器上运行 SQL Server 的多个实例时，特别是在使用故障转移群集时，需要特别关注内存。如果在运行 SQL Server 的服务器上运行其他应用程序，也需要监视内存的使用情况。

2003-6-7 19:38:17 　　SQL Server 2000 利用 Windows 2000 的新功能，可寻址超过 3GB 的物理内存 (RAM) 。参见图 4。SQL Server 2000 企业版可以使用 Windows 2000 Advanced Server 或 Windows 2000 Datacenter Server 所允许的内存量。

    文件、文件组和磁盘

    SQL Server 在磁盘文件中存储数据和日志。在基本安装中，默认情况下，创建的数据和日志文件保存在服务器配置中指定的默认位置。然而，为了获得最优的性能和管理能力，可以应用以下几条基本原则：
尽可能将数据分布到多个磁盘、信道和控制器中。

    通常，磁盘越多（无论其单个容量），访问磁盘（控制器和信道）的速度以及存储引擎读写数据的速度也就越快。系统使用量越大，数据文件与日志文件的分离程度（将它们存储在不同的物理驱动器上）也就越重要。此外，由于 tempdb 的使用已发生变化，所以应该将 tempdb 存储在大磁盘集上；例如，与数据文件存放在一起或一组磁盘上。

    使用文件组，使企业数据库更易于管理。

    每个数据库都以一个默认的文件组开始。由于 SQL Server 2000 可在不附加文件组的情况下高效工作，因此许多系统都无需添加用户定义的文件组。然而，随着系统的增长，使用附加的文件组可提供更高的管理能力，当然这要求由称职的 DBA 实施和维护。

    在 SQL Server 2000 中，如果将数据库上的特定文件组设置为只读，则该文件组中的数据不能更改，但仍可以管理诸如权限等目录信息。

    注意：在 SQL Server 2000 中，数据库引擎中的异步 I/O 操作数实现了动态管理，并且不受使用的文件或文件组个数的影响，这一点与 SQL Server 7.0 相同。

    实施或优化数据库设计时，数据库管理员（数据库系统工程师）需要考虑数据库存储组件的配置，尤其是物理和逻辑磁盘的布局、数据库文件在磁盘中的排列。

    八. 总结

    灵活性的增强和性能控制的提高，使数据库管理员可以在掌握数据库技术的使用技巧和丰富数据库的使用经验时，将精力集中在管理数据库代码、设计和存储组件等方面，并以此作为数据库系统管理的最佳途径。SQL Server 2000 数据库引擎为各种数据库实现提供了通用的可扩展性和灵活性。


ChangeAllObjOwner
Version: SQL Server 7.0/2000
Created by: Alexander Chigrik

This stored procedure can be used to run through all of a specific
database's objects owned by the 'oldowner' and change the old
owner with the new one.
You should pass the old owner name and the new owner name,
as in the example below:

EXEC ChangeAllObjOwner @oldowner = 'John', @newowner = 'Alex'

/*
Version: SQL Server 7.0/2000
Created by: Alexander Chigrik
http://www.MSSQLCity.com/ - all about MS SQL
(SQL Server Articles, FAQ, Scripts, Tips and Test Exams).

This stored procedure can be used to run through all of a specific
database's objects owned by the 'oldowner' and change the old
owner with the new one.
You should pass the old owner name and the new owner name,
as in the example below:

EXEC ChangeAllObjOwner @oldowner = 'John', @newowner = 'Alex'
*/

IF OBJECT_ID('ChangeAllObjOwner') IS NOT NULL DROP PROC ChangeAllObjOwner
GO

CREATE PROCEDURE ChangeAllObjOwner (
@oldowner sysname,
@newowner sysname
)
AS
DECLARE @objname sysname
SET NOCOUNT ON

--check that the @oldowner exists in the database
IF USER_ID(@oldowner) IS NULL
BEGIN
RAISERROR ('The @oldowner passed does not exist in the database', 16, 1)
RETURN
END
--check that the @newowner exists in the database
IF USER_ID(@newowner) IS NULL
BEGIN
RAISERROR ('The @newowner passed does not exist in the database', 16, 1)
RETURN
END

DECLARE owner_cursor CURSOR FOR
SELECT name FROM sysobjects WHERE uid = USER_ID(@oldowner)

OPEN owner_cursor
FETCH NEXT FROM owner_cursor INTO @objname
WHILE (@@fetch_status <> -1)
BEGIN
SET @objname = @oldowner + '.' + @objname
EXEC sp_changeobjectowner @objname, @newowner
FETCH NEXT FROM owner_cursor INTO @objname
END

CLOSE owner_cursor
DEALLOCATE owner_cursor
GO


GetAlltblRowsSize
Version: SQL Server 7.0/2000
Created by: Alexander Chigrik

This stored procedure can be used to get the size of all users tables
in the particular database with the number of their rows.
You should simply pass the database name, as in the example below
(if the database name was not specified, the current database will
be used):

EXEC GetAlltblRowsSize 'pubs'

*
Version: SQL Server 7.0/2000
Created by: Alexander Chigrik
http://www.MSSQLCity.com/ - all about MS SQL
(SQL Server Articles, FAQ, Scripts, Tips and Test Exams).

This stored procedure can be used to get the size of all users tables
in the particular database with the number of their rows.
You should simply pass the database name, as in the example below
(if the database name was not specified, the current database will be used):

EXEC GetAlltblRowsSize 'pubs'
*/

IF OBJECT_ID('GetAlltblRowsSize') IS NOT NULL DROP PROC GetAlltblRowsSize
GO

CREATE PROCEDURE GetAlltblRowsSize (
@dbname sysname = null
)
AS
DECLARE @execstr nvarchar(255)
SET NOCOUNT ON
IF @dbname IS NULL SELECT @dbname = DB_NAME()
SELECT @execstr = 'EXEC ' + @dbname + '..sp_MSforeachtable @command1="sp_spaceused ''?''"'
EXEC(@execstr)
GO


GetObjNameList
Version: SQL Server 7.0/2000
Created by: Alexander Chigrik

This stored procedure can be used to return the names of the tables,
constraints, stored procedures, views, rules and triggers, which
contain the particular value. You should pass the database name
and the value to search, as in the example below
(if the database name was not specified, the current database will
be used):

EXEC GetObjNameList @dbname = 'pubs', @text = 'author'

/*
Version: SQL Server 7.0/2000
Created by: Alexander Chigrik

This stored procedure can be used to return the names of the tables,
constraints, stored procedures, views, rules and triggers, which (names)
contain the particular value. You should pass the database name and the
value to search, as in the example below
(if the database name was not specified, the current database will be used):

EXEC GetObjNameList @dbname = 'pubs', @text = 'author'
*/

IF OBJECT_ID('GetObjNameList') IS NOT NULL DROP PROC GetObjNameList
GO

CREATE PROC GetObjNameList (
@dbname sysname = null,
@text sysname
)
AS
SET NOCOUNT ON
IF @dbname IS NULL SELECT @dbname = DB_NAME()
EXEC('USE ' + @dbname + ' SELECT name as object_name, object_type = CASE type WHEN ''C'' THEN ''CHECK constraint'' WHEN ''D'' THEN ''Default or DEFAULT constraint'' WHEN ''F'' THEN ''FOREIGN KEY constraint'' WHEN ''K'' THEN ''PRIMARY KEY or UNIQUE constraint'' WHEN ''L'' THEN ''Log'' WHEN ''P'' THEN ''Stored procedure'' WHEN ''R'' THEN ''Rule'' WHEN ''RF'' THEN ''Replication filter stored procedure'' WHEN ''S'' THEN ''System table'' WHEN ''TR'' THEN ''Trigger'' WHEN ''U'' THEN ''User table'' WHEN ''V'' THEN ''View'' ELSE ''Extended stored procedure'' END FROM sysobjects WHERE name like ''%' + @text + '%''')
GO


GetObjCreateDate
Version: SQL Server 7.0/2000
Created by: Alexander Chigrik

This stored procedure will scan a database for objects created
within the StartDate and EndDate and return their names and
creation date.
You should pass the database name, the StartDate and the EndDate
as in the example below
(if the database name was not specified, the current database will
be used, if the @StartDate or @EndDate was not specified, the current
date will be used):

EXEC GetObjCreateDate 'pubs', '01/05/2002', '06/05/2002'

/*
Version: SQL Server 7.0/2000
Created by: Alexander Chigrik

This stored procedure will scan a database for objects created
within the StartDate and EndDate and return their names and
creation date.
You should pass the database name, the StartDate and the EndDate
as in the example below
(if the database name was not specified, the current database will be used,
if the @StartDate or @EndDate was not specified, the current date will be used):

EXEC GetObjCreateDate 'pubs', '01/05/2002', '06/05/2002'
*/

IF OBJECT_ID('GetObjCreateDate') IS NOT NULL DROP PROC GetObjCreateDate
GO

CREATE PROC GetObjCreateDate (
@dbname sysname = null,
@StartDate datetime = null,
@EndDate datetime = null
)
AS
DECLARE @s varchar(255)
SET NOCOUNT ON
IF @StartDate IS NULL SELECT @StartDate = GetDate()
IF @EndDate IS NULL SELECT @EndDate = GetDate()
IF @dbname IS NULL SELECT @dbname = DB_NAME()
SELECT @s = 'USE ' + @dbname + ' SELECT name, crdate FROM sysobjects WHERE crdate BETWEEN ''' + CONVERT(varchar(12), @StartDate, 101) + ''' AND ''' + CONVERT(varchar(12), @EndDate, 101) + ''''
EXEC(@s)
GO


GenerateSP
Version: SQL Server 7.0/2000
Created by: Alexander Chigrik

This script will generate script for all stored procedures for the
given database.
You can pass the Server name, user name, user password, database name
and file name into GenerateSP stored procedure, as in the example
below:

EXEC GenerateSP @server = 'Server_Name',
@uname = 'User_Name',
@pwd = 'Password',
@dbname = 'Database_Name',
@filename = 'c:\script.sql'

/*
Version: SQL Server 7.0/2000
Created by: Alexander Chigrik
http://www.MSSQLCity.com/ - all about MS SQL
(SQL Server Articles, FAQ, Scripts, Tips and Test Exams).

This script will generate script for all stored procedures for the
given database.
You can pass the Server name, user name, user password, database name
and file name into GenerateSP stored procedure, as in the example
below:

EXEC GenerateSP @server = 'Server_Name',
@uname = 'User_Name',
@pwd = 'Password',
@dbname = 'Database_Name',
@filename = 'c:\script.sql'

You can specify different number of parameters (from zero to five parameters).
If you do not specify server name, then the current server will be used;
if you do not specify user name, then the current user name will be used;
if you do not specify database name, then the current database will be used;
if you do not specify file name, then script will be placed into file
script.sql on drive 'c:'; if you do not specify password, the password
will not be used (for example: username = 'sa' and empty password, you can
pass in this case only username).

My stored procedure can be used for learning some general SQL Server
features (how to work with OLE objects from the SQL Server, how to use
some system functions, how to work with cursors and so on).
*/

IF OBJECT_ID('GenerateSP') IS NOT NULL DROP PROC GenerateSP
GO

CREATE PROC GenerateSP (
@server varchar(30) = null,
@uname varchar(30) = null,
@pwd varchar(30) = null,
@dbname varchar(30) = null,
@filename varchar(200) = 'c:\script.sql'
)
AS

DECLARE @object int
DECLARE @hr int
DECLARE @return varchar(200)
DECLARE @exec_str varchar(2000)
DECLARE @spname sysname

SET NOCOUNT ON

-- Sets the server to the local server
IF @server is NULL
SELECT @server = @@servername

-- Sets the database to the current database
IF @dbname is NULL
SELECT @dbname = db_name()

-- Sets the username to the current user name
IF @uname is NULL
SELECT @uname = SYSTEM_USER

-- Create an object that points to the SQL Server
EXEC @hr = sp_OACreate 'SQLDMO.SQLServer', @object OUT
IF @hr <> 0
BEGIN
PRINT 'error create SQLOLE.SQLServer'
RETURN
END

-- Connect to the SQL Server
IF @pwd is NULL
BEGIN
EXEC @hr = sp_OAMethod @object, 'Connect', NULL, @server, @uname
IF @hr <> 0
BEGIN
PRINT 'error Connect'
RETURN
END
END
ELSE
BEGIN
EXEC @hr = sp_OAMethod @object, 'Connect', NULL, @server, @uname, @pwd
IF @hr <> 0
BEGIN
PRINT 'error Connect'
RETURN
END
END

--Verify the connection
EXEC @hr = sp_OAMethod @object, 'VerifyConnection', @return OUT
IF @hr <> 0
BEGIN
PRINT 'error VerifyConnection'
RETURN
END

SET @exec_str = 'DECLARE script_cursor CURSOR FOR SELECT name FROM ' + @dbname + '..sysobjects WHERE type = ''P'' ORDER BY Name'
EXEC (@exec_str)

OPEN script_cursor
FETCH NEXT FROM script_cursor INTO @spname
WHILE (@@fetch_status <> -1)
BEGIN
SET @exec_str = 'Databases("'+ @dbname +'").StoredProcedures("'+RTRIM(UPPER(@spname))+'").Script(74077,"'+ @filename +'")'
EXEC @hr = sp_OAMethod @object, @exec_str, @return OUT
IF @hr <> 0
BEGIN
PRINT 'error Script'
RETURN
END
FETCH NEXT FROM script_cursor INTO @spname
END
CLOSE script_cursor
DEALLOCATE script_cursor

-- Destroy the object
EXEC @hr = sp_OADestroy @object
IF @hr <> 0
BEGIN
PRINT 'error destroy object'
RETURN
END
GO


GenerateScript
Version: SQL Server 7.0/2000
Created by: Alexander Chigrik

This script will generate script for all tables and all dependent objects
for the given database.
You can pass the Server name, user name, user password, database name and
file name into GenerateScript stored procedure, as in the example below:

EXEC GenerateScript @server = 'Server_Name',
@uname = 'User_Name',
@pwd = 'Password',
@dbname = 'Database_Name',
@filename = 'c:\File_Name.sql'

/*
Version: SQL Server 7.0/2000
Created by: Alexander Chigrik
http://www.MSSQLCity.com/ - all about MS SQL
(SQL Server Articles, FAQ, Scripts, Tips and Test Exams).

This script will generate script for all tables and all dependent objects
for the given database.
You can pass the server name, user name, user password, database name
and file name into GenerateScript stored procedure, as in the example
below:

EXEC GenerateScript @server = 'Server_Name',
@uname = 'User_Name',
@pwd = 'Password',
@dbname = 'Database_Name',
@filename = 'c:\script.sql'

You can specify different number of parameters (from zero to five parameters).
If you do not specify server name, then the current server will be used;
if you do not specify user name, then the current user name will be used;
if you do not specify database name, then the current database will be used;
if you do not specify file name, then script will be placed into file
script.sql on the drive 'c:'; if you do not specify password, the password
will not be used (for example: username = 'sa' and empty password, you can
pass in this case only username).

My stored procedure can be used for learning some general SQL Server
features (how to work with OLE objects from the SQL Server, how to use
some system functions, how to work with cursors and so on).
*/

IF OBJECT_ID('GenerateScript') IS NOT NULL DROP PROC GenerateScript
GO

CREATE PROC GenerateScript (
@server varchar(30) = null,
@uname varchar(30) = null,
@pwd varchar(30) = null,
@dbname varchar(30) = null,
@filename varchar(200) = 'c:\script.sql'
)
AS

DECLARE @object int
DECLARE @hr int
DECLARE @return varchar(200)
DECLARE @exec_str varchar(200)
DECLARE @tbname varchar(30)

SET NOCOUNT ON

-- Sets the server to the local server
IF @server is NULL
SELECT @server = @@servername

-- Sets the database to the current database
IF @dbname is NULL
SELECT @dbname = db_name()

-- Sets the username to the current user name
IF @uname is NULL
SELECT @uname = SYSTEM_USER

-- Create an object that points to the SQL Server
EXEC @hr = sp_OACreate 'SQLDMO.SQLServer', @object OUT
IF @hr <> 0
BEGIN
PRINT "error create SQLOLE.SQLServer"
RETURN
END

-- Connect to the SQL Server
IF @pwd is NULL
BEGIN
EXEC @hr = sp_OAMethod @object, 'Connect', NULL, @server, @uname
IF @hr <> 0
BEGIN
PRINT "error Connect"
RETURN
END
END
ELSE
BEGIN
EXEC @hr = sp_OAMethod @object, 'Connect', NULL, @server, @uname, @pwd
IF @hr <> 0
BEGIN
PRINT "error Connect"
RETURN
END
END

-- Verify the connection
EXEC @hr = sp_OAMethod @object, 'VerifyConnection', @return OUT
IF @hr <> 0
BEGIN
PRINT "error VerifyConnection"
RETURN
END

SET @exec_str = "DECLARE script_cursor CURSOR FOR SELECT name FROM " + @dbname + "..sysobjects WHERE type = 'U' ORDER BY Name"
EXEC (@exec_str)

OPEN script_cursor
FETCH NEXT FROM script_cursor INTO @tbname
WHILE (@@fetch_status <> -1)
BEGIN
SET @exec_str = 'Databases("'+ @dbname +'").Tables("'+RTRIM(UPPER(@tbname))+'").Script(74077,"'+ @filename +'")'
EXEC @hr = sp_OAMethod @object, @exec_str, @return OUT
IF @hr <> 0
BEGIN
PRINT "error Script"
RETURN
END
FETCH NEXT FROM script_cursor INTO @tbname
END
CLOSE script_cursor
DEALLOCATE script_cursor

-- Destroy the object
EXEC @hr = sp_OADestroy @object
IF @hr <> 0
BEGIN
PRINT "error destroy object"
RETURN
END
GO


列出SQL SERVER 所有表(pjessica收藏)
-- ======================================================

--列出SQL SERVER 所有表，字段名，主键，类型，长度，小数位数等信息

--在查询分析器里运行即可,可以生成一个表，导出到EXCEL中

-- ======================================================

SELECT

(case when a.colorder=1 then d.name else '' end)表名,

a.colorder 字段序号,

a.name 字段名,

(case when COLUMNPROPERTY( a.id,a.name,'IsIdentity')=1 then '√'else '' end) 标识,

(case when (SELECT count(*)

FROM sysobjects

WHERE (name in

(SELECT name

FROM sysindexes

WHERE (id = a.id) AND (indid in

(SELECT indid

FROM sysindexkeys

WHERE (id = a.id) AND (colid in

(SELECT colid

FROM syscolumns

WHERE (id = a.id) AND (name = a.name))))))) AND

(xtype = 'PK'))>0 then '√' else '' end) 主键,

b.name 类型,

a.length 占用字节数,

COLUMNPROPERTY(a.id,a.name,'PRECISION') as 长度,

isnull(COLUMNPROPERTY(a.id,a.name,'Scale'),0) as 小数位数,

(case when a.isnullable=1 then '√'else '' end) 允许空,

isnull(e.text,'') 默认值,

isnull(g.[value],'') AS 字段说明

FROM syscolumns a left join systypes b

on a.xtype=b.xusertype

inner join sysobjects d

on a.id=d.id and d.xtype='U' and d.name<>'dtproperties'

left join syscomments e

on a.cdefault=e.id

left join sysproperties g

on a.id=g.id AND a.colid = g.smallid

order by a.id,a.colorder

-------------------------------------------------------------------------------------------------


将表信息导出Excel(pjessica收藏)
列出SQL SERVER 所有表、字段定义，类型，长度，一个值等信息

并导出到Excel 中

-- ======================================================

-- Export all user tables definition and one sample value

-- jan-13-2003,Dr.Zhang

-- ======================================================

在查询分析器里运行：

SET ANSI_NULLS OFF

GO

SET NOCOUNT ON

GO

SET LANGUAGE 'Simplified Chinese'

go

DECLARE @tbl nvarchar(200),@fld nvarchar(200),@sql nvarchar(4000),@maxlen int,@sample nvarchar(40)

SELECT d.name TableName,a.name FieldName,b.name TypeName,a.length Length,a.isnullable IS_NULL INTO #t

FROM syscolumns a, systypes b,sysobjects d

WHERE a.xtype=b.xusertype and a.id=d.id and d.xtype='U'

DECLARE read_cursor CURSOR

FOR SELECT TableName,FieldName FROM #t

SELECT TOP 1 '_TableName ' TableName,

'FieldName ' FieldName,'TypeName ' TypeName,

'Length' Length,'IS_NULL' IS_NULL,

'MaxLenUsed' AS MaxLenUsed,'Sample value ' Sample,

'Comment ' Comment INTO #tc FROM #t

OPEN read_cursor

FETCH NEXT FROM read_cursor INTO @tbl,@fld

WHILE (@@fetch_status <> -1) --- failes

BEGIN

IF (@@fetch_status <> -2) -- Missing

BEGIN

SET @sql=N'SET @maxlen=(SELECT max(len(cast('+@fld+' as nvarchar))) FROM '+@tbl+')'

--PRINT @sql

EXEC SP_EXECUTESQL @sql,N'@maxlen int OUTPUT',@maxlen OUTPUT

--print @maxlen

SET @sql=N'SET @sample=(SELECT TOP 1 cast('+@fld+' as nvarchar) FROM '+@tbl+' WHERE len(cast('+@fld+' as nvarchar))='+convert(nvarchar(5),@maxlen)+')'

EXEC SP_EXECUTESQL @sql,N'@sample varchar(30) OUTPUT',@sample OUTPUT

--for quickly

--SET @sql=N'SET @sample=convert(varchar(20),(SELECT TOP 1 '+@fld+' FROM '+

--@tbl+' order by 1 desc ))'

PRINT @sql

print @sample

print @tbl

EXEC SP_EXECUTESQL @sql,N'@sample nvarchar(30) OUTPUT',@sample OUTPUT

INSERT INTO #tc SELECT *,ltrim(ISNULL(@maxlen,0)) as MaxLenUsed,

convert(nchar(20),ltrim(ISNULL(@sample,' '))) as Sample,' ' Comment FROM #t where TableName=@tbl and FieldName=@fld

END

FETCH NEXT FROM read_cursor INTO @tbl,@fld

END

CLOSE read_cursor

DEALLOCATE read_cursor

GO

SET ANSI_NULLS ON

GO

SET NOCOUNT OFF

GO

select count(*) from #t

DROP TABLE #t

GO

select count(*)-1 from #tc

select * into ##tx from #tc order by tablename

DROP TABLE #tc

--select * from ##tx

declare @db nvarchar(60),@sql nvarchar(3000)

set @db=db_name()

--请修改用户名和口令 导出到Excel 中

set @sql='exec master.dbo.xp_cmdshell ''bcp ..dbo.##tx out c:\'+@db+'_exp.xls -w -C936 -Usa -Psa '''

print @sql

exec(@sql)

GO

DROP TABLE ##tx

GO

-- ======================================================

--根据表中数据生成insert语句的存储过程

--建立存储过程，执行 spGenInsertSQL 表名

--感谢playyuer

-- ======================================================

CREATE proc spGenInsertSQL (@tablename varchar(256))

as

begin

declare @sql varchar(8000)

declare @sqlvalues varchar(8000)

set @sql =' ('

set @sqlvalues = 'values (''+'

select @sqlvalues = @sqlvalues + cols + ' + '','' + ' ,@sql = @sql + '[' + name + '],'

from

(select case

when xtype in (48,52,56,59,60,62,104,106,108,122,127)

then 'case when '+ name +' is null then ''NULL'' else ' + 'cast('+ name + ' as varchar)'+' end'

when xtype in (58,61)

then 'case when '+ name +' is null then ''NULL'' else '+''''''''' + ' + 'cast('+ name +' as varchar)'+ '+'''''''''+' end'

when xtype in (167)

then 'case when '+ name +' is null then ''NULL'' else '+''''''''' + ' + 'replace('+ name+','''''''','''''''''''')' + '+'''''''''+' end'

when xtype in (231)

then 'case when '+ name +' is null then ''NULL'' else '+'''N'''''' + ' + 'replace('+ name+','''''''','''''''''''')' + '+'''''''''+' end'

when xtype in (175)

then 'case when '+ name +' is null then ''NULL'' else '+''''''''' + ' + 'cast(replace('+ name+','''''''','''''''''''') as Char(' + cast(length as varchar) + '))+'''''''''+' end'

when xtype in (239)

then 'case when '+ name +' is null then ''NULL'' else '+'''N'''''' + ' + 'cast(replace('+ name+','''''''','''''''''''') as Char(' + cast(length as varchar) + '))+'''''''''+' end'

else '''NULL'''

end as Cols,name

from syscolumns

where id = object_id(@tablename)

) T

set @sql ='select ''INSERT INTO ['+ @tablename + ']' + left(@sql,len(@sql)-1)+') ' + left(@sqlvalues,len(@sqlvalues)-4) + ')'' from '+@tablename

--print @sql

exec (@sql)

end

GO

-- ======================================================

--根据表中数据生成insert语句的存储过程

--建立存储过程，执行 proc_insert 表名

--感谢Sky_blue

-- ======================================================

CREATE proc proc_insert (@tablename varchar(256))

as

begin

set nocount on

declare @sqlstr varchar(4000)

declare @sqlstr1 varchar(4000)

declare @sqlstr2 varchar(4000)

select @sqlstr='select ''insert '+@tablename

select @sqlstr1=''

select @sqlstr2=' ('

select @sqlstr1= ' values ( ''+'

select @sqlstr1=@sqlstr1+col+'+'',''+' ,@sqlstr2=@sqlstr2+name +',' from (select case

-- when a.xtype =173 then 'case when '+a.name+' is null then ''NULL'' else '+'convert(varchar('+convert(varchar(4),a.length*2+2)+'),'+a.name +')'+' end'

when a.xtype =104 then 'case when '+a.name+' is null then ''NULL'' else '+'convert(varchar(1),'+a.name +')'+' end'

when a.xtype =175 then 'case when '+a.name+' is null then ''NULL'' else '+'''''''''+'+'replace('+a.name+','''''''','''''''''''')' + '+'''''''''+' end'

when a.xtype =61 then 'case when '+a.name+' is null then ''NULL'' else '+'''''''''+'+'convert(varchar(23),'+a.name +',121)'+ '+'''''''''+' end'

when a.xtype =106 then 'case when '+a.name+' is null then ''NULL'' else '+'convert(varchar('+convert(varchar(4),a.xprec+2)+'),'+a.name +')'+' end'

when a.xtype =62 then 'case when '+a.name+' is null then ''NULL'' else '+'convert(varchar(23),'+a.name +',2)'+' end'

when a.xtype =56 then 'case when '+a.name+' is null then ''NULL'' else '+'convert(varchar(11),'+a.name +')'+' end'

when a.xtype =60 then 'case when '+a.name+' is null then ''NULL'' else '+'convert(varchar(22),'+a.name +')'+' end'

when a.xtype =239 then 'case when '+a.name+' is null then ''NULL'' else '+'''''''''+'+'replace('+a.name+','''''''','''''''''''')' + '+'''''''''+' end'

when a.xtype =108 then 'case when '+a.name+' is null then ''NULL'' else '+'convert(varchar('+convert(varchar(4),a.xprec+2)+'),'+a.name +')'+' end'

when a.xtype =231 then 'case when '+a.name+' is null then ''NULL'' else '+'''''''''+'+'replace('+a.name+','''''''','''''''''''')' + '+'''''''''+' end'

when a.xtype =59 then 'case when '+a.name+' is null then ''NULL'' else '+'convert(varchar(23),'+a.name +',2)'+' end'

when a.xtype =58 then 'case when '+a.name+' is null then ''NULL'' else '+'''''''''+'+'convert(varchar(23),'+a.name +',121)'+ '+'''''''''+' end'

when a.xtype =52 then 'case when '+a.name+' is null then ''NULL'' else '+'convert(varchar(12),'+a.name +')'+' end'

when a.xtype =122 then 'case when '+a.name+' is null then ''NULL'' else '+'convert(varchar(22),'+a.name +')'+' end'

when a.xtype =48 then 'case when '+a.name+' is null then ''NULL'' else '+'convert(varchar(6),'+a.name +')'+' end'

-- when a.xtype =165 then 'case when '+a.name+' is null then ''NULL'' else '+'convert(varchar('+convert(varchar(4),a.length*2+2)+'),'+a.name +')'+' end'

when a.xtype =167 then 'case when '+a.name+' is null then ''NULL'' else '+'''''''''+'+'replace('+a.name+','''''''','''''''''''')' + '+'''''''''+' end'

else '''NULL'''

end as col,a.colid,a.name

from syscolumns a where a.id = object_id(@tablename) and a.xtype <>189 and a.xtype <>34 and a.xtype <>35 and a.xtype <>36

)t order by colid

select @sqlstr=@sqlstr+left(@sqlstr2,len(@sqlstr2)-1)+') '+left(@sqlstr1,len(@sqlstr1)-3)+')'' from '+@tablename

-- print @sqlstr

exec( @sqlstr)

set nocount off

end

GO


Tables, Rows, Columns, Primary Keys
This procedure will give you the list of tables in the current database along with total columns in the table, no. of rows and the primary key(s) defined for the table.

CREATE proc sp_DBDetails
as
/*
This procedure will give you the list of tables in the current database along with total columns in the table, no. of rows and the primary key(s) defined for the table.

Mohit Nayyar
MCP (.Net), MCSD, MCDBA
*/
set nocount on
declare @id int, @name varchar(255), @cnt int, @sql nvarchar(4000), @temp varchar(900), @pcol varchar(255)
create table #temptable (TableName varchar(255), TotalColumns int, TotalRows int, PrimaryKeyCols varchar(900))
declare tempCursor cursor for
select name, id from sysObjects where type='U' and name not like 'dt%'
open tempCursor
fetch next from tempCursor into @name, @id
while(@@fetch_status=0)
begin
set @cnt=0
set @sql='select @cnt = count(*) from [' + @name + ']'
EXEC sp_executesql @sql, N'@cnt int out', @cnt out

set @temp=''
declare intemp cursor for
select a.Column_Name from INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE a, INFORMATION_SCHEMA.TABLE_CONSTRAINTS b where b.constraint_type='PRIMARY KEY' and a.constraint_name = b.constraint_name and a.table_name=ltrim(rtrim(@name))
open intemp
fetch next from intemp into @pcol
while(@@fetch_status=0)
begin
set @temp=@temp + ',' + @pcol
fetch next from intemp into @pcol
end
close intemp
deallocate intemp

set @temp='''' + substring(@temp,2,900) + ''''

set @sql='insert into #temptable (tablename, PrimaryKeyCols, TotalRows, TotalColumns) values (''' + @name + ''',' + @temp + ',' + cast(@cnt as varchar) + ','
select @cnt=count(*) from sysColumns where id=@id
set @sql=@sql + cast(@cnt as varchar) + ')'
EXEC sp_executesql @sql
fetch next from tempCursor into @name, @id
end
close tempCursor
deallocate tempCursor

select * from #tempTable
drop table #temptable
set nocount off


List all columns, from a database
This script will list all columns, their datatypes, and sizes from any database.?It can be very useful for contractor DBAs who need to quickly scan all tables, and very helpful for developers, or when you just need a quick list of fields for a meeting.

SELECT DISTINCT
LEFT(a.name, 30) AS fieldname, left(c.name,20) AS datatype,
a.length AS size, b.name AS tablename
FROM syscolumns a INNER JOIN
systypes c ON a.xtype = c.xusertype INNER JOIN
sysobjects b ON a.id = b.id
WHERE (b.xtype = 'U') AND (b.name <> 'dtproperties')
ORDER BY size desc, tablename, fieldname


ASCII table and UNICODE table
ASCII table and UNICODE table
Script Rating Total number of votes [1]
By: jgama
ASCII table and UNICODE table

You can easily change the font of "SQL Query Analyzer":
TOOLS/OPTIONS/FONTS

SET QUOTED_IDENTIFIER OFF
GO
SET ANSI_NULLS OFF
GO

CREATE PROCEDURE ASCIItable
--usage:
--exec dbo.asciitable
AS
DECLARE @i int,@j int, @c VARCHAR(100)
SET @i=32
WHILE @i<=255
BEGIN
SET @j=0
SET @c=''
WHILE @j<=7
BEGIN
SET @c=@c+right(' '+CONVERT(VARCHAR(3),@i), 3)+' '+ CHAR(@i)+' '
SET @j=@j+1
SET @i=@i+1
END
PRINT @c
END
GO
SET QUOTED_IDENTIFIER OFF
GO
SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER OFF
GO
SET ANSI_NULLS OFF
GO

CREATE PROCEDURE UNItable AS
--UNICODE table
declare @i int, @c nchar(1), @line nvarchar(4000)
SET @i=32
SET @line=''
WHILE @i<65536
BEGIN
SET @c=NCHAR(@i)
SET @line=@line+ RIGHT(' '+rtrim(convert(nchar(7),@i)),6)+' '+@c+' '
if ((@i-31) % 12=0)or (@i=65535)
BEGIN
PRINT @line
SET @line=''
END
SET @i=@i+1
END
GO
SET QUOTED_IDENTIFIER OFF
GO
SET ANSI_NULLS ON
GO


Transfer Logins and Passwords
Transfer Logins and Passwords Between SQL Server 7.0 Servers
Script Rating Total number of votes [15]
By: coliveira
The SQL Server 7.0 Data Transformation Services (DTS) Object Transfer feature transfers logins and users between two servers, but it does not transfer the passwords for SQL Server authenticated logins. (DTS in Microsoft SQL Server 2000 does transfer passwords.)

SET NOCOUNT ON
SELECT 'EXEC sp_addlogin @loginame = ''' + loginname + ''''
,', @defdb = ''' + dbname + ''''
,', @deflanguage = ''' + language + ''''
,', @encryptopt = ''skip_encryption'''
,', @passwd ='
, cast(password AS varbinary(256))
,', @sid ='
, sid
FROM syslogins
WHERE name NOT IN ('sa')
AND isntname = 0


 sp_ServerStatus
sp_ServerStatus
Script Rating Total number of votes [3]
By: simonsabin
Have you ever wanted to check that a server exists before using it. Here is a solution that use SQLDMO from within TSQL to get the status of a SQL Server service. It also works for 2000 instances.
I have named it sp_ServerStatus for it to be based in master.

This returns the status integer and a text description through OUTPUT properties of the SP.

Example

DECLARE @s int, @t varchar(100)
exec sp_ServerStatus 'MySQLServer\instance1',@s output, @t output
select @s, @t

DROP PROCEDURE dbo.sp_ServerStatus
go
CREATE PROCEDURE dbo.sp_ServerStatus
/*******************************************************************************

Written By : Simon Sabin
Date : 14 November 2002
Description : Returns the status of a SQL Server service

History
Date Change
------------------------------------------------------------------------------
14/11/2002 Created

*******************************************************************************/
(
@servername sysname
,@status int OUTPUT
,@statusText varchar(20) OUTPUT
)
AS
SET XACT_ABORT ON

declare @hr int , @object int
exec @hr = sp_OACreate 'sqldmo.sqlserver', @object OUTPUT
IF @hr<> 0
RAISERROR ('Cannot create sqldmo.sqlserver object',15,1)

exec @hr = sp_OASetProperty @object, 'Name', @servername

exec @hr = sp_OAGetProperty @object, 'Status', @status OUTPUT

SET @statusText = CASE @HR WHEN -2147221499 THEN 'Access Denied'
WHEN -2147219782 THEN 'Server does not exist'
WHEN 0 THEN CASE @status WHEN 0 THEN 'Unknown'
WHEN 5 THEN 'Stopping'
WHEN 3 THEN 'Stopped'
WHEN 4 THEN 'Starting'
WHEN 1 THEN 'Running'
WHEN 7 THEN 'Pausing'
WHEN 2 THEN 'Paused'
WHEN 6 THEN 'Continuing'
ELSE 'Unknown' END
ELSE 'Unknown error occurred' END
EXEC sp_OADestroy @object
GO


数据库文件名称改动
原先文件名 SysDB.MDF、SysDB_Log.LDF 现在突然变成 SysDB_MDB.MDF、SysDB_Log.LDF 把SysDB_MDB.MDF改名成原来的名字附加也不行。怎么办啊，那里有2年的系统数据啊！！！
主要解答者: pengdali 提交人: ylc001
感谢: pengdali、nboys
审核者: tjan 论坛对应贴子: 查看
A :

---------------------------------------------------------------

新建一同名数据库（文件名，文件组都和原来的一样），然后停止数据库服务，用原来文件替换新建的数

据库文件，启动数据库，该数据库被设未suspect

然后把数据库改成紧急模式：
sp_configure 'allow', 1
reconfigure with override
update sysdatabases set status = 32768 where name = '数据库名'

把LDF文件改名，再执行
DBCC REBUILD_LOG ('数据库名', 'E:\fdzz\database\fdzz1204_Log.LDF' )

恢复数据库紧急模式
update sysdatabases set status = 0 where name = '数据库名'
执行
restore database 数据库名 WITH RECOVERY
sp_configure 'allow', 0
reconfigure with override

然后用DBCC CHECKDB ('数据库名')看看有没有错误

如果上面还是不行，试试吧数据库设为紧急模式，应该可以看到数据了，在把数据导出到一个新的数据库

---------------------------------------------------------------

sp_resetstatus
重置置疑数据库的状态。

语法
sp_resetstatus [ @DBName = ] 'database'

参数
[@DBName =] 'database'

是要重置的数据库名。database 的数据类型为 sysname，无默认值。

重置置疑状态
如果 SQL Server 因为磁盘驱动器不再有可用空间，而不能完成数据库的恢复，那么 Microsoft? SQL Server? 2000 会返回错误 1105 并且将 sysdatabases 中的 status 列设为置疑。按下面的步骤解决这个问题：

执行 sp_resetstatus。

用 ALTER DATABASE 向数据库添加一个数据文件或日志文件。

停止并重新启动 SQL Server。
用新的数据文件或日志文件所提供的额外空间，SQL Server 应该能完成数据库的恢复。

释放磁盘空间并且重新运行恢复操作。
sp_resetstatus 关闭数据库的置疑标志，但是原封不动地保持数据库的其它选项。

注意 只有在您的主要支持提供者指导下或有疑难解答建议的做法时，才可以使用 sp_resetstatus。否则，可能会损坏数据库。


附录D SET 命令

返回

附录D SET 命令

豆豆技术网制作 修改于:2003-5-14 投稿信箱：web@ddhome.net

请记住我们的口号是:让计算机真正大众化,让更多的人了解计算机,使用计算机！！

非本站原创文章可以自由转载、本站原创文章未经同意不得转载！

原创文章版权归本站及作者所有，未经同意不得转载，转载文章必须注明选自豆豆技术网，谢谢合作！


Managing Jobs Using TSQL
Having he honor of working for quite a few companies that did not have the resources to buy any of the nice SQL Server toys that exist out there or were willing to put an email client on the servers, I have found myself spending a good deal of time each morning checking the status of the numerous jobs running on my servers. Not a hard thing to accomplish, but very time consuming when you are talking about dozens of servers with hundreds of jobs. Maybe it was just me, but no matter how much I pleaded at some of these companies, they would go through the red-tape to get an email client put on the SQL Servers so I could use the job notification ability to send me a nice email each morning if a particular job failed. Being the poor companies DBA, I had to come up with something else.

The one computer that usually had email abilities was my local desktop, funny how they always made sure I could get the hundreds of email telling me what to do each day. To solve my problem, I made use of my desktop and created a system that checked the outcome of all the jobs across all my servers and sent me a nice little report each morning.

The first thing I did was to connect to my local msdb database and create a table to hold the repot information. You can adjust the table how you want to since I just included the basic information.

IF OBJECT_ID('tJobReport') IS NOT NULL
DROP TABLE tJobReport
GO

CREATE TABLE tJobReport
(
lngID INTEGER IDENTITY(1,1)
,server VARCHAR(20)
,jobname VARCHAR(50)
,status VARCHAR(10)
,rundate VARCHAR(10)
,runtime CHAR(8)
,runduration CHAR(8)
)
GO

Given the nature of some the schedules for the job, I felt like this would grow into a sizable table in a very short time so I created a clustered index to speed the data retrieval up.

CREATE CLUSTERED INDEX tJobReport_clustered
ON tJobReport(server,jobname,rundate,runtime)
GO

Next create a stored procedure that will populate your new table. This example makes use of linked servers to job information and job history from each of my servers, you could change the linked server format over to OPENDATASOURCE if you like.

Example of using OPENDDATASOURCE

FROM OPENDATASOURCE(
'SQLOLEDB',
'Data Source=DEV2;User ID=sa;Password='
).msdb.dbo.sysjobs sj

INNER JOIN OPENDATASOURCE(
'SQLOLEDB',
'Data Source=DEV2;User ID=sa;Password='
).msdb.dbo.sysjobhistory sh
ON sj.job_id = sh.job_id

Otherwise, simply linked all your remote servers to your desktop, adjust the following stored procedure to account for the number of linked servers you have, and create the following stored procedure in your msdb database.

IF OBJECT_ID('spJobReport') IS NOT NULL
DROP PROCEDURE spJobReport
GO

CREATE PROCEDURE spJobReport
AS
SET NOCOUNT ON

--Server 1
INSERT INTO tJobReport (server, jobname, status, rundate, runtime, runduration)
SELECT sj.originating_server, sj.name,

--What is it in English
CASE sjh.run_status
WHEN 0 THEN 'Failed'
WHEN 1 THEN 'Succeeded'
WHEN 2 THEN 'Retry'
WHEN 3 THEN 'Canceled'
ELSE 'Unknown'
END,

--Convert Integer date to regular datetime
SUBSTRING(CAST(sjh.run_date AS CHAR(8)),5,2) + '/' +
RIGHT(CAST(sjh.run_date AS CHAR(8)),2) + '/' +
LEFT(CAST(sjh.run_date AS CHAR(8)),4)

--Change run time into something you can reecognize (hh:mm:ss)
, LEFT(RIGHT('000000' + CAST(run_time AS VARCHAR(10)),6),2) + ':' +
SUBSTRING(RIGHT('000000' + CAST(run_time AS VARCHAR(10)),6),3,2) + ':' +
RIGHT(RIGHT('000000' + CAST(run_time AS VARCHAR(10)),6),2)

--Change run duration into something you caan recognize (hh:mm:ss)
, LEFT(RIGHT('000000' + CAST(run_duration AS VARCHAR(10)),6),2) + ':' +
SUBSTRING(RIGHT('000000' + CAST(run_duration AS VARCHAR(10)),6),3,2) + ':' +
RIGHT(RIGHT('000000' + CAST(run_duration AS VARCHAR(10)),6),2)

FROM msdb.dbo.sysjobs sj --job id and name

--Job history
INNER JOIN msdb.dbo.sysjobhistory sjh
ON sj.job_id = sjh.job_id

--Join for new history rows
left JOIN msdb.dbo.tJobReport jr
ON sj.originating_server = jr.server
AND sj.name = jr.jobname
AND SUBSTRING(CAST(sjh.run_date AS CHAR(8)),5,2) + '/' +
RIGHT(CAST(sjh.run_date AS CHAR(8)),2) + '/' +
LEFT(CAST(sjh.run_date AS CHAR(8)),4) = jr.rundate
AND LEFT(RIGHT('000000' + CAST(run_time AS VARCHAR(10)),6),2) + ':' +
SUBSTRING(RIGHT('000000' + CAST(run_time AS VARCHAR(10)),6),3,2) + ':' +
RIGHT(RIGHT('000000' + CAST(run_time AS VARCHAR(10)),6),2) = jr.runtime

--Only enabled jobs
WHERE sj.enabled = 1
--Only job outcome not each step outcome
AND sjh.step_id = 0
--Only completed jobs
AND sjh.run_status <> 4
--Only new data
AND jr.lngID IS NULL

--Latest date first
ORDER BY sjh.run_date DESC

--Server 2
INSERT INTO tJobReport (server, jobname, status, rundate, runtime, runduration)
SELECT sj.originating_server, sj.name,

--What is it in English
CASE sjh.run_status
WHEN 0 THEN 'Failed'
WHEN 1 THEN 'Succeeded'
WHEN 2 THEN 'Retry'
WHEN 3 THEN 'Canceled'
ELSE 'Unknown'
END,

--Convert Integer date to regular datetime
SUBSTRING(CAST(sjh.run_date AS CHAR(8)),5,2) + '/' +
RIGHT(CAST(sjh.run_date AS CHAR(8)),2) + '/' +
LEFT(CAST(sjh.run_date AS CHAR(8)),4)

--Change run time into something you can reecognize (hh:mm:ss)
, LEFT(RIGHT('000000' + CAST(run_time AS VARCHAR(10)),6),2) + ':' +
SUBSTRING(RIGHT('000000' + CAST(run_time AS VARCHAR(10)),6),3,2) + ':' +
RIGHT(RIGHT('000000' + CAST(run_time AS VARCHAR(10)),6),2)

--Change run duration into something you caan recognize (hh:mm:ss)
, LEFT(RIGHT('000000' + CAST(run_duration AS VARCHAR(10)),6),2) + ':' +
SUBSTRING(RIGHT('000000' + CAST(run_duration AS VARCHAR(10)),6),3,2) + ':' +
RIGHT(RIGHT('000000' + CAST(run_duration AS VARCHAR(10)),6),2)

FROM dev2.msdb.dbo.sysjobs sj --job id and name

--Job history
INNER JOIN dev2.msdb.dbo.sysjobhistory sjh
ON sj.job_id = sjh.job_id

--Join for new history rows
left JOIN msdb.dbo.tJobReport jr
ON sj.originating_server = jr.server
AND sj.name = jr.jobname
AND SUBSTRING(CAST(sjh.run_date AS CHAR(8)),5,2) + '/' +
RIGHT(CAST(sjh.run_date AS CHAR(8)),2) + '/' +
LEFT(CAST(sjh.run_date AS CHAR(8)),4) = jr.rundate
AND LEFT(RIGHT('000000' + CAST(run_time AS VARCHAR(10)),6),2) + ':' +
SUBSTRING(RIGHT('000000' + CAST(run_time AS VARCHAR(10)),6),3,2) + ':' +
RIGHT(RIGHT('000000' + CAST(run_time AS VARCHAR(10)),6),2) = jr.runtime

--Only enabled jobs
WHERE sj.enabled = 1
--Only job outcome not each step outcome
AND sjh.step_id = 0
--Only completed jobs
AND sjh.run_status <> 4
--Only new data
AND jr.lngID IS NULL

--Latest date first
ORDER BY sjh.run_date DESC
GO

Next, simply create a job on your desktop with whatever schedule you like to run the stored procedure. Once the table has data, it is a simple procedure to define reporting stored procedures to determine the outcome of jobs, average the run time of jobs, report on the history of the jobs, etc.

If you want an automatic email sent to you, just configure SQL Mail on your desktop and create a new job or new job step that uses the xp_sendmail system stored procedure to run a basic query.

EXEC master.dbo.xp_sendmail @recipients = 'randydyess@transactsql.com',
@message = 'Daily Job Report',
@query = '
SELECT status,server, jobname
FROM msdb.dbo.tJobReport
WHERE status = 'Failed'
AND rundate > DATEADD(hh,-25,GETDATE())',
@subject = 'Job Report',
@attach_results = 'TRUE'

So, if you have the same bad luck in getting those great tools out there or want a centralized way to keep in control of your job outcomes and history, this simple technique can go along way in helping you quickly manage those hundreds of jobs we all seem to accumulate over time.

You can find out more about sysjobs. sysjobhistory and xp_sendmail in my last book Transact-SQL Language Reference Guide.

Copyright 2003 by Randy Dyess, All rights Reserve


Nightly Failed Jobs Report
Do you get tired of reviewing each SQL Agent notifications nightly to determine which SQL Agent jobs failed? Is there a significant number of SQL Agent job notifications that it takes a while to review each? Here is an alternative that allows you to have a single email report of all job failures.

Why We Created A Single Job Failure Report
Over the past few years we have implemented a number of SQL Agent jobs in SQL Server. Each job uses the SQL Agent notification process to communicate success or failures to the DBA’s. This communication is all done via email. Due to the number of jobs we have, we get lots of emails reporting the successes and failures of our nightly jobs. Every morning the time to review each of the email notifications to determine success or failure of each SQL Agent jobs was time consuming. Also it was easy to get in a habit of quickly deleting the emails without really noticing whether the jobs succeeded or failed. For this reason we decide to create a process to produce a single job failure report.

What We Did
We decide since the job/step failure information was stored in the MSDB database that we would write a stored procedure called “usp_failed_jobs_report (see “Listing of SP” below) to produce a single report. This stored procedure performs some simple SQL to gather information from the sysjobhistory and sysjobs tables, formats the data into a report, and then emails the report to the our DBAs.

This SP code is run every morning Monday - Friday at 6 am via a SQL Agent job. The SP determines which jobs and job steps have failed since the last scheduled running of this SP. For each failed jobs/job step this SP produces a single line in the report. The line in the report shows the name of the job and step that failed, plus the date and time of the failure.

Example of Report
Here is an example on the a report produced by this SP.

The following jobs/steps failed since Aug 23 2002 6:00AM

job step_name failed datetime

------------------------------------------- --------------------------------- -------------------

ADRN1303 TRIGGER TRIGGER FILE EXIST Aug 25 2002 12:15AM

LOAD EMPLOYEE TABLE LOAD TABLE Aug 25 2002 12:58AM

LOAS EMPLOYEE TABLE (Job outcome) Aug 25 2002 12:58AM

ADDS load for PROD1 New FTP VALIDATION 1 Aug 25 2002 2:16AM

ADDS load for PROD1 New FTP VALIDATION 2 Aug 25 2002 2:16AM

This report shows that “ADRN1303 TRIGGER” job had a step failure, “LOAD EMPLOYEE TABLE” had a step and job failure (the “(job outcome)” step_name indicates a job failure), and “ADDS load for PROD1 New” had two different step failures.

Conclusion

By having a single report, the DBA's in our shop can now review a single email to determine which jobs have failed since the last business day. Having this single report allows a quick method for the DBA’s to identify all the jobs and step failures for the past day. By reviewing a single report the DBA’s don’t have to review each individual SQL Agent notification email to determine which jobs failed.

Listing of SP
CREATE procedure usp_failed_jobs_report as

-- Written by: Greg Larsen

-- Company: Department of Health, Washington State

-- Date: January 3, 2002

-- Description: This SQL Code reports job/step failures based on a data and time range. The

-- report built is emailed to the DBA distribution list.

--

-- Modified 04/12/2002 - Greg Larsen - Modified to support Long running jobs that cross reporting

-- periods

declare @RPT_BEGIN_DATE datetime

declare @NUMBER_OF_DAYS int

-- Set the number of days to go back to calculate the report begin date

set @NUMBER_OF_DAYS = -1

-- If the current date is Monday, then have the report start on Friday.

if datepart(dw,getdate()) = 2

set @NUMBER_OF_DAYS = -3

-- Get the report begin date and time

set @RPT_BEGIN_DATE = dateadd(day,@NUMBER_OF_DAYS,getdate())

-- Get todays date in YYMMDD format

-- Create temporary table to hold report

create table ##temp_text (

email_text char(100))

-- Generate report heading and column headers

insert into ##temp_text values('The following jobs/steps failed since ' +

cast(@RPT_BEGIN_DATE as char(20)) )

insert into ##temp_text values ('job step_name failed datetime ')

insert into ##temp_text values ('------------------------------------------- --------------------------------- -------------------')

-- Generate report detail for failed jobs/steps

insert into ##temp_text (email_text)

select substring(j.name,1,43)+

substring(' ',

len(j.name),43) + substring(jh.step_name,1,33) +

substring(' ',

len(jh.step_name),33) +

-- Calculate fail datetime

-- Add Run Duration Seconds

cast(dateadd(ss,

cast(substring(cast(run_duration + 1000000 as char(7)),6,2) as int),

-- Add Run Duration Minutes

dateadd(mi,

cast(substring(cast(run_duration + 1000000 as char(7)),4,2) as int),

-- Add Run Duration Hours

dateadd(hh,

cast(substring(cast(run_duration + 1000000 as char(7)),2,2) as int),

-- Add Start Time Seconds

dateadd(ss,

cast(substring(cast(run_time + 1000000 as char(7)),6,2) as int),

-- Add Start Time Minutes

dateadd(mi,

cast(substring(cast(run_time + 1000000 as char(7)),4,2) as int),

-- Add Start Time Hours

dateadd(hh,

cast(substring(cast(run_time + 1000000 as char(7)),2,2) as int),

convert(datetime,cast (run_date as char(8))))

))))) as char(19))

from msdb..sysjobhistory jh join msdb..sysjobs j on jh.job_id=j.job_id

where (getdate() >

-- Calculate fail datetime

-- Add Run Duration Seconds

dateadd(ss,

cast(substring(cast(run_duration + 1000000 as char(7)),6,2) as int),

-- Add Run Duration Minutes

dateadd(mi,

cast(substring(cast(run_duration + 1000000 as char(7)),4,2) as int),

-- Add Run Duration Hours

dateadd(hh,

cast(substring(cast(run_duration + 1000000 as char(7)),2,2) as int),

-- Add Start Time Seconds

dateadd(ss,

cast(substring(cast(run_time + 1000000 as char(7)),6,2) as int),

-- Add Start Time Minutes

dateadd(mi,

cast(substring(cast(run_time + 1000000 as char(7)),4,2) as int),

-- Add Start Time Hours

dateadd(hh,

cast(substring(cast(run_time + 1000000 as char(7)),2,2) as int),

convert(datetime,cast (run_date as char(8))))

))))))

and (@RPT_BEGIN_DATE < -- Calculate fail datetime

-- Add Run Duration Seconds

dateadd(ss,

cast(substring(cast(run_duration + 1000000 as char(7)),6,2) as int),

-- Add Run Duration Minutes

dateadd(mi,

cast(substring(cast(run_duration + 1000000 as char(7)),4,2) as int),

-- Add Run Duration Hours

dateadd(hh,

cast(substring(cast(run_duration + 1000000 as char(7)),2,2) as int),

-- Add Start Time Seconds

dateadd(ss,

cast(substring(cast(run_time + 1000000 as char(7)),6,2) as int),

-- Add Start Time Minutes

dateadd(mi,

cast(substring(cast(run_time + 1000000 as char(7)),4,2) as int),

-- Add Start Time Hours

dateadd(hh,

cast(substring(cast(run_time + 1000000 as char(7)),2,2) as int),

convert(datetime,cast (run_date as char(8))))

))))))

and jh.run_status = 0

-- Email report to DBA distribution list

exec master.dbo.xp_sendmail @recipients='DIRM.DBA@doh.wa.gov',

@subject='Check for Failed Jobs - Contains jobs/steps that have failed.',

@query='select * from ##temp_text' , @no_header='true', @width=150

-- Drop temporary table

drop table ##temp_text

GO

SET QUOTED_IDENTIFIER OFF

GO

SET ANSI_NULLS ON

GO


see if a job is currently running
Script to See if a Job is Running

Here's a simple script that checks to see if a job is currently running.

Updated on 1/18/02 with a working example of how to use the script in the comments section.

Author: Allan Saywitz

/* ===============================================================================

Name:
sp_IsJobRunning.sql

Description:
See if a job is running.

Inputs:
@JobName - name of job to see if running.

Outputs:
@IsRunning - job running status.

'Y' = job is running.
'N' = job is not running.

Returns:
1 - error occurred.
0 - no error occurred.

Example:
Declare @IsRunning char(1)
EXEC sp_IsJobRunning "some job name", @IsRunning OUTPUT
IF @IsRunning = 'Y'
Print 'Job is Running'
ELSE
Print 'Job is not Running'

Author: Allan Saywitz, divine inc.

Revision History:
12/10/2001 Created.

=============================================================================== */

IF EXISTS (SELECT *
FROM sysobjects
WHERE id = object_id('sp_IsJobRunning')
AND OBJECTPROPERTY(id, 'IsProcedure') = 1
)
DROP PROC sp_IsJobRunning
GO

CREATE PROC sp_IsJobRunning
(
@JobName varchar(64),
@IsRunning char(1) OUTPUT
)
AS

BEGIN

SET NOCOUNT ON

-- declare variables
DECLARE
@job_id uniqueidentifier,
@is_sysadmin int,
@job_owner sysname,
@execution_status int

-- initialize variables
SET @IsRunning = 'N'
SET @job_id = NULL
SET @is_sysadmin = 0
SET @job_owner = NULL
SET @execution_status = 0

-- set job id
SET @job_id = (SELECT job_id FROM msdb..sysjobs WHERE name = @JobName)
IF (@job_id IS NULL)
BEGIN
RAISERROR('Unknown job name', 16, 1)
RETURN 1
END

-- create temp table
CREATE TABLE #xp_results
(
job_id UNIQUEIDENTIFIER NOT NULL,
last_run_date INT NOT NULL,
last_run_time INT NOT NULL,
next_run_date INT NOT NULL,
next_run_time INT NOT NULL,
next_run_schedule_id INT NOT NULL,
requested_to_run INT NOT
NULL,
request_source INT NOT NULL,
request_source_id sysname NULL,
running INT NOT
NULL,
current_step INT NOT NULL,
current_retry_attempt INT NOT NULL,
job_state INT NOT NULL
)

-- set sysadmin flag
SELECT @is_sysadmin = ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0)

-- set job owner
SELECT @job_owner = SUSER_SNAME()

-- populate #xp_results
INSERT INTO #xp_results EXECUTE master.dbo.xp_sqlagent_enum_jobs @is_sysadmin, @job_owner

-- set execution status
SET @execution_status = (SELECT job_state FROM #xp_results WHERE job_id = @job_id)

-- set is running flag
IF (@execution_status = 1)
BEGIN
SET @IsRunning = 'Y'
END

-- drop #xp_results
IF EXISTS (SELECT 1 WHERE (OBJECT_ID('tempdb..#xp_results') IS NOT NULL) )
BEGIN
DROP TABLE #xp_results
END

SET NOCOUNT OFF

RETURN 0

END
GO


 FAQ:SQL Mail（4）<译> ( _Rambo)
Q10: 为什么SQL Mail属性的配置文件名下拉框没有配置文件列表？

A10: 原因之一可能是使用了Local System用户来启动SQL Server服务。该用户没有Windows用户与其关联，因此不会有该用户的mail配置文件，也当然也就无法为其配置相应的SQL Mail配置文件了。另一个可能原因就是启动服务账号的用户没有相应的mail配置文件。你必需使用启动服务用户来登录SQL Server所在的服务器，才可能为该用户配置一个SQL Mail配置文件。

Q11: 为什么只有登录了服务器后SQL Mail才正常工作？

A11: 这可能是因为没有正确配置SQL Mail。或者用户使用Outlook2002，并配置成Internet Mail模式。

Q12: 为什么SQL Mail停止响应（挂起）？

A12: 最可能导致SQL Mail停止响应的原因是与Mail服务器连接失败，特别当Mail服务器是一个Internet mail(POP3/SMTP)服务器时，当然Mail服务器为Exchage Server时也可能发生同样的问题。
一般的邮件客户端软件在连接服务器失败时会弹出继续或取消的对话框，用户只要干预即可继续，但SQL Mail不会弹出这样的对话框，也就是说SQL Mail将处于持续等待状态，即使可以正常连接了SQL Mail也不会自动重试连接。这种情况下只有重新启动SQL Server服务才可以重新发送邮件。
另一个可能导致SQL Mail停止响应的情况是当非Outlook应用程序发送邮件时产生了一个Outlook安全警告。SQL6.5和SQL7的SQL Mail正是这样的非Outlook应用程序，当发送邮件时如果产生一个安全对话框，那么SQL Mail就会象失去邮件服务器连接一样停止响应。

Q13: 如何确定是对话框导致了SQL Mail停止响应？

A13: 国为SQL Mail是通过SQL Server服务运行的，所以桌面上并不会出现导致SQL Mail挂起的对话框。只有当SQL Server以控制台应用程序而非以服务形式运行时才可能出现对话框，测试方法如下：
1. 使用SQL Server服务账号登录服务器。
2. 进入windows命令提示符状态，并进入相应SQL Server实例可执行文件目录。
3. 执行sqlservr -c
注意: 如果是SQL2000，使用了命名实例，那么需要使用-s参数，并带上实例名。
4. 使用查询分析器连接上SQL Server，执行xp_sendmail扩展存储过程发送一个测试邮件。如果SQL Server不是SQL2000，那么先运行xp_startmail扩展存储过程启动SQL Mail。
5. 如果出现对话框，那么就得先排除这个问题再能使用SQL Mail。

Q14: 为什么一个已调度的job会停止工作？该job使用mail通知方式。

A14: 当一个调度的job调用SQLAgentMail时可导致SQLAgentMail停止响应。job成功完成与SQLAgentMail通知操作员是同步进行的，在此过程中如果到邮件服务的连接停止响应，那么发送邮件的命令就会停止响应，这将使接下来的job无法运行，除非问题得到解决，并且SQL代理服务重启。

Q15: 为什么出现“Mapi logon failure”错误提示？

A15: 出现该错误信息通常意味着SQL Mail未找到用来启动的mail配置文件。或者配置文件不明确，比如配置文件名超过32个字符，这时需要删除并重建该配置文件，并使用短名字，当然还必须是可以正常工作的配置文件。

Q16: 为什么出现“General mapi failure”错误提示？

A16: 该错误通常是由于未正确配置SQL Mail造成的。

Q17: 为什么在SQL Server错误日志中会出现1073759806和1073759778号错误？

A17: 在SQL2000下使用xp_startmail扩展存储过程将会在错误日志中出现以下信息：
Error: 1073759806, Severity: 1, State: 0 Starting SQL Mail session....
Error: 1073759778, Severity: 1, State: 0 SQL Mail session started...
SQL2000下当运行xp_sendmail时，SQL Mail将会自动启动，所以xp_startmail并不是必须的，以上错误信息可以忽略。

Q18: 在发送邮件时为什么出现0x80040111（或相似的错误号）邮件错误？

A18: 解决此问题首先要确定与该错误号相关联的错误信息。MAPI不会转化相应的错号为错误信息，所以SQL Mail也只返回错误号。MAPI错误号与错误信息对应关系请查看：
http://support.microsoft.com/default.aspx?scid=kb;en-us;Q238119
INFO: List of Extended MAPI Numeric Result Codes
对于0x80040111错误，可以找到错误信息是MAPI_E_LOGON_FAILED，即SQL Mail登录配置文件提供商失败，跟据此可以纠正错误了。

Q19: 为什么SQL Mail正常工作而SQLAgentMail不正常工作呢？或者SQLAgentMail正常工作而SQL Mail不正常工作呢？

A19: 第一个可能原因是SQL Server启动账号与SQL Agent服务账号不一致。更改不能正常工作的一个账号与另一个保持一致。重启该服务进行测试。
另一个可能原因是配置文件不一致。启动服务的账号保持一致后，更改不能工作的服务的配置文件与另一个何持一致，启动服务进行测试。
有一点很重要，SQL6.5和SQL7使用简单MAPI，但SQLAgentMail却使用扩展MAPI，SQL Mail支持一些只支持简单MAPI的邮件程序，但SQLAgentMail不支持。关于SQL Mail支持的邮件程序请查看：
http://support.microsoft.com/default.aspx?scid=kb;en-us;Q311231
INF:Frequently Asked Questions - SQL Server - SQL Mail
默认邮件程序甚至可以影响SQL Mail的操作。

Q20: 为什么不能象“测试”按钮所提示的那样使用使配置文件来启动或停止SQL Mail？

A20: SQL Mail（或SQLAgentMail）属性而对话框仅仅是用来测试用户可以用该配置文件提供商来启动SQL Mail，并不确保SQL Mail一定使用该配置文件来启动。因为SQL Mail将使用默认的邮件程序，而不管配置文件提供商。如果默认的邮件程序是Outlook Express，并且服务器上Outlook Express没有被配置，SQL Mail将使用Outlook Express来启动，当然，结果是失败的。
必须使默认邮件程序和用来启动SQL Mail的配置文件提供商保持一致，才能使SQL Mail正常工作。如果不一致，则请更改默认邮件程序。

Q21: 为什么集群系统中SQL Mail没有故障恢复功能？

A21: 由于SQL Mail使用的MAPI接口没有集群功能，所以SQL Mail在SQL Server集群环境下并不支持故障恢复。更详细信息请查看：
http://support.microsoft.com/default.aspx?scid=kb;en-us;Q298723
BUG: SQL Mail Not Fully Supported for Use in Conjunction with Cluster Virtual SQL Servers

Q22: 为什么SQL Mail不能使用分发列表？

A22: SQL7.0及SQL6.5的SQL Mail可以使用一个分发列表来分发邮件，但是SQL2000只在特定的配置下可以使用分发列表来发送邮件。更详细信息请查看： http://support.microsoft.com/default.aspx?scid=kb;en-us;Q315666
BUG: XP_SENDMAIL Fails to Send Mail to Personal Distribution List


FAQ:SQL Mail（3）<译>(潇湘剑公子)
常见的 SQL Mail 问题（二）

Q5: 为什么外发邮件会粘贴收件箱？

A5 : 这个问题在 Outlook 2002 上发生的可能性更高一些。为了提高互联网电子邮件的效率， Office 开发团队将 the mail spooler for non-Exchange mail 移到了Outlook进程空间（原文是process space）。你需要打开 Outlook client 来发送外发邮件。这个问题对 Outlooks向 Exchange server 发送电子邮件没有影响。

当你使用 Outlook 2000 的时候，你同样可能遇到这样的问题。在这种情况下，最可能的原因是在 mail profile 中没有提供一个工作的 POP3 server 作为邮件接收服务器。

Q6: 为什么外发邮件会粘贴待发箱？

A6: 这样的问题经常出现在类似 Microsoft Windows Messaging 以及 Microsoft Outlook 97这样的早期电子邮件 clients 上。这些邮件 clients 从来不准备接收来自象SQL Server 这样的 Windows service 的请求。更多信息，请通过以下链接访问微软产品技术服务站点:

http://support.microsoft.com/default.aspx?scid=kb;en-us;Q237913

这些邮件 clients 现在基本上废弃了，所以不久之后你将不会再遇到这样的问题。 但是，如果你遇到了，通过联系Microsoft 产品支持服务，你将获得hotfix（热修？呵呵）。

Q7: 为什么在我的发送Sent Items文件夹中没有任何发送信息？

A7: SQL Server 6.5 和 SQL Server 7.0 的SQL Mail 在 Outlook 的 Sent Items 文件夹中保存一个发送信息的拷贝，这是简单 MAPI 界面的默认行为。即使你在Outlook mail client 中清除 Save copies of messages in Sent Items folder 选项以使该选项失效，这个选项也会被忽略掉。

SQL Server 2000 的 SQL Mail 使用扩展 MAPI 界面，它不会将发送信息在 Sent Items 文件夹中保存一个拷贝。如果你需要保存SQL Server 2000 发送的 SQL Mail 拷贝，你可以抄送给自己并且创建一个 Outlook 通讯规则来发送信息拷贝到你的Sent Items 文件夹。

Q8: 为什么我的信箱满了却在 Sent Items 文件夹中看不见任何邮件？

A8 : 在 SQL Server 7.0 和 SQL Server 2000 中， 有一个 SQL Server Agent Properties 选项 Save copies of the sent messages in the 'Sent Items' folder . 如果你不选择该选项， SQL Mail 将会把SQL Server Agent mail 的拷贝保存到 Exchange server 的根文件夹下。当邮箱满了时，使用xp_sendmail 存储过程发送邮件将失败并返回 0x80040403 错误或者MAPI_E_TABLE_TOO_BIG 错误。

你无法直接看到 Microsoft Outlook 的 SQLAgentMail拷贝，需要通过 Microsoft Exchange utility MDB Viewer 工具来查看和删除这些信息。更多信息，请通过以下链接访问微软产品技术服务站点:

http://support.microsoft.com/default.aspx?scid=kb;en-us;Q261133
http://support.microsoft.com/default.aspx?scid=kb;en-us;Q308758

Q9: 为什么SQL Mail 不删除电子邮件的附件？
Why does SQL Mail not delete my e-mail attachments?

A9: 如果你运行带查询的 xp_sendmail 存储过程， @attach_results 正确，将创建附件的临时文件并存放在Windows 操作系统安装的 System32 目录下。所有版本的 SQL Server 都支持这个行为。更多信息, 请通过以下链接访问微软产品技术服务站点:

http://support.microsoft.com/default.aspx?scid=kb;en-us;Q151545


FAQ:SQL Mail（2）<译>(潇湘剑公子)
常见的 SQL Mail 问题(一)

本文适用于以下 Microsoft SQL Server 数据库版本:
· Microsoft SQL Server 2000 (all editions)
· Microsoft SQL Server versions 6.5 , 7.0

摘要

本文采用一问一答的形式列出了一些在你使用 SQL Mail 过程中可能遇到的问题，并且向你提供一些解决该类问题的技巧。绝大多数的SQL Mail 问题都可以归结于不正确的配置。更多信息如何正确的配置你的 SQL Mail 的信息，请通过以下链接访问微软产品技术服务站点：

http://support.microsoft.com/default.aspx?scid=kb;en-us;Q263556

本文假定读者已具备 Q263556 的应用知识背景。除非另作说明，本文中关于 SQL Mail 的注释同样适用于 SQLAgentMail 。

更多信息

检查你们的配置
更正不正确的配置可以解决绝大多数 SQL Mail 问题。如何确认你已经正确的配置了你的 SQL Mail ，请看以下的列表：

· 在你运行 SQL Server 的计算机上必须安装了mail client (比如 Microsoft Outlook)

· 使用 Microsoft Outlook 2000 时，应当选择 Corporate or Workgroup 选项(Microsoft Outlook 2002 没有 Corporate or Workgroup 选项)。选择该选项，在Tools 菜单点击Options ，然后双击 Mail Services 图标。在Mail Services 对话框中点击 Mail Support 命令。除非 Outlook client 允许一直运行，当你使用 Internet mail server 时，Microsoft Outlook 2002 不支持与 SQL Mail 一起使用。

· 必须有一个工作的邮件服务器，比如Microsoft Exchange 或者 Internet mail (Post Office Protocol 3 [POP3] 或者 Simple Mail Transport Protocol [SMTP]) server。邮件发送服务器不必与邮件接收服务器一样；但是，你必须有一个用邮件帐户配置的有效的邮件接收服务器，即便是你从来不打算向 SQL Maie 发送邮件。

· 当使用 Exchange server 时，用于启动 SQL Server service 的帐户必须是一个域帐户。这个域帐户在 Exchange server 必须有一个邮箱。如果你使用 Internet mail server，那么你可以使用本地帐户启动 SQL Server;但是，在任何环境下，用于启动 SQL Server 的帐户不能是本地的系统帐户。当你使用 SQLAgentMail 时，以上规定同样适用于启动 SQL Agent 的帐户。

· 因为 mail profiles 与 Microsoft Windows 的登录帐户有关，在登录系统时应使用 SQL Server service 的启动帐户运行 SQL Server，以检查mail profile 对 SQL Server 是否可用。例如，打开控制面板，在控制面板对话框中，双击 Mail . 在 Mail 对话框中, 双击 Show Profiles .使用 SQLAgentMail ， 当 SQL Agent service 帐户与 SQL Server service 帐户不同时,你同样需要用SQL Agent service 启动帐户登录以检查mail profiles 对 SQL Agent 是否可用.

· 你应该能够打开mail client 向 mail server 测试发送和接收邮件。SQL Mail 用mail client 来发送邮件。因为 SQL Mail 在SQL Server service下运行，对话框将被送给 SQL Server service 的 "virtual desktop" 而不出现在界面上. 因为没有交互，所以 SQL Mail停止响应(挂起)。

· 你需要向 SQL Mail 提供用来检查邮件结构的 mail profile。这样做, 在企业管理器中, 打开 Support Services 文件夹.在 Support Services 文件夹中,于 SQL Mail 上按右键选择 Properties .如果你使用的是 SQLAgentMail, 你需要向 SQL Agent 提供 mail profile. 这样做, 在企业管理器中, 打开 Management 文件夹, 于 SQL Agent 上按右键并选择 Properties .

· 默认的 Internet e-mail 计划应该与你用于检查邮件结构的 mail client 是一样的. 确认你已经有了正确的计划, 打开控制面板, 双击 Internet Options , 选择 Programs 图标.

· 要使按照如前所述作出的改变生效，你需要重新启动 SQL Server service 。如果你重新配置了 mail profile, 那么需要重新启动电脑才能使新的配置生效。
常见问题

以下是顾客使用中常遇到的SQL Mail 相关问题列表。我们将以问答的形式来讨论各种各样的问题。这些问题并非按照发生频率来排列，尽管类似的问题被放在了一起。对于问题的回答，并非是面面俱到的，但是基本涵盖了导致一个特殊问题的大多数原因。

Q1: 为什么升级到 SQL Server 2000 之后 SQL Mai 停止了工作？

A1 : SQL Server 2000 对 SQL Mail 引入了重大的改变。主要的改变是 SQL Mail 现在使用扩展 Mail Application Programming Interface (MAPI) 替代简单的 MAPI. 关于简单与扩展 MAPI 的更多信息，请通过以下链接访问微软产品技术服务站点:

http://support.microsoft.com/default.aspx?scid=kb;en-us;Q200018

如果你使用SQL Server 2000, Microsoft 强烈建议你在使用 SQL Mail 前安装 Microsoft SQL Server 2000 Service Pack 2 (SP2),或者以后的补丁版本。

Q2: 为什么当我升级 Microsoft Outlook 或者应用 Outlook 安全补丁之后 SQL Mail停止了工作？

A2 : 2001年八月，为阻止计算机蠕虫病毒自动发送到你的地址簿上所有信箱，发布了一个新的 Outlook 2000 安全升级版本。当该安全补丁发现有 non-Outlook program发送的电子邮件时，将弹出对话框做如下提示：
A program is trying to automatically send e-mail on your behalf. Do you want to allow this?

如果你相将该邮件发送出去，按 YES 。

SQL Server 6.5 和 SQL Server 7.0 的 SQL Mail 试图发送邮件时被当作 non-Outlook program. 然而, 因为 SQL Mail 在 SQL Server service 被呼叫, 对话框将被送给 SQL Server service 的 "virtual desktop" 而不出现在界面上. 因为没有交互，所以 SQL Mail停止响应。

这个安全特性会因为Exchange administrator而不严格；更好的解决方法是升级数据库版本到 SQL Server 2000。SQL Server 2000 的 SQL Mail 建立了一个为安全对话框设置迂回的扩展 MAPI 连接。关于 Outlook 安全补丁的更多信息，请通过以下链接访问微软产品技术服务站点:

http://support.microsoft.com/default.aspx?scid=kb;en-us;Q262631

如果你已经使用 SQL Server 2000 ，当升级 Outlook 2002 后 SQL Mail 停止工作，你可以使用一个Internet Mail server。你可以配置 Outlook 的早期版本，这样可以从 Microsoft Windows service 发送邮件。在non-Exchange mai 模式下改进性能，当你发送邮件时，需要 Outlook client 一直运行。 如果你不希望 Outlook 2002 client 一直在服务器上运行 (需要你登录到服务器), 你可以使用一个 Exchange server 作为你的邮件服务器或者回滚到早期的 Microsoft Outlook 版本。

Q3: 怎样得知在我的服务器上是否已经安装了Outlook 安全补丁？

A3 : 在 Outlook 的 HELP 菜单中，点击 About Microsoft Outlook 。如果你的Outlook 2000 版本是 9.0.0.4201 或者以后的版本，那么就说明已经安装了 Outlook 电子邮件安全补丁。

Q4: 当安装了Outlook 安全补丁或者使用 Outlook XP 时，怎样防止 SQL Mail 被挂起？

A4 : 你需要使用 Exchange administrator 来更改默认的安全设置。详细的设置取决于你使用的是Outlook 2000 还是 Outlook 2002 (Office XP 提供)。关于更改 Exchange 安全设置的更多信息，请通过以下链接访问微软产品技术服务站点:

http://support.microsoft.com/default.aspx?scid=kb;en-us;Q263297
http://support.microsoft.com/default.aspx?scid=kb;en-us;Q290499


FAQ:SQL Mail（1）<译>(潇湘剑公子)
本文信息适用于:
· Microsoft SQL Server 2000 (所有版本)
· Microsoft SQL Server versions 6.5 , 7.0
摘要
本文回答一些关于SQL Mail profile与性能的FAQ.本文不讲述如何配置 SQL Mail,也不解答 SQL Mail 的相关故障问题.关于 SQL Mail 的相关故障问题及其解答,请通过以下链接查阅相关文章:
http://support.Microsoft.com/defaul.aspx?scid=kb;EN-US;Q315886
大多数 SQL Mail 相关问题都归结于不合适的配置.根据以下的链接,你可以获取正确的SQL Mail 配置方法
http://support.microsoft.com/default.aspx?scid=kb;EN-US;Q263556

除非另做说明,文中关于 SQL Mail 的说明同样适用于 SQL Agent Mail.

更多信息
以下是一些经常向Microsoft SQL Server 支持专家 提出的关于 SQL Mail 的问题.

Q1 : 为什么我们在使用 SQL Mail 时需要在 SQL Server 服务器上安装一个邮件客户端程序？

A1:SQL Mail 是基于Mail Application Programming Interface (MAPI) 的应用,需要 MAPI 信息子系统来发送邮件.当你在机器上安装了基于MAPI的客户端, MAPI 子系统也就相应的被安装了.Microsoft Windows NT 4.0 提供一个名为Windows Messaging 的 MAPI 客户端；但是, Microsoft Windows 2000 不提供这样一个MAPI 客户端.

支持 SQL Mail 应用的MAPI 子系统最简洁的途径是安装一个基于MAPI的客户端,就象 Microsoft Outlook.

Q2: 怎样的邮件客户端程序可以配合 SQL Mail 一起使用?

A2: 配合 SQL Mail 使用的邮件客户端程序和你所使用的 SQL Server 版本以及操作系统有关. 如果你使用 Windows NT 4.0, 那么你可以使用支持Windows NT 4.0 的 SQL Mail for SQL Server 6.5 以及 SQL Server 7.0.的 Windows Messaging client .Windows Messaging client 只能在Internet邮件服务器下工作,并且不支持SQL Server 2000.

Microsoft Outlook 98 支持 SQL Server 6.5 以及 SQL Server 7.0.如果要 Outlook 98 能在SQL Server 2000 下工作,除非你安装了Microsoft SQL Server 2000 Service Pack 1. Outlook 98 可以在一个 Exchange server 或者一个 Internet mail server 下工作. 为了支持SQL Mail ,在安装 Outlook 98 时应该选择 Corporate or Workgroup 选项.

Microsoft Outlook 2000 在 SQL Server 6.5, SQL Server 7.0 以及 SQL Server 2000 环境下工作. Outlook 2000可以在一个 Exchange server 或者一个 Internet mail server 下工作. 为了支持SQL Mail ,在安装 Outlook 2000 时应该选择 Corporate or Workgroup 选项.

Microsoft Office XP 附带的Microsoft Outlook 2002 在 SQL Server 2000 环境下的使用. Outlook 2002 内建的安全特征导致 SQL Mail 不再支持 SQL Server 6.5 与 SQL Server 7.0 .Outlook 2002 配套 SQL Mail 使用时,你必须使用Exchange server.
Microsoft 不推荐使用 Outlook 2002 配合 SQL Mail 与 Simple Mail Transport Protocol (SMTP) 或者 Post Office Protocol 3 (POP3) Internet mail servers. 这方面的内容将在在以后的文章中做详细的叙述.

Q3: 能用 Outlook Express 配合 SQL Mail 使用吗?

A3: 不行.

Q4: 如何在运行Microsoft Small Business Server 下创建mail profile?

A4: Small Business Server 是 Exchange server 与 SQL Server 在同一台机器上的特殊情况. 想获取更多信息,请根据以下链接查询:
http://suppor.microsoft.com/default.aspx?scid=kb;EN-US;Q304967

Q5: 能象使用 Lotus Notes 或者 Novell Groupwise 那样使用SQL Mail 吗?

A5: 你可以将 SQL mail 与第三方邮件服务器一起使用,如： Lotus Notes 以及 Novell Groupwise; 但是,只有当第三方邮件服务器被配置成一个 Internet mail server 时,这个构架才支持.

虽然可以配置 Microsoft Outlook client支持使之支持第三方邮件服务器, 这些第三方的邮件服务器所拥有的e-mail clients却不支持 SQL Mail 的应用. 这些e-mail clients提供属于它们自身的 MAPI 信息子系统的工具,这些工具没有在 SQL Mail 上验证过.

Q6: 当SQL Mail 接受不到邮件时,是否需要一个POP3 server?

A6:除非你设置 SQL Mail 与 Exchange server 作为内部邮件服务, 否则你必须拥有一个POP3 server. 原因是当 Microsoft Outlook 在发送外部邮件时会先检验POP3 incoming mail server. 如果没有检验到合法的 POP3 incoming mail server, 邮件将不能发送出去, 甚至 SQL Mail 会停止响应 (挂起).

Q7: 能使用 Yahoo! 或者 Hotmail 帐户 作为 POP3 server 吗?

A7: 不行. 并且,使用这样的服务器做商业用途违反了服务商的许可.

使用象 MSN 这样的 Internet Service Provider (ISP) 运作下的 POP3 帐户也不是一个好主意.一旦出于任何原因引起 SQL Mail 连接POP3 server 失败, SQL Mail 将停止响应,你必须先停止服务,然后重新启动 SQL Server ,这样才能再次发送邮件.

在你的 SQL Server 服务器上安装并配置自己的 POP3 server 是一个不错的解决方案.

Q8: 当我不愿意在 SQL Server 上安装 Microsoft Outlook 时如何使用 SQL Mail?

A8: 如果你的环境运行在Windows NT 4.0 下，或许你可以使用 Windows Messaging ,这依赖于 SQL Server 的版本.

如果你的环境运行在 Windows 2000 下, 那么只能使用 Microsoft Outlook.

使用 CDO for NT Server (CDONTS) 或者 CDO for Windows 2000 (CDOSYS) 以及SQL Server OLE 自动存储过程 ,你可以不借助 SQL Mail ,而通过一个 Transact-SQL 批处理来发送邮件.更多信息，请参阅：
http://suppor.microsoft.com/default.aspx?scid=kb;EN-US;Q312839

Q9: 如果有 Microsoft Exchange, 还需要 Outlook 吗?

A9: Outlook 提供的客户端 MAPI 并不总是适合象 SQL Server 这样的服务器端应用.与Exchange Server 一起提供的服务器端 MAPI 比 Outlook 所提供的 MAPI 更优越.很不幸, Exchange MAPI 的安装并不提供创建 SQL Mail 所必须的 mail profile 的功能. 如果你打算使用Exchange 服务器端 MAPI ，以下是创建mail profile 的技巧. 更多信息，请参阅：
http://suppor.microsoft.com/default.aspx?scid=kb;EN-US;Q306962

安装 服务器端 MAPI ,你不需要在你的 SQL Server 计算机上安装Exchange Server. 你只需要安装Microsoft Exchange Administrator. 为创建 profile , 你还需要增加 Exchange services. 更多信息，请参阅：
http://suppor.microsoft.com/default.aspx?scid=kb;Q294470

Q10: 能使用 SQL Mail 发送 HTML 邮件吗?

A10: 因为 MAPI 的局限性，你不能使用SQL Server 的当前版本发送HTML-formatted 邮件. 更多信息，请参阅：
http://suppor.microsoft.com/default.aspx?scid=kb;Q268440

如果你需要发送 HTML 格式的邮件, 你可以考虑编写存储过程，通过Collaboration Data Objects (CDO) 来发送邮件. 更多信息，请参阅：
http://suppor.microsoft.com/default.aspx?scid=kb;Q312839

Q11: 为什么 SQL Server 2000 中没有 SQL Mail 启动选项?

A11: 在SQL Server 2000 中没有手动启动 SQL Mail 的选项，在 SQL Server 2000 启动时也不会自动启动 SQL Mail. 当你第一次发送 SQL Mail 信息时 SQL Mail 将自动启动. 更多信息，请参阅：
http://suppor.microsoft.com/default.aspx?scid=kb;Q293443

用扩展存储过程 xp_startmail 和 xp_stopmail 来启动与关闭 SQL Mail 将导致内存泄漏,除非你已经在机器上安装了SQL Server 2000 Service Pack 2,否则 Microsoft不推荐使用以上两个存储过程. 更多信息，请参阅：
http://suppor.microsoft.com/default.aspx?scid=kb;Q300414

Q12: 为什么需要用域帐户来启动 SQL Server?

A12: 当使用 Exchange Server 时你必须使用域帐户来启动 SQL Server. SQL Mail 需要通过 Windows 认证来连接 Exchange Server 以避免被用户证书提示. 一个域帐户能同时登录到本地 SQL Server 计算机和远程 Exchange Server 计算机，所以是最佳方案.

Q13: 我是否一定得给SQL Mail 和SQL Agent Mail使用同样的mail profile?

A13: Mail profiles 与 Windows 登录帐户是相关联的. 如果你用同一个服务帐户启动 SQL Server 和 SQL Agent services, 那么对于SQL Mail 和 SQL Agent Mail , 你需要使用同一个 mail profile, 这对不同的 profile 是不利的.

如果要用不同的帐户启动 SQL Server 和 SQL Agent services , 那么你必须为每一个帐户配置一个mail profile 文件. 为服务帐户配置mail profile 文件, 你必须用该帐户登录到服务器.

Q14: 当 Exchange server 和 SQL Server server 分别处于两个相互不信任的域中，我应该使用什么帐户？

A14: 根据工作区的特性，将 Exchange server 视为一个Internet mail server. 当配置SQL Mail profile时,可以填入登录帐户和口令信息，这样，当 SQL Mail 连接另一个域下的 Exchange server 将不会被提示登录信息.

当SQL Server server 和 Exchange server 处于不同的防火墙下，你同样可以使用这个技巧.

Q15: SQL Mail 能与 Microsoft Desktop Engine (MSDE) 一起使用吗?

A15: MSDE 不支持 SQL Mail. 如果要使用 SQL Mail ,必须升级当前 SQL Server到一个非MSDE 版本.


正确配置和使用SQL mail(心歌)
前段时间费了九牛二虎之力，终于搞掂了SQL mail的配置与使用。
_Rambo要我整理出来有关的文档大家共享。
我就把我配置SQL mail的过程描述一遍。

我用SQL mail主要是要完成这样的功能：
>用户在网上注册后,系统将随机产生的密码发送到用户登记的Email
>用户在论坛的帖子有回复时将内容发送到用户的Email
因为上述过程都是在存储过程中完成的，所以避免了前台程序对参数的
传输处理，也不需要再用第三方的组件完成，感觉比较方便。

1.为了使用SQL mail,首先你的服务器上得有SMTP服务，我没有安装win2000 server自带的SMTP，而是用imail6.04的SMTP,感觉比较稳定，功能也比较强。
2.安装一个邮件系统,我安装了outLook 2000，我发现在配置邮件profile时，如果
不安装outLook而是用别的第三方程序，win2k中文server版在控制面板中就找不到“邮件”一项.
3.安装完outlook后再刷新控制面板,就会找到“邮件”一项，双击进行邮件的配置，为配置文件起一个名字(假设为myProfile)，以便以后SQL mail使用，在该配置文件中设置各项属性。
4.启动outlook(设置为用myProfile作为默认的配置文件),测试进行收发邮件，确认outlook工作正常。
5.用当前的域帐户启动SQL server,在企业管理器的支持服务中，点击SQL mail的属性，可以看到在配置文件选择中，出现了刚才定义的myProfile配置文件(你也可以定义多个profile)，选择这个配置文件进行测试，SQL将返回成功开始和结束一个MAPI会话的信息，如果出现错误或是没有找到邮件配置文件，那一定是你启动SQL server用的帐号有问题
6.现在你就可以在查询分析器中用XP_sendmail这个扩展存储过程发送SQL mail了，格式如下：
xp_sendmail {[@recipients =] 'recipients [;...n]'}
[,][@message =] 'message']
[,][@query =] 'query']
[,][@attachments =] attachments]
[,][@copy_recipients =] 'copy_recipients [;...n]'
[,][@blind_copy_recipients =] 'blind_copy_recipients [;...n]'
[,][@subject =] 'subject']
[,[@type =] 'type']
[,][@attach_results =] 'attach_value']
[,][@no_output =] 'output_value']
[,][@no_header =] 'header_value']
[,][@width =] width]
[,][@separator =] 'separator']
[,][@echo_error =] 'echo_value']
[,][@set_user =] 'user']
[,][@dbuse =] 'database']

其中@recipients是必需的

参数说明：

参数 说明
@recipients 收件人，中间用逗号分开
@message 要发送的信息
@query 确定执行并依附邮件的有效查询，除触发器中的插入表及删除表外，此查询能引用任何对象
@attachments 附件
@copy_recipients 抄送
@blind_copy_recipients 密送
@subject 标题
@attach_results 指定查询结果做为附件发送
@no_header 不发送查询结果的列名
@set_user 查询联接的用户名，默认为Guset
@dbuse 查询所用的数据库，默认为缺省数据库

7.不过，如果是在web应用中使用SQL mail,还有一些问题要解决:首先，就是应用程序中连接数据库的帐号，我在网站程序中的数据库连接是使用UDL文件，帐号为DbGuest,这是一个普通帐户，所以还必须在master库的扩展存储过程找到XP_sendmail，并在其属性中增加DbGuest这个用户，并选择EXEC权限。
好了，现在设置完毕，运行网站程序，测试用户注册，几乎没有什么延迟，我测试用的邮箱中就收到了这封SQL mail发出的Email:
"谢谢你的注册，你的用户名是 [心歌],密码是 123456789,建议你首次登录后修改密码"

大功告成!看起来很简单的过程，却折腾了我一个星期!希望你能比我顺利.

不过，现在虽然能用SQLmail发送附件，附加数据库查询结果，但是我还没有发现有什么办法用SQLmail发送html格式的邮件，如果你知道，请一定告诉我哟!


使用SQL Mail收发和自动处理邮件
SQL SERVER提供了通过EXCHANGE或OUTLOOK收发邮件的扩展存储过程，下面将这几个过程简单的介绍一下。

一、启动SQL Mail

xp_startmail @user,@password

@user和@password都是可选的

也可打开Enterprise Manager中的Support Services，在SQL Mail上单击右键打开右键菜单，然后按Start来启动

二、停止SQL Mail

xp_stopmail

也可用上述方法中的菜单里的Stop来停止

三、发送邮件

xp_sendmail {[@recipients =] 'recipients [;...n]'}
[,][@message =] 'message']
[,][@query =] 'query']
[,][@attachments =] attachments]
[,][@copy_recipients =] 'copy_recipients [;...n]'
[,][@blind_copy_recipients =] 'blind_copy_recipients [;...n]'
[,][@subject =] 'subject']
[,[@type =] 'type']
[,][@attach_results =] 'attach_value']
[,][@no_output =] 'output_value']
[,][@no_header =] 'header_value']
[,][@width =] width]
[,][@separator =] 'separator']
[,][@echo_error =] 'echo_value']
[,][@set_user =] 'user']
[,][@dbuse =] 'database']

其中@recipients是必需的

参数说明：

参数 说明
@recipients 收件人，中间用逗号分开
@message 要发送的信息
@query 确定执行并依附邮件的有效查询，除触发器中的插入表及删除表外，此查询能引用任何对象
@attachments 附件
@copy_recipients 抄送
@blind_copy_recipients 密送
@subject 标题
@attach_results 指定查询结果做为附件发送
@no_header 不发送查询结果的列名
@set_user 查询联接的用户名，默认为Guset
@dbuse 查询所用的数据库，默认为缺省数据库

四、阅读邮件收件箱中的邮件

xp_readmail [][@msg_id =] 'message_number'] [, ][@type =] 'type' [OUTPUT]]
[,[@peek =] 'peek']
[,][@suppress_attach =] 'suppress_attach']
[,][@originator =] 'sender' OUTPUT]
[,][@subject =] 'subject' OUTPUT]
[,][@message =] 'message' OUTPUT]
[,][@recipients =] 'recipients [;...n]' OUTPUT]
[,][@cc_list =] 'copy_recipients [;...n]' OUTPUT]
[,][@bcc_list =] 'blind_copy_recipients [;...n]' OUTPUT]
[,][@date_received =] 'date' OUTPUT]
[,][@unread =] 'unread_value' OUTPUT]
[,][@attachments =] 'attachments [;...n]' OUTPUT])
[,][@skip_bytes =] bytes_to_skip OUTPUT]
[,][@msg_length =] length_in_bytes OUTPUT]
[,][@originator_address =] 'sender_address' OUTPUT]]

参数说明：

参数 说明
@originator 发件人
@subject 主题
@message 信息
@recipients 收件人
@skip_tytes 读取邮件信息时跳过的字节数，用于顺序获取邮件信息段。
@msg_length 确定所有信息的长度，通常与@skip_bytes一起处理长信息

五、顺序处理下一个邮件

xp_findnextmsg [][@msg_id =] 'message_number' [OUTPUT]]
[,[@type =] type]
[,][@unread_only =] 'unread_value'])

六、删除邮件

xp_deletemail {'message_number'}

如果不指定邮件编号则删除收件箱中的所有邮件

七、自动处理邮件

sp_processmail [][@subject =] 'subject']
[,][@filetype =] 'filetype']
[,][@separator =] 'separator']
[,][@set_user =] 'user']
[,][@dbuse =] 'dbname']


SQLMail的原理及实际应用(batman)
SQLMail的原理及实际应用

-概述

----这里所指的SQLMail，是微软公司的关系数据库管理系统
SQLServer所提供的邮件功能。在SQLServer中包含一些扩展存储
过程，使得SQLServer可以通过基于WindowsNT内部的消息应用编
程界面（MAPI）的客户机邮件程序接收和发送邮件消息。SQLMa
il可发送的消息包括文本串，附加文件或SQL语句的执行结果
集。应用SQLMail的扩展存储过程，邮件消息可以从一个触发
器或一个存储过程中发送，并且通过SQLServer提供的任务和警
告等功能，可以根据需要在不同的时间和情况下发送。

----SQLMail工作流程

----SQLMail工作流程主要分为邮件收、发两个过程。

----在SQLServer内可以通过一定方法，调用扩展存储过程xp_s
endmail，将收信人电子邮件地址，标题，信的内容等以参数的
形式传送给xp_sendmail，由它通过客户端MAPI接口将发送邮件任
务交给如MicrosoftExchangeClient或Outlook97等邮件客户端程序，
最终通过邮件服务将邮件发送出去。

----收邮件的过程与此相似。在SQLServer端定期或手工调用x
p_readmail扩展存储过程，通过客户端MAPI接口将存放在邮件服
务器或客户端的邮件及其他信息读入SQL变量中，用来满足特
定的处理需要。
配置SQLMail

----配置SQLMail的首要条件是要有一个电子邮件帐户，不管
是局域网内的MSExchangeServer或MSMail3.x的邮件帐户，还是Int
ernet上的SMTP，POP3等邮件帐户均可。具体配置可分为以下几步
：

----1．为SQLServer创建一个NT域用户帐号，要求具有本地管
理员组的权限。仔细设置帐号口令，保证口令足够复杂，且
设成口令永不过期和用户不得更改口令。

----2．双击控制面板的服务图标，找到MSSQLServer的启动设
置，将UseSystemAccount改为UseThisAccount，将刚才建立的NT帐号
和口令输入。重新启动SQLServer。

----3．在安装SQLServer的服务器上以与启动SQLServer相同的
NT帐号登录，然后安装支持MAPI接口的邮件客户端程序，如MS
ExchangeClient和Outlook97。

----4．打开控制面板的电子邮件选项，建立一个配置文件
（Profile）。用这个配置文件启动邮件客户端程序，反复检验
直至能正常收发邮件。

----5．从SqlEnterpriserManager中，点中相应的服务器图标，从
Server菜单中选SQLMail/Configure，将在第4步中建立的配置文件
名输入。

----6．从SqlEnterpriserManager中，点中相应的服务器图标，从
Server菜单中选SQLMail/Start，如果SqlMail的图标变为绿色，则SQ
LMail成功启动。

----常用的SQLMail扩展存储过程

xp_sendmail@recipient=recipient

[;recipient2;][...;recipientn]]

[,@message=message]

[,@query=query]

[,@attachments=attachments]

[,@copy_recipients=recipient

][;recipient2;][...;recipientn]]]

[,@blind_copy_recipients=recipient

][;recipient2;][...;recipientn]]]

[,@subject=subject]

[,@type=type]

[,@attach_results={'true'|'false'}]

[,@no_output={'true'|'false'}]

[,@no_header={'true'|'false'}]

[,@width=width]

[,@separator=separator]

[,@echo_error={'true'|'false'}]

[,@set_user=user]

[,@dbuse=dbname]

----此存储过程通过客户端MAPI接口发送邮件，内容可以是
文本串，附加文件或SQL语句的执行结果集。

xp_findnextmsg[@msg_id=msg_id][OUTPUT]]

[,@type=type]

[,@unread_only={'true'|'false'}])

----此存储过程在邮箱中查找特定的邮件，并返回一封邮件
的消息ID。

xp_readmail([@msg_id=msg_id][,@type=type][OUTPUT]]

[,@peek={'true'|'false'}]

[,@suppress_attach={'true'|'false'}]

[,@originator=@senderOUTPUT]

[,@subject=@subject_lineOUTPUT]

[,@message=@body_of_messageOUTPUT]

[,@recipients=@recipient_listOUTPUT]

[,@cc_list=@cc_listOUTPUT]

[,@bcc_list=@bcc_listOUTPUT]

[,@date_received=@dateOUTPUT]

[,@unread={'true'|'false'}]

[,@attachments=@temp_file_pathsOUTPUT])

[,@skip_bytes=@bytes_toskipOUTPUT]

[,@msg_length=@length_in_bytesOUTPUT])

----此存储过程从指定的邮件收件箱中读取指定消息ID的邮
件的各项信息。

----xp_deletemail[@msg_id=]msg_id

----从邮件收件箱中删除一封指定消息ID的邮件。

----注释：

----1.存储过程的多个参数间用逗号间隔开，[]内的为可选
参数，每个参数均以@符号加字符串开头，用以区别不同的参
数项。等号后可以是常量，也可以是预先定义好的变量。

----2.如果需要将某个结果值赋予参数中预先定义好的变
量，就必须在该项参数的最后加OUTPUT。

----3.常用参数解释：

----?@recipient=recipient；指定收件人的电子邮件地址。如
果有多个收件人，可以用分号分隔开。

----?@subject=subject；邮件的标题。发送邮件时的默认值为
"SQLServerMessage"。

----?@message=message；邮件的具体内容。

----?@attachments=attachments；邮件挂接的附加文件名。

----?@type=type；基于MAPI定义的消息类型，详细信息参见"
MicrosoftWindowsNTResourceKit"或"MicrosoftMailTechnicalReference"。

----?@query=query；一条SQL可执行语句，其执行结果以正文
或附件的方式随邮件发送。

----?@msg_id=msg_id；对于信箱中的每一封邮件均被分配了

----?@attachments=attachments；邮件挂接的附加文件名。

----?@type=type；基于MAPI定义的消息类型，详细信息参见"
MicrosoftWindowsNTResourceKit"或"MicrosoftMailTechnicalReference"。

----?@query=query；一条SQL可执行语句，其执行结果以正文
或附件的方式随邮件发送。

----?@msg_id=msg_id；对于信箱中的每一封邮件均被分配了
一个特殊的消息ID，用以相互区分。

----?@originator=@sender；读取特定邮件的发送者的邮件地
址。

--基于SQLMail的电子报刊自动处理系统

----这里利用SQLMail简单的实现了一个电子报刊自动处理系
统。每当新的一期电子报刊编辑完成，只需简单追加到发行
数据库publication中，SQLServer会自动定期执行my_publish存储过
程。在my_publish存储过程中，它检查发行数据库，当发现有新
的记录（即新的一期电子报刊）时，就通过逐个从订阅数据库
sub_info中取出订阅人的邮件地址，完成给每个订阅者发送电
子报刊（以邮件形式）的任务。

----电子报刊的订阅与取消也是通过邮件来实现。SQLServer
同样定期执行my_subscibe存储过程。my_subscibe存储过程检查特定
邮箱中的所有邮件，如果存在标题为"subscribe"的邮件，就取
出它的发件人等信息，在订阅数据库中添加一条记录；同样，
如果存在标题为"stopsubscribe"的邮件，就将它的相关记录从
订阅数据库中删除。系统中用到的数据库和存储过程如下所
示。sub_info（订阅者信息数据库）

字段名称类型允许空值含义

emailvarchar(20)NOTNULL订阅者电子邮件地址

sub_datedatetimeNOTNULL订阅时间

othertextNULL订阅者的其他信息

publication（电子报刊出版数据库）

字段名称类型允许空值含义

pub_classchar(10)NOTNULL电子报刊期号

pub_datedatetimeNULL出版日期

titletextNOTNULL本期电子报刊标题

contenttextNOTNULL电子报刊正文

endnotetextNULL附加于报刊的其他信息

flagsmallintNULL1，标志为未出版的新报刊

my_subscribe，用于处理订阅者信息的存储过程。

CREATEPROCEDUREmy_subscribe

AS

declare@msg_idvarchar(64)

declare@subjectvarchar(255)

declare@messagevarchar(255)

declare@originatorvarchar(255)

declare@datevarchar(255)

declare@statusint

declare@mapifailureint

select@mapifailure=0

while(1=1)

begin

/*查找邮件并获取消息ID*/

exec@status=master..

xp_findnextmsg@msg_id=@msg_idOUTPUT

if@status<>0

begin

select@mapifailure=1

break

end

if@msg_idisnullbreak

/*读取邮件的信息到变量中*/

exec@status=master..xp_readmail

@msg_id=@msg_id,

@originator=@originatorOUTPUT,

@subject=@subjectOUTPUT,

@message=@messageOUTPUT,

@date_received=@dateOUTPUT

if@status<>0

begin

select@mapifailure=1

break

end

/*根据邮件标题，在订阅数据库中添加或删除记录*/

execxp_deletemail@msg_id=@msg_id

if@subject='subscribe'

insertintosqlmailsample..

sub_infovalues(@originator,@date,@message)

else

if@subject='stopsubscribe'

deletefromsqlmailsample..sub_infowhereemail=@originator

end/*循环结束*/

if@mapifailure=1

/*错误处理代码*/

else

return(0)

GO

my_publish，用于分发电子报刊的存储过程。

CREATEPROCEDUREmy_publish

AS

declare@recipientvarchar(255)

declare@subjectvarchar(255)

declare@contentvarchar(255)

declare@endnotevarchar(255)

declare@statusint

select@subject=pub_classfrom

sqlmailsample..publicationwhereflag=1

/*如果有新的电子报刊，则开始分发*/

if(@subjectisnotnull)

begin

/*将数据库记录取出，经过适当处理后，存放到变量中*/

select@subject=convert(varchar

(255),title)+'('+pub_class+')',

@content=convert(varchar(255),content)

+convert(varchar(255),endnote)

fromsqlmailsample..publication

whereflag=1

updatesqlmailsample..publication

setflag=0whereflag=1

declarecurcursorforselect

emailfromsqlmailsample..sub_info

opencur

fetchcur

while(@@fetch_status=0)

begin

/*取收件人的电子邮件地址*/

fetchnextfromcurinto@recipient

/*发送电子邮件*/

if@@fetch_status=0

begin

exec@status=master..xp_sendmail

@recipients=@recipient,

@message=@content,

@subject=@subject

if@status<>0

/*错误处理代码*/

end

end/*向所有订阅人发送电子报刊的循环结束*/

closecur

deallocatecur

end

GO

----总之，SQLMail在数据库和电子邮件之间架起了一座沟通

end/*向所有订阅人发送电子报刊的循环结束*/

closecur

deallocatecur

end

GO

----总之，SQLMail在数据库和电子邮件之间架起了一座沟通
的桥梁，为某些特定用途的应用提供了简单高效的解决方案
，值得一试


SQL Mail

返回

SQL Mail

豆豆技术网制作 修改于:2003-5-14 投稿信箱：web@ddhome.net

请记住我们的口号是:让计算机真正大众化,让更多的人了解计算机,使用计算机！！

非本站原创文章可以自由转载、本站原创文章未经同意不得转载！

原创文章版权归本站及作者所有，未经同意不得转载，转载文章必须注明选自豆豆技术网，谢谢合作！


Access 2000 How To's: Calculating Working Days
By David Nishimoto

Overview

This article explains how to use Access 2000 date functions when determining 1) the number of work days representing an interval between two dates, and 2) the projected end working date calculated from a start date and number of hours worked.

The user enters in a start date and actual hours as parameters in the GetEndWorkDay function. The function calculates the date in the future the work should be completed. So, if today is 12/2/2002 and the actual hours is 12, the projected end date would be 12/3/2002.

The second part uses the GetNumberOfWorkDays function to determine what percent of the estimated time actual time represents. Estimated work days is the time interval to complete a task based on a start and end date. Comparing estimated to actual time, we can provide the percent of work completed both under or over allocated percents.

GetEndWorkDay Function

  Public Function GetEndWorkDay(sStartDate, sHours)
    Dim iHoursToDays
    Dim iCount
    Dim bFlag
    Dim sEndDate
    Dim sCheckDate
    Dim iFoundCount
    Dim sDay

      'Assume an eight hour day
      iHoursToDays = round(sHours / 8,0)
      sEndDate = sStartDate
      If iHoursToDays > 1 Then
        bFlag = False
        iCount = 0
        iFoundCount = 0
        Do While bFlag = False
          iCount = iCount + 1
          sCheckDate = DateAdd("d", iCount, sStartDate)
          sDay = Weekday(sCheckDate)
          If sDay <> 1 And sDay <> 7 Then
            sEndDate = sCheckDate
            iFoundCount = iFoundCount + 1
          End If
          If iFoundCount >= iHoursToDays Then
            Exit Do
          End If
        Loop
      End If
      GetEndWorkDay = sEndDate
  End Function
  1. DateAdd returns a date to which a specific time interval has been added. In this sample the interval is "Day".

    List of Interval Settings:
    yyyy = year
    q = quarter
    m = month
    y = day of year
    d = day
    w = weekday
    ww = week
    h = hour
    n = minute
    s = second

  2. Increment through a range of possible work days to find the end work date. The number of work days is determined by dividing the hours by eight. This assumes an eight hour work day. Ignore saturday and sunday as work days. Once the number of found work days equals the work day interval, stop and return the date as the final work date.

GetNumberofWorkDays Function

  Public Function GetNumberOfWorkDays(sStartDate, sEndDate)
    Dim iDays
    Dim iWorkDays
    Dim sDay
    Dim i

    iDays = DateDiff("d", sStartDate, sEndDate)

    iWorkDays = 0

    For i = 0 To iDays
      'First day of the week is sunday
      sDay = Weekday(DateAdd("d", i, sStartDate))
      If sDay <> 1 And sDay <> 7 Then
        iWorkDays = iWorkDays + 1
      End If
    Next
    GetNumberOfWorkDays = iWorkDays
  End Function
  1. DateDiff specifics a number of time intervals between two dates.
  2. DateDiff (interal,date1,date2,firstdayofweek,firstweekofyear)

    Interval
    yyyy = year
    q = quarter
    m = month
    y = day of year
    d = day
    w = weekday
    ww = week
    h = hour
    n = minute
    s = second

    date1 and date2 are used to calculate the interval
    firstdayofweek is sunday unless specified
    firstweekofyear is jan 1 unless specified

  3. Weekday returns a number representing the day of the week.

    Return values are:
    Sunday = 1
    Monday = 2
    Tuesday = 3
    Wednesday = 4
    Thursday = 5
    Friday = 6
    Saturday = 7

  4. First determine the number of days between the two dates. Calculating the number of work days is done by not adding Saturday and Sunday dates. A query extract from a table containing all the holidays for the year could also be added for increased accuracy.


本人平时比较沉默，但朋友们都说我很疯狂—疯狂地学习、疯狂地工作。很久就有写点东西的打算啦，一直懒于打字，近来稍有空闲，决定杜撰此文，献给现些喜欢或不喜欢沉默的朋友。
----疯子自序
网上已经有很多SQL与ORACLE的对比 ，但本人要讲的即不是单纯的SQL，也不是单纯的ORACLE，更不是评价谁好谁坏（意思不大），而是两种数据库之相同和异同，本人曾讲授过SQL与ORACLE的课程，讲SQL时说SQL好，讲ORACLE时又说ORACLE棒，现在终于可以平心而评啦。
估计有人现在会嘿嘿冷笑（又一个误人子弟的骗子），老实说，当初每次讲完课，就有这种感觉—教的人不得其法，学的人不得其道。说点严肃的事吧，据说比尔与艾里森在洗手间相遇，两个又是拥抱，又是KISS，不多久就吵了起来，比尔对查询分析器（SQL QUERY ANALYZE）赞不经绝口，艾里森嘿嘿冷笑，只说了一句话—SQL PLUS内秀。
言归正传，且听我一一道来
001、SQL与ORACLE的内存分配
ORACLE的内存分配大部分是由INIT.ORA来决定的，一个数据库实例可以有N种分配方案，不同的应用（OLTP、OLAP）它的配置是有侧重的。SQL概括起来说，只有两种内存分配方式：动态内存分配与静态内存分配，动态内存分配充许SQL自己调整需要的内存，静态内存分配限制了SQL对内存的使用。
002、SQL与ORACLE的物理结构
总得讲，它们的物理结构很相似，SQL的数据库相当于ORACLE的模式（方案），SQL的文件组相当于ORACLE的表空间，作用都是均衡DISK I/O，SQL创建表时，可以指定表在不同的文件组，ORACLE则可以指定不同的表空间。
CREATE TABLE A001（ID DECIMAL（8，0）） ON [文件组]
--------------------------------------------------------------------------------------------
CREATE TABLE A001（ID NUMBER（8，0）） TABLESPACE 表空间
注：以后所有示例，先SQL，后ORACLE
003、SQL与ORACLE的日志模式
SQL对日志的控制有三种恢复模型：SIMPLE、FULL、BULK-LOGGED；ORACLE对日志的控制有二种模式：NOARCHIVELOG、ARCHIVELOG。SQL的SIMPLE相当于ORACLE的NOARCHIVELOG，FULL相当于ARCHIVELOG，BULK-LOGGED相当于ORACLE大批量数据装载时的NOLOGGING。经常有网友抱怨SQL的日志庞大无比且没法处理，最简单的办法就是先切换到SIMPLE模式，收缩数据库后再切换到FULL，记住切换到FULL之后要马上做完全备份。
004、SQL与ORACLE的备份类型
SQL的备份类型分的极杂：完全备份、增量备份、日志备份、文件或文件组备份；ORACLE的备份类型就清淅多啦：物理备份、逻辑备份；ORACLE的逻辑备份（EXP）相当于SQL的完全备份与增量备份，ORACLE的物理备份相当于SQL的文件与文件组备份。SQL的各种备份都密切相关，以完全备份为基础，配合其它的备份方式，就可以灵活地备分数据；ORACLE的物理备份与逻辑备份各司其职。SQL可以有多个日志，相当于ORACLE日志组，ORACLE的日志自动切换并归档，SQL的日志不停地膨胀……SQL有附加数据库，可以将数据库很方便地移到别一个服务器，ORACLE有可传输表空间，可操作性就得注意啦。
005、SQL与ORACLE的恢复类型
SQL有完全恢复与基于时间点的不完全恢复；ORACLE有完全恢复与不完全恢复，不完全恢复有三种方式：基于取消的、基于时间的、基于修改的（SCN）的恢复。不完全恢复可以恢复数据到某个稳定的状态点。
006、SQL与ORACLE的事务隔离
SET TRANSACTION ISOLATION LEVEL
SQL有四种事务隔离级别：
READ COMMITTED、READ UNCOMMITTED、REPEATABLE READ、SERIALIZABLE
ORACLE有两种事务隔离级别
READ COMMITTED、SERIALIZABLE
SQL虽然有四种事务隔离，事务之间还是经常发生阻塞；ORACLE则利用回退段很好地实现了事务隔离，不会产生阻塞。SQL与ORACLE如果发生死锁，都可以很快地识别并将之处理掉。
007 SQL与ORACLE的外键约束
SQL的外键约束可以实现级联删除与级联更新，ORACLE则只充许级联删除。
CREATE TABLE A001（ID INT PRIMARY KEY，NAME VARCHAR（20））
CREATE TABLE A002（ID INT REFERENCES A001（ID）ON DELETE CASCADE ON UPDATE CASCADE，AGE TINYINT）
CREATE TABLE A001（ID INT PRIMAY KEY，NAME VARCHAR2（20））
CREATE TABLE A002（ID INT REFERENCES A001（ID）ON DELETE CASCADE，AGE NUMBER（2，0））
008、SQL与ORACLE的临时表
SQL的临时表用#或##开头，使用完后自动释放，ORACLE的临时表则存在数据库中，每个会话的数据都互不干涉。oracle临时表中的纪录可以被定义为自动删除（分commit方式和transaction方式），而表结构不会被自动删除。临时表的DML，DDL操作和标准表一样。
CREATE TABLE #TEMP（ID INT，NAME VARCHAR（20））
-------------------------------------------------------
CREATE GLOBAL TEMPORARY TABLE TEMP（ID INT，VARCHAR2（20））
009、SQL与ORACLE的类型转换
SQL常用类型转换函数有：CAST、CONVERT、STR
ORACLE常用类型转换函数有：TO_CHAR、TO_NUMBER、TO_DATE
SELECT CONVERT（VARCHAR（20），GETDATE（），112）
------------------------------------------------------------------------------------------------
SELECT TO_CHAR（SYSDATE，‘YYYYMMDD’）FROM DUAL
010、SQL与ORACLE的自动编号
SQL的编号一般由IDENTITY字段来提供，可以灵活地设定种子值，增量，取值范围有BIGINT、INT、SMALLINT、TINYINT、DEIMAL等；ORACLE的编号一般由SEQUENCE来提供，由NEXTVAL与CURVAL函数从SEQUENCES取值。
CREATE TABLE A003（ID INT IDENTITY（-9999，9），NAME VARCHAR（20））
-------------------------------------------------------------------------------------------------------
CREATE SEQUENCE SEQ_001 START 9999 INCREMENT BY 9
CREATE TABLE A004（ID INT）
INSERT INTO A004 VALUES（SEQ_001.NEXTVAL）
INSERT INTO A004 VALUES（SEQ_001.CURVAL+1）
011、SQL与ORACLE的分区表
从严格意思上来讲，SQL还没有分区表，它的分区表是以UNION为基础，将多个结果集串起来，实际上是视图；ORACLE的分区表有多种：PARTITION BY RANGE、PARTITION BY HASH、PARTITION BY LIST，其它就是混合分区，以上三种基础分区的混合使用。当然ORACLE也可以象SQL那样分区视图。
CREATE TABLE A1999（ID INT，NAME VARCHAR（20））
CREATE TABLE A2000（ID INT，NAME VARCHAR（20））
CREATE VIEW V_PART AS
SELECT * FROM A1999 UNION SELECT * FROM A2000
--------------------------------------------------
CREATE TABLE A_PART1(ID INT,NAME VARCHAR2(20))
PARTITON BY RANGE(ID)(
PARTITION P1 VALUES LESS THEN (2000000) PATITION P2 VALUES LESS THEN (MAXVALUE))
CREATE TABLE A_PART2(ID INT,NAME VARCHAR2(20))
PARTITION BY HASH(ID) PARTITIONS 2 STORE IN (USERS01,USERS02)
CREATE TABLE A_PART3(ID INT,NAME VARCHAR2(20))
PARTITION BY LIST(ID)(
PARTIION P1 VALUES(‘01’,’03’,’05’) PARTITON P2 VALUES(‘02’,’04’))
012、SQL与ORACLE的存储过程
SQL的存储过程可以很方便地返回结果集，ORACLE的存储过程只有通过游标类型返回结果集，这种结果集ADO不可识别，如果想使用ORACLE存储过程的结果集，只有使用ODAC开发包（DELPHI/BCB控件组 www.51delphi.com 与www.playicq.com有下载），SQL的过程参数如果是字符必须指定参数长度，ORACLE的过程则不充许指定字符参数的长度。
CREATE PROCEDURE UP_001(@ID INT) AS
BEGIN
SELECT ID ,SUM(QTY) FROM A_TEST WHERE ID=@ID GROUP BY @ID
END
------------------------------------------------------------
CREATE OR REPLACE PACKAGE UP_002 AS
TYPE MYCURSOR IS REF CURSOR；
FUNCTION GETRECORD RETURN MYCURSOR；
END；
CEEATE OR REPLACE PACKAGE BODY UP_002 AS
FUNCTION GETRECORD RETURN MYCURSOR AS
MC MYCURSOR；
SL VARCHAR2（999）；
BEGIN
OPEN MC FOR SELECT * FROM A001；
RETURN MC；
END；
END；
ORACLE的存储函数返回结果这么艰难，但SQL的触发器竟然也充许返回结果集就令人费解啦，触发器的调试比较麻烦，在SQL实际开发中，一般都将触发器要执行的代码放到过程中进行调试，在查询分析器中可以对过程进行设断点调试。
013、SQL与ORACLE的触发器
触发器的事务是引起触发动作事务的延续，在SQL的触发器中是可以无BEGIN TRAN而可以直接COMMIT TRAN的。SQL的触发器是表级触发器，DML影响一行或无数行触发动作只触发一次，ORACLE分表级触发器与行级触发器，触发的粒度更细腻一些，SQL在处理多行时就必须使用CURSOR啦。ORACLE使用INSERTING、DELTING、UPDATING判断执行了什么DML操作，SQL只有判断INSERTED、DELETED的记录数来判断执行了什么操作，只有INSERTED映象表记录数大于0表示INSERT，只有DELETED映象表记录数大于0表示DELETE，若两个表记录数都大于0表示UPDATE。
用SQL的触发器实现级联添加、级联删除、级联更新
CREATE TABLE A1（ID INT，NAME VARCHAR（20））
CREATE TABLE A2（ID INT，NAME VARCHAR（20））
CREATE TRIGGER TRI_A1_INS ON A1
FOR INSERT , DELETE , UPDATE AS BEGIN
DECLARE @I INT,@D INT,@ID INT
SELECT @I=COUNT(*) FROM INSERTED
SELECT @D=COUNT(*) FROM DELETED
--IF (@I>0 AND @D>0) 执行更新，由于用到游标，故略去
IF @I>0
INSERT INTO A2 SELECT * FROM INSERTED
IF @D>0
DELETE FROM A2 WHERE ID=@ID
END
----------------------------------------------------------------------
用ORACLE的触发器实现级联添加、级联删除、级联更新
CREATE OR REPLACE TRI_A1_INS
AFTER INSERT OR DELETE OR UPDATE ON A1
FOR EACH ROW
BEGIN
IF INSERTING THEN
INSERT INTO A2 SELECT * FROM ：NEW；
END IF；
IF DELETING THEN
DELETE FROM A2 WHERE ID = ：OLD.ID ；
END IF；
IF UPDATING THEN
UPATE A2 SET ID = ：NEW.ID , NAME = ：NEW.NAME WHERE ID = ：OLD.ID ；
END IF；
END
014、SQL与ORACLE的游标
SQL的游标用@@FETCH_STATUS判断是否还有数据，ORACLE的游标用%FOUND、%NOTFOUND来判断游标是否结束，游标分服务端游标与客户端游标，在存储过程、函数、触发器中声明的游标为服务端游标，其它处声明的游标为客户端游标，游标需要使用较多的内存，但它比临时表的性能要优一些，因为临时表占用的是DISK I/O，DISK I/O应该比服务器的内存要更珍贵一些吧。
015、SQL与ORACLE的重复记录删除
好的数据库设计重复记录是不存在的，如果有重复记录如何删除呢？SQL可以使用SET ROWCOUNT N设置客户端缓冲区的记录来删除，ORACLE可以使用ROWID来进行，都必须进行一些简单的编程，SQL可以做用过程，更通用一些，ORACLE如果想做得通过不太容易，字段小些会更方便处理一些。
DECLARE @M INT
SELECT @M=COUNT(*) FROM A_TEST WHERE ID=X
SELECT @M=@M-1
SET ROWCOUNT @M --限制客户端缓冲区的记录数
DELETE FROM A_TEST WHERE ID=X
SET ROWCOUNT 0 --取消限制
说明 删除某条记录的重复值，如果想清除表的所有重值要使用游标，取得所有的X
---------------------------------------------------------------------
DELETE FROM A_TEST A WHERE ROWID!=(SELECT MAX(ROWID) FROM A_TEST B
WHERE A.ID=B.ID AND A.NAME=B.NAME)
说明 当数据量较大时，这种方法将会使用系统大量的资源
016 SQL与ORACLE的对象加密
SQL与ORACLE的某些对象如过程、视图、函数、触发器可能涉及商业，开发商通常希望对这些对象进行加密，SQL的加密方法在创建时带上WITH ENCRYPTION，ORACLE的对象加密明显复杂一些，要使用WRAP工具，在ORACLE的BIN目录内。
017 SQL与ORACLE的表生成SQL语句
SQL与ORACLE的表如何才导成SQL语句呢？如果一定要编程实现，SQL需要将其它数据类型的字段转换成VARCHAR类型，ORACLE则可以隐式进行数据类型转换。
CREATE TABLE A_SQL(ID INT,NAME VARCHAR(20)) –假如有两万记录
SELECT ‘INSERT INTO A_SQL VALUES(‘+CAST(ID
AS VARCHAR(20))+’,’’’+NAME+’’’)’ FROM A_SQL
----------------------------------------------------------------
SELECT ‘INSERT INTO A_SQL VALUES(‘||ID||’,’||’’’||NAME||’’’||’)’
FROM A_SQL
说明 SQL的字符串连接用+号，ORACLE字符串连接用||，单引号可以做转义符。
018、SQL与ORACLE的动态SQL
SQL与ORACLE都支持动态SQL语句，SQL用EXEC（）执行的动态SQL语句，ORACLE用EXECUTE IMMEDIATE执行动态SQL。动态SQL的效率要比非动态SQL性能差，但使用起来非常灵活，可以根据不同条件执行不同的任务。
DECLARE @SQL VARCHAR(99)
SELECT @SQL=’declare @m int select @m=count(*) from sysobjects select @m’
EXEC(@SQL)
--------------------------------------------
DECLARE
S VARCHAR2(99);
BEGIN
S:='SELECT COUNT(*) FROM '||' USER_TABLES';
EXECUTE IMMEDIATE S;
END;
19、返回记录集中前N条记录的语法？
SQL只有使用TOP，ORACLE可以使用ROWNUM
SELECT TOP N * FROM 记录集（表，视图，子查询）
---------------------------------------------
SELECT * FROM 记录集 WHERE ROWNUM<=N
20 如何返回记录集中相临两记录之间某字段的差值？
CREATE TABLE A001(ID INT,QTY INT)
INSERT INTO A001 VALUES(1,20)
INSERT INTO A001 VALUES(4,10)
SELECT IDENTITY(INT,1,1) CODE,QTY INTO #X FROM A001
SELECT B.QTY-A.QTY FROM #X A,#X B WHERE A.CODE=B.CODE-1
DROP TABLE #X
--------------------------------------------------------
CREATE TABLE A002（ID INT）
INSERT INTO A002 VALUES（1）
INSERT INTO A002 VALUES（9）
WITH A AS (SELECT ROWNUM RN,ID FROM A002)
SELECT A2.ID-A1.ID FROM A A2,A A1 WHERE A2.RN=A1.RN-1
说明 虽然语法大不相同，但最大的特点是两者都使用了自连接技术。
21 如何返回任意某个范围之间的记录集？
CREATE TABLE A03(ID INT)
DECLARE @I INT
SELECT @I=1
WHILE @I<1000 BEGIN
INSERT INTO A03 VALUES(@I)
SELECT @I=@I+1
END
--前部分是创建环境，后一部分是实现方法，比较牵强
SELECT IDENTITY(INT,1,1) CODE,ID INTO #X FROM A03
SELECT ID FROM #X WHERE CODE BETWEEN 10 AND 20
DROP TABLE #X
------------------------------------------------------
BEGIN
FOR I IN 1..999 LOOP
INSERT INTO A03 VALUES(I);
END LOOP;
END;

SELECT * FROM A03 WHERE ROWNUM<20
MINUS
SELECT * FROM A03 WHERE ROWNUM<10;
说明 在数据提取方面，ORACLE有ROWID，ROWNUM使之有相当强的优势，SQL只有使用函数IDENTITY来构建一个临时表，这样来说还不好使用CURSOR来性能会好一些。通过这个例子，大家还可以看出SQL与ORACLE的程序结构，ORACLE更严谨、人性化一些。
22、表A04中的含有A、B、C、D四个字段，当按A字段分组后，如果D等1，则只统计B的值，如果D等0,则只统计C的值。
CREATE TABLE A04(A VARCHAR(20),B INT,C INT,D INT)
INSERT INTO A04 VALUES('01',20,7,'0')
INSERT INTO A04 VALUES('01',10,8,'1')
INSERT INTO A04 VALUES('02',20,7,'1')
INSERT INTO A04 VALUES('02',10,8,'0')
SELECT A,SUM(CASE D WHEN 1 THEN B WHEN 0 THEN C END) FROM A04 GROUP BY A
---------------------------------------------------------------
SELECT A,SUM(DECODE(D,1,B,0,C)) FROM A04 GROUP BY A
说明 ORACLE 9I可以使用CASE语句来实现这种操作，但也可以用DECODE来作统计，使用CASE比DECODE提供了更为强大的功能，但DECODE的语法显然比CASE WHEN THEN END要简洁得多。
23、如何删除数据库所有表？（ORACLE则是删除模式所有表）
DECLARE @SQL VARCHAR(99),@TBL VARCHAR(30),@FK VARCHAR(30)
DECLARE CUR_FK CURSOR LOCAL FOR
SELECT OBJECT_NAME(CONSTID),OBJECT_NAME(FKEYID) FROM SYSREFERENCES
--删除所有外键
OPEN CUR_FK
FETCH CUR_FK INTO @FK,@TBL
WHILE @@FETCH_STATUS =0
BEGIN
SELECT @SQL='ALTER TABLE '+@TBL+' DROP CONSTRAINT '+@FK
EXEC(@SQL)
--SELECT @SQL='DROP TABLE '+@TBL
FETCH CUR_FK INTO @FK,@TBL
END
CLOSE CUR_FK
DECLARE CUR_FKS CURSOR LOCAL FOR
SELECT NAME FROM SYSOBJECTS WHERE XTYPE='U'
OPEN CUR_FKS
FETCH CUR_FKS INTO @TBL
WHILE @@FETCH_STATUS =0
BEGIN
SELECT @SQL='DROP TABLE ['+@TBL+']'
EXEC(@SQL)
FETCH CUR_FKS INTO @TBL
END
CLOSE CUR_FKS
----------------------------------------------------------------
DECLARE
S VARCHAR2(99);
CURSOR CUR_F IS SELECT CONSTRAINT_NAME,TABLE_NAME FROM USER_CONSTRAINTS
WHERE CONSTRAINT_TYPE='R';
CURSOR CUR_T IS SELECT TABLE_NAME FROM USER_TABLES;
BEGIN
FOR V IN CUR_F LOOP
S:='ALTER TABLE '||V.TABLE_NAME||' DROP CONSTRAINT '||V.CONSTRAINT_NAME;
EXECUTE IMMEDIATE S;
END LOOP;
FOR T IN CUR_T LOOP
S:='DROP TABLE '||T.TABLE_NAME;
EXECUTE IMMEDIATE S;
END LOOP;
END;
说明 SQL删除数据库时，用到了两个系统表：SYSREFERENCES、SYSOBJECTS，前一个可以获得所有外键键信息，后者可以获得所有表的信息，在删除表时还在表名上加了一对中括号，即使用表名含有空格键或其它特殊这符也可以顺利删除。
在ORACLE中，要删除模式的所有表，方法和SQL差不多，需要用到的数据字典也有两个：USER_CONSTRAINTS、USER_TABLES；USER_CONSTRAINTS中CONSTRAINT_TYPE值等于R表示是外键，同样也要用到CURSOR与动态SQL，这里提醒一下大家，FOR … LOOP内的变量变量是FOR … LOOP声明的，可以ORACLE的程序结构比SQL简洁。
24、如何统计数据库所有用户表的记录数（ORACLE统计模式内所有表的记录数）？
CREATE TABLE #TMP (QTY INT)
CREATE TABLE #TMP1 (TBL VARCHAR(30),QTY INT)
DECLARE @SQL VARCHAR(99),@TBL VARCHAR(30),@QTY INT
DECLARE CUR_FKS CURSOR LOCAL FOR
SELECT NAME FROM SYSOBJECTS WHERE XTYPE='U'
OPEN CUR_FKS
FETCH CUR_FKS INTO @TBL
WHILE @@FETCH_STATUS =0
BEGIN
SELECT @SQL='SELECT COUNT(*) FROM '+@TBL
INSERT INTO #TMP EXEC(@SQL)
SELECT @QTY=QTY FROM #TMP
INSERT INTO #TMP1 VALUES(@TBL,@QTY)
DELETE FROM #TMP
FETCH CUR_FKS INTO @TBL
END
CLOSE CUR_FKS
SELECT * FROM #TMP1
---------------------------------------------------------------
DESC DBMS_UTILITY
EXECUTE DBMS_UTILITY.ANALYZE_SCHEMA('SCOTT','COMPUTE');
SELECT TABLE_NAME,NUM_ROWS FROM USER_TABLES;
说明 SQL的EXEC功能可谓十分强大，竟然可以和INSERT INTO合用，将结果集存入一个表中，MS可真牛。ORACLE就只好用个偷懒的方法，首先将要统计的模式进行统计分析，在数据字典中就记载了每个表的行数，ORACLE很简单吧。
25、SQL与ORACLE快速建表的方法？
SELECT * INTO 新表名称 FROM 子查询|表名
-----------------------------------------
CREATE TABLE 新表名称 AS 子查询
说明 快速建表可以有效地消除碎片，速度极快。
26、如何实现有一组有规则的编号（如200305310001…200305310999）?
DECLARE @I INT,@C VARCHAR(20)
SELECT @I=1
WHILE @I<1000 BEGIN
SELECT @C=CASE WHEN @I<10 THEN '000'+CAST(@I AS CHAR(1))
WHEN @I BETWEEN 10 AND 99 THEN '00'+CAST(@I AS CHAR(2))
WHEN @I BETWEEN 100 AND 999 THEN '0'+CAST(@I AS CHAR(3))
END
SELECT @C=CONVERT(VARCHAR(20),GETDATE(),112)+@C
SELECT @C --在查询分析器中输出
SELECT @I=@I+1
END
---------------------------------------------------------
DECLARE
C VARCHAR2(20);
BEGIN
FOR I IN 1 .. 999 LOOP
DBMS_OUTPUT.PUT_LINE(TO_CHAR(SYSDATE,'YYYYMMDD')||LPAD(I,4,'0'));
END LOOP;
END;
说明 虽然都可以实现，但ORACLE的LPAD果然身手不凡，可怜的MS还没有类似LPAD的函数，只有用CASE进行判断组合，真得很蠢，如果你有好的办法，请明示，甚至连循环结构，SQL稍也不慎，就死循环啦（如果注释掉加蓝显示那条语句的话）。
27、关于SQL与ORACLE的分布式结构
SQL在分布式方面做得不错，不仅提供了链接服务器的方式供初级用户使用，还提供了OPENDATASOURCE、OPENXML、OPENQUERY、OPENROWSET等行集函数，可以方便地通过SQL语句从*.TXT、*.XLS、*.XML、*.MDB、*.DBF等介质获取数据，还可以从ORACLE、DB2、SYBASE等数据库获取数据；ORACLE在同构数据库之间提供了DB LINK，异构数据库之间提供了透明网关软件。
28、现在有三个表，结构如下
Score(FScoreId 成绩记录号,FSubID 课程号,FStdID 学生号,FScore 成绩)
student:（FID 学生号，FName 姓名）
subject:（FSubID 课程号，FSubName 课程名）,
怎么能实现这个表：
姓名 英语 数学 语文 历史
张萨 78 67 89 76
王强 89 67 84 96
李三 70 87 92 56
李四 80 78 97 66
SELECT A.FNAME AS 姓名,
英语 = SUM(CASE B.FSUBNAME WHEN '英语' THEN C.FSCORE END),
数学 = SUM(CASE B.FSUBNAME WHEN '数学' THEN C.FSCORE END),
语文 = SUM(CASE B.FSUBNAME WHEN '语文' THEN C.FSCORE END),
历史 = SUM(CASE B.FSUBNAME WHEN '历史' THEN C.FSCORE END)
FROM STUDENT A, SUBJECT B, SCORE C
WHERE A.FID = C.FSTDID AND B.FSUBID = C.FSUBID GROUP BY A.FNAME
------------------------------------------------------------------------
SELECT A.FNAME AS 姓名,
英语 = SUM（DECODE（B.FSUBNAME,’ 英语’,C.FSORE）），
数学 = SUM（DECODE（B.FSUBNAME,’ 数学’,C.FSORE）），
语文 = SUM（DECODE（B.FSUBNAME,’ 语文’,C.FSORE）），
历史 = SUM（DECODE（B.FSUBNAME,’ 历史’,C.FSORE）），
FROM STUDENT A, SUBJECT B, SCORE C
WHERE A.FID = C.FSTDID AND B.FSUBID = C.FSUBID GROUP BY A.FNAME
说明 这个案例主要是运用CASE与DECODE，当然也涉及GROUP BY的用法。
29、有两个表，用一条SQL语句算出商品A,B目前还剩多少？表结构如下：
商品名称mc 商品总量sl 表一(AAA)
A 100
B 120
商品名称mc 出库数量sl 表二(BBB)
A 10
A 20
B 10
B 20
SELECT TA.商品名称,A-B AS 剩余数量 FROM
(SELECT 商品名称,SUM(商品总量) AS A FROM AAA GROUP BY 商品名称)TA,
(SELECT 商品名称,SUM(出库数量) AS B FROM BBB GROUP BY 商品名称)TB
WHERE TA.商品名称=TB.商品名称
----------------------------------------------------------
SELECT 商品名称,SUM（商品总量）剩余数量 FROM
（SELECT * FROM AAA
UNION ALL
SELECT 商品名称,-出库数量 FROM BBB）A GROUP BY 商品名称
30、如何将A表的某个字段更新到表B的某个字段？
UPDATE A SET QTY=B.QTY FROM B WHERE A.CODE=B.CODE
---------------------------------------------------
UPDATE A SET QTY=(SELECT QTY FROM B WHERE B.CODE=A.CODE)
说明 这两道题在语法上SQL与ORACLE没有发别，只不过是两种思路而已。
31、有一个商品销售表，记载了某月商品的销售数量，现在要为所有商品排名次，放到一个单独的字段中，就是说达到右边显示的效果，如何作？
BU1032 5 NULL BU1032 5 2
PS2091 3 NULL PS2092 3 3
PC8888 50 NULL PC8888 50 1
UPDATE X SET ORD=(SELECT COUNT(*)+1 FROM X B WHERE B.QTY>X.QTY)
----------------------------------------------------------------
SELECT CODE,QTY,RANK() OVER (ORDER BY QTY) ORD FROM A_TEST
说明 SQL中的排序是通过UPDATE更新，然后再显示出来，而ORACLE使用了RANK OVER函数，直接将数据集显示出来，而且RANK OVER函数还可以通过PARTITION BY对分组中的数据进行排序。
32、SQL与ORACLE的文件结构
SQL文件被格式化为8K为单位的页，每8个相邻的页称为盘区（64K），若该盘区分配给一个对象，称为一致盘区，若分配给多个对象等为混合盘区，SQL有全局分配页面、数据页面、索引页页、BLOB页面、TEXT页面。ORACLE的文件最小逻辑单位是由INIT.ORA中的BLOCK_SIZE的值决定的，可以取2K、4K、6K、8K、16K、32K等，ORACLE的盘区是由一些块组成的，ORACLE的段是由盘区组成的，ORACLE有数据段、索引段、回退段（UNDO段）、临时段、CLOB/BLOB段、CLUSTER段等。
33、SQL与ORACLE如何取得一个全局唯一标识标（GUID）
SELECT NEWID（）
----------------------------------
SELECT SYS_GUID() FROM DUAL
34、本人有一张表单, 要求统计COL1~COL6中不等于2的列的个数，数据如下：
————————————————————————————————
ROW_ID | COL1 | COL2 | COL3 | COL4 | COL5 | COL6 |
1 | 2 | 1 | 1 | 2 | 3 | 2 |
2 | 1 | 1 | 2 | 2 | 2 | 2 |
3 | 2 | 3 | 2 | 2 | 1 | 2 |
4 | 2 | 2 | 2 | 2 | 1 | 2 |
5 | 1 | 2 | 2 | 2 | 2 | 2 |
6 | 2 | 2 | 2 | 2 | 2 | 1 |
————————————————————————————————
要求结果如下：
—————————
ROW_ID | COUNT |
1 | 3 |
2 | 2 |
3 | 2 |
4 | 1 |
5 | 1 |
6 | 1 |
SELECT ROW_ID,(6-(CASE WHEN COL1=2 THEN COL1 / 2 ELSE 0 END)
-(CASE WHEN COL2=2 THEN COL2 / 2 ELSE 0 END)
-(CASE WHEN COL3=2 THEN COL3 / 2 ELSE 0 END)
-(CASE WHEN COL4=2 THEN COL4 / 2 ELSE 0 END)
-(CASE WHEN COL5=2 THEN COL5 / 2 ELSE 0 END)
-(CASE WHEN COL6=2 THEN COL6 / 2 ELSE 0 END))AS COUNT FROM TABLE_A
说明 本例摘自WWW.DELPHIBBS.COM，有名的DELPHI开发网站，本人不拥有版权。该SQL的实现方法与ORACLE的实现写法完全一样，不在多述。
35、有一客户表，数据如下：
客户 日期 资金
F001 2003-03-05 123.00
F002 2003-03-04 1223.00
F002 2003-03-02 1123.00
F003 2003-03-05 1231.00
F003 2003-03-04 1232.00
要求选出每个客户最新的哪条记录 组成一个结果集，结果如下：
F001 2003-03-05 123.00
F002 2003-03-04 1223.00
F003 2003-03-05 1231.00
实现方法：
SELECT A.客户, B.日期, A.资金 FROM 客户资金表 A,
(SELECT 客户, MAX(日期) 日期 FROM 客户资金表 GROUP BY 客户 ) B
WHERE A.客户 = B.客户 AND A.日期 = B.日期
说明 ORACLE的写法与SQL一样,本例也摘自WWW.DELPHIBBS.COM，本人不拥有版权。
36 现在看一个高难度的作业，后来解决办法和本例不同，请看需求。
视图1 CITYWATER_VIEW
行政区划名称 城市用水量（亿M3） 。。。
北京市 15000 …
上海市 9000 …
天津市 5400 …
重庆市 9500 …

表2 CODE
区划 代码
北京市 100000
上海市 200000
天津市 300000
表3 CITYWATER
代码 城市用水
100000 15000
200000 9000
300000 5400
表1 DICTIONARY
字段别名 字段全名
区划 行政区划名称
代码 行政区划代码
城市用水 城市用水量（亿M3）

表1-2是数据库public中的基表,表3是数据库water中的基表；在数据库water中创建视图1，用T-SQL语句怎样实现？把查询结果的“字段别名”修改为视图中的“字段全名”，如果采用T-SQL中的常用修改列标题方式（SELECT column_name AS expression或者SELECT expression= column_name ），很烦，每个基表里的字段太多，并且基表有近200个，字段近3000个。
说明：其实现在要作的就是将表3中的“代码“、“城市用水”替代成表1中的行政区划代码、城市用户量（亿M3）等。
CREATE VIEW V_GOD
AS SELECT A.[100000],B.[310000],B.[114011],B.[114111],B.[114421],B.[114311],B.[114321] FROM CODE A,FA01P B WHERE A.[200000]=B.[200000]
DECLARE CUR_COL CURSOR LOCAL FOR
SELECT NAME FROM SYSCOLUMNS WHERE ID=OBJECT_ID('V_GOD')
DECLARE @COL VARCHAR(20),@SQL VARCHAR(999),
@COL_TOTAL VARCHAR(8000),@ALIAS VARCHAR(99),
@SOURCE VARCHAR(8000)
OPEN CUR_COL
FETCH CUR_COL INTO @COL
WHILE @@FETCH_STATUS=0
BEGIN
SELECT @ALIAS=字段名 FROM DICTIONARY WHERE 段码=@COL
IF @COL_TOTAL IS NULL
SELECT @COL_TOTAL=@ALIAS
ELSE
SELECT @COL_TOTAL=@COL_TOTAL+','+@ALIAS
FETCH CUR_COL INTO @COL
END
CLOSE CUR_COL
SELECT @SOURCE=RTRIM(TEXT) FROM SYSCOMMENTS WHERE ID=OBJECT_ID('V_GOD')
SELECT @SOURCE=RTRIM(SUBSTRING(@SOURCE,CHARINDEX('AS',@SOURCE),LEN(@SOURCE)))
SELECT @SOURCE='ALTER VIEW V_GOD('+@COL_TOTAL+') '+@SOURCE
EXEC(@SOURCE)
说明 由于该实例需要的表有两个已没有记录，所以大家只有看看T-SQL的语法及动态SQL的编写，ORACLE也类似。
37、如何用SQL操作一段XML文件？
CREATE PROCEDURE UP_XML_TEST(@DOC VARCHAR(7999))
AS
BEGIN
DECLARE @IDOC INT
EXEC SP_XML_PREPAREDOCUMENT @IDOC OUTPUT, @DOC
SELECT *
FROM OPENXML (@IDOC, '/ROOT/DATASET/BOOKS',2)
WITH(TITLE VARCHAR(32) 'TITLE',
AUTHOR VARCHAR(20) 'AUTHOR',
PRICE DECIMAL(9,2) 'PRICE')
EXEC SP_XML_REMOVEDOCUMENT @IDOC
END

CREATE FUNCTION UF_XML_TEST(@DOC VARCHAR(7999))
RETURNS @T TABLE(TITLE VARCHAR(32),
AUTHOR VARCHAR(20),
PRICE DECIMAL(9,2))
AS
BEGIN
DECLARE @IDOC INT
EXEC SP_XML_PREPAREDOCUMENT @IDOC OUTPUT, @DOC
INSERT INTO @T SELECT *
FROM OPENXML (@IDOC, '/ROOT/DATASET/BOOKS',2)
WITH(TITLE VARCHAR(32) 'TITLE',
AUTHOR VARCHAR(20) 'AUTHOR',
PRICE DECIMAL(9,2) 'PRICE')
EXEC SP_XML_REMOVEDOCUMENT @IDOC
RETURN
END

DECLARE @DOC VARCHAR(7999)
SELECT @DOC=
'<ROOT>
<DATASET>
<BOOKS>
<TITLE>DELPHI</TITLE>
<AUTHOR>ABC</AUTHOR>
<PRICE>38.00</PRICE>
</BOOKS>
<BOOKS>
<TITLE>MIDAS</TITLE>
<AUTHOR>DEF</AUTHOR>
<PRICE>26.00</PRICE>
</BOOKS>
</DATASET>
</ROOT>'
EXEC UP_XML_TEST @DOC
--SELECT * FROM DBO.UF_XML_TEST(@DOC)
说明 用过程可以方便地对XML进行操作，但编写成FUNCTION时就报错，大概MS的函数内部不充许执行OPENXML等这类行集函数。另一个重要的问题是，SQL的这种语法竟然不支持汉字字串，真是要命。
38、使用DBMS_REPAIR检测与修复破损的BLOCK？
ADMIN_TABLES 提供管理函数修复或孤立关键表，包含创建、净化与删除函数。
CHECK_OBJECT 检测并报告表或索引的破损块。
DUMP_ORPHAN_KEYS 导出破损块的数据
FIX_CORRUPT_BLOCKS 在CHECK_OBJECT检测出的破损块上做标记
REBUILD_FREELISTS 重建对象的FREELISTS
SKIP_CORRUPT_BLOCKS 设置在表或索引扫描时是否不扫描被做了破损标记的块。
SEGMENT_FIX_STATUS 整理BITMAP实体上的破损标志
上表列举了DBMS_REPAIR包所有的过程，下边将对这些过程要引入的参数的枚举值进行
说明，这引些参数将在过程应用中起决定作用。
object_type TABLE_OBJECT, INDEX_OBJECT, CLUSTER_OBJECT
action CREATE_ACTION, DROP_ACTION, PURGE_ACTION
table_type REPAIR_TABLE, ORPHAN_TABLE
flags SKIP_FLAG, NOSKIP_FLAG
SQL> EXEC DBMS_REPAIR.ADMIN_TABLES('SCOTT.EMP',DBMS_REPAIR.REPAIR_TABLE,-
DBMS_REPAIR.CREATE_ACTION,'USERS');
ORA-24129: 表名 SCOTT.EMP 没有以前缀 REPAIR_ 开始
SQL> EXEC DBMS_REPAIR.ADMIN_TABLES('REPAIR_EMP',DBMS_REPAIR.REPAIR_TABLE,-
DBMS_REPAIR.CREATE_ACTION,'USERS');
SQL> SELECT OBJECT_NAME FROM REPAIR_EMP;
SQL> EXEC DBMS_REPAIR.ADMIN_TABLES('ORPHAN_EMP',DBMS_REPAIR.ORPHAN_TABLE,-
DBMS_REPAIR.CREATE_ACTION,'USERS');
SQL> SELECT TABLE_NAME FROM ORPHAN_EMP;
ADMIN_TABLES过程可以创建DBMS_REPAIR包的使用中需要的一些辅助表。
SQL> DECLARE
M INTEGER;
BEGIN
DBMS_REPAIR.CHECK_OBJECT(SCHEMA_NAME=>'SCOTT',
OBJECT_NAME=>'EMP',
REPAIR_TABLE_NAME =>'REPAIR_EMP',
CORRUPT_COUNT=>M);
DBMS_OUTPUT.PUT_LINE(M);
END;
说明 统计SCOTT模式的EMP表有多少破损块。其它的过程本人就不再一一举例说明啦，引用方法类似与上边的实例，其它一些过程的参数列表可以通用SQL>DESC DBMS_REPAIR来查看。
39、关于UTL_FILE包的使用方法
使用UTL_FILE时有个地方要注意:INIT.ORA文件中的UTL_FILE_DIR参数必须指定路径，即UTL_FILE包只有在UTL_FILE_DIR所指的目录中有权限读写，以下的实例表示本人已经修改UTL_FILE_DIR=C:\啦。
SQL>DESC UTL_FILE
可以查看UTL_FILE包的所有类型与过程。
例将表中数据输出到文件：
CREATE OR REPLACE PROCEDURE UP_FILEW IS
ID NUMBER;
NAME VARCHAR2(20);
HANDLE UTL_FILE.FILE_TYPE;
CURSOR REGION_CUR IS SELECT * FROM A_JOB;
BEGIN
HANDLE :=UTL_FILE.FOPEN('C:\','JOB.OUT','W');
OPEN REGION_CUR;
FETCH REGION_CUR INTO ID,NAME;
WHILE REGION_CUR%FOUND LOOP
UTL_FILE.PUTF(HANDLE,'%S,%S\N',ID,NAME);
FETCH REGION_CUR INTO ID,NAME;
END LOOP;
CLOSE REGION_CUR;
UTL_FILE.FFLUSH(HANDLE);
UTL_FILE.FCLOSE(HANDLE);
END UP_FILEW;

例将文件中数据写入到表中
CREATE OR REPLACE PROCEDURE UP_FILER IS
STR VARCHAR(200);
ID NUMBER;
NAME VARCHAR2(20);
HANDLE UTL_FILE.FILE_TYPE;
POS NUMBER(6);
BEGIN
HANDLE :=UTL_FILE.FOPEN('C:\','JOB.OUT','R');
UTL_FILE.GET_LINE(HANDLE,STR);
WHILE LENGTH(STR)>0 LOOP
POS:=INSTR(STR,',');
ID := TO_NUMBER(SUBSTR(STR,1,POS-1));
NAME:=SUBSTR(STR,POS+1,LENGTH(STR));
--DBMS_OUTPUT.PUT_LINE(TO_CHAR(ID)||NAME);
INSERT INTO A_JOB VALUES(ID,NAME);
UTL_FILE.GET_LINE(HANDLE,STR);
END LOOP;
EXCEPTION
WHEN OTHERS THEN
UTL_FILE.FCLOSE(HANDLE);
END UP_FILER;
说明：由于没办法判断文件是否结束，所以本例就使用EXCEPTION处理，在文件读完时触发异常，并关闭文件。UTL_FILE.FILE_TYPE是文件句柄，就象C或PASCAL中读写文件的方式是一样的。
40、关于DBMS_JOB包的使用方法？
首先在SQL>DESC DBMS_JOB来查看DBMS_JOB的一些过程，然后可以在RDBMS目录中找到DBMSJOB.SQL，这个文件就是DBMS_JOB包的源程序，并有参数说明，本例就给出来实例与常用的数据字典（USER_JOBS、DBA_JOBS、ALL_JOBS）。
创建测试表
SQL> CREATE TABLE A_JOB(DT DATE);
创建一个自定义过程
SQL> CREATE OR REPLACE PROCEDURE UP_TEST
AS
BEGIN
INSERT INTO A_JOB VALUES(SYSDATE);
END;
/
创建JOB
SQL> VARIABLE JOB1 NUMBER;
SQL>
SQL> BEGIN
DBMS_JOB.SUBMIT(:JOB1,'UP_TEST;',SYSDATE,'SYSDATE+1/(24*60)');
--每天1440分钟，即一分钟运行TEST过程一次
END;
/
运行JOB
SQL> BEGIN
DBMS_JOB.RUN(:JOB1);
END;
/
查看结果
SQL> SELECT TO_CHAR(DT,'YYYY/MM/DD HH24:MI:SS') 时间 FROM A_JOB;
时间
-------------------
2001/01/07 23:51:21
2001/01/07 23:52:22
2001/01/07 23:53:24
删除JOB
SQL> BEGIN
2 DBMS_JOB.REMOVE(:JOB1);
3 END;
4 /
说明：JOB1是BIND VARIABLE（绑定变量），相当于一个常局变量，在当前会话的生命期内可以引用，如果我们要删除一个JOB，通常是从USER_JOBS字典中找到JOB的ID。
SQL> SELECT JOB FROM USER_JOBS;
SQL>EXEC DBMS_JOB.REMOVE(上一句查出来的JOB号);
41、关于DBMS_SQL包的使用方法？
DBMS_SQL包是动态执行SQL语句的一个包，它使用方法比EXECUTE IMMEDIATE复杂，但功能更强大一些，最主要的是它执行的SQL可以超出64K的限制，DBMSSQL.SQL是该包的SQL源程序（RDBMS目录内）。
DECLARE
T_C1_TNAME USER_TABLES.TABLE_NAME%TYPE;
T_COMMAND VARCHAR2(200);
T_CID INTEGER;
T_TOTAL_RECORDS NUMBER(10);
STAT INTEGER;
ROW_COUNT INTEGER;
T_LIMIT INTEGER := 0;
--限制只取出记录大于0的表的情况
CURSOR C1 IS SELECT TABLE_NAME FROM USER_TABLES ORDER BY TABLE_NAME;
--查出所有表的名字
BEGIN
T_LIMIT := 0;
OPEN C1;
LOOP
--取出一个表名
FETCH C1 INTO T_C1_TNAME;
--如果游标记录取完，退出循环
EXIT WHEN C1%NOTFOUND;
T_COMMAND := 'SELECT COUNT(0) FROM '||T_C1_TNAME;
T_CID := DBMS_SQL.OPEN_CURSOR;
--创建一个游标
DBMS_SQL.PARSE(T_CID,T_COMMAND,DBMS_SQL.NATIVE);
--向服务器发出一个语句并检查这个语句的语法和语义错误
DBMS_SQL.DEFINE_COLUMN(T_CID,1,T_TOTAL_RECORDS);
--定义将从FETCHROWS()函数接收数据的变量的
STAT := DBMS_SQL.EXECUTE(T_CID);
--执行此语句，必须跟着FETCH_ROWS函数并为单个行检索数据
ROW_COUNT := DBMS_SQL.FETCH_ROWS(T_CID);
--取回一行数据放入局部缓冲区
DBMS_SQL.COLUMN_VALUE(T_CID,1,T_TOTAL_RECORDS);
--返回调用FETCHROWS()取回的值，值存储在T_TOTAL_RECORDS中
IF T_TOTAL_RECORDS > T_LIMIT THEN
DBMS_OUTPUT.PUT_LINE(RPAD(T_C1_TNAME,55,' ')||
TO_CHAR(T_TOTAL_RECORDS,'99999999')||' RECORD(S)');
END IF;
DBMS_SQL.CLOSE_CURSOR(T_CID);
END LOOP;
CLOSE C1;
END;
42、SQL与ORACLE取随机数的方法，本例要求从65到97之间的随机数？
SELECT 65+FLOOR(RAND()*26)
-------------------------------------------
SELECT FLOOR(DBMS_RANDOM.VALUE(65,97)) FROM DUAL
43、SQL与ORACLE取系统时间的方法
SELECT GETDATE（）
-------------------------------------------
SELECT TO_CHAR(SYSDATE,'YYYY-MM-DD HH24:MM:SS') FROM DUAL
44、关于DBMS_FLASHBACK包的使用方法？
DBMS_FLASHBACK包处理ORACLE的闪回功能，它是ORACLE9I新增的一个功能，可以方便地提取表中数据的前映象。你不要指望闪回功能帮你找回所有有意或无意删除的数据，它最多只能得到5天内的前映象而不用回退日志。
SQL>DESC DBMS_FLASHBACK
现在看示例如下
SQL>CREATE TABLE A_TEST(ID INT);/*创建表后请退出SQL PLUS再进来做测试*/
SQL>INSERT INTO A_TEST VALUES(1);
SQL>COMMIT;
SQL>SELECT DBMS_FLASHBACK.GET_SYSTEM_CHANGE_NUMBER FROM DUAL;
此处显示SCN号1（77056701）
SQL>INSERT INTO A_TEST VALUES(2);
SQL>COMMIT;
SQL>SELECT DBMS_FLASHBACK.GET_SYSTEM_CHANGE_NUMBER FROM DUAL;
此处显示SCN号2 (77056801)
SQL>SELECT * FROM A_TEST AS OF SCN 77056701
SQL>SELECT * FROM A_TEST AS OF SCN 77056801
SQL>SELECT * FROM A_TEST
说明：如果创建表A_TEST后立即使用SELECT * FROM AS OF SCN 这种语法，ORACLE会返回ORA-01466: 无法读数据 - 表定义已更改,这是正常的，只有退出再进来就可以使用闪回语法啦，SYS用户无法使用闪回语法。
相关链接：HTTP://WWW.ITPUB.NET/SHOWTHREAD.PHP?S=&THREADID=116321
45、有一个表，记录数据较多，要求对同一分类的数据进行排序？
工號 姓名 時間 序號
001 abc 08:00 1
001 abc 12:01 2
001 abc 13:28 3
001 abc 17:40 4
002 def 07:30 1
002 def 22:59 2
SQL的解决方法如下：
CREATE TABLE A_TEST(ID VARCHAR(10),SJ VARCHAR(20),ORD INT)
INSERT INTO A_TEST VALUES('001','07',0)
INSERT INTO A_TEST VALUES('001','08',0)
INSERT INTO A_TEST VALUES('001','09',0)
INSERT INTO A_TEST VALUES('002','07',0)
INSERT INTO A_TEST VALUES('002','08',0)
INSERT INTO A_TEST VALUES('002','09',0)
INSERT INTO A_TEST VALUES('003','07',0)
INSERT INTO A_TEST VALUES('003','08',0)
INSERT INTO A_TEST VALUES('003','09',0)
UPDATE A_TEST SET ORD=(
SELECT COUNT(*)+1 FROM A_TEST B WHERE B.SJ<A_TEST.SJ AND B.ID=A_TEST.ID)
46、SQL与ORACLE如何延时执行程序？
WAITFOR DELAY ‘00:01:00’ --延时一分钟
WAITFOR TIME ’12:00:00’ --定时到12点整
------------------------------------------------
SQL>EXEC DBMS_LOCK.SLEEP(1)
说明：DBMS_LOCK.SLEEP延时一分钟与SQL第一条语法作用相当。
47、SQL与ORACLE如何返回服务器的IP地址？
CREATE PROCEDURE GETIP
AS
BEGIN
CREATE TABLE M(DEMO VARCHAR(7999))
DECLARE @SQL VARCHAR(99)
SELECT @SQL='XP_CMDSHELL '+'''IPCONFIG'''
INSERT INTO M EXEC(@SQL)
DECLARE @S VARCHAR(99),@IP VARCHAR(24),@P INT,@L INT
SELECT @S=RTRIM(LTRIM(DEMO)) FROM M WHERE DEMO LIKE '%IP ADDRESS%'
SELECT @L=LEN(@S),@P=CHARINDEX(':',@S,1)
SELECT @IP=RTRIM(LTRIM(RIGHT(@S,@L-@P)))
SELECT @IP
DROP TABLE M
END

EXEC GETIP
------------------------------------------------------------
SELECT SYS_CONTEXT('USERENV','IP_ADDRESS') FROM DUAL;
EXEC DBMS_OUTPUT.PUT_LINE(UTL_INADDR.GET_HOST_ADDRESS);
说明 SYS_CONTEXT求客户端IP地址，一般在触发器中使用
48、SQL与ORACLE中对象是如何重命名的？
EXEC SP_RENAME ‘旧表名’,’新表名’
EXEC SP_RENAME ‘表名.字段名’,’新字段名’
EXEC SP_RENAMEDB ‘旧数据库名’,’新数据库名’
------------------------------------------------------------
RENAME 旧表名 TO 新表名
数据库重命名可以用NID（从9I开始），字段重命名暂缺。
49、ORACLE9I中INSERT 的新语法，源表的结构与数据示例如下：
SQL>SELECT * FROM sales_input_table;
PRODUCT_ID CUSTOMER_ID WEEKLY_ST SALES_SUN SALES_MON SALES_TUE SALES_WED SALES_THU SALES_FRI SALES_SAT
---------- ----------- --------- ---------- ---------- ---------- -------------------- ---------- ----------
111 222 01-OCT-00 100 200 300 400 500 600 700
222 333 08-OCT-00 200 300 400 500 600 700 800
333 444 15-OCT-00 300 400 500 600 700 800 900
sales_input_table表存储了商品每周的销售明细，将它转成如下所示？
PROD_ID CUST_ID TIME_ID C PROMO_ID QUANTITY_SOLD AMOUNT COST
---------- ---------- --------- - ---------- ------------- ---------- ----------
111 222 01-OCT-00 100
111 222 02-OCT-00 200
111 222 03-OCT-00 300
111 222 04-OCT-00 400
111 222 05-OCT-00 500
111 222 06-OCT-00 600
111 222 07-OCT-00 700
222 333 08-OCT-00 200
222 333 09-OCT-00 300
222 333 10-OCT-00 400
222 333 11-OCT-00 500
222 333 12-OCT-00 600
222 333 13-OCT-00 700
222 333 14-OCT-00 800
333 444 15-OCT-00 300
333 444 16-OCT-00 400
333 444 17-OCT-00 500
333 444 18-OCT-00 600
333 444 19-OCT-00 700
333 444 20-OCT-00 800
333 444 21-OCT-00 900
请看下边的这组SQL语句，成功而方便地解决这个问题
SQL> INSERT ALL
INTO sales (prod_id, cust_id, time_id, amount)
VALUES (product_id, customer_id, weekly_start_date, sales_sun)
INTO sales (prod_id, cust_id, time_id, amount)
VALUES (product_id, customer_id, weekly_start_date+1, sales_mon)
INTO sales (prod_id, cust_id, time_id, amount)
VALUES (product_id, customer_id, weekly_start_date+2, sales_tue)
INTO sales (prod_id, cust_id, time_id, amount)
VALUES (product_id, customer_id, weekly_start_date+3, sales_wed)
INTO sales (prod_id, cust_id, time_id, amount)
VALUES (product_id, customer_id, weekly_start_date+4, sales_thu)
INTO sales (prod_id, cust_id, time_id, amount)
VALUES (product_id, customer_id, weekly_start_date+5, sales_fri)
INTO sales (prod_id, cust_id, time_id, amount)
VALUES (product_id, customer_id, weekly_start_date+6, sales_sat)
SELECT product_id, customer_id, weekly_start_date, sales_sun,
sales_mon, sales_tue, sales_wed, sales_thu, sales_fri, sales_sat
FROM sales_input_table;
刚才看了INSERT ALL的用法，现在再看看INSERT ALL与WHEN的用法：
CREATE TABLE small_orders
(order_id NUMBER(12) NOT NULL,
customer_id NUMBER(6) NOT NULL,
order_total NUMBER(8,2),
sales_rep_id NUMBER(6) );
CREATE TABLE medium_orders AS SELECT * FROM small_orders;
CREATE TABLE large_orders AS SELECT * FROM small_orders;
CREATE TABLE special_orders
(order_id NUMBER(12) NOT NULL,
customer_id NUMBER(6) NOT NULL,
order_total NUMBER(8,2),
sales_rep_id NUMBER(6),
credit_limit NUMBER(9,2),
cust_email VARCHAR2(30) );
现在已经创建了四个表，将测试的环境搭起来啦。
INSERT ALL
WHEN order_total < 1000000 THEN
INTO small_orders
WHEN order_total > 1000000 AND order_total < 2000000 THEN
INTO medium_orders
WHEN order_total > 2000000 THEN
INTO large_orders
SELECT order_id, order_total, sales_rep_id, customer_id
FROM orders;
当然，我们也可以使用ELSE来替代最后一个WHEN…THEN
INSERT ALL
WHEN order_total < 100000 THEN
INTO small_orders
WHEN order_total > 100000 AND order_total < 200000 THEN
INTO medium_orders
ELSE
INTO large_orders
SELECT order_id, order_total, sales_rep_id, customer_id FROM orders;
以上两组SQL功能是一样的。现在再看一下INSERT FIRST的用法：
INSERT FIRST
WHEN ottl < 100000 THEN
INTO small_orders
VALUES(oid, ottl, sid, cid)
WHEN ottl > 100000 and ottl < 200000 THEN
INTO medium_orders
VALUES(oid, ottl, sid, cid)
WHEN ottl > 290000 THEN
INTO special_orders
WHEN ottl > 200000 THEN
INTO large_orders
VALUES(oid, ottl, sid, cid)
SELECT o.order_id oid, o.customer_id cid, o.order_total ottl,
o.sales_rep_id sid, c.credit_limit cl, c.cust_email cem
FROM orders o, customers c
WHERE o.customer_id = c.customer_id;
说明：Large_Orders表中将不含有OTT1>290000这部分数据。
INSERT ALL
WHEN ottl < 100000 THEN
INTO small_orders VALUES(oid, ottl, sid, cid)
WHEN ottl > 100000 and ottl < 200000 THEN
INTO medium_orders VALUES(oid, ottl, sid, cid)
WHEN ottl > 200000 THEN
into large_orders VALUES(oid, ottl, sid, cid)
WHEN ottl > 290000 THEN
INTO special_orders
SELECT o.order_id oid, o.customer_id cid, o.order_total ottl,
o.sales_rep_id sid, c.credit_limit cl, c.cust_email cem
FROM orders o, customers c WHERE o.customer_id = c.customer_id;
说明：Large_Orders表中将含有OTT1>290000这部分数据，这就是INSERT ALL与INSERT FIRST的区别。
50、ORACLE9I中WITH的新用法。可以理解成WITH是用来对ORACLE9I子查询定义别名
SQL> with total as ( select deptno,sum(sal) from emp group by deptno)
select count(*) from total;

编后：如果您认为有侵权的地方，请来信说明，本人将立即删去。

EMAIL：XZHUI@VIP.SINA.COM
QQ : 77056803


ASP读数据库中WORD文档,怎么读出并把内容显示出来!
WORD文档是不是以二进制形式存储的?
---------------------------------------------------------------

给你看一下这个东西
---- 我在用ASP为某单位制作网页时遇到这样一个问题，单位以前的MIS系统中将一些Word文件以字节流的形式保存在数据库中，现在用户要求我用ASP将这些Word文件数据从数据库中取出并在网页中显示出来。开始我自然地想到在服务器上创建临时文件、然后在网页中增加一个指向这个临时文件的链接，但这个方法将大大增加服务器的负担不说，而且在服务上如何保证特定客户端所使用的临时文件不被其它客户端使用的文件覆盖，如何在文件传送给用户后将文件删除，这些问题在实际都难很好解决。那么有没有更好的办法呢？

---- 为此我仔细查看了一下ASP的参考书，发现Response对象有一个叫contenttype的属性，它定义服务器发送给客户端内容的MIME类型。MIME全称Multipurpose Internet Mail Extensions，即多功能Internet邮件扩展。我们知道，在网页编程中我们有时将超链接指向一个Word或Excel文件，当用户点击这个链接时浏览器会自动调用对应方法将这个文件打开。之所以能做到这点就是因为用户机器上安装office后会在浏览器中注册对应的MIME资源类型。比如说word文件的MIME类型是Application/msword（前者是MIME类型，后者是MIME子类），Excel文件的MIME资源类型是Application/msexcel。事实上，凡是浏览器能处理的所有资源都有对应的MIME资源类型，比如说html文件的MIME类型是Text/html，JPG文件的MIME类型是Image/JPG。在与服务器的交互中，浏览器就是根据所接受数据的MIME类型来判断要进行什么样的处理，对html、JPG等文件浏览器直接将其打开，对Word、Excel等浏览器自身不能打开的文件则调用相应方法打开。对没有标记MIME类型的文件，浏览器则根据其扩展名和文件内容猜测其类型。如果浏览器无法猜出，则将它作为application/octet-stream。要了解各种文件的MIME类型，请在win98 我的电脑->查看->文件夹选项->文件类型 中查看。

---- 于是我灵机一动，想到在ASP中可以先将WORD数据以字节流方式取出，接着将其conntenttype属性标记为Application/msword，再将它发送给客户机，客户机收到这个资源后，根据其MIME类型，会自动调用客户机上的Word（当然，前提是客户机上装了Word，否则会将其作为一个不能识别的资源，提示用户保存起来，而不是打开它）将它打开。经试验效果很好，方法简单且速度很快，而且在IE5中浏览器使用内嵌方式（类似于OLE方式）打开，效果更佳。以下是程序内容。

---- 假设表名tab_word，表中有两个字段，一个是整型，名id，用作Word数据的唯一标识，另一个Blob型，名worddata，里面存放Word数据。现在要在页面上显示id等于1的Word文件内容， ASP程序如下：

< %
' conn - 已创建的数据库连接
' rs -- 结果集
rs = conn.execute("select
worddata from tab_word where id = 1")
response.contenttype = "Application/msword"
response.writebinary(rs("worddata"))
'注意将结果集中的数据直接用writebinary发送出去，不要用变量
'接收这个数据，否则系统会报错
% >

---- 用类似的方法，还可以处理Excel、Bmp等许多类型的数据。此程序在以下系统中测试通过：服务器：NT + IIS + SqlServer客户端：Win98 + IE5 或 Netscape4.x

5
又看了这个，试了没成功，共享：

<HTML>
<HEAD>
<meta HTTP-EQUIV="Content-Type" content="text/html; charset=gb_2312-80">
<link rel="stylesheet" type="text/css" href="edit.css">
<title>保存为Word文档</title>
</HEAD>
<script language="javascript">
window.returnValue=0
</script>
<BODY>
<font color="#000080"><strong><div id="strHint" align="center">数据处理中,请等待...<br/>
Data processing, please wait...</div></strong></font><font color="#0080C0">

<script language="vbscript">

On Error Resume Next

6
document.SaveAs "FileName.txt", 7 'wdFormatUnicodeText

wdFormatDocument = 0
wdFormatTemplate = 1
wdFormatText = 2
wdFormatTextLineBreaks = 3
wdFormatDOSText = 4
wdFormatDOSTextBreakLines = 5
wdFormatRTF = 6
wdFormatEncodedText = 7
wdFormatUnicodeText = 7
wdFormatHTML = 8

Dim wApp

Set wApp = CreateObject("Word.Application")
If Err.number > 0 Then
Alert "没法保存为Word文件,请正确安装Word97"
else
wApp.visible = True
wApp.Documents.add

wApp.Selection.TypeParagraph
wApp.Selection.Font.Bold = True
wApp.Selection.TypeText "九八Joy ASP维护工作意见"

wApp.Selection.ParagraphFormat.Alignment = 1
rem 居 中

wApp.Selection.TypeParagraph
wApp.Selection.Font.Bold = false

wApp.Selection.TypeText "-- 飞鸟"

wApp.Selection.TypeParagraph

wApp.Selection.ParagraphFormat.LeftIndent = wApp.CentimetersToPoints(0)
wApp.Selection.ParagraphFormat.FirstLineIndent = wApp.CentimetersToPoints(0.72/2*2)

wApp.Selection.TypeParagraph
wApp.Selection.Font.Bold = false
wApp.Selection.ParagraphFormat.Alignment = 0

wApp.Selection.TypeText "认真落实CHINA ASP为网友解难题，办实事精神，强化运行管理，优化网络；坚持以网管为龙头，以提高接通率为重点，做好运维工作，保证全网运行安全、高效、畅通。认真落实局内六十项奋斗目标，围绕效益工程、形象工程、福利工程，开动脑筋、解放思想，以改革为动力，推动维护、服务、管理工作更上台阶。"
wApp.Selection.TypeParagraph

rem 居 右
wApp.Selection.TypeParagraph
wApp.Selection.Font.Bold = false
wApp.Selection.TypeText "一九九九年五月"
wApp.Selection.TypeParagraph

end if
</script>
<center><br/><strong>数据处理完毕!</strong></center>

<p>
</p><div align='center'><center><input type=button class=buttonface name='cmdOK' onclick="window.returnValue=0;window.close();" value=" 确 定 " ></center></div>

</BODY>
</HTML>


数据库设计规范
标题：数据库设计规范 CSDN[大力]

版本：V1.0

修订文档历史记录：
---日期--|---版本--|--说明----
2003.6.5 V1.0 文档初搞

数据库设计规范
1 目的

规范数据库设计。

2 概述

从数据库的设计原则 设计文档几方面论述数据库设计的规范思想及命名规则。

3 数据库设计原则

3.1 结构原则

3.1.1 与程序流程有关的状态信息使用域进行限制和定义，不允许最终用户修改定义

3.1.2 用户可修改的状态列表信息，用数据表来定义，一经创建后，用户只可修改名称，不能修改代码，修改后的名称与原名称应代表相同的意义。

3.1.3 用户采用列表录入的文字信息，在一个统一的‘系统信息’表中定义，用户可任意创建和修改。用此种方法录入的信息，录入的信息与选择列表不存在约束关系，列表只是作为文字录入的一种辅助手段。

3.1.4 辅助编码表应至少有两列—代码和名称。只有在名称非常稳定的情况下才可以不使用代码。使用编码表的好处是在应用程序界面中，不同的地方出现同一种数据的录入（如录入数据或查询过滤条件）时只需定义一次选择列表，不会出现两处冲突的现象，方便编程。

3.1.5 所有记录业务数据的表中都设有‘录入员’和‘录入日期’列，由系统自动记录。

3.1.6 记录业务数据的表中，根据需要设置‘过程’和‘状态’列，‘过程’列用一位数字或字符记录如录入 审核 记账等过程状态；‘状态’列用一位数字或字符记录如正常 作废 删除等状态。所有表的‘过程’状态的改变日期和操作人由一单独表来记录，表中记录状态改变所涉及的表标识 记录标识 操作人和操作日期。

3.1.7 基本编码表中应设置‘编码’ ‘缩语’ ‘名称’，‘编码’作为主键与其它表的外键形成对应关系，完成基于主-外键的完整性约束。

3.1.8 记录录入单据的表中设置‘自动单据号’，由一字符开始以区分单据类型，后跟一数字序列表示序号。‘自动单据号’由系统自动生成，作为主表的主键，不允许用户修改，并作为连接主表和明细表的外键。

3.1.9 明细表中设置序号列，用于同一单据中排列各行的序号。

3.1.10 用于记录业务数据的数据表与用于报表的数据表分别存于不同的数据库中，分别命名为‘业务数据库’和‘决策数据库’。‘业务数据库’独立于‘决策数据库’进行设计，‘决策数据库’中的数据来源于‘业务数据库’。

3.1.11 任意业务流程中应考虑创建 审核 记账 修改 删除 冲红 结存（月结 年结）的处理及满足的条件。

3.1.12 对于主-明细表结构，设计对应的视图将两表连接用于查询。

3.1.13 禁止直接用编码或名称列表（in）作为固定的过滤条件对数据进行查询。应将包含此过滤条件的查询做成特定的视图；或在编码表中加入一属性字段用于分类。

3.1.14 对于业务数据表应设定一个‘存档标记列’，对于新增行或修改行设置该标志，以便将业务数据库中的数据向决策数据库复制时用以选择需更新的数据。当采用数据库复制技术时，也希望设定此标志，以备用。此标志的设置可以由应用程序或触发器来执行。

3.1.15 决策数据库中应复制基本编码表和辅助编码表，以保持决策数据库数据的完整性。

3.1.16 同时采用多种分类方法：如果某一实体有多于一种的分类方法，或实体中的每个实例所属的类的数量是不定的，这时实体表和分类表之间的关联是多对多的，即一个实体属于一个分类而同时也属于其它的分类。对于具有可多选的分类标志的表，与分类名称列表之间存在多对多的关系，须按多对多的关系来处理。
--实体表-- --实体-分类表-- --分类表--
实例1 实例 分类 分类1->类别1.1
实例2 1对N ... 1对N 分类1->类别1.2
实例3 ... 分类2->类别2.1

3.1.17 业务过程的记录：如果一个业务表（或主表+明细表）依赖另外一个业务表的数据，或在流程上一个业务表根据另一个业务表生成，则该业务表中应有一字段记录另一业务表对应记录的标识。这样的结构使得在流程上的所有数据形成一个链。他有两个作用：其一，可以追述某一过程进行到哪一步；其二，对于流程的回退有用，当流程需要回退到某一步时，将该步骤生成的记录作废，同时作废由此记录生成的所有后续记录，即可以回退到流程的任意一点。由于数据的依赖关系，回退时实际上是从后向前作废记录，直到希望回退到的位置为止。
一个业务流程应作为一个事务（在数据库管理系统中的定义），要保证这一事务的完整性。首先一个事务要有一个标识，在这个事务进行过程中所产生的一切数据都应打上事务标识作为标记。可以‘回滚’整个事务，或‘回滚’（从后向前）到事务进行的某一点上，然后再继续进行直到完成事务。

3.1.18 分类字段的冗余：业务数据表往往关联许多的基本信息表，这些基本信息表中可能含有一些分类字段，而业务数据表的分类统计可能引用这些字段。如果这些分类字段的内容会改变，而又不希望这种改变影响业务数据的统计结果，则在这些业务数据表中应包含这些分类字段，用以记录业务发生时该字段的内容。

3.1.19 类别和状态的多选：多选分为必选（1..n）和可选（0..n）。如是必选，在设计时要有说明，在程序实现中应有控制和检查。两个可选的类别或状态表可以合并为一个表，再与引用此表的主表形成多对多的关系

3.1.20 财务记账的处理：由凭证生成各种帐目（总帐 明细帐 日记帐 部门帐 往来帐 项目帐以及备查帐）时，有两种处理方法：一种是各账本不生成表，需要时临时生成，在月结时一次生成月结帐；另一方法是生成表，当对凭证进行修改时，先做删除处理（可用数据库的级联删除功能），再补新凭证然后记帐，或，先做作废处理（各账目中与凭证相关的记录由凭证号关联），再补新凭证然后记帐。当各账本与凭证之间由凭证号关联时，可进行关联查找。

3.2 命名原则

3.2.1 表和视图命名原则
系统信息表：TS_
基本编码表：TB_
决策数据表：TD_
业务数据表：不同的业务以不同的缩写（2-3个字符）开始，如‘TCRM_’；如是辅助编码表则在代表业务的缩写后加A_，如‘TCRM_A_’。
关联数据表：TRE__表A_表B
视图：在表命名的基础上加前缀改为V_
后缀：主-明细结构的表，主表为 _M；明细表为 _L
单词首写字母要大写，多个单词间不用任何连接符号如：TCRM_UserType
注意：尽量写好列“描述”

3.2.2 列命名规则
第一个字母小写，后面的单词或拼音，采用第一个字母大写，命名规则只来自于业务，尽量表达出列的含义

3.2.3 过程、函数、触发器命名原则
过程：SP_
函数：FN_
触发器：TR_表名_[后面插入加I,修改加U,删除加D]，操作前触发器(INSTEAD OF)： TR_表名_OF[后面插入加I,修改加U,删除加D]

3.2.4 自定义数据类型、默认、规则命名原则
自定义数据类型：UD_
默认: DF_，对于非绑定的默认可取系统默认的名字
规则：RU_，对于非绑定规则(约束) 可取系统默认的名字

3.2.5 主键、外键关系、索引命名原则
主键: PK_表名
外键关系：FK_主表_从表
索引：ID_列名,符合索引列名间用_隔开


&PL/SQL书写规范(xzh2000收集)
一、 Sql&PL/SQL书写规范：
1、 语句中出现的所有表名、字段名全部小写，系统保留字、内置函数名、Sql保留字大写。
2、 连接符or、in、and、以及＝、<=、>=等前后加上一个空格。
3、 对较为复杂的sql语句加上注释，说明算法、功能。
注释风格：注释单独成行、放在语句前面。
(1) 应对不易理解的分支条件表达式加注释；
(2) 对重要的计算应说明其功能；
(3) 过长的函数实现，应将其语句按实现的功能分段加以概括性说明；
(4) 每条SQL语句均应有注释说明（表名、字段名）。
(5) 常量及变量注释时，应注释被保存值的含义(必须)，合法取值的范围(可选)
(6) 可采用单行/多行注释。（-- 或 /* */ 方式）
4、 SQL语句的缩进风格
(1) 一行有多列，超过80个字符时，基于列对齐原则，采用下行缩进
(2) where子句书写时，每个条件占一行，语句令起一行时，以保留字或者连接符开始，连接符右对齐。
5、 多表连接时，使用表的别名来引用列。
6、 供别的文件或函数调用的函数，绝不应使用全局变量交换数据；
7、 变量令名不能超出ORACLE的限制（30个字符），令名要规范，要用英文令名，从变量上能看到变量的作用，如
g名称 全局变量
m名称 局部变量
c名称 光标
p名称 参数
8、 查找数据库表或视图时，只能取出确实需要的那些字段，不要使用*来代替所有列名。要清楚明白地使用列名，而不能使用列的序号。
9、 功能相似的过程和函数，尽量写到同一个包中，加强管理。

如例（1）

二、 书写优化性能建议
1、避免嵌套连接。例如：A = B and B = C and C = D
2、where条件中尽量减少使用常量比较，改用主机变量
3、系统可能选择基于规则的优化器，所以将结果集返回数据量小的表作为驱动表（from后边最后一个表）。
4、大量的排序操作影响系统性能，所以尽量减少order by和group by排序操作。

如必须使用排序操作，请遵循如下规则：
(1) 排序尽量建立在有索引的列上。
(2) 如结果集不需唯一，使用union all代替union。
5、索引的使用。
(1) 尽量避免对索引列进行计算。如对索引列计算较多，请提请系统管理员建立函数索引。
(2) 尽量注意比较值与索引列数据类型的一致性。
(3) 对于复合索引，SQL语句必须使用主索引列
(4) 索引中，尽量避免使用NULL。
(5) 对于索引的比较，尽量避免使用NOT=（!=）
(6) 查询列和排序列与索引列次序保持一致

6、尽量避免相同语句由于书写格式的不同，而导致多次语法分析，尽量使用Bind变量。
7、尽量使用共享的SQL语句。
8、查询的WHERE过滤原则，应使过滤记录数最多的条件放在最前面。
9、任何对列的操作都将导致表扫描，它包括数据库函数、计算表达式等等，查询时要尽可能将操作移至等号右边。
10、in、or子句常会使用工作表，使索引失效；如果不产生大量重复值，可以考虑把子句拆开；拆开的子句中应该包含索引。

三、其他经验性规则
1、尽量少用嵌套查询。如必须，请用not exist代替not in子句。如例（2）
2、用多表连接代替EXISTS子句。如例（3）
3、少用DISTINCT，用EXISTS代替 如例（4）
4、使用UNION ALL、MINUS、INTERSECT提高性能
5、使用ROWID提高检索速度。对SELECT得到的单行记录，需进行DELETE、UPDATE操作时，使用ROWID将会使效率大大提高。
6、使用优化线索机制进行访问路径控制。
7、使用cursor时，显示光标优于隐式光标

本规范示例：
例一：
SELECT aka042 -- 单位缴费划入个人帐户比例
INTO prm_aaa043
FROM ka01 --医疗保险单位缴费划入个人帐户比例分段信息
WHERE akc021 = rec_kc01.akc021 -- 医疗人员类别
AND aka041 >= rec_kc01.akc023 -- 年龄上限
AND aka040 <= rec_kc01.akc023 -- 年龄下限
AND aae030 <= prm_date -- 开始时间
AND ( aae031 >= prm_date OR aae031 IS NULL ); -- 终止时间

例二：
X SELECT ......
FROM emp
WHERE dept_no NOT IN ( SELECT dept_no
FROM dept
WHERE dept_cat='A');

O SELECT ......
FROM emp e
WHERE NOT EXISTS ( SELECT 'X'
FROM dept
WHERE dept_no=e.dept_no
AND dept_cat='A');

例三：
X SELECT ......
FROM emp
WHERE EXISTS ( SELECT 'X'
FROM dept
WHERE dept_no=e.dept_no
AND dept_cat='A');

O SELECT ......
FROM emp e,dept d
WHERE e.dept_no=d.dept_no
AND dept_cat='A';

例四：
X SELECT DISTINCT d.dept_code,d.dept_name
FROM dept d ,emp e
WHERE e.dept_code=d.dept_code;

O SELECT dept_code,dept_name
FROM dept d
WHERE EXISTS ( SELECT 'X'
FROM emp e
WHERE e.dept_code=d.dept_code);

注释范例
过程注释：
过程都以sp_开头，注意过程名称要符合令名要求
/**************************************************************************
name:sp_Write_log
parameter:p_textContext in varchar2 参数描述
create date:2003-04-1
creater:chen jiping
desc:过程总功能描述
****************************************************************************/
函数注释
函数以f开头，令名符合令名标准
/**************************************************************************
name:f_Get_JobId
parameter:p_Name in varchar2 参数描述
return number:返回值描述
create date:2003-04-1
creater:chen jiping
desc:函数总功能描述
****************************************************************************/


sql的一些性能问题(soul)
今天碰到一个问题。每次看笔记评论，发觉都比较慢，于是检查。结果发觉一个有趣现象。
原始写法：
SQL="select * from vwTableA where id="&xid
RS.Open SQL, Conn, 0, 1
Conn.Execute "update TableA set hits=hits+1 where id="& xid
author=RS("author")
修改后写法：
SQL="select * from vwiTableA where id="&xid
RS.Open SQL, Conn, 0, 1
author=RS("author")
Conn.Execute "update TableA set hits=hits+1 where id="& xid
结果就是速度快了。不再有停顿。
vwTableA 是 TableA 的一个视图。
问题仅仅在于刚刚打开了一个数据表，然后马上执行一条相关的update语句。这里可能有
锁的竞争问题。

2003-6-7 16:58:00
查看评语???

2003-6-7 17:16:55 数据库索引对数据库性能的影响所有的问题在于索引的查找速度和内存的查找速度。
内存大，索引做得好，那么大部分查询只需要在索引中进行查询，而索引很容易被缓存。这样，查询会变得很快。比想象得还快。比如在1千万条记录中查找若干记录，可以如此计算，1千万条记录如果主键是int类型，因为主键都是唯一的，所以其实也是数据库中最大的一个索引。那么所占空间不过4*1e7, 40M内容，1千万条记录可能是很多个G了，经过优化的索引，一般主键是Hash表，一个索引大概是40M的10倍或更多，那么数据库查询就基本在内存中进行了。
但是hash还是很大，所以会强烈的依赖于内存的大小，其实数据库再大点，因为索引都过于大，那么情况就开始完全不同了。也许我们需要一个更大型的数据库才可以支持了。

2003-6-7 19:03:10 DBCC INDEXDEFRAG一个有用的可以提高性能的语句。
用于整理索引碎片。就像整理磁盘碎片一样，索引也是有碎片的。


数据库设计的折衷(maplesw)
作项目分析，数据库设计是一个很重要也很难的问题，
完全按照范式有可能不符合用户需求，不利于编程，
看来是具体问题具体分析，数据库设计是范式和需求的折中。
在上学时，没觉得数据类型有多重要，现在发觉了解数据类型
的具体内容也是很重要的，可以知道不同数据库之间的兼容问题
该怎么处理。

数据库设计技巧：
第2 部分— 设计表和字段
1. 检查各种变化
我在设计数据库的时候会考虑到哪些数据字段将来可能会发生变更。比方说，姓氏就是如此（注
意是西方人的姓氏，比如女性结婚后从夫姓等）。所以，在建立系统存储客户信息时，我倾向于
在单独的一个数据表里存储姓氏字段，而且还附加起始日和终止日等字段，这样就可以跟踪这一
数据条目的变化。
— Shropshire Lad
2. 采用有意义的字段名
有一回我参加开发过一个项目，其中有从其他程序员那里继承的程序，那个程序员喜欢用屏幕上
显示数据指示用语命名字段，这也不赖，但不幸的是，她还喜欢用一些奇怪的命名法，其命名采
用了匈牙利命名和控制序号的组合形式，比如cbo1、txt2、txt2_b 等等。
除非你在使用只面向你的缩写字段名的系统，否则请尽可能地把字段描述的清楚些。当然，也别
做过头了，比如Customer_Shipping_Address_Street_Line_1 I 虽然很富有说明性，但没人愿意
键入这么长的名字，具体尺度就在你的把握中。
— Lamont Adams
3. 采用前缀命名
如果多个表里有好多同一类型的字段（比如FirstName），你不妨用特定表的前缀（比如
CusLastName）来帮助你标识字段。
— notoriousDOG
时效性数据应包括“最近更新日期/时间”字段。时间标记对查找数据问题的原因、按日期重新处
理/重载数据和清除旧数据特别有用。
— kol
5. 标准化和数据驱动
数据的标准化不仅方便了自己而且也方便了其他人。比方说，假如你的用户界面要访问外部数据
源（文件、XML 文档、其他数据库等），你不妨把相应的连接和路径信息存储在用户界面支持表
里。还有，如果用户界面执行工作流之类的任务（发送邮件、打印信笺、修改记录状态等），那
么产生工作流的数据也可以存放在数据库里。预先安排总需要付出努力，但如果这些过程采用数
据驱动而非硬编码的方式，那么策略变更和维护都会方便得多。事实上，如果过程是数据驱动
的，你就可以把相当大的责任推给用户，由用户来维护自己的工作流过程。
— tduvall
6. 标准化不能过头
对那些不熟悉标准化一词（normalization ）的人而言，标准化可以保证表内的字段都是最基础的
要素，而这一措施有助于消除数据库中的数据冗余。标准化有好几种形式，但Third Normal
Form（3NF）通常被认为在性能、扩展性和数据完整性方面达到了最好平衡。简单来说，3NF 规
定：
· 表内的每一个值都只能被表达一次。
· 表内的每一行都应该被唯一的标识（有唯一键）。
· 表内不应该存储依赖于其他键的非键信息。
遵守3NF 标准的数据库具有以下特点：有一组表专门存放通过键连接起来的关联数据。比方说，
某个存放客户及其有关定单的3NF 数据库就可能有两个表：Customer 和Order。Order 表不包
含定单关联客户的任何信息，但表内会存放一个键值，该键指向Customer 表里包含该客户信息
的那一行。
更高层次的标准化也有，但更标准是否就一定更好呢？答案是不一定。事实上，对某些项目来
说，甚至就连3NF 都可能给数据库引入太高的复杂性。
— Lamont Adams
为了效率的缘故，对表不进行标准化有时也是必要的，这样的例子很多。曾经有个开发财务分析
软件的活就是用非标准化表把查询时间从平均40 秒降低到了两秒左右。虽然我不得不这么做，
但我绝不把数据表的非标准化当作当然的设计理念。而具体的操作不过是一种派生。所以如果表
出了问题重新产生非标准化的表是完全可能的。
— epepke
7. Microsoft Access 报表技巧
如果你正在使用Microsoft Access，你可以用对用户友好的字段名来代替编号的名称：比如用
Customer Name 代替txtCNaM。这样，当你用向导程序创建表单和报表时，其名字会让那些不
是程序员的人更容易阅读。
— jwoodruf
8. 不活跃或者不采用的指示符
增加一个字段表示所在记录是否在业务中不再活跃挺有用的。不管是客户、员工还是其他什么
人，这样做都能有助于再运行查询的时候过滤活跃或者不活跃状态。同时还消除了新用户在采用
数据时所面临的一些问题，比如，某些记录可能不再为他们所用，再删除的时候可以起到一定的
防范作用。
— theoden
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 记录来反映新头衔的变化，不过这样一来在时间上无法跟踪
个人所处位置的具体时间。
— teburlew
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 等。
采用一般抽象术语来标识“事物”的类别可以让你在关联数据以满足业务要求方面获得巨大的灵
活性，同时这样做还可以显著降低数据存储所需的冗余量。
— teburlew
11. 用户来自世界各地
在设计用到网络或者具有其他国际特性的数据库时，一定要记住大多数国家都有不同的字段格
式，比如邮政编码等，有些国家，比如新西兰就没有邮政编码一说。
— billh
12. 数据重复需要采用分立的数据表
如果你发现自己在重复输入数据，请创建新表和新的关系。
— Alan Rash
13. 每个表中都应该添加的3 个有用的字段
· dRecordCreationDate，在VB 下默认是Now()，而在SQL Server 下默认为GETDATE()
· sRecordCreator，在SQL Server 下默认为NOT NULL DEFAULT USER
· nRecordVersion，记录的版本标记；有助于准确说明记录中出现null 数据或者丢失数据的原
因
— Peter Ritchie
14. 对地址和电话采用多个字段
描述街道地址就短短一行记录是不够的。Address_Line1、Address_Line2 和Address_Line3 可
以提供更大的灵活性。还有，电话号码和邮件地址最好拥有自己的数据表，其间具有自身的类型
和标记类别。
— dwnerd
过分标准化可要小心，这样做可能会导致性能上出现问题。虽然地址和电话表分离通常可以达到
最佳状态，但是如果需要经常访问这类信息，或许在其父表中存放“首选”信息（比如
Customer 等）更为妥当些。非标准化和加速访问之间的妥协是有一定意义的。
— dhattrem
15. 使用多个名称字段
我觉得很吃惊，许多人在数据库里就给name 留一个字段。我觉得只有刚入门的开发人员才会这
么做，但实际上网上这种做法非常普遍。我建议应该把姓氏和名字当作两个字段来处理，然后在
查询的时候再把他们组合起来。
— klempan
Klempan 不是唯一一个注意到使用单个name 字段的人，要把这种情况变得对用户更为友好有好
些方法。我最常用的是在同一表中创建一个计算列，通过它可以自动地连接标准化后的字段，这
样数据变动的时候它也跟着变。不过，这样做在采用建模软件时得很机灵才行。总之，采用连接
字段的方式可以有效的隔离用户应用和开发人员界面。
— damon
16. 提防大小写混用的对象名和特殊字符
过去最令我恼火的事情之一就是数据库里有大小写混用的对象名，比如CustomerData。这一问
题从Access 到Oracle 数据库都存在。我不喜欢采用这种大小写混用的对象命名方法，结果还不
得不手工修改名字。想想看，这种数据库/应用程序能混到采用更强大数据库的那一天吗？采用全
部大写而且包含下划符的名字具有更好的可读性（CUSTOMER_DATA），绝对不要在对象名的
字符之间留空格。
— bfren
17. 小心保留词
要保证你的字段名没有和保留词、数据库系统或者常用访问方法冲突，比如，最近我编写的一个
ODBC 连接程序里有个表，其中就用了DESC 作为说明字段名。后果可想而知！DESC 是
DESCENDING 缩写后的保留词。表里的一个SELECT *语句倒是能用，但我得到的却是一大堆
毫无用处的信息。
— Daniel Jordan
18. 保持字段名和类型的一致性
在命名字段并为其指定数据类型的时候一定要保证一致性。假如字段在某个表中叫做
“agreement_number”，你就别在另一个表里把名字改成“ref1”。假如数据类型在一个表里
是整数，那在另一个表里可就别变成字符型了。记住，你干完自己的活了，其他人还要用你的数
据库呢。
— setanta
19. 仔细选择数字类型
在SQL 中使用smallint 和tinyint 类型要特别小心，比如，假如你想看看月销售总额，你的总额字
段类型是smallint，那么，如果总额超过了$32,767 你就不能进行计算操作了。
— egermain
20. 删除标记
在表中包含一个“删除标记”字段，这样就可以把行标记为删除。在关系数据库里不要单独删除
某一行；最好采用清除数据程序而且要仔细维护索引整体性。
— kol
21. 避免使用触发器
触发器的功能通常可以用其他方式实现。在调试程序时触发器可能成为干扰。假如你确实需要采
用触发器，你最好集中对它文档化。
— kol
22. 包含版本机制
建议你在数据库中引入版本控制机制来确定使用中的数据库的版本。无论如何你都要实现这一要
求。时间一长，用户的需求总是会改变的。最终可能会要求修改数据库结构。虽然你可以通过检
查新字段或者索引来确定数据库结构的版本，但我发现把版本信息直接存放到数据库中不更为方
便吗？。
— Richard Foster
23. 给文本字段留足余量
ID 类型的文本字段，比如客户ID 或定单号等等都应该设置得比一般想象更大，因为时间不长你
多半就会因为要添加额外的字符而难堪不已。比方说，假设你的客户ID 为10 位数长。那你应该
把数据库表字段的长度设为12 或者13 个字符长。这算浪费空间吗？是有一点，但也没你想象的
那么多：一个字段加长3 个字符在有1 百万条记录，再加上一点索引的情况下才不过让整个数据
库多占据3MB 的空间。但这额外占据的空间却无需将来重构整个数据库就可以实现数据库规模
的增长了。
— tlundin
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 个表乃至更多的列你就知道这个技巧
多有用了。
— Bryce Stenberg
第3 部分— 选择键和索引
1. 数据采掘要预先计划
我所在的市场部门一度要处理8 万多份联系方式，同时填写每个客户的必要数据（这绝对不是小
活）。我从中还要确定出一组客户作为市场目标。当我从最开始设计表和字段的时候，我试图不
在主索引里增加太多的字段以便加快数据库的运行速度。然后我意识到特定的组查询和信息采掘
既不准确速度也不快。结果只好在主索引中重建而且合并了数据字段。我发现有一个指示计划相
当关键——当我想创建系统类型查找时为什么要采用号码作为主索引字段呢？我可以用传真号码
进行检索，但是它几乎就象系统类型一样对我来说并不重要。采用后者作为主字段，数据库更新
后重新索引和检索就快多了。
— hscovell
可操作数据仓库（ODS）和数据仓库（DW）这两种环境下的数据索引是有差别的。在DW 环境
下，你要考虑销售部门是如何组织销售活动的。他们并不是数据库管理员，但是他们确定表内的
键信息。这里设计人员或者数据库工作人员应该分析数据库结构从而确定出性能和正确输出之间
的最佳条件。
— teburlew
2. 使用系统生成的主键
这一天类同技巧1，但我觉得有必要在这里重复提醒大家。假如你总是在设计数据库的时候采用
系统生成的键作为主键，那么你实际控制了数据库的索引完整性。这样，数据库和非人工机制就
有效地控制了对存储数据中每一行的访问。
采用系统生成键作为主键还有一个优点：当你拥有一致的键结构时，找到逻辑缺陷很容易。
— teburlew
3. 分解字段用于索引
为了分离命名字段和包含字段以支持用户定义的报表，请考虑分解其他字段（甚至主键）为其组
成要素以便用户可以对其进行索引。索引将加快SQL 和报表生成器脚本的执行速度。比方说，
我通常在必须使用SQL LIKE 表达式的情况下创建报表，因为case number 字段无法分解为
year、serial number、case type 和defendant code 等要素。性能也会变坏。假如年度和类型字
段可以分解为索引字段那么这些报表运行起来就会快多了。
— rdelval
4. 键设计4 原则
· 为关联字段创建外键。
· 所有的键都必须唯一。
· 避免使用复合键。
· 外键总是关联唯一的键字段。
— Peter Ritchie
5. 别忘了索引
索引是从数据库中获取数据的最高效方式之一。95%的数据库性能问题都可以采用索引技术得到
解决。作为一条规则，我通常对逻辑主键使用唯一的成组索引，对系统键（作为存储过程）采用
唯一的非成组索引，对任何外键列采用非成组索引。不过，索引就象是盐，太多了菜就篌了。你
得考虑数据库的空间有多大，表如何进行访问，还有这些访问是否主要用作读写。
— tduvall
大多数数据库都索引自动创建的主键字段，但是可别忘了索引外键，它们也是经常使用的键，比
如运行查询显示主表和所有关联表的某条记录就用得上。还有，不要索引memo/note 字段，不
要索引大型字段（有很多字符），这样作会让索引占用太多的存储空间。
— gbrayton
6. 不要索引常用的小型表
不要为小型数据表设置任何键，假如它们经常有插入和删除操作就更别这样作了。对这些插入和
删除操作的索引维护可能比扫描表空间消耗更多的时间。
— kbpatel
7. 不要把社会保障号码（SSN）选作键
永远都不要使用SSN 作为数据库的键。除了隐私原因以外，须知政府越来越趋向于不准许把
SSN 用作除收入相关以外的其他目的，SSN 需要手工输入。永远不要使用手工输入的键作为主
键，因为一旦你输入错误，你唯一能做的就是删除整个记录然后从头开始。
— teburlew
上个世纪70 年代我还在读大学的时候，我记得那时SSN 还曾被用做学号，当然尽管这么做是非
法的。而且人们也都知道这是非法的，但他们已经习惯了。后来，随着盗取身份犯罪案件的增
加，我现在的大学校园正痛苦地从一大摊子数据中把SSN 删除。
— generalist
8. 不要用用户的键
在确定采用什么字段作为表的键的时候，可一定要小心用户将要编辑的字段。通常的情况下不要
选择用户可编辑的字段作为键。这样做会迫使你采取以下两个措施：
· 在创建记录之后对用户编辑字段的行为施加限制。假如你这么做了，你可能会发现你的应用程
序在商务需求突然发生变化，而用户需要编辑那些不可编辑的字段时缺乏足够的灵活性。当用
户在输入数据之后直到保存记录才发现系统出了问题他们该怎么想？删除重建？假如记录不可
重建是否让用户走开？
· 提出一些检测和纠正键冲突的方法。通常，费点精力也就搞定了，但是从性能上来看这样做的
代价就比较大了。还有，键的纠正可能会迫使你突破你的数据和商业/用户界面层之间的隔
离。
所以还是重提一句老话：你的设计要适应用户而不是让用户来适应你的设计。
— Lamont Adams
不让主键具有可更新性的原因是在关系模式下，主键实现了不同表之间的关联。比如，
Customer 表有一个主键CustomerID，而客户的定单则存放在另一个表里。Order 表的主键可能
是OrderNo 或者OrderNo、CustomerID 和日期的组合。不管你选择哪种键设置，你都需要在
Order 表中存放CustomerID 来保证你可以给下定单的用户找到其定单记录。
假如你在Customer 表里修改了CustomerID，那么你必须找出Order 表中的所有相关记录对其进
行修改。否则，有些定单就会不属于任何客户——数据库的完整性就算完蛋了。
如果索引完整性规则施加到表一级，那么在不编写大量代码和附加删除记录的情况下几乎不可能
改变某一条记录的键和数据库内所有关联的记录。而这一过程往往错误丛生所以应该尽量避免。
— ljboast
9. 可选键有时可做主键
记住，查询数据的不是机器而是人。
假如你有可选键，你可能进一步把它用做主键。那样的话，你就拥有了建立强大索引的能力。这
样可以阻止使用数据库的人不得不连接数据库从而恰当的过滤数据。在严格控制域表的数据库
上，这种负载是比较醒目的。如果可选键真正有用，那就是达到了主键的水准。
我的看法是，假如你有可选键，比如国家表内的state_code，你不要在现有不能变动的唯一键上
创建后续的键。你要做的无非是创建毫无价值的数据。比如以下的例子：
Select count(*)
from address, state_ref
where
address.state_id = state_ref.state_id
and state_ref.state_code = 'TN'
我的做法是这样的：
Select count(*)
from address
where
and state_code = 'TN'
如你因为过度使用表的后续键建立这种表的关联，操作负载真得需要考虑一下了。
— Stocker
10. 别忘了外键
大多数数据库索引自动创建的主键字段。但别忘了索引外键字段，它们在你想查询主表中的记录
及其关联记录时每次都会用到。还有，不要索引memo/notes 字段而且不要索引大型文本字段
（许多字符），这样做会让你的索引占据大量的数据库空间。。
— gbrayton

点击查看用户来源及管理<br>发贴IP：61.48.16.230 2003-6-15 10:15:34
编辑这个贴子 鲜花(0) 鸡蛋(0)
ugvanxk 帅哥哟，在线，有人找我吗？

等级：版主
文章：264
积分：686
注册：2002-11-2
搜索ugvanxk在『数 据 库』的所有贴子 第2楼
发贴心情
第4 部分— 保证数据的完整性
1. 用约束而非商务规则强制数据完整性
如果你按照商务规则来处理需求，那么你应当检查商务层次/用户界面：如果商务规则以后发生变
化，那么只需要进行更新即可。
假如需求源于维护数据完整性的需要，那么在数据库层面上需要施加限制条件。
如果你在数据层确实采用了约束，你要保证有办法把更新不能通过约束检查的原因采用用户理解
的语言通知用户界面。除非你的字段命名很冗长，否则字段名本身还不够。— Lamont Adams
只要有可能，请采用数据库系统实现数据的完整性。这不但包括通过标准化实现的完整性而且还
包括数据的功能性。在写数据的时候还可以增加触发器来保证数据的正确性。不要依赖于商务层
保证数据完整性；它不能保证表之间（外键）的完整性所以不能强加于其他完整性规则之上。
— Peter Ritchie
2. 分布式数据系统
对分布式系统而言，在你决定是否在各个站点复制所有数据还是把数据保存在一个地方之前应该
估计一下未来5 年或者10 年的数据量。当你把数据传送到其他站点的时候，最好在数据库字段
中设置一些标记。在目的站点收到你的数据之后更新你的标记。为了进行这种数据传输，请写下
你自己的批处理或者调度程序以特定时间间隔运行而不要让用户在每天的工作后传输数据。本地
拷贝你的维护数据，比如计算常数和利息率等，设置版本号保证数据在每个站点都完全一致。
— Suhair TechRepublic
3. 强制指示完整性
没有好办法能在有害数据进入数据库之后消除它，所以你应该在它进入数据库之前将其剔除。激
活数据库系统的指示完整性特性。这样可以保持数据的清洁而能迫使开发人员投入更多的时间处
理错误条件。
— kol
4. 关系
如果两个实体之间存在多对一关系，而且还有可能转化为多对多关系，那么你最好一开始就设置
成多对多关系。从现有的多对一关系转变为多对多关系比一开始就是多对多关系要难得多。
— CS Data Architect
5. 采用视图
为了在你的数据库和你的应用程序代码之间提供另一层抽象，你可以为你的应用程序建立专门的
视图而不必非要应用程序直接访问数据表。这样做还等于在处理数据库变更时给你提供了更多的
自由。
— Gay Howe
6. 给数据保有和恢复制定计划
考虑数据保有策略并包含在设计过程中，预先设计你的数据恢复过程。采用可以发布给用户/开发
人员的数据字典实现方便的数据识别同时保证对数据源文档化。编写在线更新来“更新查询”供
以后万一数据丢失可以重新处理更新。
— kol
7. 用存储过程让系统做重活
解决了许多麻烦来产生一个具有高度完整性的数据库解决方案之后，我所在的团队决定封装一些
关联表的功能组，提供一整套常规的存储过程来访问各组以便加快速度和简化客户程序代码的开
发。在此期间，我们发现3GL 编码器设置了所有可能的错误条件，比如以下所示：
SELECT Cnt = COUNT (*)
FROM [<Table>]
WHERE [<primary key column>] = <new value>
IF Cnt = 0
BEGIN
INSERT INTO [<Table>]
( [< primary key column>] )
valueS ( <New value> )
END
ELSE
BEGIN
<indicate duplication error>
END
而一个非3GL 编码器是这样做的：
INSERT INTO [<Table>]
( [< primary key column>] )
valueS
( <New value> )
IF @@ERROR = 2627 -- Literal error code for Primary Key Constraint
BEGIN
<indicate duplication error>
END
第2 个程序简单多了，而且事实上，利用了我们给数据库的功能。虽然我个人不喜欢使用嵌入文
字（2627）。但是那样可以很方便地用一点预先处理来代替。数据库不只是一个存放数据的地
方，它也是简化编码之地。
— a-smith
8. 使用查找
控制数据完整性的最佳方式就是限制用户的选择。只要有可能都应该提供给用户一个清晰的价值
列表供其选择。这样将减少键入代码的错误和误解同时提供数据的一致性。某些公共数据特别适
合查找：国家代码、状态代码等。
— CS Data Architect
第5 部分— 各种小技巧
1. 文档、文档、文档
对所有的快捷方式、命名规范、限制和函数都要编制文档。
— nickypendragon
采用给表、列、触发器等加注释的数据库工具。是的，这有点费事，但从长远来看，这样做对开
发、支持和跟踪修改非常有用。
— chardove
取决于你使用的数据库系统，可能有一些软件会给你一些供你很快上手的文档。你可能希望先开
始在说，然后获得越来越多的细节。或者你可能希望周期性的预排，在输入新数据同时随着你的
进展对每一部分细节化。不管你选择哪种方式，总要对你的数据库文档化，或者在数据库自身的
内部或者单独建立文档。这样，当你过了一年多时间后再回过头来做第2 个版本，你犯错的机会
将大大减少。
— mrs_helm
2. 使用常用英语（或者其他任何语言）而不要使用编码
为什么我们经常采用编码（比如9935A 可能是墨水笔的供应代码，4XF788-Q 可能是帐目编
码）？理由很多。但是用户通常都用英语进行思考而不是编码。工作5 年的会计或许知道
4XF788-Q 是什么东西，但新来的可就不一定了。在创建下拉菜单、列表、报表时最好按照英语
名排序。假如你需要编码，那你可以在编码旁附上用户知道的英语。
— amasa
3. 保存常用信息
让一个表专门存放一般数据库信息非常有用。我常在这个表里存放数据库当前版本、最近检查/修
复（对Access）、关联设计文档的名称、客户等信息。这样可以实现一种简单机制跟踪数据
库，当客户抱怨他们的数据库没有达到希望的要求而与你联系时，这样做对非客户机/服务器环境
特别有用。
— Richard Foster
4. 测试、测试、反复测试
建立或者修订数据库之后，必须用用户新输入的数据测试数据字段。最重要的是，让用户进行测
试并且同用户一道保证你选择的数据类型满足商业要求。测试需要在把新数据库投入实际服务之
前完成。
— juneebug
5. 检查设计
在开发期间检查数据库设计的常用技术是通过其所支持的应用程序原型检查数据库。换句话说，
针对每一种最终表达数据的原型应用，保证你检查了数据模型并且查看如何取出数据。
— jgootee
6. Access 设计技巧
对复杂的Microsoft Access 数据库应用程序而言，可以把所有的主表放在一个数据库文件里，然
后增加其他数据库文件和装载同原有数据库有关的特殊函数。根据需要用这些函数连接到主文件
中的主表。比如数据输入、数据QC、统计分析、向管理层或者政府部门提供报表以及各类只读
查询等。这一措施简化了用户和组权限的分配，而且有利于应用程序函数的分组和划分，从而在
程序必须修改的时候易于管理。
— Dennis Walden

2003-5-8 22:29:00


sqldmo

unit
setup;

interface

uses
Windows, Messages, SysUtils,
Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls,
Buttons, ComCtrls, ExtCtrls,shellapi,registry;

type
TSetup_F =
class(TForm)
Panel1: TPanel;
Label1:
TLabel;
EdtSrv: TEdit;
Label2: TLabel;
edtuser: TEdit;
Label3: TLabel;
Edit3:
TEdit;
Label4: TLabel;
EdtDbname:
TEdit;
StatusBar1: TStatusBar;
Label5:
TLabel;
EdtFilename: TEdit;
BitBtn3:
TBitBtn;
OpenDialog1: TOpenDialog;
Label6:
TLabel;
Edtlogin: TEdit;
Panel2:
TPanel;
BitBtn1: TBitBtn;
BitBtn2:
TBitBtn;
procedure BitBtn1Click(Sender: TObject);
procedure BitBtn3Click(Sender: TObject);
private
{ Private declarations }
procedure
initsqlserver;
procedure setup;
procedure
shinklog;
function isnt:boolean;
function
isexistsmssql:boolean;
function
startmssql(pass:string):boolean;
public
{ Public
declarations }
end;

var
Setup_F:
TSetup_F;

implementation
uses   SQLDMO_TLB
;
var
SQLServer : _SQLServer;
SQLRestore :
_Restore;
SQLDatabase : _Database;
SQLApplication :
_Application;
SQLLogin : _Login;
SQLUser :
_User;
isexists:boolean=false;
{$R *.dfm}

procedure
TSetup_F.BitBtn1Click(Sender: TObject);
begin
{ if isexistsmssql
then
if startmssql('') then
showmessage('ok');
}
initsqlserver;
setup;
end;

procedure
TSetup_F.initsqlserver;
//&sup3;&otilde;&Ecirc;&frac14;&raquo;&macr;,&frac14;ì&sup2;é&Ecirc;&Ccedil;·&ntilde;°&sup2;×°sqlserver
begin
try
SQLServer := coSQLServer.Create;
SQLRestore :=
coRestore.Create;
SQLDatabase := coDatabase.Create; ;
SQLApplication := coApplication.Create;
SQLLogin :=
coLogin.Create;
SQLUser :=
coUser.Create;
except
Raise
Exception.Create('·&thorn;&Icirc;&ntilde;&AElig;÷&acute;&acute;&frac12;¨&Ecirc;§°&Uuml;,&Ccedil;&aring;&Egrave;·&para;¨&Auml;ú&micro;&Auml;&frac14;&AElig;&Euml;&atilde;&raquo;ú&Eacute;&Iuml;&Ecirc;&Ccedil;·&ntilde;&Ograve;&Ntilde;°&sup2;×°&ordm;&Atilde;SQL
Server&Ecirc;&yacute;&frac34;&Yacute;&iquest;&acirc;&sup3;&Igrave;&ETH;ò!');
Close;
end;

end;

procedure
TSetup_F.setup;
var
i,j,index:integer;
sfilename:string;
owner:variant;
DBUserIsExists:boolean;
begin
if
FileExists(edtfilename.Text) then
//&Aring;&ETH;&para;&Iuml;±&cedil;·&Yacute;°ü&Ecirc;&Ccedil;·&ntilde;&acute;&aelig;&Ocirc;&Uacute;
sFileName := edtfilename.Text
else
begin
MessageBox(Handle,'&Ouml;&cedil;&para;¨&micro;&Auml;&Ecirc;&yacute;&frac34;&Yacute;&iquest;&acirc;°&sup2;×°°ü&Icirc;&Auml;&frac14;&thorn;&sup2;&raquo;&acute;&aelig;&Ocirc;&Uacute;&pound;&not;&Ccedil;&euml;&Ouml;&Oslash;&ETH;&Acirc;&Ouml;&cedil;&para;¨','&Igrave;á&Ecirc;&frac34;',MB_OK
or MB_ICONINFORMATION);
edtfilename.SetFocus;
exit;
end;

SQLRestore.Database := edtdbname.Text;
//sDatabaseName;
SQLRestore.ReplaceDatabase :=
true;
SQLRestore.Action :=
SQLDMORestore_Database;
SQLRestore.Files :=
ExtractShortPathName(sFileName);

//&Aacute;&not;&frac12;&Oacute;&Ecirc;&yacute;&frac34;&Yacute;&iquest;&acirc;

SQLServer.Connect(Edtsrv.Text,'sa',emptyparam);
Screen.Cursor
:= crHourGlass;
try
StatusBar1.Panels[0].Text:='&Otilde;&yacute;&Ocirc;&Uacute;°&sup2;×°&Ecirc;&yacute;&frac34;&Yacute;&iquest;&acirc;&pound;&not;&Ccedil;&euml;&Eacute;&Ocirc;&ordm;ò&iexcl;¤&iexcl;¤&iexcl;¤';
try
SQLRestore.SQLRestore(SQLServer);
//&raquo;&Ouml;&cedil;&acute;&Ecirc;&yacute;&frac34;&Yacute;&iquest;&acirc;
except
MessageBox(Handle,'&raquo;&Ouml;&cedil;&acute;&Ecirc;&yacute;&frac34;&Yacute;&iquest;&acirc;&Ecirc;§°&Uuml;','&acute;í&Icirc;ó',MB_OK
or MB_ICONSTOP);
exit;
end;
SQLUser.Name
:=edtuser.Text;  //sDBUserName;
SQLUser.Login :=Edtlogin.Text;//
sLoginName;
SQLUser.Role := 'db_owner';

DBUserIsExists :=
false;
for i := 1 to SQLServer.Databases.Count
do
begin
Owner := '';
Index :=
i;
if UpperCase(SQLServer.Databases.Item(Index,Owner).Name) =
UpperCase(edtdbName.Text) then
begin
for
j := 1 to SQLServer.Databases.Item(i,Owner).Users.Count do
begin
showmessage(SQLServer.Databases.Item(i,Owner).Users.Item(j).Name);
if SQLServer.Databases.Item(i,Owner).Users.Item(j).Name
=edtuser.Text then
begin
DBUserIsExists := true;
break;
end;
end;
// if not DBUserIsExists then
//   SQLServer.Databases.Item(i,Owner).Users.Add(sqluser);
//   showmessage(string(sqluser));
end;
end;
finally
Screen.Cursor :=
crDefault;
SQLServer.DisConnect;
end;
StatusBar1.Panels[0].Text:='°&sup2;×°&Iacute;ê&sup3;&Eacute;!';
end;

procedure
TSetup_F.BitBtn3Click(Sender: TObject);
begin
if OpenDialog1.Execute
then
begin
EdtFilename.Text:=OpenDialog1.FileName;
end;
end;

procedure
TSetup_F.shinklog;
var
svr : _SQLServer;
dbs : Databases
;
db : _Database ;

tlog : TransactionLog ;
logs :
LogFiles ;
log : _LogFile ;
i : integer ;
p :
ProPerty_ ;
begin
svr := CoSQLServer.Create()
;
svr.Connect('BRUCE', 'sa', EmptyParam);
//
&Aacute;&not;&frac12;&Oacute;&micro;&frac12;SQL
Server&Ecirc;&yacute;&frac34;&Yacute;&iquest;&acirc;
//
&sup2;&Icirc;&Ecirc;&yacute;: _SQLServer.Connect(&Server
Name&, &User name&, &password&) ;

dbs :=
svr.Databases ;
for i := 1 to dbs.Count do
begin
if (dbs.ItemByID(i).Name = 'DFW') then
begin
db := dbs.ItemByID(i) ;
break ;
end;
end;
// &acute;&Oacute;SQL
Server&Ouml;&ETH;&Ntilde;&iexcl;&Ocirc;&ntilde;&Atilde;&ucirc;×&Ouml;&Icirc;&ordf;DFW&micro;&Auml;Database&para;&Ocirc;&Iuml;ó
- db

tlog := db.TransactionLog ;
logs := tlog.LogFiles
;
//
&acute;&Oacute;_Database&para;&Ocirc;&Iuml;ó&raquo;&ntilde;&micro;&Atilde;LogFiles&para;&Ocirc;&Iuml;ó

for
i:= 1 to logs.Count do
begin
if (logs.Item(i).Name =
'DFW_log') then
begin
log := logs.Item(i)
;
break ;
end;
end;
//
&acute;&Oacute;LogFiles&para;&Ocirc;&Iuml;ó&Ouml;&ETH;&Ntilde;&iexcl;&Egrave;&iexcl;&Atilde;&ucirc;&Icirc;&ordf;DFW_log&micro;&Auml;_LogFile&para;&Ocirc;&Iuml;ó
- log

log.Shrink(10, EmptyParam);
//
&frac12;&laquo;Log&Icirc;&Auml;&frac14;&thorn;&Ntilde;&sup1;&Euml;&otilde;&micro;&frac12;10M&Ograve;&Ocirc;&Iuml;&Acirc;

svr
:= nil ;
end;

function TSetup_F.isnt:
boolean;
begin
result:=(win32majorversion>=4) and
(win32platform=ver_platform_win32_nt);
end;

function
TSetup_F.isexistsmssql:
boolean;
const
mssqlserver='software\mircosoft\mssqlserver';
var
reg:tregistry;
begin
result:=isexists;
if
result then exit;
if not isnt then
reg:=TRegistry.Create
else
reg:=TRegistry.Create(key_read);
try
with reg
do
begin
reg.RootKey:=hkey_local_machine;
result:=keyexists(mssqlserver);
isexists:=result;
end;
finally
free;
end;

end;

function
TSetup_F.startmssql(pass:string):
boolean;
const
mssql_98startcommand='scm_action 1
-pwd%s';
mssql_ntstartcommand='net start
mssqlserver';
mmsql_98stopcommand='scm_action
6';
mmsql_ntstopcommand='net stop
mssqlserver';
var
s:string;
begin
Screen.Cursor:=crHourGlass;
try
if
not isnt then
s:=format(mssql_98startcommand,[pass])
else
s:=mssql_ntstartcommand;
try
winexec(pchar(s),SW_HIDE);
result:=true;
except
result:=false;
end;
finally
Screen.Cursor:=crdefault;
end;
end;

end.


OLE Automation Stored Procedures
OLE Automation Stored Procedures
SQL Server supports seven OLE Automation Stored Procedures:

sp_OACreate
sp_OADestroy
sp_OAGetProperty
sp_OASetProperty
sp_OAMethod
sp_OAGetErrorInfo
sp_OAStop
You can find the description of these stored procedures below.

sp_OACreate
First of all, you should call sp_OACreate stored procedure to create an instance of the OLE object. You should pass two parameters into sp_OACreate stored procedure: program ID or class ID and variable. The variable will be a reference to the OLE object for the further using by other sp_OA stored procedures.
This is the example (for SQL Server 7.0):

DECLARE @object int
DECLARE @hr int

-- Create an object that points to the SQL Server
EXEC @hr = sp_OACreate 'SQLDMO.SQLServer', @object OUT
IF @hr <> 0
BEGIN
EXEC sp_displayoaerrorinfo @object, @hr
RETURN
END

NOTE: If you use SQL Server 6.5, you must write 'SQLOLE.SQLServer' instead 'SQLDMO.SQLServer'.

See this link for more information
"sp_OACreate (T-SQL)": http://msdn.microsoft.com/library/psdk/sql/sp_oa-oz.htm

sp_OADestroy
This stored procedure can be used to destroy a created OLE object. If you don't call sp_OADestroy, the created OLE object will be destroyed automatically when the batch completes execution.
This is the example:

-- Destroy the previously created SQL Server object
EXEC @hr = sp_OADestroy @object
IF @hr <> 0
BEGIN
EXEC sp_displayoaerrorinfo @object, @hr
RETURN
END

See this link for more information
"sp_OADestroy (T-SQL)": http://msdn.microsoft.com/library/psdk/sql/sp_oa-oz_1.htm

sp_OAGetProperty
You can use sp_OAGetProperty stored procedure to get a property value of an OLE object.
See this link for more information
"sp_OAGetProperty (T-SQL)": http://msdn.microsoft.com/library/psdk/sql/sp_oa-oz_3.htm

sp_OASetProperty
You can use sp_OASetProperty stored procedure to set a property of an OLE object to a new value.
See this link for more information
"sp_OASetProperty (T-SQL)": http://msdn.microsoft.com/library/psdk/sql/sp_oa-oz_5.htm

sp_OAMethod
You can use sp_OAMethod stored procedure to call a method of an OLE object.
This example calls the VerifyConnection method of the previously created SQL Server object:

-- Verify the connection
EXEC @hr = sp_OAMethod @object, 'VerifyConnection', @return OUT
IF @hr <> 0
BEGIN
EXEC sp_displayoaerrorinfo @object, @hr
RETURN
END

See this link for more information
"sp_OAMethod (T-SQL)": http://msdn.microsoft.com/library/psdk/sql/sp_oa-oz_4.htm

sp_OAGetErrorInfo
This stored procedure can be used to display OLE Automation error information, when error occurs, as sp_displayoaerrorinfo stored procedure.
However, easier to use sp_displayoaerrorinfo stored procedure instead of sp_OAGetErrorInfo, because sp_displayoaerrorinfo is a wrapped stored procedure for sp_OAGetErrorInfo.

See this link for more information
"sp_OAGetErrorInfo (T-SQL)": http://msdn.microsoft.com/library/psdk/sql/sp_oa-oz_2.htm

sp_OAStop
This stored procedure can be used to stop the OLE Automation Stored Procedures execution environment.
The execution environment will automatically restart the next time you call sp_OACreate stored procedure.

See this link for more information
"sp_OAStop (T-SQL)": http://msdn.microsoft.com/library/psdk/sql/sp_oa-oz_6.htm

Example: generate script
This script will generate script for all tables and all dependent objects for the given database.
You can pass the server name, user name, user password, database name and file name into sp_GenerateScript stored procedure, as in the example below:

EXEC sp_GenerateScript @server = 'Server_Name',
@uname = 'User_Name',
@pwd = 'Password',
@dbname = 'Database_Name',
@filename = 'c:\File_Name.sql'

You can specify different number of parameters (from zero to five parameters).

If you not specify server name, then the current server will be used;
If you not specify database name, then the current database will be used;
If you use Windows NT Authentication mode, then you should not specify username and password;
If you not specify user name, but specify password, then the current user name will be used;
If you not specify password, then password will not be used (for example: username = 'sa' and empty password, you can pass only username in this case);
If you not specify file name, then script will be placed into file script.sql on the drive 'c:'.
This stored procedure can be used for learning some general SQL Server features (how to work with OLE objects from the SQL Server, how to use some system functions, how to work with cursors and so on).

if object_id('sp_GenerateScript') is not null drop proc sp_GenerateScript
GO

CREATE PROC sp_GenerateScript (
@server varchar(30) = null,
@uname varchar(30) = null,
@pwd varchar(30) = null,
@dbname varchar(30) = null,
@filename varchar(200) = 'c:\script.sql'
)
AS

DECLARE @object int
DECLARE @hr int
DECLARE @return varchar(200)
DECLARE @exec_str varchar(200)
DECLARE @tbname varchar(30)

SET NOCOUNT ON

-- Set the server to the local server
IF @server is NULL
SELECT @server = @@servername

-- Set the database to the current database
IF @dbname is NULL
SELECT @dbname = db_name()

-- Create an object that points to the SQL Server
EXEC @hr = sp_OACreate 'SQLDMO.SQLServer', @object OUT
IF @hr <> 0
BEGIN
EXEC sp_displayoaerrorinfo @object, @hr
RETURN
END

-- Connect to the SQL Server
IF (@uname is NULL) AND (@pwd is NULL)
BEGIN
-- Windows NT Authentication mode is used
EXEC @hr = sp_OAMethod @object, 'Connect', NULL, @server
IF @hr <> 0
BEGIN
EXEC sp_displayoaerrorinfo @object, @hr
RETURN
END
END
ELSE
IF (@uname is NULL)
BEGIN
-- Set the username to the current user name
SELECT @uname = SYSTEM_USER
EXEC @hr = sp_OAMethod @object,'Connect',NULL,@server,@uname,@pwd
IF @hr <> 0
BEGIN
EXEC sp_displayoaerrorinfo @object, @hr
RETURN
END
END
ELSE
IF (@pwd is NULL)
BEGIN
EXEC @hr = sp_OAMethod @object, 'Connect', NULL, @server, @uname
IF @hr <> 0
BEGIN
EXEC sp_displayoaerrorinfo @object, @hr
RETURN
END
END
ELSE
BEGIN
EXEC @hr = sp_OAMethod @object,'Connect',NULL,@server,@uname,@pwd
IF @hr <> 0
BEGIN
EXEC sp_displayoaerrorinfo @object, @hr
RETURN
END
END

-- Verify the connection
EXEC @hr = sp_OAMethod @object, 'VerifyConnection', @return OUT
IF @hr <> 0
BEGIN
EXEC sp_displayoaerrorinfo @object, @hr
RETURN
END

SET @exec_str = "DECLARE script_cursor CURSOR FOR SELECT name FROM "
+ @dbname + "..sysobjects WHERE type = 'U' ORDER BY Name"
EXEC (@exec_str)

OPEN script_cursor
FETCH NEXT FROM script_cursor INTO @tbname
WHILE (@@fetch_status <> -1)
BEGIN
SET @exec_str = 'Databases("'+ @dbname +'").Tables("'
+ RTRIM(UPPER(@tbname))+'").Script(74077,"'
+ @filename +'")'
EXEC @hr = sp_OAMethod @object, @exec_str, @return OUT
IF @hr <> 0
BEGIN
EXEC sp_displayoaerrorinfo @object, @hr
RETURN
END
FETCH NEXT FROM script_cursor INTO @tbname
END
CLOSE script_cursor
DEALLOCATE script_cursor

-- Destroy the object
EXEC @hr = sp_OADestroy @object
IF @hr <> 0
BEGIN
EXEC sp_displayoaerrorinfo @object, @hr
RETURN
END
GO


Setup full-text search

MSSQLCity.Com - All about MS SQL
About Us
Articles
Administering
Comparison
General
Know How
Replication
Tuning
Undocumented
UDF
SQL 6.5
FAQ
Scripts
Tips
Test Exams
Advertise
Download
History
Search
Traffic
Related Links
Your button logo
Add to Favorites

Setup full-text search: step by step guide

Alexander Chigrik
chigrik@mssqlcity.com

Introduction
General concepts
Full-text indexes vs. regular SQL indexes
Maintaining full-text indexes
SQL Server 2000 full-text search enhancements
Step by step example
Literature

Introduction

In this article, I want to tell you about some general full-text search concepts: about software requirements, about Microsoft Search Service, about maintaining full-text indexes, about the comparison of full-text indexes with regular SQL indexes, and how to setup full-text search step by step.

This document was developed against Microsoft SQL Server 7.0, but full-text search can work in SQL Server 2000 as well, and SQL Server 2000 has some full-text search enhancements.

General concepts

SQL Server 7.0 supports full-text search. This is a new feature, which was not supported in the previous versions. The full-text search allows you to create special indexes for SQL Server character-based data. These special indexes, called full-text indexes, support linguistic and proximity searches on several languages. By using full-text indexes, you can search by words, phrases, words in close proximity to each other, and by inflexional forms of verbs and nouns. The full-text indexes can be created on the following SQL Server character-based data types:

  • char
  • nchar
  • varchar
  • nvarchar
  • text
  • ntext

    Note. In additional to the above data types, SQL Server 2000 can create full-text indexes of text data stored in image columns (only the text data stored in image columns can be indexed, images or pictures cannot be indexed).

    The full-text search runs as a service named Microsoft Search Service (MSSearch service).

    To work with full-text search, you should have the following operation systems:

    • Windows NT Server version 4.0 (with Service Pack 4 or later for SQL Server 7.0 and Service Pack 5 or later for SQL Server 2000)
    • Windows NT Server Enterprise Edition version 4.0 (with Service Pack 4 or later for SQL Server 7.0 and Service Pack 5 or later for SQL Server 2000)
    • Windows 2000 Server
    • Windows 2000 Advanced Server
    • Windows 2000 DataCenter
    It's because the MSSearch service cannot be installed on Windows 9x, Windows NT Workstation, or Windows 2000 Professional clients, but these clients can use full-text search, when the MSSearch service installed on the server machine.

    The full-text search supported under the following SQL Server editions:

  • SQL Server 7.0 Standard Edition
  • SQL Server 7.0 Enterprise Edition
  • SQL Server 2000 Personal Edition (except on Windows 9x)
  • SQL Server 2000 Standard Edition
  • SQL Server 2000 Developer Edition
  • SQL Server 2000 Enterprise Edition
  • SQL Server 2000 Enterprise Evaluation Edition

    To install full-text search in SQL Server 7.0, you should choose Custom installation type, because full-text search is not supported under the Typical or Minimum installation types.

    To install full-text search in SQL Server 2000, you should choose Typical or Custom installation types, because full-text search is not supported under the Minimum installation type.

    Because working with full-text search is very resource expensive, you should have enough physical and virtual memory. Set the virtual memory size to at least 3 times the physical memory installed in the computer, and set the SQL Server max server memory server configuration option to half the virtual memory size setting (1.5 times the physical memory).

    Full-text indexes vs. regular SQL indexes

    The full-text indexes very differ from the regular SQL Server indexes. The full-text indexes are stored in the full-text catalogs outside of SQL Server databases and managed by the MSSearch service. Full-text catalog is a set of operation system files (the default directory determined during installation is Ftdata subdirectory in the Microsoft SQL Server directory; for example, C:\MSSQL7\Ftdata, the default directory for SQL Server 7.0; and C:\Program Files\Microsoft SQL Server\Mssql\Ftdata, the default directory for SQL Server 2000).

    Unlike regular SQL indexes, only one full-text index per table is allowed. 249 nonclustered and 1 clustered regular SQL indexes are allowed per table.

    Unlike regular SQL indexes, full-text indexes are not updated automatically when the data upon which they are based is inserted, updated, or deleted. To reflect these changes, you should update full-text indexes manually, or create a job to update these indexes on a scheduled basis.

    Unlike regular SQL indexes, full-text indexes cannot be created, managed, or dropped using the Transact-SQL statements, only by using SQL Server Enterprise Manager, wizards, or stored procedures. For example, DROP INDEX and CREATE INDEX statements are not allowed for full-text indexes.

    Maintaining full-text indexes

    Because full-text indexes are not updated automatically when the data upon which they are based is inserted, updated, or deleted, you should immediately update full-text indexes when data in its associated table changes.

    There are two ways under SQL Server 7.0 to synchronize full-text indexes with its table data:

  • Full Population
  • Incremental Population

    Full Population - is a population, when the index entries are built for all the rows in all the full-text catalog's tables.

    Incremental Population - is a population, which only adjusts index entries for rows that have been added, deleted, or modified after the last population. To make Incremental Population the indexed table must have a column of the timestamp data type. If the indexed table does not have a timestamp column, only full population can be used.

    The population of full-text indexes can be made either through Enterprise Manager or through the sp_fulltext_catalog system stored procedure.

    Because full-text index population can take time, these populations should be scheduled during CPU idle time and slow production periods, such as in the evenings or on weekends.

    SQL Server 2000 full-text search enhancements

    SQL Server 2000 introduces a new way to maintain the full-text indexes. There is Change tracking. Change tracking population maintains a log of all changes to the full-text indexed data, and propagates the changes to the full-text index. There are three Change tracking population's options:

  • Background
  • On demand
  • Scheduled

    With the Background option, changes to rows in the table are propagated to the full-text index as they occur. You can use this option only when you have enough CPU and memory, because it can take many time.

    With the On demand option, you should manually update the full-text index by using the sp_fulltext_table with the update_index for the @action parameter.

    With the Scheduled option, you can use SQLServerAgent to schedule periodic jobs that execute the sp_fulltext_table system stored procedure with the update_index for the @action parameter.

    This is the example to start the Change tracking with the Background option for the Product table in the Sales database:

    USE Sales
GO
EXEC sp_fulltext_table 'Product', 'Start_change_tracking'
EXEC sp_fulltext_table 'Product', 'Start_background_updateindex'
GO

    Note. The Change tracking does not track any WRITETEXT or UPDATETEXT operations.

    Another SQL Server 2000 full-text search enhancement is image filtering. Image filtering allows you to index and query documents stored in image columns (only the text data stored in image columns can be indexed, images or pictures cannot be indexed).

    Note. Though a single computer can have multiple instances of SQL Server 2000, only one MSSearch service can exist. So, a single MSSearch service manages the full-text indexes for all the instances of SQL Server 2000 on the computer.

    Step by step example

    To set up full-text search, you should make the following steps:

  • Enable the database to support full-text indexes.
  • Create a full-text catalog.
  • Register appropriate table(s) for full-text processing.
  • Add each column that participates in a full-text index.
  • Create a full-text index.
  • Start a full population of the full-text catalog.

    Setup full-text search can be made either through Enterprise Manager or through the system stored procedures.

    In this example, I will create a full text index for the pr_info column from the pub_info table in the pubs database. Because using GUI interface is much easier and understandable way to set up full-text search in comparison with using system stored procedures, I will illustrate the setup full-text search using the SQL Server Enterprise Manager.

    Run SQL Server Enterprise Manager, expand a server group and expand a server. Expand Databases and click a database to enable. On the Tools menu, click Full-Text Indexing as shown in Figure 1.

    Choose SQL Server Full-Text Indexing Wizard
    Figure 1.

    This will launch the SQL Server Full-Text Indexing Wizard shown in Figure 2.

    Welcome to the SQL Server Full-Text Indexing Wizard
    Figure 2.

    Selecting the Next button will take you to the choosing of a SQL Server table for full-text indexing as shown in Figure 3.

    Select a table
    Figure 3.

    Select the pub_info table and click the Next button. Clicking the Next button will take you to Figure 4.

    Select an Index
    Figure 4.

    Now you must select a unique index for the pub_info table. Select UPKCL_pubinfo and click the Next button. Clicking the Next button will take you to Figure 5.

    Select Table Columns
    Figure 5.

    Now you should select the character-based columns you want to be eligible for full-text queries. Add pr_info column and click the Next button. Clicking the Next button will take you to Figure 6.

    Select a Catalog
    Figure 6.

    Now you must select an existing full-text catalog or create one for the pubs database. Because in our example there are no existing full-text catalogs, you must create the new fcPubs full-text catalog. Clicking the Next button will take you to Figure 7.

    Select or Create Population Schedules (Optional)
    Figure 7.

    At this step, you should select or create population schedules. Because in our example there are no existing population schedules, you must created the new schedule by clicking the New schedule button. Clicking this button will take you to Figure 8.

    New Full-Text Indexing Schedule
    Figure 8.

    Specify the new population schedule's parameters. Clicking the Change button will take you to Figure 9.

    Edit Recurring Full-Text Indexing Job Scheduling
    Figure 9.

    Edit recurring full-text indexing job scheduling as shown in Figure 9 and click the Next button. Clicking the Next button will take you to Figure 10.

    Select or Create Population Schedules (Optional)
    Figure 10.

    Selecting the Next button will take you to the completion of the configuration as shown in Figure 11.

    Completing the SQL Server Full-Text Indexing Wizard
    Figure 11.

    SQL Server will then perform a number of operations corresponding to the options that were selected. Once completed, you should see Figure 12 confirming that Full-Text Indexing Wizard completed successfully.

    Define full-text indexing
    Figure 12.

    Now you should start a full population of the fcPubs full-text catalog as shown in Figure 13.

    Start Population
    Figure 13.

    Once completed, you should see Figure 14 confirming that population of full-text catalog started successfully.

    Population of full-text catalog started successfully
    Figure 14.

    Literature

    1. Full-Text Indexes

    2. Full-Text Indexing Support

    3. Full-Text Query Architecture

    4. Maintaining Full-Text Indexes

    5. Implementation of Full-text Search


undocumented system tables
Introduction
In this article, I want to tell you about the undocumented system tables
shipped with SQL Server 2000. These tables are used by some system stored
procedures and most are stored in the master database (only the sysfiles1,
sysproperties and sysfulltextnotify system tables are stored in each database).

Undocumented system tables

syscursorcolumns

Contains the list of the cursor's columns. This table is stored
in the master database. The syscursorcolumns table is used by
the sp_describe_cursor_columns system stored procedure to report
the attributes of the columns in the result set of a server cursor.

Column name Data type Description cursor_handle int A unique value for the cursor within the scope of the server. column_name sysname, nullable The column name. The column is NULL if the column was specified without an accompanying AS clause. ordinal_position int Relative position of the column. The first column is in position 1. The value for any hidden columns is 0. column_characteristics_flags int A bitmask indicating the information stored in DBCOLUMNFLAGS in OLE DB. Can be one of the following:1 = Bookmark2 = Fixed length4 = Nullable8 = Row versioning16 = Updatable column (set for projected columns of a cursor that has no FOR UPDATE clause and, if there is such a column, can be only one per cursor). column_size int Maximum possible size for a value in this column. data_type_sql smallint Number indicating the SQL Server data type of the column. column_precision tinyint Maximum precision of the column as per the bPrecision value in OLE DB. column_scale tinyint Number of digits to the right of the decimal point for the numeric or decimal data types as per the bScale value in OLE DB. order_position int If the column participates in the ordering of the result set, the position of the column in the order key relative to the leftmost column. order_direction varchar(1), nullable A = The column is in the order key and the ordering is ascending.D = The column is in the order key and the ordering is descending.NULL = The column does not participate in ordering. hidden_column smallint If a value of 0, this column appears in the select list. The value 1 is reserved for future use. columnid int Column ID of the base column. If the result set column was built from an expression, columnid is -1. objectid int Object ID of the base table supplying the column. If the result set column was built from an expression, objectid is -1. dbid int ID of the database containing the base table supplying the column. If the result set column was built from an expression, dbid is -1. dbname sysname, nullable Name of the database containing the base table supplying the column. If the result set column was built from an expression, dbname is NULL.

syscursorrefs

Contains one row for each server cursor. This table is stored in the
master database and contains the cursor name, cursor scope (local
or global) and cursor handler. The syscursorrefs table is used by
the sp_describe_cursor, sp_describe_cursor_columns,
sp_describe_cursor_tables and sp_cursor_list system
stored procedures to get cursor name, cursor scope and cursor handler.

Column name Data type Description reference_name sysname, nullable Name used to refer to the cursor. cursor_scope tinyint 1 = LOCAL2 = GLOBAL cursor_handl int A unique value for the cursor within the scope of the server.

syscursors

Contains the attributes of a server cursor. This table is stored in the
master database. The syscursors table is used by the
sp_describe_cursor system stored procedures to report the attributes
of a server cursor and the sp_cursor_list system stored procedures to
report the attributes of server cursors currently open for the connection.

Column name Data type Description cursor_handle int A unique value for the cursor within the scope of the server. cursor_name sysname, nullable Name of the cursor. status int Same values as reported by the CURSOR_STATUS system function:1 = The cursor referenced by the cursor name or variable is open. If the cursor is insensitive, static, or keyset, it has at least one row. If the cursor is dynamic, the result set has zero or more rows.0 = The cursor referenced by the cursor name or variable is open but has no rows. Dynamic cursors never return this value.-1 = The cursor referenced by the cursor name or variable is closed.-2 = Applies only to cursor variables. There is no cursor assigned to the variable. Possibly, an OUTPUT parameter assigned a cursor to the variable, but the stored procedure closed the cursor before returning.-3 = A cursor or cursor variable with the specified name does not exist, or the cursor variable has not had a cursor allocated to it. model tinyint 1 = Insensitive (or static)2 = Keyset3 = Dynamic4 = Fast Forward concurrency tinyint 1 = Read-only2 = Scroll locks3 = Optimistic scrollable tinyint 0 = Forward-only1 = Scrollable open_status tinyint 0 = Closed1 = Open cursor_rows decimal(10,0) Number of qualifying rows in the result set. fetch_status smallint Status of the last fetch on this cursor.0 = Fetch successful.-1 = Fetch failed or is beyond the bounds of the cursor.-2 = The requested row is missing.-9 = There has been no fetch on the cursor. column_count smallint Number of columns in the cursor result set. row_count decimal(10,0) Number of rows affected by the last operation on the cursor. last_operation tinyint Last operation performed on the cursor:0 = No operations have been performed on the cursor.1 = OPEN2 = FETCH3 = INSERT4 = UPDATE5 = DELETE6 = CLOSE7 = DEALLOCATE

syscursortables

Contains the base tables referenced by a server cursor. This table
is stored in the master database. The syscursortables table is used by
the sp_describe_cursor_tables system stored procedures to report the
base tables referenced by a server cursor.

Column name Data type Description cursor_handle int A unique value for the cursor within the scope of the server. table owner sysname, nullable User ID of the table owner. table_name sysname, nullable Name of the base table. optimizer_hints smallint Bitmap consisting of one or more of:1 = Row-level locking (ROWLOCK)4 = Page-level locking (PAGELOCK)8 = Table Lock (TABLOCK)16 = Exclusive table lock (TABLOCKX)32 = Update lock (UPDLOCK)64 = No lock (NOLOCK)128 = Fast first-row option (FASTFIRST)4096 = Read repeatable semantic when usedwith declare cursor (HOLDLOCK) lock_type smallint Scroll-lock type requested either explicitly or implicitly for each base table that underlies this cursor. The value can be: 0 = None1 = Shared3 = Update server_name sysname, nullable Name of the linked server the table resides on. NULL if OPENQUERY or OPENROWSET are used. objectid int Object ID of the table. 0 if OPENQUERY or OPENROWSET are used. dbid int ID of the database the table resides in. 0 if OPENQUERY or OPENROWSET are used. dbname sysname, nullable Name of the database the table resides in. NULL if OPENQUERY or OPENROWSET are used.

sysfiles1

Contains one row for each file in a database. Each database contains the
sysfiles1 system table.

Column name Data type Description status int For internal use only. fileid smallint File identification number unique for each database. name nchar(128) Logical name of the file. filename nchar(260) Name of the physical device, including the full path of the file.

sysfulltextnotify

Contains full-text catalog's notifications. The sysfulltextnotify table is
used by the sp_fulltext_database, sp_fulltext_catalog, sp_fulltext_table
and sp_fulltext_column system stored procedures.
Each database contains the sysfulltextnotify system table.

Column name Data type Description tableid int Table ID. rowinfo smallint For internal use only. ftkey varbinary(482) Full-text key value.

syslocks

Contains information about active locks. This table is provided for
backward compatibility only, and has been replaced by syslockinfo.

Column name Data type Description id int Table ID. dbid smallint Database ID. page int Page number. type smallint Type of lock:1 = Exclusive table lock2 = Shared table lock3 = Exclusive intent lock (will do page locking onindicated pages)4 = Shared intent lock5 = Exclusive page lock6 = Shared page lock7 = Update page lock (changes to exclusive lock ifpage is actually modified)8 = Exclusive extent lock9 = Update extent lock11 = Next extent lock12 = Previous extent lockAny of the above lock types can appear with 256(0x100) added to them, indicating that the lockis blocking another user.257 = Blocking exclusive table lock265 = Blocking update extent lock spid smallint ID of process that holds the lock.

sysproperties

Contains extended properties that can be defined on various objects
in a database. The extended properties are a new SQL Server 2000
feature. These extended properties can be used to store a caption
for a table, view, or column, to store an input mask for a column,
to store the formatting rules for displaying the data in a column,
and so on. The sysproperties table is used by the sp_droptype,
sp_validatepropertyinputs, sp_addextendedproperty,
sp_updateextendedproperty, sp_dropextendedproperty
and sp_revokedbaccess system stored procedures to manage
extended properties, and by the fn_listextendedproperty system
function to retrieve the value of an existing extended property.
Each database contains sysproperties system table.

Column name Data type Description id int Extended property id. smallid smallint Data type id, or column id, or user id, or index id. type tinyint Extended property type. name sysname User-defined name of an extended property. value sql_variant, nullable The value of an extended property. It can contains up to 7,500 bytes of data.

sysxlogins

Contains each Windows NT account or group name and each SQL Server
login name. This table is stored in the master database.

Column name Data type Description srvid smallint, nullable Server ID. sid varbinary(85), nullable Security identifier. xstatus smallint For internal use only. xdate1 datetime Date the login was added. xdate2 datetime Date the login was updated. name sysname, nullable Login name. password varbinary(256), nullable Password of the user (may be NULL). dbid smallint ID of the database. language sysname, nullable User's default language. isrpcinmap smallint, nullable For internal use only. ishqoutmap smallint, nullable For internal use only. selfoutmap smallint, nullable For internal use only.


事务全攻略

一 事务的属性

事务具有ACID属性
即 Atomic原子性, Consistent一致性, Isolated隔离性, Durable永久性

原子性

就是事务应作为一个工作单元,事务处理完成，所有的工作要么都在数据库中保存下来，要么完全
回滚，全部不保留

一致性
事务完成或者撤销后，都应该处于一致的状态

隔离性

多个事务同时进行，它们之间应该互不干扰.应该防止一个事务处理其他事务也要修改的数据时，
不合理的存取和不完整的读取数据

永久性
事务提交以后，所做的工作就被永久的保存下来

二 事务并发处理会产生的问题

丢失更新

当两个或多个事务选择同一行，然后基于最初选定的值更新该行时，会发生丢失更新问题、
每个事务都不知道其它事务的存在。最后的更新将重写由其它事务所做的更新，这将导致数据丢失。

脏读
当第二个事务选择其它事务正在更新的行时，会发生未确认的相关性问题。
第二个事务正在读取的数据还没有确认并且可能由更新此行的事务所更改。

不可重复读

当第二个事务多次访问同一行而且每次读取不同的数据时，会发生不一致的分析问题。
不一致的分析与未确认的相关性类似，因为其它事务也是正在更改第二个事务正在读取的数据。
然而，在不一致的分析中，第二个事务读取的数据是由已进行了更改的事务提交的。而且，不一致的分析涉及多次（两次或更多）读取同一行，而且每次信息都由其它事务更改；因而该行被非重复读取。

幻像读

当对某行执行插入或删除操作，而该行属于某个事务正在读取的行的范围时，会发生幻像读问题。
事务第一次读的行范围显示出其中一行已不复存在于第二次读或后续读中，因为该行已被其它事务删除。同样，由于其它事务的插入操作，事务的第二次或后续读显示有一行已不存在于原始读中。

三 事务处理类型

自动处理事务

系统默认每个T－SQL命令都是事务处理 由系统自动开始并提交

隐式事务

当有大量的DDL 和DML命令执行时会自动开始，并一直保持到用户明确提交为止，切换隐式事务可以用SET IMPLICIT_TRANSACTIONS
为连接设置隐性事务模式.当设置为 ON 时，SET IMPLICIT_TRANSACTIONS 将连接设置为隐性事务模式。当设置为 OFF 时，则使连接返回到自动提交事务模式

用户定义事务

由用户来控制事务的开始和结束 命令有: begin tran commit tran rollback tran 命令

分布式事务
跨越多个服务器的事务称为分布式事务,sql server 可以由DTc microsoft distributed transaction coordinator
来支持处理分布式事务,可以使用 BEgin distributed transaction 命令启动一个分布式事务处理

四 事务处理的隔离级别

使用SET TRANSACTION ISOLATION LEVEL来控制由连接发出的所有语句的默认事务锁定行为

从低到高依次是

READ UNCOMMITTED

执行脏读或 0 级隔离锁定，这表示不发出共享锁，也不接受排它锁。当设置该选项时，可以对数据执行未提交读或脏读；在事务结束前可以更改数据内的数值，行也可以出现在数据集中或从数据集消失。该选项的作用与在事务内所有语句中的所有表上设置 NOLOCK 相同。这是四个隔离级别中限制最小的级别。

举例

设table1(A,B,C)
A B C
a1 b1 c1
a2 b2 c2
a3 b3 c3

新建两个连接
在第一个连接中执行以下语句
select * from table1
begin tran
update table1 set c='c'
select * from table1
waitfor delay '00:00:10' --等待10秒
rollback tran
select * from table1

在第二个连接中执行以下语句
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
print '脏读'
select * from table1
if @@rowcount>0
begin
waitfor delay '00:00:10'
print '不重复读'
select * from table1
end

第二个连接的结果

脏读
A B C
a1 b1 c
a2 b2 c
a3 b3 c

'不重复读'
A B C
a1 b1 c1
a2 b2 c2
a3 b3 c3

READ COMMITTED

指定在读取数据时控制共享锁以避免脏读，但数据可在事务结束前更改，从而产生不可重复读取或幻像数据。该选项是 SQL Server 的默认值。

在第一个连接中执行以下语句
SET TRANSACTION ISOLATION LEVEL READ COMMITTED
begin tran
print '初始'
select * from table1
waitfor delay '00:00:10' --等待10秒
print '不重复读'
select * from table1
rollback tran

在第二个连接中执行以下语句
SET TRANSACTION ISOLATION LEVEL READ COMMITTED

update table1 set c='c'

第一个连接的结果

初始
A B C
a1 b1 c1
a2 b2 c2
a3 b3 c3

不重复读
A B C
a1 b1 c
a2 b2 c
a3 b3 c

REPEATABLE READ

锁定查询中使用的所有数据以防止其他用户更新数据，但是其他用户可以将新的幻像行插入数据集，且幻像行包括在当前事务的后续读取中。因为并发低于默认隔离级别，所以应只在必要时才使用该选项。

在第一个连接中执行以下语句
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
begin tran
print '初始'
select * from table1
waitfor delay '00:00:10' --等待10秒
print '幻像读'
select * from table1
rollback tran

在第二个连接中执行以下语句
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
insert table1 select 'a4','b4','c4'

第一个连接的结果

初始
A B C
a1 b1 c1
a2 b2 c2
a3 b3 c3

幻像读
A B C
a1 b1 c1
a2 b2 c2
a3 b3 c3
a4 b4 c4

SERIALIZABLE

在数据集上放置一个范围锁，以防止其他用户在事务完成之前更新数据集或将行插入数据集内。这是四个隔离级别中限制最大的级别。因为并发级别较低，所以应只在必要时才使用该选项。该选项的作用与在事务内所有 SELECT 语句中的所有表上设置 HOLDLOCK 相同。

在第一个连接中执行以下语句
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
begin tran
print '初始'
select * from table1
waitfor delay '00:00:10' --等待10秒
print '没有变化'
select * from table1
rollback tran

在第二个连接中执行以下语句
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
insert table1 select 'a4','b4','c4'

第一个连接的结果

初始
A B C
a1 b1 c1
a2 b2 c2
a3 b3 c3

没有变化
A B C
a1 b1 c1
a2 b2 c2
a3 b3 c3

五 事务处理嵌套的语法和对@@TRANCOUNT的影响

BEGIN TRAN @@TRANCOUNT＋1
COMMIT TRAN @@TRANCOUNT－1
ROLLBACK TR
------------------------------------------------------------


sp_lock2
Version: SQL Server 7.0/2000
Created by: Alexander Chigrik
http://www.MSSQLCity.com/ - all about MS SQL
(SQL Server Articles, FAQ, Scripts, Tips and Test Exams).

This stored procedure can be used instead of sp_lock system stored procedure
to return more detailed locking view (it can return user name, host name,
database name, object name, index name and object owner).
This is the example to use sp_lock2:

EXEC sp_lock2

/*
Version: SQL Server 7.0/2000
Created by: Alexander Chigrik
http://www.MSSQLCity.com/ - all about MS SQL
(SQL Server Articles, FAQ, Scripts, Tips and Test Exams).

This stored procedure can be used instead of sp_lock stored procedure
to return more detailed locking view (it can return user name, host name,
database name, object name, index name and object owner).
This is the example to use sp_lock2:

EXEC sp_lock2
*/

USE MASTER
GO
IF OBJECT_ID('sp_lock2') IS NOT NULL DROP PROC sp_lock2
GO
CREATE PROCEDURE sp_lock2
@spid1 int = NULL, /* server process id to check for locks */
@spid2 int = NULL /* other process id to check for locks */
as

set nocount on
/*
** Show the locks for both parameters.
*/
declare @objid int,
@indid int,
@dbid int,
@string Nvarchar(255)

CREATE TABLE #locktable
(
spid smallint
,loginname nvarchar(20)
,hostname nvarchar(30)
,dbid int
,dbname nvarchar(20)
,ObjOwner nvarchar(128)
,objId int
,ObjName nvarchar(128)
,IndId int
,IndName nvarchar(128)
,Type nvarchar(4)
,Resource nvarchar(16)
,Mode nvarchar(8)
,Status nvarchar(5)
)

if @spid1 is not NULL
begin
INSERT #locktable
(
spid
,loginname
,hostname
,dbid
,dbname
,ObjOwner
,objId
,ObjName
,IndId
,IndName
,Type
,Resource
,Mode
,Status
)
select convert (smallint, l.req_spid)
,coalesce(substring (s.loginame, 1, 20),'')
,coalesce(substring (s.hostname, 1, 30),'')
,l.rsc_dbid
,substring (db_name(l.rsc_dbid), 1, 20)
,''
,l.rsc_objid
,''
,l.rsc_indid
,''
,substring (v.name, 1, 4)
,substring (l.rsc_text, 1, 16)
,substring (u.name, 1, 8)
,substring (x.name, 1, 5)
from master.dbo.syslockinfo l,
master.dbo.spt_values v,
master.dbo.spt_values x,
master.dbo.spt_values u,
master.dbo.sysprocesses s
where l.rsc_type = v.number
and v.type = 'LR'
and l.req_status = x.number
and x.type = 'LS'
and l.req_mode + 1 = u.number
and u.type = 'L'
and req_spid in (@spid1, @spid2)
and req_spid = s.spid
end
/*
** No parameters, so show all the locks.
*/
else
begin
INSERT #locktable
(
spid
,loginname
,hostname
,dbid
,dbname
,ObjOwner
,objId
,ObjName
,IndId
,IndName
,Type
,Resource
,Mode
,Status
)
select convert (smallint, l.req_spid)
,coalesce(substring (s.loginame, 1, 20),'')
,coalesce(substring (s.hostname, 1, 30),'')
,l.rsc_dbid
,substring (db_name(l.rsc_dbid), 1, 20)
,''
,l.rsc_objid
,''
,l.rsc_indid
,''
,substring (v.name, 1, 4)
,substring (l.rsc_text, 1, 16)
,substring (u.name, 1, 8)
,substring (x.name, 1, 5)
from master.dbo.syslockinfo l,
master.dbo.spt_values v,
master.dbo.spt_values x,
master.dbo.spt_values u,
master.dbo.sysprocesses s
where l.rsc_type = v.number
and v.type = 'LR'
and l.req_status = x.number
and x.type = 'LS'
and l.req_mode + 1 = u.number
and u.type = 'L'
and req_spid = s.spid
order by spID
END
DECLARE lock_cursor CURSOR
FOR SELECT dbid, ObjId, IndId FROM #locktable
WHERE Type <>'DB' and Type <> 'FIL'

OPEN lock_cursor
FETCH NEXT FROM lock_cursor INTO @dbid, @ObjId, @IndId
WHILE @@FETCH_STATUS = 0
BEGIN

SELECT @string =
'USE ' + db_name(@dbid) + char(13)
+ 'update #locktable set ObjName = name, ObjOwner = USER_NAME(uid)'
+ ' from sysobjects where id = ' + convert(varchar(32),@objid)
+ ' and ObjId = ' + convert(varchar(32),@objid)
+ ' and dbid = ' + convert(varchar(32),@dbId)

EXECUTE (@string)

SELECT @string =
'USE ' + db_name(@dbid) + char(13)
+ 'update #locktable set IndName = i.name from sysindexes i '
+ ' where i.id = ' + convert(varchar(32),@objid)
+ ' and i.indid = ' + convert(varchar(32),@indid)
+ ' and ObjId = ' + convert(varchar(32),@objid)
+ ' and dbid = ' + convert(varchar(32),@dbId)
+ ' and #locktable.indid = ' + convert(varchar(32),@indid)

EXECUTE (@string)

FETCH NEXT FROM lock_cursor INTO @dbid, @ObjId, @IndId
END
CLOSE lock_cursor
DEALLOCATE lock_cursor

SELECT * FROM #locktable
return (0)
-- END sp_lock2
GO


 Rename SQL Agent
(SQL Server 2000 only)

use msdb
go

if exists (select * from sysobjects where name = N'sp_sqlagent_rename' and type ='P')
drop proc dbo.sp_sqlagent_rename
go

create proc dbo.sp_sqlagent_rename
@old_server nvarchar(30)
as

set nocount on

if (charindex(N'8.00', @@version, 0) = 0)
begin
raiserror('sp_sqlagent_rename is only required on SQL Server 2000, procedure will abort', 11, 1)
end

if (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 0)
begin
raiserror('sp_sqlagent_rename can only be used by sysadmin role members, procedure will abort', 11, 1)
end

declare @new_server nvarchar(30)
select @new_server = convert(nvarchar(30), serverproperty(N'servername'))

if exists(select * from msdb.dbo.sysjobs where upper(originating_server) = upper(@old_server))
begin
update msdb.dbo.sysjobs
set originating_server = @new_server
where originating_server = @old_server

raiserror('sp_sqlagent_rename, %d entries updated from %s to %s', 10, 1,
@@rowcount, @old_server, @new_server )
end
else
begin
raiserror('sp_sqlagent_rename, no entries found for @old_server = %s, 0 rows are updates', 10, 1, @old_server)
end
go

-- sample usage
-- exec msdb.dbo.sp_sqlagent_rename @old_server = 'GERTD00\DEV'


Auto start SQL Agent
-- sp_sqlagent_autostart
-- created by gertd@sqldev.net
-- NOTE: works on NT/Win2K/WinXP only, not in Win9x
--
use master
go

if exists (select * from sysobjects where name = 'sp_sqlagent_autostart' and type = 'P')
drop proc dbo.sp_sqlagent_autostart
go

create proc dbo.sp_sqlagent_autostart
as
set nocount on

declare @service nvarchar(128)

if (charindex(N'7.00', @@version, 0) > 0)
begin
select @service = N'SQLServerAgent'
end

if (charindex(N'8.00', @@version, 0) > 0)
begin
if (serverproperty('instancename') is null)
select @service = N'SQLServerAgent'
else
select @service = N'SQLAgent$' + convert(nvarchar(128), serverproperty('InstanceName'))
end

exec master.dbo.xp_servicecontrol 'START', @service
go

exec master.dbo.sp_procoption 'sp_sqlagent_autostart', 'startup', 'true'
go


Set SQL Agent login and password
-- Copyright (C) 1991-2002 SQLDev.Net
--
-- file: sp_sqlagent_set_connection.sql
-- descr.: Set login and password for regular connections to SQL Agent
-- author: Gert E.R. Drapers (GertD@SQLDev.Net)
--
-- @@bof_revsion_marker
-- revision history
-- yyyy/mm/dd by description
-- ========== ======= ==========================================================
-- 2003/03/20 gertd v1.0.0.0 first release
--
-- @@eof_revsion_marker
-- ***************************************************************************
use msdb
go

if exists (select * from sysobjects where name = 'sp_sqlagent_set_connection' and type = 'P')
drop proc dbo.sp_sqlagent_set_connection
go

create proc dbo.sp_sqlagent_set_connection @host_login_name sysname, @host_login_password sysname, @regular_connections int = NULL
as
set nocount on

declare @rc int,
@os int

-- check if sysadmin role member
if is_srvrolemember ('sysadmin') <> 1
begin
raiserror('Only members of the sysadmin role can execute sp_sqlagent_set_connection', 16, 1)
return
end

-- check parameters
if (@host_login_name is null) or (len(@host_login_name) = 0)
begin
raiserror('Illegal parameter value %s is NULL or empty', 16, 1, '@host_login_name')
return
end

if (@host_login_password is null) or (len(@host_login_password) = 0)
begin
raiserror('Illegal parameter value %s is NULL or empty', 16, 1, '@host_login_password')
return
end

-- check if SQL Server 2000, depends on master.dbo.xp_sqlagent_param
if (charindex(N'8.00', @@version, 0) = 0)
begin
raiserror('sp_sqlagent_set_connection is not supported for versions earlier than SQL Server 2000', 18, 1)
return
end

-- check OS, master.dbo.xp_sqlagent_param only works on NT
exec @rc = master.dbo.xp_MSplatform @os output
if (@os = 2) -- Windows 9x
begin
raiserror('sp_sqlagent_set_connection is not supported on Windows 95/98 platforms', 18, 1)
return
end

-- only if @regular_connections is turned on we allow setting the connection, otherwise we delete it
if (@regular_connections is null)
begin
exec @rc = master.dbo.xp_instance_regread
N'HKEY_LOCAL_MACHINE',
N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
N'RegularConnections',
@regular_connections OUTPUT,
N'no_output'
end
else
begin
exec @rc = master.dbo.xp_instance_regwrite
N'HKEY_LOCAL_MACHINE',
N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
N'RegularConnections',
N'REG_DWORD',
@regular_connections
end

-- delete user id and password
if (@regular_connections = 0)
begin
print N'Delete HostLoginID'
exec @rc = master.dbo.xp_sqlagent_param 2, N'HostLoginID'
print N'Delete HostPassword'
exec @rc = master.dbo.xp_sqlagent_param 2, N'HostPassword'
end

-- set user id and password
if (@regular_connections = 1)
begin
print N'Set HostLoginID'
exec @rc = master.dbo.xp_sqlagent_param 1, N'HostLoginID', @host_login_name
print N'Set HostPassword'
exec @rc = master.dbo.xp_sqlagent_param 3, N'HostPassword', @host_login_password
end
go

-- sample usage

-- regular_connections is already turned on either using SQL Enterprise Manager or
-- exec msdb.dbo.sp_set_sqlagent_properties @regular_connections = 1
-- this sets the login and password
exec msdb.dbo.sp_sqlagent_set_connection N'sa', N'LowRider99'

-- this switches to regular connections and set the login and password
exec msdb.dbo.sp_sqlagent_set_connection N'sa', N'LowRider99', 1


Enumerate the SQL Agent
摘自 [image] http://sqldev.net/sqlagent.htm

use msdb
go

if exists (select * from sysobjects where name = N'sp_sqlagent_enum_errorlogs' and type ='P')
drop proc dbo.sp_sqlagent_enum_errorlogs
go

create proc dbo.sp_sqlagent_enum_errorlogs
as

set nocount on

declare @rc int
declare @version int
declare @pos int
declare @errorlog_file nvarchar(255)
declare @filename nvarchar(255)
declare @filename_no_ext nvarchar(255)
declare @dirname nvarchar(255)
declare @buf nvarchar(255)

-- SQL Server 7.0
if (charindex(N'7.00', @@version, 0) > 0)
begin
exec @rc = master.dbo.xp_regread
N'HKEY_LOCAL_MACHINE',
N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
N'ErrorLogFile',
@errorlog_file OUTPUT,
N'no_output'
end

-- SQL Server 2000 needs to use instance aware Registry read
if (charindex(N'8.00', @@version, 0) > 0)
begin
exec @rc = master.dbo.xp_instance_regread
N'HKEY_LOCAL_MACHINE',
N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
N'ErrorLogFile',
@errorlog_file OUTPUT,
N'no_output'
end

-- reverse the string to find the last slash '\'
select @buf = reverse(@errorlog_file)

-- determine posiktion of last slash, now first slash in reversed string
select @pos = len(@buf) - charindex(char(92), @buf, 0) + 1

-- extract the directory only part, part before the last slash
select @dirname = substring(@errorlog_file, 0, @pos)

-- extract the filename, part after the last slash
select @filename = substring(@errorlog_file, @pos + 1, len(@errorlog_file) - @pos)

-- extract the filename with extension, part after dot in @filename
select @filename_no_ext = substring(@filename, 0, charindex(N'.', @filename, 0))

-- since xp_dirtree does not allow any filters, create temp table to store results
create table #t
(
[subdirectory] nvarchar(255) not null,
[depth] int not null,
[file] int not null
)

-- add all file names to the temp table
insert into #t exec master.dbo.xp_dirtree @dirname, 1, 1

-- retrieve only files that match @filename_no_ext%
select [name] = subdirectory from #t where subdirectory like @filename_no_ext + '%' order by [name]

drop table #t
go

-- sample usage
-- exec msdb.dbo.sp_sqlagent_enum_errorlogs
-- go


Read a SQL Agent log file
http://sqldev.net/sqlagent.htm

use msdb
go

if exists (select * from sysobjects where name = N'sp_sqlagent_read_errorlog' and type ='P')
drop proc dbo.sp_sqlagent_read_errorlog
go

create proc dbo.sp_sqlagent_read_errorlog @fileversion int = null
as

set nocount on

declare @rc int
declare @version int
declare @pos int
declare @errorlog_file nvarchar(255)
declare @filename nvarchar(255)
declare @filename_no_ext nvarchar(255)
declare @dirname nvarchar(255)
declare @buf nvarchar(255)

-- SQL Server 7.0
if (charindex(N'7.00', @@version, 0) > 0)
begin
exec @rc = master.dbo.xp_regread
N'HKEY_LOCAL_MACHINE',
N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
N'ErrorLogFile',
@errorlog_file OUTPUT,
N'no_output'
end

-- SQL Server 2000 needs to use instance aware Registry read
if (charindex(N'8.00', @@version, 0) > 0)
begin
exec @rc = master.dbo.xp_instance_regread
N'HKEY_LOCAL_MACHINE',
N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
N'ErrorLogFile',
@errorlog_file OUTPUT,
N'no_output'
end

-- reverse the string to find the last slash '\'
select @buf = reverse(@errorlog_file)

-- determine posiktion of last slash, now first slash in reversed string
select @pos = len(@buf) - charindex(char(92), @buf, 0) + 1

-- extract the directory only part, part before the last slash
select @dirname = substring(@errorlog_file, 0, @pos)

-- extract the filename, part after the last slash
select @filename = substring(@errorlog_file, @pos + 1, len(@errorlog_file) - @pos)

-- extract the filename with extension, part after dot in @filename
select @filename_no_ext = substring(@filename, 0, charindex(N'.', @filename, 0))

-- change errorlog file to version X
if (@fileversion is not null)
select @errorlog_file = @dirname +N'\' + @filename_no_ext + N'.' + convert(nchar, @fileversion)

-- read file
exec master.dbo.xp_readerrorlog 1, @errorlog_file
go

-- sample usage

-- read currently active log file, SQLAGENT.OUT
-- exec msdb.dbo.sp_sqlagent_read_errorlog

-- read version 1 of log, SQLAGENT.1
-- exec msdb.dbo.sp_sqlagent_read_errorlog 1


SQL Agent Date and Time Handling
version: 1.0.0.1

last updated: 22 November 2002

SQL Agent uses two ways to represent date and time information. In some cases it uses the SQL Server datatime data type, but in most occasions it uses two integers to represent the date and time separately. The format of the date and time integers are very straight forward, the date is formatted as YYYYMMDD and the time is formatted as HHMMSS. Everywhere where schedule information is represented the date and time are stored using integers.

The problem is that you need to convert the integer representation to a datetime, before you can leverage the existing datetime manipulation functions in SQL Server. This article provides helper functions to convert between the two representations. In total there are five user defined functions.

Name Description
fn_AgentDate2DateTime Converts an SQL Agent integer date representation in to a SQL Server datetime datatype, since the time is not specified, the time is always 00:00:00.000
fn_AgentTime2DateTime Converts an SQL Agent integer time representation in to a SQL Server datetime datatype, since the date is not specified, the date is always 1900-01-01
fn_AgentDateTime2DateTime Converts an SQL Agent integer date and time representation in to a SQL Server datetime datatype
fn_DateTime2AgentDate Converts a SQL Server datetime into an SQL Agent integer date representation
fn_DateTime2AgentTime Converts a SQL Server datetime into an SQL Agent integer time representation

Below follows the source code for the five user defined functions:

create function [dbo].[fn_AgentDate2DateTime] (@agentdate int)
returns datetime
as
begin
declare @date datetime,
@year int,
@month int,
@day int,
@datestr nvarchar(40)

select @year = (@agentdate / 10000)
select @month = (@agentdate - (@year * 10000)) / 100
select @day = (@agentdate - (@year * 10000) - (@month * 100))

select @datestr = convert(nvarchar(4), @year) + N'-' +
convert(nvarchar(2), @month) + N'-' +
convert(nvarchar(4), @day)

select @date = convert(datetime, @datestr)

return @date
end
go

-- example
select [dbo].[fn_AgentDate2DateTime](20020430)
go

create function [dbo].[fn_AgentTime2DateTime](@agenttime int)
returns datetime
as
begin
declare @date datetime,
@hour int,
@min int,
@sec int,
@datestr nvarchar(40)

select @hour = (@agenttime / 10000)
select @min = (@agenttime - (@hour * 10000)) / 100
select @sec = (@agenttime - (@hour * 10000) - (@min * 100))

select @datestr = replace(convert(nvarchar(2), @hour) + N':' +
convert(nvarchar(2), @min) + N':' +
convert(nvarchar(2), @sec), ' ', '0')

select @date = convert(datetime, @datestr)

return @date
end
go

-- example
select [dbo].[fn_AgentTime2DateTime] (110015)
go

create function [dbo].[fn_AgentDateTime2DateTime] (@agentdate int, @agenttime int)
returns datetime
as
begin
declare @date datetime,
@year int,
@month int,
@day int,
@hour int,
@min int,
@sec int,
@datestr nvarchar(40)

select @year = (@agentdate / 10000)
select @month = (@agentdate - (@year * 10000)) / 100
select @day = (@agentdate - (@year * 10000) - (@month * 100))

select @hour = (@agenttime / 10000)
select @min = (@agenttime - (@hour * 10000)) / 100
select @sec = (@agenttime - (@hour * 10000) - (@min * 100))

select @datestr = convert(nvarchar(4), @year) + N'-' +
convert(nvarchar(2), @month) + N'-' +
convert(nvarchar(4), @day) + N' ' +
replace(convert(nchar(2), @hour) + N':' +
convert(nchar(2), @min) + N':' +
convert(nchar(2), @sec), ' ', '0')

select @date = convert(datetime, @datestr)

return @date
end
go

-- example
select [dbo].[fn_AgentDateTime2DateTime] (20020222, 110015)
go

create function [dbo].[fn_DateTime2AgentDate] (@date datetime)
returns int
as
begin
declare @dateint int

select @dateint = (datepart(year, @date) * 10000) +
(datepart(month, @date) * 100) +
(datepart(day, @date))

return @dateint
end
go

-- example
select [dbo].[fn_DateTime2AgentDate] (getdate())
go

create function [dbo].[fn_DateTime2AgentTime] (@date datetime)
returns int
as
begin
declare @timeint int

select @timeint = (datepart(hour, @date) * 10000) +
(datepart(minute, @date) * 100) +
(datepart(second, @date))

return @timeint
end
go

-- example
select [dbo].[fn_DateTime2AgentTime] (getdate())
go


ExportToExcel
Version: SQL Server 7.0/2000
Created by: Alexander Chigrik
http://www.MSSQLCity.com/ - all about MS SQL
(SQL Server Articles, FAQ, Scripts, Tips and Test Exams).

This stored procedure can be used to insert the result set of the
particular select statement into Excel file (c:\ImportToExcel.xls,
by default).
You can pass the server name, user name, user password, the select
statement to execute, and the file name to store the results set,
as in the example below:

EXEC ExportToExcel @server = '.',
@uname = 'sa',
@QueryText = 'SELECT au_fname FROM pubs..authors',
@filename = 'c:\ImportToExcel.xls'

/*
Version: SQL Server 7.0/2000
Created by: Alexander Chigrik
http://www.MSSQLCity.com/ - all about MS SQL
(SQL Server Articles, FAQ, Scripts, Tips and Test Exams).

This stored procedure can be used to insert the result set of the
particular select statement into Excel file (c:\ImportToExcel.xls,
by default).
You can pass the server name, user name, user password, the select
statement to execute, and the file name to store the results set,
as in the example below:

EXEC ExportToExcel @server = '.',
@uname = 'sa',
@QueryText = 'SELECT au_fname FROM pubs..authors',
@filename = 'c:\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) = 'c:\ImportToExcel.xls'
)
AS
DECLARE @SQLServer int,
@QueryResults int,
@CurrentResultSet int,
@object int,
@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

-- Sets the server to the local server
IF @server IS NULL SELECT @server = @@servername

-- Sets the username to the current user name
IF @uname IS NULL SELECT @uname = SYSTEM_USER

SET NOCOUNT ON

EXEC @hr = sp_OACreate 'SQLDMO.SQLServer', @SQLServer OUT
IF @hr <> 0
BEGIN
PRINT 'error create SQLDMO.SQLServer'
RETURN
END

-- Connect to the 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

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

EXEC @hr = sp_OAMethod @QueryResults, 'CurrentResultSet', @CurrentResultSet OUT
IF @hr <> 0
BEGIN
PRINT 'error get CurrentResultSet'
RETURN
END

EXEC @hr = sp_OAMethod @QueryResults, 'Columns', @Columns OUT
IF @hr <> 0
BEGIN
PRINT 'error get Columns'
RETURN
END

EXEC @hr = sp_OAMethod @QueryResults, 'Rows', @Rows OUT
IF @hr <> 0
BEGIN
PRINT 'error get Rows'
RETURN
END

EXEC @hr = sp_OACreate 'Excel.Application', @object OUT
IF @hr <> 0
BEGIN
PRINT 'error create Excel.Application'
RETURN
END

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

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

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)
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


ExportToWord
Version: SQL Server 7.0/2000
Created by: Alexander Chigrik
http://www.MSSQLCity.com/ - all about MS SQL
(SQL Server Articles, FAQ, Scripts, Tips and Test Exams).

This stored procedure can be used to export the text string into
Microsoft Word. You can pass the text string and the file name
(if the file name was not specified, the c:\ImportToWord.doc
will be used) into this stored procedure, as in the example below:

EXEC ExportToWord @Text = 'Test example',
@filename = 'c:\ImportToWord.doc'

This stored procedure can be used to export the text string into
Microsoft Word. You can pass the text string and the file name
(if the file name was not specified, the c:\ImportToWord.doc
will be used) into this stored procedure, as in the example below:

EXEC ExportToWord @Text = 'Test example',
@filename = 'c:\ImportToWord.doc'
*/

IF OBJECT_ID('ExportToWord') IS NOT NULL DROP PROC ExportToWord
GO

CREATE PROCEDURE ExportToWord (
@Text varchar(200) = null,
@filename varchar(200) = 'c:\ImportToWord.doc'
)
AS
DECLARE @object int,
@Range int,
@Documents int,
@Document int,
@hr int,
@result_str varchar(255)

IF @Text IS NULL
BEGIN
PRINT 'You should write text'
RETURN
END

SET NOCOUNT ON

EXEC @hr = sp_OACreate 'Word.Application', @object OUT
IF @hr <> 0
BEGIN
PRINT 'error create Word.Application'
RETURN
END

EXEC @hr = sp_OAGetProperty @object, 'Documents', @Documents OUT
IF @hr <> 0
BEGIN
PRINT 'error create Documents'
RETURN
END

EXEC @hr = sp_OAMethod @Documents, 'Add', @Document OUT
IF @hr <> 0
BEGIN
PRINT 'error with method Add'
RETURN
END

EXEC @hr = sp_OAGetProperty @Document, 'Range', @Range OUT
IF @hr <> 0
BEGIN
PRINT 'error create Range'
RETURN
END

EXEC @hr = sp_OASetProperty @Range, 'Text', @Text
IF @hr <> 0
BEGIN
PRINT 'error set Text'
RETURN
END

SELECT @result_str = 'SaveAs("' + @filename + '")'
EXEC @hr = sp_OAMethod @Document, @result_str
IF @hr <> 0
BEGIN
PRINT 'error with method SaveAs'
RETURN
END

EXEC @hr = sp_OAMethod @Documents, 'Close'
IF @hr <> 0
BEGIN
PRINT 'error with method Close'
RETURN
END

EXEC @hr = sp_OADestroy @object
IF @hr <> 0
BEGIN
PRINT 'error destroy Word.Application'
RETURN
END
GO


Create Excel XLS from T-SQL
Create Excel XLS from T-SQL
Script Rating Total number of votes [30]
By: David A. Long
This is a T-SQL script that uses OLE, ADO, Jet4 ISAM, and Linked Server to create and populate an Excel Workbook (XLS) file from T-SQL query. If the Excel Worksheet exists, the query will append to the "table".
The code is designed to be used by SQL Agent and to append to the step output with verbose and minimal detail.
Code is pretty well commented, including some hard won knowledge about Jet4 ISAM, OLE, ADO, and usage of the Excel table from T-SQL
-- Create XLS script DAL - 04/24/2003
--
-- Designed for Agent scheduling, turn on "Append output for step history"
--
-- Search for %%% to find adjustable constants and other options
--
-- Uses OLE for ADO and OLE DB to create the XLS file if it does not exist
-- Linked server requires the XLS to exist before creation
-- Uses OLE ADO to Create the XLS Worksheet for use as a table by T-SQL
-- Uses Linked Server to allow T-SQL access to XLS table
-- Uses T-SQL to populate te XLS worksheet, very fast
--
PRINT 'Begin CreateXLS script at '+RTRIM(CONVERT(varchar(24),GETDATE(),121))+' '
PRINT ''
GO

SET NOCOUNT ON
DECLARE @Conn int -- ADO Connection object to create XLS
, @hr int -- OLE return value
, @src varchar(255) -- OLE Error Source
, @desc varchar(255) -- OLE Error Description
, @Path varchar(255) -- Drive or UNC path for XLS
, @Connect varchar(255) -- OLE DB Connection string for Jet 4 Excel ISAM
, @WKS_Created bit -- Whether the XLS Worksheet exists
, @WKS_Name varchar(128) -- Name of the XLS Worksheet (table)
, @ServerName nvarchar(128) -- Linked Server name for XLS
, @DDL varchar(8000) -- Jet4 DDL for the XLS WKS table creation
, @SQL varchar(8000) -- INSERT INTO XLS T-SQL
, @Recs int -- Number of records added to XLS
, @Log bit -- Whether to log process detail

-- Init variables
SELECT @Recs = 0
-- %%% 1 = Verbose output detail, helps find problems, 0 = minimal output detail
, @Log = 1
-- %%% assign the UNC or path and name for the XLS file, requires Read/Write access
-- must be accessable from server via SQL Server service account
-- & SQL Server Agent service account, if scheduled
SET @Path = 'C:\TEMP\Test_'+CONVERT(varchar(10),GETDATE(),112)+'.xls'
-- assign the ADO connection string for the XLS creation
SET @Connect = 'Provider=Microsoft.Jet.OLEDB.4.0;Data Source='+@Path+';Extended Properties=Excel 8.0'
-- %%% assign the Linked Server name for the XLS population
SET @ServerName = 'EXCEL_TEST'
-- %%% Rename Table as required, this will also be the XLS Worksheet name
SET @WKS_Name = 'People'
-- %%% Table creation DDL, uses Jet4 syntax,
-- Text data type = varchar(255) when accessed from T-SQL
SET @DDL = 'CREATE TABLE '+@WKS_Name+' (SSN Text, Name Text, Phone Text)'
-- %%% T-SQL for table population, note the 4 part naming required by Jet4 OLE DB
-- INSERT INTO SELECT, INSERT INTO valueS, and EXEC sp types are supported
-- Linked Server does not support SELECT INTO types
SET @SQL = 'INSERT INTO '+@ServerName+'...'+@WKS_Name+' (SSN, Name, Phone) '
SET @SQL = @SQL+'SELECT au_id AS SSN'
SET @SQL = @SQL+', LTRIM(RTRIM(ISNULL(au_fname,'''')+'' ''+ISNULL(au_lname,''''))) AS Name'
SET @SQL = @SQL+', phone AS Phone '
SET @SQL = @SQL+'FROM pubs.dbo.authors'

IF @Log = 1 PRINT 'Created OLE ADODB.Connection object'
-- Create the Conn object
EXEC @hr = sp_OACreate 'ADODB.Connection', @Conn OUT
IF @hr <> 0 -- have to use <> as OLE / ADO can return negative error numbers
BEGIN
-- Return OLE error
EXEC sp_OAGetErrorInfo @Conn, @src OUT, @desc OUT
SELECT Error=convert(varbinary(4),@hr), Source=@src, Description=@desc
RETURN
END

IF @Log = 1 PRINT char(9)+'Assigned ConnectionString property'
-- Set a the Conn object's ConnectionString property
-- Work-around for error using a variable parameter on the Open method
EXEC @hr = sp_OASetProperty @Conn, 'ConnectionString', @Connect
IF @hr <> 0
BEGIN
-- Return OLE error
EXEC sp_OAGetErrorInfo @Conn, @src OUT, @desc OUT
SELECT Error=convert(varbinary(4),@hr), Source=@src, Description=@desc
RETURN
END

IF @Log = 1 PRINT char(9)+'Open Connection to XLS, for file Create or Append'
-- Call the Open method to create the XLS if it does not exist, can't use parameters
EXEC @hr = sp_OAMethod @Conn, 'Open'
IF @hr <> 0
BEGIN
-- Return OLE error
EXEC sp_OAGetErrorInfo @Conn, @src OUT, @desc OUT
SELECT Error=convert(varbinary(4),@hr), Source=@src, Description=@desc
RETURN
END

-- %%% This section could be repeated for multiple Worksheets (Tables)
IF @Log = 1 PRINT char(9)+'Execute DDL to create '''+@WKS_Name+''' worksheet'
-- Call the Execute method to Create the work sheet with the @WKS_Name caption,
-- which is also used as a Table reference in T-SQL
-- Neat way to define column data types in Excel worksheet
-- Sometimes converting to text is the only work-around for Excel's General
-- Cell formatting, even though the Cell contains Text, Excel tries to format
-- it in a "Smart" way, I have even had to use the single quote appended as the
-- 1st character in T-SQL to force Excel to leave it alone
EXEC @hr = sp_OAMethod @Conn, 'Execute', NULL, @DDL, NULL, 129 -- adCmdText + adExecuteNoRecords
-- 0x80040E14 for table exists in ADO
IF @hr = 0x80040E14
-- kludge, skip 0x80042732 for ADO Optional parameters (NULL) in SQL7
OR @hr = 0x80042732
BEGIN
-- Trap these OLE Errors
IF @hr = 0x80040E14
BEGIN
PRINT char(9)+''''+@WKS_Name+''' Worksheet exists for append'
SET @WKS_Created = 0
END
SET @hr = 0 -- ignore these errors
END
IF @hr <> 0
BEGIN
-- Return OLE error
EXEC sp_OAGetErrorInfo @Conn, @src OUT, @desc OUT
SELECT Error=convert(varbinary(4),@hr), Source=@src, Description=@desc
RETURN
END

IF @Log = 1 PRINT 'Destroyed OLE ADODB.Connection object'
-- Destroy the Conn object, +++ important to not leak memory +++
EXEC @hr = sp_OADestroy @Conn
IF @hr <> 0
BEGIN
-- Return OLE error
EXEC sp_OAGetErrorInfo @Conn, @src OUT, @desc OUT
SELECT Error=convert(varbinary(4),@hr), Source=@src, Description=@desc
RETURN
END

-- Linked Server allows T-SQL to access the XLS worksheet (Table)
-- This must be performed after the ADO stuff as the XLS must exist
-- and contain the schema for the table, or worksheet
IF NOT EXISTS(SELECT srvname from master.dbo.sysservers where srvname = @ServerName)
BEGIN
IF @Log = 1 PRINT 'Created Linked Server '''+@ServerName+''' and Login'
EXEC sp_addlinkedserver @server = @ServerName
, @srvproduct = 'Microsoft Excel Workbook'
, @provider = 'Microsoft.Jet.OLEDB.4.0'
, @datasrc = @Path
, @provstr = 'Excel 8.0'
-- no login name or password are required to connect to the Jet4 ISAM linked server
EXEC sp_addlinkedsrvlogin @ServerName, 'false'
END

-- Have to EXEC the SQL, otherwise the SQL is evaluated
-- for the linked server before it exists
EXEC (@SQL)
PRINT char(9)+'Populated '''+@WKS_Name+''' table with '+CONVERT(varchar,@@ROWCOUNT)+' Rows'

-- %%% Optional you may leave the Linked Server for other XLS operations
-- Remember that the Linked Server will not create the XLS, so remove it
-- When you are done with it, especially if you delete or move the file
IF EXISTS(SELECT srvname from master.dbo.sysservers where srvname = @ServerName)
BEGIN
IF @Log = 1 PRINT 'Deleted Linked Server '''+@ServerName+''' and Login'
EXEC sp_dropserver @ServerName, 'droplogins'
END
GO

SET NOCOUNT OFF
PRINT ''
PRINT 'Finished CreateXLS script at '+RTRIM(CONVERT(varchar(24),GETDATE(),121))+' '
GO


配置复制

返回

配置复制

豆豆技术网制作 修改于:2003-5-14 投稿信箱：web@ddhome.net

请记住我们的口号是:让计算机真正大众化,让更多的人了解计算机,使用计算机！！

非本站原创文章可以自由转载、本站原创文章未经同意不得转载！

原创文章版权归本站及作者所有，未经同意不得转载，转载文章必须注明选自豆豆技术网，谢谢合作！


合并复制

返回

合并复制

豆豆技术网制作 修改于:2003-5-14 投稿信箱：web@ddhome.net

请记住我们的口号是:让计算机真正大众化,让更多的人了解计算机,使用计算机！！

非本站原创文章可以自由转载、本站原创文章未经同意不得转载！

原创文章版权归本站及作者所有，未经同意不得转载，转载文章必须注明选自豆豆技术网，谢谢合作！


快照复制

返回

快照复制

豆豆技术网制作 修改于:2003-5-14 投稿信箱：web@ddhome.net

请记住我们的口号是:让计算机真正大众化,让更多的人了解计算机,使用计算机！！

非本站原创文章可以自由转载、本站原创文章未经同意不得转载！

原创文章版权归本站及作者所有，未经同意不得转载，转载文章必须注明选自豆豆技术网，谢谢合作！


复制选项的规划

返回

复制选项的规划

豆豆技术网制作 修改于:2003-5-14 投稿信箱：web@ddhome.net

请记住我们的口号是:让计算机真正大众化,让更多的人了解计算机,使用计算机！！

非本站原创文章可以自由转载、本站原创文章未经同意不得转载！

原创文章版权归本站及作者所有，未经同意不得转载，转载文章必须注明选自豆豆技术网，谢谢合作！


复制的概述和术语

返回

复制的概述和术语

豆豆技术网制作 修改于:2003-5-14 投稿信箱：web@ddhome.net

请记住我们的口号是:让计算机真正大众化,让更多的人了解计算机,使用计算机！！

非本站原创文章可以自由转载、本站原创文章未经同意不得转载！

原创文章版权归本站及作者所有，未经同意不得转载，转载文章必须注明选自豆豆技术网，谢谢合作！


事务复制

返回

事务复制

豆豆技术网制作 修改于:2003-5-14 投稿信箱：web@ddhome.net

请记住我们的口号是:让计算机真正大众化,让更多的人了解计算机,使用计算机！！

非本站原创文章可以自由转载、本站原创文章未经同意不得转载！

原创文章版权归本站及作者所有，未经同意不得转载，转载文章必须注明选自豆豆技术网，谢谢合作！


复制监视器

返回

复制监视器

豆豆技术网制作 修改于:2003-5-14 投稿信箱：web@ddhome.net

请记住我们的口号是:让计算机真正大众化,让更多的人了解计算机,使用计算机！！

非本站原创文章可以自由转载、本站原创文章未经同意不得转载！

原创文章版权归本站及作者所有，未经同意不得转载，转载文章必须注明选自豆豆技术网，谢谢合作！


数据库设计经验谈(夜来香)

一个成功的管理系统，是由：[50% 的业务 + 50% 的软件] 所组成，而 50% 的成功软件又有 [25% 的数据库 + 25% 的程序] 所组成，数据库设计的好坏是一个关键。如果把企业的数据比做生命所必需的血液，那么数据库的设计就是应用中最重要的一部分。有关数据库设计的材料汗牛充栋，大学学位课程里也有专门的讲述。不过，就如我们反复强调的那样，再好的老师也比不过经验的教诲。所以我归纳历年来所走的弯路及体会，并在网上找了些对数据库设计颇有造诣的专业人士给大家传授一些设计数据库的技巧和经验。精选了其中的 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. 理解客户需求
    看起来这应该是显而易见的事，但需求就是来自客户（这里要从内部和外部客户的角度考虑）。不要依赖用户写下来的需求，真正的需求在客户的脑袋里。你要让客户解释其需求，而且随着开发的继续，还要经常询问客户保证其需求仍然在开发的目的之中。一个不变的真理是：“只有我看见了我才知道我想要的是什么”必然会导致大量的返工，因为数据库没有达到客户从来没有写下来的需求标准。而更糟的是你对他们需求的解释只属于你自己，而且可能是完全错误的。

第 2 部分 - 设计表和字段

  1. 检查各种变化
    我在设计数据库的时候会考虑到哪些数据字段将来可能会发生变更。比方说，姓氏就是如此（注意是西方人的姓氏，比如女性结婚后从夫姓等）。所以，在建立系统存储客户信息时，我倾向于在单独的一个数据表里存储姓氏字段，而且还附加起始日和终止日等字段，这样就可以跟踪这一数据条目的变化。
  2. 采用有意义的字段名
    有一回我参加开发过一个项目，其中有从其他程序员那里继承的程序，那个程序员喜欢用屏幕上显示数据指示用语命名字段，这也不赖，但不幸的是，她还喜欢用一些奇怪的命名法，其命名采用了匈牙利命名和控制序号的组合形式，比如 cbo1、txt2、txt2_b 等等。
    除非你在使用只面向你的缩写字段名的系统，否则请尽可能地把字段描述的清楚些。当然，也别做过头了，比如 Customer_Shipping_Address_Street_Line_1，虽然很富有说明性，但没人愿意键入这么长的名字，具体尺度就在你的把握中。
  3. 采用前缀命名
    如果多个表里有好多同一类型的字段（比如 FirstName），你不妨用特定表的前缀（比如 CusLastName）来帮助你标识字段。

    时效性数据应包括“最近更新日期/时间”字段。时间标记对查找数据问题的原因、按日期重新处理/重载数据和清除旧数据特别有用。
  4. 标准化和数据驱动
    数据的标准化不仅方便了自己而且也方便了其他人。比方说，假如你的用户界面要访问外部数据源（文件、XML 文档、其他数据库等），你不妨把相应的连接和路径信息存储在用户界面支持表里。还有，如果用户界面执行工作流之类的任务（发送邮件、打印信笺、修改记录状态等），那么产生工作流的数据也可以存放在数据库里。预先安排总需要付出努力，但如果这些过程采用数据驱动而非硬编码的方式，那么策略变更和维护都会方便得多。事实上，如果过程是数据驱动的，你就可以把相当大的责任推给用户，由用户来维护自己的工作流过程。
  5. 标准化不能过头
    对那些不熟悉标准化一词（normalization）的人而言，标准化可以保证表内的字段都是最基础的要素，而这一措施有助于消除数据库中的数据冗余。标准化有好几种形式，但 Third Normal Form（3NF）通常被认为在性能、扩展性和数据完整性方面达到了最好平衡。简单来说，3NF 规定：
    * 表内的每一个值都只能被表达一次。
    * 表内的每一行都应该被唯一的标识（有唯一键）。
    * 表内不应该存储依赖于其他键的非键信息。
    遵守 3NF 标准的数据库具有以下特点：有一组表专门存放通过键连接起来的关联数据。比方说，某个存放客户及其有关定单的 3NF 数据库就可能有两个表：Customer 和 Order。Order 表不包含定单关联客户的任何信息，但表内会存放一个键值，该键指向 Customer 表里包含该客户信息的那一行。
    更高层次的标准化也有，但更标准是否就一定更好呢？答案是不一定。事实上，对某些项目来说，甚至就连 3NF 都可能给数据库引入太高的复杂性。

    为了效率的缘故，对表不进行标准化有时也是必要的，这样的例子很多。曾经有个开发餐饮分析软件的活就是用非标准化表把查询时间从平均 40 秒降低到了两秒左右。虽然我不得不这么做，但我绝不把数据表的非标准化当作当然的设计理念。而具体的操作不过是一种派生。所以如果表出了问题重新产生非标准化的表是完全可能的。
  6. Microsoft Visual FoxPro 报表技巧
    如果你正在使用 Microsoft Visual FoxPro，你可以用对用户友好的字段名来代替编号的名称：比如用 Customer Name 代替 txtCNaM。这样，当你用向导程序 [Wizards，台湾人称为‘精灵’] 创建表单和报表时，其名字会让那些不是程序员的人更容易阅读。
  7. 不活跃或者不采用的指示符
    增加一个字段表示所在记录是否在业务中不再活跃挺有用的。不管是客户、员工还是其他什么人，这样做都能有助于再运行查询的时候过滤活跃或者不活跃状态。同时还消除了新用户在采用数据时所面临的一些问题，比如，某些记录可能不再为他们所用，再删除的时候可以起到一定的防范作用。
  8. 使用角色实体定义属于某类别的列[字段]
    在需要对属于特定类别或者具有特定角色的事物做定义时，可以用角色实体来创建特定的时间关联关系，从而可以实现自我文档化。
    这里的含义不是让 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 记录来反映新头衔的变化，不过这样一来在时间上无法跟踪个人所处位置的具体时间。
  9. 采用常用实体命名机构数据
    组织数据的最简单办法就是采用常用名字，比如：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 等。
    采用一般抽象术语来标识“事物”的类别可以让你在关联数据以满足业务要求方面获得巨大的灵活性，同时这样做还可以显著降低数据存储所需的冗余量。
  10. 用户来自世界各地
    在设计用到网络或者具有其他国际特性的数据库时，一定要记住大多数国家都有不同的字段格式，比如邮政编码等，有些国家，比如新西兰就没有邮政编码一说。
  11. 数据重复需要采用分立的数据表
    如果你发现自己在重复输入数据，请创建新表和新的关系。
  12. 每个表中都应该添加的 3 个有用的字段
    * dRecordCreationDate，在 VB 下默认是 Now()，而在 SQL Server 下默认为 GETDATE()
    * sRecordCreator，在 SQL Server 下默认为 NOT NULL DEFAULT USER
    * nRecordVersion，记录的版本标记；有助于准确说明记录中出现 null 数据或者丢失数据的原因
  13. 对地址和电话采用多个字段
    描述街道地址就短短一行记录是不够的。Address_Line1、Address_Line2 和 Address_Line3 可以提供更大的灵活性。还有，电话号码和邮件地址最好拥有自己的数据表，其间具有自身的类型和标记类别。

    过分标准化可要小心，这样做可能会导致性能上出现问题。虽然地址和电话表分离通常可以达到最佳状态，但是如果需要经常访问这类信息，或许在其父表中存放“首选”信息（比如 Customer 等）更为妥当些。非标准化和加速访问之间的妥协是有一定意义的。
  14. 使用多个名称字段
    我觉得很吃惊，许多人在数据库里就给 name 留一个字段。我觉得只有刚入门的开发人员才会这么做，但实际上网上这种做法非常普遍。我建议应该把姓氏和名字当作两个字段来处理，然后在查询的时候再把他们组合起来。

    我最常用的是在同一表中创建一个计算列[字段]，通过它可以自动地连接标准化后的字段，这样数据变动的时候它也跟着变。不过，这样做在采用建模软件时得很机灵才行。总之，采用连接字段的方式可以有效的隔离用户应用和开发人员界面。
  15. 提防大小写混用的对象名和特殊字符
    过去最令我恼火的事情之一就是数据库里有大小写混用的对象名，比如 CustomerData。这一问题从 Access 到 Oracle 数据库都存在。我不喜欢采用这种大小写混用的对象命名方法，结果还不得不手工修改名字。想想看，这种数据库/应用程序能混到采用更强大数据库的那一天吗？采用全部大写而且包含下划符的名字具有更好的可读性（CUSTOMER_DATA），绝对不要在对象名的字符之间留空格。
  16. 小心保留词
    要保证你的字段名没有和保留词、数据库系统或者常用访问方法冲突，比如，最近我编写的一个 ODBC 连接程序里有个表，其中就用了 DESC 作为说明字段名。后果可想而知！DESC 是 DESCENDING 缩写后的保留词。表里的一个 SELECT * 语句倒是能用，但我得到的却是一大堆毫无用处的信息。
  17. 保持字段名和类型的一致性
    在命名字段并为其指定数据类型的时候一定要保证一致性。假如字段在某个表中叫做“agreement_number”，你就别在另一个表里把名字改成“ref1”。假如数据类型在一个表里是整数，那在另一个表里可就别变成字符型了。记住，你干完自己的活了，其他人还要用你的数据库呢。
  18. 仔细选择数字类型
    在 SQL 中使用 smallint 和 tinyint 类型要特别小心，比如，假如你想看看月销售总额，你的总额字段类型是 smallint，那么，如果总额超过了 $32,767 你就不能进行计算操作了。
  19. 删除标记
    在表中包含一个“删除标记”字段，这样就可以把行标记为删除。在关系数据库里不要单独删除某一行；最好采用清除数据程序而且要仔细维护索引整体性。
  20. 避免使用触发器
    触发器的功能通常可以用其他方式实现。在调试程序时触发器可能成为干扰。假如你确实需要采用触发器，你最好集中对它文档化。
  21. 包含版本机制
    建议你在数据库中引入版本控制机制来确定使用中的数据库的版本。无论如何你都要实现这一要求。时间一长，用户的需求总是会改变的。最终可能会要求修改数据库结构。虽然你可以通过检查新字段或者索引来确定数据库结构的版本，但我发现把版本信息直接存放到数据库中不更为方便吗？。
  22. 给文本字段留足余量
    ID 类型的文本字段，比如客户 ID 或定单号等等都应该设置得比一般想象更大，因为时间不长你多半就会因为要添加额外的字符而难堪不已。比方说，假设你的客户 ID 为 10 位数长。那你应该把数据库表字段的长度设为 12 或者 13 个字符长。这算浪费空间吗？是有一点，但也没你想象的那么多：一个字段加长 3 个字符在有 1 百万条记录，再加上一点索引的情况下才不过让整个数据库多占据 3MB 的空间。但这额外占据的空间却无需将来重构整个数据库就可以实现数据库规模的增长了。身份证的号码从 15 位变成 18 位就是最好和最惨痛的例子。
  23. 列[字段]命名技巧
    我们发现，假如你给每个表的列[字段]名都采用统一的前缀，那么在编写 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 个表乃至更多的列[字段]你就知道这个技巧多有用了。

第 3 部分 - 选择键和索引

  1. 数据采掘要预先计划
    我所在的某一客户部门一度要处理 8 万多份联系方式，同时填写每个客户的必要数据（这绝对不是小活）。我从中还要确定出一组客户作为市场目标。当我从最开始设计表和字段的时候，我试图不在主索引里增加太多的字段以便加快数据库的运行速度。然后我意识到特定的组查询和信息采掘既不准确速度也不快。结果只好在主索引中重建而且合并了数据字段。我发现有一个指示计划相当关键——当我想创建系统类型查找时为什么要采用号码作为主索引字段呢？我可以用传真号码进行检索，但是它几乎就象系统类型一样对我来说并不重要。采用后者作为主字段，数据库更新后重新索引和检索就快多了。

    可操作数据仓库（ODS）和数据仓库（DW）这两种环境下的数据索引是有差别的。在 DW 环境下，你要考虑销售部门是如何组织销售活动的。他们并不是数据库管理员，但是他们确定表内的键信息。这里设计人员或者数据库工作人员应该分析数据库结构从而确定出性能和正确输出之间的最佳条件。
  2. 使用系统生成的主键
    这类同技巧 1，但我觉得有必要在这里重复提醒大家。假如你总是在设计数据库的时候采用系统生成的键作为主键，那么你实际控制了数据库的索引完整性。这样，数据库和非人工机制就有效地控制了对存储数据中每一行的访问。
    采用系统生成键作为主键还有一个优点：当你拥有一致的键结构时，找到逻辑缺陷很容易。
  3. 分解字段用于索引
    为了分离命名字段和包含字段以支持用户定义的报表，请考虑分解其他字段（甚至主键）为其组成要素以便用户可以对其进行索引。索引将加快 SQL 和报表生成器脚本的执行速度。比方说，我通常在必须使用 SQL LIKE 表达式的情况下创建报表，因为 case number 字段无法分解为 year、serial number、case type 和 defendant code 等要素。性能也会变坏。假如年度和类型字段可以分解为索引字段那么这些报表运行起来就会快多了。
  4. 键设计 4 原则
    * 为关联字段创建外键。
    * 所有的键都必须唯一。
    * 避免使用复合键。
    * 外键总是关联唯一的键字段。
  5. 别忘了索引
    索引是从数据库中获取数据的最高效方式之一。95% 的数据库性能问题都可以采用索引技术得到解决。作为一条规则，我通常对逻辑主键使用唯一的成组索引，对系统键（作为存储过程）采用唯一的非成组索引，对任何外键列[字段]采用非成组索引。不过，索引就象是盐，太多了菜就咸了。你得考虑数据库的空间有多大，表如何进行访问，还有这些访问是否主要用作读写。

    大多数数据库都索引自动创建的主键字段，但是可别忘了索引外键，它们也是经常使用的键，比如运行查询显示主表和所有关联表的某条记录就用得上。还有，不要索引 memo/note 字段，不要索引大型字段（有很多字符），这样作会让索引占用太多的存储空间。
  6. 不要索引常用的小型表
    不要为小型数据表设置任何键，假如它们经常有插入和删除操作就更别这样作了。对这些插入和删除操作的索引维护可能比扫描表空间消耗更多的时间。
  7. 不要把社会保障号码（SSN）或身份证号码（ID）选作键
    永远都不要使用 SSN 或 ID 作为数据库的键。除了隐私原因以外，须知政府越来越趋向于不准许把 SSN 或 ID 用作除收入相关以外的其他目的，SSN 或 ID 需要手工输入。永远不要使用手工输入的键作为主键，因为一旦你输入错误，你唯一能做的就是删除整个记录然后从头开始。

    我在破解他人的程序时候，我看到很多人把 SSN 或 ID 还曾被用做系列号，当然尽管这么做是非法的。而且人们也都知道这是非法的，但他们已经习惯了。后来，随着盗取身份犯罪案件的增加，我现在的同行正痛苦地从一大摊子数据中把 SSN 或 ID 删除。
  8. 不要用用户的键
    在确定采用什么字段作为表的键的时候，可一定要小心用户将要编辑的字段。通常的情况下不要选择用户可编辑的字段作为键。这样做会迫使你采取以下两个措施：
    * 在创建记录之后对用户编辑字段的行为施加限制。假如你这么做了，你可能会发现你的应用程序在商务需求突然发生变化，而用户需要编辑那些不可编辑的字段时缺乏足够的灵活性。当用户在输入数据之后直到保存记录才发现系统出了问题他们该怎么想？删除重建？假如记录不可重建是否让用户走开？
    * 提出一些检测和纠正键冲突的方法。通常，费点精力也就搞定了，但是从性能上来看这样做的代价就比较大了。还有，键的纠正可能会迫使你突破你的数据和商业/用户界面层之间的隔离。
    所以还是重提一句老话：你的设计要适应用户而不是让用户来适应你的设计。

    不让主键具有可更新性的原因是在关系模式下，主键实现了不同表之间的关联。比如，Customer 表有一个主键 CustomerID，而客户的定单则存放在另一个表里。Order 表的主键可能是 OrderNo 或者 OrderNo、CustomerID 和日期的组合。不管你选择哪种键设置，你都需要在 Order 表中存放 CustomerID 来保证你可以给下定单的用户找到其定单记录。
    假如你在 Customer 表里修改了 CustomerID，那么你必须找出 Order 表中的所有相关记录对其进行修改。否则，有些定单就会不属于任何客户——数据库的完整性就算完蛋了。
    如果索引完整性规则施加到表一级，那么在不编写大量代码和附加删除记录的情况下几乎不可能改变某一条记录的键和数据库内所有关联的记录。而这一过程往往错误丛生所以应该尽量避免。
  9. 可选键(候选键)有时可做主键
    记住，查询数据的不是机器而是人。
    假如你有可选键，你可能进一步把它用做主键。那样的话，你就拥有了建立强大索引的能力。这样可以阻止使用数据库的人不得不连接数据库从而恰当的过滤数据。在严格控制域表的数据库上，这种负载是比较醒目的。如果可选键真正有用，那就是达到了主键的水准。
    我的看法是，假如你有可选键，比如国家表内的 state_code，你不要在现有不能变动的唯一键上创建后续的键。你要做的无非是创建毫无价值的数据。如你因为过度使用表的后续键[别名]建立这种表的关联，操作负载真得需要考虑一下了。
  10. 别忘了外键
    大多数数据库索引自动创建的主键字段。但别忘了索引外键字段，它们在你想查询主表中的记录及其关联记录时每次都会用到。还有，不要索引 memo/notes 字段而且不要索引大型文本字段（许多字符），这样做会让你的索引占据大量的数据库空间。

第 4 部分 - 保证数据的完整性

  1. 用约束而非商务规则强制数据完整性
    如果你按照商务规则来处理需求，那么你应当检查商务层次/用户界面：如果商务规则以后发生变化，那么只需要进行更新即可。假如需求源于维护数据完整性的需要，那么在数据库层面上需要施加限制条件。如果你在数据层确实采用了约束，你要保证有办法把更新不能通过约束检查的原因采用用户理解的语言通知用户界面。除非你的字段命名很冗长，否则字段名本身还不够。

    只要有可能，请采用数据库系统实现数据的完整性。这不但包括通过标准化实现的完整性而且还包括数据的功能性。在写数据的时候还可以增加触发器来保证数据的正确性。不要依赖于商务层保证数据完整性；它不能保证表之间（外键）的完整性所以不能强加于其他完整性规则之上。
  2. 分布式数据系统
    对分布式系统而言，在你决定是否在各个站点复制所有数据还是把数据保存在一个地方之前应该估计一下未来 5 年或者 10 年的数据量。当你把数据传送到其他站点的时候，最好在数据库字段中设置一些标记。在目的站点收到你的数据之后更新你的标记。为了进行这种数据传输，请写下你自己的批处理或者调度程序以特定时间间隔运行而不要让用户在每天的工作后传输数据。本地拷贝你的维护数据，比如计算常数和利息率等，设置版本号保证数据在每个站点都完全一致。
  3. 强制指示完整性(参照完整性?)
    没有好办法能在有害数据进入数据库之后消除它，所以你应该在它进入数据库之前将其剔除。激活数据库系统的指示完整性特性。这样可以保持数据的清洁而能迫使开发人员投入更多的时间处理错误条件。
  4. 关系
    如果两个实体之间存在多对一关系，而且还有可能转化为多对多关系，那么你最好一开始就设置成多对多关系。从现有的多对一关系转变为多对多关系比一开始就是多对多关系要难得多。
  5. 采用视图
    为了在你的数据库和你的应用程序代码之间提供另一层抽象，你可以为你的应用程序建立专门的视图而不必非要应用程序直接访问数据表。这样做还等于在处理数据库变更时给你提供了更多的自由。
  6. 给数据保有和恢复制定计划
    考虑数据保有策略并包含在设计过程中，预先设计你的数据恢复过程。采用可以发布给用户/开发人员的数据字典实现方便的数据识别同时保证对数据源文档化。编写在线更新来“更新查询”供以后万一数据丢失可以重新处理更新。
  7. 用存储过程让系统做重活
    解决了许多麻烦来产生一个具有高度完整性的数据库解决方案之后，我决定封装一些关联表的功能组，提供一整套常规的存储过程来访问各组以便加快速度和简化客户程序代码的开发。数据库不只是一个存放数据的地方，它也是简化编码之地。
  8. 使用查找
    控制数据完整性的最佳方式就是限制用户的选择。只要有可能都应该提供给用户一个清晰的价值列表供其选择。这样将减少键入代码的错误和误解同时提供数据的一致性。某些公共数据特别适合查找：国家代码、状态代码等。

第 5 部分 - 各种小技巧

  1. 文档、文档、文档
    对所有的快捷方式、命名规范、限制和函数都要编制文档。

    采用给表、列[字段]、触发器等加注释的数据库工具。是的，这有点费事，但从长远来看，这样做对开发、支持和跟踪修改非常有用。

    取决于你使用的数据库系统，可能有一些软件会给你一些供你很快上手的文档。你可能希望先开始在说，然后获得越来越多的细节。或者你可能希望周期性的预排，在输入新数据同时随着你的进展对每一部分细节化。不管你选择哪种方式，总要对你的数据库文档化，或者在数据库自身的内部或者单独建立文档。这样，当你过了一年多时间后再回过头来做第 2 个版本，你犯错的机会将大大减少。
  2. 使用常用英语（或者其他任何语言）而不要使用编码
    为什么我们经常采用编码（比如 9935A 可能是‘青岛啤酒’的供应代码，4XF788-Q 可能是帐目编码）？理由很多。但是用户通常都用英语进行思考而不是编码。工作 5 年的会计或许知道 4XF788-Q 是什么东西，但新来的可就不一定了。在创建下拉菜单、列表、报表时最好按照英语名排序。假如你需要编码，那你可以在编码旁附上用户知道的英语。
  3. 保存常用信息
    让一个表专门存放一般数据库信息非常有用。我常在这个表里存放数据库当前版本、最近检查/修复（对 FoxPro）、关联设计文档的名称、客户等信息。这样可以实现一种简单机制跟踪数据库，当客户抱怨他们的数据库没有达到希望的要求而与你联系时，这样做对非客户机/服务器环境特别有用。
  4. 测试、测试、反复测试
    建立或者修订数据库之后，必须用用户新输入的数据测试数据字段。最重要的是，让用户进行测试并且同用户一道保证你选择的数据类型满足商业要求。测试需要在把新数据库投入实际服务之前完成。
  5. 检查设计
    在开发期间检查数据库设计的常用技术是通过其所支持的应用程序原型检查数据库。换句话说，针对每一种最终表达数据的原型应用，保证你检查了数据模型并且查看如何取出数据。
  6. Microsoft Visual FoxPro 设计技巧
    对复杂的 Microsoft Visual FoxPro 数据库应用程序而言，可以把所有的主表放在一个数据库容器文件里，然后增加其他数据库表文件和装载同原有数据库有关的特殊文件。根据需要用这些文件连接到主文件中的主表。比如数据输入、数据索引、统计分析、向管理层或者政府部门提供报表以及各类只读查询等。这一措施简化了用户和组权限的分配，而且有利于应用程序函数（存储过程）的分组和划分，从而在程序必须修改的时候易于管理。

谨把本文献给战斗在狐狸战线上的同志们！！！

-- 夜来香
2002/05/30


更新索引统计
分布页面并不是每次一个记录更新时都要进行更新.在大型数据库中,这会导致巨大的性能损失.因此,当用户初始创建一个空表时,分布页面仍是空的.它仅在发生如下情况时才被更新:
1.用户在一个已存在数据表上创建一个索引.
2.用户进行了update satatic语句
从系统管理员角度来看,用户应该创建一个工具来自动地更新分布页面.自动更新应该至少每周一次,如果数据量每天增加10%以上则应每天一次.
因为不可能每天都添加索引,用户需要使用update statistics语句更新分布页面,用以优化SQLserver.

UPDATE STATISTICS
在指定的表或索引视图中，对一个或多个统计组（集合）有关键值分发的信息进行更新。若要基于列生成统计，请参见 CREATE STATISTICS。

语法
UPDATE STATISTICS table | view
[
index
| ( statistics_name [ ,...n ] )
]
[ WITH
[
[ FULLSCAN ]
| SAMPLE number { PERCENT | ROWS } ]
| RESAMPLE
]
[ [ , ] [ ALL | COLUMNS | INDEX ]
[ [ , ] NORECOMPUTE ]
]

参数
table | view

要更新统计的表或索引视图的名称。表名和视图名必须符合标识符的规则。有关更多信息，请参见使用标识符。由于索引名在每个数据库中不唯一，所以必须指定 table 或 view。可选择指定数据库、表或视图所有者。只有在 Microsoft? SQL Server? 2000 企业版中才支持索引视图。

index

要更新统计的索引。索引名必须符合标识符的规则。如果未指定 index，则更新指定表或索引视图中的所有索引的分发统计。若要查看索引名和描述的列表，请带表名或视图名执行 sp_helpindex。

statistics_name

要更新的统计组（集合）的名称。统计名称必须符合标识符规则。有关生成统计组的更多信息，请参见 CREATE STATISTICS。

n

是表示可以指定多个 statistic_name 组的占位符。

FULLSCAN

指定应读取 table 或 view 中的所有行以收集统计。FULLSCAN 提供与 SAMPLE 100 PERCENT 相同的行为。FULLSCAN 不能与 SAMPLE 选项一起使用。

SAMPLE number { PERCENT | ROWS }

当为较大的表或视图收集统计时，指定要采样的表或索引视图的百分比或行数。number 只允许使用整数，无论它是 PERCENT 还是 ROWS。若要对较大的表或视图使用默认采样行为，请将 SAMPLE number 和 PERCENT 或 ROWS 一起使用。Microsoft SQL Server 将确保值的采样数不低于某一数目，以保证统计有用。如果 PERCENT、ROWS 或 number 选项导致要采样的行数过小，SQL Server 则自动根据表或视图中的现有行数改正采样。

说明 默认行为是在目标表或索引视图上进行采样扫描。SQL Server 自动计算所需的样本大小。

RESAMPLE

指定使用从所有现有统计（包括索引）继承的采样速率来收集统计。如果采样速率导致要采样的行过少，SQL Server 则自动根据表或视图中的现有行数改正采样。

ALL | COLUMNS | INDEX

指定 UPDATE STATISTICS 语句是否影响列统计、索引统计或所有现有统计。如果未指定选项，则 UPDATE STATISTICS 语句影响所有的统计。每个 UPDATE STATISTICS 语句只能指定一种类型（ALL、COLUMNS 或 INDEX）。

NORECOMPUTE

指定过期统计不自动重新计算。统计过期与否取决于在索引列上进行的 INSERT、UPDATE 和 DELETE 操作的数量。指定该选项时，将导致 SQL Server 禁用自动统计重建功能。若要还原自动统计重新计算，请重新执行 UPDATE STATISTICS（不要 NORECOMPUTE 选项），或者执行 sp_autostats。

重要 禁用自动统计重新计算会导致 SQL Server 查询优化器对于涉及指定表的查询选择非最佳的策略。

注释
SQL Server 保留每个索引中关于键值分发的统计，并且使用这些统计来决定查询处理中使用哪个（或哪些）索引。用户可以通过使用 CREATE STATISTICS 语句生成基于非索引列的统计。查询优化依赖于分发步骤的准确性：

如果索引中的键值有显著变化，请对此索引重新运行 UPDATE STATISTICS。

如果索引列中添加、更改或删除大量数据（即如果键值分发更改），或者用 TRUNCATE TABLE 语句将表截断然后重新填充，请使用 UPDATE STATISTICS。
若要查看统计最近一次更新的时间，请使用 STATS_DATE 函数。

只有当能够在计算列上创建索引时，才可以在包含这些计算列的表上创建或更新统计。有关在计算列上创建索引的要求和限制的更多信息，请参见 CREATE INDEX。

权限
UPDATE STATISTICS 权限默认授予表或视图的所有者，并且该权限不可转让。

示例
A. 更新单个表的所有统计
本示例更新表 authors 上的所有索引分发统计。

UPDATE STATISTICS authors

B. 仅更新单一索引的统计
本示例仅更新表 authors 的索引 au_id_ind 的分发信息。

UPDATE STATISTICS authors au_id_ind

C. 使用 50% 采样更新特定统计组（集合）的统计
本示例首先创建表 authors 中 au_lname 列和 au_fname 列的统计组，然后对其进行更新。

CREATE STATISTICS anames
ON authors (au_lname, au_fname)
WITH SAMPLE 50 PERCENT
GO
-- Time passes. The UPDATE STATISTICS statement is then executed.
UPDATE STATISTICS authors(anames)
WITH SAMPLE 50 PERCENT
GO

D. 使用 FULLSCAN 和 NORECOMPUTE 更新特定统计组（集合）的统计
本示例更新表 authors 中的 anames 统计组（集合），强制对表 authors 中的所有行进行完全扫描，并且关闭该统计组（集合）的自动统计更新。

UPDATE STATISTICS authors(anames)
WITH FULLSCAN, NORECOMPUTE

sp_updatestats对当前数据库中所有用户定义的表运行 UPDATE STATISTICS。

语法
sp_updatestats [[@resample =] ''resample'']

返回代码值
0（成功）或 1（失败）

参数
[@resample =] ''resample''

指定 sp_updatestats 将使用 UPDATE STATISTICS 命令的 RESAMPLE 选项。新统计表将继承旧统计表的采样比率。如果未指定 ''resample''，则 sp_updatestats 使用默认采样更新统计表。该参数的数据类型为 varchar(8)，默认值为 ''NO''。

注释
sp_updatestats 会显示表示其进度的消息。完成更新之后，该存储过程将报告已为所有的表更新了统计信息。

权限
只有 DBO 和 sysadmin 固定服务器角色的成员才能执行该过程。

示例
下例为数据库 pubs 中的表更新统计信息。

USE pubs
EXEC sp_updatestats


Sqlserver7 编程技术内幕提供的方法.

drop proc pr_updateindex
create proc pr_updateindex
as
set nocount on
declare get_index_curs cursor
for select name--tablename
from sysobjects --systemtable
where type=''u'' -usertable

declare @holdtable varchar(30)
declare @message varchar(40)
declare @dynamic varchar(51)

open getindex_curs
fetch next from getindex_curs into @holdtable
while @@fetch_status=0
begin
select @dynamic=''update statistics ''+@holdtable
select @message=''updating''+@holdtable
exec(@dynamic)
print @message
fetch next from getindex_curs into @holdtable
end
close getindex_curs

我觉得用上面的sp_updatestats就可以,具体这个没用过


 http://www.integer.org

Copyright (C) 2003 Cameron Michelis copying and redistribution of this file is permitted provided
this notice and the above comments are preserved.
*/

Set quoted_identifier off
use master
DECLARE @fillfactor varchar(2)
DECLARE @tablename varchar(30)
DECLARE @tablename_header varchar(75)
DECLARE @dataname varchar(30)
DECLARE @dataname_header varchar(75)
DECLARE datanames_cursor CURSOR FOR SELECT name FROM sysdatabases
WHERE name not in ('master', 'pubs', 'tempdb', 'model', 'northwind')
/* Variable Initialization */
select @fillfactor = "0" -- Set Fill factor here
-- Note "0" will use original fillfactor.
/* End Variable Initialization */
OPEN datanames_cursor

FETCH NEXT FROM datanames_cursor INTO @dataname

WHILE (@@fetch_status <> -1)
BEGIN
IF (@@fetch_status = -2)
BEGIN
FETCH NEXT FROM datanames_cursor INTO @dataname
CONTINUE
END
SELECT @dataname_header = "Database " + RTRIM(UPPER(@dataname))
PRINT " "
PRINT @dataname_header
PRINT " "
EXEC ("USE " + @dataname + " DECLARE tnames_cursor CURSOR FOR SELECT name from sysobjects where type = 'U'")
Select @dataname_header = RTRIM(UPPER(@dataname))
Exec ("Use " + @dataname)
OPEN tnames_cursor
FETCH NEXT FROM tnames_cursor INTO @tablename
WHILE (@@fetch_status <> -1)
BEGIN
IF (@@fetch_status = -2)
BEGIN
FETCH NEXT FROM tnames_cursor INTO @tablename
CONTINUE
END
SELECT @tablename_header = " Updating " + RTRIM(UPPER(@tablename))
PRINT ""
PRINT @tablename_header
EXEC ("USE " + @dataname + " DBCC DBREINDEX (" + @tablename + "," + "''" + "," + @fillfactor + ")")
EXEC ("USE " + @dataname + " UPDATE STATISTICS " + @tablename)
FETCH NEXT FROM tnames_cursor INTO @tablename
END
DEALLOCATE tnames_cursor
FETCH NEXT FROM datanames_cursor INTO @dataname
END
DEALLOCATE datanames_cursor
PRINT ""
PRINT " "
PRINT "Indexing complete for All User Databases"


周周共享论坛--『数 据 库』浏览：sp_IndexKeyText

总访问量: 43278 今日访问: 295 昨日访问: 449 您的访量:
加入收藏
联系我们
新手上路
上线 [image] 重登陆 [image] 用户控制面板 [image] 搜索 [image] 论坛状态 [image] 帮助 [image] 退出

>> 欢迎 ugvanxk [ 发短信 :: 发表的主题 :: 参与的主题 :: 我能做什么 ] 我的收件箱 (0 新)
周周共享论坛 → 『数 据 库』 → 浏览：sp_IndexKeyText

您是本帖的第 4 个阅读者 浏览上一篇主题 刷新本主题 树形显示贴子 　 浏览下一篇主题
* 贴子主题： sp_IndexKeyText
ugvanxk 帅哥哟，在线，有人找我吗？

等级：版主
文章：210
积分：548
注册：2002-11-2
搜索ugvanxk在『数 据 库』的所有贴子 楼顶
发贴心情 sp_IndexKeyText
sp_IndexKeyText.sql

This stored procedure will script all indexes, primary keys and foreign keys. It's useful as a documentation tool and also for being able to generate scripts that can be used while making modifications that require referential integrity to be removed. A full explanation of the switches required to generate the various output options are included in the script.

Updated on 3/4/02 with fixes for a couple of bugs and a minor alteration of the functionality.

Author: John Thorpe

点击查看用户来源及管理<br>发贴IP：61.48.16.31 2003-6-3 18:20:18
鲜花(0) 鸡蛋(0)
ugvanxk 帅哥哟，在线，有人找我吗？

等级：版主
文章：210
积分：548
注册：2002-11-2
搜索ugvanxk在『数 据 库』的所有贴子 第2楼
发贴心情
/*
*** Karl Mortimer, John Thorpe
*/
-- Tested on SQL SERVER 7.0 & 2000
-- Supports upto 16 column compound keys
--
-- procedure to create 'create index...' statements and 'drop index...' statements for a given table
-- option to include or exclude Primary keys and create 'alter table...' statements
-- option to include or exclude Foreign Keys and create 'alter table...' statements
-- excludes unique constraints (as these do not include indexing) and will not include
-- sorted_data and sorted_data_reorg parameters to the statement
-- as they must be specified manually. This means that if data needs
-- to be sorted when the index is put on, a dbcc reindex must be
-- executed for that table afterwards with the sorted_data / sorted_data_reorg
-- parameters correctly set
--
if exists (select * from sysobjects where id = object_id('dbo.sp_IndexKeyText') and sysstat & 0xf = 4)
drop procedure dbo.sp_IndexKeyText
GO

create procedure sp_IndexKeyText

@Options tinyint = 25, -- bitmap of options. First bit determines whether indexes are included
-- Second bit determines whether primary keys are included
-- Third bit determines whether foreign keys are included
-- Fourth bit determines whether drop statements are scripted
-- Fifth bit determines whether create statements are scripted
-- Default bit 1 true for indexes only, bits 4 and 5 true for both to be scripted.
@Tablename VARCHAR(50) = NULL --optional tablename to drop indexes on a given table

as

set nocount on

if @Options & 8 <> 8 AND @Options & 16 <> 16 --Both Create and Drop script options turned OFF
begin
RAISERROR('Both drop and create scripting switched off, no output would be produced',10,1)
RETURN
end

if @options = 0
begin
RAISERROR('All Index options turned off - no output will be produced',10,1)
RETURN
end

if @Tablename is not null
and @Tablename not in (select name from sysobjects where type = 'U')
begin
RAISERROR('Invalid value given for @Tablename - table does not exist in current database',10,1)
RETURN
end

declare @objid int /* the object id of the table */
declare @indid int /* the index id of an index */
declare @keys varchar(200) /* string to build up index key in */
declare @inddesc varchar(68) /* string to build up index desc in */
declare @dbname varchar(30),
@primary BIT,
@unique BIT,
@clustered BIT,
@fillfactor TINYINT,
@ignoredupkey BIT,
@allowduprow BIT,
@ignoreduprow BIT,
@location sysname,
@index_name VARCHAR(30),
@with VARCHAR(100),
@IndexString VARCHAR(255),
@objname VARCHAR(50),
@Title varchar(30),
@empty varchar(1),
@groupid smallint,
@ExecString varchar(200)

select @empty = '' -- Empty string for concatenation

create table #sysfilegroups(
groupid smallint not null,
allocpolicy smallint not null,
status int not null,
groupname sysname not null)

select @ExecString = 'select * from '+DB_NAME(0)+'..sysfilegroups'

insert into #sysfilegroups
exec (@ExecString)

IF @Tablename is NULL
BEGIN

DECLARE CurTables CURSOR FOR
SELECT name
FROM sysobjects
WHERE type = 'U'
ORDER BY name

END
ELSE
BEGIN

DECLARE CurTables CURSOR FOR
SELECT name
FROM sysobjects
WHERE type = 'U'
AND name = @Tablename
ORDER BY name

END

OPEN CurTables
FETCH NEXT FROM CurTables INTO @objname

CREATE TABLE #CREATE (description VARCHAR(255))

CREATE TABLE #DROP (description VARCHAR(255))

WHILE @@FETCH_STATUS = 0
BEGIN
/*
** Check to see the the table exists and initialize @objid.
*/
select @objid = object_id(@objname)

/*
** Table doesn't exist so return.
*/
if @objid is NULL
begin
GOTO NEXT_TABLE
end

/*
** See if the object has any indexes.
** Since there may be more than one entry in sysindexes for the object,
** this select will set @indid to the index id of the first index.
*/
select @indid = min(indid) from sysindexes
where id = @objid
and indid > 0
and indid < 255

/*
** If no indexes, return.
*/
if @indid is NULL
begin
GOTO NEXT_TABLE
end

set nocount on

/*
** Now check out each index and figure out its type and keys
*/

while @indid IS NOT NULL
begin
select @primary = 0

/*
** See if the index is primary key constraint (0x800).
*/

if exists (select *
from master.dbo.spt_values v, sysindexes i
where i.status & v.number = v.number
and v.type = 'I'
and v.number = 2048
and i.id = @objid
and i.indid = @indid)
BEGIN
if @Options & 2 = 2 -- Primary Key Flag Set
-- if primary key, set flag
select @primary = 1
else GOTO Ignore_index
END

if @primary = 0 and @Options & 1 <> 1 GOTO Ignore_index

/*
** See if the index is unique key constraint (0x1000).
*/
if exists (select * from master.dbo.spt_values v, sysindexes i
where i.status & v.number = v.number
and v.type = 'I'
and v.number = 4096
and i.id = @objid
and i.indid = @indid)
BEGIN
-- if Unique key, ignore index
GOTO Ignore_index
END

/*
** First we'll figure out what the keys are.
*/
declare @k int
declare @thiskey varchar(30)
declare @lastindid int

select @k = 1

while @k <= 16
begin
select @thiskey = index_col(@objname, @indid, @k)

if @thiskey is null
goto keysdone

if @k = 1
select @keys = '['+index_col(@objname, @indid, @k)+']'
else
select @keys = @keys + ', ['+index_col(@objname, @indid, @k)+']'

/*
** Increment @k so it will check for the next key.
*/
select @k = @k + 1

end

/*
** When we get here we now have all the keys.
*/
keysdone:

/*
** Initialize the index description by figuring out if it's a
** clustered or nonclustered index.
*/
if @indid = 1
select @clustered = 1 -- clustered

if @indid > 1
select @clustered = 0 -- non-clustered

/*
** Now we'll check out the status bits for this index and
** build an english description from them.
*/

/*
** See if the index is ignore_dupkey (0x01).
*/
if exists (select *
from master.dbo.spt_values v, sysindexes i
where i.status & v.number = v.number
and v.type = 'I'
and v.number = 1
and i.id = @objid
and i.indid = @indid)
BEGIN
SELECT @ignoredupkey = 1
END
else
BEGIN
SELECT @ignoredupkey = 0
END

/*
** See if the index is unique (0x02).
*/
if exists (select *
from master.dbo.spt_values v, sysindexes i
where i.status & v.number = v.number
and v.type = 'I'
and v.number = 2
and i.id = @objid
and i.indid = @indid)
BEGIN
SELECT @unique = 1
END
else
BEGIN
SELECT @unique = 0
END

/*
** Add the location of the data.
*/

select @location = fg.groupname from #sysfilegroups fg, sysindexes i
where fg.groupid = i.groupid
and i.id = @objid
and i.indid = @indid

-- get the index_name and fillfactor

select @index_name = name,
@fillfactor = origfillfactor
from sysindexes
where id = @objid
and indid = @indid

-- set with clause
SELECT @with = NULLIF('FILLFACTOR = '+CONVERT(VARCHAR,@fillfactor)+',', 'FILLFACTOR = 0,')

select @with = case when @with is null then @empty else @with end
+CASE @ignoredupkey
WHEN 1 THEN 'IGNORE_DUP_KEY '
WHEN 0 THEN @empty
END

-- if @with clause contains anything add keyword 'WITH' to beginning of string

IF @with <> @empty
BEGIN
SELECT @with = ' WITH '+@with
END

if @primary = 0
begin
select @IndexString = NULLIF(' ON '+@Location, ' ON PRIMARY')

select @IndexString = case when @IndexString is null then @empty else @IndexString end

-- print create index statement out
SELECT @IndexString =
'CREATE '+CASE @unique
WHEN 1 THEN 'UNIQUE '
WHEN 0 THEN @empty
END
+CASE @clustered
WHEN 1 THEN ' CLUSTERED '
WHEN 0 THEN ' NONCLUSTERED '
END
+' INDEX ['+@index_name +'] '
+'ON ['+@objname+'] ('+@keys+')'
+case when @with is null then @empty else @with end
+@Indexstring
end
else
begin
select @IndexString = NULLIF('ON '+@Location, 'ON PRIMARY')

select @IndexString = case when @IndexString is null then @empty else @IndexString end

-- print alter table statement out
select @IndexString =
'ALTER TABLE ['+@objname+'] ADD CONSTRAINT ['+@index_name+'] PRIMARY KEY '
+CASE @clustered
WHEN 1 THEN 'CLUSTERED '
WHEN 0 THEN 'NONCLUSTERED '
END
+'('+@keys+')'
+case when @with is null then @empty else @with end
+@IndexString
end

IF @Options & 16 = 16 --create flag ON
BEGIN
SELECT @IndexString = @IndexString + CHAR(13)+'GO'+CHAR(13)+CHAR(13)

INSERT INTO #CREATE(description) valueS (@IndexString)
END

IF @Options & 8 = 8 --drop flag ON
BEGIN
if @primary = 0
SELECT @IndexString = 'DROP index ['+@objname+'].['+@index_name+']'
else
SELECT @IndexString = 'ALTER TABLE ['+@objname+'] DROP CONSTRAINT ['+@index_name+']'

SELECT @IndexString = @IndexString + CHAR(13)+'GO'+CHAR(13)+CHAR(13)

INSERT INTO #DROP(description) valueS (@IndexString)
END

ignore_index:

/*
** Now move @indid to the next index.
*/
select @lastindid = @indid
select @indid = NULL
select @indid = min(indid)
from sysindexes
where id = @objid
and indid > @lastindid
and indid < 255
end

NEXT_TABLE:

FETCH NEXT FROM CurTables INTO @objname

END -- WHILE loop

/*
** Now print out the contents of the temporary index table.
*/
SET nocount on
IF @Options & 8 = 8 --drop flag ON
begin
if @Options & 2 = 2 AND @Options & 1 = 1 SELECT description AS 'Index / Primary Key Drops' FROM #drop
if @Options & 2 <> 2 AND @Options & 1 = 1 SELECT description AS 'Index Drops' FROM #drop
if @Options & 2 = 2 AND @Options & 1 <> 1 SELECT description AS 'Primary Key Drops' FROM #drop
end

IF @Options & 16 = 16 --create flag ON
begin
if @Options & 2 = 2 AND @Options & 1 = 1 SELECT description AS 'Index / Primary Key Creates' FROM #create
if @Options & 2 <> 2 AND @Options & 1 = 1 SELECT description AS 'Index Creates' FROM #create
if @Options & 2 = 2 AND @Options & 1 <> 1 SELECT description AS 'Primary Key Creates' FROM #create
end

CLOSE CurTables
DEALLOCATE CurTables

drop table #create
drop table #drop

点击查看用户来源及管理<br>发贴IP：61.48.16.31 2003-6-3 18:21:43
编辑这个贴子
ugvanxk 帅哥哟，在线，有人找我吗？

等级：版主
文章：210
积分：548
注册：2002-11-2
搜索ugvanxk在『数 据 库』的所有贴子 第3楼
发贴心情
set nocount off

/*
*** Foreign Key code generation
*/

IF @Options & 4 = 4 -- Foreign Key flag ON
begin

SET nocount on
IF @Options & 8 = 8 --drop flag ON
begin
select 'Foreign Key drops' = 'alter table ['+object_name(fkeyid)+']'+char(13)
+'drop constraint ['+object_name(constid)+']'+char(13)+char(13)+'GO'+char(13)+char(13)
from sysreferences
where OBJECT_NAME(rkeyid) like isnull(@Tablename,'%')
order by object_name(fkeyid)
end

IF @Options & 16 = 16 --create flag ON
begin
select 'Foreign Key creates' = 'alter table ['+object_name(fkeyid)+'] '+char(13)
+'add constraint ['+object_name(constid)+'] '
+'Foreign key(['+f1.name+']'
+case when f2.name is null then @empty else '['+f2.name+']' end
+case when f3.name is null then @empty else '['+f3.name+']' end
+case when f4.name is null then @empty else '['+f4.name+']' end
+case when f5.name is null then @empty else '['+f5.name+']' end
+case when f6.name is null then @empty else '['+f6.name+']' end
+case when f7.name is null then @empty else '['+f7.name+']' end
+case when f8.name is null then @empty else '['+f8.name+']' end
+case when f9.name is null then @empty else '['+f9.name+']' end
+case when f10.name is null then @empty else '['+f10.name+']' end
+case when f11.name is null then @empty else '['+f11.name+']' end
+case when f12.name is null then @empty else '['+f12.name+']' end
+case when f13.name is null then @empty else '['+f13.name+']' end
+case when f14.name is null then @empty else '['+f14.name+']' end
+case when f15.name is null then @empty else '['+f15.name+']' end
+case when f16.name is null then @empty else '['+f16.name+']' end
+')'+char(13)
+'references ['+object_name(rkeyid)+'] (['+c1.name+']'
+case when c2.name is null then @empty else '['+c2.name+']' end
+case when c3.name is null then @empty else '['+c3.name+']' end
+case when c4.name is null then @empty else '['+c4.name+']' end
+case when c5.name is null then @empty else '['+c5.name+']' end
+case when c6.name is null then @empty else '['+c6.name+']' end
+case when c7.name is null then @empty else '['+c7.name+']' end
+case when c8.name is null then @empty else '['+c8.name+']' end
+case when c9.name is null then @empty else '['+c9.name+']' end
+case when c10.name is null then @empty else '['+c10.name+']' end
+case when c11.name is null then @empty else '['+c11.name+']' end
+case when c12.name is null then @empty else '['+c12.name+']' end
+case when c13.name is null then @empty else '['+c13.name+']' end
+case when c14.name is null then @empty else '['+c14.name+']' end
+case when c15.name is null then @empty else '['+c15.name+']' end
+case when c16.name is null then @empty else '['+c16.name+']' end
+')'+char(13)
+char(13)
from sysreferences,
syscolumns c1,
syscolumns c2,
syscolumns c3,
syscolumns c4,
syscolumns c5,
syscolumns c6,
syscolumns c7,
syscolumns c8,
syscolumns c9,
syscolumns c10,
syscolumns c11,
syscolumns c12,
syscolumns c13,
syscolumns c14,
syscolumns c15,
syscolumns c16,
syscolumns f1,
syscolumns f2,
syscolumns f3,
syscolumns f4,
syscolumns f5,
syscolumns f6,
syscolumns f7,
syscolumns f8,
syscolumns f9,
syscolumns f10,
syscolumns f11,
syscolumns f12,
syscolumns f13,
syscolumns f14,
syscolumns f15,
syscolumns f16
WHERE c1.colid =* rkey1
AND c1.id =* rkeyid
AND c2.colid =* rkey2
AND c2.id =* rkeyid
AND c3.colid =* rkey3
AND c3.id =* rkeyid
AND c4.colid =* rkey4
AND c4.id =* rkeyid
AND c5.colid =* rkey5
AND c5.id =* rkeyid
AND c6.colid =* rkey6
AND c6.id =* rkeyid
AND c7.colid =* rkey7
AND c7.id =* rkeyid
AND c8.colid =* rkey8
AND c8.id =* rkeyid
AND c9.colid =* rkey9
AND c9.id =* rkeyid
AND c10.colid =* rkey10
AND c10.id =* rkeyid
AND c11.colid =* rkey11
AND c11.id =* rkeyid
AND c12.colid =* rkey12
AND c12.id =* rkeyid
AND c13.colid =* rkey13
AND c13.id =* rkeyid
AND c14.colid =* rkey14
AND c14.id =* rkeyid
AND c15.colid =* rkey15
AND c15.id =* rkeyid
AND c16.colid =* rkey16
AND c16.id =* rkeyid
AND f1.colid =* fkey1
AND f1.id =* fkeyid
AND f2.colid =* fkey2
AND f2.id =* fkeyid
AND f3.colid =* fkey3
AND f3.id =* fkeyid
AND f4.colid =* fkey4
AND f4.id =* fkeyid
AND f5.colid =* fkey5
AND f5.id =* fkeyid
AND f6.colid =* fkey6
AND f6.id =* fkeyid
AND f7.colid =* fkey7
AND f7.id =* fkeyid
AND f8.colid =* fkey8
AND f8.id =* fkeyid
AND f9.colid =* fkey9
AND f9.id =* fkeyid
AND f10.colid =* fkey10
AND f10.id =* fkeyid
AND f11.colid =* fkey11
AND f11.id =* fkeyid
AND f12.colid =* fkey12
AND f12.id =* fkeyid
AND f13.colid =* fkey13
AND f13.id =* fkeyid
AND f14.colid =* fkey14
AND f14.id =* fkeyid
AND f15.colid =* fkey15
AND f15.id =* fkeyid
AND f16.colid =* fkey16
AND f16.id =* fkeyid
AND OBJECT_NAME(rkeyid) like isnull(@Tablename,'%')
end

SET nocount off
end

drop table #sysfilegroups

set nocount off

return

go

点击查看用户来源及管理<br>发贴IP：61.48.16.31 2003-6-3 18:22:02
编辑这个贴子

本主题贴数3，分页： [1]
*快速回复：sp_IndexKeyText
你的用户名: 还没注册? 密码: 忘记密码?
内容
  • HTML标签： 不可用
  • UBB标签： 可用
  • 贴图标签： 可用
  • Flash标签：可用
  • 表情字符转换：可用
  • 上传图片：可用
  • 最多16KB
    • 邮件回复 显示签名 [Ctrl+Enter直接提交贴子]

    锁定 | 解锁 | 删除 | 移动 | 固顶 | 奖励 | 惩罚 | 发布公告

    Powered by：DVBBS Ver5.0 Final
    Copyright ?2002 - 2003 pigtwo.com , 页面执行时间：1,015.625毫秒


SQL Database Maintenance Script

Here is a small script that will perform maintenance on all databases on a SQL server.

The script looks at what databases are configured on the SQL Server then retrieves a list of tables for each DB on the SQL Server. The script then uses this list to perform DBCC DBREINDEX on each table in the DB. It also performs UPDATE STATISTICS on the table. When the script has finished re-indexing and updating the statistics on each table in the DB, it then performs a DBCC CHECKDB on the database.

The script then moves onto the next database on the SQL server until all databases have been maintained.

The advantages of using this type of script is that the script doesn't need to be modified if tables, indexes or databases are added, altered or deleted.


Tracking Index Usage
Tracking Index Usage

How can I track how frequently my applications use indexes on a particular table and find out which applications are using the indexes?

Tracking index usage helps you determine which indexes you need to keep, but you can't get this information without a detrimental effect on your server performance. To get the information, run an audit-object trace on the object IDs that represent the indexes. You can also get the information by running a Showplan trace using SQL Server Profiler.

Listing 1: Code to Retrieve ID Numbers to Use for Filtering
-- Get the database ID number for your database.
-- Use this number to filter in Profiler.
SELECT dbid FROM master..sysdatabases
WHERE [name] = N'Northwind'
-- Get the object ID for the base table or view that you're interested in.
-- Use this number to filter in Profiler.
SELECT object_id('dbo.employees')
-- Get your SPID.
-- Use this number to filter in Profiler.
SELECT @@SPID

Listing 2: Test Queries for the Employees Table in Northwind
-- Use the code below to generate test data showing
-- various execution plans against an object.
-- Index seek
SELECT lastname
FROM dbo.employees
WHERE lastname = 'Fuller'
-- Index scan
SELECT lastname
FROM dbo.employees
-- Clustered index scan
SELECT *
FROM dbo.employees
-- Index seek and bookmark lookup
SELECT *
FROM dbo.employees
WHERE lastname = 'Fuller'

Listing 3: Script to Autostart a Trace Without Profiler
/****************************************************/
/* Created by: SQL Profiler */
/* Date: 02/13/2003 06:24:01 PM */
/****************************************************/
/* Comments added by SQL Mag */

-- Create a queue.
DECLARE @rc int
DECLARE @QueueHandle int
EXEC @rc = xp_trace_addnewqueue 1000, 5000, 95, 90, 69216261, @QueueHandle output if
(@rc != 0) GOTO error

-- Set the events.
-- SQL Server 2000杝pecific events won't be scripted.

/* SQL MAG: 40 = SQL:StmtStarting - to get the SQL statement being executed */
EXEC xp_trace_seteventclassrequired @QueueHandle, 40, 1

/* SQL MAG: 48 = Object Open - to check that the objects we're interested in are being opened */
EXEC xp_trace_seteventclassrequired @QueueHandle, 48, 1

/* SQL MAG: 68 = Execution Plan - to see what indexes are being used in the plan */
EXEC xp_trace_seteventclassrequired @QueueHandle, 68, 1

-- Set the filters.
/* SQL MAG - Ignore Profiler events. */
EXEC xp_trace_setappfilter @QueueHandle, N'', N'SQL Profiler'

/* Only look at DBID 6 (Northwind). */
EXEC xp_trace_setdbidfilter @QueueHandle, 6

/* Only look at the dbo.employees base object. */
EXEC xp_trace_setobjidfilter @QueueHandle, 117575457

/* Only look at SPID 7. */
EXEC xp_trace_setspidfilter @QueueHandle, 7

-- Set the destinations.

-- Save the definition in the registry.
EXEC xp_trace_savequeuedefinition @QueueHandle, N'Hunt for Index Usage', 1

-- Display queue handle for future references.
SELECT QueueHandle=@QueueHandle
GOTO finish

error:
SELECT ErrorCode=@rc

finish:
GO