返回总 返回总 返回总 返回总 目录 目录 目录 目录 目 目 目 目 录 录 录 录 第 1 章 Transact-SQL DDL..............................................................5 1.1 创建数据库.............................................................................................5 1.2 创 建 表.............................................................................................8 1.3 表 的 列...........................................................................................11 1.4 .........................................................................................18 表 的 约 束 1.5 创 建 视 图.........................................................................................22 1.6 视 图 分 类.........................................................................................25 1.7 创建视图选项.......................................................................................27 1.8 创建视图举例.......................................................................................28 1.9 .........................................................................................32 创 建 索 引 1.10 删除数据库 表 视图和索引.........................................................34 1.11 小 结.............................................................................................34 第 2 章 Transact-SQL DML 详解 .................................................. 36 2.1 Select ............................................................................................36 语句 2.2 Select 的子句 ........................................................................................42 2.3 复杂的 Select 语句 ...............................................................................46 2.4 Select 的条件 ........................................................................................51 2.5 Select 的其他用法 ................................................................................59 2.6 Insert .............................................................................................67 语句 2.7 Update 语句 ..........................................................................................69 2.8 Delete 和 Truncate Table 语句 .............................................................72 2.9 并行修改和表锁...................................................................................73 2.10 存 储 过 程.......................................................................................75 2.11 .....................................................................................79 存储过程参数 2.12 存储过程编程技巧.............................................................................81 2.13 游 标.............................................................................................84 2.14 存储过程错误处理.............................................................................89 2.15 触 发 器.........................................................................................90 2.16 .................................................................................96 触发器编程技巧 2.17 小 结.................................................................................................99 第 3 章 SQL Server 与 其他产 品集成 概述 ................................. 101 3.1 SQL Server 与 Access 的集成...........................................................101 3.2 SQL Server 与 Excel 的集成..............................................................1063.3 在 IIS 和 IE 中使用 SQL Server ........................................................106 3.4 SQL Server 与 Microsoft Transaction Server 集成............................107 3.5 .............................................................................................111 小 结 第 4 章 使用 Access 访问 SQL Server ........................................ 112 4.1 概 述.............................................................................................112 4.2 Access 连接到 SQL Server.................................................................115 4.3 Access ........................................................................125 设计 应用程序 4.4 开发 VBA 应用程序 ..........................................................................134 4.5 编 程 心 得.......................................................................................141 第 5 章 使用 ODBC 访问 SQL Server 数据 库 ........................... 143 5.1 ODBC .........................................................................................143 概述 5.2 ODBC 组成.........................................................................................144 5.3 ODBC 应用.........................................................................................146 5.4 ODBC 调用的前期和后期工作.........................................................153 5.5 通过 ODBC 访问 SQL Server 数据库...............................................158 5.6 ODBC SQL Server ...............................................163 通过 修改 数据库 5.7 通过 ODBC 调用 SQL Server 数据库的存储过程...........................167 5.8 ODBC 错误处理.................................................................................169 5.9 小 结.............................................................................................170 第 6 章 使用 ADO 访问 SQL Server 数据 库.............................. 171 6.1 概 述.............................................................................................171 6.2 使用 ADO 连接到 SQL Server ..........................................................177 6.3 使用 Recordset 对象查询 SQL Server 数据库..................................181 6.4 使用 Recordset 对象修改 SQL Server 数据库..................................189 6.5 Command SQL Server .................................193 使用 对象操纵 数据库 6.6 高 级 应 用.......................................................................................201 6.7 错 误 处 理.......................................................................................210 6.8 小 结.............................................................................................211 第 7 章 使用 SQL-DMO 管理 SQL Server ................................. 212 7.1 概 述.............................................................................................212 7.2 SQL-DMO 的核心对象分层结构......................................................214 7.3 SQL-DMO 应用初步..........................................................................219 7.4 使用 SQL-DMO 管理 SQL Server 服务器........................................224 7.5 .............................................................................................232 小 结 第 8 章 在 C 中使 用 嵌入 SQL 访问 SQL Server 数据 库.......... 233 8.l 嵌入式 SQL 应用程序开发环境 ........................................................233 8.2 嵌入式 SQL 数据类型 .......................................................................234 8.3 SQL ...............................................................................235 嵌入式 语法8.4 嵌入式 SQL 数据库访问过程 ...........................................................242 8.5 SQL 命令执行方式 ............................................................................248 8.6 SQL .......................................................................252 嵌入式 选项设置 8.7 建立嵌入式 SQL 应用程序 ...............................................................252 8.8 小 结...............................................................................................257第一部分 Transact-SQL 编程 本部分主要讨论 Microsoft SQL Server 的 Transact-SQL 程序设计技术 Transact-SQL 是结构化 查询语言(SQL) 的扩展 而 SQL 是关系数 据库语言的 工 业标 准 SQL 语言分两种 一是用于定义关系数据库的数据 叫做数据定义语 DDL DML 言 二是 用于操纵关系 数据库中的数据 叫做数据操 纵语言 本部分将分别讨论 DDL 和 DML 设计技术第1章 Transact-SQL DDL 在 Transact-SQL 语言中 DDL 数据 定义 语言 是基础 因为 它负 责各 类数 据库 对象 的创建 和删 除 本 章主 要 介绍下 列四 个基 本 DDL 语句 Create Database Create Table Create View 和 Create Index 以及 删除 这些 语句 创建 的对 象 Drop Database Drop Table Drop View 和 Drop Index 1. 1 创建数 据库 在 SQL 术语中 数据 库是一 个 容器 包含 了相 关的 基 表 视图 索引 存储 过程 和其 他对象 在创建这些对 象之前 必须 有一个存储它们 的数据库 在数据库 中 对 象被进 一 步组织为 有一个所 有者 对于某些类型的对象 例如 表 只要属于不同的用户 就可以 在 同一个数 据库中有同样 的名称 然而 对于 产品系统 最好不要出现重复的 名称 一般情 况 下 大多 数产 品对 象由 数 据库所 有者 拥有 为了创 建数 据库 用户 必 须是系 统管 理员 或者 被授 权 使用 Create Database 语句 Create Database 命令 最简 单的 形式 如下 Create Database AppDta 这条语 句创 建 AppDta 数据库 并且把 SQL Server 的 model 数据 库定 义复制到新数 据 库中 也就是说 model 数据库中的每一个表 视图 存储过程 等等的 空拷贝都 复制在 新 数据库 中创 建 SQL Server 为这个数据 库创 建两 个 NT Server 文件 appdta.mdf 保存数 据 appdta_log.ldf 保存 事务 日志 的内 容 这两个文 件 的 默认初 始 大小分 别设 置为 model 数据库 的主文 件和 日志 文件 的大小 如果 需要 SQL Server 将自动扩 展这 些文 件 1.1.1 指定 位置 和大 小 如果 希望为数 据库或事 务日志指 定 一 个或者多 个 特 定 文 件 增加 一 个 On 子句 列出 一个或 者多 个文 件 并 可 为分配 这个 文件 的空 间( 以 MB 为单 位)指 定一个可 选值 例 1-1 说 明了创 建数 据库 的基 本语句 例 1-1 Create Database AppDta On Primary ( Name = AppDta1, Filename = `c: production data appdta1.mdf'', Size = 10MB, MaxSize = 100MB, FileGrowth = 10MB), ( Name = AppDta2, Filename = `c: production data appdtal.ndf'', Size = 10MB, FileGrowth = 10MB ) On 关键 字之 后的 第一 项应该 指 定可 选的 Primary 关键字 表明 这是 一个 主文 件 它包 含该数据 库的系统表和 初始化信息 指定的 其他从属 文件用于保存在用户 表和其 他对象 中 的数据 在这 个示 例中 的 参数如 下 Name SQL Server 使用的逻 辑名 称 Filename 完全限 定的 NT Server 文件 名 Size 文件 的初 始大 小 默认 值是 model 数据 库主 文件(model.mdf) 的大小 MaxSize 最大 的文 件尺 寸 默认 值是 占满 整个 空间 FileGrowth 当需要 时 SQL Server 扩展 文件 的量 默认值 是 10% 一个 SQL Server 数据库 可以 超过 一百 万 TB 大小 1TB=1000MB 为了提 高性 能和 可恢 复性 可以 使用 Log On 子句来 指定 数据 库的 SQL Server 将事务 日志存 储在 一个 与数 据库对 象 不同 的设 备上 如例 1-2 例 1-2 Create Database AppDta On Primary ( Name = AppDta1, Filename = C:\Production\date\appdtal.mdf , Size = 10MB, MaxSize = 100MB, FileGrowth = 10MB), ( Name = AppDta2, Filename = ‘C:\Production\date\appdtal.mdf’, Size = 10MB, FileGrowth = 10MB) Log On ( Name = AppDtaLog1, Filename = ’d:\production\log\appdtalog1.ldf''’, Size = 10MB, MaxSize = 100MB, FileGrowth = 10MB ) 这种 方 式 如 果 数 据 库 所在的 物 理设备被破 坏 日 志还可 以 使 用( 如 果 该日志 所在 的设 备 没 有被 破坏) 使用一个 以 前的数据库备份和一个未 破 坏的日志的脱机拷贝 可以将数据 库恢复 到保 存数 据库 恢复的 设 备失 败时 的状 态 当 指 定明确 的文 件时 按照 SQL Server 的 约定 使用.mdf 作 为 数据 库 主 文件的扩 展名 .ndf 作为 数 据库从属文件 的 扩 展名 .ldf 作为 事务日 志文 件的 扩展 名 1.1.2 修改 数据 库 在数据 库创 建之 后 可以使 用 Alter Database 语句增 加新 文件 删除已 有的文 件 或修改 文件的 设置 例 1-3 的语句 可 增加 一个新 文件 例 1-3 Alter Database AppDta Add File ( Name = AppDta3, Fileanme = `c:\production\data\appdta3.ndf , Size = 10MB, FileGrowth = 10MB ) 还有一 个 Add Log File 子句 它与 Add File 子句有 相同 的格 式 Remove File 子句只使 用以前 指定 的逻 辑文 件名 Alter Database AppDta Remove File AppDta2 Modify File 子句需 要以 前指 定的 逻辑文 件名 并且 可 以带任 何其 他的 参数 例如 参 数 File Growth: Alter Database AppDta Modify File ( Name = AppDta1, FileGrowth=50MB ) 1.1.3 定义 文件 组 数据库文件 不包括事务日志文件 可以组成文件组 最初创 建 一个数据库时 该数 据库的默 认文件组包含 了主文件和没 有明确 分配给用 户定义 文件组的从属 文件 在许多 情 况下 默认 的 文 件组 已经足够 了 对于有些 系统 在 指定的 设备 上创 建用 户定 义 的文件 组 可以提高 数据库性能或 可恢复性 因 为可以指定表或 索引所在的文件组 所以 文 件组提 供 了一种间 接手段 可以 把表和索引放 在指定的设备上 另外 当使用一个 包含了 许多文 件 的文件 组时 SQL Server 根 据文件 可用 的自 由空间 把 文 件组中的 数 据 按 比例 散布在文 件 中 下面 是一 个创 建文 件 组的示 例 Alter Database AppDta Add FileGrouup AppDtaGroup1 当创建 一个 文件 组之 后 可以使 用 Alter Database 语句把新文 件 增加到该 文 件 组中 对 于那些增 加到用户 定义的文件组中的 文件 一般应该 指定一个 与该数据库主文件 不同的 设 备 如例 1-4 例 1-4 Alter Database AppDta Add File ( Name = AppDta2, Filename = d:\production\data\apppdta2.ndf , Size = 10MB, MaxSize = 100MB, FileGrowth = 10MB), ( Name = AppDta3, Filename = d:\production\data\appdta3.ndf , Size = 10MB, MaxSize = 100MB, FileGrowth = 10MB) To FileGroup AppDtaGroup1 为了删 除文 件组 和文 件组所 包 含的 文件 可以 使用 类 似下面 的语 句 Alter Database AppDta Remove File AppDta2 Alter Database AppDta Remove File AppDta3 Alter Database AppDta Remove FileGroup AppDtaGroup1 在删除 文件 或者 文件 组时 它 们必须为空 也可以 使用 Alter Database 语句改 变某 个数 据库 的默 认文 件组 例如 下面 的语 句 Alter Database AppData Modify FileGroup AppDtaGroupl Default 1.2 创 建 表 在关系 数据 库中 基表 包 含了实 际的 数据 在一 个 SQL Server 数 据库中 可以 创建 多 达两万 亿个 表 为了 用 SQL 创建表 输入 一条 Create Table 语句 指定 下述内容 包含表 的数 据库 表的所 有者 表名 在同一 个数据库中和同一个所有者下 该表名必须与任何其它基表或视图 不同 指定 1 到 1024 个列 主键约 束(可选) 1 到 250 个 Unique 约束( 可选) 1 到 253 个外 键约 束( 可选) 1 个或 者多 个 Check 约束 限制 插入 表中 的数 据(可选) 存储表 的文 件组( 可选) 1.2. 1 创建 表的 一般 要求 例 1-5 示意 了一 个比 较复杂 的 Customer 表的 Create Table 语句 正如例 1-5 所示 Create Table 语句首 先指 定将 要创 建 的表 然后 列出 列和 约束 的定 义 之间由逗号 分 开 用一 组 括号括 住 SQL 是一 种 自由 格 式的语 言 一条语句可以放 在多 行 上 在字 和符号之间使 用空格 以提高可读性 该示 例说明 代码样式 代码以每 个列的定 义开始 约束 单独 占一 行 每 个列 的定义中 相似 的部 分对齐 虽然 这种列样式不是必须 的 但是它 比不 对齐 的文 本流形 式 更容 易理 解 SQL 关键字 对大 小写 不敏感 Create Table CREATE TABLE 和 CrEaTe TaBLE 都是正 确的 然而 记住 在安 装 SQL Server 时选 择的 排列 顺序 决定 标识 符和 字符串 文字 是否对大 小写敏 感 如 果使 用对 大 小写不 敏感 的排 列顺 序(默认 的安装 选项) 安装 SQL Server 那么 SQL Server 认为表 名 Customer customer 和 CUSTOMER 是 相同的 并且‵x 等于‵x 如 果使用 对大 小写 敏感 的排列 顺 序安 装 SQL Server 那么 SQL Server 把表 名Customer customer 和 CUSTOMER 作为 不同 的标 识符 并且 认为‵x 不等 于 ‵X 注意 即 使使用对大小 写不敏感 的排列顺序 系统表 中的表名和其他 对象名 称也是 以输入它 们的 方式 存储 例如Customer customer 或CUSTOMER 因此 如果 在AppDta 数据库 中 显 示表 的 清 单 那么 例 1-5 所 示的 语 句创建的 表将被 列为 Customer, 使用对 大小 写 不 敏感 的排列顺 序 仍然可以 在 SQL 语句中 使用 CUSTOMER 或者 customer 引用 该表 例 1-5 中使用的表名是一种 完全限定的名称 在 未限定的 表名 Customer 之前包括 数据库 名称(AppDta)和 所 有者名 称 dbo 使 用 小圆点“.”作 为限定名称 的分 隔 符 当创建数 据库对象时 一般建议 在代码中明确 指定数 据库名称和所有 者名称 这样 可 以归类表 的数据库和所 有者 避免偶 然在错误的数据 库中或 者使用错误的 所有者 创建表 正如本 示例 所示 为了 创 建一个 由数 据库 所有 者拥 有 的表 可 以在限 定 的名 称中使用 dbo 如果省 略了 数据 库名 称 就会 在当前 数据 库中 创建 表 当 前数 据库可以是分 配 给 SQL Server 帐户的 数据 库 或者 是在 Use 语句中指 定的 数据 库 例 1-5 是 Customer 基表 的 Create Table 语句 例 1-5 create Table AppDta.dbo.Customer ( CustId Int Not Null check(CustId>0), Name Char(30) Not Null check(Name<>''''), ShipLine1 VarChar(100) Not Null Default '''', ShipLine2 VarChar(100) Not Null Default '''', ShipCity Char (30) Not Null Default '''', ShipState Char ( 2) Not Null Default '''', ShipPostalCode1 Char (10) Not Null Default '''', ShipPostalCode2 Char (10) Not Null Default '''', ShipCountry Char (30) Not Null Default '''', PhoneVoice Char (15) Not Null Default '''', PhoneFax Char (15) Not Null Default '''', Status Char ( 1) Not Null Default '''', CreditLimit Money( 1) Not Null Check ( (CreditLimit Is Null) Or (CreditLimit >=0)); EntryDateTime DateTime Not Null Default Current_Timestamp,RowTimeStamp imeStamp Not Null, Constraint CustomerPk Primary Key ( CustId), Constraint CustomerStatus Check ( ( Status<>'' '')Or (CreditLimit Is Null) ) ) Use AppDta 所有者 名称 也可 以省略 SQL Server 会用当 前 的 用户名 作 为所 有 者 完 全限 定的表名 和视图 名必 须是 唯一 的 SQL 对象名 称可 以长 达 128 个字符 包括 字母 数字和下述特殊 的 符 号 _( 下划线) #( 井号) $( 美 元符号) 和@(at 号) 如果只使 用字母(A-Z) 开 头并且 只 包 含字 母 和数 字(0-9) 对于跨 系统 或者 跨国 应用程 序 可以 避免 可能 出现 的 命名问 题 另外 Transact-SQL 有多个保留 字 例如 Create Table 和 Order 它 们 有特殊的 含义 这些保 留字 列在 Microsoft SQL Server Transact-SQL 参考手 册中 的 保 留关键字 话题中 如果希 望使 用这 些保 留字作 为 表名 列名 或者 其他 SQL 对 象名称 那么当它们出现 在 SQL 语句中 时 必须 用双 引号括 起 这些 名称 下 面这 个示例 说 明如 何编写一 个 SQL Server 语句 从表名 为 Order 的表 中检索 行 Select From Order Where CustId = 499320 引号括 起的 名称 也可 以用于 包 含特 殊字 符的 名称 如 下示例 Select From Order+Detail Where CustId = 499320 一般应 避免 使用 SQL 的保留字 或者 特殊 的字 符作 为 SQL 对 象的名 称 注意 必须 使用 SQL Server 的 quoted identifier 用 户选项才 能 使 用引号标 识符 Transact-SQL 还允 许 使 用方 括号 作为 关 键字的 分 隔符 例如 Order 这种语 法不需 要引 号标 识符 用户选项 1.2.2 在指 定文 件组 上创 建表 正 如 在本 章前 面“ 定义文件组” 一节中讨论的那样 可以定义文件组 它是一个或 者多 个存储应 用程序数据的 操作系统文件 的集合 为了把 一个新 表放在用户定 义的文 件组上 可以在 Create Table 语句 的末 端增 加一 个 On 子句 Create Table AppDta.dbo.Customer ( CustId Int Not Null Check ( CustId > 0 ), Name Char( 30 ) Not Null Check ( Name <> ‘ ’ ) ... Constraint CustomerStatus Check ( ( Status <. ‘ ’ ) Or ( CreditLimit Is Null ) ) ) On AppDtaGroup1 这个文 件组 必须 已由 Alter Database 语句创 建 注意 SQL 还有 一个 Create Schema 语句 用于将 多个 Create Table,CreateView 和Grant 语句 组合 到一 个 SQL 语句中 当处 理 Create Schema 语句 时 SQL Server 按序连 续创建对 象 以 便满 足 所 有的逻 辑依 赖 关 系(例如 视图 和外 键) 虽然 一般由 代码 生 成 器使 用 Create Schema 语句 但 是 该语 句 也可 以 用 于手工创 建 有从属 外键 的两 个或 者多个 表 1.3 表 的 列 在 Create Table 语句 中 在 新 表名之 后 可以 编码 1 到 1024 个 列定义 行的 最大长 度 是 8060 字节 包 括 内部 数据所需的 字 节 实际 中 每 行 最 大 的应 用程 序 数 据要低于 8060 字节 例如 有 10 个列 声明 为 Character 数 据类 型的表最多可以 保 存 8038 个 应 用数据字 节 每一个列定义指定列名和一种数据类型 有些数据类型有长度或者精度( 数 字总长) 另外 Decimal 和 Numeric 数据类 型有 小数(小数 点右 端的 位 数) 表 1-1 列出 了 SQL 列的数 据类型 表1- 1 SQL 列的数据类型 数据类型 描述 字符和文 字符和文 字符和文 字符和文 本 本 本 本 Char(length) 固定长度的字符串 长度从 1 到 8000 如果省略了长度 那 Character(length) 么默认值是 1 NChar(length) 固定长度的 Unicode 字符串 长度从 1 到 4000( 如果省略了 NCharacter(length) 长度 则默认长度是 1) National Char(length) National Character(length) Char Varying(max-length) 变长度的字符串 最大长度从 1 到 8000 如果省略了 max- Character Varying(max-length) length, 则默认长度是 1 VarChar(max-length) nChar Varying(max-length) 变长度的 Unicode 字符串 最大长度从 1 到 4000 如果省略 nCharater Varying(max-length) 了 max-length, 则默认值是 1 nVarChar(max-length) National Char Varying(max-length) National Character Varying(max-length) National VarChar(max-length) Text 变长度字符数据 最多达到 2 147 483 647 字节 行中 存储指向第一个数据页的指针 实际的文本是以 b- 树数据 页 存储 nText 变长度的 Unicode 字符数据 最多可达 1 073 741 823 National Text 个字符( 或者 2 147 483 646 字节 行中存储指向第一 个数据页的指针 实际的文本是以 b- 树数据页存储 Dec(precision,scale) 数值 precision 是位数 范围是 1 到 38 scale 是小数点右边的 Decimal(precision,scale) 位数 范围是从 0 到 precision 指定的数字 可用 Decimal(p) 表 Numeric(precision,scale) 示 Decimal(p,0),也可以用 Decimal 自身表示 Decimal(18,0) 然 而 Decimal 使用明确的 Precision 有助于提高文档的清晰度 续表 数据类型 描述 注意 对于 Decimal 列 SQL Server 的最大默认 precision 是 28 使 用/p 命令行开关启动 SQL Server 可以设置该值为其他值 数字 数字 数字 数字 单字节 无符号 二进制整数 范围是 0 到 255 TinyInt 两字节二进制整数 范围是-32,768 到 32,767 SmallInt 四字节二进制整数 范围是-2,147, 483,648 到 2,147, 483, 647 IntInteger 八字节二进制整数 范围是-1.8E19 到 1.8E19 BigInt 浮点数 Mantissa-size-in-bits 是尾数的位数 范围是 1 到 53 1-24 Float(mantissa-size-in-bits) 指定单精度(4 字节) 25-53 指定双精度(8 字节) 注意 可以使用 Float 本身表示 Float(53) 等价于 Float(23) Real 列有 7 位数精度 Real 等价于 Float Double Precision 货币 货币 货币 货币 四字节数字 小数点右面有四位数字 范围是-214 748.3648 到 SmallMoney 214 748.3647 八字 节数 字 小数 点右 面有 四 位数 字 范围 是从-922 337 203 Money 685 477.5808 到 922 337 203 685 477.5807 日期和时 日期和时 间 间 日期和时 日期和时 间 间 四字节日期和时间 日期范围是 1-1-1900 到 6-6-2079 时间精度是 SmallDateTime 自午夜开始的 1 分钟之内 八字节日期和时间 日期范围是 1-1-1753 到 11-31-9999 时间精 DateTime 度 3.33 毫秒之内 字节 字节 二 二 进制和 进制和 图像 图像 字节 字节 二 二 进制和 进制和 图像 图像 一位 数字 0 或者 1 Bit 固定长度二进制数据 长度从 1 到 8000 字节 如果省略 Legth, 默认 Binary(length) 值是 1 变长度二进制数据 最大长度从 1 到 8000 字节 如果省略 max- BinaryVarying(max-length) Legth 默认值是 1 Va r B i n a r y(max-length) 变长度二进制数据 最长为 2,147,483,647 字节 行中存储指向第一 Image 个数据页的指针 实际的数字以 b- 树数据页存储 系统类型 系统类型 系统类型 系统类型 等价于 nChar Varying(128) SysName 唯一标识数字 存储为 16 字节的二进制值 UniqueIdentifier 当插入或者修改行时 由 SQL Server 指定的唯一的 8 字节 TimeStamp 时间序列值 注意 名称是 TimeStamp 而无数据类型的列是 使用 TimeStamp 数据类型创建的 Cursor 允许在 存储过 程中创建 游标变 量 游 标允许一 次一行 地处理 数 Cursor 据 这个数据类型不能用作表中的列数据类型 可包含除text,ntext image, 和 timestamp 之外的其它任何数据类 型 Sql_variant 可用来 声明 T-SQL 语句的 变量 也可作 为自定 义函数 的返回 值 但 不 Table 能用来定义表的列在上述 所 有 的数据类 型 中 text ntext 和 image 是 较为特 殊的 三 个 由于 text ntext 和 image 型的数 据最 大可达 2GB 所以 在 7.0 及以前 的版 本中 用 text ntext 和 image 定 义的列 不能 在每 一行 中 直接存 储数 据 而只 能在 其 中存储 指针 由指 针指 向实际 存 储 text ntext 和 image 型数据 的页面 而在 SQL Server 2000 中 情况有 所变 化 用户 可 以用系 统 存储过 程 sp_tableoption 的 开关选 项 text in row 来决 定是 否直 接在 行中 存储 数据 当 text in row 被设 置为 off 时 同 7.0 及以 前的 版本 一 样 只能 在其 中存 储指 针 指向 实际的 页面 而不 能存 数 据 当 text in row 被设 置为 on 时 就可 以在 其中 存储 较小的 text ntext 和 image 型数据 只有当 数据 超过 了一 行所允 许 的范 围时 才存 到单 独 的页面 文件 中去 1.3. 1 TimeStamp 列 无论何 时插 入或 者修 改一行 数 据时 都会 自动 修改 用 TimeStamp 数据 类型说明的列 或 名为 TimeStamp 但没有数据 类型 的列 SQL Server 设置 TimeStamp 列为下述值 保证该 值在一 个数 据库 中是 唯一的 并且 大于 以前 指定 的值 该 值的数 据 类型与 DateTime 数据类 型不同 永远 也不 能直 接 设置 TimeStamp 列的值 一个表 只能 有一 个 TimeStamp 列 可以使 用 TimeStamp 列的值 决定 自从 上次检索 之 后 是否有 其他 进程 修改 数据行 在应 用程 序中 检查 TimeStamp 列的值 提供 了一种 有 效的 方 法 可以 用这 种方 法在 防 止 与其他进 程的 修改 冲突 时 允许 并行 浏览 和修 改表 1.3.2 Identity 列 SQL Server 允许标识表中一个整数(TinyInt SmallInt Integer Decimal(p 0) 或者 Numeric(p 0)) 列作 为该 表的 Identity 列 为此 可以 在 列的数 据类 型之 后增 加 Identity 关 键字 如下 示例: ..., RowId Integer Identity,... 当插入 一个 新行 时 SQL Server 自动为 identity 列分配一 个数 字 在默 认情 况下 对每 一个新 行 这个 数字 以 1 开始 增量 是 1 通 过 在关键 字 Identity 的后面指定 一个 或 者两 个 值 可以 设置 起始 值和 增 量值 RowId Integer Identity(10),...Start at 10,increment by 1 RowId Integer Identity(100,2),...Start at 100,increment by 2 注意 Identity 列不 允许 空(参 见 下一节)并 且不能 保 证唯一 性 除非 指定 一个 主键 约 束或者 唯一 约束 或者 为 该列创 建一 个唯 一索 引 1.3.3 行全 局唯 一标 识符 列 SQL Server 还允许在表中标记一个 UniqueIdentifier 列作为一个行全局唯一标识符 (GUID) 列 为此 可 以在列 的数 据类 型之 后增 加 RowGuidCol 关键 字 例如 下 面的例子 ... RowGuid UniqueIdentifier RowGuidCol,... 当插入 一个 新行 时 RowGuidCol 列不 能像 一个 Identity 列那样自动 修改 然而 可以 将 NewId 函数指 定为 列的默 认 值 以达到 类似 的效 果 RowGuid UniqueIdentifier RowGuidCol Not Null Default NewId() 注意 Identity 列包 含了 一个 系统 生成 的 整数 一 般 用作主 键 一个 RowGuidCol 列 一般包 含一 个 NewId 函数的 值 主 要用于复 制或 者用于 需要一个在 所有系统 的所有 表的 所 有行中 有一 个唯 一标 识符 1.3.4 Sql_variant Sql_variant 是 SQL Server 2000 的新 增数 据类 型 目前 在 ODBC 中 还没有完 全支 持这 种变量 因此 在用 Microsoft OLE DB Provider for ODBC (MSDASQL) 查询具有 Sql_variant 类型的列时 会返回二进制值 如 对包含字符串 ''PS2091'' 的列进行查询时会得到 0x505332303931 的返 回值 Sql_variant 在所有的数 据 类 型中 优 先 级最高 在进行比较或转 化时按表 1-2 的顺序进 行 Sql_variant 型的数 据进 行比 较时 按如 下规 则进 行 表1-2 优先级 数据族 sql_variant sql_variant datetime datetime smalldatetime datetime float approximate number real approximate number decimal exact number money exact number smallmoney exact number bigint exact number int exact number smallint exact number tinyint exact number bit exact number nvarchar Unicode nchar Unicode varchar Unicode char Unicode varbinary binary binary binary uniqueidentifier uniqueidentifier 1. 当进行 比 较 的 sql_variant 值属于不同基 数 据 类型 而且 基数 据 类型属于 不 同 的数据 类型族 时 在表 1-2 中 族 优先级 高的 sql_variant 值的优 先级 就高 2. 当进行 比 较 的 sql_variant 值属于不同基 数 据 类型 而且 基数 据 类型又相 同 时 在表1-2 中基数据 类型 优先 级高的 sql_variant 值的优先 级就 高 3. 当 sql_variant 变量属 于 char, varchar, nchar 或 varchar 时 它们 按照 标准 LCID 进行 比较 1.3.5 Table 型数 据与 用户 自定 义函 数 SQL Server 2000 新增 了 Table 型数据 Table 型数据 不能 用来 定义 列的 类型 只能 用作 T-SQL 变量或者 作为 自定义 函 数的 返回 值 例 1-6 是一 个简 单的 table 型 数据的 例子 例 1-6 Declare @TableVar Table (Cola int Primary Key, Colb char(3)) Insert Into @TableVar Values (1, ''abc'') Insert Into @TableVar Values (2, ''def'') Select From @TableVar Go 以上语 句定 义了 一个 名为 TableVar 有两 列的 table 型变 量 像通 常的 表一 样 table 型 数据也 有 insert select 等操 作 在 SQL Server 2000 中 table 型数 据与 用户 自定 义函 数 是密不 可分 的 SQL Server 2000 支持两 种类 型的 函数 内 置函数 和用 户定 义函 数 内 置函数 只允 许 T-SQL 语句调 用 而不 能更改 使 用 用 户定 义函数可 以根 据需要定 义自 己所 需 的 函数 用户 定义 函数 可 以带参 数 也可以 不带 参数 但只 能 返回单 值 正是 由于 这个 原 因 SQL Server 2000 增加了 table 型 数据 其值 可以 是整 型 字符 型或 数值 型 例 1-7 是 一个简 单的 用户 定义 函数 说明 了用 户定义 函数 的基 本结 构 例 1-7 Create Function CubicVolume (@CubeLength decimal(4,1), @CubeWidth decimal(4,1), @CubeHeight decimal(4,1) ) Returns decimal(12,3) As Begin Return ( @CubeLength @CubeWidth @CubeHeight ) End 在例 1-7 中用 CREATE FUNCTION 创建 了一 个函 数 CubicVolume 来计算 立方 体的 体 积 变量 CubeLength CubeWidth CubeHeight 为输 入参 数 返回 值为 数值 型 BEGIN 表 明函数 体的 开始 END 表明 函数 体的 结束 通过例 1-8 我们 就会 清楚用 户 定义 函数与 table 型数 据 是如何 有机 结合 的 例 1-8 Use pubs Go Create Function SalesByStore (@storid varchar(30)) Returns Table AsReturn (Select title, qty From sales s, titles t Where s.stor_id = @storeid and t.title_id = s.title_id) 1.3.6 空列 和非 空列 SQL 支持空 列或 者空 表达式的概 念 当某 列为 空时 意味着 实际 值是 未知 的 第 2 章 将详细 讨论 如何 使用 空值 但是 当声 明一 个表 时 可以 为 某个允许 空 的列指定 Null 关键字 或者为 某个 不允 许空 的列指 定 Not Null 关键 字 当 SQL Server 带 有 这 种功能时 它把 没有 Null 或Not Null 的列 看作 是指 定 Not Null 的列 然而 这是 向后兼容 的 ANSI 标准的 SQL 语法 在这 种 SQL 语法中 把没 有 Not Null 的列 定 义看作 指定 了 Null 的列它对就 像 在 第 二章的 设置 最大 的 ANSI SQL-92 兼容性 节一样 执行 sp dboption 存储 过 程改变服务 器 的默认 值 或改 变指 定数据 库 的默 认值以符 合 ANSI 标准 sp_dboption AppDta, `ANSI null default'', true 前面例 1-8 中的 示例 和本书 其 他示 例 都为 那些不 允 许 空的列指 定 Not Null 符合 ANSI 标准 在本 示例 中 只有 Creditlimt 列 定 义为允 许空 对于 一个 允许 空的 列 当插 入或 者 修改一 行数 据时 可以 设 置该列 为空 1.3.7 用户 定义 的数 据类 型 SQL Server 提供了 一种 非标 准的数 据类 型 称 之为 用 户定义 的数 据类 型 而且 用户 定义的 数据 类型 是内 置数据 类 型 长度(如果合 适)和 可空性的 同义 词 为了创 建一 个用 户定 义的数 据 类型 使用 sp_addtype 存储过 程 如下 示例 所示 sp_addtype TDescription,`Character Varying (50)'', `Not Null'' 本示例 创建 了 TDescription 数据 类型 作为 Character Varying(50) 数据 类型的同 义 词 该数据 类型 不允 许空 在 创建这 种数 据类 型之 后 可 以把它 用在 Create Table 列说明中 例 如 …, ProductDescription TDescription 等价于 ProductDescription Character Varying (50) Not Null,.., 用户定 义的 数据 类 型 提供了 一 种很好的 方法 可以 标 准化用 于主 键和 外 键的数 据 类型 以及标准 化经常重复的列 的定义 在每个数据库内 数据类型名 称必须是 唯一的 可遵 循 一些约 定 每个 用户 定义的 数 据类 型以 T 开头 用于 区分 数据 类型 名称 和列 名 称 如果不 再需 要某 个用 户定义 的 数据 类型 那么 可以 使 用 sp_droptype 存储过程 删除 它 sp_droptype TDescription 1.3.8 缺省 值 在 Create Table 语句 上 可以 使用 Default 子句 为某列定义一个缺省 值 当使 用没 有 列 出基 表中所有 列的 Insert 语 句插入 一行数据 时 或者 通 过 一个 没 有 包括基表 中所有列 的视 图插入 一行 数 据 时 SQL Server 在 那些不在 列清单或者 视 图中的列 中放入一个 缺省 值 也 可以在 Insert 和 Update 语句 中使 用 Default 关键 字 设置 该列 的值 为缺 省值在本章 前面 的例 1-5 中 大多数 字符 列 的 缺省值 都是 空 格 然而 Name 列 没有缺 省值 并且不 允许 为空 所以 在 Customer 表中 插入一 行新数据 时 必须为 Name 列提供 一 个 值 CreditLimit 列的缺省值 为 Null 这个 特殊 的 Default 子句 实际 上没 有必 要 因为 SQL Server 假设 Null 是允 许空 的列 的 缺省值 而不 用明 确地 使用 Default 子句 EntryDateTime 列使用 Current_TimeStamp 函数作为 缺 省值 当插 入一 个新 行时 为 EntryDateTime 列指定 关键字 Default 可以 使 SQL Server 把当前系 统的 日期 和时 间放 在该 列中 对于 定义 该列 的数 据类 型 和长度 Default 子 句的 值必须是 有效 的( 如果该列 允许 空 那么 Null 关键 字也 是有 效 的) 就像 前面 提到 的 如 果该列 允许 为空 并且 没有指 定 Default 子句 SQL Server 就用 Null 就用 Null 作为 其缺 省值 如果 该列 不允 许为 空值 且没有 指定 Default 子句 那么 除非 为该 列明 确地 指定 一个 值 否则 不能 插入 一新 行 不能 为有 TimeSamp 数据类 型或 者 Identity 属性的列指 定 Default 子句 除 了 常量 值外 还可 以指定一 个常 量表 达式 这个 表达式 可 以包括 列在 表 1-3 中的任 何 niladic 函数 或者 列在 第 2 章表 1-2 中 的 其他标 量函数 注意 也可 以使 用 Create Default 语句 创建 一个 有 名称的 缺省 值 为 了把 这个有 名称 的缺 省值与某 个列 或者用 户定义 的数据类 型相 关联 可以使 用 sp_bindefault 系统存储过程 虽然有名称的缺省值提供 了一种重新使用缺省定义的 方式 但 是这种 方法 不是ANSI 标准 建议 有名 称的 缺省值仅用于用户定 义的 数据 类型 表1-3 允许用于 Create Table 的 Default 子句的 Niladic 函数 Niladic 函数 所使用的值 User 执行 SQL Insert 或者 Update 语句的用户的数据库用户名 Current_User Session_User Current_Timestamp SQL 语句执行时的日期和时间( 返回与 GetDate 函数相同的值) System-User 执行 SQL 语句的用户的登录帐号 ID 1.3.9 计算 的列 除了保存应用 程序数据的列 外 还可以在 表中定义计算的列 虽然计 算的列有一个列 名 但是 其定义的其余 部分只是使用 同一个表中的其 他列的 表达式 并且 可能是 常量或者 系统函 数 下 面的 示例 说 明如 何 通过连接 三个 其他 列 来创建 FullName 计算的列 Cerate Table AppDta.dbo.Employee ( EmplId Int Not Null Check ( EmplId > 0 ), LastName Char ( 30 )Not Null, FirstName Char ( 30 )Not Null, MdlInl Char ( 1 )Not Null, ..., FullName As FirstName + MdlInl + LastName, ... 不能直 接插 入或 者修 改计 算 的 列 相反 当从 表中 检 索一行 数据 时 SQL Server 计算 2 这个值 在 计算 列 的 表达式中 不能 引用 其他 表中 的 列或者 使用 子查 询 子查询 在第 章 论述 也不 能在 主键 唯一 键或 者外 键或 者另 一个列 的 Default 子句 中 使用 计算的 列 SQL 视图 将 在本章后面的 创建视图 一节中讨论 提 供了一个能计 算列的超集 并且在 数 据 的 物理 存储( 基表) 和 数 据的逻 辑 表 示(视图) 之 间提供 了 一个有价值的区分 在大多数情 况 下 视图 是实 现计 算列 的 首选 方式 1.3. 10 增加 删除 和修改 表 列 可以使 用 Alter Table 语 句 在一个 已 有 的表中增 加一 个 新列 下面 的示 例说 明如 何 在本 章前面 例 1-5 中创 建的 Customer 表中增加 一个 新列 Alter Table AppDta.dbo.Customer Add Discount Decimal ( 5,3 ) Default 0 Check ( Discount Between 0 And 100 ) Alter Table 语句为修改数据 库表提供 了灵活性 不 必 手 工删 除和重新 创建表 然而 注意所 有的 新列 要么 允许空 要么 有一 个 Default 子句 以便 SQL Server 可以为已 有 行初 始化该 列 否则 新 列的规 则等 同于 在 前面“创建表” 一节 中 Create Table 语句 的规则 Alter Table 还允许改变已 有 列的数据类型 大小和可 空 性 下面的语句设置 Discount 列的数 据类 型和 大小 及不允 许 空 Alter Table AppDta.dbo.Customer Alter Column Discount Decimal ( 7,5 ) Not Null 一般不 能修 改 Text nText Image 或者 TimeStamp 列 不能 修改 计算 可复 制列 不能 修改计算 的列 约束 缺省值 或者索引中引用的列 这种规 则的例外是可 增加在 索 引中使 用的变 长度 列 的 长度 并且可 以改 变在唯一 性约 束或 者 检 查约 束 中使用的变长 度 列的长 度 另外 不能 修改 有 RowGuidCol 属性 的列 的类 型 大小 或者 可空 性 可以为 UniqueIdentifier 列增加 或者 删除 RowGuidCol 属性 这与 Alter Table 语 句 的 形式稍 微有些不 同 Alter Table AppDta.dbo.Customer Alter Column RowId Add RowGuidCol 使用 Alter Table 语 句还 可 以从表 中删 除一 个已 有的 列 Alter Table AppDta.dbo.Customer Drop Column Discount 在可以删除一 列之前 必须删除任何引用 该列的约束 缺省 表 达式 计算列表达式或 者索引 不能 删除 复制 的 列 注意 Alter Table 语 句 还 允许激活 和禁 止数 据库 触 发器 这在第 2 章讨 论 1.4 表 的 约 束 当创建 一个 表时 可以 有 选择地 指定 四种 类型 的约 束 主键 唯一性 外键 检查 注意 通过增加相应的子句作为 列定义的一部 分或者在最后的列定义之后 Transact- SQL 允许指 定缺 省值 和约束 本书的 示例 涉及 到下 面 一些约 定 Default 子句 编码 为列 定义 的一 部分 涉及到 一个 列的 检查 约束编 码为 该 列定 义的 一部 分 其他约 束在 最后 一个 列的定 义 之后进 行 编码 按照 下 列顺序 主键约 束 唯一性 约束 外键约 束 检查约 束 1.4.1 非空 约束 非空约 束表 明本 列不 允许为 空 1.4.2 键约 束 主键 唯一性 和外键约束保护确认行或者 创建行之间关系的数据完整 性 当插入或者 修改表 中的 行 时 SQL Server 强制 这些 约束 并且在有外键 约束 的 情况 下 当 修 改或者 删 除被引 用表 中的 行时 SQL Server 强制外键 约束 最好 用 Constraint 关键 字再加上 一 个约 束名称 开始 每一 个约 束子句 Constraint CustomerPk Primary Key ( CustId ) Constraint 关键 字和 名称 是可 选的 但是 最好 使用 他 们 约束 是不 要求 的 尽管设计时 大多数 基表 都 有 一个 主键用作 表中 行的 唯 一 标识 符 例如 在例 1-8 中 CustId 列是用于 标识客 户的 主键 在 一个数 据库中 约束 名称 必须 是 唯一的 主键约 束的 基本 形式 如下: Constraint constraint-name Primary Key Clustered NonClustered (column-name ..) On filegroup 主键最 多可 以有 16 个列 每一 个主 键列 的定 义不 允 许空 对 于一 个有 主键约束的表 SQL Server 禁止插 入 或 者修 改一行 以 免在 同一个表中 两 行的主键 列有 相同的值 一个 表 的定义 只能 有一 个主 键约束 唯一性约束类 似于主键约束 然而 列在 唯一键中的列可以为空 一 个唯一性键最多 可以有 16 个列 并 且一 个表 最多可有 250 个 唯 一性约 束 唯一 性约 束的 形式 也 类似 主键 约 束的形 式 Constraint contraint-name Unique Clustered NonClustered ( column-name,...) On filegroup 对于每 一个 主键 和唯一性约 束 SQL Server 创 建一个内 部 索引 如 果为 任一约束指定 NonClustered 那么 SQL Server 为该约束创建一个非聚簇索引 如果为任何约束指定 Clustered 那么 SQL Server 为这个 约束 创建 一个聚簇索 引 为所 有其 他约 束创 建 非聚簇 索 引( 每 一 个 表 中 只 能有一个 聚 簇索引) 如果对任何 其 他 约 束 没 有 指定 Clustered 那么 SQL Server 为没有 指定 NonClustered 的主键 约束 创建 一个 聚簇 索引 对于 唯一 性约 束 除非 指 定 Clustered 否则 SQL Server 创建一个 非聚簇索 引 使用 On 子句 可以把 主键 或者 外键 索引放 在一 个指 定的 文件组 上 这些 内容 在本 章后 面 其 他索引选项 一 节中讨 论外键是 一个 表(从属 表) 的一组 列 该表 的列 值匹 配另 一个 通 常与此 不同 的表 即父 表( 或 引用表)中 的主键值 或者唯一性键值 外键约束指定 外键的列( 在 同一个 表中 用 作 约束)和父 表中主 键或 者唯一性 键的列 SQL Server 要求父 表和从 属表在 同 一 个数据库 中 并且 每 一 个外键 列必 须与 相应 的主键 或 者唯 一键 列有相 同 的数据 类 型和 大小 参考一 下包 含有 关销 售信息 的 Sale 表 该表 包括 一个 ClustId 列 它包 含放 置订单的 客户 ID 可以 用下 面的 约束 创建 Sale 表 Create Table AppDta.dbo.Sale ( OrderId Int Not Null Check ( OrderId > 0 ), CustId Int Not Null, ..., Constraint SalePk Primary Key ( OrderId ), Constraint SaleCustFk Foreign Key ( CustId ) References dbo.Customer ( CustId ), ... ) SaleCustFk 外键约 束指 定 Sale 表中的 CustId 列是引用 Customer 表中 CustId 主键的外 键 使用 这种 约束 SQL Server 不允许应用 程序 在 Sale 表中 插入新行 除非该 行的 CustId 列包含 了 Customer 表中已有 的 CustId 值 在 7.0 以前的版 本中 这种 约 束也禁止 将 Sale 表 中一行 的 CustId 列修改 为在 Customer 表 的任 何行 中 都 不存在的 非空 值 换句话 说 新的 或 者修改 的 Sale 行必 须有 Customer 中的父行 外键 约 束也防 止父 表删 除或 者修 改 将会在 从 属表中 造成 的 孤行 (没 有匹配 的外 键值) 在 SQL Server 2000 中 用户有 了 更灵活 的选 择 在 SQL Server 2000 中 新增了 On Update,On Delete 两个子句 带有 NO ACTION,CASCADE 两个参 数 ON DELETE 决 定了 当你 试图 删 除有外 键约 束的 行时 系统 所采 取的 措施 参数 NO ACTION 表明 删 除操作 失败 并返 回出 错信 息 参数 CASCADE 表明 带有 外键 约束 的所 有行 在指 向的 行被 删除 时 都 会被 随之删 除 ON UPDATE 及其 参数 的 用法同 ON DELETE 注意 如果 外键 中的 任何列 都 允许 空 那么 SQL Server 允许在属表中 的一行有 一个 外 键 该外 键有 一个 或者 多 个为空 的外 键列 总之 外键 列 不应允许为空 1.4.3 检查 约束 另一种约 束类型是检查 约束 它 指定在 一行成 功地插 入或者删除之前 必 须 满足的 条 件 列级的检 查约束测 试 单个列 的值 并 且编码 为列定义的一部 分 在本 章前面 的 例 1-8 中 CustId Name 和 CreditLimit 列有列级的 检查 约束 Name 列 的检查 约束 要 求一个 新 值 或者修 改值 不是 空格 也 不是空 值 如下 ... Name Char( 30 ) Not Null Check ( Name <> ; ‘ ’) ... 每一个 列只 能有 一个 列级约 束 但是 允许 复合 条件 如在 CreditLimit 列中的约束 ... CreditLimit Money Default Null Check ( ( CreditLimit Is Null ) Or ( CreditLimit >= 0 )), ... 表级的检查约束在列定义之后 并且 就像 键约 束一 样 以 Constraint 关键字和约束名 称开始 然后 在 Check 关键 字之 后附 加用 括号 括住 的逻 辑表 达式 Constraint CustomerStatus Check ( ( Status <> ‘ ’ ) Or ( CreditLimit Is Null ) ) ) 这种约束实现 下述策略 当 某客户被分配 一个信贷限额 甚至 可以是 0 时 他必须有 一个非 空格 状态 可以 有 多个表 级检 查约 束 它为 SQL Server 强制 数 据完整 性提 供了 一种 强大的 工具 注意 还可 以使 用 Create Rule 语句 创建 一种 规则 它类似于检 查 约 束 为 了把一个 规则与 一个 列或 者一 个用户 定 义的 数据 类型 相关 联 可以使 用 sp_bindrule 系 统存储过程 虽然这种规 则提供 了一种重复使 用规 则定义的方式 但是它不是 标准的ANSI 方法 另外 规则 只能 引用 一个 列 而表 级 约束 可以引用多个 列 如果使用用户 定义的数据 类型 那么可以考虑 在合 适时把 一个规则绑定到数据 类型上 为该 数据 类型 进 一步定 义有 效值 合并 使用 SQL Server 的 用户定义 数 据类型 和规 则 可以 获得更 接 近于 ANSI 标准 域的 功 能 而 这 种功能 在 SQL Server 中不能 使用 1.4.4 唯一 性约 束 最后一 种约 束是 唯一 性约束 具有 唯一 性约 束的 列在不 同 行的 取值 必须 为的 不同值 空 值例外 主键 约束 也可 用作 唯一 性约 束 但取 值不能 为 空 1.4.5 禁止 用于 复制 的约 束 可以为 外键 或者 检查 约束增 加 关键 字 Not For Replication 就像 下面 的代 码语 句 以便当 复制进 程修 改行 时 SQL Server 不强制这种 约束 这 种约束 仍可 以强 制用 户修 改 Constraint CustomerStatus Check Not For Replication ( ( Status <> ‘ ’ ) Or ( CreditLimit Is Null ) ) 1.4.6 增加 删除 和禁 止约 束 在创建 一个 表之 后 需 要 增加或 者删 除约 束 Alter Table 语 句提供了 这种 功能 Alter Table AppDta.dbo.Sale Drop Constraint SaleCustFk Alter Table AppDta.dbo.Sale Add Constraint SaleCustFk Foreign Key ( CustId ) References dbo.Customer ( CustId ) 第一个 示 例 删除表中 外键 约 束 SaleCustId 第二个示例 增 加 一个 外 键 约束 使用 Alter 语句指 定约 束的 规则 与使用 Create Table 语 句的 规则一 样 如果不 想在已有 的数 据 上强 制 新的外键 或者 检 查 约束 那么可 以在 表 名 后面 增加 With NoCheck 子句 Alter Table AppDta.dbo.Sale With NoCheck Add Constraint SaleCustFk Foreign Key ( CustId ) References dbo.Customer ( CustId ) 还可以 临时 禁止 某个 约束 例如 插入 一行 不满 足约束的数 据 Alter Table AppDta.dbo.Sale NoCheck Constraint SaleCustFk 当准备 再次 强制 约束 时 使用如 下的 语句 Alter Table AppDta.dbo.Sale Check Constraint SaleCustFk 当禁止或者允许约束时 可以指定 All 关键字代替约束名称 在默认情况下 当允许一 个约束时 With NoCheck 选项发生作用 它在约束禁止保留在数据库中时允许插入或修改任 一行 为了检查数据库中的全部行是否仍然满足一个允许的约束 增加一个 With Check 子句 Alter Table AppDta.dbo.Sale With Check Check Constraint SaleCustFk 注意 当增 加或 者允 许约束 时 可以 临时 禁止 约束 或 使用 With Nocheck 选项 这两 个 约束环境 都允许有不满 足完整性规则 的数据 但 可能 导出这 种问 题 应 用 程序希 望所有 数 据满足 该表 的约 束 1.5 创 建 视 图 在 SQL 术语中 视 图是 一 种类表 对象 是某 人或 者应用 程 序执 行数 据库 查询时所显 示 的对象 然而 视图不 包含任何数据 相反 视图定 义在一 个或者多个基 表上 或者其 他 视图上 并且 提供 了一 种 访问 基 表中数据 的方 法 可 以使用 SQL 视 图做下 面的 工作 选择基 表中 行的 子集 只包括 一个 基表 列的 子集 基于一 个或 者多 个基 表列 导出 新的 视图 列 将 多 个基 表中 的相 关行 连 接 到视图中 的一 行 上 使用联 合运 算 合并 来自多 个 表中 的行集 视图提供 了一种简 化和限 制访问数据的方 法 例如 为了提 供一个只包含信 贷限额 至 少为$5,000 的客 户的 视图 可以 执行 下面 的语 句 Create View dbo.CustHighCredit As Select From AppDta.dbo.Customer Where CreditLimit >= 5000 当执行 这个 Create View 语句 时 SQL Server 在 数 据 库的系 统表 中创 建该 视图 的 定义 SQL 一旦创 建了 该视 图 就可以像在其 他 语句中 使用表 一 样使 用该视图 例如 可以 执行 下面的 Update 语句来 修改所 选 行的 Status 列 Update CustHightCredit Set Status = `B'' Where ShipCity = `Seattle'' And Status = `X''注意这 个 Update 在 该视 图 上 的工作 方式的 一些 重要 信 息 只有 那些 满足 全部 三个 条件 的行 CreditLimit >= 5000,ShipCity = `Seattle'' 和 Status = `X'' 才可以使其状态设置为 B 当 处理 Update 语句 时 SQL Server 只访问那 些满 足 CustHighCredit 视图 的选 择标准 的 数据 行 对于这 些行 SQL Server 接着应用 Update 语句的 选 择 标准来 确定 修改 哪些 行 正如 本例 所 示 可 以把 视图 看成 一个只 包 含那 些符 合指 定标 准的行的表 Create View 语句 是 SQL 中最复 杂的 DDL 语句 学 习 该语句 的最 好方 法是 一次 只看 一 部分 在 Create View 关键 字 的后面 应 提供视 图名 称 最 好使 用包 括视 图所 有者的 限定名称 在 Create View 语句 中 不能 使用 数据 库名 称限 定视图 名 称 因为 视图 总是 创建在 当前 的数 据库中 然而 在视图 定义中使用的 表名或者视图名 可以限 定 这样就允 许在其 他数据 库 的表和视 图上创建视图 在同一个数 据库中 对于 同 一个所 有者 视图名 称不能 与任何 的 基表名 称或 者视 图名 称相同 注意 如果 没有 限定 视图名 称 那么 SQL Server 使 用当前的用 户 Create View 语句 的下 一部 分是 视图 中 多达 1024 个列 名 的可选列 表 如 果 没 有指定 名 称列表 就像 前面 的示 例 那样 视图的结果表中的列名就 由在 As 子 句 后指定的 选择表 达 式定义 选择 表达 式与 在第 2 章中论述 的 Select 语句 的 结构类 似 选择 表达 式的 结 果 表确定 一个或者 多个基表或者 视图中 的哪 些列和行 包含在将 要定义 的视图 中 记住 结 果表是 一 个 SQL 概念 但不 必是 一 个存储 在磁 盘或 者内 存中 的 实际表 在下 面的 示例 中 选择 表达 式定义 了一 个结 果表 该 结果表 中有 Customer 基表中的 全 部列 和信贷限额至少是$5000 的 那些行 Select From AppDta.dbo.Customer Where CreditLimit >= 5000 注意 Select 子句 后面 的星 号()表示全 部列 当 创 建视图 时 SQL Server 在创建 视 图 时 在结 果表 中生 成列 清 单 如果以后 将这些列 增加到基表 中 那么 新列 就 不 能包含 在以 表示 列清 单 的视图中 直到 该视 图被 删 除和重 新创 建为 止 1.5. 1 定义 视图 内容 选择表 达式 可能 是掌 握 SQL 语法中 最重 要 也 常常 是 最高挑 战性 的方 面 选 择表达 式指定一 个由 From 子 句中列出的基表和视图中导 出的结果表 并且 用于视图定义 SQL Select Update Delete Insert 和 SQL 游标声 明中 在有 关 SQL 的 DML(数 据操纵语 言) 的 下一章 中 我 们将 深入 研 究选择 表达 式和 SQL 相关部分 的 结构 现在 我们 只是 看几 个选 择表达 式的 简单 部分 以 了解 视 图 工作的基本 方式 1.5.2 选择 表达 式 选择表 达式 总是 以 Select 关键 字开 始 对于 最简 单的选 择 表达 式 在 Select 关键字 的 后面 有列 名清 单 或用暗示所有列的清单 以及 指 定一个 基表 或者 视图 的 From 子句 下面的 Create View 语句 使用 一个 选择表 达 式 该表 达式 用 Customer 表中 的全 部行 定义 一 个结果 表 但是 只包 含一部 分 列: Create View dbo.CustShip As Select CustId, ShipLine1, ShipLine2, ShipCity, ShipState, ShipPostalCodel, ShipPostalCode2, ShipCountry From AppDta.dbo.Customer 引用 CustShip 视图 的 SQL 语句可 以把 它看 成一 个表 该表 由列 出的 列和 Customer 表 中的全 部行 组成 From 子句可以 列出 视图 和表 Create View dbo.CustHighCreditSeattle As Select From AppDta.dbo.CustHighCredit Where ShipCity = `Seattle'' SQL Server 合并(AND)Where 子句 在一个视图上定义另一个视图 前面给出了 CustHighCreditSeattle 视图 的定 义只 包含 Customer 表中满 足 CreditLimit>=5000 且 ShipCity = `Seattle'' 的行 1.5.3 合并 多个 表 From 子句 中也可 以列 出多个基表和视图 例如 下 面的语 句定 义一个连接 Customer 和 Sale 表中 相关 行的 视图 Create View dbo.Custsale As Select Customer.CustId Customer.Name, Sale.OrderId, Sale.SaleDate, Sale.SaleTotal From AppDta.dbo.Customer, AppDta.dbo.Sale Where Customer.CustId = Sale.CustId 注意 在From 子句 中 表和 视图最 多 可有 256 个 当在 From 子句中 指定 多个表 或 者视 图时 理论 上 SQL Server 生成 了一 个中 介结果 表 它包 含了列在表和 视图中的全部 行的全部组合 每一行 都 有 所 有表的 所有 列 从这 个 完整的 组合 集中 只有 那 些满足 在 Where 子句中 指定 条件 的行 才能 包括 在选 择 表达式 的结 果表中 而且 只有 列 在 Select 关 键 字 后面的列 才 包含 在结果 表中 在 这 个示例 中 只有 匹配 CustId 列值的 列值 才包 含在 结果 表中 最终 结果 表对 每一 个销 售( 有一 个匹 配的 客户) 包含一 行 而每一行 都 有销 售 数 据 和客户数 据 因为 在 Select 关 键 字的后 面 指 定 了明确 的 列清单 所以 结果 表中 只 有五个 列 注意 SQL Server 常常 使用 非常有效 的方法 来实 际实现引用 多个 表 的选 择表 达式 这 里 所 描述 的内 容是 多表 选 择表达式是 如何 工作 的逻 辑 定义 作为一 个连 接两 个表 的示例 见表 1-4(客户 样本 数据) 表 1-5(销 售样本 数据) 和表 1-6( 通过前面 的列 表中 定义 的 CustSale 视图 中看 到的 样本 数 据) 表1-4 Customer 表中的样本数据 Custld Name 10001 Ajax Plumbing 10003 Picante Software 10008 Bubba''s Grill 表1-5 Sale 表中的样本数据 Orderld SaleDate SaleTotal Custld 3678 1998-02-29 567.25 10003 3679 1998-03-06 1089.00 10001 3680 1998-03-06 376.50 10008 3681 1998-03-22 2012.90 10001 3682 1998-03-23 1233.00 10001 3683 1998-04-02 440.00 10003 表1-6 通过 CustSale 视图中看到的样本数据 Custld Name Orderld SaleDate SaleTotal 10001 Ajax Plumbing 3679 1998-03-06 1089.00 10001 Ajax Plumbing 3681 1998-03-22 2012.90 10001 Ajax Plumbing 3682 1998-03-23 1233.00 10003 Picante Software 3678 1998-02-29 567.25 10003 Picante Software 3683 1998-04-02 440.00 10008 Bubba''s Grill 3680 1998-03-06 376.50 注意 在本例 中 选择表达式中的列名是 如何使用这些列所在的表名 限定的 例如 Customer.CustId 指定在结果 表 中的 这个 列来 自 Customer 表的 CustId 列 为了 限 定一个 列名 在列名 前面 增加 一个 圆点(.) 后跟 表名 或者 是列 名 在这个 示例 中 没 有为 视 图的列 明确 指定 名称 所以 SQL Server 使用 结果表 中同 样的 未 限 定名 称 CustId Name OrderId SaleDate 和 SaleTotal 如果以 后打 算用 限定名引 用 CustSale 视图 的列 名称 那么应使 用 CustSale.CustId.CustSale Name 等等 如果在 选择 表达式 的结 果表 中有 重复的 未 限定列名 称 例如 Customer.CustId 和 Sale.CustId 都在结 果表 中 那 么 必 须为 视 图 的列 使 用 明确的和 唯 一 的列 名 这 些内容 在本章后 面 的“重 新 记录和 重 新命名 列” 一节 中描 述 选择表 达式 的 Where 子 句 是可选 的 如果 没有 指定 Where 子句 并在 From 子 句 上指定 一个表 或者 视图 那么 选 择表达 式包 括来 自基 表或 者 视图中 的所 有行 或者 如果在 From 子 句上指定多个 表和 视图 那么 选择表 达式 包括所有的行组合 Where 子 句可以指定一种 非 常复杂 的搜 索条 件 例 如 进行一 次测 试 在第 2 章 中对此深入 研究 1.6 视 图 分 类 1.6. 1 集群 视图 为适应 当今 大型 数据 库的要 求 SQL Server 2000 增加 了集 群视 图的 功能 由于 大型 Web站点和 数据 中心 的出 现 集群服 务器 这一 概念 得到 了 广泛的 应用 类似 于此 在 SQL Server 2000 中可 以把 一张原始 大数 据表分成 若干个 较 小 的成员 表 在每 一个 成员 表中 存储 着原 始 表的所有 行的一个子集 每张成员表 可以放在一个独 立的服 务器上 服务 器可 以 产生各 自 的视图 然后 用 Transact-SQL 语言的 UNION 语 句来合 并成总 视 图 就像 创建 自 一张完 整 的表一 样 如下 面的 例子 CREATE VIEW PartitionedView AS SELECT FROM MyDatabase.dbo.PartitionTable1 UNION ALL SELECT FROM Server2.MyDatabase.dbo.PartitionTable2 UNION ALL SELECT FROM Server3.MyDatabase.dbo.PartitionTable3 1.6.2 只读 视图 和可 修改 视图 选择表达式可以使用一个公共 值( 例如 在同一城市中的客户) 来 组合行 并且计算平 均值 例如 在一 个或者 多个列 上的总数 或者 平均值( 例如 一个 平 均折扣) GroupBy 和 Having 子句 指定 合计 这 些内容 在第 2 章中讨 论 目前 主要 应 了 解使用 Group By 和 Having 子句的 外部( 不是 嵌套) 选 择 表达式 的任 意视 图是 只读 视图 例 1-9 所示 的视图包含了 每 个 城 市的一行 在这个城市 中至少有一个 客户 对于该城 市中的 客户每一行有 城市名 称和平 均 的客户 折扣 率 例 1-9 Create View dbo.CustDiscountAvg ( ShipCity, DiscountAvg ) As Select ShipCity, Avg (Discount ) From AppDta.dbo.Customer Group By ShipCity 这个视 图可 以包 含下 面这些 行 ShipCity DisclountAvg Seattle 00.056 Eugene 00.009 Portland 00.012 Richmond 00.011 Loveland 00.003 Denver 00.024 注意 在这 个示 例中 视 图如何 没有 为 行指定 特殊 的 顺序 在 SQL 中 行序总是当检 索行 换言 之 在Select 语句 中 时指 定 而不 是 当定义 基表 或者 视图 时指 定 使用像这种的 视图 将不能通过视图插入 或者修改行 如果这是可能 的 例如 如果在 CustDiscountAvg 视图 上修 改 Eugene 行的 DiscountAvg 值 那么 SQL Server 可以改 变 Customer 基表的 哪些 行 如果第 一个 Select 跟着 As 关键字 不 包含至少 一个 直 接 没有 表达 式 从基表的列 中导出 的列 或者 第一 个 Select 指定 Distinct 关键 字 或者列 函数 例如 Max 那 么视图也 是只读 的 有子 查询( 嵌套的 Select 语句 在第 2 章中 讨论)的 视图也 是只 读的 不能使 用 只读视 图作 为 SQL Insert Update 或者 Delete 语句 的目 标 对于引 用视 图的 Delete Insert 和 Update 语句 还 有 附加的 限制 对于 多表 视图 不 能使用 Delete 语句 Insert 或者 Update 语句只 能 影响 一个基 表 如 果视图省 略 了没有缺省 值也不 允许 空的 基表 列 那么不 允许 执行 Insert 语句 Update 语 句不能 直接 修改 计算 列的 值 总之 改变 数据 库内容 的 最好方 法是 直 接在 基表上 执 行 Delete Insert 和 Update 操作 或者在基 于单个基表的 简单视图上执 行 如 果 使用 很 复杂的 视图 执 行修改 操作 一定要认 真测试 以便 得到 理想 的 结果 前面的 CustDiscountAvg 示例还 说明如 何明确编码视图列的名 称 在视图名 称后面 紧 跟列的 清单 如果 有 并用 括号 括住 每个 列 项 之 间用逗号分 开 视图的列名分别对应 于 选择表 达式 结果 表中 的列 在这 个 示例中 对应 关系如 下 视图的列名 结果表的列 ShipCity ShipCity DicountAvg Avg(Discount) 不能为 视图 指定 列的 数据类 型 Null 或者 Not Null 缺省值 键 约 束或者 Identity 属性 视图列 的数 据类 型和 其它属 性由 基 于结 果表 列( 或 者表达 式) 确定 SQL 包括 Convert 函数 可以把 一个 列的 数据 类型或 者 长度 转变 到另 一个 导出的 列 中 1.7 创建视 图选项 1.7. 1 With Check 选项 Create View 语句 的另 一个 选项 是 With Check Option 该选项通 过选 择行 的子 集 的可修 CreditLimit<5000 改视 图限 制 行 的插 入 和 修 改操作 例如 下面 的视 图定义不 允 许 用 创建 行的插 入或 者修 改操 作 Create View dbo.CustHighCredit As Select From AppDta.dbo.Customer Where CreditLimit >= 5000 With Check Option 这就防 止所 谓的 幻觉 修 改 在这种修 改 中 虽然可 以通过 视图插入 或 者 修改一 行 但是这 一行 不能 通过 该视图 检 索出 来 当视 图指 定 With Check Option 并 且另一 个视图 定义 在这个有 检查选项的视 图时 检查选 项限制 还应用到 从属的 视 图上 例如 为 了 在下面 的 视图中 插入 一行 该 行必须 有 CreditLimit 5000 而不 考虑 CustHighCreditSeattle 视图定 义 是否指 定 With Check Option: Create View dbo.CustHighCreditSeattle As Select From AppDta.dbo.CustHighCredit Where ShipCity = `Seattle'' 如果定 义在 另一 个视 图上的 视 图指 定 With Check Option 那 么所有低层 视图 的条 件 必 须满足 除了该条件外 还在要定义的视图上指定 而不考虑 低层 视图 的定 义是否指定 With Check Option 1.7.2 Schema Binding 选项 SQL Server 2000 的 CREATE VIEW 语句 还支 持 Schema Binding 选项 这一 选项 不允 许 视图的 引用 表进 行更 改 你可以 在创 建索 引的 任何 视 图上使 用 Schema Binding 选项 索引视 图 Indexed Views 也是 SQL Server 2000 的 新增特 性 它可 以大 大提 高 数据仓 库和决 策分 析系 统中 常用的 复 杂查 询的 性能 1.7.3 With Encryption 选项 正常情 况下 Create View 语 句的 完整文 本存 储在 syscomments 系统表中 可 以被有 访 问 syscomments 表读权限的任何用户检索 可以 为要 加密的 视图 在 Create View 语句和 syscomments 内容中 增加 一个 With Encryption 选项 Create View dbo.CustHighCredit With Encryption As Select From AppDta.dbo.Customer Where CreditLimit >= 5000 因为没 有办 法检 索加 密视图 的 源数 据 因此 如果 使用 With Encryption 选项 一 定要在 一个源 文件 中保 存原 先的数 据 1.8 创建视 图举例 既然我 们已 经研 究了 如何创 建 SQL 视图 的基 本知 识 现在多看 一 些 示例 1.8. 1 复合 条件 视图可 以使 用复 合条 件选 择数 据 行 示例 1-10 选 择 下述客户 不在 Richmond 信贷 限额在 1000 到 9999 之间 状态 是 A B 或 C 例 1-13 Create View dbo.CustSpecialCredit As Select From AppDta.dbo.Customer Where ShipCity <> `Richmond'' And CreditLimit Between 1000 And 99999 And Status In ( `A'',`B'',`C'' ) 2 SQL Between In 这个示 例使 用了 一些 在第 章中讨论 的 语法 特征 例如 和 它 可以提 高搜索 条件 的可 读性1.8.2 重新 排序 和重 新命 名列 可以重 新排 序和 重新 命名列 就像 下面 的示 例 1-11 它为运 输信 息提 供了 缩写 名 称 例 1-11 Create View dbo.CustShipAbv ( CsStrt, CsCity, CsSt, CsZip, CsCrRt, CsAttn, CsCnry, CustId ) As Select ShipLine1, ShipCity, ShipState, ShipPostalCode1, ShipPostalCode2, ShipLine2, ShipCountry, From AppDta.dbo.Customer 1.8.3 导出 列 下面的 示例 1-12 说明 如何使 用 Substring 和 concatenation(+) 操作在 SQL 视 图中导 出列 例 1-12 Create View dbo.EmpNamePhonePfx ( EmpId, PfxVoice, FullName ) As Select EmpId, SubString ( PhoneVoice,5,3 ), FirstName + MdlInl + LastName From AppDta.dbo.Employee SQL 在连接固定长度 的 字 符 列之 前 还提供了去掉尾部 空格 的方 法 下 面的表 达式 去 掉两端 的空 格 并 且在 名 称的 每 一部分之 间放 一个 空 格 LTrim( RTrim (FirstName ) ) + ‘ ’ + LTrim( RTrim ( MdlInl ) ) + ‘ ’ + LTrim( RTrim ( LastName ) ) + ‘ ’ + 注意 在这 个示 例中 LT r i m 和 RTrim 函数如何 嵌套来 去 除两 端的 空格 1.8.4 自连 接表 SQL 视图可 以自 连接 表 如 在下面的 示例 1-13 中 生成 一个 视图 其 中 每 一个雇员有 ID 一行 包括 该雇 员经 理的 和名称 例 1-13Create View dbo.EmpMgr ( EmpId, LastName, MgrEmpId, MgrLastName ) As Select Emp.EmpId, Emp LastName Emp.MgrEmpId, Mgr.LastName From AppDta.dbo.Employee Emp, AppDta.dbo.Employee Mgr Where Emp.MgrEmpId = Mgr.EmpId 在这个 视图 的选 择表 达式中 From 子句列出了 同一个 表 Employee 两次 为了 清晰地 引 用 表的 相应 角色( 也就是 说 要么 是第 一个 角色 它是整个雇员集 要么 是第二种角色 它是检 索匹 配的 经理 行) 在 From 子句中引 用的 每一个 Employee 表后面有 一个相 关 名称( 也 称为别 名) Emp 用于 第一次引 用 Mgr 用于第 二 次引用 使用唯一的 相关 名 称作为列 的 限 定名代替模糊的表名 可以在任何选择表达式中使 用相关名 称 而不 仅是在指 定连接的 选 择表达 式中 使用 对于 嵌 套的或 者 其它 很长很 复杂 的 选择表 达式 简短 的相 关名称可使 SQL 代码更 易读 1.8.5 连接 四个 表 SQL 视图可 以创 建在 最多 256 个基表 上 包括 直接 列 在视图 的 From 子句上的表 以 及基于 From 子句中的 视图的 表 作 为最 后一个 示例 考虑 在例 1-5 中 定义的 Customer 表 和在例 1-14 中定 义的 三个表 根据本 章前 面的 例 1-5 中的 Customer 表 和在例 1-14 中 定义的 三个 表 例 1-15 中的 视图提 供了 一个 连接 说 明所有 客户 定购 的全 部零 件 例1-14 Create Table AppDta.dbo.Sale (OrderId Int Not Null, SaleDate Date Not Null, SaleTotal Money Not Null, CustId Int Not Null, Constraint SalePk Primary key (OrderId) Constraint SaleCustFk Foreign key (CustId) Reference Customer (CustId)) Create Table AppDta.dbo.Part (Part Id Int Not Null, PartDesc VarChar(50) Not Null, Constraint Part Pk Primary key (PartId)) Create Table AppDta.dbo.SaleItem (OrderId Int Not Null, PartId Int Not Null,Qty Int Not Null, CustId Int Not Null, Constraint SaleItemPk Primary key (OrderId,PartId) Constraint SaleItemOrderFk Foreign key (CustId) Reference Sale (OrderId), Constraint SaleItemPartFk Foreign key (PartId) Reference Part (PartId), 例 1-15 Create View dbo.CustSalePart (CustId, Name, SaleDate, PartDesc) As Select Customer.CustId, Customer.Name, Sale.SaleDate, Part.PartDesc From AppDta.dbo.Customer, AppDta.dbo.Sale, AppDta.dbo.SaleItem, AppDta.dbo.Part Where Customer.CustId=Sale.CustId And Sale.OrderId =SaleItem.OrderId And SaleItem.PartId =Part.PartId 表 1-4(Customer) 表 1-5(Sale) 表 1-7(Part) 和表 1-8 SaleItem 显示了这 些基表 的 样 本数据 当使 用在 例 1-18 中定 义的 视图 检索 数据 时 结 果在表 1-9 中显示 表1-7 Part 的样本数据 Partld PartDesc 2654 Fax machine 3620 Stapler 4101 Desk 表1-8 SaleItem 表的样本数据 Orderld Partld Qty 3678 2654 1 3679 3620 10 3679 4101 2 3680 4101 3 3681 2654 2 3682 2654 1 3682 4101 1 3683 3620 5表1-9 通过 CustSalePart 视图看到的样本数据 Custld Name SaleDate PartDesc 10001 Ajax Plumbing 1998-03-06 Stapler 10001 Ajax Plumbing 1998-03-06 Desk 10001 Ajax Plumbing 1998-03-22 Fax machine 10001 Ajax Plumbing 1998-03-23 Fax machine 10001 Ajax Plumbing 1998-03-23 Desk 10003 Picante Software 1998-02-29 Fax machine 10003 Picante Software 1998-04-02 Stapler 10008 Bubba''s Grill 1998-03-06 Desk 可以看 出 SQL 有许多定义 视 图内 容的 方法 在第 2 章 进 一步研 究选择表 达式 时 我 们将学 习到 更多 的内 容 当使用 SQL 视图时 下面 的 提示有 助于 测试 它 们 使用查 询分 析 器输入 如下 语句 Select From CustSalePart 这个 Select 语 句显 示指 定 视图的 全部 列和 行 可以浏览 这 个结果 看 看 是否有自己希 望看到 的数 据 1.9 创 建 索 引 虽然在 Create Table 或者 Create View 语 句 中没有指 定一 个 特殊 的行 序 但是 SQL Server 确实使 用内 部索 引来 有效地 进 行行 选择 和排 序 当执行一条 DML 语句 例如 Select 语句 时 或者 当打 开一 个 SQL 游标时 SQL Server 自 动 选 择使用 的索 引 当指 定一 个主 键或 者 唯一性 约束 时 SQL Server 为基表创建 一个 内部 索引 可以 使用 SQL Create Index 语句创 建附加 的索 引 Create Index 语句 所要 求的 部分 相当 简单 写出一 个索引 名 称 然 后指 定 索 引要在其 上 创建的 一个 基表 和最 多 16 个列 索引 名称 不能 限定 因为 它 们 总是由表 的所 有 者拥 有 并 且创建 在与 表 相 同的 数据库中 在 一个表的 索引 集中 索引 名称 必须 唯一 为 了 避免混 淆 使用在 数据 库中 也是 唯一的 索 引名 称 即使 SQL Server 不要求这一 点 也应 如 此 不 能在索 引上使 用 Text nText Image Bit 或计算 机的 列 索引 中所 有列 总长度 是 900 个字节 下 面的示 例在 Customer 表的 ShipCity 和 CreditLimit 列上创建一 个索 引 Create Index CustCityCreditIx On AppDta.dbo.Customer ( ShipCity, CreditLimit ) 与视图 不同 不 能直 接用任 何 SQL DML 语句 访问 SQL 索引 在 SQL 中 索引 只 在 SQL Server 内部使 用 他 们的 功 能 通常与 性能 相关 注意 可以 有选 择地 为索引 定 义指 定 Unique 选项 它与在 Create Table 语句 上指 定 唯 一 性约 束有 同样 的效 果 应该使用唯一性 关联起 来约 束而不是 单独 的唯 一性 索引 因为 它能 把表 定义和完 整 性 规则关 联 起来1.9. 1 聚族 索引 SQL Server 允许每 一个 基表 有一个 聚簇 索引 聚 簇索 引使表 中的 数据 与索 引一 同 存储 并使行的 物理顺序与索 引的顺序保持 一致 这种结构 可以大 大提高访问行 的性能 特别 是 对于有连续键值的行集 如果以 某种 特殊 的顺 序 频繁 访 问行( 例如 按名称访问雇员) 检 索相关 的行 集(例如 指定订单 ID 的订单 项) 或 者根据 值的 范围检索 行 那 么 考虑在 合适 的列上 使用 聚簇 索引 如 果这些 列已 经列 在主 键约 束 或者唯 一性 约束 中 可以 在 Create Table 的约束 子句 中增 加 Clustered 关键 字 这些内 容在 本章 前 面 键约 束 一节 已经 讨论 过 否则 可以在 Create Index 语句 中增 加 Clustered 关键字 如下 示例 所示 Create Clustered Index EmpNameIx On AppDta.dbo.Employee ( LastName, FirstName, MdlInl ) 如果没 有指 定 Clustered 关键 字 那么 SQL Server 创建一个 非聚 簇索 引 注意 对于一 个长键使用聚簇索引 要考 虑到 空 间 性 能的平衡 当表有一个聚族索 引时 SQL Server 存储一个聚簇键值作为 定位器 用于该表的非 聚族 索引 中的内容 对于一 个很 大的表 这样可能显著增加 非聚簇索 引所需 的空 间 对 于较小 的表 影 响不会太大 对于没 有聚 簇索 引的 表 SQL Server 使 用一个 内 部 的 行标 识符 作为 存储 每 一个索引项 的定 位器 1.9.2 其他 索引 选项 在 Create Index 语句 中 SQL Server 支持几 个其 他选项 这些 选项 汇总 在表 1-10 中 表1- 10 Create Index 语句选项 语句 选项 当在一个已经有数据的表上创建新索引时 x 指定要填充的每一个叶级 FillFactor = x ( 最低层) 索引节点( 页) 的百分比(0 到 100) 默认值是 0 它等价于 100 创建内部节点时总是有足够的空间分配 至少留有一个或者两个附加项 注意 不要将 FillFactor 用于没有任何数据的表 一般情况下 只有知道 将来表的改变方案时 FillFactor 才有意义 可以与 FillFactor 一同使用 以指定应在内部 以及叶级 索引节点上以 Pad_Index 相同的有效比率分配自由空间 指定 该表 已有 的聚 簇索 引应 该删 除 和重 建 然后 重建 所有 已存 在的 非 聚 Drop_Existing 簇索引 这样可以加快一些索引的创建 禁止自动重新计算过时的索引统计信息 Statistics_NoRecompute 注意 Create Index 语句 还支 持 Ignore_Dup_Key 选项 用于 向后 兼容 对 于新索 引 避免用 该选 项 下面的 示例 说明 如何 创建一 个 非唯 一的 聚簇 索引 其填 充因子是 25%: Create Clustered Index EmpNameIx On AppDta.dbo.Employee ( LastName, MdlInl, FirstName ) With Fillfactor = 25 为了增强性能 可以在一个与包含表中数 据不同的文件组上创建一个 非聚簇索引 为 了把索引放在一个指定的文件组上 在列 清单 和其 他 选项之 后( 如 果有其他 选项) 使用第 二个 On 子句 Create Index CustCityCreditIx On AppDta.dbo.Customer ( ShipCity, CreditLimit ) On AppDtaGroup2 索引对数据库 性能有正面影响和负 面影响 简单地说 索引可以加速 数据检索 但是 由于 SQL Server 需 要 花费 一些 时间 修改 基表 上 索引列 的 内 容可能减 慢修 改 索引 列的 值 作 为一种正 常的规则 在 行最初插入表 中后需要经常修 改的列 上创建索引时 一 定 要小心 这 种索引 应该 提供 显著 的检索 优 点 来平衡 它 们对修 改 的影响 1. 10 删除数据 库 表 视图 和索引 为了删 除数 据库 对象 可 以使用 下面 一条 语句 Drop Database database-name,... Drop Table table-name,... Drop View view-name,... Drop Index table-name.index-name,... 使用这些语句 时一定要小心 当删除数据 库时 数据库中的全部对象 也都被删除了 当删除 一张 表时 引用 该 表的所 有索 引和 约束 也被 删 除 并且 当 删除 一 张 表或者 视图时 所有依赖 这张表或者视 图的视图都变 得无效 直到 用 同样的 名称 从 属视图 和引 用 的相应 列 创建另 一个 表或 者视 图为止 所有 Drop 语句 都允 许使 用以 逗号 分开 的对 象列 表 用于 Drop Table 语句 的 表名可 以 使用数 据库 名称 和所 有者名 称 限定 用于 Drop View 语 句的视图 名称 或者 用于 Drop Index 语句的 表名 可以 由所 有者名 称 限定 只能 在当 前数 据 库中删 除视 图和 索引 1.11 小 结 当创建数据库 对象时 一定要考虑命名 文档和该进程的其他方面 在开始时 稍微 费点劲 将更 易于 使用 和 管理数 据库 下 面列 出了 一 些有 助 于创建和 维护 对象 的 建议 在源文 件中 存储 创建 产品数 据 库 表 视图 和索 引的大 多 数 SQL 语句 把这 些语 句提取 到查 询分 析器 中或者 使 用 OSQL-i 命令执行 这些 语句 对于每 一个 数据 库 在 子 目录中 组织 SQL 源文件 例如\AppDta 对于每一 种 SQL 对象类 型 使 用包 含该 对 象名称 以及 表示 包括 哪些 语 句的 有 一定意义 的 源文件 名 例如 Create Database AppDta 创建数 据库 的 SQL 语句 Create Table Customer 创建表 的 SQL 语句 Create Table All 创建数 据库 中所 有表 的 SQL 语句 Create View CustHighCredit 创建视 图的 SQL 语句 Create View All 创建数 据库 中全 部视 图的 SQL 语句 Create Index CustCityCreditIx 创建索 引的 SQL 语句 Create Index All 创建数 据库 中全 部索 引的 SQL 语句 把注释 放在 SQL 源代码的开 始 描述 将要 创建 的对 象 在多行 语句 中对 齐列 名 数据类 型和 约束 以 方便阅 读 对所有 的 SQL 名称 创 建 和遵循 一个 良好 的命 名约 定 在数据库 表 视图 索 SQL (A-Z) (0-9) 引 列名 约束和其他 对象的名称中只使用字母 和数字 避免使 用 SQL 的保留字 作为 名称 例如 Order 使用用 户定 义的 数据 类型或 者 标准 的列 定 义 以提 供有 类 似功能的列的一致定 义 例如 对包 含简 短描 述文本 的 所有 列使 用 VarChar(50) 可以创 建 SQL Server 规则 和缺 省 值 并 且 把 它 们 绑定在 用 户 定 义的数据类型上 创建与由 ANSI 标准域提 供的类 似功 能 使用服 务器 连接 选项 和 sp_dboption 存储过 程来 设置 ANSI 标 准的各 种默 认行 为 在 Create Table 语句 上 为所 有列指 定 Not Null 选项 除了 那些 应该 接收 系统 空 值的列 一般 应对 唯一 性 和外键 字段 使用 Not Null 在 Create Table 语句 上 对于一 个没 有 为提 供一 个值的 Insert 操 作但 可 接收缺省 值 Default ( Create Table Default 的列增 加 子句 一 般 应 为某个列使 用 的 子句 而不 是 使用 Create Default 语句 和 sp_bindefault 系统 存储 过程) 在 Create Table 语句 上 对 任何应用到单个列 上 的 完整性 约 束编码列级的检查 约 束 一般 应使 用列 级检 查约 束 而不 是使 用 Create Rule 语句 和 sp_bindrule 系统 存储过 程 对大多数表 定义一个或者多个字段作为主键 约束 并且用 主键约束作为 确认 行 的主要 方式 在 Create Table 语 句 的 所 有 列定义之后 编码 任 何 键或者 多 列检查约束 使用 一 致的顺 序 例如 主键 之 后是唯 一性 键 再之 后是 外 键 最后 是检 查约 束 在约束 名称 中 使 用一 致 的前缀 或者 后缀 例如 Pk 表 示主键 Uk 表 示唯一性 键 Fk Ck 表示外键 表示 检查 约束 使用主 键或 者唯 一性 约束 而不 是使 用 Create Index 语句 来强 制唯 一的 键值 考虑潜 在的 修改 性能 和检索 性 能 决定 创建 哪些 索引 Transact-SQL 的数 据定 义语 言(DDL)是实 现 SQL Server 数 据库的 关键 是直 接输入 DDL 语句 还 是使用企业管 理器的图形界 面 还是通过 一个编程界面 执行 DDL 都有 助于 全 面 理解本 章研 究的这些 语句 一旦创 建了 合适的数 据库对 象 就可 以使 用在 第 2 章中研 究的 SQL 数据 操纵 语言(DML)第2 章 Transact-SQL DML 详解 本章主要介绍 4 个基本的 DML 语句 Select Insert Update 和 Delete 同时 将深入研 究 Transact-SQL 的 Select 达式 因为它是 Transact-SQL 查询和修改数据库的核心 本章还介 绍用于事务完整性的 Transact-SQL 工具 在本章的最后 将描述如何编写包含了多条 SQL 语 句的存储过程和触发器 2. 1 Select 语句 Transact-SQL 的 Select 语句 从一 个或 者多 个表 或者 视图 中检 索行 如果 使用 查询 分析 器输入 一条 Select 语句 那么结 果显 示在 结果 标签 窗 口中 如图 2-1 所示 可以 编辑 和打 印这些 结果 并且 把它 们 保存在 非数 据库 文件 中 2-1 图 查询分析器结果样本 为了使 用 Select 语句 列 出希望 在结 果中 的列 确认将 要访问 的表和视 图 并且指定 返回行 的选 择条 件 还 可 以 对行进行 组合 和排 序 Transact-SQL 将 Select 语句 上指 定的 信 息 和 系统 表中 的信 息合 并 起 来 以决定要 检索的 内容 和如何执行检索 Select 语 句的基本 结构如 下 Select select-list From table-list Where search-condition Group By Grouping-column-list Having search-condition Order By order-by-column-list 有几个 附加 的子 句扩 展了 select 语句 的 Transact-SQL 版 这些 子句 我们 也会 看到 掌握 Select 语句 这种 非常强 大 并且 形式 非常 复杂的 功能 是成功使用 Transact-SQL 的关键 许多 Select 语句 的 形式类 似于 其他 的 Transact-SQL DML 语句形式 例如 Update 并且 就像 我们 在第 1 章 中 看 到的那样 选择 表达 式 是定义 Transact-SQL 视图的中 心 也 是完整 的 select 语句 的一 部分 下面的 示例 用于 说明 Select 语句 的每 一部 分 许多 示 例使用 图 2-2 到图 2-4 所示的三 个表 Create Table Customer ( CustId Int Not Null, Name Char(30) Not Null, ShipCity Char(30) Null, DisCount Dec(5,3) Null, Constraint CustomerPk Primary Key (CustId)) 图2-2 Customer 基表样本 我们从 一个 最简 单的 select 语句 示例 开始 Select From customer Select 关键 字后 面的 星号() 表示全部 列 From 子句指定要使用的一个 或多 个基 表或 者 视图 在本 例中 是 Customer 表 在 From 子句中 可 以使用 数据 库名 称和 或所有者 名 称 来限定 表名 或者 视图 名 例如 AppDta.dbo.Customer 本 章中 的示 例使 用无 限定的 表名和视 图名称 来简 化 代 码 使之更容 易理 解 在这 个示 例中 因为 没有 进一 步限 制 要 检 索 的数据 也就是 说 没有 Where 子句 所以 检索 所有 行和 所有 列 Create Table Sale (OrderId Int Not Null, CustId Int Not Null, TotalAmt Money Not Null, SaleDate DateTime Not Null, ShipDate DateTime Null, Constraint SalePk Primary Key (OrderId), Constraint SaleCustFk Foreign Key (CustId) References Customer(CustId))图2-3 Sale 基表样本 Create Table Employee (EmpId Int Not NUll, FirstName Char(20) Null, MdlInl Char(1) Null, LastName Char(30) Not Null, MgrEmpId Int Null Constraint EmpPk Primary Key (EmpId) Constraint EmpMgrFk Foreign Key (MgrEmpId) References Employee (EmpId)) 图2-4 Employee 基表样本 注意 “” 表示该语句 准备好时已有的全部 列 当交 互 式执行 一个 Select 语句 时 同时 准备和 执行 该语 句是 很必要 的 然而 在存 储过 程或者 HLL 程序中 语句 可能 在翻译阶段准 备( 作 为程 序 创建的 一部 分) 并在以后执行 如果在该语 句准 备 好以 后 在表 或视 图中 增 加了 一个 列 那么直到该 语句重新 准备 为止 这个新 列将不 包含 在暗 示的 列清单中 2.1.1 搜索 条件 前面 的示 例检 索全 部 客 户 数据 但是 假设只想 要 Seattle 城市的客 户 怎么办呢? 通过 增加一 个 Where 子句限 制检 索的 行 可以 完成这个 任 务 Select From Customer Where ShipCity=''Seattle'' 最简单 的 Where 子句包 含了 一个 Transact-SQL 搜索条件 它是 一个 Transact-SQL 简单 条件 如上 所示 该搜 索 条件为 真的 全部 行都 被检 索 出来 当需要 使 用 Select 语句 检 索一个 指定 行时 可以使用主 键值指 定一个搜 索条件 如下 面的语 句 Select From Customer Where CustId=499320有主键 列的 Where 子句 允许 使用 Transact-SQL 进行单行 操作 对应于 HLL 内置的 I/O 操作(例如 Cobol Read 或者 Rewrite 语句) 可读取或者 修改一行 记录 Where 子句 还可 以包 含一 个搜 索条 件 该 条件 是由 两 个或者多 个 Transact-SQL 的简单 条件使 用 And 或者 Or 连 接 起来的 下面 的示 例说 明连 接( 使用 And) 两 个简单 条 件的搜 索条 件 Select From Customer Where ShipCity=''Seattle'' And Discount>0 为了对 条件 取反 可以 在 任何条 件的 开始 或者 在由 And 或者 Or 连 接的条件之前 指定 Not 逻辑 运算 符 为 了使用 Not 取反 一个 复合 条件 使用括 号括 住条 件 Not(ShipCity=''Seattle'' and Discount>0) 如果 ShipCity 不是 Seattle( 并且不 为空)或者如 果 Discount 0(并 且不为 空) 那么该条 件为真 还可以 使用 括号 指定 有 And 和 Or 的复 合条 件的 判断 顺序 如 下 面的示例 ShipCity=''Seattle'' Or Discount>0) And TotalAmt>100 SQL Server 首先判 断复 合条 件 ShipCity=`Seattle'' Or Discount>0 然后 这个 由 Or 连接 的条件 的结 果与 最后 一个条 件 TotalAmt>100 的值 取 And 如果没 有括 号 那么 Transact-SQL 首先判 断取 反表 达式 然后 是 And 连 接的条 件 最后 是 Or 连接 的 条件 因此 这个 示例 ShipCity=''Seattle'' Or Discount>0 And TotalAmt>100 等价于 ShipCity=''Seattle'' Or (Discount>0 And TotalAmt>100) 注意 这 个示 例不 等价 于 前面那 个用 括号 括住 由 Or 连接 的 条 件的示 例 2.1.2 三值 逻辑 Transact-SQL 条件是一个 逻辑表达式 对于 一个 给定 的行 可以是真 假或 者未 知 当 ANSI_Null 选项 打开 时 就像 在第 二章 的“ 设 置最大 的 ANSI SQL-92 兼容性”一 节 中推荐 的那 样 如 果 涉 及 到 比 较并且一个或者两个将比较的 值 为空 那么 该条 件的 结果未知 ( 回 忆一下 在第 1 章中 讨论 Creat Table 语句 时 允许 空的 列——使用 明 确 的或 者暗示 的 Null 属 性定义的列——可以 设置为 空 而不 是包含 一 个 有 效值 ) 例如 如果 ShipCity 是空的 条件 ShipCity= ''Seattle'' 的值未知 一般地 按照 ANSI 标准行为 对于任何比较 expression =expression 1 2 如果 expression 为空 或者 expression 为空 或者两 者都 为空 那么其结 果 未知 这种 规 1 2 则应用 到的 比较 运算 符是不 等 于 大于 或者 任何 其他可 能 性 注意 当 ANSI_Null 选项 关闭 时(不建议 如此) SQL Server 将空值作 为一 个实 际值 比较 null=null 判断 为真 而比较 null=non-null 判断为假 为了用 Not 取反 条件 或者用 And 和 Or 合并 条件 Transact-SQL 使用三 值逻 辑( 参见表 2-1) 而不是常 规的 二值 逻辑表2-1 Transact-SQL 的三值逻辑 p Q not p P and q p or q 真真假真真 真假假假真 真 未知 假 未知 真 假真真假真 假假真假假 假未知真假未知 未 知真未 知未 知真 未知 假 未知 假 未知 未知 未知 未知 未知 未知 这种 不 常 使 用 的 逻 辑 形 式对于处理其值可能是未知的 条件 是 很 必 要 的 正如 表 2-1 所 示 当用 And 连 接两 个条件 时(表中 的 p and q) 如 果 有一个 条件 的值 未知 那 么 这种连 接 也是未 知的 select 语句的结果 表只包含那些 Where 子句 搜 索条 件 为真的 行 如果搜 索条件为假或 者未知 那么 该行 就被 忽 略了 所以 如果 某行 有一个 空 的 ShipCity 列并且其 Discount 列 是 0.05 那么 这个 搜索 条 件 Where ShipCity=''Seattle'' And Discount>0 判断为 未知 并 且不 选择该行 注意 有一 个空 ShipCitp 列 的行也将 用 下 面的搜 索条 件忽略 Where Not (ShipCity=''Seattle'' And Discount>0) 2.1.3 从视 图中 检索 数据 Select 语句可以在 From 子句中使用视图以及基表检索数据( 回忆一下 视图是 Transact-SQL 的对象 定义 了一 种从 一个 或者 多个 基表 中访 问数 据的 方式) 当在 Select 语 句中指 定视 图时 SQL Server 合并视图 的定 义和 Select 语 句的定 义来 生成 结果 表 例如 考虑下 面由 Create View 语句 定义 的视 图 Create View CustSeattle As Select From Customer Where ShipCity=''Seattle'' CustSeattle 视图 使用 Select 语 句 的一种形 式( 称 为选 择表达 式) 来指 定该 视图 只包含 Cus- tomer 基表中满足 指定 条件 ShipCity=`Seattle'' 的行 可以 在一 个 DML Select 语 句 中使用该 视图 该 语句 进一 步限 制 返回的 行是 得到 折扣 的客 户 Select From CustSeattle Where Discount>0 该 Select 语句 的结 果表 只 包含 Customer 表中得 到 折扣的 Seattle 客户记录 一般当视图 Select Where SQL ( And 和 语句 都包 含 子句时 测 试 这 两个搜索 条件 的连 接 有效 地使用 运 算符)2.1.4 指定 要检 索的 列 一般情 况 下 希 望 包 含在结果 表中 的 列清 单 在 Select 关 键 字之后 就像 以前提到的那 样 当在 Select 关键字后面 指定 时 就表示全 部列 都 包含在结 果 表 中 不指 定 可以 列 出想要 的列 并用 逗号 分 开 Select CustId, Name From Customer Where ShipCity=''Seattle'' 2.1.5 剔除 重复 的行 当选择 的 列 表不 包 含 主键列时 结 果 表 可能会包 含重 复 的行 例如 下面的 Select 语 句为每 一个 客户 返回 一行 Select ShipCity From Customer 但是 因为 结果 表中 只有 ShipCity 列 那 么可 能有 多 个行有 相同 的城 市值 为了从 结 果表中 剔除 重复 的行 可以 在 Select 关键 字之 后使 用 Distinct 关键 字 如下 面的语 句 Select Distinct ShipCity From Customer 这 将 返回 至少 有一 个客 户 的 城市列表(没有 重复) 2.1.6 常量 函数 和表 达式 在 Select 关 键字 后 面 的选项 列 表 中 也可 以包含常 量 函 数和使 用列名 常量和函数 的 表达式 注意 Transact-SQL 还允许使用 IdentityCol 关键字 替代 有 Identity 属性的列的 列名 或 者使用 RowGuidCol 关键 字替代有 RowGuidCol 属 性 的列的 列名 例如 这个 Select 语句 Select Name, ''has a discount of'', Discount 100, From Customer 使用常 量和 算术 表达 式检索出如下所示的 结果 表 Smith Mfg. has a discount of 5.000 % Bolt Co. has a discount of 2.000.% Ajax Inc. has a discount of Null % Adapto has a discount of .000 % Bell Bldg. has a discount of 10.000 % Floradel has a discount of .000 % Alpine Inc. has a discount of 1.000 % Telez Co. has a discount of .000 % Nautilus has a discount of 5.000 % Udapto Mfg. has a discount of .000 % Seaworthy has a discount of 1.000 % AA Products has a discount of 1.000 %Wood Bros. has a discount of 1.000 % 2.2 Select 的子句 2.2.1 Group By 子句 可以使 用 Group By 子 句 将 合计函 数应 用 到所选 行的 子组 中 例如 下 面的语句 Select Shipcity, Count() As"Customer Count", Avg(Discount) As"Avg.Discount" From Customer Group By ShipCity 为在不 同城 市中 的每 一组客 户 返回 一行 如下 所示 ShipCity Customer Count Avg.Discount Albany 3 005000 Eugene 3 043333 Portland 4 027500 Seattle 3 03333 在这个 示例 中 ShipCity 是组合列 它把 Customer 表中的 行分 成组 一组 对应 每一 个 不同的 ShipCity 值 合计函 数 Count 和 Avg 应用到 每 一个组 并且 在结 果表中为每一 个 组 生成一 行 对于 任何 允许空 的 组合 列 认 为所 有的 空 值都在 同一 组中 注意 在这 个示 例中 有空 Discount 列的行 显然 不能 排除 允许 结果 集在每 一个 城 市 中有全 部客 户数 平均 折 扣值是对于 有非 空 Discount 列 的客户 在 Group By 子句中 可 以 列出列 名或 者标 量表 达式 下面 的 Group By 子句为每一 个 计算的 DaysToShip 和 SaleDate 列的组 合创 建一 个单 独的 组 Select SaleDate, DateDiff(Day,SaleDate,ShipDate) As DaysToShip, Avg(TotalAmt) As AvgAmt From Sale Where ShipDate Is Not Null Group By SaleDate, DateDiff (Day,SaleDate,ShipDate) 正常情 况下 在选 择列 表 和 Group By 子句中 列出 组合 的列 名( 和表 达式) 以便在最 终 的结果表 中 每一行有 该组的确认值 任何出现在选 择列表 中的其他列必 须用作 合计函 数 的参数 当使用 Where 子句 select 行时 可以 为组 合列 的某 个 特殊值 剔除 全部 行 当 某个组 没 有选择 的行 时 SQL Server 一般不为该 组在 结果 表中生 成 一行 因此 使用 图 2-2 中的数 据 如下 语句 Select ShipCity, Count() As "Customer Count" From Customer Where Discount>.01 Group By ShipCity 生成的 表中 没有 Albany 或者 Seattle 的行 ShipCity Customer Count Eugene 2 Portland 2 为了使 每一 个组 合值 有一行 即 使是那 些组中 没有 行 的组 也可在 Group By 关键字之 All 后使用 关键 字 Select ShipCity, Count() As "Customer Count" From Customer Where Discount>.01 Group By All ShipCity 这条语 句将 生成 下面 的结果 ShipCity Customer Count Albany 0 Eugene 2 Portland 2 Seattle 0 2.2.2 Having 子句 在合计函数应用 到 组 合 行之后 可以使用 Having 子 句 限 制 结 果 表中的行 Having 子 句有一 个与 Where 子句 类似 的形 式 这 种 形式在 组 合行 之前选择行 例如 可 以输入 下面 的 Select 语句 Select Shipcity, Count() As "Customer Count", Avg (Discount) As "Avg.Discount" From Customer Where Discount Is Not Null Group By ShipCity Having Avg(Discount)>.01 为平均 折扣 率大 于百 分之一 的 城市检 索 出如 下信 息 ShipCity Customer Count Avg.Discount Eugene 3 0.043333 Portland 4 0.027500 Having 子句的搜索条件可以包括组合列 例如 ShipCity 或者合计函数 例如 Avg(Discount) 注意 虽然 可以 在没 有 Group By 子句的 情况 下指 定 Having 子句 但是 很少 使用 Select 理论上 选 择表 达式 有一定 的顺 序 这部 分 语 句已论 述过 有助于 理解 何时 测 试 Where 和 Having 子句 的 搜索条 件 以及 其他子 句如 何起 作用 每一 步都从 前一步 的 中介结果表 中生 成一 个假 想结果 表 这些 步骤 如下 1) 列在 From 子句中的全 部 表 和视 图的 全部行的 全部组 合 都包 括 在 这一 步生 成的中介 结果表 中 ( 如果 在 From 子 句 中 只指 定一 个表 或者 视 图 那 么 中介 结果 表 的 列和行 与 在 指 定表或 者视 图中 的列 和行相 同 2) 如果 指定 一个 Where 子句 那么 这个 搜索 条件 应 用到由 第一 步生 成的 结果 表 中的每 一行 只 有 使 搜 索 条 件 为真的那些行才包括在由这一 步 生成 的 中 介 结 果表中 ( 如 果没有指 定 Where 子句 那么包 括第 一步 生成 的结 果表 中的 全部 行 ) 3) 如果 指定Group By 子句 那么 来自 前面 一步 生成的 结 果表 中的 行组合成 单 独的 组 使一个 组中 的所 有行 对于所 有 组合 列都 有相 同的值 (如 果没有指 定 Group By 子句 那么 就认为 所有 行在 一个 组中 ) 4) 如 果指定 Having 子句 那 么这种搜 索 条 件应 用 到 每一 个 组 只有使 搜索条件 为真 的行 的组才包 含在这一 步生成的 中 介 结果 表 中 ( 如果 没有 指 定 Having 子句 那么包 括上 一步生 成的 结果 表中 的所有 组 和行 ) 如果指 定 Having 子句 但 是 没有指 定 Group By 子句 那么 这一 步生 成的 中介 结果 表 要么是 空的 要么 包含 前 几 步生成的 所有 行 5) 如果 既没 有指 定 Group By 子句 也没 有指 定 Having 子句 那 么这 一步生 成的 中介 结果表包 括第一步和第 二步生成的结 果表中的行 每 一行包 含在 选 择列表 中 指定 的直接 的 和导出 的列 如果指 定了 Group By 子句或者 Having 子句 或者 这两 个子 句 那么 这一 步生 成的 中介 结果 表 为 在 第 一 步 到 第 四步生成的每一个行组包括一 行 ( 如果前 面的结果表是空的 那么 这 一 步生 成的 结果 表也 是 空 的 ) 每一行包含包括在选 择 列表中 的 任何组合列 以及 将选择 列表中 的合 计函 数应 用到组 的 结果 6) 如果 对选 项列 表指 定 Distinct 关键字 那 么 删 除前面 步 骤中 生成 的结 果表中的冗 余 行 否则 所 有的 行都 包 括在最 终的 结果 表中 这一系 列步 骤为 理解 选择表 达 式的 结果 提供 了一 种方式 不 必理解 SQL Server 实际上 是如何 执行 Select 或者 其 他语句 的 SQL Server 的优化器 可以 使用 索引 或者 其他技 术 来生 成等价 的结 果 2.2.3 Where 子句 和Having 子句 因为根 据组 合列 的值 来选择行时 既可以 使用 Where 子句 也可 以使 用 Having 子句 所以可 以选 择下 面一 条语句 检索 在 Portland 或者 Seattle 中 客户的 平均 折扣 率 Select ShipCity, Avg(Discount) From Customer Where Shipcity In (''Portland'', ''Seattle'') Group By ShipCity 或 Select ShipCity, Avg(Discount) From Customer Group By ShipCity Having ShipCity In(''Portland'', ''Seattle'') 使用 Where 子句 是一种 编码 这种 检索 语句 的较清 晰 方式 在有 些情 况下 把 测 试放在 Where 子句 中执 行明 显快 于把 这种 测试 条件 放在 Having 子句 中执 行 因为 SQL Server 在 组合步 骤和 计算 合计 函数值 之 前剔 除了行 2.2.4 Order By 子句 使用 Order By 子句 可以 在 Select 语句 的 结果 表 显示 或者返 回到 程序 之前 排列 顺序 Order By 子句使用 升序 或者 降序(使用 关键 字 Desc) 指定 一组 列 对于 某个 在结 果表 中无 名 称的 列(例如 由表 达式或 者函数 指定 并且 没有别名) 可以使 用一 个 相 对列 号 来 代替列 名 指定使 用无 名称 的列 对行排 列 顺序 注意 虽然 Order By 子句不 是选 择表 达式 的一 部分 但 是它只有 在 Select 语 句 中才有 效 因此 当执 行一 条交互 式 的 Select 语句 或者 在一个 Insert 语 句中时 或者 在程序 中声 明游 标时 可以使 用 Order By 子句 但是 不能 在视 图定 义中 使 用 Order By 子句 下面这 条语 句按 照客 户 ID 的顺 序检 索已 经运 输的 订 单 并且在客户 ID 内 根 据运输 订单耗 费的 天数( 最 长的 时 间间隔 排在 第一 位)排序 Select CustId, OrderId, SaleDate, ShipDate, DateDiff(Day,SaleDate,ShipDate) From Sale Where ShipDate Is Not Null Order By CustId, 5 Desc 在 这 种情 况下 从 DateDiff(Day,SaleDate,ShipDate) 表达式中 产生 的无名称的第五 列被 用来对 CustId 值相同的 行排 序 对于使这条语句可读性更高 可 在 表达式 或者 函数 后 面加上 As 关 键字和一个 列 名 为一个 导出 的列 编码 一个别 名 用这 种技 术重 写前 面 的示例 Select CustId, OrderId, SaleDate, ShipDate, DateDiff(Day,SaleDate,ShipDate) As DaysToShip From Sale Where ShipDate Is Not Null Order By CustId, DaysToShip Desc2.3 复杂的 Select 语句 上面示 例 说 明了 Select 语 句的基 本组 成部 分 在下一个 示例中 我们将 看一些比较复 杂的变 化 从选 择列 表中开 始 我们 还将 研究 一些 在 Transact-SQL 中可以使用的 附 加子 句 2.3.1 在From 子句 中指定 多 个表 From 子句中最 多可 以列 出 256 个表 和视 图 如果 在 From 子句中列出 了多 个表 或者 视 图 那么 执行 Select 语 句时就 好 像 指 定了一 个 表 该表有 指定 表 中 的全部列 和这 些 表中 所 有行的 可能 组合 即两 个 表的笛 卡尔 乘积 虽然 当 执行多 表 Select 语句 时 这样 大的 中介 表并不 总是 创建 但是 这 是考虑 所发 生事 件的 最简 单 的方式 例如 在图 2-2 和图 2-3 中 的两个 表可 以用 在下 面的 Select 语句 中 Select From Customer, Sale 结果列 见表 2-4 它并 没有什 么 用 因为 有些 行的 组合 信息 来自 不相 关的 客户 和销 售 表2-4 使用笛卡尔积生成的检索样本 客户表中的列 销售表中的列 Custld Name ShipCity Discount Orderld Custld TotalAmt SaleDate ShipDate 133568 Smith Mfg. Portland .050 234112 499320 35.00 1998-05-01 1998-05-15 133568 Smith Mfg. Portland .050 234113 888402 278.75 1998-05-01 1998-05-04 133568 Smith Mfg. Portland .050 234114 499320 78.90 1998-05-03 Null 133568 Smith Mfg. Portland .050 234115 890003 1,000.00 1998-05-04 1998-05-10 133568 Smith Mfg. Portland .050 234116 246900 678.00 1998-05-04 1998-05-08 133568 Smith Mfg. Portland .050 234117 133568 550.00 1998-05-05 1998-05-08 133568 Smith Mfg. Portland .050 234118 905011 89.50 1998-05-05 1998-05-10 133568 Smith Mfg. Portland .050 234119 499320 201.00 1998-05-05 Null 133568 Smith Mfg. Portland .050 234120 246900 399.70 1998-05-06 1998-05-08 246900 Bolt Co. Eugene .020 234112 499320 35.00 1998-05-01 1998-05-15 246900 Bolt Co. Eugene .020 234113 888402 278.75 1998-05-01 1998-05-04 246900 Bolt Co. Eugene .020 234114 499320 78.90 1998-05-03 Null 246900 Bolt Co. Eugene .020 234115 890003 1,000.00 1998-05-04 1998-05-10 246900 Bolt Co. Eugene .020 234116 246900 678.00 1998-05-04 1998-05-08 246900 Bolt Co. Eugene .020 234117 133568 550.00 1998-05-05 1998-05-08 246900 Bolt Co. Eugene .020 234118 905011 89.50 1998-05-05 1998-05-10 246900 Bolt Co. Eugene .020 234119 499320 201.00 1998-05-05 Null 246900 Bolt Co. Eugene .020 234120 246900 399.00 1998-05-06 1998-05-08 275978 Ajax Inc. Albany Null 234112 499320 35.00 1998-05-01 1998-05-15 275978 Ajax Inc. Albany Null 234113 888402 278.75 1998-05-01 1998-05-04 续表 客户表中的列 销售表中的列 Custld Name ShipCity Discount Orderld Custld TotalAmt SaleDate ShipDate 275978 Ajax Inc. Albany Null 234114 499320 78.90 1998-05-03 Null 275978 Ajax Inc. Albany Null 234115 890003 1,000.00 1998-05-04 1998-05-10 275978 Ajax Inc. Albany Null 234116 246900 678.00 1998-05-04 1998-05-08 275978 Ajax Inc. Albany Null 234117 133568 550.00 1998-05-05 1998-05-08 275978 Ajax Inc. Albany Null 234118 905011 89.50 1998-05-05 1998-05-10 275978 Ajax Inc. Albany Null 234119 499320 201.00 1998-05-05 Null 275978 Ajax Inc. Albany Null 234120 246900 399.70 1998-05-06 1998-05-08 499320 Adapto Portland .000 234112 499320 35.00 1998-05-01 1998-05-15 499320 Adapto Portland .000 234113 888402 278.75 1998-05-01 1998-05-04 ...... 890003 AA Portland .010 234119 499320 201.00 1998-05-05 Null Products 890003 AA Portland .010 234120 246900 399.70 1998-05-06 1998-05-08 Products 905011 Wood Bros. Eugene .010 234112 499320 35.00 1998-05-01 1998-05-15 905011 Wood Bros. Eugene .010 234113 888402 278.75 1998-05-01 1998-05-04 905011 Wood Bros. Eugene .010 234114 499320 78.90 1998-05-03 Null 905011 Wood Bros. Eugene .010 234115 890003 1,000.00 1998-05-04 1998-05-10 905011 Wood Bros. Eugene .010 234116 246900 678.00 1998-05-04 1998-05-08 905011 Wood Bros. Eugene .010 234117 133568 550.00 1998-05-05 1998-05-08 905011 Wood Bros. Eugene .010 234118 905011 89.50 1998-05-05 1998-05-10 905011 Wood Bros. Eugene .010 234119 499320 201.00 1998-05-05 Null 905011 Wood Bros. Eugene .010 234120 246900 399.70 1998-05-06 1998-05-08 但是如 果使 用选 择列 表并且 增 加一 个 Where 子句 那么 可以 得到 一个 很有 用的表 Select Sale.OrderId, Sale.CustId, Customer.Name From Customer, Sale Where Sale.CustId=Customer.CustId 下面所 示的 结果 表提 供了一 个 清单 包括 客户 的 ID 和 每一个销 售人 员姓 名 Orderld Custld Name 234112 499320 Adapto 234113 888402 Seaworthy 234114 499320 Adapto 234115 890003 AA Products 234116 246900 Bolt Co. 续表 Orderld Custld Name 234117 133568 Smith Mfg. 234118 905011 Wood Bros. 234119 499320 Adapto 234120 246900 Bolt Co. 这种两表 运算称作 相等连 接 这是一种最常用和最 有用的关 系型数据库运算 相等 连 接只选择 笛卡尔积中的 行 其中 同表中的相关列有 相等的 值 在这个示 例中 相等连 接 选择的 列中 销 售表 中的客 户 ID(Sale.CustId)匹配客户表中的客 户 ID(Customer.CustId) 除 了 这 种相 等连 接 Transact-SQL 允许将要 连接 的表 使用 其他 比较 运算 符 例如 大于(>) 或者 不等于(<>) 正如 本例 所 示 当多个 表中 有同 名的 列 时 可以使 用 table.column 形 式 限制列 名以避 免混 淆不 清 注意 在下 一示 例中 要说明 限定 名称 可以 是一 个关联 名 称而 不是 一个 表名 还可 以使 表自 己连 接 也 就是说 一个表可以在 From 子句中假设有好几个角色 当 在任何 其他 子句 中指 定列名 时 为了 保证 清除 所指 那 一个表 的角 色 必 须为任何列 在 From 子句后 的表 增加 一个 唯一的 关 联名 称 也称 为表的 别名 例如 输入 下 面 的语句 Select Emp.EmpId, Emp.LastName, Mgr.LastName From Employee As Emp, Employee As Mgr Where Emp.MgrEmpId=Mgr.EmpId 生成如 下所 示的 表 该表包 括 了雇 员姓 名和 他们 的经理姓名 Emp.Empld Emp.LastName Mgr.LastName 104681 Rillens Allen 227504 Waterman Herbert 898613 Allen Herbert 899001 Castor Allen 在这个 示例 中 Employee 表用于 两种 角色 一种 角色 提供 雇员 组(关 联名称 是 Emp) 另一 种角 色提 供了 查询 表 用于 寻找 与每 一个 MgrEmpId 值相对应的名称( 关联名称是 Mgr) 关联 名称 在表 名和 可选 的 As 关键字 之后 使用 一个 限定 的列 名如 Emp.LastName 可使列 值所 在的 Employee 表的角 色更 加清 楚 对于在 两个 表之 间的 简单内 连 接(不匹 配的 行从 结果 中 删除) 使用 From 和 Where 子句 的技术就很合适 对于 比 较复杂 的外 连接( 不匹 配的 行 仍然保 留在 结果 中) 或者 对于多 个 表用在 有一 个或 者多 个连接 的 查询 语句 中的 情况 可以 在 From 子句中明确 地使用 join 语 法 前面 的示 例也 可以 写 成如下 的形 式 Select Emp.EmpId, Emp.LastName, Mgr.LastName From Employee As Emp Join Employee As Mgr On Emp.MgrEmpId=Mgr.EmpId SQL Server 还有变 化的 join 语法 用于 支持 在左 外连 接 右 外连接 和全 外连 接的变化 左外连 接的 语法 如下 Select Customer.CustId, Customer.Name, Sale.SaleDate From Customer Left Outer Join Sale On Customer.CustId=Sale.CustId 该 Select 语句 的结 果如 表 2-5 所示 表2-5 使用左外连接的检索样本 CustId Name SaleDate 133568 Smith Mfg. 1998-05-05 246900 Bolt Co. 1998-05-04 246900 Bolt Co. 1998-05-06 275978 Ajax Inc. Null 499320 Adapto 1998-05-01 499320 Adapto 1998-05-03 499320 Adapto 1998-05-05 499921 Bell Bldg. Null 518980 Floradel Null 663456 Alpine Inc. Null 681065 Telez Co. Null 687309 Nautilus Null 781010 Udapto Mfg. Null 888402 Seaworthy 1998-05-01 890003 AA Products 1998-05-04 905011 Wood Bros. 1998-05-05 右外连 接的 语法 使用 Right Outer Join 关键字 并且 扩展到运算符右端 表中为空的不匹 配行 如果 使用 Full Outer Join 关键 字 那么 两个 表 中的不 匹配 行都 包括 在结 果 表中 如左 下连接 和右 外连 接所 述 注意 SQL Server 还有非标 准的连 接运 算符=( 左外 连接) 和=( 右外 连接) 用于兼容 以 ANSI join ( ) 前的版 本 这些 运算 符不等 价 于 标准的 关键 字 如 上所述 并且不 应 该用于 新的 查询 语句 2.3.2 Union 关键 字 通过指定 两个集的 联合 也可以导出左外 连接 不匹配 因此连接起来 的 行集和 第 一个表(左表)不匹配的行用空值 或任 何其 他的 值 扩 展用于 第二 个表 中的 列 例如 可 Select 以输入 下面 的 语句 检索 包含 所有 客户 和他 们 的销售 日期 如果有 的 左 外连接 Select Customer.CustId, Customer.Name, Sale.SaleDate From Customer, Sale Where Customer.CustId=Sale.CustId Union Select Customer.CustId, Customer.Name, Null As SaleDate From Customer Where Not Exists (select From Sale Where Sale.CustId=Customer.CustId) 这个示 例使 用 SQL Union 运算符 它将两 个 Select 表达 式 的结 果表合并到一 个 的结 果 表中 第一 个 Select 语 句 检索至 少有 一个 匹配 Sale 行的 Customer 行( 内连 接) 第二 个 Select 语句使 用一 个嵌 套的 Select 语句( 在“Exists 条件” 一 节 中解释)只 返回匹配的 行 重要的 是 第二个 行集 为每 一个 Customer 行包 括一 行 其中 不 存在任 何匹 配 Sale 表中 的行 完整 的 Select 语句(联合 两个 选 择 表达式) 的 结 果与表 2-5 所 示的左外 连接 相同 包含 Union 运算 符的 Select 语 句可 以指 定一 个结 果表作 为 由选择表达式定义的 两 个或 者多个 中介 结果 表的 联合 回忆 一下 选择 表达式 是 Transact-SQL 表达式的一种形式 它 以 Select 关键 字开 始 有一 个 From 子句 并且 可以有 Where Group By 或者 Having 子句 选择表 达式 本身 没有 Union 运算 符或 者 Order By 子句 完整 的 select 语句 可以 是简 单的一 个选 择表 达式 或者是 两 个或 者多 个选 择表 达式的 联 合 可以 有 Order By 子句 当 Select 语句包 含一 个 Union 运算符 时 在最 后一 个 选择 表达 式之后 指定 Order By 子句 并且 决定 在选择 表达 式中 行联 接的顺 序 为了指定两个 选择表达式的联合 这些选择表达式的结果表必须与联 合兼容 其含义 是它们必须有相同数量的列和每一双对应的列( 根据在各自的选择列表中的位置相对应) 必须有兼容的列定义( 或 者 是相同 的数 据类 型 或者允许隐含地转换) 结果表 中 的列名 是 在第一 个选 择表 达式 中使用 的 列名 或者 别名 当指定 Union 运算 符时 冗 余 行一 般从最 终的结 果表中 剔 除了 如 果在 结果 表中的所 有列都 有相 同的 值 那 么 两个行 是冗 余的 可以指 定 Union All 在结 果表 中包括 冗 余行 下面的 示例 说明 如何 用 Union All 得到 全部 雇员 和联 系人 的姓 Select LastName From Employee Union All Select LastName From Contractor 可以使用的另 一种技术是对结果行中的每 一行增加一个标记列 以显 示 该行来自哪一 个选择表达式 下面的 Select 语句检索全部雇员和联系人的姓名( 包 括姓名 相同 的人) 并 且根据 姓名 排序 Select ''Employee'' As Tag, FirstName, MdlInl, LastName From Employee Union All Select ''Contractor'' As Tag, Firstname, MdlIn1, LastName From Contractor Order By LastName, FirstName, MdlInl 结果表 的样 本如 下所 示 每一行 包含 一个 标记 表 示 该人是 雇员 还是 联系 人 Tag FirstName MdlInl LastName Employee Trish S Allen Employee Rich D Castor Contractor Bill M Dutcher Employee Dave R Herbert Contractor Cricket S Katz Contractor Tim L Murphy Employee Barb L Rillens Contractor Richard M Russle Employee Greg J Waterman 该示例 还说 明如 何编 码 Order By 子句来对 联合两 个选择表达 式 的 Select 语句 中的 行排 序 前面的 那些 示例 说明 了 Select 语句 的大 部分 内容 在 下一节 我们 将深 入研究 在 Where 和 Having 子句 中使 用的 搜索 条件 搜索 条件 是 选择 表 达式的 重要 组成 部分 它是 Transact- SQL 的“ 设置 在某 点” 功 能 的核心 必须 很好 地理解 才能有效地使用视图 Select Insert Update 和 Delete 语句 以及 Transact-SQL 游标 2.4 Select 的条件 正像我 们所 看到 的那 样 选择 表达式 的 Where 和 Having 子 句有一个搜 索 条件 包含 了一个 简单 的条 件 或者由 And 或者 Or 连接的 多个 简单 条件 回忆 一下 一个 简单 的条 件指定 一个 逻辑 表达 式 判断为 真 假或 者未 知 Transact-SQL 有好几种 条件 包括 Basic Null Between In Like Exists 和 Quantified 注意 Text NText 和 Image 数据类型 只能 用在 Like 条件 中和 允许使用 Text 和 Image 参数的 函数 例如 PatIndex 中 Text 和 Image 数据类型不 能 用在 子查 询的选择列 表中2.4.1 Basic 条件 Basic 条件 使用 下面 列出 的 比较运 算符 来比 较两 个值 运算符 含义 = 等于 <> 不等于 < 小于 <= 小于或者等于 > 大于 >= 大于或者等于 Transact-SQL 还允许下 面的等 价 运算 符 运算符 等价于 != <> !< >= !> <= 我们已 经看 到了 几个 简单的 Basic 条件 示例 其一 般的 语法 形式 是 expression expression 1 2 其中 每 一个表达式可 以是列名 文字 或者 一些 有 效的 运 算 字 符串或者其它形 式 的表达 式 是上 面列 表 中的一 个逻 辑比 较运 算符 这种条 件的 一个 示例 如下 Customer.CustId Sale.CustId Basic 条件 还可 以采 用下 面 的形式: Expression (select-expression) 其中 expression 和 与上面有同样的含义 select-expression 指定一列并且 生成只有 一行的 结果 表 下面 的 select 语 句显示 比平 均 折扣 率高 的所有客户 Select CustId, Name From Customer Where Discount>(select Avg (Discount) From Customer) 在搜索 条件 中使 用的 选择表 达 式称 作子 查询 在上 面 的示例 中 子查 询是 Select Avg(Discount) From Customer 这个特殊的子 查询生成一个标量值—— 也就 是说 只有一行和一 列的结 果表 在本 例 中 该 值 是 全部客户 的 平均 折 扣 率 然后 外部 Select 语 句 的 搜索条件 比 较 每一 个 客 户 的 折扣率 和平 均的 折扣 率 并且在 Select 语句 的结 果表中 只包括那些 Discount 列 值 大于平 均 值的 Customer 行 2.4.2 Null 条件 回忆 一 下 利用 ANSI 标 准行为 比较 两个 值 时 如 果其中一个值 或 者 两 个 值 都 是空 的 那么 该比 较值 未知 Null 条 件提 供了 一种 测试 空或 者非 空的方式 语 法如下Where ShipDate Is Null 或者 Where ShipDate Is Not Null 2.4.3 Between 条件 Transact-SQL 还有一些 复合 条件 的快 捷形 式 Between 条 件是两个 不等 值测 试的 方法 搜索条 件 Where Discount Between 0.01 And 0.02 等价于 Where Discount>=0.01 And Discount<=0.02 还可以 使用 Where Discount Not Between 0.01 And 0.02 等价于 Where discount <0.01 Or Discount >0.02 2.4.4 In 条件 为了简 化一 系列 相等 性测试 可以 使用 In 条件 例 如 搜索条 件 Where ShipCity In(''Eugene'', ''Portland'', ''Seattle'') 等价于 Where ShipCity=''Eugene'' Or ShipCity=''Portland'' Or ShipCity=''Seattle'' In 条件的另 外一 种形 式允许 使 用子 查询 定义 一组要比较 的 值 Select CustId, Name From Customer Where ShipCity In (select Distinct City From Warehouse) 在这个示例中 子查询生成一个结果表 对于有仓库的每一个城市 在结果表中有一 行 在 Customer 表中 如果 某行 的 ShipCity 列包含子 查询 结果 表中 的一个城市 那么才 选 择该 行 Select 语句的最终结果表只有那些客户与仓库在同一个城市中的客户 注意 In 条件的 子查 询必 须指 定只有 一 个列 的结 果表 在 In 条 件的 两 种 形式之前 还可 以指 定 Not 关键字 取反 这种 测试 对于那 些允 许有 多行子查询 的 条件 可以在 子查 询中使 用 Union 运算 符 下 面的示例 说明如 何编 码这 种类 型的测 试 Select CustId, Name, ShipCity From Customer Where ShipCity In(select ShipCity From Customer Where CustId=246900 Union select ShipCity From Customer Where CustId=687309 使用图 2-2 中的 数据 这条 语 句生 成下面 的结 果 Custld Name ShipCity 133568 Smith Mfg. Portland 246900 Bolt Co Eugene 499320 Adapto Portland 499921 Bell Bldg Eugene 687309 Nautilus Portland 890003 AA Products Portland 905011 Wood Bros Eugene 2.4.5 Like 条件 Like select 条件提 供字 符串 样式匹 配 一个 有搜 索条 件的 语 句如下 Select CustId, Name From Customer Where Name Like ''%Steel%'' 这条语 句将 显示 在名 称的任 何 地方 有字 符串 Steel 的客 户 包括 下面 的名 称 Ajax Consolidated Steel Portland Steel Yards Steel Fabricators of the Northwest John Steeling Grocery Company Umpqua Steelhead Fly Fishing Guides 在 Like 关键 字之 前的表达式 必须确 定一个字 符串( 例如 字符 或者 日期/时间 列 或者 字符串 函数 例如 SubString) 在 Like 关键字 之后 编码 一个 字符 串文 字 这个 字符 串提 供了将 要匹 配 的 样式 在 这个样 式中 可以 使用 百 分号(%) 表示 零个或 者多 个 字符 的 子串 (_) 下划 线 字 符表 示出 现一 个任意字 符的 子串 下面的条件测 试的名称是 有四个字 符 后 三个字 符是 ick Name Like ''_ick'' 这种样 式匹 配 Dick Rick Mick Nick 以及 dick rick mick nick kick 和 lick 这 种样式 不匹 配 ick( 太短) Ricky( 太长) 或者 rock( 没有包 含 ick) 注意 在 默认情况下 SQL Server 使用 忽略大小写不同的排序 所以样式_ick 匹配 DICK 等等 当安 装 SQL Server 时 可以 指定 这种排 列 顺序 对于 比较 运算 大写字母和小 写字母是不同的 在这种情 况下 这种样式 _ick 不匹配字符 串 DICK 还可以使用这种模式 例如 aeiou 来匹 配在 方括 号内 字 符 集 合中的 单个 字 符 或 者可 以使用一 种范围 例如 0-9 指定字符集 在本例中 是任 一 数 字 如 果把符号^ 放在某个 字符集或者范 围的开始 那 么表示匹配字符 集包含 除了在方括号 之内的 字符的 全部字符 因此 ^aeiou 表示 除了 a e i o u 之 外的任意字符 ^0-9 表示 除了数 字 的任意 字符 如 果 需要 匹配% _或者 字符 那么可以把 这 种字符 放 在方括号内 例如 下面的 样 式所匹 配的 字符 串至 少两个 字 符 并且 在第 二个 或者最 后 一个 字符 上有 一个 下划线 Name Like _%[_]% 第一个_匹配 任何 一个 字符 第一 个% 匹 配零 个或 者多个字 符 [_] 只匹 配 一 个字符 最 后一个%匹配 零个 或者 多 个任意 字符 指定% 或者 字 符的 另 一种方 法是 指定一 个退出字 符 例如 下面的样式与前 一个 示例中 的样 式等 价 Name Like _%\_% Escape \ 这个示例的 Escape 子句 指 定在 样式中 的\ 字符后面的任意字符都 将作为一个文字符对 待 2.4.6 Exists 条件 Exists 条件 是使 用子 查询 的另 一种 条件 形式 语法 是 Exists(select-expression) 如果 选择表达 式 的 结果表 中包 含 一行或者 多 行 那么 该条 件 为真 否则 为假(Exists 条 件 的 值永 远也 不会 未知) 虽然选择表达 式 的选择列 表可 以 指 定任意数量的列 但这些列 值 都被忽 略了 所 以可 以只使 用 作为选 择列表 在 Exists 条件之 前 可以 指定 Not 关键字 那么只 有当 选择 表达 式的结 果 表是 空的 该取 反条 件 的值为 真 当 且仅当 Seattle 中至少 有 一个客 户 那么 下面 的条件 为 真 Exists (Select From Customer Where ShipCity=`Seattle'') 这个示 例可 能不 太实 用 因为它 只是 说明 在 Seattle 是否 有客 户 在说 明如 何生成 左 外 连接的 示例 中 我 们将 会 看到搜 索条 件中 更有 意义 的 Exists 条件 Select Customer.CustId, Customer.Name, Null As SaleDate From Customer Where Not Exists (select From Sale Where Sale.CustId=Customer.CustId) 在这 个 搜 索 条 件 中 使 用 的选择表达 式 称 为 相 关 子 查 询 因为 内部 的 选 择 表 达式( 在 Not Exists 条件 之后)引用 Customer.CustId 它是在 外部的选择表达式中指定 的表 的 某个相 关 引 用列 因此 内部 选择 表 达式的 判断 与外 部 选择 表达 式的当 前行 相关 联 仔细看 看这 个示 例 有 助 于明白 相关 子查 询和 Exists 条件 的作 用 本示 例的 Exists 条 件回答这个问题 “ 有这个客户的销售记录吗?” 如果 回 答是没 有 那么这个搜索条件(Not Exists…) 为真 并且 选择 该客 户 (回忆 一下 这个 Select 语句 是左 外连 接的第二部 分 并 且企图 选择 不匹 配的 Customer 行 )这个 Exists 条 件 生成一 个包 含该 客户 的全 部 Sales 行的临时结 果 表 测 试 该 客户是否 有任 何 销 售 如 果 这组行非 空 那么 Exists 条件为真—— 该 客户有 一个 或者 多个 销售 包含该 客户 销售 的临 时结果 表 是由 下面 的子 查询(嵌套 的 选择表 达式) 生成 Select From Sale Where Sale.CustId=Customer.CustId 因为 只对 Sale 行 的数量感兴趣 所以用 指定 一 个暗 示的列项 而不 是列出明 确的 列名 称 用于 这个 选择 表达 式 的搜索 条件 非常 简单 如果 Sale 行的 CustId 列包含的客户 ID 与 当前在 外部 Select 表达 式 中测试 的 Customer 行相同 那么这个 Sale 行就 包 含在选择表达式 的结果 表中 可以 考虑 用 SQL Server 执行 下面 完整 的 Select 语 句算法 For all Customer rows Set CurCustId=Customer.CustId Set TmpSaleResultTable to Empty(remove all rows) For all Sale rows If Sale.CustId=CurCustId Add Sale row to TmpSaleResultTable EndIf EndFor If TmpSaleResultTable is Empty Add Customer row to final result table EndIf Endfor 尽管 SQL Server 没有必 要使 用这 种算 法执 行 Select 语句 但 这 种算法为理 解相 关子 查 询的结 果提 供了 一种 逻辑方 式 子查询可 以非常复杂 但是它们为表达不同的 搜索条 件提供了强大的 功能 现在 看 看 一个更加 复杂但是实用 的子查询用法 假设希望检索 每一个 客户的姓名和 城市 这个客 户 的订单 总 数 大于在该 城 市中所 有客 户订 单 的 平 均 数 下面的 Select 语句 检索 出希 望 得 到 的 客户清 单 Select CurCust.Name, CurCust.ShipCity From Customer As CurCust Where Exists ( Select From Sale As BigSale Where BigSale.CustId=CurCust.CustId Ang BigSale.TotalAmt> ( Select Avg(AvgSale.TotalAmt) From Customer As AvgCust Sale As AvgSale Where AvgCust.CustId=AvgSale.CustId And AvgCust.ShipCity=CurCust.ShipCity 使用图 2-2 中的 数据 这条 语 句生 成下面 的结 果Name ShipCity Smith Mfg Portland Bolt Co Eugene AA Products Portland 仔细看 看这 条 Select 语 句 是如何 构造 的 可以 展示 Transact-SQL 的许多高 级检 索能 力 第一个 From 子句指定 结果表 中 的行 来自 Customer 表 关联 名称 CustId 用在这条 语 句 Customar 的其他 地方 限定 来自 表的这 个特 殊角 色中的 列 第一个 Where 子 句使用 Exists 条 件查 询该 客户 是否 有任 何满 足指定条件的定 单 这组 将要测试的订单由第一个子查询( 第二 个 Select 关键 字 开始的 查询) 指定 记住 当使用 子 查询时 可以 认为 SQL Server 对每个 由在 外部 Select 语句 中的 From 子句定义 的行 执行 子 查询 因此 在这 个示 例 中 对于 在 Customer 中的每一 行 执行 子查 询 然后 测试 其结 果 是否包 含任 何行 第一个 子查 询检 索 Sale 表的 行 因 为只 对 该 子查询的 结果 进行 测试 看 其是否 包含任 何行 所以 所有 列都 被检索 出 来(Select) 该 From 子句指 定由 相关 名称 BigSale 限定的 Sale 表中的行 只有在这个 子查询中检索 的行才是当前的 客户 并且 其 销 售量 大于 当 前客户 所 在城市 中的 客户 定购 的平均 数 量 Where Sale 子句 指定 两个 条件 的连 接 对于 在子 查询 结果 中的 行 这两个 条件 必须 为 真 第一 个条 件是 Sale 行的 客户 ID 必须与 当前 客户 行的 客户 ID 相同 第二个条件是 一种使用另一个子查询的基 本条件 每一个销售 总量与一系列销售的平 均总 量进 行 比 较 在这个 示例中 使用了大于(>) 测试 并且 因为 在 基 本条 件中的两 个值 必 须是标量 所 以在 这个 示 例中子 查询 返回 的这 组 行只 能 包 含一个值( 有 一个列的 一行) 通 AV G 过在子 查询 的结 果表 列项中 只 指定 合计函 数 该子 查询 检索 有一 个列 的一 行 它包含 所希望 的平 均值 然后 这个值 与列 值 BigSale.TotalAmt 进行 比较 第二个 子查 询( 即第三 个 Select 语句) 指定 计算平 均值的 这组行 这些 行来 自 Customer 和 Sale 表的 相等 连接(AvgCust.CustId=AvgSale.CustId) 但是 只有 那些 与当 前客 户在 同一 个城市中 的客户才能包 含在平均值中 为了判断这个 条件 在最内的子查 询中的 每一行 的 (AvgCust.ShipCity) (CurCust.Shipcity) 城市 与在主查询 中当 前客户 所 在的城市 进行比较 2.4.7 限定 的条 件 最后一 种条 件类 型是 限定的 条 件 其语 法形 式如 下 expression quantifier(selcet expression) 比较运 算符 可以 是任 何列在“Basic 条件”中的 一个 限定 符可 以是 关键 字 All 或者 Any Some Any 注意 关键 字可 以用作 的同 义字 下面的 示例 检索 折扣 率大于 所 有 Portland 客户的折 扣 率的客 户行 select CurCust.CustId, CurCust.Discount From Customer As CurCust Where CurCust.Discount > All ( Select CityCust.Discount From Customer As CityCust Where CityCust.ShipCity=''Portland'' And CityCust.Discount Is Not Null) 如果该子查询 的结果表是空的 或当前客 户的折扣率大于在子查询的 结果表中的全部 值 那么 该示 例的>All 限 定 条件为真 使用 列在 图 2-2 中的 客户 数据 只检 索到 一行 Custld Discount 499921 0.100 用在限定中的 选择表达式必须只有一个列 这种比较测试应用到选择 表达式的结果表 中的每 一个 值 一般地 有限 定符 All 的条 件是 如果选择表达 式的结果表是空的 或者对于结 果表中 的全部 值的比较测试 为真 则条件 为真 如果对 于在 结果 表中 至少一 个 值的 比较 测试 为假 则条 件为假 如果对于在结 果表中至少一个值 这种比较测 试不能 判断 并且对于在结 果表 中 至少一 个值 这种 比较 测 试未知 则条件 未知 有 Any 限定符的 条件 是 如果这 种比 较测 试对 于在结 果 表中 的至 少一 个值 为真 则 条件为 真 如果选择表达 式的结果表是空的 或者比较测 试对于 在结果 表中的全部值 为假 则条件 为假 如果比较测试 对于在结果表中的至少一个值不 能判断 并且比 较测试对于在 结果 表 中至少 一个 值未 知 则条件未 知 当编码 一个 限定 条件 时 一定要 小心 不 要把 用英 语 按照正 常方 式表 示的 条件 与 SQL All 和 Any 限定符的 指定 含义搞 混了 例如 可能 听到 有人想要“ 比 Portland 任 何客 户的折 扣率 都大的 客户”的清 单 但是 如果 使用 下面 的 Select 语句 那么 检索 到的 清单 将包 括比 Portland 客户中 最低 折扣 率大 的所有客 户 Select CurCust.CustId From Customer As CurCust Where CurCust.Discount > Any ( Select CityCust.Discount From Customer As CityCust Where CityCust.ShipCity=''Portland'' And CityCust.Discount Is Not Null) 显然这 个清 单包 括了 Portland 的一些 客户( 在 Portland 的 客户中 折 扣率不 是最 低 的那些 客户) 使用 All 限定 符( 就像 在上 一个 例子 中那 样) 检索比 Portland 任 何客户的 最 高折扣 率 大的客 户 这样 当然 就 排除了 全部 Portland 客户 这个 Select 语句 的一 种比 较简 单的 方 法使用 了下 面的 基本 条件 Select CurCust.CustId From Customer As CurCust Where CurCust.Discount > ( Select Max (CityCust.Discount) From Customer As CityCust Where CityCust.ShipCity=''Portland'')2.5 Select 的其他用 法 2.5.1 使用 Case 表达 式 Transact-SQL 提供了一 种 Case 结构 可 以有 条件 地返回 一个 值 Case 结 构的形 式如下 Case When condition Then result-expression 1 1 … When Then condition result-expression n n Else result-expressio n End SQL Server 按顺序 判断 每一 个条件 当找 到第 一个 为 真的条 件时 SQL Server 使用这 个表达 式的 值( 在 Then 关 键字之 后) 如果 没有 为真 的 条件 那么 使用 可选 的 Else 关键字 后 面的表 达式 如果 没有 为 真的条 件并 且没 有 Else 关键 字 那么 Case 表达 式 的值为 空 在 下面的 示例 中 使用 Case 表 达式为每一 个客 户创 建 一个 分类 值 Select CustId, Name, Case When Discount > .3 Then 2 When Discount > .0 Then 1 Else 1 End As Category From Customer 当一系 列测 试对 应于 同一个 表 达式 的不 同值 时 有一种 Case 结 构的快 捷形 式 例如 为了扩 展一 组字 符代 码 可以使 用下 一个 显示 的表 达 式 Select PartId, Case PartType When ''E'' Then ''Electrical'' When ''M'' Then ''Mechanical'' Else ''Other '' End From Part 每一个 When 条 件为 不同的 值 也可 以是 表达 式 测 试条件 PartType=value Case 表 达式可 以用 在标 量值 是有效 的 大多 数的 地方 包 括用在 Select Insert Update 和 Delete 语 句中 2.5.2 使用 子查 询作 为标 量值 在任何可以使 用标量值或者表达式的地方 都可以使用返回一列和一 行的子查询 下 面 的 示例 说明 如何 检索 每 一个客 户的 折扣 率对 所有 客 户的平 均折 扣率 比率 Select Name, Discount, Discount/ (select Avg(Discount) From Customer) As TimesAvg From Customer Order By TimesAvg Desc 这条语 句返 回下 面的 结果 Name Discount TimesAvg Bell Bldg. .100 4.615 Nautilus .050 2.307 Smith Mfg .050 2.307 Bolt Co. .020 0.923 AA Products .010 0.461 Alpine Inc. .010 0.461 Seaworthy .010 0.461 Wood Bros. .010 0.461 Adapto .000 0.00 Floradel .000 0.00 Telez Co. .000 0.00 Udapto Mfg. .000 0.00 Ajax Inc. Null Null 甚至可以在这 种子查询中使用相关名称 这种技术 如下面的示例所 示 允许检索一 个类似 的清 单 但 根据 同 一个城 市的 客户 的平 均折 扣 率 计算 折扣 比率 Select CurCust.Name, CurCust.Discount, ( select Avg(AvgCust1.Discount) From Customer As AvgCust1 Where CurCust.ShipCity=AvgCust1.ShipCity) As CityAvg, CurCust.Discount/ ( select Avg( AvgCust2.Discount) Form Customer As Avgcust2 Where Curcust.ShipCity=AvgCust2.ShipCity) As TimesCityAvg From Customer As CurCust Order By TimesCityAvg Desc 这条语 句返 回下 面的 结果 Name Discount CityAvg TimesCityAvg Alpine Inc. 0.01 0.003333 3.0003 Bell Bldg 0.10 0.043333 2.3077 Seaworthy 0.01 0.005000 2.0000 Smith Mfg 0.05 0.027500 1.8181 Nautilus 0.05 0.027500 1.8181 Bolt Co 0.02 0.043333 0.4615 AA Products 0.01 0.027500 0.3636 Wood Bros 0.01 0.043333 0.2307 续表 Name Discount CityAvg TimesCityAvg Adapto 0.00 0.027500 0.0000 Telez Co 0.00 0.005000 0.0000 Floradel 0.00 0.003333 0.0000 Udapto Mfg 0.00 0.003333 0.0000 Ajax Inc NUll 0.005000 Null 2.5.3 在From 子句 中使用select 表达 式 From 如前所 示 子句列出了 一个 或者 多个 表和 或视图 由 这 些 表或者视图 中 的行生 成一个 Select 语句 的结 果 集 还可 以在 From 子句中使用选择表达 式 然后由这些检 索 到 的行创建一个临时表 这种功能是一种用单个行连 接合计值 的简便方 式 如下 例所示 如 果客户 达到 了同 一个 城市中 客 户的 平均 折扣 率 那 么 计算给 每一 个客 户多 少折 扣 Select Customer.CustId CustSaleTotal.SumTotalAmt, CityDisc.AvgDisc, CustSaleTotal.SumTotalAmtCityDisc.AvgDisc As AvgDiscValue From Customer Join (Select Sale.CustId, Sum(TotalAmt) As SumTotalAmt From Sale Group By CustId) As CustSaleTotal On Customer.CustId=CustSaleTotal.CustId Join (Select ShipCity, Avg(Discount) As CityDisc From Customer Group By shipCity) As CityDisc On Customer.ShipCity=CityDisc.ShipCity 注意 对于许 多查询 可以使用简单的或 者相关的子查询来生成一个 标量值 连接表 Where From 和视图 或者 使用 子句 子查 询达 到与 子句中选择表达 式 的同 样效 果 2.5.4 其它 Select 语句功 能 Transact-SQL 为选择表达 式提供了几个其他功能 为将结果集的大小限 定为一个指定 数字或 者行 的百 分比 可以 在 选 择列 项之 前 增加 一个 Top 子句 下面 的示 例说 明如 何指定 一个准 确的 行数 Select Top 10 Name, Discount From Customer Order By Discount Desc这条语 句返 回下 面的 结果 Name Discount Bell Bldg 0.01 Smith Mfg 0.05 Nautilus 0.05 Bolt Co 0.02 Alpine Inc 0.01 Seaworthy 0.01 AA Products 0.01 Wood Bros 0.01 Adapto 0.00 Floradel 0.00 在大多 数情 况下 Select 语句 将有 一个 Order By 子句和一 个 Top 子句 以使那些记 录 是某些 行中 最高 值或 者最低 值 的列 为了 返回 所选 行 的指定 百分 比 使 用下 面的形 式 Select Top 10 Percent Name, Discount From Customer Order By Discount Desc 这条语 句返 回下 面的 结果 Name Discount Bell Bldg. 0.10 Smith Mfg 0.05 当有一 个 Order By 子句( 在大多 数情 况中) 时 还 可 以指 定所有行的 Order By 列值与包 括在 Top 数字 中的 最后 一行 或者 包括 在结 果中 的百 分比的 Order By 列值相同的所 有 行 Select Top 10 Percent With Ties Name, Discount From Customer Order By Discount Desc 这条语 句返 回下 面的 结果 Name Discount Bell Bldg 0.10 Smith Mfg 0.05 Nautilus 0.05 With Ties 选项 是有 用的 例如 它可 避免有 特 定折 扣 率的一 些客 户包 括在 结果 中 而 有同样 折扣 率的 其他 客户却 被 排除 在结 果之 外 2.5.5 Group 子句 的Rollup 和 Cube 选项 前面 介绍 了合 计 函 数和 Group By 子 句提供 了一 种检索合 计信 息的 方式 这些标准的 SQL 功能 既 不 能 提供合并 详 细 行 和 汇 总 信息的方式 也不 能 在 同一个查询中 提 供 一 种获 取 多层汇 总信 息的 方式 Transct-SQL 有两个对 Group By 子句的扩展 选项 可 以提供 这些功 能 在Group By 列项之后 并且 在 Having 子句 之后( 如果 有) 可以指 定 With Rollup 选项 或者 With Cube 选项 With Rollup 使 SQL Server 在 分 组列表 的 每一层 增加 一个 子总 行 例 如 下面 的语 句使 用 With Rollup 选项 Select Customer.CustId, Sale.OrderId, Avg(SaleItem.Price) As "Avg.Sale Price" From ( Customer Join Sale On Customer.CustId=Sale.CustId) Join SaleItem On Sale.OrderId=SaleItem.OrderId Group By Customer.CustId,Sale.OrderId With Rollup 当使用 查询 分析 器时 该查 询 结果如下 面 前 三列所 示 CustId OrderId Avg.Sale Price Notes 133568 234117 6,5733 Group By row 133568 Null 6.5733 Rollup summary for a CustId 246900 234116 4.0066 Group By row 246900 234120 4.2800 Group By row 246900 Null 4.1160 Rollup summary for a CustId 499320 234112 4.9000 Group By row 499320 234114 8.3400 Group By row 499320 234119 4.2800 Group By row 499320 Null 6.1971 Rollup summary for a CustId 888402 23411 7.2033 Group By row 888402 Null 7.2033 Rollup summary for a CustId 890003 234115 3.3066 Group By row 890003 Null 3.3066 Rollup summary for a CustId 905011 234118 5.9000 Group By row 905011 Null 5.9000 Rollup summary for a CustId Null Null 5.5221 Rollup summary for all rows 注意 这个 汇总 层如 何从右 向 左汇 总 Group By 的列项 With Cube 选 项与此类似 但 Group By 对每一 个可 能的 列组 合 通过从 列项中 删除 一个 或者 多个列 创建 了 一组汇 总行 在前面 的示 例中 使用 With Cube 替代 With Rollup 将对 CustId 和 OrderId 的每一个 组 合 每一个 CustId 值 每一 个 OrderId 值和 全部 行生 成汇总 行 如果 Group By 列项有三 个 例 如 C1 C2 C3 那么 在 结果表 中有 八组 汇总 行 对每一 个 C1 C2 和 C3 值的 唯一 组合( 这 些行 是由带或不带 Cube 选项 的 Group By ) 子句创 建 对于每 一个 唯一 的 C1 和 C2 值对 对于每 一个 唯一 的 C1 和 C3 值对 对于每 一个 唯一 的 C2 和 C3 值对 对于每 一个 C1 值 对于每 一个 C2 值 对于每 一个 C3 值 对于整 个所 选行 集的 一行 注意 Cube 运算 符有令人迷惑的名 称 因为 它支 持多于三维的操作( 就 像一个立 方体 3 一样) 并且 汇总 行集 的数量 不 是 C (Group By 列数的立 方) 而是 2 如果所 有的 Group By 列都不允 许空 或者 使用 空的 组合 列剔 除全 部 行 那么 可 以指出 哪些结 果表 中的 行是 通过检 查 组合 列中 的空 值 由 Rollup 选 项或者 Cube 选项 增 加的汇总 行 否则 一 个空 组合 列 可能 包含在 有空 列的 行组 没有办 法区 分两 种类 型的 汇 总行 SQL Server 提供了 Grouping 函数 解决 这个 问题 为了 在结果 表 中增 加区分汇总行类型的 列 对 在选择 列表 中的 每一 个组合 列 包括 一个 Grouping 函数 Select Grouping( Customer.CustId) As RollupAllCustId, Grouping( Sale.OrderId) As RollupAllOrderId, Customer.CustId, Sale.OrderId, Avg( SaleItem.Price) From ( Customer Join Sale On Customer.CustId=Sale.CustId) Join SaleItem On Sale.OrderId=SaleItem.OrderId Group By Customer.CustId,Sale.OrderId With Rollup 结果如 下 RollupAllCustId RollupAllOrderId CustId OrderId Avg.Sale Price 0 0 133568 234117 6.5733 0 1 133568 Null 6.5377 0 0 246900 234116 4.0066 0 0 246900 234120 4.2800 0 1 246900 Null 4.1160 0 0 499320 234112 4.9000 0 0 499320 234114 8.3400 0 0 499320 234119 4.2800 0 1 499320 Null 6.1971 0 0 888402 234113 7.2033 0 1 888402 Null 7.2033 0 0 890003 234115 3.3066 0 1 890003 Null 3.3066 0 0 905011 234118 5.9000 0 1 905011 Null 5.9000 1 1 Null Null 5.5221第一列( 别名 是Rollup AllCustId) 的值 1 表示 没有 使用 CustId 值决定子 组 的 Rollup 汇总 行 在本 例中 只 有所 有 行的汇 总行 才在 该列 上是 1 RollupAllCustId 列的 0 表示普 通 的 Group By 行和任 何使 用 CustId 决定子组 的 Rollup 汇总 行 同样 第二个列( 别名是 Rollup- AllOrderId) 的 0 或者 1 取 决于汇 总行 的类 型 在这 个示 例中 所有 的 Rollup 汇 总 行都将 1 作为 RollupAllOrderId 列的值 因为 没有 Rollup 汇总 行使用 OrderId 决定子 组 OrderId 是 最低层 的组 合列 只用 于 确定普 通的 Group By 组 注意 普通 的 Group By 行的 0 表示全 部 Grouping 函数 列 而生成 Rollup 行 的至 少有 一个值为 1 的 Grouping 函数列 按照 同样 的方式 可以 用 Grouping 函数 区分 使用 Cube 选项 生 成的结 果表 中的 行的 类型 2.5.6 Compute 子句 对于交 互式 检索 Transact-SQL 还有另一 种提 供分类汇 总 功能 的 特征 可以 在一 个 Select 语句之 后 包 括几 个选 择 表达式 联合 中的 一个 一个 或者多 个 Compute 子句 该子句列出 了合计函 数以及用于一 个或者多个合 计层的列 下面 的语句 生成了对于每 一个客 户有分 类 汇总的 Sales 行清 单和 一个 总和 Select CustId, OrderId, TotalAmt From Sale Order By CustId,OrderId Compute Sum(TotalAmt) By CustId Compute Sum(TotalAmt) 当使用 查询 分析 器时 查 询结果 如下 所示 CustId OrderId TotalAmt ---------- ----------- -------- 133568 234117 550.0000 sum -------- 550.0000 CustId OrderId TotalAmt ---------- ----------- --------- 246900 234116 678.0000 246900 234120 399.7000 sum --------- 1077.7000 CustId OrderId TotalAmt ---------- ----------- --------- 499320 234112 35.0000 499320 234114 78.9000 499320 234119 201.0000 sum ---------- 314.9000CustId OrderId TotalAmt ---------- ----------- --------- 888402 234113 278.7500 sum ---------- 278.7500 CustId OrderId TotalAmt ------------ ----------- ------------ 890003 234115 1000.0000 sum -------- 1000.0000 CustId OrderId TotalAmt ---------- ------------ --------- 905011 234118 89.5000 sum ------- 89.5000 sum ------- 3310.8500 16 行受影响 Compute 子句 可以 包含 任何 列在 前面 表 2-3 中的 合计函 数 Compute 子句中可选的 By 列项定 义 break 层次 并且 必须 包含 列在 Order By 子句中的 一个 或者 多个 列或 者表 达式 By 列必须从 Order By 子句中的 第一 项开 始 并且按 照从 左到 右的 顺序 不能 跳过 任 何项 就像前 面的 示例 那样 可 以使用 多个 Compute 子句 来得 到几 个层 次的 合计 信息 2.5.7 Into 子句 Select 语句 的主 要用 途是 检索 数据 Transact-SQL 有一种扩 展 允许用 Select 语句检 索 到的行 创建 一个 新表 新 表的名 称由 Into 子句 指定 Into 子句紧 跟在 Select 列项之 后 Select Into CustomerSeattle From Customer Where ShipCity=''Seattle'' 新创建 的 表 有与 在 Select 列项 中 指 定的 相 同 的列 并且 有来自 结果表中 的行 包括行 选择或者 合计的影响 这 种功能 的主要目 的是创建一个临时表 它可 以用 于查 询 会话 过 程 中的后 续 检 索 如果 希 望使 用 Select 语 句将行增 加 到一 个永久 表中 那么 必须 使 用下面 的 系统存 储过 程调 用打 开 Select into/bulkcopy 选项 sp_dboption AppDta select into/bulkcopy true 在 SQL Server Enterprise Manager 中 右击 数据 库 从 弹出 的菜 单中选择 Properties, 在 Options 标签 上 Select Into/bulk copy 复选框 也可 以设 置这 个选 项 注意 这个新表没有任何可能在源表上定义的约束 这是一个通常应该使用 Create Table 而不 是 select 来创 建新 的永 久 性 表的 原因 为了从 一个 或者 多 个 表中将数据复 制到一个 已有 的表 中 使 用 带有子 查询 的 Insert 语句 下面论述 此项 内容 2.6 Insert 语句 SQL 有三种 可以 用于 修改表 数 据的 重要 的 DML 语句 Insert Update 和 Delete 所有 这三种语 句都可以 修改表中的一行或 者表中的一个行 集 它们 都 不 能在一条语句 中修改 多 个表 还 可以 通过视图 修 改数据 然而 在一 个视图 上运行 的 DML 语句一次只 能修 改基 表 所有 这三 种 DML 语句允许 使用 Where 子句指 定 将要插 入 修改 或者 删除 的 行集 在 这些语 句中 的 Where 子 句的一 般形 式 与 Select 语句 中 Where 子 句的形 式相 同 2.6.1 基本 Insert 语句 为了 向表中增 加 一 个新 行 可以使用 Insert 语句 例如 为 了 增加 一 个 新客户 输入 下面的 语句 Insert Into Customer ( CustId, Name, ShipCity, Discount) Values ( 678987, ''Atlas Inc'', ''Portland'', Null) 括号中的列项跟在表名或者视图名之后 Va l u e s 子句 指定 新值 列 表 这些 值也在括号 中 与列 项中的相应列 对应 可以省 略列项 在这 种 情况下 暗示的列项 是全部 列 其顺 序是在 Create Table Alter Table Create View 或者 Alter View 等 DDL 语句中定义的 顺 序 然而 省略 列项 并不 好 因 为 它容 易出 错并且 可 读性比较差 在插入的列项 中 必须包 括系统不能确定其值的任 何列 如果一 个列是用 Identity 关 键字定 义的 或在 Create Table 或者 Alter Table 上 指定了 Default 子句 或是一 个 TimeStamp 数据 类 型 或者 允许 空 那么可以省略这个列 如果一 个省略 的列 有一 个明 确的缺省 值( 在 第 1 章中 描述 了) 那么 使 用该缺 省值 否则 SQL Server 为 Identity 列插入下一个 增 量 identity 值 为 TimeStamp 列插入当 前的 时戳 值 或者将 列设置 为 空 可以使 用任 何有 效表 达式作 为 列的 值 如上例 所示 可以使 用 Null 关 键字明 确 地设置 某列为 空 当然 设置 为 空的列 不能 用 Not Null 子句 定义 还可以 在 Insert 语句的 值项 中使 用 Default 关 键字指 定 应该使 用某 个列 的缺 省值( 或者 空值) 指定 Default 的 任 何列要 么有 一个 缺省 值 如 前所述 要么允 许空 值 2.6.2 多行 Insert 语句 多行 Insert 语句 从一个表 中 将数据复 制 到另 一 个 表 并且实 现了最接 近于 关系赋值运 算的 Transact-SQL 等价运算 多行 Insert 语 句的 目 标 表必须 已经 存在( 不像 有 Into 子句的 select 语句那 样 Insert 语句 不能创 建 新表) 例如 下面 的 Insert 语句从 一个 旧版 本的 Customer 表中将 所有 行复 制到 一个新 版 本的 表中 它 有一 个附加 的 ShipState 列Insert Into Customer ( CustId, Name, ShipCity, ShipState, Discount) select CustId, Name, ShipCity, , Discount From CustOld 最初 新表 中的 所有 行都有 一 个空 白的 ShipState 列 因为 Select 语句 的结 果表包 括 空 格文字 作为 在其 选择 列表中 倒 数第 二个 元素 虽然 该 Insert 语 句只可以 在一个表 中 增 加行 但 是插入的 行 可 以来 自 多 个表 例如 下面的 Create Table 和 Insert 语句复 制了合 并 的客 户和销 售 信息 Create Table SaleWithCust ( OrderId Int Not Null, TotalAmt Money Not Null, Name Char (30) Not Null) Insert Into SaleWithCust ( OrderId, TotalAmt, Name) select OrderId, TotalAmt, Name From Customer,Sale Where Customer.CustId=Sale.CustId 在 Insert 语句执行之后 对在 Customer 或者 Sale 表中数据的改变并不反映在 SaleWithCust 表中 多行 的 Insert 语句 与 视图不 同 它从 From 子句 中引用的表中 拷 贝 数 据 就像前 面两 个示 例那 样 可以在 一条 Insert 语句中 使用 Select 语 句指定 将要 插入 的行 这种嵌 套的 Select 语句 可 以使用 在前 一节 中讨 论的 From Where Having Group By Union 和 Order By 子句(但是 不包 括 Into 或者 Compute 子句) Transact-SQL 还允许指 定嵌 套的 Execute 语句 它 调 用一个 存储 过程 为多 行 Insert 语句 生成结 果表 如果 有一 个 存储过 程 CalcCustCreditRating, 它使 用 Customer 和 Sale 表生成 一 个有客 户信 贷比 率的 结果表 那么 可以 使用 下面 的语句 创 建和 加载 这个 新表 Create Table CustCreditRate ( CustId Int Not Null, CreditRate Int) Insert Into CustCreditRate ( CustId, CreditRate) Execute CalcCustCreditRating CalcCustcreditRating 必须提供一个有两个列的结 果 集 这 两 个列与 Insert 语句 的列项 相对应 存储 过程 在本 章 后面讨 论 2.6.3 在视 图中 插入 行 为了通过 视图插入行 这个视图 必须不包含任 何由文 字或者表达式定 义的列 视图 中 的列必须是基表中的简单 列 或者是可插入的视图 如 果 一个视图分散在多个基表 中( 例如 视图是 一个 连接) 那么 Insert 语句只 能查 看在 一个 基表 上定 义的 列 当使 用视图 时 任何 没有显示 为可修改的视 图列的 基表列 必须有一个缺省 值 如 前所述 或者允许空 并且 新 行为省 略的 列获 得缺 省值或 者 空值 注意 尽管 视图 的 Insert 规则看 起来 有些 复杂 但是 实际 上大 多数 的 Insert 操作要 么 使用一个基表 要么使用 在一个 基表上的一些 或者 全部列的视图 对于这些情 况 要了 解的 主要 规则 是 对省略 列的 限制 2.7 Update 语句 在 Update 语句 的 Where 子 句中使 用 某个 指定 行的 主 键值并 且为 一个 或者 多个 列 赋于新 值 可以 修改 该行 例如 下面 的 Update 语 句改 变客户 的姓名 并 且对某个客 户的 当前 折 扣率增 加两 个百 分点 Update Customer Set Name=''Wood Products'' Discount=Discount+.02 Where CustId=905011 Set 关键 字之 后是 一个 或者多 个 列的 赋值 形式 column-name=expression 使用搜索条 件 指 定多个行 可 以 修 改一组行 下 面 的语 句给所 有 的 Portland 客户提供 百分之 十的 折扣 率 Update Customer Set Discount=.10 Where ShipCity=''Portland'' 如果没 有指 定 Where 子句 那么 修改 指定 表中 的全 部行 注意 这种 Update 语句 也 称作搜 索的 Update 因为 SQL Server 搜索 将要 修改 的全 部 行 使用 Transact-SQL 游标( 在本章 后面 的 存储过 程 一 节 中讨论) 还可以 定位修改 这时 首先检索出来希望修改 的行 然后指定 Where Current Of cursor-name 来修 改当 前行 使用 Null 关 键字 可以 将允许空的列设置 为空 Update Customer Set Discount=Null Where ShipCity = ''Portland'' 类似地 使用 Default 关键 字 可以设置 有缺 省值 的 列( 或允许 空 的 列) 为其 缺省值( 或者 为空值)Update Customer Set Discount=Default Where ShipCity = ''Potland'' 2.7.1 在Update 语句 中 使用子 查询 在某个列赋值 右端上的表达式可以是一个 标量子查询 即返回一个单 行和一个单列的 查询 下面 的示 例将 Portland 客户的 折扣 率 设置 为全 部客户 的平 均折 扣 率 Update Customer Set Discount = (select Avg (Discount) From Customer) Where ShipCity = ''Portland'' 在列的赋值中 使用一个相关子查询 可以 根据一个表中相关行的值设 置另一个表中行 的值 下 面的 示例 说明 了 如何把 价格 项从 单独 的 Price 表 中复制 到 Item 表中 Update Item Set ItemPrice = (select ItemPrice From Price Where Price.ItemId = Item.ItemId) Where Exists (select From Price Where Price.ItemId = Item.ItemId) 注意这 个示 例是 如何 使用 Exists 条件 在 Items 表中限制这些 行 的变 化 这些 行在 Price 表中有 一个 匹配 行 如 果 没有这 种条 件 所有 的 Item 行都将被 修改 没有 匹配 Price 行的 那些行 将其 价格 设置 为空 注意 Transcat-SQL 有一个对 Update 语句 的非 ANSI 标准 的扩 展 允许 From 子句作 为 Update 语句 的一部 分(不只 是在 子查 询中) 使用这 种扩 展 前面 的示 例可 以 写成下 面的 形式 Update Item Set ItemPrice = Price.ItemPrice From Item,Price Where Price.ItemId = Item.ItemId Update 语句可以使用 有 子查询 的搜索条件检索将 要 修改的 行 下面的语 句 对 那 些 当 前 的折扣 率低 于百 分之 十并且订单总数超过 1000 的客 户提高 到百 分之 十的 折扣 率 Update Customer Set Discount = .10 Where (Discount<.10 Or Discount Is Null) And 1000 < (select Sum(TotalAmt) From Sale Where Sale.CustId = Customer.CustId) 在 Update 语句 的 Where 子句 中 可 以使用 在 select 语 句中讨 论的 任何 条件 甚至 可以 用将要 修改 的表 作为 某个子 查 询的 基表 因此 下 面 的子查 询是 有效 的 Update Customer Set Discount = .10 Where Discount Is Null Or Discount < ( select Avg(Discount) From Customer Where ShipCity = ''Portland'') 这条 Update 语句 为 那些 没 有折扣 或者 折扣 率低 于 Portland 客 户的平均 折扣 率的 客户 提供百 分之 十 的 折扣 率 在 本例中 SQL Server 计算子 查 询的平均 值 并用该平均 值 作 为 Update 中 将要 修改 的全 部 行 的测试值 注意 虽然 对 Portland 客 户的单 个修 改可 能改 变平 均值 但是 这种 改变 不影响 在 Update 中所 选择 的行 2.7.2 修改 多个 表 为了 修改 多个 表 必须 使 用多条 DML 语句 例如 为 了增加 雇员 和 承 包人 的小 时工 资 可以 使用 下面 两条 语 句 Update Employee Set HourlyRate = HourlyRate 1.05 Update Contractor Set HourlyRate = HourlyRate 1.05 当修改主键 唯一性键或者外键列时 必 须考虑该表存在的键值约束 例如 为了改 变一个 客户 的 ID 必须 保证 在 Customer 表中以 及在所 有 用 CustId 作为外键 的表中 改 变 CustId 列值如 果不 存在 外键 约束 那么只需 使用 多个 Update 语句 例 如下 面的示 例 Update Customer Set CustId = 123789 Where CustId = 888402 Update Sale Set CustId = 123789 Where CustId = 888402 但是如 果 Sale 表有 一个 指 定 CustId 列的 外键 约束 那 么这两 条语 句都 会引 起错 误 因 为每一 个语 句本 身都 将产生 不 匹配 的 Sale 行 一种 解决 方案 是插 入一 个新 的 Customer 行 将 Sales 行改为 引用 这个 新的 Customer 行 然后 删除旧 的 Customer 行 Insert Into Customer ( CustId, Name, ShipCity, Discount) Select 123789, Name, ShipCity, Discount From Customer Where CustId = 888402 Update Sale Set CustId = 123789 Where CustId = 888402 Delete From Customer Where CustId = 8884022.8 Delete 和 Truncate Table 语句 为了从 一个 表中 删除 一行 输入 如下 所示 的 Delete 语句 Delete From Customer Where CustId = 905011 使用指 定多 行的 搜索 条件 可以 从一 个表 中删 除一 组 行 Delete From Customer Where ShipCity =''Portland'' 注意 就像 Update 语 句一样 这种 Delete 语句也 称 为搜索 删除 Transact-SQL 还有定 位删除 这些 内容 在 存 储过程 一节讨 论 用于 Delete 语句 的搜 索条 件也 可以 包含 子查 询 就像 在 Update 语 句示例 中描 述 的那样 为了删 除与 Sale 行没 有关系 的所 有客 户 可以 使用 一个 有 Not Exists 条件 的 Where 子句和 一个相 关子 查询 Delete From Customer Where Not Exists ( Select From Sale Where Sale.CustId = Customer.CustId) 注意 Transact-SQL 有一个 Delete 语句 的非 ANSI 标准 扩展 允许 From 子句作为 Delete 语句的 一部 分(不仅 仅是 包 含在子 查询 中) 总之 子查 询是首选 的 技术 2.8.1 清除 整个 表 输入一 个没 有 Where 子句 的 Delete 语句 可以有意 或 者无意 从表 中清 除全 部行 Delete From Customer 注意 在从 表中 清除 全部行之 后 该表 仍然 存在 它只是 一个 空表 使用 在第 1 章讨 论的 Drop 语句 可以 从系 统表 中删 除一 个表 SQL Server 提供的 Truncate Table 语句 可 作为一种 快 速清除 表中 数据 的方 法 下 面的语 句在功 能上 等价 于前 一个 Delete 语句 Truncate Table Customer 注意 不像 Delete 语句 Truncate Table 语句 不把 删除 的行 放在 事务 日志 中 因 此不能 撤销此 语句 Truncate Table 语 句 也不能激 活 该 表的 delete 触发 器 不能 在外 键 约束中 作为 父表 引用 的表上 使 用 Trancate Table 语句 2.8.2 从多 个表 中删 除行 就像在 Update 语句中 讨论的 那 样 如果 希望 从多 个表中 删 除行 必须 执行 多个 Delete 语句2.9 并行修 改和表锁 当两个 NT/2000 Server 进程 访问 同一 个基 表时 一个 进程 的行 修改 可能 与另 一进 程的 检索或 者修 改相 冲突 例如 如果 一个 进程 执行 下面的 Select 语句 Select Avg (Discount) From Customer 而另一 个进 程正 在执 行修改 Discount 列的语 句 Update Customer Set Discount = .10 Where ShipCity =''Portland'' 第一个进程可能得到一个基于一些 Portland 客户的旧 Discount 值而其他客户的新 Discount 值的 平均 值 SQL Server 自动采 取一 些措 施 防止 这两 个进 程检 索和 修改 单个行 的冲突 2.9.1 如何 防止 访问 冲突 对于 Select 和 Update 语句 Transact-SQL 有一些扩 展 为明 确锁 定基 表以防止冲 突 访 问提供 了一 种方 式 在 这 两种情 况下 在引 用的 表名后 面 使用 With 关键 字 可以 使用表提 示 下面 的语 句防 止 Select 语句 冲突 修改 Select Avg( Discount) From Customer With (TabLock) 在 Select 语句 中 TabLock 提 示 指定在 Select 语 句 执行过程 中 保持对整 个 表 占有共 享 锁 共享 锁防 止其 他修 改 访问 但是 允许 其他 阅读 访 问 另外 Update 语句 可以 使 用带有 TabLockX 表 提示的 From 子句来占有 排它 锁 这可 防止另 一个 进程 对表 进行任 何 类型 的访 问 下面 的语句 保 证在 Update 语 句执行 期间 没有 其他用 户访 问 Castomer 表 Update Customer Set Discount = .10 From Customer With (TabLockX) Where ShipCity = ''Portland'' 总之 应该尽 量用最短的时间锁定表 因 为表锁可能堵塞其他进程对 表进行 正常的访 问 2.9.2 如何 维持 数据 库的 一致 性 当修改 SQL Server 数据库时 另外 一种 考虑 是当 修改 多行 时 维 持数 据库的 一致 性 假设输 入下 面的 Update 语句 对于 那些 有非 空折 扣率的 所 有客 户提 高折 扣率 Update Customer Set Discount = Discount + 0.001 Where Discount Is Not Null 为了执 行这 条语 句 SQL Server 检索 测 试 并 可能修 改每一 行 如 果在处 理 了一些 不 是全部 Customer 行 后 执 行这条 语句 的 进程突 然中 断( 例如 断电) 那么 Customer 表将处于不一致的状态 虽然有些行 增 加 但 是 有 些行没有 增 加 Update 语句 也不 能 重 新 输 入 因 为 这将 为那 些在 前面 没 有完整 执行 更新 的过 程中 已 经修改 的客 户增 加额 外的 折 扣率 SQL Server 为事务 的完 整性和恢复 性提 供了 一 种 功能 保证多行 事务 的执 行 要么全部 完成 要么 全部 没有 完成 SQL Server 确保 由 Update 语句 在完 成 和 正在提 交 之前突 然 失败造 成的 全部 行的 变化自 动 由 SQL Server 撤消 即使因突然断电关 闭 系 统也是如 此 在 这次失 败的 修改 之后 表 中的所 有行 都恢 复到 开始 修 改之前 的状 态 注意 这种 SQL Server 功能 没有 什么 奇妙 当修 改一 个表 时 SQL Server 会在该行 修改之 前 在事 务日 志(用 于这种目的 的 SQL Server 文件) 中存 储 每一 行的改前 图像 拷贝 如 果整 个修 改不能正 常完 成 SQL Server 用这些改 前图 像将 每一 行改回 到其 预先 修改 的值 在默认 情况 下 SQL Server 运行在自动 提交 事 务 模式下 在这 种模 式下 把每一个单 独的 Update Insert 或者 Delete 语 句 作为一种 要么 全 部完成 要么 全 部没 有 完成的事务 当 这种语 句完 成时 这些 变 化就是 永久 的 当以自 动提 交事 务模 式运行 时 可以 定义 一个 明确 的 事务 执行 一个 Start Transaction 语句 其后是 希望包括 在 该 事务 中 的 语句 明 确结束事 务时 可以 用 Commit 语句使改变 永久化 或者 使用 Rollback 语句取 消这 个事 务中 的变 化 如果不使用自动提交的事务模式( 有或 者没 有明 确的 事 务) 可以将事务模式设置为 隐 含的事 务模 式 使 用隐 含 的事务 模式 在一 个会 话中 列 在下面的语 句中 第一 个 SQL 语句 开始第 一个 事务 结束 某 个事务 的 Commit 或者 Rollback 语句暗示开始 下一个事 务 Alter Table Create xxx Delete Drop xxx Fetch Grant Insert Open Revoke select Truncate Table Update 隐含的 事务 模式 符合 ANSI 标准的 SQL 方法 可以 使用 下面 的语 句将某个 指 定的 会 话 改为隐 含的 事务 模式 Set Implicit_Transactions On 事务可以 用于将多个修 改语句组合到一个操作 中 以 便 使 在 事务中全 部修改语句做 出 的所有数 据库变化要么 发生 要么没 有发生 考虑一 个经典 的银行事务 在这 个 事务中 从一个 储蓄 帐号 转帐 一笔金 额 到一 个支 票帐 户 这种事 务 要求 至少有两个 Update 语句 并 且要么两 条语句全部完 成要么两条语 句全都没有完整 这一 点非常关键 使用 一 个明确 的 事务 这些 语句 的顺 序如下 Begin Transaction Update Saving Set Balance = Balance-100.00 Where AccountId = 123987 Update checking Set Balance = Balance + 100.00 Where AccountId = 123987 Commit 如果决 定取 消还 没有 提交的 事 务更 新 只需 执行下 面 的 Rollback 语句 Rollback 使用隐 含的 事务 模式 时 不能使 用 Begin Transaction 语句 因为 事务 的开 始由 会话 中 的第一 条语 句或 者前 面的 Commit 或者 Rollback 语句 定义 Commit 和 Rollback 语句 一 般 都可以用 在存 储过 程和 HLL 程序中 而不 是交 互式 执行 特别 是 Rollback 语句经常用于当检测到错误时 取消未提交的修改 Rollback 语句使 用 的基本 逻辑 如下 --Begin transaction (explicitly or implicitly) Update Saving Set Balance = Balance -100.00 Where AccountId =123987 If Error Rollback Else Update Checking Set Balance = Balance +100.00 Where AccountId = 123987 If Error Rollback Else Commit EndIf EndIf 这个事 务只 有在 两个 帐户转 帐 都成 功完 成时 才能 提交 注意 使用 明确 的事 务 可 以有选择 地在 Begin Transaction Commit 和 Rollback 语句 之后 指 定 一个事务 名 称 但 这并没 有 任何实际的影 响 还可以嵌 套 事 务(例如 在触发 器中) SQL Server 忽略 除了 外部 的 Commit 或者 Rollback 语 句之外的 全 部语句 然而 嵌套 的事务 要 求 Begin Transaction 和 Commit Rollback 操作 成对 如果处理不正确会引起错误 总之 事务 的开始步和 结束 点应 该在应用 程 序 中必 须要 么全 部完 成 要么全部不 完 成 的操作初 始 化和确 定其 完成 状态 2. 10 存 储 过 程 除了上 面描 述的 单个 DML 语句外 SQL 有许 多语 句允许编写存储过程 存储 过程 是 Transact-SQL 语句编译 而成 的对 象 可以 用 EXECUTE storedprocedurename 语句( 或者在其 它数据 库界 面中 的函 数 例如 ODBC)运行它 存储 过程 可以 返回 一个 或多 个结果 集 整型 返回代 码 OUTPUT 自 变量 或参数 存 储程序有 许多 优 点 高速 度 可重 用性 商业 逻 辑封装 保护 尚未 优化 的查询 减少网络拥挤 以及安 全 性( 因为只 需对存储过程授予许可 而对它 访问 的对 象不 需授予 许 可) 存储 过程 是 Transact-SQL 开发人员 与数据库交互操作 提高数 据库 性能 和可 靠性的 基 础 Microsoft SQL Server 能提供 永久 或临 时存储 过程 临 时存储 过程 存储 在 tempdb 系统 数据库 中 在 一 个存 储过程中 可以 使用 任何 SQL 语句 但 是 不 包括下 面的语 句 Create Default,Create Procedure,Create Rule,Create Trigger,Create View 存储过 程非 常类 似任 何 HLL 过程—— 它可 以有 输入 输 出参数 本地变 量 数字 和 字符计 算和 赋值 数据库操作(DDL 和 DML) 以及控制 执行流程 的 逻辑 下面 是一 个 创建 存储过 程的 简单 示例 Create Procedure ListCustWithDiscount @MinDiscount Dec(5,3) As select From Customer Where Discount >= @MinDiscount 存储过 程总 是在 当前 的数据 库 中创 建 为了 执行 这个过 程 使用 下面 的语 句 Execute ListCustWithDiscount .1 注意 当使 用查 询分 析器或 者 OSQL 时 如果 存储 过程 调用 是要执行 的 唯 一语句 或者 是批语 句中 的第 一条 语句 那么 关键 字 Execute 是可 选的 存储过 程最 大的 嵌套 层数是 32 内置 函数@@NestLevel 提供了当 前的 嵌套 层 当定义 存储 过程 时 存 储 过程名 称在 Create Proedure 关 键字之 后 参数 声明 在存 储过 程名称 之后 As 关键字 表示 存储 过 程 主体 的开 始 存储 过程 主体 是 一 个或 者多个 SQL 语 句 存储 过程 可以 有选 择 地返回 一个 整数 值(类似于 HLL 函数返回一 个值 的方 式) 可以 定 义输出 参数 将数 据返 回到调 用 者 下面 简要研 究这 些 话题 注意 Transact-SQL 允许有选择地使用一个名称和数字区分一个存储过程( 例如 ListCust 1) 当 执 行用数字创建的存 储 过 程 时 也必须包括该数 字 虽然 这种 方法允 许在 一个 Drop Procedure 语句中 删除 有相 同名 称 和 不同数字 的全 部 过程 但是实践证明 这 种命名 不好 我 们建 议对 于 全部存 储过 程使 用唯 一的 描述性 名称( 没有 数字) 2.10.1 修改 和删 除存 储 过程 Alter Procedure 语句 允许 改 变存储 过程 的代 码 而 不必 改变 已经 授于 该过 程的许可 Alter Procedure 语句 的语 法类 似于 Create Procedure 语句的 语法 下 面的示 例说 明如 何修正存 储 过程 ListCustWithDiscount: Alter Procedure ListCustWithDiscount @MinDiscount Dec(5,3) As select From Customer Where Status And Discount .= @MinDiscount 使用 Sp_rename 系 统存 储过程可以 重新 命名 一 个 存储过 程 它带 三个 参数 旧名称 新名称和对象类型( object 是存储过程的对象类型) 下面的示例重新命名 ListCustWithDiscount 过程 sp_rename ''ListCustWithDiscount'', ''ListCustomerWithDiscount'', ''object'' 无论何 时改 变存 储过 程使用 的 表或 者当 SQL Server 启动 之后 第一 次运 行存储 过程 时 SQL Server 自动编 译 和 优化 该存储过程 如 果增加了一 个 新索引 并且 希望强制重 新 编 译 来利用 该索 引 可以 使用 sp_recompile 系统 存储 过程 如下 所示 sp_recompile `ListCustWithDiscount'' 该示例 标记 ListCustWithDiscount 过程 以便 下次 调用 它时重 新编 译 可以使 用 Drop Procedure 语句 删除 存储 过程 Drop Procedure ListCustWithDiscount 2.10.2 显示 有关 存储 过 程的信 息 有三个 系统 存储 过程 可以显 示 有关 存储 过程 的信 息 sp_help procedure-name 显示 该存 储过 程的 所有 者和 创建 的时 间 sp_helptext procedure-name 显 示 该存储 过程 的源 代码 sp_depends procedure-name 显示该 存储 过程 引用 的对 象清 单 如果丢 失了 最初 用来 创建存 储 过程 的源 代码 那么 sp_helptext 系 统存储 过程 特 别有用 2.10.3 存储 过程 中的 延 迟名解 析性 能(Delayed Name Resolution Behavior) 由于允 许 SQL Server 开发人 员创 建在 Transact-SQL 代码中 并不 存在 的存 储过 程 批处 理文件 或触 发程 序及 引用对 象 延迟 名解 析可 节省 Transact-SQL 编程人员 的大量 时 间和 精 力 在较 老的版本 中 Transact-SQL 编程人员必须按特定的顺序创 建对 象 例如 在 6.x 版本中 当创 建存 储过 程 时 存储 过程 在创 建脚 本的 Transact-SQL 代码中所引用 的 对象 必 须在分析该存储过程( 即 句 法分析) 和创 建存 储过 程对 象时 存在 于数 据库 上 如 果 在数据 库 上找不 到存 储过 程所 引用的 对 象 就无 法创 建存 储过程 对 象 在以 前的 版本 中 SQL Server 会返 回 引用 对象 不存 在 的错误声明 为了创建引 用 某个 对 象 的 存 储 过 程( 也可 以 是触发 程序) 就必 须创 建该 对象 这是 非常 费时 的 也不 符 合逻辑 的循 环 在 Microsoft SQL Server 7 及 2000 中 创 建存 储过 程 时若引 用对 象不 存在 当分析该 存储过程( 分析 语句 的合 法 性) 时 即使 在数 据库 上找 不到 存储 过程 或触 发程 序 中 引用的 对 象 SQL Server 也允 许 创建 存储过程 并将 它放到数据 库 上 在以 后的 解析运行阶 段 会 解 析名称 解析阶段是另 一个合法性分 析阶段 它发 生 在运行 期间 而不是 对象创 建期间 如果存 储过 程在运行 期间引 用了对 象 这 可能 是几 天 之后的 事情 而 被引用的对 象仍 未 创 建 SQL Server 将发出 一个 运行错 误的 信息 因为 存 储过程 通不 过解 析阶 段 这样会 使 Transact-SQL 的开发 更加 容易 因为 在创 建存 储过 程时 可能 有许 多对象 不 存 在 但是 它们将在执行 存储过程的运 行期间创建 在 解析阶 段的运行期间 可 以 阐明查 询 计划(Query plan) Microsoft SQL Server 用表中 的数 据量 表 中出 现的 索 引等信 息来 建立 查询 表 现有 引 用对象的 数据必须在解 析阶段存在 但是因为在这一 阶段并 不实际执行存 储过程 对象 所 以引用 对象 名不 必存 在于对 象 创建 语法 验证 阶段 SQL Server Optimizer 必须有关于 现有(existing) 对象的信 息 才能 阐明 该查 询计 划 并把该计 划放 入 procedure cache(程序高 速缓 存)的内 存 中如图 2-5 所示 然后 Microsoft SQL Server 使用该 查询 计划 执行 存储 过程 对象 图2-5 存储过程和触发器的新延迟名称解析 2.10.4 Alter Procedure 语句 Microsoft SQL Server 有关存 储过 程的 另一 个特 点是 它能 改变 存储 过程 对象 而不 必 从数据库和 syscomments 系统表中 删去 它来 改变 存储过程 对象 改 变 存储过程的能力是非 常重要的 因为存储过 程和触发程序 都带有其他引用 和许可 在删除对象 时会销 毁它们 只改变对象而不是删除它将保留 与该 对象 相关的引用 和许可 下表展示了谁能使用 Alter Procedure 语句 并解 释了 ALTER PROCEDURE 的每 个参数 Alter Procedure 的参数 描述 该参数是用户希望改变的程序名 Procedure Name 该参数用来组合同名的存储过程 它是一个可选参数 Number 此参数是 用来把值 传递给 存储过程 的自变量 在该 自变量名 前使用@ Argument Name 符号 这表明用户在执行时必须把 值传递给存储过程 用户自定义 的 存储程序可以有 0 至 1024 个自变量 指定该参数的数据类型 唯一不能传递的数据类型是 image 数据类型 Data Type 可以为该自变量提供一个缺省值 Default 指定自变量可用于执行存储过程后的返回值 OUTPUT 查询计划不能保存在内存中 以便 下一次执行该存储过程时使用 用 RECOMPILE 户每次要求执行存储过程时都会生成新的查询计划 必须使用 RECOMPILE 参数 如果没有给定重新编译参数 则创建存储过程时 不会采用重新编译设定的缺省值 存储过程用加密格式存储于数据库的 syscomments 系统表中 如果要 ENCRYPTION 使用加密 必须在 alter 中使用该参数 必须明确给定 ENCRYPTION 参数 如果没有给定加密参数 则 不会采用创建存储过程时加密设 定 的缺省值 续表 Alter Procedure 的参数 描述 在创建或改变作为过滤器的存储过程时 可使用 FOR FOR REPLICATION REPLICATION 且只有通过复制才能使用 该参数不能与 WITH RECOMPILE 选项一起使用 单词 AS 指明一个或多个 Transact-SQL 语句 它们组成随后的存储 AS 过程 这些语句是存储过程的实际 Transact-SQL 代码 SQL Statements ALTER PROCEDURE 的用法 Stored Procedure Owner Data Definition Language Administrators DBO ALTER PROCEDURE 语 句 的语法 如下 ALTER PROC EDURE procedure name ;number ({@argument_name } data_type = default OUTPUT ) ,... WITH {RECOMPILE | ENCRYPTION | RECOMPILE, ENCRYPTION} FOR REPLICATION AS Transact-SQL statements ... 2. 11 存储过程 参数 存储过 程最 多可 以有 1024 个参 数 每 一个 参数的声明有下面的 基本 形式 @parameter-name datatype @ Unicode @ $ # _ 参数名 称以 开始 以 后 的字符 可以 是 字母 数字 或者 符号 参 数的名 称不 应该 以@@ 开始 因为 SQL Server 把 这 种 记号用 于一 些内 置的 函数 参数可 以 是列允 许的 任何 数据 类型(参见第 1 章) 如果有 多个 参数 用逗 号将 参数 声明分开 如果当调用存 储过程时没有提供参数 可 以定义一个缺省的输入值 可以为 上一个示 例提供 一个 缺省 值 它 将 选择 折扣率 大于 零的 全部 行 如下 所示 Create Procedure ListCustWithDiscount @MinDiscount Dec(5,3) = 0.001 As select From Customer Where Discount >= @MinDiscount .001 使用 这个定义 只用下 列语句 就可 以 调用这个 存储过 程 这条语句 等价于用 作为 参数调 用该 存储 过程 Execute ListCustWithDiscount 所有参 数都 可以 用作 输入参 数 为了 用一 个参 数作 为 输出参 数 在其 声明 上增 加 Output 关键字 在缺 省值 的后 面 如下示 例 Create Procedure GetCustDiscount @CustId Int, @Discount Dec(5,3) Output As Set @Discount = ( select Discount From Customer Where CustId=@CustId) 注意 在这个 示例中 如何使用一个标量 子查询 在表列中赋一个值 当该存储过程返 回时 输 出参数有其最 后赋予 的值 所有存储过程的 参数都 允许空 也就 是说 可以输入 输出空 值 当调 用有 一个 输 出 参数 的 存储过程时 必 须为该参 数 提供一 个 变 量 在 Execute 语句 上的参 数之 后使 用 Output 关键字 Execute GetCustDiscount 123789, @CustDiscount Output 在 Execute 语句 上 只 要存 储过 程参 数声 明了 一个缺省 值 就 可 以使用 Default 关键字 替代该参数 只要没有提供变量的全部参数都有一 个缺省值 还可以 在参数列 表末尾省 略 这些参 数变 量 注意 因为有 缺省值的参数 主要是 可选参 数 所以可把 它们放在参数 列表的 末尾 这 更便于 在调 用存 储过 程时 省略 相应 的变 量 调用存储过程 时指定参数的另外一种方法 是使用在存储过程定 义中定 义的相应名称 下面的 语句 说明 这种 技术 Execute GetCustDiscount @CustId = 123789, @Discount = @CustDiscount Output 使用参数 名称 正如本 例一样 不要求变量按 照任何 特定的顺序指定 下面的顺序 也 是有效 的 Execute GetCustDiscount @Discount = @CustDiscount Output, @Custid = 123789 Transact-SQL 允许在某 些位 置指 定一 些参 数(以第 一个 变量 开始) 然 后 是另 外一些用参 数名称指 定的其他变量 如果使用这 种参数名称技术 指定一 个变量 那么 也必须 在调用时 使用参 数名 称指 定任 何后面 的 变量 2.11.1 返回 结果 集 存储过 程为 每一 个执 行的 Select 语句 返回 一个 结果 集 这种 Select 语句 没 有 用作一 个 标量 子查 询(也就是说 一个值) 并且不对 选择列表中 的全部列赋 予变 量或者 参 数 所以 例如 下面 的存 储过 程返回 两 个结 果集 Create Procedure ListLowHighDiscCust As select From Customer Where Discount <.01 select From Customer Where Discount >.1 调用这个存储 过程的应用程序可以在返回 的结果集中处理这些行 在 默认情况下 查 询分析 器和 OSQL 实用程序 显示 每一 个返 回的 结果 集 2.11.2 状态 返回 值 存储过 程可 以使 用 Return 语句 结束 执行 并 且 为调用 者 设 置将要返 回的 状态 值 如下 所示 Create Procedure ListCustWithDiscount @MinDiscount Dec(5,3) = 0.001 As If (@MinDiscount >1.0) Return (1) select From Customer Where discount >= @MinDiscount Return (0) 返回值是可选 的 并且可以是任何整数表 达式 为了得到这种状态值 可 在 存储过 程 调用上 写一 个赋 值 Execute @Status = ListCustWithDiscount .1 注意 SQL Server 使用 0 表 示成功 调用 -1 到-99 为系 统调 用错 误( 例如 数 据转换 错 误) 也可 以使 用 1 表示 成 功调用 但是 避免 返回 的状态 值 在-1 到-99 范围内 2. 12 存储过程 编程 技 巧 可以在存储过 程中的任何地方增加注释 可以是在两个短横之 后 直 到当前行末尾 或者分 散到 多行 的/ 和/ 分隔符之 间 --This is a comment / And so is this multiline comment/ 2.12.1 无限 定的 对象 名 称 在存储过程中 无限定的对象名称暗示由 存储过程所有者名称限定 对于那些可能由 其他用 户拥 有的 对象 一 定要明 确地 限定 2.12.2 本地 变量 声明 所有在存储过 程中的本地变 量都必 须在使 用之前声明 并且必须在存 储过程中有唯一 的名称 变量 的适 用范 围 是从其 声明 开始 到 Create procedure 语句 的 结 束的所 有代 码 注意 虽然 在存 储过 程中可 以 使用 Begin 和 End 语句 来创建 嵌套 的块 但是 这对变 量 的范围 没有 影响 变量声 明的 格式 类似 于参数 的 形式 Declare @variable-name datatype变量名 称的 语法 规则 与参数相 同 并且 允许使 用除 了 Text NText 和 Image 外的数据 类型 不能 为变 量声 明一个 初 始值 下面 是一 个声 明 整数变 量的 示例 Declare @RowCnt int 2.12.3 赋值 语句 可以使 用 Set 语句 对一 个变 量或 者参 数赋 值 Set @RowCnt = 1 虽然可以在存 储过程中为输入参数和输出 参数赋值 但是改变一个输 入参数的值对调 用者的 数据 没有 影响 还可以 对在 单行 Select 语 句的选 项表 中的 全部 元素 增 加一个 赋值 表达 式 如 下所示 Create Procedure GetCustnameAndDiscount @CustId Int, @Name VarChar(30) Output, @Discount Dec(5,3) Output As select @Name =Name, @Discount = Discount From Customer Where CustId = @CustId 这种多行赋值 的形式可以避免几乎相等查 询的多 次执行 从 而 提高性 能 然而 一定 要小心 这只 能用 于那 些 保证 只返回 一行 的查 询 2.12.4 语句 块 无论在 存储 过程 的任 何地方允许使 用 一 个单 独的 SQL 语句 都 可 以使用语 句块 语句 块由 Begin 和 End 语 句分隔 开 所以 例如 下面 的 三个语 句块 将作 为一 个复 合 语句对 待 Begin Set @DeletedRowCnt = 0 Delete From Customer Where CustId = @CustId Set @DeletedRowCnt = @@RowCount end 对于编 码 If…Else 语句 和 为编码 While 循环体 语 句 块是非 常重 要的 2.12.5 显示 消息 Print 语 句允许返 回任何字符 串表达 式 包括 文字 字 符参 数 和变量 以及传 给调 用者 消息句 柄的 函数 Print Cast(@CustCount As VarChar(10)) + `rows'' 将要返 回的 字符 串最 多可以有 1024 个字 符 注意 在默 认的 情况 下 查询分 析器 和 OSQL 消息 句柄 显示 由 Print 语 句返回 的 任何消 息 ODBC API 在 诊断 记 录中返回消 息可 以由 应用 程 序检 索的文本2.12.6 条件 执行 可以在 存储 过程 中使 用 If…Else 语句 其方法 类似 于 在 HLL 程序中 使用 这种控制结构 If 关键字后面 是一 个条 件 该条件 可 判断是 真 假或 者未 知 就像 在本 章前 面“条 件 和子查 询”一节 中描 述的 那样 当条 件为 真时 执 行该 条件后 面的 语句 当该 条件 为假 或者 未知 时 执行在 Else 关键 字 如果指 定 后面的 语句 下面 是一 个有 趣的 示例 强调 写 存储过 程时 考虑空 值的 重要 性 Create Procedure TestNull As Declare @x int Set @x = Null If ((@x =0) or (@x <>0)) Print `True'' Else Print `Unknown'' 由于 ANSI 标准 行为 该存 储过 程 将打印 Unknown 这是由于 SQL 的三 值 逻辑规 则 (回忆 一下 任 何与 空 值比较 的结 果都 是未 知 两 个未知 值的“ 或”结 果还是 未知 ) 可以嵌 套 If…Else 语句 控 制结构 并 可以在一 行 上编写多个 SQL 语句 所以表 达多 步 条件的 清晰 方式 是使 用 Else If 结构 如下 所示 If ( @CustCount =0) Begin Print `No rows'' End Else If (@CustCount=1) Begin Print `1 row'' End Else Begin Print Cast (@CustCount As VarChar(10)) +`rows'' End 几次编 程实 践可 以避 免这种 问 题 并且 使代 码易 于阅读 使用 括号 括住 If 语 句 上的条 件 并且 使用 嵌套 的括 号 明确地 说明 复合 布尔 表达 式 的判断 顺序 另外 对 于一个 If 或者 Else 分支 中的 所有 代码 使用 Begin 和 End 块 即使 是在 该块 中只 有一 条语 句也是如 此 如 果没有 一直 使用 Begin…End 块 那 么很 容易 编码 一 些看起 来像 多条 语句 的 Else 分支 但 是 事实 上不 是 如 下示例 If (@Custcount =0) Print `No rows'' Else Delete From Customer Where Discount Is Null Print Cast (@CustCount As VarChar(10)) +`rows'' 如果没 有为 Else 分支 定义一 个块 那么 最后 一条 语句 总是 要执 行 因为 它不 是 If…Else 语句的 组成 部分 2.12.7 While 循环 可以在 存储 过程 中使 用 While 语句 定义 一个 执行 循环 在 While 关 键字之 后 编码 一 个控制循环执行的条件 在 While 语句体的每一次执行之前( 简 单语句 或者 块语 句) 都要测试条 件 如果条件 为 真 执行 循环 体 如果 条件 为 假或者 未 知 控 制 流程转到 While 语 句后面 的语 句 下面 是一个 While 循环的 简单 示例 Create Procedure TestWhile As Declare @Count int Declare @Limit int Set @Count =0 Set @Limit=10 While (@Count<@Limit) Begin Print Cast (@Count As VarChar(10))+`iteration'' Set @Count=@Count+1 End 在 While 循环 内 可 以使用 Continue 和 Break 语句 修 改执行 流程 Continue 条件将控 制移到 While 循环 的开 始(条件测 试) Break 语句 立即 退出 循环 2.12.8 GoTo 语句 和标 签 SQL 有一个 GoTo 语 句和语 句标 签 但 不 推荐使 用 GoTo 编 程技术 为了 编码 一个 标 签 在标 识符 后面 加一 个 冒号 在 GoTo 语句上写 的 标签没 有冒 号 如下 所示 SkipNextStep: … GoTo SkipNextStep 2.12.9 WaitFor 语句 为了 暂停执行 一个指定 的 时 间间 隔 或 者到 一 个 指定 的时间 可以 使用 WaitFor 语句 该语句 有两 种形 式 如下所 示 WaitFor Delay `00:10:30'' 或者 WaitFor Time `14:30'' 第一个示例使用 Delay 关键 字指定时间间隔 为 10 分钟 30 秒 可以 使用任何有效 的 datetime 格式 但没 有日 期部 分 所以最 长延 迟时 间是 24 小时 第 二个示例使 用 Time 关 键字等 到下 午 2:30 2. 13 游 标 SQL 游标 提 供 了 一种循环 结 果 集 分别读 每一行的方式 在 存 储过程中 游标 提供 了 一种有 用 的 技术 它可 以 实现不 易用 Select 语句 语 法轻 易地表 达的 复 杂 计 算 可 修改的 游 标还提供 了一种手段 用于在结果集 中根据复杂的条 件选择 修改或者删除行 为 了 使用 一 个游标 有五 个基 本步 骤 1). 声明 游标 2). 打开 游标 3). 从游 标中 重复 提取 读取 行 有选 择地 修改 或者 删除 所取 的行 4). 关闭 游标 5). 当不 再需 要游 标时 释 放游标下面是 一个 使用 游标 的示例 列出 客户 ID 和名称 Create Procedure ListCust As Declare @CustId Int Declare @CustName VarChar(30) Declare CustCursor Cursor For Select CustId,Name From Customer Order By CustId For Read Only Open CustCursor While (0=0) Begin Fetch Next From CustCursor Into @CustId,@CustName If (@@Fetch_Status <> 0) Break Print Cast (@CustId As VarChar(10)) + +@CustName End Close CustCursor Deallocate CustCursor 游标名 称在 Declare 和 Cursor 关键字 之间 并且 是在以 后 的语 句中 用于引用该游标 的 名称 游标 声明 的核 心是一 个 Select 语句 它 可以 包 括前面 讨论 的除 了 Compute 子句以 外 的所有 子句 特别 是 Select 语句 可以 用 Order By 子句排列 所取 出的 行的 顺序 重要 的是 Select 语句 还可 以在 Where 子句中 使用 参数 和变 量(以 及允许标 量值 的 其他 位置) 因此 可 以定义 一个 游标 以便 结 果集依 赖执 行时 间值 由 Select 语 句定义的 结果 集在 执 行 Declare Cursor 语句 时并 不实 际生 成 而是 当对 该游 标执 行 Open 语句 时 生成该结果集 这样就 允许在打 开 或重新打 开 游标 确定取出包括在结 果集中 的那些行之前 可 以 改变用 于 游标的 Where 子 句的任 何变量值 第一个 Fetch Next 语句 阅读 第一 个选 择的 行 并且 把选择列表中的 每一个值 放到 在 Into 子句的 相应 位置 指定 的变量 或 者参 数中 后面 的 Fetch Next 语 句阅读下 一行 直到 读完 结 果集中 的全 部行 为止 注意 虽然 Into 子句 是可 选的 但是 Fetch 不 能 将值 检 索到变 量或 者参 数中 因而 没 有实际 用途 在 Fetch 之后 SQL Server 设置@@Fetch_Status 内置函数 返 回 0( 成功) -1( 没有未 读 的行)或者-2( 行在 Fetch 操作之 前已 经被 删除) 可以测 试@@Fetch_Status 来 控制循环 如 本示例 所示 应留 意@@Fetch_Status 为由 连接 使用 的任 何游 标返回 最 近的 Fetch 的状态 所以如 果需 要 在 过程 中的 后 面 使用 该 状 态值 那 么 应该 把@@Fetch_Status 状 态 值赋一个 变 量 一旦完成了提 取行 就应该关闭游标 可 以重新打开一个游标 重新 从开始处理同样 的结果集 或者处理一个 不同的结果集 这取决于在游 标声明 中使用的本地 变量的 新值 当 不再需 要一 个游 标时 应 该释放 该 游标来 释放 系统 资 源2.13.1 可滚 动的 游标 在默认 情况 下 Fetch 操 作 的 唯一类型 是 Fetch Next 对 于 更加灵活的操 作 可以 在 Cursor 关键字 之前 增加 Scroll 关键 字 Declare CustCursor Scroll Cursor For select CustId,Name From Customer Order By CustId For Read Only 可滚动 的游 标允 许各 种 Fetch 操作 使用 如表 2-6 所 示的关 键字 表 2-6 Fetch 语句的定位关键字 关键字 定位游标 并读取行 Next 当前行的下一行 Prior 当前行的前一行 First 第一行 Last 最后一行 Absolute n n>0 定位到从开始的第 n 行 n=0 没有返回的行 n<0, 定位到末尾前的第 n 行 Relative n n<-1 定位到当前行之前的第 n 行 n=-1 同 Prior 关键字相同 n=0 定位到当前行( 重读) n=1 同 Next 关键字相同 N>1 定位到当前行之后的第 n 行 2.13.2 不敏 感的 游标 在默认情况下 大多数游标是动态的 也就是说 当其他进程修改包 括在游标结果集 中的 行时 后面的 Fetch 语句将得到新值 如果 希望 行集 固定在打 开游标的 时刻 在游标 名称后 面增 加 Insensitive 关键字 这将使 SQL Server 制作 结果 集中 行的 临时 拷贝 并且 把 这个临 时拷 贝中 的行 返回到 后 面的 Fetch 语句 即使 没有 Insensitive 关键字 当 某个游 标的 Select 语句 有 Distinct Union Group By 或者 Having 关键 字时 SQL Server 对 待 该游标 好 像指定 Insensitive 关键字一 样 2.13.3 可修 改的 游标 如果希 望修 改或 者删 除通过 游 标取 出的 行 那么 可以用 For Update 子 句而不 是 For Read Only 子句声明 该游 标 (不 能 在 不敏感的游 标上 使用 For Update 子句 如上所 述) For Update 子句还 允许 一个 可选 的列项 这是 限制 Update 语句 的 Set 子 句为列出 的 列的结果 下面 的示例 说明 一个 可修 改游标 一个 取样 Fetch 与只 读游 标上 使用 的 相同 以 及 在当前行上 Update 和 Delete 语句 的形 式 Declare @CustId Int Declare @Discount Dec(5,3)Declare CustCursor Cursor For Select CustId,Discount From Customer Order By CustId For Update of Discount Open CustCursor … Fetch Next From CustCursor Into @CustId,@Discount If (@@Fetch_Status = 0) Begin If (@Discount=0.0) Begin Delete From Customer Where Current of CustCursor End Else If (@Discount>.5) Begin Update Customer Set Discount =.5 Where Current of CustCursor End End … Close CustCursor Deallocate CustCursor 注意 两个 Update 和 Delete 语句 的 Where 子句 如何 使用 Current of cursor-name 短语 指定将 要在 基表 中修 改或者 删 除的 行 这两 种语 句称为定位修改和定 位删 除 2.13.4 其它 游标 选项 SQL Server 有 Declare Cursor 语句的 扩展 形式 提供了其它选项 如表 2-7 所示 为了 使用这 些选 项 不能 在 Cursor 关键 字之 前使 用 Insensitive 或者 Scroll 关键 字 相反 可以 在 Cursor 关键 字之 后 编码 一个 或者 多个 在表 2-7 第一 列 中 的关键 字 如果 编码多 个 关键 字 按照 表 2-7 中行 的顺序 编 码 使用 Declare Cursor 语句 的扩 展形 式 还可 以编 码一个 For Update 子句 限制要修改 的列 不能 使用 For Read Only 子句 因为 该选 项由 Read_Only 关键字提供 了 注意 Declare Cursor 语 句 的非扩 展形 式是 ANSI 标准形 式 除非 需要 一个 只能由 扩 展 形式提 供的 选项 否则 建 议使用非护 展形 式 表 2-7 扩展的游标选项 扩展的游标关键字 效果 Local 指定游标范围是定义游标的存储过程 Global 指定游标的范围 Local 是本次连接 并且可以由任何存储过程使用 当这两个选项都没有指 Global 定时 缺省值是 Global 除非已经使用 sp_dboption 系统存 储过 程 数 据库的选项“default to local cursor option” 设置为打开 ( 建议使用本地 游标 避免在多个过程中无意中访问同一个游标而引起的边缘效 应 ) 续表 扩展的游标关键字 效果 Forward_Only 指定唯一可用的 Fetch 选项是 Fetch Next Scroll 指定可 Forward_Only Scroll 以使用列在表 2-6 中的 Fetch 选项 如果指定 Dynamic Static 或者 Keyset 选项 那么缺省值是 Scroll, 否则是 Forward_Only Dynamic 指定结果集是动态的 如上所述 FastForward 指定游标是 Dynamic FastForward Forward_Only 和 Read_Only 并且有性能优化 Static 指定游标是不 Static Keyset 敏感的 如上所述 Keyset 指定行集 和它们在结果集中的顺序当游 标 打开时不会改变 然而 当提取到时任何对非主键列的变化都可以反 映出来 Read_Only 等价于前面讨论的 For Read_Only 子句 Scroll_Locks 指定 Read_Only Scroll_Locks 游标是可修改的 并且行锁保持在取出的每一行上 因此保证该行可 Optimistic 以修改或 者删 除 Optimistic 指定游标是可修改的 但是行锁不保持在取出的 每一行上 相反 SQL Server 基于自从该行取出后 另一个进程是否 修改该行 来确定是否允许修改 如果游标隐含地从一种类型转变到另一种类型 指定省略警告消息 Type_Warning 2.13.5 游标 参数 和变 量 除了标量参数 和本地变量之外 如前所述 还可以声明游标的输出参 数和变量 如下 所示 Crate Procedure OpenGoodCust @GoodCustCursor Cursor Varying Output, @MinDiscount Dec(5,3)=.2 As Set @GoodCustCursor=Cursor For Select Custid,Name From Customer Where Discount> =@MinDiscount Order By CustId For Read Only Open @GoodCustCursor 为了声 明一 个游 标 参 数 在 参 数名称后 面加 上 Cursor Varying Output( 这三 个 关键字都 要求) 然后 使用 Set 语 句设置参 数( 或游 标 变量) 如 这 个示例所 示 可以使用 下 面 的语 句调用 这个 过程 并使 用返回 的 游标 Declare @CustCursor Cursor Declare @CustId Int Declare @CustName VarChar(30) Execute OpenGoodCust @CustCursor Output, .3 While (0=0) Begin Fetch Next From @CustCursor Into @CustId, @CustName If (@@Fetch_ Status <> 0) Break Print Cast(@CustId As VarChar(10))+ + @CustName End Close @CustCursor Deallocate @CustCursor 注意 这个游 标只能传回到调用程序 不 能做为一个参数提供给过程 调用 游标参数 的一种用 法是在被调用 的过程中使用 逻辑来 选择要打 开和传 回的游标 因此简化 调用过 程 的进程 2. 14 存储过程 错误 处 理 作为一 个老 生常 谈的 话题 本节 讨论 如何 进行 存储 过 程的错 误处 理 2.14.1 调用 RaisError 语句 如果存储过程碰到一个问题 希望向调用程序发送一个错误消息 那么可以使用 RaisError 语句( 注意 不 要 拼错这 个关 键字) 下面是一个示 例 用折扣值的替换参数发 送 一个 ad hoc 消息 Create Procedure ListCustWithStatus @Starus VarChar(10) As If (( @Status < > ''Active'') And ( @Status < > ''Inactive'') And ( @Status < > ''Deleted'')Begin RaisError(''Invalid status: %s. '' , 16, 1,@Status) Rerurn End Select From Customer Where Status= @Status 发送 ad hoc 消息 的 RaisError 语句 的基 本语 法是 RaisError(message,severity,state,substitution1,… 消息最 多可 以有 8000 个字 符 最多 可以 包括 20 个替 换参数 每一 个由 百分 号和一 种 格式说 明表 示 格式 说明可 以 是%s 表示字 符串 %d 表 示整数 不支 持浮 点数 和 小数替 换 参数 格式 说明 的附 加信息 参见 Transact-SQL 说明书的 RaisError 语句 严重程 度值 是 0 到 25 之间 的整 数值 值 20 到 25 终 止当前的连 接 状态 值是 1 到 127 之间的 一个 整数 并且 有 应用程 序指 定的 含义 对于字符串中 的每一个替换参数 必须提 供一个文字 参数或者变量 这个值将与发 送到调 用程 序的 消息 串合并 2.14.2 调用 sp_addmessage 系统存 储过 程 使用 sp_addmessage 系统 存储 过程 调用 可 以 不用消 息串 而 用 一个消息 ID( 在 50001 到 2147483647 之间 的一 个整数)来确认 一个 已经 增加 到 sysmessages 表中的消 息串 sp_addmessage 60001,16, ''Invalid starus: %s.'' 在这个 示例 中 第一 个参数 是 消息 ID 第二 个是 严重 程度 第三 个是消息串 下面是 一个如 何使 用这 种预 定义消 息 的示 例 RaisError(60001, 16, 1,@Status) 为了替 代一 个 sysmessages 表中已 有的 消息 使用 如 下的语 句 sp_addmessage 60001, 16, ''Invalid starus: %S.''Null,False,Replace 为了删 除一 个 sysmessages 表中已 有的 消息 使用 如 下的语 句 sp_dropmessage 60001 在默认 情况 下 @@Error 内置 函数 为严 重程 度低 于 10( 包括 10)的 消息设置 为 0 为严 重超过 10 的消 息设 置错 误号 Ad hoc 消息 的隐 含消 息 ID 为 50000 可 以增加 With SetError 选项来 指定@@Error 设置 为消 息 ID 而不 考虑 消 息 的严重 程度 RaisError(60001,16, starus)With SetError 可以在 With 子句 上指 定的其 他 选项 是 Log 在服 务器 的错 误日 志 中和事 件日 志中 登 录错 误 对于 严重 程度 超过 18 的消 息要使 用求 该选 项 Nowait 立即 发送 消息 给 客户 调用另 一个 发 送 错误 消息 的 存 储过 程的 一 个存 储 过 程可 以 通过测 试@@Error 函数 检 查最近 的错 误 if (@@Error>0)Begin …handle error End 当测试@@Error 时要 小心 因为它在 每个操作 中重新设 置 下面 的 代 码提 供 了一 种保 护和测 试@@Error 值的 安全 方式 Declare @ProcError int … Execute MyProc Set @ProcError=@@Error … If(@ProcError>0)Begin … handle error End 2. 15 触 发 器 触发器 是一 种特 殊的 存储过 程 当 Insert Update 或者 Delete 语 句修改 表中 一个 或者 多个行 时 执行 触发 器 因为 SQL Server 对特 定表 上的 每一 个指 定操 作调用 一个 触发 器 所以可 以使 用触 发器 扩展 SQL Sever 的内 置完 整性 和数 据操 纵功 能 注意 不像 Delete 语句 Trancate Table 语句 不激 活触 发器 Write Text 语 句也不 激活 触发器 在 SQL Sever 2000 中支 持两 种类型 的 触发 器 前触 发器(Instead Of Trigger)和 后 触发器 (After Trigger) 前 触发 器就是在 语句 执行之前 激活 触 发器 而后 触发 器就是在 语 句执行 之后激活 触发 器 可以 通过 FOR 子句来 选择 使用 何种 触发 器 下面 首先 以 SQL Sever 2000 默认的 后触 发器 为例 介 绍触发 器的 使用 方法 通过使 用如 下的 Greate Trigger 语句 创建 一个 触发 器 create Trigger TrackCustomerUpdates On AppDta.dbo.Customer For Update As Insert Into AppDta.dbo.CustUpdLog (CustId, Action, UpdUser, UpdDateTime) Select CustId, ‘Update’, Current_User, Current_TimeStamp From inserted 这个示 例说 明在 触发 器定义 中 的关 键元 素 触发 器名称 在 Create Trigger 关键 字之 后 On 子 句指定一 个 基 表 该 触发 器 与此表相 关联 ( 不能在视图上 创 建 后 触发器) For 子句 指定触 发器 激活 的动 作 可以使 用一 个或 者多 个 Insert Update 或者 Delete 关键字 然后 是 As 关键 字, 可以 编码 一个 前面 讲过 的存 储过 程 在这个 示例 中 触发 器由 一个 Insert 语 句构成 它向 一个 跟踪 对 Customer 表修 改的 表中 增加行 一旦创 建了 这个 触发 器 对每一 个在 Customer 表上执行 的 Update 语句 SQL Server 自动 激活 触发 器的 存储 过 程 在 触发 器的存储 过程 中 可以 使用 inserted 标识符引用一个 临时的 驻留 内存 的表 它包 括 了受影 响的表 的 全 部新行 注意 不需 要在 触发 器中创 建 一个 inserted 表 SQL Server 管理这些由触发器自 动使 用的驻 留内 存表 在这个 示例 中 从 Customer 表的更 新行 中的 CustId 值是 插入 到用 户定 义的 CustUpdLog 表的新 行的 一部 分 在 CustUpdLog 表中 的另 外三个列 保 存动 作类 型( 例如 Update) 执行 该动作 的用 户和 时戳 为 了也跟 踪 Insert 和 Delete 操作 除 了前面那 个语 句 外 还可以使 用下面 两个 Create Trigger 语句 Create Trigger TrackCustomerInserts On AppDta.dbo.Customer Instead Of Insert As Insert Into AppDta.dbo.CustUpdLog ( CustId, Action, UpdUser, UpdDateTime) Select CustId, ''Insert'' Current_User, Current_TimeStamp From inserted Create Trigger TrackCustomerDeletes On AppDta.dbo.Customer For Delete As Insert Into AppDta.dbo.CustUpdLog ( Custid, Action, UpdUser, UpdDateTime) Select CustId, ''Delete'' Current_User, Current_TimeStamp From deleted 第二个 触发 器使 用 deleted 标识符 它引 用一 个临 时的驻 留 内存 表 该临 时表 包含 触发 器影响 到的 表的 全部 旧行 注意 由 inserted 和 delete 关键 字 引用 的临时 表与 定 义触发 器的 基表 有同 样的列 结 构 当为每一种操 作创建一个触发器时 可以 为所 有 三种 操 作创建 一 个触 发器 并且使用 相应的编程技术处理 每 一种操 作 下面的示例在 For 子句中列出了三种语句类型 并且使 用条件 语句 将相 应的 跟踪值 插 入到 CustUpdLog 表中 Create Trigger TrackCustomerUpdates On AppDta.dbo.Customer For Insert,Update,Delete As Declare @InsertedCount Int Declare @DeletedCount Int Set @InsertedCount=(Select Count()From inserted) Set @DeletedCount=(Select Count()From deleted) If ( @InsertedCount>0)Begin Insert Into AppDta.dbo.CustUpdLog ( CustID, Action, UpdUser, UpdDateTime) Select CustId, Case When( @DeletedCount>0)Then ''Update'' Else ''Insert'' End, Current_User, Current_TimeStamp From inserted End Else If(@DeletedCount>0)Begin Insert Into AppDta.dbo.CustUpdLog ( CustId, Action, UpdUser, UpdDateTime) select CustId, ''Delete'', Current_User, Current_TimeStamp From deleted End 正如本 例所 示 无论 何时 Insert 或者 Update 语句 影响 一个 或者 多行 时 inserted 临时 表都有 记录 行 无论 何时 Delete 或者 Update 语句 影 响一个 或者 多行 时 deleted 临时表都 有记录 行 对于 一个 Update 语句 deleted 临时 表有 旧行 inserted 临 时表有新 行 这个 示 例还反 映了 触发 器的 另一个 重 要方 面 对于 某个 表 的 Update 或者 Delete 操作 即使该语 句没有 影响 到行 也激 活 触发器 ( 也就 是说 没有 满足 Where 子 句的行) 触发器的 存储 过 程应该 预测 这种 可能 性 2.15.1 检查 指定 列的 变 化 对于 Insert 或者 Update 触发 器 可以 在 As 关键 字后 使用 一个 If Update(colunm-name) 子句 测试 该 Insert 或者 Update 语句 是否 明确 地列 出一 个或 者多 个特 殊的 列 下面的 示例 说明如 何使 用这 种测 试 Create Trigger TrackDiscountUpdates On AppDta.dbo.Customer For Update As If Update(Discount) Insert Into AppDta.dbo.DiscountLog ( CustId, Action, Discount, UpdUser, UpdDateTime) select CustId, ''Update'', Discount, Current_User, Current_TimeStamp From inserted 可以在 If 子句 中使 用 And 和 Or 逻辑 运算 符来 合并 测 试 如下 示 例 Create Trigger TrackCustomerUpdates On AppDta.dbo.Customer For Update As If Update(Name) And Update(Discount)… Create Trigger TrackCustomerUpdates On AppDta.dbo.Customer For Update As If Update(Name)Or Update(Discount)… 对于 Update 语句 如果 该 列列在 Set 表达 式中 那么 Update(colunm-name) 测试为真 SQLServer 不检查新 值是否 与 旧值 不同 对于 Insert 语句 如果 该 Insert 语句没有 列 项( 意 思是全部列) 或者如果该列明确地列在列项中 那么 Update(column-name) 测试为真 Update(column-name) 测试的规则 可以 应用 到是 否使 用文 字 表达 式或 者 NULL 或者 Default 关键字 赋一 个新 值或 者改变 值 对指定 列的 另外 一种 测试是 使 用 Colamn_Updated 函数 它返 回一 个位 图 表示在 SQL Insert 或者 Update 语句 中指 定哪 些列 表 中的第 一列 由位 图的 最低 顺序 位置 表示 第二 列 由位图 中较 高位 置表 示 等等 下面 的语 句测 试是 否 指定第 一 第三 或者 第四 列 Create Trigger TrackCustomerUpdates On AppDta.dbo.Customer For Update As If(Colunms_Updated()&13)>0… 在这个 示例 中 标记 值 13 是代 表 1 4 和 8 列的三个 位图值 之和 可以 使 用 任何按位 操作来 标号 Columns_Updated 函数值 然 后 使用 逻辑 比 较运算来 测 试 这 个标号值 我们 建 议使用这种位图技术 因 为 它 依赖于表中 列的 相对 位 置 Update(column_name) 测试是一种 较好的 技术 因为 它不 依 赖 于列的位 置 2.15.2 修改 和删 除触 发 器 为了改变一个触发器的 定义 可 以删 除和重新创建该 触发器 或者使用 Alter Trigger 语句 为了 删除 一个 触发器 使用 Drop Trigger 语句 如下 所示 Drop Trigger TrackCustomerUpdates 除了第 一个 关键 字之 外 Alter Trigger 语句有 与 Create Trigger 语 句相同的 结构 2.15.3 使用 触发 器 不仅 可以为一 个表创建 多 个 触发 器 而且 还可以为 一 个 表的 同一个 SQL 语句( 例如 Update 语句) 创建 多个 后触发 器 不能 为同 一个 SQL 语 句 创 建多个 前触 发器 每 一个新的 Create Trigger 语句 增加 触发 器到 那些 指定 表和 语句已有 的 触 发器中 对 于所创建 的多 个触 发器 可以用系 统存储过程 sp_settriggerorder 来指定第一个 被激活的触发 器和最后一个被 激活的触 发器 而对于 其他的触发器 则不能指定其激 活顺序 只能由系统 决定 这种触发 器的特征 不会引起任何 特殊的问题 因为总是可以实 现各种 动作作为正常 的存储 过程 并 且按照 要求 的顺 序从 一个触 发 器中 调用它们 注意 虽在 7.0 及以 前的版本 中是不能 指定触发 器的激活 的顺序的 包括第 一个和最 后一个 触发器总是创 建在与其相关的表所在的同 一个数据库中 并且 在该 数 据库中 每一个 触发器 必须 有唯 一的 名称 可以 使用 所有 者名 称限 定 触发器 名称 例如 dbo.Track Customer- Updates 它应 该与 表的 所 有者相 同 注意 虽 然一个触发器 总是与同 一个数据库中 的一个 基表相关联 但 是触发器可以 引 用 其 他数 据库 中的 对象 这种功能提供了一种方法 在不同数据库的两个表之 间实现 参考 完整 性而 Create Table 外键支 持不 允许 这 么做 尽管 触发 器是 一 种 存储 过 程 但是不 能使 用 Execute 语 句调用 它 如 果 有希 望共 享 触 发器和 正常 的存 储过 程的编 码 那么 只 需 把共享代 码 放在存 储过 程中 从触 发器中 调 用它 如果一个触发 器修改一个表 那么这些修 改可能会激活另一个触发器 或者本身 在默 认情况 下 SQL Sever 允许这种 嵌套 的触 发器 调用 深度 为 32 层 虽然我 们建 议 允许嵌 套的 和叠代的 触发器 但是 可以使用系统 存储过程禁止这 么做 下面 的 语 句 在 指定 的 数 据 库 上 防止叠 代触 发器 sp_dboption AppDta,`recursive triggers'',`false'' 为了在 所有 数据 库中 防止嵌 套 触发 器调 用(包括 叠代 调 用) 可以使用下 面的 语 句 sp_configure `nested triggers'',0 2.15.4 显示 有关 触发 器 的信息 有三个 系统 存储 过程 可 以显示 有关 触发 器的 信息 sp_help trigger_name 显示 触发 器的 所有 者和 创建 时 间 sp_helptext trigger-name 显 示触发 器的 源代 码 sp_depends trigger-name 显示 该触 发器 参考 的对 象清单 如果丢 失了 用来 创建 触发器 的 源代 码 那么 sp_helptext 系 统存储过 程非 常有 用 2.15.5 前触 发器 前面以 后触 发 器 为例 介绍 了 触 发器 的 基 本内 容 下 面再 介 绍一下前 触发 器 的 不同之 处 要创建 一个 前触 发器 必须 用 Instead Of 显式 声明 如下面 的例 子 create Trigger TrackCustomerUpdates On AppDta.dbo.Customer Instead Of Update As Insert Into AppDta.dbo.CustUpdLog (CustId, Action, UpdUser, UpdDateTime) Select CustId, ‘Update’, Current_User, Current_TimeStamp From inserted与后触发器不 同的 是 前 触发器既可 以在 表又可以在视图上 创建 但一条语句 只能创 建一个 前触 发器 因此 前 触发器 不存 在激 活顺 序问 题 2. 16 触发器编 程技巧 一般地 触 发器 编程 类似于 存储过 程的 编程 然而 对 于触发 器编 程 还要 考虑几点 例如 不能 在触 发器 中使用 下 面这 些语 句 Alter Database Create Trigger Drop View Alter Procedure Create View Grant Alter Table Deny Load Database Alter Trigger Disk Init Load Log Alter View Disk Resize Reconfigure Create Database Drop Database Restore Database Create Default Drop Default Restore Log Create Index Drop Index Revoke Create Procedure Drop Procedure Truncate Table Create Rule Drop Rule Update Statistics Create Schema Drop Table Create Table Drop Trigger 2.16.1. 改变 触发 程序 语 句 现在用 户能 改变 触发 程序对 象 而不 必从 数据 库和 syscommands 系统 表中 删除该 对 象 这是非常 重要的 因为 触发程序携带 了在删除对象时 被销毁 的其他引用和 许可 改变该 对 象而不 是删 除它 可以 保 留与该 对象 相关 的引 用和 许 可 如果 使用 WITH APPEND 选项 则对每 个 Update Insert 和 Delete 而言 可存 在多 个 触发程 序 这意 味着 对每 个 Update Insert 和 Delete 而言 可激 活多 个触 发程 序 WITH APPEND 的 功能存在 于由 sp_dbcmptlevel 设 置的 7 级兼 容性 Alter Trigger 的用 法 Table Owner Data Definition Language Administrators DBO 其语法 如下 ALTER TRIGGER trigger_name ON table WITH ENCRYPTION {FOR { , INSERT , UPDATE , DELETE } NOT FOR REPLICATION ASsql_statement ...n } {FOR { , INSERT , UPDATE } NOT FOR REPLICATION AS IF UPDATE (column) {AND | OR} UPDATE (column) ,...n sql_statement ...n } 这是如 何用 Alter Trigger 语句 改变 插入 触发 程序 的实 例 ALTER TRIGGER mytrigger ON tablename FOR INSERT AS RAISERROR ( mytrigger error , 1, 2) 下表描 述了 Alter Trigger 的自 变量 Alter Trigger 参数 描述 要改变的触发程序名 TriggerName 触发程序将执行的表名 TableNa me 在 syscomments 系统表中加密编码关键词 WITH ENCRYPTION { , INSERT , 表示哪个语句将激活该触发程序 UPDATE , DELETE , INSERT , UPDATE } 如果与复制相关的 sqlrepl 注册 ID 修 改了表格 则不能激活 NOT FOR REPLICATION 该触发程序 下一个是 Transact-SQL 语句 AS Transact-SQL 语句 是触发程序的 Transact-SQL 语句 根据对指定的列是 INSERT 还是 UPDATE 来提供 IF 逻辑 IF UPDATE(ColumnName) 检查 INSERT 或 UPDATE 操作的列名 ColumnName 2.16.2 使触 发程 序能 递 归调用 它本 身 sp_dboption 如果设 置了 递 归触发 程序 选项 并且 更新 了触 发程 序内 部的 表格 该触 发 程序就 可以 递归 调用 自身 下面是 一个 实例 sp_dboption mydb, recursive triggers , true 在这个 实例 中 如 果更 新 了触发 程序 内部 的表 就把 recursive trigger 数据库 选项设 置 为真 而且 允许 该触 发程序 递 归调 用其 自身 2.16.3 多个 触发 程序 对 应一个 触发 动作 如果指 明了 WITH APPEND 或把 sp_dbcmptlevel 的兼容性 等级 设置 为 70 就有多个 触 发程序 对应 于数 据库 修改操 作 Update Insert 和 Delete触发器不应该 将结果集返回给调用程序 这就意味着不能在触发器中 编码自由格式的 Select 语句 最好 使用 标量 子查 询为 变量 赋值 因为 这种语句不创建结果集 还可在触发 器的开 始编 码 Set NoCount ON 语句 防止 SQL Sever 发送消 息 说明在存 储 过程 中 每一 条 语句影 响了 多少 行 注意 如果 在触 发器 中(或 者在正 常的 存储 过程 中) 使用 Set 语句 那么 当触 发器 完成 执 行时 Set 选项 值恢 复到 其 原来的 值 触发器的其中 一个作用是对超出主键 外 键和检查约束范围的检查提 供附加的完整性 检查 在检 查了 触发 器中的 条 件之 后 可以 通过 一条 Rollback 语句 取 消 整个事 务 下面 的示例 说明 如何 编码 一个触 发 器 确保 为一 个已 经同意 提 供折 扣的 客户 赋于 一个折 扣 Create Trigger CheckDisckDiscountUpdates On AppDta.dbo.Customer For Insert,Update As --Check only if Discount column is explicitly set If Update(Discount) Begin Declare @User VarChar(256) Set NoCount On --Check only if an actual discount is being assigned If (Exists ( select From inserted Where Discount >0)) Begin --Make sure this user has approval to assign discounts If ( Not Exists ( select From Employee Where UserName=Current_User And DiscountAuthority = `Y'')) Begin --Send message and abort the transaction Set @User=Current_User RaisError ( `User %s not approved to assign discounts.'', 16,1,@User) Rollback End End End 对于每 一个 Insert Update 或者 Delete 语句 触发 器 调用一 次 并且 只有 在这 条语 句 满足全部的 主 键 外键和检查 约束时 才激 活触 发器 在激 活触 发器 时 SQL 语句 的结 果 反映到 目标 数据 库表 中(和 驻留内 存的 inserted 和 deleted 表中) 但是还没有 提交 在调用触 发器之前 一条 语句必须满足所 有的约束 这意味着不能使用触发 修改数 据 库 来满 足某 个约 束 例如 如果 有 Sale 行匹 配一 个客 户的 ID 值 假 设 有一外键约束 防止从 Customer 表中 删除一 行 可以 考虑 为在 Customer 表上的 Delete 操作 创 建一个触发 器 以便 删除 一个 Customer 行 也 将 删除全 部依 赖的 Sale 行 因此避 免出 现与 外 键约束 冲 突的“ 孤”Sale 行 在 SQL Sever 7 中 这种 操作 不能 进行 因为 在调 用触 发器 之前 系统 检查 外键约 束 并且 当检 查约束 时 有 孤行 那么该触 发器不 能调 用 触发器可操纵 目标表 这就意味着可以对 行做附加的操作 这些行是 由激活触发器的 SQL 语句插 入或 者修 改的 如果 使用 这种 功能 一 定 要避免 或者 处理 叠代 的触 发器 调用 2. 17 小 结 就像使 用任 何语 言一 样 遵守一 定的 编码 经验 和约 定 是有益 的 以便 SQL 代 码 更易维 护 下面 列出 了一 些有 益 的建议 对于反 复使 用的 DML 语句 创 建 一个 包括 这些 语句 的 存 储过 程 并且 把它 们存储在一个 源文 件中 这个 源 文 件可 以从 查询 分析 器 中调 用 或者使用 OSQL 在批处 理中运 行 在 SQL 源代码的 开始 处 加上一 些注 释 描 述执 行的动 作 使用空 格 空行 和分 割线注 释 可以 提高 源代 码的 可 读性 在 Select 子句 下 缩位 From Where Group By 和 Having 子句 对齐每 一个 子句 的开 始和连 续处(例如 Select 列项 from 表项 搜索 条件) 在多行 语句 上 为了 提高可 读 性 对齐 列名 表达 式 搜 索 条件等等 在选择 列表 中使 用 As 子句 为表 达式 或者 函数 提供一 个 有含 义的 列名 用查询 分析 器输 入 Select 语句时 考虑使 用 SubString 标量 函数缩短字符字段 另 外 考虑 使用 Cast 标 量函数 只 返回 数字字段 中 有 意 义的部分 这些 技术 可以 使 结 果更容 易查 看 使用有 意义 的表 相关 名称(也就是 说 别名) 例如 CurCust, 而不 是 C1) 当指定 表达 式 函 数或 者 搜索条 件时 考虑 允许 空值的 列 一定要保 证 用在基本条 件中的子查询 或者替代表达式中的标量 值的子 查询 不 能在结 果表 中有 多个 行 在限定 的条 件中 一 定要正 确使 用 Any 或者 All 关键字 一般地 当指 定 Group By 子句时 在 选择 列 表 中包括全 部的 组合 列 以便 结果表 中的每 一行 都有 该组 的确认 信 息 在 Insert 语句中使 用明 确的 列项 弄 明白每 一 个新 值与 列的对应 一定要 在 Update 或者 Delete 语句 中包 括一 个 Where 子句 除非 打算 删除 或者 修 改表中 的全 部行 当修改主键 唯一性键或者外键列时 或者当 删除由 外键引 用的表行时 一定 要 考虑主 键约 束 唯 一性 键 约束和 外键 约束 的影 响 当执行多行检 索或者修改语句时 一定要考虑 由其它 进程可能产生的访问 冲突 考虑在 DML 语句上使 用表提 示 或者 使用 其他 SQL Server 功能来防 止冲 突 当需要 保证 要么 全部 执行要 么 不执 行的 多行事 务的 执行 时 使用 提交 控制 语句 Transact-SQL 的数据操 纵语 言(DML) 是访问 SQL Server 数据库的 重要工具 作为一位 开发人 员可 以在 ad hoc 模式 下使 用 DML 语句 和工 具 例如 查询 分析 器 但是 最重 要的是 在 SQL Server 数据库上 建立 的大 多数 应用 程序 要求 使用 DML 编程 在本 章中 介绍 的编 程 建议有 助于 维护 代码 和更好 地 执行 SQL第二部分 桌面 应用开 发 SQL Server 2000 作为 Microsoft .NET Enterpise Servers 的重要组成部分 是 以后台数据库的身份出现的 对于应用 SQL Server 数据库系统的用户 访问和 操作数据库通常是通过前端 客户端 来完成的 这就是通常所说的服务器/ 客 SQL Server 户机模式 那么 如何通过前端程序访问和操作 数据库呢 本部分 将详细讨论这个问题 如何通过 Access ODBC ADO 在 C 中嵌入 SQL 访问 SQL Server 数据库第3 章 SQL Server 与其他产品集成 概 述 近来 Microsoft 公司 一直 坚持 着产 品集成 的 策略 不同 产品 之间 的差 别越 来越 少 其界 限越来越 不明显 有时 甚至很难区分 它们 同样 在 这产品 集成成为一种 趋势的 时期产 生 并发展 起来 的 SQL Server 也没什 么不 同 SQL Server 最初就 是设 计成 与操 作系统 特别 是 已推出 的 Windows 2000 以及 其他 Microsoft 应用 程 序集成 使用 的 SQL Server 的操作 系 统集成 来源 于企 业管 理器 MMC 插件 它 的系 统服 务 以及对 诸如 集群 和失 效等 OS 特性的 支持 一般 说来 产 品集成 允 许 用 户或 编程 人员在他们 所 处的 环 境 中访 问 SQL Server 比 如 尽管 基于 MMC 的 企 业管理 器提 供了 一套 具有 一 致外观 和感 觉的 MMC 插件 上百万 Microsoft Access 用 户仍 然 对这种 界面 表示 困惑 通过在 SQL Server 中增 加其 他程 序和 服务 器能访 问的 入口 Microsoft 已使 SQL Server 成 为 更 多人使 用的 可移 植的 工具 本章中 主要 介绍 SQL Server 与一些 Microsoft 产 品的集 成 包括 Microsoft Access 和其 他 Office 应用 产品 以 及 在服务 器端 或客 户端 与 Internet Information Services IIS 或 Internet Explorer 的 Web 集成 编 程方面 SQL Server 也可以与 所 有 Microsoft 的 可视化 编程工 具集 成 如 Visual Basic Visual C++ Visual InterDev 等 在后 续的 章节 中我 们将 陆 续介绍 有关 的内容 3. 1 SQL Server 与 Access 的集 成 近几年 来 Microsoft Access 一种 桌面 数据 库产 品 已经作 为一 种前 端工 具应 用 于 SQL Server 由于 它与 Microsoft Office 系统 产品 的集 成性 Access 简 化了依赖 于其 他 Office 应 用工具 中的 菜单 和工 具条的 用 户界 面 对那 些需 要在前 端 机如 Windows 9x 桌 面 或膝上电脑 工作的 用户 和编 程人 员来说 Microsoft Access 是个 不 错的选 择 Access 则为 热销 的 Office 系列产 品的 一部 分 数 以 百万计 的用 户已 经很 熟悉 它 了 Microsoft 最初的 Access 文 件格式 .mdb 使用 Jet 数 据库引 擎 它是 一种 决定 数据 访问系 统如 何在 磁盘 文件上 存 储数 据的 操作 系统 构件 与 MS Access 1.0 和 Visual Basic 3.0 一同推 出 的 Jet 引 擎是用来 处 理 小型数据 库 的 虽然这 几 年 它有所发 展 并 在 Access 2000 中得到 支持 但近 来 Microsoft 公司 正在 努力 促进 用 户使 用 OLE 数 据库和基于 SQL Server 的数据 库在 Office 2000 中 用户 可以 像访 问本 地的 MDB 格 式文件 一样 访问 SQL Server 中 文件格 式 .mdf 使用 Access 早 期 版本的 用户 仍然 可以 利用 ODBC 连 接远程访 问 SQL Server 数据库 这点 不利 于产 品的 集成 因为 它只 是简 单地 将 MS Access 链接 到 真 实数据 库上 通过 本节 读者 将了解 到 MS Access 的一些 特性 这些 特性 允许 用户 SQL Server 使用 些桌面 数据 库产 品 随着 Access 2000 的到 来 SQL Server 集成也发 展到了 一 个新 的阶 段 Access 2000 可以将 SQL Server 看作一个 后端 存储 back-end store 使其 作用 于一 个新 的 Access 项目 以便 产 生 一个真正 的 客 户 端/ 服务 器模 型 实 现远程访 问 Access 项目 带有.adp 扩展 名的 文件 通常 包括 表单 报表 宏 构件 Data Access Page 见下一 节 创建 Access 项目 这些项 目一 般 不 直接 含有表或 查询 而是直 接与 包含 数 据 库对 象 例 如表 存储 过 程 视图 数据 库 图 等的 SQL Server 后端存储 相连 这里 值 得注意 的是 存在 相互 独立 的 客户端 Access 用户 和服务 器 SQL Server 构件 用户 接口 和 数据构件 业务 规则 的分 离 性是 client/server 或 N 层 开 发的一 个重 要特 性 在 Microsoft Access 2000 中 微软 公司附 带了 SQL Server 7 的一 种 可以用 作后 端存 储 的个人 版 然而 对 于拥有 SQL Server 7 的完 全版 或 SQL Server 2000 的用 户 还是应 该安 装完全 版 用户 可以 将 SQL Server 和 Access 集 成用于 Windows 95 Windows 98 或 Windows NT 4.0+的平 台上 值得 注意 的是 Access 中的 SQL Server 版本根本 不提 供用 户界面 例如 企业 管 理 器 当然 这 也 不算 什么 大问 题 因为 Access 也 可 以为 用 户使用 提 供 不 错 的可 视化界 面 一般 地 如 果 同时使 用 Access 2OOO 和 SQL Server 很可 能将 Access 作为前 端 Access 可以为 各 种 不 同类 型 的 用 户 提供很友好的 用 户 接 口 UI 这一 点不 同于 企业 管理器 3.1.1 创建 Access 项目 为了创 建一 个 Access 项目使 用 SQL Server 作为 后端存 储 过程 只要 启动 Access 并 从对话 框中 选定 Access Database Wizard Pages 和 Projects 就 可以了 每次 选定 都开 启一 个 新的对 话框 以便 从常 用 选项中 选取 以下 内容 数据库 创建 一新 的 Access 数据库 MDB 文件 Data Access Page 创建 一新的 Data Access Page 一种 Microsoft 的 Internet Explorer 5.0 专用 的网 页 以便 将客 户端 数据 绑定 到活 跃的 远 程数据 项目 存在 的数 据库 打 开一个 已创 建的 Access 项目 项目 新数 据库 创建 一 个新的 Access 项目 ADP 文件 为便于 了解 可以 先创 建 一个连 接到 Pubs 数据 库的 Access 项目 从上 面介 绍过 的选 项中选 取 Project Existing Database 找到一 合适 的 文件夹 以便 保留 项 目 .adp 文件 选定了 项目 名之 后就 会出现 一 个 OLE DB 数据链接 属 性的对 话枢 通过 该对 话框的 指导 就 可以连 接到 SQL server 数据库上了 Microsoft 公司随 着 Windows 2000 和 Access 2000 的推 出提出 了数 据链 接 data link 的概念 数据 链接 对于 OLE DB 来说 相当 于 ODBC 文件 Data Source Name DSN 它允 许诸如 ASP Active Server Pages We b 页面 Visual Basic 程序 这样 的客 户端 连接 到与 之相 容的数 据源 OLE DB 上 一般地 这些 数据 源都 是数据 库 而且 OLE DB 可以扩 展 ODBC 以便于 支持 其他 类型 的文档 如 email 等 不论 何时 使用 数据 链接 与使 用 ODBC DSN 很 相似 它们 都是 连接 到 SQL Server 并提供 必要 的登 录信 息 Access 2O00 的 Data Link Properties 对话 框可 以提 示用 户要 连接 到哪 个数 据库 举例 来 说 用户 要连 接到 与 Access 处于 同一 机器 上的 SQL Server 的 Pubs 数 据库上时 可以 在对 话框中 提供 该机 器名 或 local 作为 SQL Server 名 填 写完对 话框 后 Access 显示出 Access 项目 再选 定 Pubs 数据 库 必须提 供的 表格 存储 过程 和数 据库 图表 使用 Access 项目 的一 个好处 就 是操 作 SQL Server 数据库 就像操 作本 地 MDB 一样方 便 SQL Server 数据库 提供 的标志 其 MDB 文档 的方 法很 方便 用户 使用 并且 Access 接口要比企 业管 理器 或其 他 SQL Server 工具提 供的 接口 高级 得多 在 3.l.2 节中 会介 绍一 些 Access 为 SQL Server 提供的一 些特 性 3.1.2 使用 Access 项目 将 Access 作为 SQL Server 前端的 一个 最明 显的 好处就 是 Access 为 用户数据 库提 供了 一个清晰 而有条理的视 图 在企业管 理器 用户常常 面对一 些令人困惑的 视图树 的结点 数 组 而这 些结 点包 含了 许 多对开 发者 毫无 意义 的选 项 并不 是每 个人 都需 要或 想 要管理 SQL Server 对于 只要 任务 得以完 成 的开 发者 来说 他 们 只要对 单个 SQL Server 数据库作 些 处 理 而不 必对 企业 管理 器 提供的 管理 选项 加以 操作 Access 为 用 户 提 供 了 关 于 所 需信息的数据库级 视 图 以及 使 工 作 得以完成的工具 实 际上 用户 要做 的每 件事在 ACCCSS 中都可以 很容 易 地实现 从创 建视 图和 存 储过程 到 创建可 视化 展示 表及 表间相 关 性的 数据 库图 由于 在 Access 中 可以获 得这 些特 性 大多 数 开发者 有理 由选 择 SQL Server 的完全 版 用户 通过 Access 操作 SQL Server 数据库 并且 在 Access 中的 操作 引起 的 数据库 变化 直接 体现 在企 业 管理器 中 所以 可知 用户 是直 接对 主数据 库本 身进 行操 作 而不是 操作 该数 据库 的一 个 本地副 本 下面讨 论的 工作 在 Access 2000 中都可 完成 使用表 用户可 以在 Access 中创 建 打开 设计 删除 修改 SQL Server 表 在 Access 中使 用 SQL Server 表与使用 基于 Jet 的表很 相似 SQL Server 表的窗口看 上去 感觉和 Access 的窗口 差不 多 都有 Design 视图 和 Datasheet 视图 Design 视 图允许用 户设 计表 的字 段结 构 不过 要设 计字 段的 属 性不太 容易 对基 于 Jet 的 MDB 数据 库 Design 现 图 窗口的底部 增加了 Field Properties 区域 以便 于用 户改 变当 前字 段的 数据类 型 格式 和其 他属性 但对 SQL Server 数据库 用 户必 须从提供了 所有 字段的独立 的 属性窗口 或下 拉列表框中 访 问 各 个字段 不过 这也 没什 么 困难 只是 方法 不同 罢了 对于 SQL Server 数据库 它与 企业 管 理器中 的 Design Table 视图 更一 致一 些 使用触 发器 用户可 以在 Access 的表 视 图中创 建 修改 删除 触发器 就像 在企 业管 理器 中对 触发 器的操 作一 样 Access 中 的触发 器编 辑器 与企 业管 理 器中的 差不 多 只是 Access 缺少 Check Syntax 选项 使用视 图 在 Access 中访 问视 图实 际 上与企 业管 理器 中进 行的 动 作很相 似 在 Visual Studio 97 中 首次引 进的 View Designer 窗口 已经 移植 到了 Access 2000 SQL Server 7 SQL Server 2000 以及 Visual Studio 6.0 并 得 到更新 因此 任何 熟悉 这些 工具 的人 都能 方便 地协 同工 作 只有 一个不同 点 在所有其 他工具中 视 图设计器包括四 个区域 分别显示表 图 字 段栅栏 原始 SQL 以及组 成这 张视图 的 查询 结果 而在 Access 中 由于 Design/Datasheet 模式的使 用 只有 前三 个区 域是 可 见的 第四 个区 域可 以通 过 从 Design 视 图切换到 Datasheet 视图 看到 使用数 据库 图 这 一 功能 也是 Access 2000 从企 业 管 理 器 的 类似功能中借鉴过来的 数据 库图 从外观 上来说 和产 生自Visual InterDev 和 FrontPage 之类工 具生成 的 Web 站 点形成的 视 图很相 似 可以用一种最简单的方法显示出表连接很复杂的数据库结构 依赖关系清楚地表示出 Access 能将 用户 在可 视化环 境 中所 要执 行的 功能 转化成 SQL 语 句序列 使用存 储过 程 存储过 程毫 无疑 问是 SQL 开发者必 备的 工具 Access 2O00 中 的存储过 程 设计 器 看起来 和企 业管 理器 或 Visual Studio 6.0 中的使 用不 同色 彩作 标记 的编 辑器 很相 似 存储 过 程的 执 行 也 和 企业管理器 中类 似 如 果 存 储过程需要 参数 Access 将 在 运行该过 程 以前 提 示用户 输入 测试 值 这 种 方法非 常方 便 使用表 单 Access 中 的表 单和 企业 管 理器中 的任 何对 象都 不真 正 具有一 对一 的关 系 熟悉 Access 的用户知 道 Access 应 用 程序是 一种 表单 的典 型应 用 用户 可以 借助 于类 似 Visual Basic 表单设计 器的工具来创 建数据库的访 问前端 如果 使 用向导 它 将 帮助用 户生 成 可以投 入 使用 的 表 单 的 基本框架 Access 有一 个表 单类 型 库 其 中包括 各种 可以 添加入表单 中的字 体 颜色 和背 景图 案的 组 合 Access 的表 单在 SQL Server 中并不存 在相 应的 对象 因此 对 企业管 理器 而言 Access 的任何 表单 都是 不可 用的 使用过 Visual Basic 或 Microsoft Office 所带的 VBA Visual Basic for Application 编辑 器的用 户应 该很 熟悉 Access 表单 其 环境 从本 质上 说 也不过 是控 件的 拖放 和修 改 包括 命 令按钮 文本 框 检查 框以及与底层 数据库结构相关 的控件 表单完成后 将作为 用户访 问 数据库的 前端 使用户 拥有一个可修 改的 总的来说 简单的 办法来查看和 修改数 据 对程 序员来 说 这是 使用 Access 采访 问 SQL Server 的 最 好理由 过去 Access 表 单 只能访问 Access 数据 库 目 前的 集 成度已 使得 它有 能力 访问 SQL Server 数据 库了 使用报 表 像 Access 表单 一样 报表也 是 Access 的特 性之 一 报表 的设 计也 和表 单相 同 可以 通过按 步骤 进行 的向 导 也可以 通过 Visual Basic 式 的设计 环境 手工 设计 报表可 用于 生成 数据库 信息 的统 计功 能 这 对用户和 管理 来说 都是 很 适用的 使用 SQL Server 提供的 工具 来创建 报表 几乎 是不 可能的 尽管 某些 极为 敬业 的 SQL 程 序员这 些年 来一 直用 Visual Basic 或其他 工具 来创 建他 们自己 的 报表 用过 Access 的用 户一定 会觉 得使 用 Access 来建报 表 是多么 明智 使用 Data Access Page Data Access Page 也许 是 Access 2000 带 来 的最 激动人心 的新 特性 它要 求用 户具 有 Internet Explorer 5.0 Access 2000/0ffice 2000 包含 有 因为 它使 用的 客 户 方数据 绑定仅 对 该测览 器有 效 实际 上 Data Access Page 是一 种客 户方 的 不像 基于 ASP 是 服 务器方的 HTML 文档 它包 含动 态 HTML 脚本和 Microsoft Design Time ActiveX 控件 DTC 可 为 Access 提供 基于 表单 和 We b 的应用 程序 Data Access Page 可 做得和 表单 一 模一样 只不过可 以在 Web 浏览 器中运 行 通过 这种 方式 用 户 可以经 由 Internet 或本 地 Intranet 来访 问 而不 需要 在他 们的 机 器上运 行 Access Data Access Page 也 提供安 全保证 用户的 数据 库登录 及口 令并 不是 组成页 的 HTML 代码的 一部 分 Data Access Page 和 SQL Server 中的任何 Web 特性都 无必然 联 系 而是 对它 们 的扩充 不过 Data Access Page 需要 Internet Explorer 5.0 支持 而 SQL Server Web Assistant 产生的 任何 Web 页面 都能在 几乎所 有 We b 测览器 中使 用 3.l.3 一 节中将重 点介 绍 Data Access Page 3.1.3 创建 Data Access Page 要创建 Data Access Page 必须首 先打 开一 个 Access 项目 并链 接到 诸如 Pubs 的 SQL Server 数据库 在 数 据 库窗口的 工 作 对象列表 中 将 出现一个 名 叫 Pages 的选 项 这一选 项 即可使 用户 通过 向导 或者通 过 Design View 窗口 创建 新的 Data Access Page 也 可以使用 户编辑 一个 已存 在的 Data Access Page 使用 Data Access Page Wizard Data Access Page Wizard 是创 建数 据相 关的 网页 的最容 易 方法 仅仅 选择 Create Data Access Page by Using Wizard 选项 然后 按照 向导 中的 各步 选择 就 行了 这一 切和 先前 提到 过的 View Wizard 类似 尽 管用户 可以 仅在 Access 环 境中使 用产 生的 Data Access Page Access 同时 也将 HTML 文件及 其相 关文 件 包括 XML 数据 图像 文件 及其 他 保存 在 My Documents 文件 夹中 用 户还可 以将 结果 文件 发布 到 Web 服 务器上 允许 其他 人使 用 Office 2000 新的 Web 文件 夹特 性通 过 Internet 访问 使用 Design 视图 Design 现图 的使 用需 要在一 定 程度 上熟 悉 Design 视 图编辑 器 这一 编辑 器正 如 表单编 辑器一 样与 Visual Basic 风格 类似 实际 上 用户 必 须在栅 栏化 的表 单中 添加 控 件 并将 其 与数据库 中的对象相关 联 和使用向 导相比 这一 过 程相当 乏味 但用户 却能由 此获得 一 些在向导 中无法得到的 可修改性 一 种推荐的做法是 用向 导生 成 页 然 后在 有 必要修 改 时在 Design 视图 中进 行 除非只 有一 个很 简单 的数 据 库 否则 使用 Design View 来设计 Data Access Page 通常 是一 项很大 的 工程 3.1.4 Microsoft SQL Server 的双 向数 据复 制 在 Access 97 中 数据 可以 从 Access 数据库 复制 到 SQL Server 中 而从 SQL Server 向 Access 数据 库复 制数 据则是 不 可能 的 这种 由 SQL Server 发布者 Publisher 向基于 Jet 的 Access 订阅 者 subscriber 的单 向数 据复 制功能 在 Access 2000 得 到了改进 现在 数据可 在 SQL Server 和 Access 之间 双向 复制 注意 注意 注意 注意 这一 特性 当然 是有 限制 的 只有 类似表的精 确数据才 能被 复制 表单 报表和 其他非 数据 对象 是不 允许复 制 的 有趣的 是 Access 2O00 和 SQL Server 之间的 复制 冲突 现在 可以 由 SQL Server 的冲突 解决向 导 Conflict Resolution Wizard 来处 理 它视 SQL Server 的 一部分 而不 属于 Access 冲突解决 向导用一张冲 突表来存储那 些不能传达到服 务器的 改变 它根据 内部基 于优先 级 的冲突 解决 算法 来决 定哪些 改 变允 许执 行 哪些 不允许 3.2 SQL Server 与 Excel 的集 成 从 Excel 20O0 开始 使用 SQL Server 也包括其他各种数据库 作为数据透视表 PivotTable 或 Pivot Chart 的数 据源 成为 了可 能 数 据透视 表允 许用 户动 态过 滤大 量 数据 以使显示 能随操作实时 改变 当用户 进行一大组相关 数据集 的比较时 数 据透 视 表就显 得 更为 有用 了 数据 透视 图 Pivot Chart 用 图形方式 提供 了相同的 功能 使大量 数据 的操 作能通过 图表进行 虽 然数据透视表 的创建并不需要 相关的 数据透视图 后者 通 常总是 与 一个数 据透 视表 相关 连 并且必 须位 于同 一个 Excel 工 作本中 创建 Excel 数据 透视 表 要创建数据透视表 可以在 Excel 中找 到合 适的 工作 本 然后 在菜 单中 选择 Data 的 PivotTable and PivotChart Report 激活 PivotTable 和 PivotChart 向导 它 将向 用 户询问 要使 用的数 据类 型 这有 四种可 能 Microsoft Excel 列表 或数 据库 外部数 据源 多联合 域 Multiple consolidation ranges 另一个 PivotTable 或 PivotChart 仅自 己创 建时 才可 用 为使 用 SQL Server 必 须 选择外 部数 据源 向 导的 第二步将 要求用户 使用 Microsoft Query 来查找数 据源 该 部件必 须已 安装 Choose Data Source 对 话框允许 用户 选择 一个 已创建 的 ODBC 数据 源名 DSN 创建 一个 新 DSN 或 创建一 个特 殊的 查询 如果要 创建 新 DSN 就需 要按 向导 的指 引一 步一 步来 在 本例 中 将创建 一个 名叫 northwind 的 DSN 指 向 Northwind SQL Server 数据库 从这 开始 向 导将提 示 选择 查询 中所需 的字 段 所列 举 出的 Northwind 数据 库中有 若 干表 包括 Categories Category Sales for 1997 等等 当选 好要 显 示 的字段时 向导将 提示用 户为 PivotTable 选 定 工作 表 位 置 或者 如果用 户从一 批没被连接 上 的 表中选择了字 段 Microsoft Query 则 字被启 动 来完成手 工 连 接 与其使 用 Microsoft Query 不如 退回去重 新考 虑一下 因为 在 不相关 的数 据基础上 生成 逻 辑表是 很 困难的 事情 在此 用 户 仅需将 页域 列域 行域 以 及数据 项拖 入 PivotTable Excel 将像 平时与 原始 数据 协同 工作一 样 与 PivotTable 协作 3.3 在 IIS 和 IE 中使 用SQL Server 作为 Microsoft .NET Enterprise Servers 套件 中基 于 Windows 服 务器的关 键成 员 SQL Server 与其他 Microsoft 服务 器产 品都 有良 好的 集成 其中 包括 Internet Information Services Microsoft 的 Windows DNA 战略的 关键 概念 之一 就是 分布 式应 用 IIS 是目前为开 发分 布式 应用的 最合 理的 手段 通过 World Wide Web Web 的发 展将 SQL Server 的开发 者带 入一 种有 趣的两难 境 地 现在 Microsoft 技术提 供 了 两种 可能 的途 径向 客 户 方传 送应 用 一种是基于服务器的通常的方法 例如 Active Server Pages 技术 可以 在任 何操 作系 统平 台的 浏览 器 上使用 处理 主要 由服 务方完 成 另一种方 案 来 自 Internet Explorer 5.0 它 使 用 户 能 将 问题交由客户方的数据 库 逻 辑完 成 从而 减轻了 服务 器的 压力 两 种方法 各有 千秋 3.3.1 在服 务方 访问 数据 库 在今天 的 We b 上 客户 之 间仅有 一小 部分 公用 的功 能 可以依 赖 不同 的 Web 刘览器 运行在 不同 的操作系 统之上 甚至 底层 的硬 件 平 台也不 同 从而 提供 的功能也千 差万 别 因此 Microsoft 开发 广 ASP 允许 We b 开发者 通过 服务 器 IIS 插件执行 数据 库访 问及 其他 功能 然后 将纯 义本 传回给 例 览器 同为 传给 浏览 器 的 信息是以 文本 形式 存在 的 任何 Web 浏览器 都能 接受 因此 We b 开发 者就 无需 考虑 兼容 性 问题 例如 大多 数 ASP 代码都 是 用 VBScript 编写 的 而它与 Netscape Navigator 不兼容 当 Web 页 面到达 浏览 器 时 已看 不到任 何 VBScript 的 影子了 3.3.2 使用 Internet Explorer 进行客 户端 数据 库 访问 另一 方 面 如果所 有 客 户 端 都使 用 Internet Explorer 5.0 或 稍早一点 IE 4.o1 with Service Pack1 由 于这一浏 览 器 具有客户方数据 相关 的 特性 用 户就可 以 使 用一套全 新 的 功能 让 客户方有能力 处理实时数据 将使服务器从繁 重的工 作中解放出来 并 使 用户处 理 变得更 加有 力 更加 具有动 态 性 当然 很少能 保证所有用户都使用同一种 浏览器 当前 客户方的数 据绑定服务仅在 Internet 上有意义 因为 此时 Internet Explorer 的选 择可 以作 为一 种标 准 注意 注意 注意 注意 客户方数 据绑 定技 术概 念最 早出 现在 IE 4.0 中 第一个 使用 这一 概念 的 技术是 远程数 据服 务 Remote Data Service RDS 随着 Visual Studio 98 的发 行 另一种 此概 念的 应用 成为现 实 那就 是设 计时 控件 Design time Control 3.4 SQL Server 与 Microsoft Transaction Server 集成 Microsoft Transaction Server MTS 是 Internet Information Server Microsoft Web 服务 器的组 成部 分 用户 可通过 访 问 MTS API 来 开发 自 己的软 件构 件 DLL 文件 将业 务逻 辑分解成 事务 Transaction 事务 概念 对 SQL Server 开发者来说应当尽快熟悉 Transaction Server 事务正如 SQL Server 事务 一样 要 求完整 地执 行 或者 整个 成 功 或者 整个失败 考虑下面这 个经典的银行 存取结余事务 它由两 个不同的动作 构成 取和存 然而 作 为一次成功的 传输任务 两 种操作都需要发 生 如 果其中任何一 个失败 则整 个 事务回 滚 如同 两个 操作都 没 发生 注意 Windows 2000 将 MTS 重命名 为 Component Services 组件服 务 从 而更为 贴 切地反 映出 这一 技术 的用途 对 COM+/Windows DNA 体 系结构来 说 Component Services 是一 种关 键的 管理 工 具 在 Transaction Server 出现以 前 程序 员不 得不 手工 实 现这一 行为 数据 库开 发者在 存 储过程 中使 用 BEGIN TRANSACTION 和 COMMIT TRANSACTION 等 T-SQL 命 令 来实现 SQL Server 的事务 处理 如 果这种 基于 SQL Server 的事 务中 有一 项操 作失 败 则整个 事务 将要回 滚 如果 有必 要的话 SQL Server 事务还 可以前 滚 Microsoft Transaction Server 继 承了 SQL Server 的事务 思想 并运 用于 其他 技术 实现 上 和 SQL Server 一样 Transaction Server 与 Microsoft Distributed Transaction Coordinator MSDTC 交互以 确认 事 务满足 ACID 完整 性 Atomicity 一致性 Consistency 分离性 Isolation 和耐 久性 Durability 测试 MTS 与 IIS 集成发行 而且 通常 与其 相关 因为 We b 向 开发者 提供 的是 一种 无 状态的 环境 典型 情况 下 Web 客户端 连接 一个 远程 资源 如 Web 页面 或是 通过 Web 页面访 问的远 程数 据库 一旦 所有 数据 传输 完就 立即 断连 Web 正 是依靠这 种不 连续 的连 接才 能 保持速 度可 接受 如果 We b 客户 保持 与服 务器 的持 续 连接 服务 器的 需求 将更 大 处理 能 力也要 更强 同时 整个 Internet 本身 的结 构也需 要升级 在 Transaction Server 的帮助 下 这种无 状态 的 We b 环境 就 是可避 免的 一组 操作 事务 的无 错性 就能 得到 保证 如同 SQL Server 事务一样 MTS 事 务中的 一个 操作 失败 整个事务就失败 更重要的是 这一 功 能由 MTS 隐含 完成 用户 不必 开发 自己 的相 应操 作 3.4.1 MTS 事务 和 IIS 从 IIS 4.0 开始 Active Server Pages ASP 服务 方脚 本允 许访 问 MTS 特性 IIS 3.0 中脚本 还不 能访 问 MTS 特性 We b 开发 者由 此可 以 创建多 层 n 层 client server 方式 的 Web 应用 一个多 方 Web 应用 通常 包括 下列 三层 表达层 基于 HTML 或 DHTML 的用户 界面 基干 Web 浏 览器的 用户 界面 使用 户 能够通 过丰 富的 Web 表 单 和控件 与 We b 应用程 序进 行交 互 业务 逻辑 层 决定 应用 程序如何 进行 的规 则 业务 逻辑是用 来连 结数 据仓 库 data store 如 SQL Server 和表达 层 数据服 务层 一般 是一 种结 构化 的数 据仓 库 例如 SQL Server 或 oracle 多 个数据 服务层 可供 一个 Web 应用使 用 这一 服务 由中 间层 业务逻 辑层 的 MTS 构件 管理 使用这种 三层策略 通常将数据与表达 层分离 而 MTS 构 件或脚本则工作在这两层之 间 提 供 逻 辑上的空 间 满足 这种分 离 并 决定 另 两 层如 何 交 互 Microsoft 的 这 种将客户 服 务器模 式开 发与 Web 开 发 合成到 一起 的战 略被 称为 Windows Distributed InterNet Application Architecture Windows DNA 它使用 HTML 动态 HTML DHTML 和 Active Server Pages 向用 户提 供第 一层 表达层 而 Microsoft Transaction Server 和使 用 MTS 的构件 如 IIS IIS/ASP 构件 以及 ASP 脚本等 占据 了中 间层 业务逻 辑层 SQL Server 通常作 为 数据服 务层 尽管 其他 诸 如 Oracle Microsoft Access 或 Exchange Server 邮件 包等 工具 也能 有限地 使用 要与 MTS 集成 数据 仓库 必须 支持 X Open 组 织提出 的 XA 协议 SQL Server 当然是 与 MTS 合作 的首 选数 据库 注意 注意 XA 是一 种由 X/0pen DTP 组 织定义 的两相提 交协议 允许多个 数据库服 务器间 注意 注意 的 事 务协 同 并将 其看 作 一个 事务 这一实现保证 了本与事 务的 所有 服务 器上 的数据 库全 部更 新 或 者 没有一个更 新 许多Unix 数据 库 包括Oracle Informix 和DB2 都完 全支 持 XA 协议 用 MTS 编写 ASP 通过使 用 TRANSACTION REQUIRED 指令和 ASP 的 Server 对象 Transfer Execute方法 用户 可以 充分 利用 MTS 的优 势来 编写 ASP 脚本 下面 的代 码段 说明 了在 ASP 中如 何初始 化一 个事 务 <% @TRANSACTION=REQUIRED %> <% ‘Begin transaction ‘Code that must occur in a transaction ‘End transaction %> 这段代 码中 有几 点需 要注意 代码 代表 的是 一整 页 ASP TRANSACTION 指 令 必须置 于页首 第一 行 它提 醒 ASP 编译器 代码 所访 问的 所 有 ASP 和 构件都是 同一 事 务的不 同 部分 TRANSACTION 指令 的使 用格 式为<% TRANSACTION=Va1ue%> 其中 Va l u e 是 表 3-1 所示 各值 之一 表3- 1 TRANSACTION 指令的可选 VALUE 值 值效果 Required 指令后的脚本将初始化一个 MTS 事务 Required_New 指令后的脚本将初始化一个 MTS 事务 Supported 指令后的脚本不初始化 MTS 事务 Not_Supported 指令后的脚本不初始化 MTS 事务 一个包 含 MTS 事务 的 ASP 页面通过 Server.Transfer 或 Server.Execute 方 法从另一 ASP 页面中 调用 Transfer 办法 简单 地将 当前 会晤 的控 制 传递到 另一 ASP 页面 第一个 ASP 页 面中创 建的 任何 对象 和变量 都 传送 到第 二个 ASP 页面 中 Transfer 方 法格式 如下 Server.Transfer ”path” 代码中 的”path”是指 第二 个 ASP 页面 的绝 对或 相对 路 径 当使 用事 务型 ASP 页面 以 TRANSACTION 指令 开头 时 用户 通常 使用 Transfer 方 法来引用 事务 页面 当事务 页面运 行结 束后 控制 传 回调用 的页 面 Server 对象的 Execute 方法 也能 使用 它调 用另 一 ASP 页 面时不传 送任 何参 数或 对象 Execute 方法 的调 用格 式与 Transfer 办法相 同 Server.Execute ”path” 其中 path 的意 义也 相同 不管 使用 server.Transfer 还是 server.Execute 第二个 被 调用 的 ASP 页面都应在 第 一行包含 TRANSACTION 指令 然后 当文 件中 的代 码执行 完后 它 按照 调用 时相 同 的规则 将控 制返 回给 调用 页 事务型 ASP 最后 一个 要注意 的 地方 是 事务 型 ASP 页 面能处理 两种 Transaction Server 事件 OnTransactionAbort 和 OnTransactionCommit 具体 处 理 哪个事务当然 取 决 于 事 务是 否成功 完成 在此介 绍一 个简 单的 ASP 页代码 样例 transactl.asp 它引 用名 为 tansact2.asp 的文件 <% @ Language VBScript %> Test transaction< TITLE> < HEAD> Test transaction <% Server.Execute ’tansact2.asp’ %>
This appears after the transaction is complete /BODY 被调用 的页 见下 <% @ TRANSACION=REQUIRED%> <% Response.Write” This comes to you from within a transaction ” Sub OnTransactionAbort Response.Write ”Transaction unsuccessful ” End Sub Sub OnTransactionCommit Response.Write ”Transaction successful ” End Sub %> 输出结 果首 先是 Heading one 字体 的 Test transaction 段 如 transactl.asp 所示 然后 控 制转入 事务 页面 transact2 它首 先打 印一 行 Heading Two 字体 然后 在事 务成 功 完成时 打 印一条成功信 息 控制再返回调用文件 transactl.asp This appears after the transaction is complete. 信息显示出来 显然 大多数 事务 代码都比 这复 杂 但总 体结 构大致相 同 下一 节 我们 将讨 论 ASP SQL Server 和 MTS 的用 法 3.4.2 MTS 事务 和 SQL Server 既然 SQL Server 本身提 供事 务处 理功 能 为什 么还 要在 访问 SQL Server 的主 页中 使 用 MTS 呢 要记 住 只有 在 SQL Server 中一行行 地遵循 Transact SQL 规 范才能 自动 提交 事 务 如果 希望 显式 的强 制 使用事 务处 理 用户 必须 使 用 Transact SQL 代码 包括 BEGIN TRANSACTION COMMIT TRANSACTION ROLLBACK TRANSACTION 等 以保证 代码正 常运 行 同 时如 果 失败还 可以 回滚 然而 如 果使用 Active Server Pages 和 ActiveX Data Objects ADO 来编 写 SQL Server 应用 一般应 使用 ADO 的 对象模型 来操 作 数据 而 不是 Transact-SQL 代码 例如 在 ADO 中 用户 可 以使用 INSERT 的 Transact-SQL 语句 向表或 视图 中添 加新 行 但使用 Recordset 对象的 AddNew 方 法将更有 效一 些 类似的 操 作 包括 行删 除 修改 添加等 显然 都适 用于 事务型 ASP 页面 而另一 方面 如果 有条 件使用 Transaction Server 和 ADO 来幕后完 成事 务处 理的 话 编写 复杂 的 Transact-SQL 代 码就成 为不 必要 的事 情了 没有 理由 无视 Microsoft 提 供的易 用工 具而 坚持 把时 间浪 费在 显 式地编 写事 务处 理代 码上 3.4.3 强制 分布 式事 务处 理 SQL Server 也能提 供内 建的 Transaction Server 服务 要认识 这一 点 可 以打 开企业 管 理器 右击 想修 改的 服务器 选择 Properties 在弹 出的 对话 框中 选择 Connection 页 单击 Enforce Distributed Transactions MTS 单选 按钮 如果 Microsoft Transaction Server 对 SQLServer 不可用 这一 选项 将是 无效 的 3.4.4 MTS 事务 处理 和可 视化 编程 工具 Microsoft Transaction Server 也能经 由 Microsoft 可视 化编 程工 具访 问 其中 包括 Visual Basic Visual C++ 和 Visual J++ 实际 上 开发 者将 业务 逻辑 层封 装在 Visual Basic Visual C++ 或 Visual J++ 写的 MTS 构件 中 其效 率和 通用 性都比在 事 务型 ASP 页 面中使用 要好 当然 这一内 容不 在本 章的 范围内 但作 为真 正的 Windows DNA Web 应 用程序 必须 使用 MTS 构件来 充当 客户 端 可以是 We b 训览 器 也 可以 是真实 的 Win32 程序 和 SQL Server 数 据源 之间的中 间层 即 业务逻辑 层 通过将数 据访问 构件 封 装入 Transaction Server 它们 就会随 着网 络的 变化 自动伸 缩 3.5 小 结 本章介 绍了 如何 使 SQL Server 与 Microsoft 其它 产品集 成 重点 介绍 了 Access 2000 Excel IIS IE 和 MTS 如何 与 SQL Server 协调工作 并且 比较 了 Access 97 和 Access 2000 与 SQL Server 集成时的 差别 特别 是连 接和 双向 复制 方面 我们 还学 习了如 何在 SQL Server 数据上 创建 Excel PivotTable 如何 在 IIS 作为 Web 服 务器时 将 SQL Server 作为数 据层 最 后 我们 了解 了 SQL Server 能使用 MTS 来 控制 分布事 务 并保 证数 据的 跨多 服 务器一 致第4 章 使用Access 访问SQL Server 在本章 中 将学 习如 何使用 Microsoft Access 的强大 的数 据库 设计 和开 发功 能创 建利 用 SQL Server 的高性 能 多用 户数据 库的 客户 机 服 务 器应用 程序 4. 1 概 述 Microsoft Access 既 是一 个 独立的 数据 库平 台 也 是一 个强大 的数 据库 开发 工具 可以 用来 开 发 定 制 的客户机 服务 器数 据库 应用程序 Access 包含 了一 套丰 富的数据库 开发工 具 允 许 创 建 定制的数据 库应 用 程 序 Access 主要是作 为一个 独立 的数 据库 然 而 在本 章 也可以 看到 如何 使用 Access 开发 SQL Server 的数据库 应 用程序 随着 Access 2000 的发布 SQL Server 与 Access 之 间 的集成 性能 有了 明显 的改 善 本章 将以 Access 2000 为 背 景介绍 使用Access 开发 SQL Server 数据库 应用 程序 的方 法 作为一 种数 据库 平台 Access 使用 Microsoft Jet Database Engine 处理 其 存储 和查询 功 能 Access 数据 库存 储在.mdb 文件 中(Microsoft Database) 这些 文件 一般 存放 在 PC 的硬 盘驱动 器上 或者 网络 共享上 Jet 引擎主 要用 于处 理单 用户 数据 库 它也 可以 用 于比较 小的 多用户 应用 程序 由 Access 使用 的 Jet 引 擎 包含 一个功 能 完整 的查询处理 器 它回 应 Jet SQL 语句 Jet SQL 语句的语 法虽 然类 似于 SQL Server 的 Transact-SQL 语句 但是 两者 并不 相 等 就 像 希 望 数据库系统 所做 的 事 情 一 样 Access 的 查询 处 理器 可以 执行 查 询 检索 结 果 集 连接 文件 和执 行那 些 类似于 SQL Server 更加强 壮的 多用 户数 据库 执行的查询动 作 除 了 其 数据 库功 能外 Access 提 供 了 一 个 可 视化 数据 库 开 发环境 它提供了一个可 视化 数 据 库设计工 具 允许快速 创建数据库 并且包含了一个 可视的 窗体创建器 可以 用 于创建 数 据输入 窗口 以及 图形 化的报 表 设计 工具 Access 的主 要可视 开发 工具 如表 4-1 所示 表4- 1 Access 的快速开发工具 组件 描述 Table Designer 用来创建或者链接表和视图 查询设计器 用来创建数据库查询 窗体 Designer 用来创建数据输入窗体 报表设计器 用来创建数据库报表 Macro Creator 用来创建数据库宏 可以用来自动化简单的动作 用来创建 Visual Basic for Applications 脚本 用于比较复杂的编程任务 VBA Editor 除了这 些主 要的 组件 Access 还 包括了 30 多个 向导 可以 用于 执行 许多 常见 的任 务 例如创 建窗 体 报表 和查询 图 4-1 提供 了 Access 环 境的高端 概览 如 图 中所示 Access 的核心是其本地的查询处理器 也称作 Microsoft Jet(Joint Engine Technology 简称 Jet)Database Engine 该 Jet 引擎自 己访 问存 储在 本地 的.mdb 文 件中的 数据 然而 使用 外 部的数据 库驱动程 序 Jet 引擎还可 以访问存 储在其他 数据库中的数据 这些 数 据库包 括 Paradox dBase 和 ODBC 数据 源 Access 的 前端开 发 工具通 过 Jet 引 擎与不同 的 数据库 联系起来 图4-1 Access 概览图 4.1.1 在多 用户 情况 访问 Access 和 SQL Server 的区 别 Access 能够 处理 多用 户 但在 Access 和 SQL Server 处 理 多用 户 的 方式上 有 很大的差 Access 别 使用 每一个系 统包含 其自 己的 查询处理器 在并 行的 数据 库访问上 没有 集 中式的 控制 或者 优化 图 4-2 示 意 了一个 多用 户的 Access 应 用程序 在一个 典型 的多 用户 Access 应用 程序 中 数据 库可 以 由运行 Access 的 多客户机 系统 共享和访问 在每一种情况下 查询处理 器位 于联网 的客户 机系统上—— 不 是数据 库本 身 联网 既 没有集中化的 机制来优化并 行的访问 又没 有任何 中央资源处理 其他资 源冲突 的 Access 锁定 问 题 数据库通 过系统 表协调多 个用户 这 些系统 表也 像数 据库表一样 面对 同种类型 的多用户冲突 问题 另外 为了满足查询和 其他数 据库的请求 客户 机 系统必 须 通过网络 执行网络文件 读 为本地查 询引擎提供数据 即使 在 一个快速网 络中 通过网 络 阅读数 据也 比使 用本 地的硬 盘 驱动 器访 问数 据要 快许多 倍 即使 如 此 对于 比较少的用 户 这些问题 也 是 可 管 理的 Access 可 以 有效地用来 创建 Access 部门 的 或 者 其 他 较 小规模 的 多用户应用程 序 然而 当数 据 访问请求增加时 不能 够伸缩来 满足这些要求 当涉及大量 的用户或者用户 数虽少 但是操作频繁 时 缺 乏优化 缺乏资 源管 理和 对网 络 I O 的要求 就使 多用 户的 Access 应 用程序的 性能 和可 靠 性大幅 度 地下降图4-2 多用户的 Access 应用程序 相对而 言 SQL Server 主要用于多 用户 数据 库访 问 使用 SQL Server 数据 库和查 询 处理器驻 存在中央数据 库服务器上 在客户机系统上 没有 本地的查询处 理器 相反 在 客户机 系统 上的 数据 库应用 程 序向 SQL Server 系统 发送 数据 库请 求 SQL Server 查询处 理 器接收所 有到达的请求 访问数据存 储和把每一个请 求的结 果返回给每一 个客户 机系统 4-3 SQL Server 图 提供 了使 用 访问多用 户数 据库 的概览 图4-3 多用户的 SQL Server 数据库访问 在一个 多用 户的 SQL Server 实现中 数据 和查 询引 擎位 于中 央 SQL Server 系统上 客 户应用 程序 使用网络 协议和 网 络 通 讯库 与该 服务器通讯 SQL Server 的 查 询处 理 器处理 所 有到达的 请求 分 析这些请求 并且 应用 处 理这些 请 求 的 优化 因为查询引擎和 数据库 一 般位于同 一个系统上 所以所有锁和 数据访问都非常 快并且 效率高 这种 客户 机 服务 器 SQL Server 体系结 构允 许 向上伸展 处理数 百个 或者 数千 个客 户 4.1.2 Access 作为 前端开 发 工具访 问 SQL Server Access 主 要 用 于 创 建 独 立 的 单用户数据库应用 程 序 另一 方 面 它还可以用作客 户机 服务器 应用程序的开 发工具 相同 的图形开发功能 使它成 为一种高度的 个人数 据库开 发 工具 又使 它成 为开 发 SQL Server 数据库 应用 程序 的强 大工 具 链 接 它们 的 是 开放数据 库连接性(ODBC) 标准 ODBC 标 准 由微软 公司 开 发 通过 ODBC 驱 动程序 提 供 了访问 关 系型数 据库 的方 式 ODBC 已经被 广泛 采纳 并且 所有 主要 的数 据库 供应 商现 在都 支持 它 包括 Access 在 内的 许多 终 端用户 程序 也包 括内 置的 ODBC 支持 可以 在第 5 章得到 ODBC 更多的 信息 使用 SQL Server 的高性 能 多用 户数 据库 ODBC 可 以连接 Access 的高产 前端数 据库 工具 这样 就 允许使 用 Access 快速 简单 地 创建数 据 输 入窗体 以及访问 存储 在 SQL Server 中的数 据库 的查 询和报 表 4.1.3 Access 通过ODBC 访问 SQL Server 机制 在详细 论述 如何 连接 Access 和 SQL Server 之前 先看 看当 Access 使用 ODBC 连接到 SQL Server 数据库 时使 用的 网络体 系结 构 图 4-4 说 明了由 Access 和 ODBC 连接到 SQL Server 使用的 网络 层次 图4-4 Access 使用 ODBC 的网络体系结构 在顶层 可以 看到 Microsoft Access 应用程 序 因为 Access 是 一种允许 ODBC 的应用 程序 所以 它可 以调 用由 ODBC Driver Manager 提供 的各种 ODBC API 函数 然后 ODBC Driver Manager 使用 相应 的 ODBC 驱动程 序连 接到 目 标数据 源 在 SQL Server 中 就是 SQL Server ODBC ODBC IPC( ) 驱动 程序 驱动程 序使 用相 应的 网络 进程 间通 讯 方 法 与数据库 服 务器上 的相 应网 络库 通讯 网络 IPC 机制 允许 联网 的 系统与 另一 个系 统通 讯 两 个常用 的 IPC 机制 是 Named Pipes 和 TCP IP Sockets 客 户机的 IPC 机 制使用 实现 的网 络 协议与 服 务器的 IPC 通讯 网络协议负责发送和接收网络上的数据流 两种常用的网络协议是 NetBEUI 和 TCP IP 最后 在这 个 通 讯堆 栈的底端是 物 理网络 物理 网络 定义要 求 物理 连接客户 机和服务器系统 的硬件 物理网 络包括适配器卡 集线 器和 电缆 这些 用于连 接 分布式 的客 户机 和服 务器系 统 Ethernet 和 Token Ring 是 两个最常用 的网 络拓 扑 结构 4.2 Access 连接到SQL Server 为了从 Access 连接 到 SQL Server 必须满足 三个 主要的 要 求 第一 个非 常明 显 必须 从运行 Access 的 系统 中登录 到 SQL Server 中 这 就 要求在 Access 系统 和 SQL Server 系统 之间有 网络 连接 并且 增 加一个 到 SQL Server 系统 的登 录帐 户 接下 来 SQL Server ODBC 驱动程 序必 须安 装在 Access 系统 上 必须 创建 目标 SQL Server 系统 的数 据源 最后 必须 在 Access 中创 建一 组链 接 表 这些链 接表 使用 ODBC 连 接访问 SQL Server 的表4.2.1 安装 SQL Server ODBC 驱动 程序 Microsoft SQL Server ODBC 驱动程序 是由 Microsoft Office 或者 Microsoft Access Setup 程序安 装的 正如 所希 望 的那样 如果 要求 Access 作为 Microsoft Office 套的 一部 分 就需 要运行 Microsoft Office Setup 程序 如果 使用 独立 的 Microsoft Access 版本 就需 要运 行 Access 的 Setup 程序 然而 在默 认情 况下 SQL Server ODBC 驱动 程 序是不 安装 的 如 果使用“Typical” 安装选 项来安 装 Access 或者 Office 那么 SQL Server ODBC 驱 动 程序将 不 出现在 系统 中 注意 注意 注意 注意 也可 以通 过安 装 SQL Server Client Management 实用 程序 在系 统上 安装 SQL Server ODBC 驱动 程序 为了对 已有 的 Access 安装 SQL Server ODBC 驱动程 序 需要重新 运行 Access 或者 Office 安装 程序 Office 97 中文 版安 装 程序显 示如 图 4-5 所 示的对 话框 4-5 Office 97 图 中文版安装对话框 为了在 一个 已有 Access 的 系统上 安装 Microsoft SQL Server ODBC 驱 动程序 先单击 对话框 中的 添加 删除 显示 维护 对话框 如图 4-6 所示 选择 Data Acess 复选框 然后单 击 更改 选项 则显示 Data Access 选项对 话框 如图 4-7 所示 图4-6 安装程序的维护对话框图4-7 安装程序的 Data Access 对话框 在 Data Access 选项 对话 框 上 选择 数据 库驱 动程 序 复选 框 然后 单击 更 改选项 按钮 则显 示如 图 4-8 所示 的数 据 库驱动 程序对 话 框 选择 Microsoft SQL Server 驱动程 序 复选 框 然后 单击 确定 接着 Office 97 中文 版 安装程序 把 Microsoft SQL Server ODBC 驱动程 序安 装在 系统 上 图4-8 安装程序的 Database Drivers 对话框 提示 在 SQL Server ODBC 驱动 程序 安装 之后 它 可 以由各 种 Office 程 序用来 访问 SQL Server 数据 库 4.2.2 配置 数据 源 安装 SQL Server ODBC 驱 动程序 本身 还不 足 以 开始从 Access 中使 用 ODBC 还需 要 创建一 个数 据源 为 了创建 SQL Server 的一 个 ODBC 数据 源 需要 从 Windows2000 的程 > > ODBC ODBC Windows 95/98 序 管理 工具 数据 源 运行 数 据源管 理器 假如 系统 是 或者 Windows NT 则要 从控 制面 板中 运行 ODBC 数据 源管 理器 打开 ODBC 数据源管理器图标 则显 示 ODBC 数据源 管理 器 对话 框 如图 4-9 所示 图 4-9 使用 ODBC 数据源管理器增加一个数据源 注意 有关ODBC 数据 源管 理器 提供 的选 项的 详细 信 息 参见 第 5 章 可以在 用户 DSN 标签 系统 DSN 标 签 或者文 件 DSN 标签 上 增加 一个 新数 据 源 如 果该数 据源 只是 用于 当前用 户 那么 选择 用户 DSN 标签 如果 该数 据源 可以 由 系统 中的 全 部用户 使用 那么 选择 系 统 DSN 标签 文件 DSN 标 签允许 创建 一个 可以 由其 他 用户共 享 DSN DSN 的 为了 配置 一个 新的 系统 数据 源 选择 用户 标签 然后 单击 添 加 则显 示 创 建新数 据源 对话 框 如图 4-10 所示 4-10 ODBC 图 选择一个 驱动程序 当前 安装 在 系 统上 的 所 有 ODBC 驱 动程序 都列在该 对 话 框 中 为了 安 装 SQL Server ODBC 驱动 程序 从列 表中 选择 SQL Server 然后单 击 完成 这时 显示 SQL Server DSN 配置 向导 对话 框 如图 4-11 所示图4-11 选择 SQL Server DSN 数据源名称 在该向导的第 一个文本框中 输入数据源 的名称 这个名称可以是任 意名称 其主要 作用是 为该 ODBC 数据 源 提供一 个有 意 义的名 称 说 明 字 段允许进 一 步地确 认 这个数 据源 接下来 从该 下拉 组合 框 中 选择 希望 该数 据源 连接的 SQL Server 系统 的名 称 所有 网络 上的 SQL Server 系统都 列在 组合 框中 名称“local” 用于 在 SQL Server 系统自身上的 ODBC 连接 一般 不使 用“local” 而是希望 从下 拉框 中选 择 联网的 SQL Server 系统 中的系 统 名称 单击 下一 步 按钮 则显 示 认证 对话框 如图 4-12 所示 图 4-12 指定 SQL Server DSN 认证 这种认 证允 许指 定将 要执行 的 SQL Server 的安 全性 认证 模式 如 果使 用集成 安全性 选择该 选项 可以 使用 Windows NT 认证 它 意味着 NT 口令可用于 连 接 SQL Server 系统 如果使 用 SQL Server 认证 那么 需要 使用 SQL Server 特定 ID 登录 到 SQL Server 中 单击 下一 步 则用 SQL Server 初始化连 接 并 且显 示 该连接 默认 的对 话框 如图 4-13 所示图 4-13 更改 SQL Server DSN 的默认数据库 该对话 框顶 部的 复选 框允许 设 置在 SQL Server 登录 中指 定的 默认 数据 库 在本 例中 可以看 到选 择 SQL Server 的示例数 据 库 pubs 作为默 认 数据库 对于其它 选 项连接 可以 使其他 项为 默认 值 单击 下一 步 按 钮则 显示 下一个配置 SQL Server DSN 对话框 如图 4-14 所示 图 4-14 设置语言 字符集和区域变量 该对话 框允 许设 置 SQL Server ODBC 驱动 程序 将要 使 用的语 言 字符 集和 区域 变量 对于大多 数用户的安装 默认的设置 是可接受的 单击 完成 则 完成数 据源 配 置 在这 之后 一 系列 可选 的对 话 框允许 测试 新创 建的 数据 源 在 SQL Server ODBC 驱 动 程序安 装和 使用 ODBC 管 理器 配 置数据源 之后 可以准 备 Access SQL Server 制作从 到 的连接 4.2.3 链接 表 Access 总是从打开一个本 地 的.mdb 文件 开始 .mdb 文件一 般位于本地的硬盘驱动器 上 但 是 它 也 可 以 位 于 一个网 络 共 享 的驱动器上 .mdb 文件包含了所有 Access 数据 库对象 例如 表 视图 查 询和报表 定义 正 如可 以从 一 个数据库系 统中 得到 的那 样 Access 的核心 数据 库组 件是 表 所有其 他数 据库 对象 都是 基 于在 Access 表 中定义 的数 据库 结构 创 建的 在 一 个 典型的单用 户实 现 中 Access 表包含组 成 数据 库 的 所 有 实 际 数据 但是 这 并 不是使 用 Access 作为 SQL Server 的前端那 种情 况 在 这 种模式 中 SQL Server 数 据 库包含 了 全 部表 和数 据 最初 这似 乎是 一个 问题 然而 Access 提供了 一 种 链 接表机 制 允许 本地的 Access 表使 用 ODBC 连接到远 程的 数 据库 表 全部 Access 的界 面 查 询 和报表 创 建组件都 可以使用链接 表 就好像它 们是本地表一样 在下 一节 中 将会 看到 如 何创建 一 组链接 表 这些 表链 接到在 SQL Server 的示 例数 据 库 pubs 中的 表 创建数 据库 是建 立 Access 数据库 应用 程序 的第 一步 建立一 个使 用 SQL Server 数据 库的 Access 应用 程序 没有什么 不 同 在图 4-15 中 可以 看到 新建 对话 框 它允许 Access 创建一 个新 数据库 单击 空数据 库 图标 然 后单击 确定 则 创 建一 个将 使用 链接 到 SQL Server 的表的 新数 据库 图 4-15 创建一个新的 Access 数据库 注意 为了 简单 起见 本 示例示 意创 建一 个新 的 Access 数据 库 它所包含的表将链接 到 SQL Server 的pubs 数 据库中的表 还可 以将链 接表添加到一 个 已 有的 Access 数据库 中 Access 显示 一个 对话 框 允许输 入将 要链 接到 SQL Server 数据库的 Access 数据库 的 Access .mdb 名称和 位置 在指 定数 据 库的名 称和 路径 之后 创 建一个 新的 文件 并且 显示 主 Acess 数据 库窗 口 如图 4-16 所示 在本 示例 中 Access 的 数据库名 称是 AccessPubs 正如所 期望 那样 该数 据 库存储 在 AccessPubs.mdb 文件 中图 4-16 创建一个新的 Access 表 在如图 4-16 所示 的主 Access 数据 库窗 口上 的新 建按 钮允 许创 建新 的 Access 数据库 对 象 第一 个标签允许创 建表 其他标 签允许创建新查 询 窗体 报表 宏和代码模块 为 了创建 一个 链接表 选择 表 标签 然后 单 击 新建 这时 显示 一个弹出对 话框 如 图 4-17 所示 图 4-17 选择 Access 表类型 这个对 话框 允许 选择 将要创 建 的 Access 表的类 型 当 设计一 个本 地的 Access 表时 一般选 择 数据 表视 图 设计 视图 或者 表向 导 来调 用 Access 的 本地数 据库 设计 实 用程 序 如果 希望从另 一个源 如 Excel 工作表 拷贝 表结构和数据 可以 选择 导入 选项 该选 项也 可以 用来从 一 个 ODBC 数据源 如 SQL Server 引入 数据 然而 在使 用 导入 选项和使 用 链接表 选项之间 有很大的不同 导入 选项在本 地 的 Access 数据库 中制 作一 个引 入数据 的 快照 拷贝 任何 以后 的 Access 操 作都只 影响 到该 数据 的本 地 拷贝 原始数 据源保持 不变 链接 表 选 项 创建一 个 ODBC 连接 该连接 将用于访 问存 储在远程 数据源的数据 没有执行数 据的本地拷贝 所有的 数据库操作都 在远程 数据库 表 中执行 为了 创建 一个 链 接到 SQL Server 表的 新 Access 表 从列 表中 选择 链接 表 然 后单击 确定 这时 显示 Access 链接 向导 如图 4-18 所示图 4-18 选择链接表源 该对话 框允 许选 择将 要用于 链 接表 的数 据源 为了 创 建一个 从 Access 表到 SQL Server 表的链 接 必须 单击 文件类 型下 拉 框 然后 从列 表框中 选 择 ODBC Database 就会出 现 ODBC Driver Manager 的 Select Data Source 对话 框 如图 4-19 所示 图 4-19 选择 SQL Server 数据源 在这里 必须 选择 使用 SQL Server ODBC 驱动 程序 的 ODBC 数 据源名称 然 后 单击确 定 该 ODBC Driver Manager 将加 载 SQL Server ODBC 驱 动程序 并且 启用 一个 到 SQL Server 的连接 如果 使用 混合 安全 模式 那么 ODBC Driver Manager 提 示输入一 个 SQL Server 登录帐 号 ODBC Driver Manager 将制作 一个 到 SQL Server 数据库 的连 接 该 数 据库是 在 创建数 据源 时 指 定的 然而 在登 录进 程 中 可 以忽略 这种 指 定 在 创 建了 到 SQL Server 的连接 之后 就列 出 SQL Server 数据库中 的表 清单 在图 4-20 中 可以 看到 pubs 数据库 中的 SQL Server 表清单图 4-20 选择要链接的 SQL Server 表 Access 总是 使用 所有 者前缀 区 分连 接表 因此 所 有 列出的 表由 前缀 dbo 开始 其后 是表名 通过 加亮 列表 中 的表 可以 选择 单个 表 或单击 Select All 可以 选择该 SQL Server 数据库 中的 全部 表 注意 当链 接到 SQL Server 上的文 件时 最 好逐 个 选择希 望链 接的 文件 选择全 部表 也包括 SQL Server 的系 统文 件 这些 不是 应用 程序文 件 他们 一般 不应 该包 含 在Access 数据 库中 链接一 个 Access 表和 一个 SQL Server 表 在本 地的 Access 数 据库中创 建了 一个 Access 表定义 在本 章的 这些 示 例中 链 接表是在 AccessPubs.mdb 数据 库中创建 链 接表定义 包 含该表 的结 构和 ODBC 驱 动程序 以及 登录 帐户 信息 这些 信息 是连 接 SQL Server 所需要的 有表 结构 的本 地拷贝允 许 Access 的设 计工 具当 创建 查 询 窗体和报表时提供非常好的应 答 然而 当链接表包 含所有表和列 信息的拷贝时 它并不 包含实际的数 据 基 表仍然 留 在 SQL Server 数据库中 不像 SQL Server Access 要求所 有可 修改 的表 都 有唯 一列标 识符 这是 由于 Jet 引擎 使用了一 种优化的记录 锁结构 在这 种结 构 中 根据 唯一列值进行 修改 之前 检 索要修 改 的每一行 然后 Jet 引擎检查这些列值 确保没 有其 他用 户 修改该 行 虽然 SQL Server 不要求 这样 但是 任何 由 Access 修改 的表 要求 如此 如果该 链接 表没 有唯 一列 标识 符 那 么该向 导显 示 Select Unique Record Identifier 对话框 如图 4-21 所示 该 对话 框 提示选 择一 个可用 作唯 一记 录标 识符的 列 图 4-21 选择唯一记录标识符可以在 SQL Server 表中选择 一个 列 使该 列用 作行 的唯 一标 识符 当 选择相 应的 列之 后 单击” 确定” 则创 建该表 的 链接 Access Table Wizard 进行下一 个 选 择的 表 也 可 以跳过 选择用 于唯 一索 引的 列 但是 如果 这样 做 那么 Access 将 把 这些表作 为只读表 不允许 对表进 行修 改 注意 在链 接到 SQL Server 表之前 确 保理 解了 将 要链接 的所 有表 的结 构 这不 仅是 创建数 据库 应用 程序 要求的 知 识 而且 还使 处理 Select Unique Row IDentifier 对话 框更加简 单 然而 如果在这种 进程 中产 生错 误 那么 总可 以从 Access 数据库 中删 除该 表 然 后 重新链 接该 表 从 Access 中 删除一个 链接 表并 不从 SQL Server 中删 除该 表 它只 是删 除本 地的 链 接 在链接 所有 要求 的表 之后 这些 表将 出现 在 Access 表 的清单 中 如图 4-22 所示 在 Access 中 所有 链接 表都与 ODBC 全局 图标 有关 而不 是与 用于 本地 Access 表 的 小表图 标有关 在图 4-22 中 可 以看到 来自 pubs 数据 库的 全部 表都 已经 链接 到相 应的 Access 表 当要求 的全 部 SQL Server 表链接 之后 可以 开始 使用 Access 的界 面 工具 来建 立数 据输入窗 体 查询 和报 表 它 们使用 SQL Server 表中 的数 据 Access 提 供了一 个丰 富的 开发 环境 他们包括用于快速生成数据 输入窗体 定 制查询和报表的工具 以及 使用 VBA 为执行复 杂的编 程任 务的 工具 在本 章 下一节 将介 绍这 些基本 步 骤 这些 步骤 要求 使用每 一 个 Access 的主数 据访 问工 具和 SQL Server 表 图 4-22 链接表清单 4.3 设计 Access 应用 程序 Access 连接 到 SQL Server 之后 我们 就可 以访 问 SQL Server 的数据库 信息 了 本节主 要介绍 如何 用 Access 的 各 类设计 工具 快速 设计 出各种 常 见的 Access 应用 比如 查询 窗体 报表 宏等 4.3.1 查询 设计 Access 的 查询 设计 器允 许 使用其 图形 化查 询生 成器 界 面快速 创建 查询 就像 Access 的 其他数 据库 工具 一样 查 询设 计器可 以用 于链 接 SQL Server 表 就像 链接 Access 内部表一 样简单 Access 查 询可 以 用作独 立的 工具 或者 作为 Access 窗 体或者 报表 的基 础 为了 使用 Access 的查 询创 建器 创 建一个 新查 询 首先 从主 Access 对话 框 中选 择查询标签 对 于一个 新数 据库 初始 化 的 查询对话 框是 空的 如图 4-23 所示 图 4-23 Access 数据库的 Queries 标签 为了创 建一 个使 用链 接的 SQL Server 表的 新查 询 单击新建 这将启动 Access 的查 询向导 Query Wizard Access 查 询向 导允 许创 建几种不同类型的 查询 查 询 向导可 以启 动设计视 图 简单查询 向导 交叉表 查询 向 导 查找 重复项 查 询向 导 或 者 查 找 不匹配 项 查询向导 简单查询向 导指导 用户创建一个 没有行选 择的简 单查询 交叉表查询 向导指导 用户创建 一个交叉查询 它修改来自 两个不同表列的 结果 查找重复项和 查找不匹配项 创 建的查 询可 定位 那些 要 么是 两 个表 之间 的冗 余 值 要么 是 在两 个表 之 间 没有 匹配值的列值 设计 视 图 调 用 基 本 的图形 查 询设计器 它 可 以 用 于 基本的 查 询和复杂的查询 Access 设计 视图如 图 4-24 所示 图 4-24 Access Query Design View 通过提 供包 含在 Access 数 据库中 的全 部表 的清 单 启动 Access 设 计视图 然 后从列表 中选择将 要包含在查询 中的表 所选 的表出现在设计 视图的 上部 表示在所选表 之间任 何 关系的 连接 在图 4-24 中 看到 这个 示例 查询 包括 三 个表 authors titleauthors 和 titles 如果熟 悉 pubs 数据 库 那 么就知 道 authors 表包 含了 样本 作者 信息 titleauthors 表提供 了 从作者 到他 们写 的书 之间的 链 接 titles 表 包含 了有 关 每一本 书的 书名 信息 提示 对于 快速 设计 查询和 报 表来 说 像 Access 的 Query Design View 这 样强大的 工具 可 以大 大节 约时 间 但 是它 们 并不能取代基本数 据库结构 的知 识 无论 使 用 哪一种 工具 为了 有效 地 使用这种工 具 必 须理 解数据 库 表及其关 系 在表窗口中将 列拖放到该屏幕下部的设计 网格中 可以选择列 在这 个示 例 中 可以 看到已 经选 择 au_lname au_fname title price 和 state 列 包括在 该查 询中 除了 state 列之 外 所有列都将在结果集中可见 包括 state 列是为 了 允许基 于列 值“CA”选择行 这样 在查询中 只包括来自 加利福尼亚的 作者的那些行 单击感 叹号按钮执行 该查询 并且 显 示结果 窗口 如图 4-25 所示 图 4-25 查询结果窗口 使用 Access 查 询设 计的 结果清 楚地 说明 Access 可以 快速 而 轻 易生成结 果的 威力 在 本示 例 中 Access 查 询 设计 只 使用 几 个 交 互 式 动 作建 立了 一 个包括行 选择条件的 三个 文 件 的连接 生成 这个 结果 时 不需直接 输入 任何 SQL 语句 在这 种表 面现象之后 Access 创 建了所 有要 求的 SQL 语句 使用 右击 结果 窗口 时 显示 的弹出 菜单 允 许看到由 Access 生 成来产 生结 果的 SQL 语句 生成查询不是 一次性进程 使用右击结果 窗口时 显示的弹出菜单 可 以在结果窗口和 设计窗口 之间选择 快速查看查询结果 再 切换到 设 计模式 仔细 调整查询条件 的能力允 许快速 开发 复杂 的查 询 当完成设计查 询时 单击保存按钮则保存 查询说明 关闭查询设计视 图窗口也要提示 保存查 询说 明 4.3.2 窗体 设计 Access 的窗 体设 计器 允许使 用 类似 Visual Basic 的窗 体设 计器 或者 通过 一组 向导 快速 地创建 简单 的数 据输 入窗体 像其 他的 Access 数据 库 工具一 样 窗体 设计 器可 以用 于链 接 的 SQL Server 表 就像 用于 Access 本地 表一 样 为 了启动 Access 窗 体设计 器 从 Access 数 据库窗 口中 单击 窗体 标签 然后 单击 新建 这时 显 示新建 窗体对话 框 如图 4-26 所示 该对话 框提 供了 用于 创建数 据输 入窗 体 的几 个选 项 设 计视图 选 项启动一 个类 似 Visual Basic 的图 形化 窗体 设计 器 适合 于设 计访 问一 个或 者多 个表 的定 制 窗体 窗体向 导 创建一 个基于一个表或者查询 的简单窗体 自动窗体有纵栏表 表格或者数据表等选 项 顾名 思义 纵栏 表布局一次提供 一行 数据 显示 在两 列 上 表格 布局 显示多行多 列 而数据表布 局在绑定 数据的网格上 显示多行 图 表向导创建可显 示几种 图形图表的窗 体 数据透 视表 向导创 建一 个包 含一 个 Excel 数据透 视 表的窗 体 选 择 窗 体向导 则 显示如图 4-27 所示的 窗 口 图 4-26 新建 Form 对话框 图 4-27 窗体向导窗口 窗体向 导很 类似 于查 询 向导 提 供的 那样 也 允许 你选 择 包 含在该 窗体 中 的 多表和 多列 在本示 例中 可以 看到 这 种特殊 的窗 体基 于 department 表 并且 包含 在 department 文件中 的两个 列已 经包 含在 窗体中 注意 Access 的数 据输 入窗 体操 作 SQL Server 表中 的数据 使用 这些 窗体进行的 任 何改变 都写 到 SQL Server 数据 库中 为了 使 pubs 数 据 库中的这 些表 保持 其原 始状态 本示 例使 用一 个 用户创 建的 表department Department 表 不是原始pubs 数据库中的一部分 创建它的目 的是说明窗体 的输入和修改而不必修改原始的 pubs 表 Department 表有 两个 列 Dep_ID 是整数 数 据类型 它也 是该 表的 唯 一键 Dep_Name 是一 个长 25 的字符 类型 列当选择 包含 在窗 体中 的表和 列 之后 单 击完 成生 成数据输入窗体 如下所 示 该数据 输入 窗体 说明 了 Access 快速生 成可 以使 用 SQL Server 表的数据输入窗体的 能 力 这种 数据输入窗体 只用两分钟就能生成 并且 不 要求编 码 该窗体允 许用户 输入 修 改或者 删除 department 表中 的行 在窗 口底 端的 数据浏 览 栏允 许在 SQL Server 表 中 向前向 后滚动 行 注意 Access 的数据 输入 窗体 和 动作查询 可以 执行 终 端用户有 数 据库许可执行 的动 作 必须 在允许终端用 户访问存储在 SQL Server 数据库中的公司数 据之前 实现安 全性 数据 库访 问规划 4.3.3 报表 设计 Access Access 报 表设 计器 使用 图 形化的 报表 设计 器界 面或 者 选择其 中一 个 的报表向 导 允许 快速 地创 建报 表 像 Access 的其 他数 据库 工 具一样 报 表 设计器 使用 链接 的 SQL Server 表 就像 使用 内部 的 Access 表一 样 当设 计报 表时 可以 在报表 本身 上包 含表 和行 选择条件 或者可以使 该报表基于一 个已经存在的查 询 在下一节 我们 将看 到 如何使 用 一个预 定义 的 Access 查 询 创建一 个新 的 Access 报表 Access Access 不像 查询 设计 器 它是自动确定表 之间的关系 报 表设计器 不能 确 定 不同的表之间 的关系—— 即使是列名 相同也是如此 然而 像窗体设 计器一样 报表设 计 器能够使 用一个已 存在的查询作为输 入 这样就允许 设计的查询也可以把双层条 件作为 报 表选择 条件 在图 4-28 中 可以 看到 显示 在查 询设 计 器中的 SQL Server 的 pubs 数据库 中 三个表 的样 本查 询 图 4-28 报表输入的样本查询如上所 示 该 样本 查询 创 建了一 个结 果集 根据库 存列出 销 售情况 stores 表通过 stor_id 列链接 到 sales 表 并且 为了 得到 该书 名的 信息 该查 询还 使用 title_id 列连 接 sales 表和 titles 表 然后 使用 Sales 名称 根据 Stores 保 存该查 询 当定 义该 报 表条件 的 查 询定义 之后 可以 使用 该查 询作为 在 Access 报表 设计 器的 输 入 为了使 用 Access 的 报表 设 计器创 建一 个新 报表 首先从 主 Access 数 据库对 话框 中选 择报表标 签 刚开始 对于一个新数 据库 报 表对 话 框将是 空的 为了创 建一个 新报表 单击新 建 则显 示如 图 4-29 所示 的新 建报 表对 话框 图 4-29 新建 报表对话框 报表向导 选项允许启动 一个可视 的报表设计器 允许 手工布局报表 报表向导指导 用 户通过一 系列对话 框 帮助创建有几 个列的报表 自 动报表选 项允许创建使用纵 栏表布 局 或者表格 布局的报表 纵栏表 布局提 供一次一行 有 两个列 的数据输出 表格布 局在 一个 单页面上 显示多行 和多列 图表向导 创建一个可以显 示几种图形的报表 标签向导用于 打 印几种 不同 类型 的邮 寄标签 创建新 报表 的最 简单 方法是 使 用 Access 的报表 向导 在图 4-29 中 在上面 的列 表中 选择报表 向导 在下拉 列表框中选择 以前创建的查询 名称 单击确定 显 示下 一 个报表 向 导对话 框 如图 4-30 所示 图 4-30 报表向导列选项对话框在上面的下拉 列表框 报表向导对话框显 示要作为输入的查询名称 最初 包括在查 询中的全 部列都将列在 该对话框左端 的列表框中 并 且该对 话框右端的列 表框是 空的 可 以单击左 端列表中 的每一个列名 然 后单击右箭头按 钮 把该列增加到右端列表 选择 包 含在查询 中的字段 同样 可以在右 端列表中选择将 要删除 的列名 然后 单击 左 箭头 从 报表中 删除 该列 当选 择 了所有 希望 的列 之后 单 击 完成按 钮则 生成 如图 4-31 所 示 的报表 图 4-31 一个 Access 报表 该报 表进 一步 说明 了 Access 的快速数 据库 开发 能 力 使用一个预创建的查询作为输 Access 入 这个 报 表执 行 了一个 三文 件连 接 并 且在几 分 钟内创建 用户可能会 看 到 尽 管它可以 快速而简单地 创建报表 但 对于挑选报表和 列标题 它并不能做 得很好 显然 使用 SQL Server 的表和 列标 题并 不是 最友 好的 报表 布局 然而 一旦 报表生成之 后 就可 以快速 改变 报表 的标 题 为了定 制该 报表 选择 Access Database 对 话框上的 报 表标签 然 后单击 报表 选择 希望 定 制的报 表 当加 亮希 望的 报 表之后 可以 单击设计来调用 Access 报 4-32 表设计 器 如图 所示 图 4-32 使用报表设计器定制报表 所选的报表将 会显示在报表设计器中 从 报表设计器中 可以 单击 希望修改的字段 然后输入 新的标题 值 改变该报表的 标题和列标题 同样 将 字段从一个位置移 动到另 一 个位置 可以 改变 报表 的 布局 提示 图 形化的报表设 计器是一 种易于使用的 通用工 具 即使 如此 一般最好使用 相应的向 导生 成初 始的 报表 然后使用 报表 设计 器调 整 报表 4.3.4 宏设 计 Access 宏 允许 自动 重复 操 作或者 组合 相关 操作 的批 例如 可以 使用 Access 宏运行 一 系列的 查询 或者 报表 显 示预先 确定 的数 据 输入 窗体 的顺序 或者 拷贝 和更 名 Access 数据 库中的 全部 表 宏不 能提供 由 VBA(Visual Basic for Application) 代码提 供的 更细的 控 制 但 是它们仍然是一种强大的自动化工具 为了创建一个新的 Access 宏 必须先在 Access Database 对话框中的宏标签上选择一个宏 然后 单击 新建 这时 显示 宏的 编辑 器 如图 4-33 所示 图 4-33 运行一批报表的 Access 宏 在 宏 编辑 器中 可 以看到 一 个宏 QueryBatch 该 宏 组合了 两 个 查 询和一个报表 操作 列控制指 定的动作 这 些动作的每一 步都在宏中执行 备 注 列 是 简 单地描 述将 要 执行的 动 作说明 用户 可能 会猜 测 OpenQuery 动作运行一 个已有的查 询 而 OpenReport 动作运 行 一个已有 的报表 在该 窗口下半部分 的文本字段中指 定该动 作将要应用到 的数据 库对象 在图 1-33 中 可以 看到 OpenReport 动作 将打 开保 存的 报表 Sales by Stores 视图字段表 示 该输出 将定 向到 打印 机 除了 Open Query 和 OpenReport 外 Access 还支持 许 多动作 表 4-2 列出 了由 Access 支持的 不同 的宏 动作 表 4-2 Access 宏命令 动作 描述 创建一个定制菜单 AddMenu 限制数据 ApplyFilter 发出嘟嘟声 Beep 取消引起宏运行的事件 CancelEvent 关闭指定的窗口 Close 拷贝指定的对象 CopyObject 删除指定的对象 DeleteObject 在屏幕上显示文本 Echo 续表 动作 描述 找到匹配 FindRecord 条件的下一条记录 FindNext 查找匹配搜索条件的第一条记录 FindRecord 集点设置在窗体的指定控件上 GoToControl 在活动的窗体上移到指定的页面上 GoToPage 移动到指定的记录 GoToRecord 鼠标指针切换 Hourglass 最大化活动窗口 Maximize 最小化活动窗口 Minimize 移动和调整活动窗口 MoveSize 显示一个消息框 MsgBox 显示指定的窗体 OpenForm 打开指定的代码模块 OpenModule 运行指定的查询 OpenQuery 运行指定的报表 OpenReport 打开指定的表 OpenTable 引出数据库对象的内容 Output To 打印数据库对象的内容 PrintOut 退出 Access Quit 重新命名指定的数据库对象 Rename 刷新指定的屏幕对象 RepaintObject 刷新一个查询 Requery 当前窗口恢复为其前一个窗口 Restore 执行 Windows 应用程序 RunApp 执行 VBA 函数 RunCode 执行 Access 菜单命令 RunCommand 执行 Access 宏 RunMacro 执行 SQL 语句 Run SQL 保存指定的对象 Save 选择指定的对象 SelectObject 击键 SendKeys 通过电子邮件发送一个对象 SendObject 设置菜单项的状态 SetMenuItem 设置控件或者数据库字段的值 SetValue 切换系统消息的显示 SetWarnings 删除全部过滤器 ShowAllRecords 显示指定的工具栏 ShowToolbar 终止所有宏的执行 StopAllMacros 终止当前宏 StopMacros 从数据库中引入引出数据 TransferDatabase 从电子表格中引入引出数据 TransferSpreadsheet 引入引出一个 ASCII 文本文件 TransferText可以看 出 Access 宏可 以 快速构 造 并且 提供 了 各种 功 能模块 可以 使用 宏模 拟 VBA 应用程 序 可以 保存 宏作为 Visual Basic 的模 块 为 了保存 一个 已经 存在 的宏 作 为 Visual Basic 模块 右 击宏名称 然 后从弹出的菜单中 选 择 另 存 为/导出选项 这时 显示该 宏的另 存 为 对话框 如图 4-34 所示 选择 另存为 Visual Basic 模块单选 按 钮 然后 单击确定 把该 宏 转变成 VBA 代码 图 4-34 宏保存为 Visual Basic 模块 4.4 开发 VBA 应用 程 序 宏是一个自动化常用任务和模拟应用程序的很好工具 它没 有提 供与 VBA 代码相同 层次的 应用 程序 控件 VBA 是一种封闭 的 Visual Basic 编 程语言 这种 Access 实 现 提供了 许多 Visual Basic 有 的高 效率工 具 例如 像 Visual Basic 一样 Access Module 编 辑 器支持 彩色代 码关 键字 语 句完成 语法 检查 和集 成调 试 VBA 非 常适 合 于 复杂的任 务 还可 以 Access VBA 用于操 纵 之外的其他对 象 例如 标 准 的操作系统文 件 另外 代 码 提供了 一 层在 Access 模块 中没 有的错 误 处理 Access 提供 了 DAO( 数 据 访问对 象)对象框 架作 为 Jet 引擎 的 一 种编程 界面 可以 使用 VBA 和 DAO 创建 数据 库 应用程 序 这 些应 用程序 可以 使用 本地 的 Access 数 据 库或者 SQL Server 数据库 VBA Access SQL 本章的 下一 节介 绍一 些常见 的 编码 技术 可以使 用 这些技术在 中访问 Server 数据库 第一 个示 例说 明如 何使 用 DAO 执行 一个 简单 的查 询 第二 个示 例说 明如 何 建立和 执行 参数 化查 询 第三个 示例 说明 如何 用 SQL Passthrough 调用 SQL Server 的存储 过程 使用 SQL Passthrough 为了创 建一 个新 的 Access 代码模 块 从主 Access Database 对 话框中 选择 Module 标签 Access VBA 然后单 击新 建 这时 显示 代码 编辑 器 当代 码 编辑器 显示 时 创建 函数或 者 子例程 执行 定制 的数 据 库访问 提示 当在Access 中写VBA 代码时 最好 创建 函数而 不 是子 例程——即 使该代 码模 块 不需要 返回 任何 值 对于 Access Macro Builder 或者 Switch board Add-in VBA 子例 程是 不可 见的 而函数 可见 4.4.1 使用 DAO 记录 集 DAO 记录 集对 象表 示一 个 查询的 结果 使用 DAO Database 对象 的 OpenRecordset 方法创建 Recordset 对象 正如 所希 望的 那样 DAO Database 对 象表示 一个 Access 数据库 下面的 DAOQuery 函数说明 如何 从当 前数 据库 中创 建一 个新 记录 集 然 后访 问 包含在 该记 录集中 的全 部行 和列 Private Function DAOQuery() Dim db As Database Dim rs As Recordset Dim fld As Field Set db = CurrentDb Set rs = db. OpenRecordset("Select From dbo_authors", dbOpenDynaset) Do Until rs.EOF For Each fld In rs.Fields Debug.Print fld Next rs.MoveNext Loop rs.Close End Function 在这 个子 例程的开始 声明了几个 DAO 对象类型变 量 用于 存 放 DAO Database Recordset 和 Field 对象 接下 来 把当 前数 据库 的值赋 给 db Database 对 象变量 CurrentDB 对象是 一个 表示 当前 数据库 的 Access 对象 在本 示例中 变量 db 是 AccessPubs.mdb 数据 库的一 个实 例 它包 含了链接 到 SQL Server 的 pubs 数 据库的 表集 接下 来 使用 Database 对象上 的 OpenRecordset 方法创建一 个新的记录集 对象 rs OpenRecordset 方 法带 两个参 数 第一 个参 数是 一条 SQL 语句 它定 义将 要返 回 的结果 集; 第二个 参数 是一 个常 量 它指定 将要 创建 的 Recordset 的类 型 在本例 中 SQL Select 语句 从表 dbo_authors 中检 索全 部行 和列 该表 链接 到 SQL Server pubs 数据库 中的 authors 表 常量 dbOpenDynaset 表示这将 是 Dynaset 类型 的 Recordset 它支持修 改以 及 前后 滚动 执 行 OpenRecordset 函数 发送 SQL 请求 到 SQL Server 然后 返回 满足 该请 求的 结果 集 使用 Do Until 循环 可以 访问 记录 集对 象的 内容 在这 个循 环内 执行 rs Recordset 对象 的 MoveNext 方法 在 Recordset 对象内向前移动当前游标的位置 该循环一直执行到 Recordset 对象 的 EOF 属性为 真为 止 这就 表示 读完了 Recordset 中 的所有 行 在 Do Until 循环内 使用 For Each 循 环处理 每一 行的 Field 对象 在 DAO 中 Field 对象 表示一个列 当在 记录集对 象中的全 部行和列 都 读 完之 后 调用 Close 方法释放记录 集对象 并且 重新 声明它 使用 的资 源 4.4.2 使用 带有 参数 的查 询 前面的 DAO 示例 说明 使用一 个 动态 SQL 语句 来创 建一 个 Recordset 对象 构 造 和执行 动态的 SQL 语句是非 常简单 的 当每 一次 执行 这些 SQL 语句 时 必须 对这 些语 句进 行语法分析 并创 建执 行规 划 这对于 ad hoc 查询类 型的 应 用程序 很好 因为它们很少重 复 相 同 的语句 但是 对于在线事务处 理(OLTP) 类型的应用程序 就不是最好的方法 因为它多次 执行同 一条 语句 使用 预 先准备 的 SQL 语句 OLTP 类 型的应用 程序 可以 得到 最 好的性 能 不像动 态的 SQL 语句 每 一次运 行这 些语 句时 都 必 须创建 新的 执行 规划 而使 用预 先准 备的 SQL 语句 只 创建 一 次执行 规划 并且 是在 准备该 语 句时 创建 所有 以后 的执 行使 用 已有的 访问 规划 不需 要 创建新 的执 行规 划 这就 使 重复执 行预 先准 备的 SQL 语 句 比动态 的 SQL 语句的性 能有 了极大 的 提高 如果每 一次 执行 准备 好的 SQL 语句 时都 需要维 护 完全相 同 的 SQL 语 句和选 择标 准 那么它并不非常灵活 对于应 用 程 序需要执行的每一次查 询 都 需 要一 条完 全 不 同的 SQL 语句 幸运 地是 预 准备的 SQL 语 句能 使用参 数标 记 它 实际上是数 据值 的占 位符 参数 允许用 不同 的数 值重 新使用 同 一条 SQL 语句 Access 通过 Query Definition 对象支持 参数化查 询 使用 VBA 代 码或使 用查 询设 计器 创建 查询 可以 在 Access 中创建 一 个参 数化查 询 在图 4-35 中 可以 看到 使用 一个 输入 参数 的 Access Query Definition 图 4-35 使用查询设计器创建参数化查询 SQL Server Stores Sales titles 这个查 询定 义执 行了 三个 文件 的连 接 和 表 这 种特殊 的查询从 输入文件中检 索商店名称 商店状态 书 名 和销售 数量 列 不像 本章 前 面提出 的 比较简 单的 查询 在执 行 该查询 之前 该查 询提 示匹配 状 态列 的值 在条件行中的 Enter the State 子句 告诉 Access 在运行 该查 询之 前 提示 该 列的值 该 Access 查 询定义保 存在 State 的 Sales 下 如果 直接 从 Access 运行 该查 询 那么 出 现一个 对话 框 提示 输入 一 个 state 代 QueryParm VBA DAO 码 下面 的 函数说明 如何 使用 和 执行 一 个参 数化查询 Function QueryParm() Dim db As Database Dim qd As QueryDef Dim rs As Recordset Dim fld As Field Dim sStateCode As String Set db = CurrentDb sStateCode = InputBox("Enter the state code") Set qd = db.QueryDefs("Sales for State") qd.! [Enter the State]= sStateCode Set rs = qd.OpenRecordset() Do Until rs.EOF For Each fld In rs.Fields Debug.Print fld Next rs.MoveNext Loop rs.Close End Function 像前面 的 DAO 示例 一样 这个函 数开 始声 明用 于 DAO 对 象的变量 以及 将要 包含 用 户提供 的 state 代码 的字 符 串变量 使用 CurrentDb 对象 db Database 对 象赋值 当前 的数 据 库 然后 一个 简单 的 InputBox 用来从用 户获 得 state 代码 该代 码存 储在 sStateCode 字 符串中 然后 qb QueryDef 对象 设置 为在 前面 使用查 询 设计 器创 建的 查询 定义名 称 接下来 这个 包含 在 sStateCode 变量 中的 输入 state 代 码赋值 给该 查询 的输 入参 数 该 查询的 输入 参数 使用 Enter the State 名称( 包 括 方括号) 它与在查 询设 计器 中 建立的 输入 参数同名 在赋值 输入参数之后 通过 OpenRecordset 函数执行该查询 然后 由该查询 返回的 结果 集内 容显 示在 Access Debug 窗口 中 4.4.3 使用 SQL Server 存储过 程 使用参 数化 查询 可以 显著提 高 需要 反复 执行 SQL 语句的 Access 应 用程序的 性能 然 而 可 以使 用存 储过 程获得从 Access 到 SQL Server 应用 程序的 最好 性能 非常 像预 准备 的 SQL 语句 存 储 过 程 的执 行 规划在存储 过 程 执 行 之 前创 建 然而 应用 程序 每运 行一 次 预准备的 语句必须准备 一次 而存储 过程的执行规划 只准备 一次 且 在 存 储过程 创建时 准 备 因为 没有 必要 分析 到 来的 SQL 语句 的语 法或 者准备 一 个执 行规 划 所以 存储 过程 为访 问 SQL Server 数据库提 供了 最高 的性 能机 制 SQL Server 存储过程还能够在存储过程中执行多个操作( 称为批) 并且可能 返回 多个 结果集 下面 的 CallSP 函数 说明 如何 使用 Access VBA 和 DAO 来调用一个 返回 多个 结果 集的 SQL Server 存储过 程 Function CallSP() Dim db As Database Dim qd As QueryDef Dim rs As Recordset Dim fld As Field Set db = CurrentDb On Error Resume Next db.QueryDefs.Delete ("Pubs_Report_1") On Error GoTo ErrorHandler Set qd = db.CreateQueryDef("Pubs_Report_1") qd.Connect = "ODBC;DSN=TECA SQL Server;UID=sa;PWD=;DATABASE=pubs" qd.ReturnsRecords = True qd.SQL = "reptq1" Set rs = qd.OpenRecordset() On Error Resume Next db.TableDefs.Delete "TempResults" db.TableDefs.Delete "TempResults1" db.TableDefs.Delete "TempResults2" db.TableDefs.Delete "TempResults3" db.TableDefs.Delete "TempResults4" db.TableDefs.Delete "TempResults5" db.TableDefs.Delete "TempResults6" On Error GoTo ErrorHandler ''Now process the next result set using a Jet table Set qd = db.CreateQueryDef(" ") qd.SQL = "Select Pubs_Report_1. Into TempResults From Pubs_Report_1" qd.Execute Set rs = db.OpenRecordset("TempResults") Debug.Print "Recordset 1" Do Until rs.EOF For Each fld In rs.Fields Debug.Print fld Next rs.MoveNext Loop Set rs = db.OpenRecordset("TempResults1") Debug.Print "Recordset 2" Do Until rs.EOF For Each fld In rs.Fields Debug.Print fld Next rs.MoveNext Loop Set rs = db.OpenRecordset("TempResults2") Debug.Print "Recordset 3" Do Until rs.EOF For Each fld In rs.Fields Debug.Print fld Next rs.MoveNext Loop Set rs = db.OpenRecordset("TempResults3") Debug.Print "Recordset 4" Do Until rs.EOF For Each fld In rs.Fields Debug.Print fld Next rs.MoveNext Loop Set rs = db.OpenRecordset("TempResults4") Debug.Print "Recordset 5" Do Until rs.EOF For Each fld In rs.Fields Debug.Print fld Next rs.MoveNext Loop Set rs = db.OpenRecordset("TempResults5") Debug.Print "Recordset 6" Do Until rs.EOF For Each fld In rs.Fields Debug.Print fld Next rs.MoveNext Loop Set rs = db.OpenRecordset("TempResults6") Debug.Print "Recordset 7" Do Until rs.EOF For Each fld In rs.Fields Debug.Print fld Next rs.MoveNext Loop qd.Close rs.Close Exit Function ErrorHandler: DisplayDAOError End Function CallSP 函数 开始 声明 了对 象变 量 用于 DAO Database Recordset QueryDef 和 Field 对象 然后 使用 CurrentDb 对象把 当前 的数 据库 赋 予 db Database 对象 接下来 使用 On Error 语句 激活 VBA 的错 误处 理 在本 示例 中 如果 后面 的 QueryDelete 方法失 败 On Error Resume Next 语句 用来 允许 进程继 续 进行 QueryDelete 方法用 来删 除 任何存 在的 查询 定义 Pubs_Report_1 的实例 删 除 任 何旧的 查询 定义 允许 CallSP 函数不 碰 到错误 继续 运行 接下来 dbDatabase 对象的 CreateQueryDef 方法用来创建一个新的查询定义 Pubs_Report_1 因为 这是 一个 新的 数据 库对 象 所以 SQL Server 连接 信息 必须赋 于 Query Definition 的 Connect 属性 然后 ReturnRecords 的 属性设 置为 True 通知 DAO 该 Duery Definition 将返 回一 个结 果集 接下 来 SQL 属性设置 为 reptq1 这是 pubs 数 据 库中已有 的存储 过程 的名 称 下 面 的程序 清单 列出 了构 成 reptq1 存 储过程 的 Transact-SQL 语句 Create Procedure reptq1 As Select pub_id, title_id, price, pubdate From titles Where price is Not Null Order By pub_id Compute Avg(price) By pub_id Compute Avg(price) 当在 pubs 数据 库上 运行 时 Compute 语句中 的 By 子句使该存储过程生 成七 个结 果集 为了 处 理 多 个 结果集 Access 必须 创建 多个 临时表 每 一个表 保存 一个 结果集 代码 的 下 一段使 用 TableDef.Delete 方法 删除 任何 存在 的旧 临时 工作 表 接下来 创建 一个 新的 QueryDef 它使 用 Pubs_Report_1 查 询作为 输入 然 后输出多 个临 时表 每 一个 临时 表 包含一 个不 同的 结果 集 该 DAO 对 象框架自 动 计 数 每 一个不 同 的结果 集 在本 示例 中 第一个 结果 集是 TempResults 第二 个结 果集 是 TempResults1 等 等 当运 行 Query Definition 的 Execute 方法时 处 理 多个结 果集 之后 每一 个 不同的 结 果集内 容用 前面 提供 的相同 技 术输 出到 Access Debug 窗口 中 4.4.4 错误 处理 在 CallSP 函数 和其 他 VBA 函数之间 一个明 显的 不同 点是 前者 使用 了错 误处 理 使 用 On Error 语句 允许 Access 捕捉 和回 应 运行时 的错 误 否则 它将 生成 应用 程序 错误 前面的 CallSP 函 数使 用两种 不同 的方 式处 理错 误 首先 On Error Resume Next 语句 用于知道 错误可能发生 的位置 在碰 到错 误 时 程序 继续执 行 而不中断 下一个 语句的 执 行 另外 On Error Goto ErrorHandler 语句 用来 捕捉 和回 应未 知的 错误 未知 的错 误是不 能预料的 因此 如果 碰到一个出乎 意料的错误 那 么程序 的执行不应该 正常继 续进 行 在这种情况下 该错误句柄执行 DisplayDAOError 函数 通知用户碰到了错误 子例 程 DisplayDAOError 如下 所示 Private Sub DisplayDAOError() Dim er As Error For Each er In Errors MsgBox "Number: " & er.Number & vbCrLf & _ "Source; " & er.Source " vbCrLf & _ "Text: " & er.Description Next End Sub DisplayDAOError 子例 程开 始时 声明 一个 包含 DAO Error 对 象的变量 接下 来 For Each 循环用 来在 DAO Errors 集 合中叠 代 循环 是必 要的 因为 Errors 集 合可以 包含 多个 错误 成 员 其中 每一 个成 员保 存 不同错 误条 件的 信息 在 For Each 循环 内 对于 每一 个显 示有 关 错误条 件的 基本 信息 创 建一个 Msgbox 在本 示例 中 显示 错误 信息 号 源代码行和文 本 描述 4.5 编 程 心 得 对于使 用 Access 执行 的任何 客 户机 服务 器类 型的 数 据库 应 用程序开 发 可以 应用 几 个常见 的提 示 当使 用 Access 连 接 到某 个外 部数 据库中的表时 遵循 下面 一些 建议 使用链 接表 而不 是 OpenDatabase 方法 链 接表 在本 地 存储数 据库 结构 允许 Access 避免每 一次 打开 表时 从表中 检 索 确信只 检索 需要 的数 据 本地的 Access 数据库 应用 程 序经常 检索 比需 要多 得多 的 数据 然 后执 行外 部的 函 数来 查 找到真正 需要 的数 据 可以使用 SQL Select 语句 的 Where 子句 限制从 SQL Server 中返回的 数据 大大 降低 服务 器 上 所需的 磁 盘 I O 以及 发送 结果 到 Access 所需 的网络 带宽 尽可能地 使用只向前的 记录集 至今 为止 只向前的记录集是最 好的记录集执 行 类型 它们 还使 用比 较少的 本 地资 源 只是当应用程 序确实需要修改记录集的能力时 才使 用可修 改的记录集 可修 改 的记录 集功 能更 强 但 是 也需要 比只 读的 记录 集更 多 的资源 尽可能 地使 用存 储过 程 SQL Server 存储过 程为 访问存 储 在 SQL Server 中的 数据 提供了 最快 的速 度 避免多 机种 环境 的连 接 多机种 连接 是 Access 和 DAO 的 功能之一 因为 它们允 许 Access 和其 他数 据源 中 的表连 接 然而 这几 乎总是需要创建一个 或者 多个 临 时表 它还要求本地的 Jet 引擎 执行 实际 的连 接 它 把工作 负荷 从高 端的 服务 器 移动到 通常 低端 的客 户机 Access 开发 应用 程序 的强大 工 具 可以快 速地 创建 连 接到 SQL Server 数据库 的客户机 服 务 器 数 据 库应用程序 然而 尽 管 其 生产效率很 高 Access 还是局 限 于一定 的 范围 和 灵活性 为了 完全 控制 定 制的应 用程 序开 发 需 要研究专门 的 应用程序开 发环 境 例如 Visual Basic 和 Visual C++ 在后 面的 章节 中将 陆续 介绍 有关 方面 的 内容 另外 在 SQL Server 与 Access 的集 成方 面还 涉及到 数 据转 换问 题 因为 SQL Server 是一种 比 Access 更为强 大 灵活的 数据 库工 具 使用 Access 开发 SQL Server 数据库 应用程序有时并不能完 全满 足要 求 因此就 需要 将 Access 数 据 转换成 SQL Server 数据 然 后 直接用 SQL Server 进行开 发 SQL Server 2000 中的 DTS 使得 这一 工作 非常 容易 但 具 体内容 不属 于本 章讨 论的 范 围第 5 章 使用 ODBC 访问 SQL Server 数据 库 ODBC 是 由微软 定义的一 种数据库 访 问 标准 提供 了一 种标 准的 桌 面数据库 访问 方法 以访问 不同 平桌 台上 的 SQL 数据库 本 章将介 绍如 何在 Visual Basic 中使 用 ODBC 应用程 序编程 接口(API) 开发 SQL Server 数据库 应用 程序 5. 1 ODBC 概述 ODBC 是微软公 司 给出的访问数据库 的 API 已 经 成为 事实上的数据库 访 问接口的工 业标准 ODBC 虽然 可以 使用 一个 ODBC 应用程 序访 问在 本地 PC 数 据库上的数 据 但是 它主要 用于 访问 在多 机种平 台 上的 数据 库 例如 SQL Server Oracle 或者 DB2 虽然 ODBC 最初是 作为 一种 Windows 标准 但是 它也 在其 他几 种平 台上 实现 这些 平台 包 括 OS/2 和 UNIX ODBC API 是独立于数据库的 并且基于由 X/Open 和 ISO/IEC 开发的 SQL/CLI(SQL/Call Level Interface) 规范 ODBC 3.0 标准由 76 个 函数组成 这 些 函数在 Microsoft 的 ODBC3.0 参考 与 SDK 指南中 表面 上 ODBC API 由一 组函 数调用 组 成 但 ODBC 的核 心是 SQL ODBC 函数的 主要 功能 是将 SQL 语句发送到目 标数据库 中 然后 处理这 些 SQL 语句产 生的结 果 显然 服务 器必 须支持 SQL 以 便通过 ODBC 来访问 5.1.1 特点 ODBC 是通 过一组 标准 的函 数调 用来 实现 的 ODBC 的 一 个最大的优 点是 它是 一种 被广泛 地采 纳的 桌面 标准 使用 ODBC 时 没 必 要理解 这些 函数 使用 ODBC 所需的所有 代码都 创建 在支 持 ODBC 的 应用程序 中 像 Microsoft Access Word Excel 或者 Visual Basic 对于桌 面应 用程 序 ODBC 已经成 为一 种广 泛采 纳的 数据 库访 问标 准 并且 有 180 多种桌 面应 用程序支 持 ODBC 用户只需 要知道如 何使用这 些 应 用程 序 应用程序的基层已经 编 码以使 用 ODBC API 这 种功能 应用 到终 端用 户应 用 程序中 像 Word 或者 Excel 以及许多 ODBC 的客 户机/服务 器开 发工 具 不 必 真正理解 ODBC API 就可 以写 基于 ODBC 的客户 机/ 服务 器应 用程 序 例如 Visual Basic PowerBuilder Delphi 和 Visual C++ 都提供了它 们自己 的代 码层 来封 装 ODBC 函数——使程序开发 人 员访 问 ODBC 功能 而不需直接理 解 ODBC 函数 5.1.2 通信 机制 就像 大 多 数技术一 样 通 过 由各 种 客 户机/ 服 务 开发工 具提供的 内 置 数据访问 函数 越 是较好 地理 解应 用程 序的深 层 次 越能更 加 有效 地使用 ODBC 由于 ODBC 主 要 是用于连 接 PC 到不 同的 客户 机/服 务器数 据库 系统 所 以它 一 般使用 了许 多网 络组 件 图 5-1 示意 了由 ODBC 连接 使用 的通讯 层 在顶 层 可以 看到 支 持 ODBC 的 应用程 序 它调 用由 ODBC CLI 提供的各 种 ODBC API 函数 在 ODBC CLI 下面是网 络进程通讯(IPC) 机制 它允许不 同的网 络进 程互 相通 讯 不同的 IPC 机制 示例 是 Nemed Pipes 和 TCP/IP Sockets 所使用的实际 IPC 机制 依赖 于网 络协 议 这些 协议 可 下 一层即 网络协 议层 找到 网络协议负责发送和接收通过网络的数据流 不同网络协议的示例是 NetBEUI 和 TCP/IP 图5-1 ODBC 使用的网络组件 在这个通讯堆 栈的底部是物理网络 物理 网络包括适配器卡和实际连 接网络 系统所需 的缆线 Ethernet 和 Token Ring 是两 个最 常用 的网 络 拓扑结 构样 例 5.2 ODBC 组成 在详细 介绍 如何 编写 ODBC 应用程序 之前 先看 一看不 同的 ODBC 组件 这有助 于 理 解应用 程序 如何 关联 ODBC 体系结构 并且 看到 用来连 接 ODBC 兼容 数 据 库的路 径 图 5-2 说明了 由 ODBC 使 用的 分 层体系 结构 图5-2 ODBC 组件 5.2.1 应用 程序 ODBC 应用 程序要 么是 一种 像 Wo r d Excel 或者 Visual Basic 的 应用程 序 要么 是一 种 定制的 ODBC 应用 程序 使用 Visual Basic Visual C++ 或者一些 其 他 PC 开 发平台编写 ODBC 应用 程序与 ODBC 驱动 管理 程序(ODBC32.DLL) 进行静态或 动态连接 并且 ODBC 应用程 序调 用由 ODBC 驱 动管理 程序 提供 的 ODBC API 函数5.2.2 驱动 管理 程序 支持 ODBC 的应 用程 序 像 Visual Basic 调用 ODBC 驱 动管理 程序( 包括 16 位的应 用 程序 ODBC DLL 或 32 位的 应用 程序 ODBC 32 DLL) 注意该应用 程序 不能 直接 调用ODBC 驱 动程序 它可 调用 包含 在 ODBC 驱动 管理 程序 中的 函数 而 ODBC 驱 动管理 程 序程序 调 用相应 的 ODBC 驱动 程序 这样 就保 证 ODBC 函数 无论是 连接 到 SQL Server 还是另 外一 个数据 库平 台 例如 Oracle 总是 按同 一种 方式 调用 这种分 层的 体系 结构 还允 许多 个 ODBC 驱动程 序共 存并 且同 时激活 ODBC 驱动 管理 程序 负责加 载 相应 的 ODBC 驱动 程 序到内 存中 并将以后 的 请求传送 给正确 的 ODBC 驱动 程序 当 ODBC 驱动管 理程 序 加载一 个 ODBC 驱 动程序时 ODBC 驱 动管理 程序 建立 一个 指向 ODBC 驱动程序 中函 数的 指 针表 ODBC 驱动 管 理程序 使 用名为 连接句 柄的 标识 符来 确认每 一 个加 载的 ODBC 驱动程 序的 函数 指针 5.2.3 驱动 程序 ODBC 驱动 程序负 责发 送 SQL 请求到 关系 型数 据库管 理 系统(REDMS) 中 并且 把结 果 返回给 ODBC 驱动 管理 程 序 然后 ODBC 驱动 管 理程序 把这 些请 求传 送给 ODBC 客户 机应用 程序 每一 个兼 容 ODBC 的数据库 都有 其自 己 的 ODBC 驱 动程序 例如 SQL Server ODBC 驱动 程序只 与 SQL Server 通讯 并且 不能 用于访 问 Oracle 数据 库 同样 Oracle ODBC 驱动程 序不 能用 来访 问 SQL Server 数据库 对于 有些 系统 ODBC 驱 动程序可 能重 新格 式 化 SQL 请求为由 目标 RDBMS 要求的 正确 的 SQL 语法 ODBC 驱动 程序处 理从 ODBC 驱 动管 理程 序中 传送过来的 ODBC 函 数调用 在每 一 个 ODBC 驱动 程序 中的 函 数通过 由 ODBC 驱 动 管理程 序 维护 的函 数指 针来调用 5.2.4 数据 源 使用 ODBC Administrator 程序创建 数据 源 通 过使 用 用户创 建的 名称 关联 一个 目标 关 系型数 据库 和 ODBC 驱动程 序 数据 源隔 离 用户和 ODBC 连 接的基 本机 制 然后 用户 使 用有意 义的 数据 源名 称访问 该 数据 库 但不 需 要 知道有 关 ODBC 驱动 程 序 或者关 系型数据 库的技 术细 节 当 ODBC 应用 程序 第一 次 连接到 一 个目标 数据 库时 它把 数据 源名 称传 送到 ODBC 驱 动管理 程序 中 然后 ODBC 驱 动 管理 程序 使用 数据源 确定 加载 哪一 个 ODBC 驱动程 序 ODBC 驱 动程 序 一般 需要 该关 系 型 数据库的 登录 帐户 ID 和 口令来 连接 目标 数据 库 在 16 位的 Windows 3.1 系统 上 ODBC 数据源 信息 存 储在 ODBC.INI 文件 中 在 32 位 Windows 95 和 Windows NT 系统 中 ODBC 数据 源信 息存 储在 注册 表的 两个 不同 注册 键 中 用户数据源信息存储在 HKEY-CURRENT-USER 系统数据源信息存储在 HKEY_LOCAL_MACHINE 键中 用户数据源对于每个用户是唯一的 系统数据源可以用 于系统 中的 全部 用户 图 5-3 示 意 了一个样 本用 户 数据 源的 注册 键图5-3 用户数据源使用的注册键 5.3 ODBC 应用 前面讲 了必 要的 预备 知识 从本 节开 始 讲解 ODBC 应 用方法 5.3.1 配置 ODBC 数据 源 在开始 使用 ODBC 之前 必须安 装一 个 ODBC 驱动 程序 并 配 置一个数 据源 当在系 统上安 装任 何一 种支 持 Microsoft ODBC 的应用 程序 时 一般 都安 装了 Microsoft SQL Server ODBC Microsoft Office 2000 Visual Studio Enterprise 驱动 程序 例如 当安 装下 列任 何一 种 和 Edition 6 等产 品时 可 以 选择安 装 SQL Server ODBC 驱 动程序 只安装 SQL Server ODBC 驱 动 程序本 身 还 不能 开始使 用 ODBC 还需要创 建 一个数 据源 为了 创建 ODBC 数据 源 需 要运行 ODBC 管理 器 它 可 以通过 Windows 2000 的 程 序 >管理 工具 >数据 源 ODBC 来运 行 若系统 是 Windows 95/98 或者 Windows NT 则可以 在控 制面 板中 找到 打开 ODBC 数据 源管 理器图 标 则 显示 一个如图 5-4 的窗 口 ODBC 数 据源管 理器 程 序显示 所有 当前 已安 装的数 据 源 不同 的标签允 许使用 不 同类 型的 数据 源 用户 DSN 标 签 显示可以 由当前登录用户使用的数据 源清 单 系统 DSN 标签显示可以由系统上 全 部用户使用的 系统数据源清单 文件 DSN 标签显示允许连接到一 个文件提供程序的 数 ODBC ODBC 据源清 单 驱动 程序 标 签 显示 当前 安装 在系 统上 的所有不同 的 驱动程 序 跟踪 标签 允许 跟踪 某 个给定 ODBC 驱动程 序的 所有 活动 关于 标签 显示 由 ODBC 数据源 管理 器使 用的 DLL 及其版 本的 清单图5-4 使用 ODBC Administrator 增加一个数据源 DSN ODBC 为了配 置一 个新 的用 户数据 源 必须 单击 用户 标签 如果 这是 第一 次运 行 管理器 那么 这个 列表 是 空的 如果 已经 安装 了其 他 ODBC 驱 动程序并 且建 立 了数据 源 那么它 们将 出现 在 用户 DSN 或者 系统 DSN 的 列表中 要为 SQL Server 增加一 个数 据源 从 ODBC 数据源 管理 器对 话框 中选 择 添加 按钮 将进入 创建 新数 据源 对话框 如图 5-5 所示 当前 安装 在系 统上 的全 部 ODBC 驱动程 ODBC 序都列 在该 对话 框中 通 过单击 驱 动 程序的 名称 然后 单击 完成 按钮 可以选择 希望用 于新 数据 源的 ODBC 驱动程序 图5-5 选择一个 ODBC 驱动程序 下一个 对话 框随 所选 的 ODBC 驱动程 序 的 不同而不 同 选择 SQL Server ODBC 驱动程 序则显 示 Microsoft SQL Server DSN 配置向 导 如图 5-6 所示 在这个 文本 框 中 输入 ODBC 数据源 名称 可以 选择 任 意名称 其 主要 作用 是以 一 个有 意 义 的名称关联 驱动程 序 和目标 RDBMS 当 ODBC 第一次 企图 连接 数据 时 使用 该名 称 应用 程序 必须 指定 数据源名称 允许 用户 从所 有 已经配 置的 数据 源清 单中 选 择一个 数据 源 图 5-6 SQL Server DSN 配置向导 ( 界面 1) 描述 是 一个 文本字段 可 以用 来帮助确认数 据源 这种数据源 描述 其实只 用于 ODBC 管理 器 最后 从下 拉组 合框 中 选择希 望 将 该数据源 连接 到 的 SQL Server 系统 网络 上的 所 有 SQL Server 系统都列 在这 个组 合框 中 如果 在 SQL Server 系统本身上配 置 这种 数 据源 那么可 以输 入名 称 (local) 这将 把数 据源 连接 到在 同一 个物 理系 统上 运行 的 SQL Server 单击 下一 步 按钮 则 显示下 一个 对话 框 如图 5-7 所示 这个 SQL Server DSN 配 置向导 的第 二个 界面 允许指 定 将要 执行 的 SQL Server 安全认 证模 式 窗口顶 端的 单选 按钮 允许在 使用 Windows NT 登录或 者 SQL Server 登录之间 选 择 使用 Windows NT 登录 ID 登 录到 SQL Server 中需要 委托连 接 并需 要实 现 Windows NT 认 证模式 或者 混合 安全 模式 虽然使 用 SQL Server 的认证 不要 求委 托连 接 但是 要求 用户 输入 一个 登录帐 号 ID 和口令 5-7 SQL Server DSN ( 2) 图 配置向导 界面 在第二个窗口中的 客户端配置 按钮允许改变用来连接 SQL Server 的 Network Library 一般来说 这 些内 容不需 要改 变选择 连接 SQL Server 以获得其 它选 项的 默认 值 复选 框 表示 其 余 步骤的 默认 设置 从 SQL Server 系统中提 取 最后 如果 选择 SQL Server 认证 那么 需要 输入 用来 连接 到 SQL Server 的登 录帐 号 ID 和口令 单击 下一 步 按钮 初 始化一 个 SQL Server 的连 接 显 示如图 5-8 所 示的窗 口 在 SQL Server DSN 配置 向导 顶部 的复 选框 允许 忽略 在 SQL Server 登录时 指定的默认数据库 不选这 个复 选框 表示 使用在 SQL Server 的登 录 ID 中 输入 的 默认数据 库 图 5-8 SQL Server DSN 配置向导 ( 界面 3) 附加数据库 文件名称 复选框允许指定 一个可以附接的数据库名称 该数据库将用 作本次 连接 的默 认数 据库 选择 为准 备好 的 SQL 语句创 建临 时存 储过 程 复选 框允许 当使 用 SQLPrepare 函数 时 创建 一个 临时 的存 储 过程 该选 项只 能 用于 SQL Server 5.5 数据 库 SQL Server 7.0 及 2000 为预 准备 的 SQL 语句在 SQL Server 的 Procedure Cache 中 创建了一 个共 享 的执行 规划 一般情 况下 为SQL Server 6.5 数 据 库选 择这 个选 项 可以提 高使 用预 准备 语句 的 ODBC 应 用程序的 性能 当 选择 该 复选 框之后 出现两个单选 按钮 使用这些单选 按钮 可以确 定 何时删除 这些临时存储 过程 选择第 一个单选按钮 可使 这 些临时 存 储过 程在 断 开连接 时 删除 选择第二个单选按钮 则当以后为同样的语句句柄调用 SQL Prepare 或调用 SQLFreeStmt 函数 或者该 ODBC 应 用 程序 断开 连接时 删除 时临 时存 储过 程 选择 使用 ANSI 引用的标 识 符 复选 框则 使 SQL Server ODBC 驱 动程序在 SQL 语句 中对于 引号 强制 ANSI 规则 选择 使用 ANSI 空值 填充 及警 告 复选框 使 SQL Server ODBC 驱动 程序对 于处 理空 列 变长 字段 上添 加尾 部空格 和对 ANSI 规则 冲 突提出警告等 强制 ANSI 规则 最后 如果 主 SQL Server 无效 请使 用备 用 SQL Server 复选框 允许 备份 SQL Server 系统 以防 主 SQL Server 系统的 失败 如果 为主 SQL Server 系统定义 一个备用服务器 并 且激活 如果 主 SQL Server 无效 请… 选项 那么 当 ODBC 驱 动程序连 接到 主服 务器 时 它还 检索 连接 到备 用 服务器 所需 的信 息 在对 主 SQL Server 的连 接丢失 后 ODBC 驱动程序 结束 当前 的事 务 然 后试图重新 创 建 到备 用服务 器 的连 接 单击 下一 步 按钮 显示 SQL Server DSN 配 置向导 如图 5-9 所示 的窗 口 它 指定由 SQL ServerODBC 驱动程 序使 用的 语言 字符集 区域 设置 和日 志文 件 第一 个复 选框指 定将 使用 哪一 种语言 显 示 ODBC 消息 如 果在连 接的 SQL Server 系统 上只安装了一 种语言 那 么这 个复 选框和 与 其相 关的 下拉 组合 框不能使 用 图 5-9 SQL Server DSN 配置向导 ( 界面 4) ODBC 下一个 复选 框控 制 驱动程 序如 何执 行字 符集 翻 译 选择 执行字 符数 据 转换 复选框 指定 ODBC 驱 动 程序使 用 Unicode 转换 在 ODBC 客 户机和 SQL Server 服务器 之 间传送 的 ANSI 字符 串 如 果 没有 选择该 选项 那么 ANSI 数据 直 接 发送给 SQL Server 这要求 客户 机和 SQL Server 系统使用 相同 的代 码页 当输 出货 币 数字 日 期和时 间时 请使 用区 域设置 复选 框 允 许忽略在 SQL Server 登录信 息中 指定 的默 认设置 下面两个复选框允许设置长时间运行查询的最大时间( 毫秒) 并 且控制 SQL Server ODBC 驱 动程序 是否 记 录 驱动程序 的统计 如果激活 这些选项 它 们 将 把活动记录在 文本 框中指 定的 文件 默认 设 置禁止 这两 个选 项 单击 完成 按钮 则完 成 SQL Server 数据 源的 配置 并且 显示 如图 5-10 所示 的确认 SQL Server DSN 对话框 该 配置向 导对 话框 提供 了 在数据 源配 置进 程中 选择 的 全部选 项 单击 确定 按钮则创 建新的数据源 如果在配置窗 口中显 示的任何值是 不正确 的 那么 可以单 击 取消 按钮 显 示前一 个 SQL Server DSN 配置 向 导界面 在这里可以单击 下 一步 和 上一 步 按钮 在一 系列 对话 框之 间移 动图 5-10 SQL Server DSN 配置向导 ( 界面 5) 单击 测试 数据 源 按 钮 则 建立一个 到 SQL Server 的连 接 并且 验证 选项设 置 如果 该连接不成功 则显示 一个错误信息 确认错误情况 否则 如果连接成功 那么显示 如 图 5-11 所示 的对 话框 图 5-11 SQL Server DSN 配置向导( 界面 6) 当安装 SQL Server ODBC 驱动程 序和 使用 ODBC 数 据源管 理器 配置 一个 数据 源 之后 可以准 备运 行 ODBC 应 用 程序或 者创 建自 己的 定制 客 户机/服 务器数 据库 应用 程 序 5.3.2 ODBC API 使 用基础 使用 ODBC API 比使 用数据 控 件与 Visual Basic 的内 置 DAO 方 法复杂得 多 这 主要是 由于要 调用 一个 外部 DLL 中的函 数 然而 使用 ODBC API 可 以提高 性能 因此 很值 得 使用 当应 用程 序直 接使用 ODBC API 时 它可 以调用 包 含在 ODBC 驱动 管理程 序 中的 函数 ODBC 驱动管理程 序在 16 位应用程序中是 ODBC.DLL 在 32 位应用程 序中是 ODBC32.DLL 为了 使用 ODBC API 当调 用 ODBC API 函数 时 需要 按照 一 定的顺 序图 5-12 提供 了 ODBC API 的应 用概 图 图 5-12 ODBC API 应用概图 首先必 须认 识到 所有 ODBC 应 用 程 序都必 须遵 循如 图 5-12 所 示的通 用 ODBC API 这 非常重 要 对 于包 容面 较 小的桌 面应 用程 序 例如 Word Excel 和 其他支 持 ODBC 的应 用 程序 以及 自己 写的 ODBC 客户机/服务 器应 用程 序 都是 如此 虽然 这些 很不明 显 但是 Word MS Qurey Excel Access 和所有 其他 支持 ODBC 的应 用 程序 都进 行同 样系列的 ODBC API 调用 就像 定制 的 Visual Basic 应用 程序 一样 在图 5-12 中 正 像看 到的那 样 在所 有 ODBC 应 用程 序 中必须做的第一件事 情 是调 用 SQL AllocHandle 函 数 来创建 一个 环境 句柄 ODBC 驱动 管理 程序 使用该环境句 柄 跟 踪 每一个 ODBC 连接 和其 状 态 接下来 ODBC 应用 程序 必须 调用 SQLSeeEnvAttr 函数 这个函数注册将 要使用 的 ODBC API 版本 如果 没 有调用 这个 函数 那么 ODBC 驱 动程序假 设应 用程 序 使用 ODBC 2.0 调用约 定 如果 使用 设置为 SQL_OV_ODBC 3 其值 为 3 的 SQL_ATTR_ODBC_VERSION 环境属 性调 用 SQLSetEnrAttr 函数 那么 ODBC 驱动 程序知 道该 应用 程序 将使 用 ODBC 3.x 调用约 定 当设置 这些 环境 选项 之后 所有 ODBC 应用程 序必 须 调用 SQLAllocHandle 函数创 建 一个数 据库 连 接 句柄 当 分配数 据库 连接 句柄 时 SQLAllocHandle 函数 关联这个 新数 据 库 连接句 柄来 确认 在数 据库连 接 中使 用的 驱动程 序接下来 使用 由以 前的 SQLAllocHandle 函数 返回 的 数据库 句柄 使用 SQLConnect 函 数启动 一个 对数 据库 的 ODBC 连接 该 SQLConnect 函数 必须 提供 连接 到目 标数据 库 的数 据所需 源名 称和 登录 信息 SQLDriverConnect 函 数或者 SQLBrowseCcnnect 函 数 都可以 替 代 SQLConnect 函数 SQLDriver Connect 函 数 提示 终端 用户输入必要 的 数 据源 连接 信息 SQLBrowseConnect 函数 向 应用程 序返 回一 个字 符串 它确 认丢 失的 连接 信息 当应 用程 序 没有提供所有必要的数据源登录信息时 一般使用 SQLDriverConnect 函数 而 SQLBrowseConnect 函数 通 常用来 创建 定制 的登 录对 话框 最后一 段 ODBC 初始 化代码 是 调用 SQLAllocHandle 函数 创建 一个 语句 句柄 该语 句句柄用于处理 SQL 请求 当这 个语 句分 配之 后 ODBC 驱动管理程序使用该语句句柄 管理 ODBC 连接 的状 态信息 例如 在执 行 Select 操作 之前 应 用程 序不 能提取数据 语 句句柄 允许 ODBC 驱动 管 理程序 了解 ODBC 语句 的 当前状 态 因此 也知 道可 以 在给定 时 间执行 的有 效操 作 当分配 该语 句句 柄之 后 ODBC 应用程 序可 以开 始使 用该 语句 句柄 处理 SQL 请求 这 是 ODBC 应用 程序 所做 的 实际工 作 一个 典型 的 ODBC 应 用程序使 用这 种语 句 句 柄处理许 多 SQL 请求 然后 检索 和 处理这 些请 求的 结果 使用 SQLExecute 或者 SQLExecDirect 函 数将发 送 SQL 语句到 连接的 数 据库 中 使用 SQLExecute 函 数执行准 备的 SQL 或 者 静态的 SQL 语句 SQLExecDirect 函数 用于 ad hoc 或 动态的 SQL 语句 在 SQLExecute 函数可 以 使用之前 必须使用 SQLPrepare 函数 SQLExecute 一般用于重复执行的 SQL 语句 SQLExecDirect 函数 通常 用于 只执 行一 次的 SQL 语句 SQLExecute 和 SQLExecDirect 函 数可以 处理 任何 在主 机数 据库 上有 效的 SQL 语句 如果该 SQL 请求是一 个 Select 操作 它通 常创 建 一个结 果 集 该 结 果集包含了 满足 SQL 查询的 信息 使用 SQLFetch 函数访问 这个 结果 集 有关创 建 SQL 语句 的 详 细信息 参见 第 1 章和 第 2 章 当 ODBC 应 用程 序准 备终止时 它还 必须 执行 一些 清 理步 骤 释放 各种 ODBC 句柄占 用的内存 然后关闭已 启动的会话 正如用户猜测的 那样 对于每一个初 始化语 句 还有 一个相 应的 清理 语句 例如 当 ODBC 应用程 序终 止 时 对于 每一 个打 开的 语 句句柄 必 须首先 执行 SQLFreeHandle 函数 然后 对于 每一 个打 开的 连接 必须 调用 SQLDisconnect 函数 接下 来 它必 须调用 SQLFreeHandle 函数 释放 由每 一个 数据 库连 接句 柄占 用的 内存 最后 ODBC 应用 程序 必须 调用 SQLFreeHandle 函数 释放 ODBC 环境 占 用 的内存 刚开始 这些 似乎 非常 复 杂 但是 对于 所有 的 ODBC 应 用程序 这些 ODBC 初始化 和 清理函 数都 是非 常类 似的 一旦 定义 了这 些函 数 就 可以把 它 们 剪贴到其 他 ODBC 应用程 序中 5.4 ODBC 调用的前期 和后期 工作 既然已 经看 到了 如何 使用 ODBC API 的基 本规 则 现 在 详 细看看 如何从 Visual Basic 中调用 ODBC 函数5.4.1 分配 环境 句柄 SQLAllocHandle 函数 是必 须由 ODBC 3.x 应 用 程 序调用 的第 一个 函数 正如 在下 面的 程序清 单看 到的 那样 该 SQLAllocHandle 函数带 三 个参数 iResult% = SQLAllocHandle(SQL_HANDLE_ENV,SQL_NULL_HANDLE, henv&) If iResult% <> SQL_SUCCESS Then `Error Handling End If 第一个 参数 是一 个标 志 表 示 将要分配的 句柄 类型 SQL_HANDLE_ENV 常 量 指定将 要分配 的 ODBC 环境 句柄 第 二个 参数用 来表 示父 句柄 因为 ODBC 环 境是最 基本的 ODBC 句柄 所以它没有父句柄 SQL_NULL_HANDLE 常量用来指定一个空句柄 第三个参数 是一个 长变 量 它包 含由 SQLAllocHandle 函数返 回 的环境 句柄 SQLAllocHandle 函数 返回 一个 整数 值 表示 该函 数的 成功 或者 失败 值 SQL_SUCCESS 表示该 函数 调用 完全 成功 其他 值表 示有 错误 5.4.2 释放 环境 句柄 必须在 ODBC 应用程序结束之前 调用 SQLFreeHandle 函数释放环境句柄 SQLFreeHandle 函数 释放 由以 前的 SQLAllocHandle 函 数分配的 内存 和句 柄 下 面的代 码说 明如何 释放 一个 ODBC 环 境句柄 iResult% = SQLFreeHandle(SQL_HANDLE_ENV,ByVal henv&) If iResult% <> SQL_SUCCESS Then `Error Handling End If SQLFreeHandle 函数 的第 一个 参数 表示 将要 释放 的句 柄 类型 SQL_HANDLE_ENV 常 量指定 将要 释放 的 ODBC 环境句 柄 第 二个 参数 是一个长变量 它包含 将要 释 放的 ODBC 环境句 柄的 值 SQLFreeHandle 函数 返回 一个 整数 表示 函数 调用 的状态 值 SQL_SUCCESS 表示该 调用完 全成 功 5.4.3 设置 环境 属性 当 ODBC 环境 分配 之后 必须调 用 SQLSetEnvAttr 函数 指定 将要 使用 的 ODBC 3.x API 或者 ODBC 2.x API 如 果 没有使 用 SQL SetEnvAttr 函 数明确 地设 置环 境为 ODBC 3.x 那 么环境 默认 是 ODBC 2.x API 设置 这是 为了 确保尽可 能 与已使用 ODBC 2.x API 的 ODBC 应用程 序兼 容 除 了控 制 应用程 序使 用的 ODBC API 级之外 还可 以使 用 SQLSetEnvAttr 函数建 立连 接池 连接 池 主要用 于中 间层 的应 用程 序 它允 许 ODBC 连 接一直 打开 并可 以在以后 再次使用 这 样可以提高应 用程序的连接时 间 下 面的代码说明 如何设 置环境 属 性支持 ODBC 3.x API iResult% = SQLSetEnvAttr(henv&,SQL_ATTR_ODBC_VERSION,SQL_OV_ODBC3 0) If iResult% <> SQL_SUCCESS Then '' Error Handling End If SQLSetEnvAttr 函数的第一个参数是一个长变量 它包含了环境句柄 第二 个参 数包含一个 常量 它指定 将要设 置的环 境属 性类 型 SQL_ATTR_ODBC_VERSION 常量表示 将 要设置 的是 ODBC 版本 第三个 参数 是一 个常 量 它包 含将 要使 用的 版本 SQL_OV_ODBC3 常量说 明将 要使 用 ODBC API 3.x 最后 一个 参数 指定第 三 个参 数的 长度 只有当第三 个 参 数包含 一个 字 符 串时 才需要 第四 个参数 如 果它 包 含一个 数字 值 正如 前面 的 示例那 样 可以把 最后 一个 参数 设置为 0 5.4.4 分配 数据 库连 接句 柄 当建立 ODBC 环境 句柄和 设置 ODBC 环境 属性之 后 ODBC 应用程 序就可 以使 用 SQLAllocHandle 函数创建一个数据库连接句柄 下面的示例说明如何创建一个数据库连接句 柄 iResult% = SQLAllocHandle(SQL_HANDLE_DBC,ByVal henv&,hdbc&) If iResult% <> SQL_SUCCESS Then `Error Handling End If 像 ODBC 环境 句柄 一样 使用 SQLAllocHandle 函数 创建 数据 库连 接句 柄 为了 创建 一个数据库连接句柄 必须将 SQLAllocHandle 函数的第一个参数设置为 SQL_HANDLE_DBC 常量 下一 个参 数包 含父 句柄 在本例 中 父句柄 是以 前 创建的 环 境 句柄 最后 第三 个参 数 返回创 建的 数据 库连 接句 柄 SQLAllocHandle 函 数返 回一个整 数 表示 SQLAllocHandle 函数执行成 功 或 者 失败 如果返 回 SQL_SUCCESS 常量 那么 函数 执行 成功 5.4.5 释放 数据 库连 接句 柄 由 ODBC 应 用程 序创 建的每 一 个数 据库 连接 句柄都必须在应用 程 序 终止之前 释 放 下 面的代 码说 明如 何释 放一个 数 据库 连接 句柄 iResult& = SQLFreeHandle (SQL_HANDLE_DBC, ByVal hdbc&) SQLFreeHandle 函数 的第 一个 参数 表示 将要 释放 的句 柄 类型 SQL_HANDLE_DBC 常 量指定 将要 释放 一个 数据库 连 接句 柄 第二 个参 数包含要释放 的 数 据库 连接句柄的值 5.4.6 连接 到数 据源 当 ODBC 应用 程序 创建 一 个数据 库连 接句 柄之 后 应用 程序 可以 使用 该句 柄建立对数 据源 的物理连 接 创建对 ODBC 数据源连接的 最常 用 方法 是使用 SQLConnect 函数 或者 SQLDriverConnect 函数 这两个函数将应用程序连接到某一个数据源 主要差别是 SQLConnect 函 数负 责提 供 建立 连接 所 需要的全 部信 息 而函 数 SQLDriverConnect 把获取 连接信 息的 任务 转交 给 ODBC 驱动程 序 使用SQLConnect 下面 的代 码说 明如 何调用 SQLConnect 函 数连接 到数 据源 Dim sDSN As String, sUID As String, sPWD As String sDSN = "TECA SQL Server" sUID = "sa" sPWD = "sapwd" iResult& = SQLConnect(ByVal hdbc&, sDSN, Len(sDSN), sUID, Len(sUID),_ sPWD, Len(sPWD))If iRESULT% <> SQL_SUCCESS And iResult% <> SQL_SUCCESS_WITH_INFO Then '' Error Handling End If SQLConnect 函数的第一个 参数带有数据库连接句柄 它是前面 使用 SQLAllocHandle 函数创建 的 下一个参 数集提供必要 的登录信息 第 二个参 数是一个字符 串 它 包含应 用 程序将 要使 用 的 数据 源名称 第三 个参数是 一个 整数 它包 含了 数据 源名 称的 字 符串长 度 如果第 二个 参数 包含 一个有 效 的数 据源 名称 那么 ODBC 驱 动管理程 序加 载相 应的 驱动 程 序 并且 把它传送给其 他连接参数 如果该数据源参 数是一 个空指针 无 效的 或 者包含 名 称 DEFAULT 那么 ODBC 驱动 管理 程序 使用 在 ODBC Administrator 定 义 的 默认数 据源 如果默认的 数 据 源不存在 那么 SQLConnect 函 数返回 SQL_ERROR 第 四 个 参数是包 用 户 ID 的字 符串 第五 个参 数是 一个 整数 它包 含用户 ID 字符 串 的长度 第六 个参数 是一 个包含 口令 的字 符串 第 七个参 数是 一个 整数 表 示 口令的 长度 如果成功创建了与 数据源的连接 那么 SQLConnect 函 数返回 SQL_SUCCESS 或者 SQL_SUCCESS_WITH_INFO 使用 SQLDirverConnect 正像 在前面的示例中看到的那样 SQLConnect 函 数确认 数 据源和提供必要的登录信息 如果 ODBC 应用程序不能提供这些值 那么可以使用 SQLDriverConnect 函数 使 ODBC 驱 动 程序 在运 行时 提示输入这种 信 息 下面 的 示例说 明如 何调用 SQLDriverConnect 函数 Dim sInConnect, sOutConnect As String sInConnect$ = "DSN=" sOutConnect$ = String$ (1024,0) iResult% = SQLDriverConnect (ByVal hdbc&, ByVal hwnd&, _ ByVal sInConnect$, Len(sInConnect$), ByVal sOutConnect$,_ Len(sOutConnect$), iOutConnectLen%, _SQL_DRIVER_COMPLETE) If iResult%<> SQL_SUCCESS And iResult% <> SQL_SUCCESS_WITH_INFO Then '' Error Handling End If 像 SQLConnect 函数 一样 SQLDriverConnect 函 数 的第 一个参 数是 数据 库连 接句柄 第二个 参数 包含 调用 应用程 序 的 Windows 句柄 或者如果 该 窗口句柄 未 知 那么该参数是 一个空 指针 第三 个参 数 包含一 个连 接字 符串 连接字 符串 可以 是空 的 它使 ODBC 驱动程序 提 示输入全部连接的 信息 或 者 它可以 包含任 何或 者所 有的 连接信 息 如果 提供 了连 接字 符 串 那么 它采 用下 面的 形式 Keywordl=value;keyword2=value;keyword3=value 下表列 出了 可用 于 ODBC 3.x 连接 字符 串的 有效 关键 字 关键字 描述 数据源名称 DSN 数据源文件名称(.dsn) FILEDSN 指定的 ODBC 驱动程序名称 DRIVER 登录帐号 ID UID 登录帐号 ID 的口令 PWD 将包含保存的连接信息的数据源文件名称 SAVEFILE为 SQL Server 提供全部 必要 的登 录信 息的 连接 字符 串示 例如 下 DSN=MySQLServer;UID=MyLoginID;PWD=MyPwd DSN 关键 字表 示一 个数 据源 名称 值 它是以 前使 用 ODBC Administrator 配置的 UID 关键字 表示 创建 数据 源连接 时 使用 的登 录 ID PWD 关 键字表 示与 登录 ID 关 联 的口令 如 果省略 了这 些关 键字 的值 就像 在前 面的 代码 示例 那 样 或者 这些 值是 无效 的 那么 ODBC 驱动程 序显 示一 个对 话框 提示 用户 输入 必要 的连 接 信息 SQLDriverConnect 函数的 第四 个 参数是一 个 整 数 它 包含 连 接字符串 的 长 度 第五个 参数是 一个 字符 串 它包含 由 ODBC 驱动程序 成 功连接所返回的已经完 成的 连 接字符 串 这个字 符串 最小 是 1024 字节 可以 包含 完整 的连 接 字符串 第 六个参数 是一 个 整数 包含 完整连 接字 符串 的长 度 由 SQLDriverConnect 函 数返回 的 第七 个参数是一个 指 针 指向 完整 连接字 符串 的长 度 最 后 一个参 数是 一个 整数 它指定 ODBC 驱动 程 序是 否提示输入附加 的连接 信息 常量 SQL_DRIVER_COMPLE 表 示如 果包含 在 连接 中的信息不足的创建连 接 该驱动 程序 将显 示一 个对话 框 收 集必要 的连 接信 息 5.4.7 与数 据源 断开 连接 在应用程序终 止之前 必须与每一个连接 的数据源断开连接 下面的 代码说明如何使 用 SQLDisconnect 函 数断开与数据源的连 接 iResult% = SQLDisconnect(hdbc&) SQLDisconnect 函数需要输入数据库连接句柄 并且返回一个整数值 表示 该函 数调 用成功 或者 失败 5.4.8 分配 语句 句柄 当分配 ODBC 环境 和数 据 库连接 句柄 并 连接数 据源 之 后 分配 语句 句柄 是最 后 必要的 ODBC 初始 化代码 下面 的示 例说 明如 何使 用 SQLAllocHandle 函 数创建一 个 ODBC 语句 句柄 iResult% = SQLAllocHandle(_SQL_HANDLE_STMT,ByVal hdbc&,hstmt&) If iResult% <> SQL_SUCCESS Then `Error Handling End End If SQLAllocHandle 函数的第一个参数是一个常量 表示将要分配的句柄类型 SQL_HANDLE_STMT 常量 指定 将要 分配一 个 ODBC 语 句句柄 第 二个 参数表示父句柄 对于一 个 ODBC 语句 句柄 父句 柄是 数据 库连 接句 柄 最后 一个 参数 是与 ODBC 语句句 柄 关联的 语句 句柄 SQLAllocHandle 函 数返 回一个整 数 表示 SQLAllocHandle 函数 的 成功或者 失败 如 果返回 SQL_SUCCESS 常量 那么 该函 数执 行成 功 5.4.9 释放 语句 句柄 像环境 句柄 和数 据库 连接句 柄 一样 所有 由 ODBC 应 用程序分 配的 ODBC 语 句 句柄都 必须在应 用程序终止之 前释放 然而 不像环境句柄 和数据 库连接句柄 这两 个 句柄都 使用 SQLFreeHandle 函数 而释 放语 句句 柄 可以使 用 SQLFreeHandle 或者 SQLFreeStmt 函数 使用 SQL FreeStmt SQLFreeStmt 函数可以 关闭 由语句 句 柄使 用的 游标 它可 以 解除与 某 个给定 的语 句句 柄相 关联的 参 数 或者 释放 由 ODBC 语 句句柄 使用 的全 部资 源 下面 的代 码显示 了一 个调 用 SQLFreeStmt 函数的示例 iResult% = SQLFreeStmt(hstmt&,SQL_CLOSE) SQLFreeStmt 函数的第一个 参 数是 以前 由 SQLAllocHandle 函 数创建 的语 句句 柄 第二 个参数是一个 整数 它控 制执行 的释放语句句 柄操作的类型 SQL_CLOSE 常量关闭一个 与语句 句柄 关联 的打 开的游 标 其 他常 用的常 量是 SQL_UNBIND SQL_RESET_PARAMS 和 SQL_DROP SQL_UNBIND 常量释放 使 用 SQLBindParameter 函数绑定 到语句 句 柄的 全 部列 SQL-RESET-PARAMS 常量 释放 用 SQLBindParameter 函数 绑定 到语 句句柄 上 的全 部 参数 SQL_DROP 常量 使 SQLFreeStmt 函数释放全 部与 该语 句关 联的 资源 使用 SQLFreeHandle SQLFreeHandle 函数还可以用来释放 ODBC 语句句柄 SQLFreeStemt 函数比 SQLFreeHandle 函数 提供了 更 强的控 制 度 实际 上 SQLFreeHandle 函数等 价于 使用 SQL_DROP 常量的 SQLFreeStmt 函数 使用 SQL FreeHandle 函数是释放 由 ODBC 语句 句柄 使用 的 全部资 源 的首选 方法 这个函 数一 般用 作终 止应 用程序 进 程的 一 部分 由 SQLFreeStmt 选项提供 的附加灵 活性 使 其成为 其 他用户的更好选择 下 面的代 码 示例说 明如 何调 用 SQLFreeHandle 函数 释放 一个 语句 句柄 iResult% = SQLFreeHandle(SQL_HANDLE_STMT,ByVal hstmt&) SQLFreeHandle 函数 的第 一个 参数 表示 将要 释放 的句 柄 类 型 SQL_HANDLE_STMT 常量指 定将 要释 放一 个语句 句 柄 第二个 参数 是将 要 释放的 语句 句柄 提示 在初 始化 过程中 大多数 应用 程序 还使 用 SQLGetInfo 函数 SQLGetInfo 函数 不是一 个必 选函 数 对 于 检索有 关 ODBC 驱动 程序 功 能的信 息 它是 很有 用的 另外 还常 常使 用 SQLTables 和 SQLCloumns 函数检 索数据 库中 有关 表和 列的 信 息 5.5 通过 ODBC 访问 SQL Server 数据 库 上一节 研究 了全 部 ODBC 应 用 程序所 需的 ODBC 初 始化和 终止 函数 在这 一节 将介 介绍一 些基 本的 ODBC 数 据访问 函数 用来 从 ODBC 数 据源上 检索 插入 或者修 改 信息 SQL 是 ODBC 数据 访问 的核 心 使用 以前 由 SQLAllocHandle 函 数创建 的语 句句 柄 把 SQL 语句从 ODBC 应用 程序 中 发送到 数据 源 ODBC 应用程序 可以 发送 任何 有效 的 SQL 命令 到主机数据库 ODBC 应用程序可 使用动态的 SQL 语 句或者预准备的 SQL 语句 使用 SQLExecDirect 函数 执行 动态 的 SQL 使用 SQLPrepare 和 SQLExecute 函 数的组 合执行 预 准备的 SQL 5.5.1 使用 动态 的 SQL 和SQLExecDirect 检索数 据 使用 SQLExecDirect 函 数 执行动 态的 SQL SQLExecDirect 函数 判断 SQL 语句 并且 在执行 SQLExecDirect 函 数时创 建数 据访 问路 径 SQL Select 语句 可能 是最基 本 的 SQL 操作 所 以使用 SQL Select 语句 从 ODBC 数据源 中检索信 息是 本章 讨论 的第一 种 ODBC 数据访问 ODBC 的 强 大功能之一 就是 执 行 ad hoc 查 询 连接 多个 文件 和检 索 动态的 数据 集 SQL Select 语句是 这些 ad hoc 查 询的执 行者 下 面的代 码示 例说 明如 何使用 SQLExecDirect 函数 调用一 个 SQL Select 语句 从表中 检 索 指 定的行 集 sSQL$ = Select from authors where state = `OR'' iResult% = SQLExecDirect(hstmt&,sSql$,Len(sSql$)) If iResult% <> SQL_SUCCESS Then `Error Handling End If 首先 把 SQL Select 语句赋给一个字符串 变量 在本例 中 赋给变量 SQL$ 的字符 串 选择表 authors 中的 全部 行 其中 state 列的 值等 于 OR 常量 接下来 SQLExecDirect 函数 在主机数据库中执行该 SQL 语句 SQLExecDirect 函数的第一个参数是一个以前使用 SQLAllocHandle 函数 创建 的 ODBC 语句句 柄 在该 语句 句柄 之后 下一 个参 数包 含了 该 SQL 语句 第三 个参 数是 一个整 数 它包 含将 要执 行的 SQL 语 句的长 度 注意 使用 SQLExecDirect 函数 运行 的 SQL 语句不 包 括变量 所有 数据 选择条件都使 用文字 在 SQL 字符 串中 编 码 例如 在前 面的 示例 中 只选择那些 state 列包 含了文 字 OR 的行 构造 如果 希望 选择 一个 行集 其中 state 列 包含了一 个不 同 的值 那么 需要 重新 构造一 个 新字 符串 它包 含使 用 不同文 字值 的 SQL Select 语句 然后 可以 执行 这 个新的 SQLExecDirect 语句 SQLExecDirect 函数 返回 一个 整数 它报 告该 操作 的 成功或 者失 败 如果 SQLExecDirect 函数在 主机 数据 库上 执行成 功 那么 创建 了包 含满 足 SQL Select 语 句 操 作条件的 数据 的 结 果集 SQLExecDirect 函数 返回 SQL_SUCCESS 在本章后面 可以看到如何处 理这个 结 果集 如果 该函 数由 于 SQL 无效而 失败 因为 该用 户 没有授 权请 求的 对象 或者 由于 其他 原因 那么 SQLExecDirect 函数 通常 返回 SQL_ERROR(-1) 应用 程序 可以 使用返 回 的代 码 和 SQLGetDiagRec 函数 确 定各种 错误 的原 因 ODBC 错 误处理 和 SQL DiagRec 函数也在本 章后面 讨论 5.5.2 使用 SQLExecute 和 预准备 SQL 语句 动态地 创建 和执 行 SQL 语句的 功能 给了 ODBC 应用 程序 极 大 的灵活性 然而 为这 种灵活 性付 出的 代价 是性能下 降 每次 SQLExecDirect 函 数执行 时 判断 SQL 语 句 和创建 必要的数 据访问路径意 味着在结果返 回给终端用户之 前 必 须 等 待所有这 些操 作 完成 对 于那些 每一 个数 据库 查询的 SQL 语句 有很 大变 化的 ad hoc 查 询情况 实际 上 ODBC 应用 程序不 能显 著提 高性 能 然而 对于 那些 执行 预定 义 的 SQL 语 句集 的 应 用程序或者 对于 那 些执行 重复 SQL 语句的应用 程 序 SQLPrepare 函数 和 SQLExcute 函 数在灵活 性 和性能 方 面都有 都有 很大 提高 SQLPrepare 函 数 在主 机数 据库中执行 SQL 语 句要求 许多 开支 当调 用 SQLPrepare 函数 时 判断 SQL 语句 和打 开游标 这就给 SQLExecute 函 数留 下了运 行已 经准备 的 SQL 语句的 任务 使用 SQLPrepape 和 SQLExecute 函数 相对 于执行 一 次的 SQL 语句 并没有明显的 优势 执行 一个 SQLExecDirect 函数 所需 的时 间 与 执行一 次 SQLPrepare 和 SQLExecute 函数所需 的时间几乎是 相等的 因为 这两种方法都执 行同样 的步骤 然而 相对 于执 行多次的 SQL 语句 组合 SQLPrepare 和 SQLExecute 函数将大 幅 度 提高性 能 SQLPrepare 语 句只需 要调 用 一 次 它负 责分 析 SQL 语句语 法并打 开游标 之后 比较 快的 SQLExecute 语句可以运行需要的次数 移动游标打开的开支到 SQLPrepare 语句使得重复执行 SQLExecute 函数 比重 复执 行 SQLExecDirect 函数 快得 多 sSql$ = "Select from authors where state = “ ? “ `Prepare the SQL Statement iResult% = SQLPrepare(hstmt&,sSql$,Len(sSql$)) If iResult% <> SQL_SUCCESS Then ''Error Handling End If `Bind the parameter iResult% = SQLBindParameter(ByVal hstmt&,1,SQL_PAREM_INPUT,_ SQL_C_CHAR,SQL_CHAR,4,0,sSstate,4,Istate) If iReslt% <> SQL_SUCCESS Then ''Error Handling End If ''Run the prepared SQL statement using the statement handle iResult% = SQLExecute(hstmt&) If iResult% <> SQL_SUCCESS Then ''Error Handling End If 使用 SQLPrepare 和 SQLExecute 函数的 组合 非常 类似 于以 前看 到的 SQLExecDirect 函 数 首先 给字 符串 变量赋予了 一 个 SQL 语句 其次 执行 SQLPrepare 函数 SQLPrepare 语句带 的参 数与 SQLExecDirect 函数 要求 的参 数完 全 一样 第一 个参 数是 ODBC 语句句 柄 第二个 参数 是包 含将 要执行 的 SQL 语句 的字 符串 第 三个参 数是 SQL 字 符串的 长度 然 而 SQLPrepare 语句 实际 上不 在数 据源 上运 行 SQL 语句 记 住这一点很 重要 它只是 为 SQL 语句的 运行 做准 备工作 注意 使用 SQLPrepare 和 SQLExecute 执行的 SQL 语 句可以 包括 变量 也称 为参数 其参数 是利 用 SQLBindParameler 函数 实现 与 SQL 语 句相关联 接下来 在前 面的 示例 中 可以看 到 SQL 使用了 一个问 号(?)代 替文字 OR 当 SQLPrepare 函数成 功完 成之 后 调用 SQLExecute 函 数实际上执行 SQL 语句 该 SQLExecute 函数只 带一个 参数 这就 是 ODBC 语句句柄 这个 ODBC 语 句句柄必 须与 以前 SQLPrepare 函数 使用的 ODBC 语句 句柄 相 同 当该 SQLExcucte 函数 成功完 成之 后 包含 满足 SQL 请求的 数据的 结果 集可 以用 于 ODBC 应用程 序 5.5.3 使用 结果 集 既然 已 经 看 到 了如何将 SQLSelect 请 求发送到数 据 源 下 一步就是检索 由 返 回 到 应用 程序的 SQL 请求创建 的结果 集 SQLFetch 函数是基本 的 ODBC 函数 允许 浏览 结果 集 检索 结果 集中 的数 据 有 两种不 同的 方 法 或者使 用 SQLGetData 函数逐个处理结果集的 每一个 元素 或者 使用 SQLBindCol 函数绑 定所 选列 到程 序变 量中 使用未绑定的列和 SQLGetData 当使用 SQLExecDirect 或者 SQLPrepare 和 SQLExecute 函数 对执 行 SQL 语句 生 成了结 果集 之 后 包含 数 据 的结 果集返回到 应 用 程序中 使用 未绑 定的 列和 SQLGetData 函数 是处 理 Visual Basic 中 结果集 的最 直 接了当 的方 法 因为 不需 要涉 及任 何 Visual Basic 的内存 管理 问 题 然而 正 如在 下面 两个示 例中 将要 看到的 那样 使用 SQLGetData 函数 需要 更多的 代码 并且 没有 使用 绑定的参 数 那样清 楚 下面的 示例 说明 如何 使用 SQLFetch 和 SQLGetData 的 组合从 ODBC 结 果集中 检 索数据 确定结 果集 中的 列数 iResult% = SQLNumResultCols(hstmt&,iNbrColumns%) If iResult% <> SQL_SUCCESS Then ''Error handling End If 获取结 果集 中的 所有 行 Do Whlt SQLFetch(hstmt&) <> SQL_NO_DATA_FOUND SRecord$ = " " ''Loop through all of the columns in the result set For i% = 1 To iNbrColumns% iResult% = SQLGetDate(ByVal hstmt&,i%,SQL_CHAR,sBuffer,_ Len(sBuffer),1BufferLen&) ''Add each column to the sRecord string separated by Tabs sRecord$ = record$ & Chr$(9) & Left$(buffer,bufferLen&) Next i% ODBC SQL NumResultCols 为了检 索未 绑定 结果 集中的 数 据 需要 调用 的第 一个 函数 是 函数 正如 其名 称一 样 它返回 结果 集中 的列 数 SQLNumResultCols 函数 带两个 参 数 第 一个参 数是 由前 面的 SQLExecDirect 或者 SQLExecute 使 用的语 句句 柄 第二 个 参数是 一个 整数 它包 含结 果集 中的列 数 就像 所有 其他 的 ODBC 函 数一样 SQLNumResultCols 函 数返回 一个 整数 表示 该 函数调 用的 成功 或者 失败 SQLFetch SQLFetch 接下来 使用 函数检索 结果 集中 的每一 行 每一 次成 功地 调用 函 数都将 检索 结果 集中 的一行 SQLFetch 函数使 用在 以前 的 SQLExecDirect 或者 SQLExecute 函数中 使用 的语 句句 柄 对于每 一次 成功 的调 用 SQLFetch 函数都返回 值 SQL_SUCCESS 当 SQLFetch 函数从结果集中检索最后一行时 SQLFetch 函数返回值 SQL_NO_DATA_FOUND(100) SQLFetch For-Next 在前面 的示 例中 可以 看 到在每 一个 函数之后 在 循环中 间 调 用 SQLGetData 函数 对于 结果 集中 的每 一列 执行 SQLGetData 函数 该 函 数提 取一个给 定列的 值 并 且把 它移 动 到一个 程序 值中 SQLGetData 函 数带六 个参 数 像其 他的 ODBC 检索函 数一 样 SQLGetData 函数 的第 一个 参数 是语 句句 柄 它必 须与 SQLFetch 函数使用 的语句 句柄 一样 第二 个 参数是 一个 整 数 它包含 SQLGetData 函 数从列值 中提 取的 数据 提示 当使 用 SQLGetData 函数 时 结果 集中 的列 号 从 1 开 始 另外 结果 集中的 列必 须按照 升序 方式 访问 第三个参数表示列的 SQL 数据类型 第四个参数是一个缓存区 包含了成功完成 SQLGetData 函数 之后 的列值 第五 个参 数是 一个 输入 参数 它包 含第 四个 参数 的长 度 第 六个参 数是 一个 输出 参数 它包 含由 SQLGetData 函 数检索的 数据 长度 Visual Basic 使用绑 定列 和 SQLBindCol 在 中使用 未 绑定 的 列 比较直观 但使用绑 定列要求比 较少的代码 并且从结果集 中返回的列中检 索数据 时 这是一种 比较好 的方法 然而 Viual Basic 处理 指针 的 有 限能力使 得这 种解 决方 案比 使用 带绑 定列 的 SQLGetData 复 杂 就像 在下 面的 示例 中 看到的 那样 SQLBindCol 函 数带一 个指 向变 量 的指 针 作为其 一 个参数 并且 它希 望变 量 在内存 中的 位置 从执 行 SQLBindCol 函 数到以 后执 行 SQLFetch 函 数都是 一样 的 对于 C 应用 程序 这不 是问 题 然而 Viual Basic 可在内 存 中自由移动字 符串 变量 这 就意 味着它 们的位 置 可 能会 改 变 从而使 SQLBindCol 函 数失败 解决 这种 问题 的一 种 方 法是 在 SQLBindCol 函数 中使 用 一 个位 数 组 代替 一个字符 串变 量 下面 的代 码说明 如何 使用 位数 组和 SQLBindCol 函数 从结 果中 检索 数据 Dim bBuffer(256) As Byte Dim 1BufferLen As Long iResult% = SQLBindCol(hstmt&,1,SQL_C_CHAR,bBuffer(0),_ 256,1BufferLen) Do While SQLFetch(ByVal hstmt&) < >SQL_NO_DATA_FOUND List1.AddItem ByteArray2String(bBuffer()) Loop 正如看 到的 那样 SQLBindCol 函数的 参数 非常 类似 在 SQLGetData 函 数上使用 的参 数 SQLBindCol 函数的第一个参数是语句句柄 它必须与在以前的 SQLExecDirect 或者 SQLExecute 语句 上使 用的 语句 句柄 相 同 第二 个参 数是 结果 集中 的列数 第三 个参数 是列 数据类型 第四个参数 是指向缓存区 的指针 它将 包 含列数 据 这个指针 作为位 数组中 的 第一个 元素 传送 提示 当位 数组 作为参数到 一 个外 部函 数 时 总 是使用(0) 索引传 送函 数到 该数组 的 第 一个元 素 第五个参数是 一个输入参数 它包含缓存 区的长度 第六个参数是一 个输出参数 它 将包含 当 SQLFetch 完成时返 回数 据的 实际 长度 在这个示例中 可以看到 没有 必要 把数 据明 确地 移 动到每 个 SQLFetch 函数之后 SQLBindCol 函 数自 动执 行 指定数 据的 转换 并且用指 定 列 的值填充 绑 定的变量 在本 例 中 是一个 位数 组 然而 当使 用位 数组 时 还有一 个另 外的 考虑 Visual Basic 以 Unicode 格 式存储位 数 组 可能 在 ODBC 应用 程 序可以 使用 这个 字符 数据 之 前 需要 把 Unicode 位 数 组转换为 Visual Basic 字符 串 在前 面示 例 中调用 的 ByteArray2String 函数如下 Private Function ByteArray2String(bArray() As String Dim sString As String Dim nStringLen As Integer ''Convert the string to Unicode sString = StrConv(bArray(),vbUnicode) nStringLen = InStr(sString,Chr(0)) - 1 ''Return the Unicode string ByteArray2String = Left(sString,nStringLen) End Function这个函 数把 这个 位数 组作为 输 出 并且 使用 Visual Basic 的 StrConv 函数把 Unicode 位 数组转 换为 ASCII 然后 在 得到 的 字符串 中搜 索 Null 字符( 这是 C 语言中字符串的终止 字符) 然后 在 Null 字符 处切 断返 回的 字符 串 5.6 通过 ODBC 修改 SQL Server 数据 库 正像可 以使 用动 态的 或者静 态 的 SQL 执行 SQL Select 语句 一样 还 可以使 用同样 的 SQLExecDirect 或者 SQLPrepare 和 SQLExecute 函 数执行 其它 SQL 语句 可以使 用应用 到 Select 语句 的同 样标 准 在 SQLExecDirect 以及 SQLPreqare 和 SQLExecute 函 数 之间选择 如果该 SQL 语句只执 行一次 那么 一般 使用 SQLExecDirect 函数 如果 该 SQL 语句要 调 用许多次 或者希望把不同的参数传送到 SQL 语句中 那么一般使用 SQLPrepare 和 SQLExecute 语句 在下 一节 将会 看到 如何 用 预准备 的语 句和 一个 打开 的游 标 调用一些 基本的 SQL 语句来插 入 删除 修改 数据 行 5.6.1 插入 数据 行 在理论 上 插入 数据 行的 ODBC API 代 码非 常类 似于用来检索 数 据 的代 码 首先 ODBC 应用程序必须执行全部必要的 ODBC 初始化步骤 包括分配语句句柄 接下来, 使用 SQLPrepare 函数 准备 该语 句 使用 SQLBindParameter 语句把该 语句 使用 的参 数绑 定到 语句 上 最后 使用 SQLExecute 函数 执行 该 SQL 语句 主要差 别是 SQL 语句 本身的差别 使 用 Insert 语句替代 使用 Select 语句 下面 的代 码示 例说 明如 何使 用 ODBC 插入 数据 `Prepare the Insert statement sSql$ = “Insert into department values (?,?)” iResult% = SQLPrepare(hstmt&, sSql$, Len(Sql$)) If iResult% <> SQL_SUCCESS Then ''Error Handling End If ''Bind the parameters iResult% = SQLBindParameter(ByVal hstmt&, 1, SQL_PARAM_INPUT,_ SQL_C_LONG, SQL_DECIMAL, 4, 0, Dep_ID, 4, lpLenDep_ID) If iRESULT% <> SQL_SUCCESS Then ''Error Handling End If iResult% = SQLBindParameter(ByVal hstmt&, 2, SQL_PARAM_INPUT, _ SQL_C_CHAR, SQL_CHAR, 25, 0, bDep_Name, 25, lpLenDep_Name) If iResult% <> SQL_SUCCESS Then ''Error Handling End If ''Now Execute the Insert statement iResult% = SQLExecute(ByVal hstmt&) If iResult% <> SQL_SUCCESS Then ''Error Handling End If使用 ODBC API 插 入数 据 的第一 步是 分配 一个 SQLInsert 语句到一 个字 符串 中 不必 奇怪 插入 数据 行的 SQL 语句是 Insert 语句 有关编写 SQLInsert 语句的 详细 信息 参见 第 1 章和第 2 章 注意 这个 Insert 语句 两个 参数 标记 用作 变量的 占 位符 当该语句 执行 时 这些变 量将 传送 到该 语句中 接下来 使用 SQLBindParameter 绑定 两个参数 到程序变 量中 该 SQLBindParameter 函数非 常类 似前 面看 到的 SQLBindCol 函数 第一 个参 数是 在 SQLPrepare 函 数 中使用的语 句句柄 第二 个参 数是 SQL 语句的 参数 序号 其中 1 表 示第一个 参数 2 表示第 二 个 参数 等等 第 三个参数 是 一 个 整 数 表示 该参数是输入 输出或者输入/ 输出参数 第四 个参数 指定在 ODBC 应用 程序 中 使用的 参数 的 PC 数据类 型 第 五个 参数 指定 在数据库中使 用 的 数据 类 型 当调 用 SQLExecute 函数 时 ODBC 驱动 程序 负 责 在这 两个 数据 类型 之间 转 换 参数数据 第六个 参数是一个整数 指定数据列的大 小 第七个参数表示小数的 位数 正 如希望看 到的那样 对 于 字符数 据或 整数 该参数值总是 0 第 八个参数 是一 个 指向 缓 冲 区的指针 它包含了在参 数中使 用的数据 第九个参数是一个输 入参数 它包 含 了在 第 八 个参数中 使用的数据 第九个参数是 一个输入参数 它包 括 在 第八 个参数 中使 用 的参数 据 库的长 度 最后 第十 个 参数是 一个 指向 数 据缓冲 区 长 度的指针 提示 绑定到 ODBC 语句句 柄的 参数 标记依然起作 用 直到 使用有 SQL_DROP 选项的 SQLFreeStnt 关闭 该语 句 或者使用 SQL_RESET_PARAMS 选 项重新 设置 语句 参数 为止 对于在 SQL 语句使用 的每一 个 参数 标记 都 需要运 行 SQLBindParameter 语句 在前 面的示 例中 SQLInsert 语句使 用两个 参数 标记 因 此应用 程序 需要 运行 SQLBindParameter 函数 两次—— 每一 个 参 数 一次 在所 有必要的参数 标记绑定之后 可以使用 SQLExecute 函执行 该 SQL 语句 并 且 把 数据插入 到数 据库 中 5.6.2 删除 数据 行 使用预 准备 SQL 从 ODBC 数据库 中删 除行 与 在使用 Insert 语 句中看 到的 操作 完全 一 样 首先 SQL 语句加上任 何 参数 标记 传送 到一 个字符 串 中 其次 使用 SQLBindParameter 语句把 参数 绑定 到程 序变量 上 最后 使用 SQL Execute 命 令执行该 SQL 语句 下面的 代 码示例 说明 如何 使用 ODBC 删除行 ''Prepae the Delete statment sSql$ ="Delete from department where Dep_ID = ?" iResult% = SQLPrepare(hstmt&,sSql$,Len(sSql$)) If iResult% <> SQL_SUCCESS Then ''Error Handling End If ''Bind the parameters iResult% = SQLBindParameter(ByVal hstmt&,1,SQL_PARAM_INPUT,_ SQL_C_LONG,SQL_CHAR,4,0,Dep_ID,4,lpLenDep_ID) If iResult% <> SQL_SUCCESS Then ''Error Handling End If iResult% = SQLExecute(ByVal hstmt&)If iResult% <> SQL_SUCCESS Then ''Error Handling End If 正如看 到的 那样 用来 执 行删除 操作 的 ODBC 函数与 用来 执行 插入 操作 的 ODBC 函数 完全相 同 主要 差别 是 SQL 语句本 身 在这 个示 例中 Delete 语句 只 带有一 个参 数——在 这个示 例中 正好 是 department 表的主 键 因为 Delete 语句 只 使 用了一个参数标 记 所以 该代码 只需 要使 用一 个 SOLBindParameter 函数 当调用 SQLExecute 函数 时 才 执行实 际 的删除 操作 提示 对于 使用 不同 参数的 SQL 语句 使用 多个 语句句 柄 如果 ODBC API 应 用 程序需 要执行 各种 不同 的 SQL 语句 其中每 一个 语句 要求 一 个不同 的 参数集 那么 处 理这 种情 形的 最简 单方 法 是使用多条语句 在这种 结构中 每一 条语 句都 有 自 己的参 数绑 定集 5.6.3 修改 数据 行 使用 ODBC 预准 备的 语句和 游 标修 改行 与 在 插 入和删 除操 作中所使 用 的 ODBC 函数 集完全 一样 重复 一遍 主要差 别是 SQL 语 句 本身 和 必须绑 定的 参数 下面 的示 例说 明如 何使用 ODBC API 修 改一组 列 ''Prepare the Update statement sSql$ = "Update DEPARTMENT Set Dep_Name = ? where Dep_ID = ?" result% = SQLPrepare(hstmt&,sql$,Len(sql$)) If result% <> SQL_SUCCESS Then ''Error handling End If ''Assign the parameter values nDep_ID = CLng(Text_DeptNumber.Text) ''Convert the string to a byte array for parameter binding String2ByteArray bDep_Name,Text_DepDesc.Text ''Setup the binding lengths lpLenDep_ID = 4 lpLenDep_Name = 25 ''Bind the parameters result% = SQLBindParameter(ByVal hstmt&,1,SQL_PARM_INPUT,_ SQL_C_CHAR,SQL_CHAR,25,0,bDep_Name(0),25,lpLenDep_Name) If result% <> SQL_SUCCESS Then ''Error Handling End If result% = SQLBindParameter(ByVal hstmt&,2,SQL_PARAM_INPUT,_ SQL_C_LONG,SQL_DECIMAL,4,0,nDep_ID,4,lpLenDep_ID) If result% <> SQL_SUCCESS Then ''Error HandlingEnd If result% = SQLExecute(ByVal hstmt&) If result% <> SQL_SUCCESS Then ''Error handling End If 该 SQL Update 语句 使用 第 一个参 数标 记 的 值修改列 Dep_Name 其中 列 Dep_ID 的值 等于在 第二 个参 数标 记中使 用 的值 在这 个示 例中 因为列 Dep_ID 是 一个唯一 键 所以 SQL 语句只 修改 一条 记录 然而 Update 语句 可以 修改 任何数 量 的行 只要 其满 足 在 Where 子句 中指定 的选 择条 件 在本章前面的示例 中 可以看到如何使用 ByteArray2String 函数检 索在参数中返回的 位数组 的内 容 并 且把 该 值 放在一个 字符 串变 量中 这里示 意的 参数 化 的 Update 语句有 完 全相 反的要求 在执行 Update 操作之前 字符串 变量 的内容 必须 移动 到一 个位数组 中 String2ByteArray 函数执行这 种 任务 下面 的代 码清单 列 出了 String2ByteArray 函数的 源代 码 Private Function String2ByteArray(bArray() As Bytem,sString As String) Dim nStringLen As Integer Dim i As Integer ''Move each element of sString to bArray For i = 0 To Len(sString) - 1 bArry(i) = Asc(Mid(sString, i+1, 1)) Next i ''Zero out all remaining array items For i = Len(sString) To UBound(bArray) bArray(i) = 0 Next i End Function 像前面 讨论 过的 插入 和删除 操 作一 样 该修 改函 数首先 使 用 SQLPrepare 函 数准备该 语 句 其次 调用 两次 SQLBindParameter 函数 每一 个 参数标 记一 次 最后 当调 用 SQLExecute 函数时 执行 Update 语句 像前 面的 那些 示例 一样 重要的 是在 SQLPrepare 中使用 的 语 句句柄 匹配 在以 后的 SQLBindParameter 和 SQLExecute 操作 中 使 用的语 句句 柄 5.6.4 数据 并发 性 当修改 多用 户数 据库 时 例如 SQL Server 数 据 并 发性是 碰到 的一 个主 要问 题 当 SQL Fetch 操作检 索一 行时 一 般没有 把 锁放在 所选 择的 行上 如果 多个用 户同时修改一 个 数 据 库 那么 可能有多个用 户同时修改同 一行 如果发生 这种情 况 并且没有 强制数 据并 发性 的机制 那 么第 一个 用户的 修 改可 能被 第二 个用 户的修改所覆 盖 使用 SQL Server 实现数 据并发性主要有 两 种 方 法 可以使用一 种 数 据 编 码技 术 叫做 优化 记录 锁定 或利 用 SQLServer 的行级 锁定 功能 的行 级锁 特征 优化记录 锁定 确保数据 完整性 的一种常 用方法是 使用优化记录锁定 优化并发性是 在允许 修改 操作 之前 检 查 要 修改的列值 来实 现 的 例如 考虑 下面 简单 的 SQL Update 操 作 Update departent Set Dep_Name = ? Where Dep_ID = ? 该 Update 语句 没有保 护数据 并 发性 Dep_Name 列值 总是 由最 后一 个 执行 Update 语 句的用 户设 置 使用 优化并 发 性 这个 SQL Update 语 句可以 改成 下面 的形 式 Update department Set Dep_Name = ? Where Dep_ID = ? And Dep_Name = ? 在这个 示例 中 And 子句提 供了 一种 机制 检查 Dep_Name 列 自从数 据检 索以 来是 否 修改过 在 And 子句 中使用 的值 是最 初检 索到 的 Dep_Name 列值 如果 另一 个用 户修 改这 个特殊 行的 Dep_Name 列 那么 这个 Update 语 句将失 败 因为 Dep_Name 字 段 不再等 于以 前检索 的值 使用 And 子句 实现 优化 数据 并发 性似 乎是 该问 题的 一个 很好 的解决 方 案 但 是并非 全部 的 Update 操作 都像 这个 示例 一样 简单 如果 Update 语句需要 处 理 40 或者 50 个列 那么 就无 法想 象这种 修 改操 作会 发生 什么 样的情 况 这种 解决 方案 显然 是 不明智 的 这个问题 的另外一种解决 方案是 在表中增 加一个时戳列 使用时 戳列实现 优化并 发性需 要 如下一 条 SQL Update 语句 Update department Set Dep_Name = ? Where Dep_ID ? And Time_Stamp = ? 使用这 种方 法 不管 修改了 多 少列——SQL Update 语 句只需 要增 加一 个 And 子句 然 而 使用 时戳 实现 优化 记 录锁定 要求 具有 修改 表结 构 的能力 并非 所有 的 ODBC 应用程 序 都有这 种能 力 5.7 通过 ODBC 调用 SQL Server 数 据库的存储 过 程 调用 存储 过程非常 类似 于 执行预 准备 的 SQL 语句 然而 与 当程序执行时 才创 建 的 ODBC 预 准备语 句不 同 存储过程 必须在其 调用之前 存 在 使 用 一 个简单的 文本编辑 器 就可以 创建 SQL Server 存储过程 然而 创建 存储 过程 最常 用的 方法 是使用 SQL Server 查 询分析 器 图 5-13 创建一个插入记录的存储过程图 5-13 示意 了如 何使用查询 分 析器创 建一个 插入 记录的 简单的 存储 过程 该 spInsert 存储过 程向 表 department 中插入记 录 这个 存储 过程带 两 个参 数 第一 个参 数是一 个 整数 与表中 第一 个列 相对 应 第二个 参数 是一 个 25 字 节的字符字段 它与表中第 二 个列 相对 应 与 Insert Update 和 Delete 不同 预准 备语 句使 用以前 讲 过的 ODBC 游标 SQL Server 存储过程 在第一次执行 时预编译 并 且在数据库中保 存为存储过程对象 另外 不像使 用 预准备 语句 创建 的 ODBC 语句那 样 当客 户程 序终 止 以后 存储 过程 继续 存在数 据 库中 提示 可以 使用SQL Server 提供的 sp_stored_procdures 存 储过程列出 某个 给 定数 据 库的存 储过 程清 单 另外 可以 使用 sp_helptext 存储 过 程来 查看 某个已有的 存储过 程的Transact SQL 源代 码 执行存 储过 程所 需的 ODBC 函数调用 序列 非常 类似 ODBC 游标所需的 ODBC 函数调 用 但是 也有 些不 同 下 面的示 例说 明准 备 ODBC 语句名柄所需的 Visual Basic 代码 它 调用了 存储 过程 spInsert result%=SQLALLocStmt(ByVal dbHandle&, hstmt2&) If result@<>SQL_SUCCESS Then ''Error Handling End If ''Prepare the SP statememt handle sql$="(Call spinsert(?,?)" result%=SQL Prepare(hstmt& sql$ Len(sq1($)) If result% <> SQL_SUCCESS Then ''Error Handling End If 正如看 到的 那样 这个 过 程按照 以前 在使 用 ODBC 游 标的示例 中看 到的 那种 模 式来分 配和准 备 ODBC 语句 主 要差别 是预 准备 的 SQL 语句 SQL Call 语 句用来 调用 存储 过 程 存储过 程的 名称 在 Call 关键 字之 后 然后 参数 标记 用于 由存 储过 程使 用的 全 部参数 提示 为了 执行 SQL Call 语句 ODBC 客户 程 序的 用 户必须 有该 存储 过程 的 EXECUTE 权限 要求绑 定参 数和 执行 该语句 的 Visual Basic 代码与 在 以前使 用 ODBC 预 准备语句 的代 码完全 一样 Dim lDep_ID As Long Dim bDip_Name(25)As Byte Dim lpLenDep_ID AS long Dim lpLenDep_Name As long ''Assign the parameter Values lDep_ID=0 lDep_ID=CLng(Text_DeptNumber.Text) ''convert the string to a byte array for parameter binding StringToByteArray bDep_nName,Text_DeptDesc.Text''Setup the binding lengths lpSenDep_ID =4 lpLenDep_Name=25 ''Bind the parameters result%=SQLBindParameter(ByVsl hstmt&, 1, SQL_PARAM_INPUT, _ SQL_C_LING, SQL_INTEGER, 4, 0, lDep_ID, 4, lpLenDep_ID) If result%<>SQL_SUCCESS Then ''Error Handling End if result%=SQLBindParameter(ByVal hstmt&, 2, SQL_PARAM_INPUT, _ SQL_C_CHAR, SQL_CHAR, 25, 0, bDep_Name(0), 25, lpLenDep_Name) If result%<>SQL_SUCCESS Then ''Error Handling End If Result % = SQLExecute(ByVal hstmt&) If result% <> SQL_SUCCESS Then ''Error Handling End If 绑定本 地变 量和 执行 SQL Call 语句的 ODBC 函数与 要求 执行 一个 标准 的预 准备 语句 完全一样 在前面的示 例中 可以看 到本地变量在代 码段的 开始 声明 重复一遍 位字 符 串用于 字符 串变 量中 得到 Visual Basic 的预 定位 置 来在内 存中 移动 字符 串变 量 接下 来 因为 spInsert 存储 过程 使用两 个 参数 所以 调用 函数 SQLBindParameter 两次 最后 当绑 定这些 参数 之后 当调 用 SLQExecute 函 数时实 际执行了 spInsert 存 储过程 5.8 ODBC 错误处理 报告 ODBC 错 误的 第一 个 位置是 在每 一个 不同 的 ODBC 函数生成 的 返回 代 码中 然而 返回代码本身并不能提供许多信息 相反 有关 ODBC 错误的主要附加信息源是 SQLGetDiagRec 函数 当 ODBC 函数调 用不 返回 值 SQL_SUCCESS(0) 时 ODBC 应用程 序 一般调 用 SQLGetDiagRec 函数 下面 的代 码说 明如 何 调用 SQLGetDiagRec 函数 Function DisplayODCError(hstmt&)As Integer Dim sSqlState$ Dim sSqlErrorMsg$ Dim iSqlErrorMsgLen% Dim lSqlErrorCode& Dim sSqlErrorStr% Dim iResult$ Dim iResponse% sSqlState$ = String$(16, 0) sSqlErrorMsg$ = String$(SQL_MAX_MESSAGE_LENGTH – 1, 0) DoIRecNbr = iRecNbr + 1 iResult% = SQLGetDiagRec(SQL_HANDLE_STMT,hstmt&, iRecNbr%,_ sSqlState$, lSqlErrorCode&, _sSqlErrorMsg$, _ Len(sSQLerrorMsg$),isqlErrorMsgLen) If iResult% = SQL_SUCCESS Or iResult%=SQL_SQL_SUCCESS_WITH_INFO Then If iSqlErrorMsgLen%=0 Then MsgBOX"No additional information",vbOKOnly Else MsgBox sSqlErrorStr$&Left$(sSqlErrorMsg$,_ iSqlErrorMsgLen%),vbOKOnly End If End If Loop Until result<>SQL_SUCCESS End Function 这个示例说明如何在 Visual Basic 中建立一个通用的 ODBC 错误处理函数 DisplayODBCError 函数 使用 SQLGetDiagRec 函数检 索与某 个给 定 的 ODBC 语句句柄相关 的全部 ODBC 错误 然 后 在消息 中显 示这 些错 误 这个 函数 依赖 于调 用路 径传送 到 相应 的 语句句 柄中 该函数 的第 一个 部分 声明在 函 数中 使用 的工 作变 量 接下来 使用 一个 Visual Basic 的 Do 循环 来执 行 SQLGetDiagRec 函数 直到 检索 出 全 部的错 误 正 如用户猜测的那样 某 个 给 定的 语句 句柄 可能 关 联 多个错误 条件 SQLGetDiagRec 函 数的第 一个 参数 是 一个整 数 它表示 SQLGetDiagRec 函数 将 要使 用的句 柄类 型 值 SQL_HANDLE_STMT 表 示 该函数将 使用一 个 ODBC 语句 句柄 第二 个参 数 是要使 用的 句 柄 第三 个参 数是 用作 输 入的一 个整 数 它 表示 要检索的 错误记 录 错误 记录 号 从 1 开始 如果 这个 参数 设置 为一 个 不存在 的 记录号值 那么 SQLGetDiagRec 函数 返回 值 SQL_NO_DATA 第四个参数是一个包含 SQLSTATE 代 码的输出 参数 第五个 参 数 是一个 指向输出 缓冲区的指针 它包含了 本地数 据源的错 误代码 第六 个参数是一个 包含错误文本的 输出字 符串 第七个参数是 一个输 入 参数 它 包含在第六个 参数中使用的 错误消息字符串 中可用 的字节数 最后 第 八个参 数 是一个 指向 缓冲 区的 指针 该 缓冲区 包含 在错 误消 息 字符串 中实 际返 回的 字节 数 只要 SQLGetDiagRec 函数返回 SQL_SUCCESS 那么该函数可以反复调用 SQL_NO_DATA 返回 代码 表示 没有 可用 的错 误记 录 5.9 小 结 ODBC 是一 种强大 而灵 活的 数据 访问 标准 在本 章中 介 绍了如 何为 SQL Server 建立 ODBC 数据 源 以及 如何 使用 ODBC API 执行 所有 的基 本数 据操 纵操作 借 助 这些信 息 就可以 准备 开始 使用 Visual Basic 建立自 己的 ODBC API 应 用程序 虽然编 写 ODBC 应用 程序 比使 用数据控 件和 活动数据 对象(ADO) 复杂 但是可以 获 得更 高性能 下 一 章我们将 介绍如 何用 ADO 访问 SQL Server第6 章 使用ADO 访问SQL Server 数据库 ADO 是基 于 OLE DB 基础 之上 用于 访问 OLE DB 兼 容数据 源 比如 SQL Server 2000 的数据 访问 接口 OLE DB 是一组 COM 接口 库 其 使得应 用程 序可 以访 问多 种 数据源 由于 ADO 使用 OLE DB 作为其 基础 它享 有 OLE DB 提 供的数据访 问体 系 结构 ADO 应 用开发 人员 无需 了解 如何编 写 COM 接口 6. 1 概 述 OLE DB 为 ODBC 的后 继者 考虑 到 ODBC 的 广 泛应用 使得 OLE DB 还有 许多 事 情 要做 ODBC 是基 于 SQL 的 它 可以 很好 地用 于关系型数据库访问 但 不 能用于非关 系型 数据源 像 ODBC 一样 OLE DB 也提 供了 对关 系型 数据 的访 问 但是 OLE DB 扩展了 由 ODBC 提供 的功能 OLE DB 主要用作 所有 数据 类型 的标 准 接口 除了 关系 型数据 库 的访 问外 OLE DB 还提 供了对 各 种各 样数据 源的 访问 包括像 Excel 电子 表 格 的列表数 据 像 dBase 的 ISAM 文件 电子邮 件 Windows 2000 的 Active Directory 和 IBM 主机的 DB2 数据 使用 OLE DB 用一 个 接口 就可以 访问 许多 不同 的数 据源 正如其 名称 所示 OLE DB 创建于 OLE 基础上 不像 ODBC 提 供了一种 DLL 调用级 接口 ADO 为 OLE DB 提供 了 COM 接口 此 接 口允许从其 他 OLE 兼 容的应用 程序 中调 用 OLE DB 是 Microsoft 的数 据访 问策 略( 称为通 用数据 访 问) 的基 础 通用数据访 问 指 的 是 一 组通 用接 口 它用来 代 表 来 自任何数 据源的 数 据 OLE DB 是使通用数据 访问 成为现 实的技 术 在数 据库 中所有 的 对象 维护的 理 念中 通 用数据 访问 和 OLE DB 各执一 端 创 建 OLE DB 时 不是 企图把 商 业要 求的所 有不 同 数据位移动到一个面 向对 象的 数 据库 中 而是 根 据 商 业 数 据 应在各 种 数据源中维护 这 一 理 解 创建的 OLE DB 提供 了针 对各 种数据 的类似 接口 OLE DB 可以 用 来访 问任何 可以 用基 本的 行和 列格 式表 示的 数据 6.1.1 OLE DB 体系 结构 使用OLE DB 的应 用程 序 一般 被 归类为 OLE DB 提 供者或 者 OLE DB 消费 者 图 6-1 表明了 OLE 提供 者和 OLE 消费者 之间 的关 系 正如看 到的 那样 OLE DB 消 费 者 就是为使 用 OLE DB 接口而编 写的应用程 序 相比 较而言 OLE DB 提供 者 负责访 问数 据 源 并且 通过 OLE DB 接口 向 OLE 消 费 者提供数据 进一步 划分 则有 两种 OLE DB 提供 者 数据 提供 者 和服务 提供 者 数 据提 供者简 单地 从 数据源中 提取数据 而 服务提供者则 传输和处理数据 服务 提供者一般提 供比较 高级的 功 能 来扩 展在 OLE DB 数 据提供 者中 的基 本数 据访 问 功能 Microsoft Query 是一个 OLE DB 服务提 供者 的示 例 而 SQL Server 的 Microsoft OLE DB 提供 者 是一 个数据提供者 的 样 例图6-1 OLE DB 消费者和提供者 正如希 望从 ODBC 源得 到 的那样 基于 不同 的 OLE DB 提 供者的功 能 OLE DB 提供 了不同 等级 的功 能 所有 OLE DB 驱动程 序支 持一 个 通用接 口 每 一个 驱动 程序可 以扩 展 OLE ODBC OLE DB OLE 功能 的基 本等 级 非常 类似于 每一 个不 同的 数 据源使 用 其自己 的 DB 提供 者 图 6-2 示意 了 如何要 求不 同的 OLE DB 提 供者访问 多个 数据 源 在 该图中 可 以看到 Visual Basic 应用 程序 如何 使用 OLE DB 访 问若 干个多机种数 据 源的高层概览 除 了 ODBC 数据 库 使 用不同 的 OLE DB 提供 者可 以访 问每 一个 不同 的数 据源 例如 可以 使用 SQL OLE DB Microsoft 的 SQL Server OLE DB 提 供者访 问 SQL Server 数据库 可以 OLE DB Microsoft Excel Exchange ODBC 分别使 用他 们的 提供者 访问 包含 在 或者 中的数 据 是一个 OLE DB 每 个数 据 源 提供者 这一 规则的 例外 为了提 供与 已有 的 ODBC 数 据 源最大 的兼容 性 Microsoft 开发了 MSDSSQL 这是 ODBC 的 OLE DB 提供 者 大多数的 OLE DB 提供者 提供 了直 接的 数据库 访 问 而 ODBC 的 MSDASQL OLE DB 提 供者使 用 已有的 ODBC 驱动程 序访 问数 据 ODBC 的 MSDASQL OLE DB 提 供者将 OLE DB 调用映射为等价的 ODBC ODBC OLE DB 调用 正如 用户 猜测的那 样 由这 个 的 提 供者 提 供的功 能依 赖于 基本的 ODBC 驱动 程序 图6-2 OLE DB 概览图每一个 OLE DB 提供 者通过 其显 示 的 COM 接口传送 数 据访问和反映其功 能 然而 OLE DB COM 接口 是一 种低级 接 口 要求 支持 指针 数据 结构 和直 接内 存分 配 因此 直 接使用OLE DB 提供 者不适 合 于不 支持低级 功 能( 如指针)的 开发环境 例如 Visual Basic VBA VBScript Java JScript JavaScript 和其 他几 种语 言 但它适合于 ADO ADO 允许 通过交 互式 脚本 语言 访问 OLE DB 提供者 这些 脚本语 言 需要访问数据 但 是不支 持低级 内存访 问和 操纵 6.1.2 ADO ActiveX Data Objects ADO 基本 上是 一个 OLE DB 消费者 它提 供了 对 OLE DB 数 据源的应 用程 序级 访问 ADO 还作 为许 多不 同的 Microsoft 开 发产品 Visual Studio Enterprise Edition 6 SQL Server 2000 等的 标准 组成 部分 提供 正如在 图 6-2 看到 的那 样 为了 访问 SQL Server 数据 OLE DB 提 供了两种 不同 的方 法 用于 SQL Server 和用于 ODBC 的 OLE DB 提供 者 ADO 可以使用这 两个 OLE DB 提 供者 它利用多层体系结构 这种体 系结 构使 用 ADO 从 基 本的网 络协 仪和 拓 朴 结构中 隔 离应用 程序 图 6-3 示意了 ADO OLEDB ODBC 和 PC 联 网 支 持之间的 关系 图6-3 ADO 使用的网络组件 在该图 的顶 端 可以 看到 Visual Basic ADO 应用 程序 Visual Basic 应 用程序 创 建和使 用各种 ADO 对象 ADO 对象框 架调 用相 应的 OLE DB 提供 者 如果 ADO 应 用 程序正在 使用 ODBC 的 OLE DB 提供 者 那么 将使 用 MSDASQL OLE DB 提供 者 如果 ADO 应用 程序使 用 SQL Server 的 OLE DB 提供 者 则使 用 SQLOLEDB 提供 者 当使 用 ODBC 的 OLE DB 提供 者时 ADO 加载 文件 msdasql.dll 该文 件再加载 ODBC 驱动 管理 程序 ODBC 的 OLE DB 提供 者映 射由 ADO 产生的 OLE DB 调用 到 ODBC 调用 然后 ODBC 调用被传送 给 ODBC 驱动 管理 程序 ODBC 驱 动 管 理程序负责 加载 相 应的 ODBC 驱 动程序 ODBC 驱动程 序一 般 使 用一 种网络 进程 通信(IPC) 方法 例 如 命名管 道 TCP/IP 套 接字或 者 SPX 与提供 对 目标数 据访 问的远 程 IPC 服务 器通 信 SQL Server 的本地 OLE DB 提 供者不 使用任何 附加 的中 间层当使用 SQL Server 的 OLE DB 提供者 时 ADO 加载 sqloledb 文件 该文 件直 接 加载和 使 用与数 据库 通信 的相 应的网 络 IPC 方法 IPC 客 户机组件通过正在使 用的 联网 协 议创建 与 相应的 服务 器 IPC 通信 链接 网络 协议 负责 发送 和接收 通 过网 络的 IPC 数据 流 通用的网 络协议 包括 NetBEUI,TCP/IP 和 IPX 最后 在 这个 堆 栈的底 部是 物理 网络 拓扑 结构 物理 网络包 括实 际连 接网 络系统 的 适配 器卡 和电缆 线 Ethernet 和 Token Ring 是两 种最 常 用的 网络拓 扑 6.1.3 ADO 体系 结构 就像其 他几 个数 据访 问对象 模 型一 样 ADO 是使 用 层次对 象框 架实 现的 然而 ADO 对象模 型比 数据 访问 对象(DAO)或者 远程 数据 对象(RDO)框 架更简 单 在图 6-4 中 可以看 到 ADO 对象 层次 结构 的概览 在 ADO 对 象 模型中 Connection Recordset 和 Command 对象 是三 个 主 要的对象 Connection 对象 表示 对远 程数 据源 的连 接 除了创建对数据源的连接之外 Connection 对 象还可 以用 来控 制事 务范围 Connection 对 象可与 Recordset 对象 或者 Command 对象关 联 Recordset 对象 表示 从数 据源 返回 的结 果集 Recordset 对 象既可 以使 用一 个打 开 的 Connection 对象 也可 以 创 建它 自己对目 标数 据源 的 连 接 Recordset 对象允 许查 询和修改数 据 每一 个 Recordset 包含 一个 Field 对象集合 其中 每个 Field 对象表示 Recordset 中 的 一个数据 列 Command 对象可用 来执 行命 令和 参数化的 SQL 语句 可以用 于 SQL 语 句 和返回结 果 集的 SQL 查询 像 Recordset 对象 一样 Command 对象 既可 以使 用一 个活 动的 Connection 对象 也 可以 创建 它自 己 到目标 数据 源的 连接 Command 对象包 含一 个 Parameters 集合 在这个 集合 中的 每一 个 Parameter 对象表示 Command 对象使 用的 一个 参数 在 Command 对象执 行参 数化 的 SQL 语句的情 况 下 每一 个 Parameter 对象 表示 SQL 语 句中的一 个参 数 在 Connection 对象 的下 面是 Errors 集合 在 Errors 集合 中 每一 个 Error 对 象包含了 一个错 误信 息 该错 误是由 ADO 对象 框架 中的 一个 对象 碰到 的 除了 图 6-4 所 示 的主对象 之外 Connection Command Recordset 和 Field 对象都有 一个 Properties 集合 它是由 一 组 Property 对象组成 的 每一个 Property 对象都可以用 于 得到 或者 设置 与对 象相关 的 各种 属性 刚开始 看到 ADO 框架 时 觉得其 层次 结构 与 DAO 和 RDO 相同 其实 不然 与其他 数据访 问对 象框 架不 同 所有的 ADO 对象——除去 Errors Fields 和 Properties 对象—— 都可 以创 建在 自己 身上 而 不需要访 问更 高一 层的 对 象 这就使 ADO 对 象框架 比其他 对 象模型 更简 单 更灵 活 例如 ADO 对象框 架允 许 打开的 访问 一个 Recordset 对象 而不 必首先 创建 一个 Connection 对象实 例 直接 使用 每一 个对 象而 不必 首先 调用 更 高层的 对象 的能 力使 ADO 比其 他对 象 框架使 用起 来更 加简 单 然而 正如将要在一些代码示例中看 到的那 样 ADO 并 非总 是 像 使用其 他框 架 那 样简单 明了图6-4 ADO 对象层次结构 ADO 是作 为一 个 OLE 自 动 服务器 创建 的 这 将更易于 从 Visual Basic 中访 问 ADO 函 数 使用 ODBC 或者 其他基 于 DLL 的 API 时 必须 在.bas 或者.cls 模 块中手工 声明 函数 和 参数 而使 用 ADO 只需 要 在项目 中增 加 对 ADO 的引 用 这些将在下 一节 解释 当在 Visual Basic 开发 环境 中增 加 ADO 的引用 之后 就 可以使 用 所有的 ADO 对象 在 Visual Basic 中 ADO 使用 所需 的步 骤总 结 如下 1. 在 Visual Basic 中 添加 对 Microsoft ADO 2.x 对 象 库的引 用 2. 使用 Connection Command 或者 Recordset 对象 打开 一个 连接 3. 使用 Command 或者 Recordset 对象 访问 数据 4. 关闭到 Connection Command 或者 Recordset 对象 的连 接 6.1.4 ADO 文件 相关 清单 表 6-1 汇总 了ADO 文件 相 关清单 目录 文件 描述 C:\Program Files\Common Files\System\ADO 所有 实现 ADO 对象的文件 实现 SQL OLE DB 提供者的动态链接库 C:\Program Files\Common Files\System\OLE DB Sqloledb.dll C:\Program Files\Microsoft SQL Server\80\ Sqloledb.h 开发 SQL OLE DB 消费者的 C/C++ 头 Tools\Devtools\Include 文件 开发 Microsoft Visual Basic 应用程序的 C:\Program Files\Common Files\System\OLE DB Sqloledb.rll SQL OLE DB 资源文件 实现 MSDASQL 提供者的动态链接库 C:\Program Files\Common Files\System\OLE DB Msdasql.dll C:\Program Files\Microsoft SQL Server\ Msdasql.h 开发 MSDASQL 消费者 的 C/C++ 头文 80\Tools\Devtools\Include 件 ADO 范例程序 C:\Program Files\Microsoft SQL Server\ ALL 80\Tools\Devtools\Samples\Ado 6.1.5 在Visual Basic 中 增加对 ADO 的引用 在从Visual Basic 中使 用 ADO 之前 必须 设置 对 ADO 类 型库的 引用 ADO 类型库也称为 ADO 自动 服务 器 当 第一次 从 Microsoft Web 站 点中下载 ADO 支 持时或者 当安 装了 包含 ADO 的一 种产 品( 这些 产品列 在前 面一 节中) 时 提供对 ADO 2.x 基 本支持 的文件 安装 在系统 上 然而 在 开始在 Visual Basic 项目 中使 用 ADO 之前 需要 在 Visual Basic 的开 发环境 中设 置对 ADO OLE 类型 库的引 用 为了 在 Visual Basic 5 或者 6 中增 加 对 ADO Objects 2.6 Library 的引用 启动 Visual Basic 然后 选择 Project| References 则显 示 References 对 话框 如图 6-5 所示 本 章后面 的内 容 以 ADO Objects 2.6 Library 为例进行 介绍 图6-5 设置对 ADO Objects 2.6 Library 的引用 在 References 对话 框中 滚动 Available References 列表 直到 看到 Microsoft ActiveX Data Objects 2.6 Library 选项 单击 这个 复选 框 然 后单击 OK 按钮 则将 ADO Objects Lirary 增加到 Visual Basic 的 IDE 中 与 ActiveX 控件 不同 在 Visual Basic 的 IDE 中 增 加一个引 用不会 在 Visual Basic 的 Toolbox 中创 建任 何可 视的 对 象 为了 看到 ADO 对象 属性 和方 法 需要 使用 Visual Basic 的 对象浏 览器 图 6-6 使用 Visual Basic 的对象 浏 览 器显示了 ADO Objects Library 图6-6 从对象浏览器中查看 ADO 类6.2 使用 ADO 连接 到 SQL Server 在 Visual Basic 开发 环境 中增 加了 对 ADO 2.6 Library 的引用之后 就可以准备 使 用 ADO 创建Visual Basic 应用 程序 不像 DAO 或者 RDO 对 象模型 ADO 不用在创 建 对 数据 源的 连接之 前必 须创 建的 顶层对 象 使用 ADO 应用 程 序执行 的第 一步 操作 是使 用 Connection Command 或者 Recordset 对象 打开 一个 连接 ADO 可以使用 ODBC 的 OLE DB 提供者 MSDASQL 或者 SQL Server 的 OLE DB 提 供者连 接到 SQL Server MSDASQL 提供者允 许 ADO 对象 框架 用于 已有的 ODBC 驱动 程序 而 SQL OLE DB 提 供者直 接连 接到 SQL Server 这两 个 OLE DB 提供 者都 可以 用于 ADO Connection Command 和 Recordset 对象 在下一 节中 将 介绍 如何使 用 ODBC 的 OLE DB 提 供者和 SQL Server 的 OLE DB 提供者 创建 一个 与 SQL Server 的连接 还将介绍如何使用 ADO Connection 对 象以及直 接通 过 Recordset 对象 连接 到 SQL Server 6.2.1 使用 ODBC 的OLE DB 提供者 打开 一个 连接 如果熟 悉 DAO 或者 RDO 对象框 架 可以 使用 ADO Connection 对象 和 ODBC 的 OLE DB 提供者 创建 一个 对 SQL Server 系统的 连接 这可 能是 开始 创建 ADO 应 用程序的 起点 像 DAO 和 RDO 一样 ODBC 的 OLE DB 提供者 MSDASQL 使用一 个 ODBC 驱 动 程序访问 SQL Server 这意 味着 运行 应用程 序 的系 统要 么必须 有一个用于 SQL Server 的 ODBC 驱动程 序 和一个 在 ODBC Administrator 中用于 SQL Server 的 Data Source Name(DSN) 要 么应用程 序必须 使用 一个 无 DSN 的连 接字 符串 下面的 代码 说明 如何 使用 ADO Connection 对象 和 MSDASQL 提供者提示 用 户选择 一 个将要 用于 连接 SQL Server 的已有的 DSN Dim cn As New ADODB.Connection ''DSN Connection using the OLE DB provider for ODBC-MSDASQL cn.ConnectionString= " _ DATABASE=pubs;UID=_ sLoginID _ ;PWD= sPassword” ''Prompt the user to select the DSN cn.Properties("Prompt") = adPromptComplete cn.Open cn.Close 在这个 代码 示例 的开 头 可以看 到创 建了 一个 ADO Connection 对象 的 新实例 cn 由 于 ADO 对象 不依 赖上 层对象 所以 每一 个对 象通 常必须 有 一个 使用 Visual Basic 的 New 关 健字的 Dim 语句 接下 来 Connection 对象 cn 的 ConnectionString 属性赋予了一个 ODBC 连接字 符串 像正 常的 ODBC 连接字 符串 一样 这个用 在 ADO 的 Connection String 属性 中的连接 字符串必须包 含一组预定义 的关键字 其中 每一个 关键字和与其 相关的 值使用 分 号分开 因为 ADO 基于 OLE DB 而不仅 是 ODBC 所以在连 接字 符串 中使 用的 这些 关键 字与在 标准 的 ODBC 连 接 字符串 中使 用的 关键 字有 点 儿不 同 表 6-2 提 供了有 效的 OLE DB 连接字 符串 关键 字表6-2 OLE DB 连接字符串的关键字 关键字 描述 OLE DB PROVIDER 这个可选的关键字可以用来指定将要使用的 提供者的名称 如果没有提 供提供者的名称 那么该连接将使用 MSDASQL 提供者 DSN 由 ODBC Administrator 创建的已经存在的数据源名称 一个已经存在的 ODBC 驱动程序的名称 DRIVER SERVER 一个已经存在的 SQL Server 系统的名称 ID UID 用于数据源的登录帐号 PWD 与该登录帐号 ID 相关的口令 DATABASE SQL Server 的目标数据库名称 当 OLE DB 连接 字符 串赋予 Connection 对象的 ConnectionString 属 性之后 该 Connection 对象的 Prompt 属性 赋予 adPromptComplete 常量值 该值 指定 ODBC 驱 动管理 程序 提示 任 何没有 在连 接字 符串 中提供 的需 求的 连 接信 息 Prompt 属性控制 ODBC 驱动 管理 程 序 如何 回应 包含 在连 接 字 符串 中的 关键 字和 值 表 6-3 列出 了用 于 Prompt 属性 的有 效值 表6-3 ADO MSDASQL Prompt 常量 常量 描述 AdPromptNever ODBC 驱动管理程序只能使用由连接字符串提供的信息建立连接 如果没有提供足够的信息 那么连接失败 abPromptAlways ODBC 驱动管理程序总是显示 ODBC Administrator 以提示输入连接 信息 abPromptComplete ODBC 驱动程序确定所有需要的连接信息是否都提供在连接字符串 中 如 果提供 了所 有必要 的信 息 就 不必 进一步 提示 建立 连接 如果 丢失 了任 何必 要的 信息 ODBC Administrator 提示用户输入丢 失的信息 AbPromptCompleteRequired 该选项像 adPromptComplete 一样 但禁止提示任何已经提供的信息 提示 ADO Connection Command 和 Recordset 对象 的 Properties 集合允许 在 属 性集 合 中 使用 命名 的条 目得 到 和设置属性 值 事实 上 有些 像 Prompt 属性 的 ADO 属 性没有 直接 通过 对象 框架显 示 并且 只 能通过 Properties 集 合访问 这个动态 的Properties 集合 为 ADO 对象模型 提供 了 比 DAO 或者 RDO 更 大的灵 活性 它还 可隐藏 属性 使其 比较 为直接 的 DAO 和 RDO 对 象模 型更难 找 到和使 用 如果不能 找到一 个认 为应 该存 在的 ADO 属性 试着 通过 迭 代 Properties 集 合来找到这 个 属性 在这个 示例 中 这 个连 接 字符串 没有 使用 PROVIDER 关键 字 所以 在默 认情况 下 使用 ODBC 的 OLE DB 提供 者 MSDASQL 这就 意味着与 SQL Server 的连接是通过一 个 ODBC 驱动 程序建 立的 另外 连接 字符 串没 有为 DSN 关 键字指 定值 这就 意味着这个 连 接字符 串必 须使 用 DRIVER 关键字 建立 一个 无 DSN 连接 或者为建立一个 到 SQL Server 的连接 ODBC 驱 动管 理 程序必 须提 示用 户输 入 Data Source Name(DSN) 在这 个示例 中 没有使 用 DRIVER 关键 字 值 adPromptComplete 在 Prompt 属性中 指定 这 就允许 ODBC 驱 动管理 程序 提示 用户 选择一 个 已经 存在 的 ODBC 数据 源 当用户 完成 了 ODBC 驱 动 管理程 序的 提示 之后 Connection 对象 cn 的 Open 方法连 接 到 SQL Server 这个 Connection 对象的 Open 方法带 三个 可选 的参 数 第一 个可 选的 参数 接受 包 含一个 OLE DB 连接字符 串 的字符 串 该参 数执 行与 Connection 对象 的 ConnectionString 属性 完全 相同 的 功能 可以 使用 这个 参数 作 为 设置 ConnectionString 属 性的另 一 种方法 第二个可选参 数接收一个字符串变量 它包含 一个用 于目标数据源 的有效 的登录 帐号 ID 第三个 可选 参数 接收 一个字 符 串变 量 它 包含 一个 用 于该目 标数 据源 的口 令 提示 OLE DB 连接 字符 串和 Open 方 法 的第 二个 和第三 个参数 都允 许指 定登 录信息 但是不 要同 时使 用这 两种方 式 因为 正常 情况 下 需 要使用 OLE DB 连 接字符串 提供 OLE DB 提供 者的 名称 所以 为了 简单 起见 常 常 只 提供登录信 息作 为 OLE DB 连接字 符串 的一 部分 在这个 示例 中 没有 其他进 程 所以 用 Close 方法终 止连 接 6.2.2 使用 ODBC 的OLE DB 提供者 打开 一个 无 DSN 连接 前面的 示例 说明 了如 何使用 MSDASQL 提供 者和 一个已 有 的 DSN 创 建一个 SQL Server 连接 然而 在一 些情 况 下 应 用 程序需 要 建立一 个基 于 ODBC 的连 接 并 且 它不能 依靠 一个预 配置 的 DSN MSDASQL OLE DB 提供者 也支 持使 用无 DSN 连接 使用无 DSN 连 接不需 要已 有的 数据 源 下面的 代码 说明 如何 使用 ADO Connection 对象 和 MSDASQL 提供者建 立一个 到 SQL Server 的无 DSN 连接 Dim cn As New ADODB.Connection DSNless Connection using the OLE DB provider for ODBC-MSDASSQL cn.ConnectionString = "DRIVER=SQL Server" & _ ";SERVER=" & sServer & _ ";UID=" & sLoginID & _ ";PWD=" & sPassword & _ "; DATABASE=pubs" cn.Open cn.Close 上面代 码第 一步 是创 建一个 新 ADO Connection 对象 cn 接下来 为 Connection 对象 cn 的 ConnectionString 属 性赋 予 了一个 连接 字符 串 因为 这 个连 接字 符串表示企图创 建一个无 DSN 连接 所 以 它 与在 前面示例 中提 供 的连 接字 符串 不 同 因为 没有 使用 PROVIDER 关 键字 所以 在默 认情 况下 使用 ODBC 的 MSDASQL 提供者 正 如 用户可能 猜 测的那样 不需要 DSN 关键 字来 创建 一个 无 DSN 连接 相反 DRIVER 关 键字的 值是“SQL Server” 表示应 该使 用 SQL Server ODBC 驱动 程序 注意 虽然 DRIVER 关键 字使 用的 值可 以放 在括 号中 例如{SQL Server} 但是 这不 是必要的 除了指 定将 要使 用的 ODBC 驱动程序 之外 一个 无 DSN 的 ODBC 连接 字 符 串必须 表 示将要 使用 的服 务器 和数据 库 这些 值由 SERVER 和 DATABASE 关 键字提供 最后 在 表 6-2 中描 述的 UID 和 PWD 关键字 提供 必要 的 SQL Server 登录信 息 使用无 DSN 连接 字符 串设 置 ConnectionString 属 性之后 Connection 对象 的 Open 方 法启动 一个 对 SQL Server 系统的 连接 然后 Connection 对象 的 Close 方法终止该连接 6.2.3 使用 SQL Server 的OLE DB 提供 者 打 开一个 连接 当没有 可用 的本 地 OLE DB 提供者 时 ODBC OLE DB 提 供者主 要是允许 ADO 应用 程序访 问 ODBC 兼容 的数据 库 ODBC 当然是 已建立 的 数 据库访问 标准 并且由各种流 行 的数据 库支 持 而 OLE DB 不是这样 它是一 门 新技术 几乎 没有 那多 本地 的 OLE DB 提 供者可 供使 用 并 且它 们 大多数 来自 Microsoft SQL Server 7 是最早有本 地 的 OLE DB 提 供者的 数据 库之 一 SQL Server 7 包括了由 文件 sqloledb.dll 提供 的用于 SQL Server 的 OLE DB 提供 者 使用 SQL Server 的 OLE DB 提供者 非常类 似于 使用 ODBC 的 OLE DB 提供 者 因为 SQL Server 的 OLE DB 不使 用 ODBC 所以 没有 必要 使用 一个 数据 源或 者一 个已 经存 在的 ODBC 驱动程 序 然而 必 须指定 OLE DB 提供 者 的名称 下面的 示例 说明 如何 使用 ADO Connection 对象 和 SQL Server 的 OLE DB 提供 者 建 立一个 对 SQL Server 的连接 Dim cn As New ADODB.Connection Connect using the OLE DB provider for SQL Server - SQLOLEDB cn.ConnectionSting =" PROVIDER=SQLOLEDB" & _ "; SERVER=" & sServer & _ "; UID=" & sLoginID & _ "; PWD=" & sPassword & _ "; DATABASE=pubs" cn.Open Perform processing here cn.Close 上面代 码与 前面 的那 些示例 一 样 创建 了一 个 ADO Connection 对 象的实例 然后 为 ADO Connection 对象 的 ConnectionString 属性赋予了一 个 OLE DB 连 接字符 串 该连 接字 符串使 用 PROVIDER 关键 字指 定使 用的 SQL OLE DB 提供 者 为了使 用 SQL Server 的 OLE DB 提供 者 必须 指定 PROVIDER 关键字 如 果省 略 了这个 关键 字 那 么默 认的提供者是 MSDASQL 另外 还需 要 SERVER 和 DATABASE 关键 字 SERVER 关 键字指 定将要 连 接的 SQL Server 系统名 称 DATABASE 关键字 确认 将要 使用 的数 据库 UID 和 PWD 关键 字提供 了登 录到 SQL Server 所需的认 证值 当设置 ConnectionString 属性 之后 Open 方法 启动 这 个连接 一旦 建立 了连 接 就可 以执 行其它数 据库的访 问 在这 个 示 例中 没 有附加 的工作 所以 使用 Close 方法 关闭连 接6.2.4 终止 一个 连接 像前面 示例 说明 的那 样 在终止 应用 程序 之前 应该使 用 Connection 对象 的 Close 方 法终止 该数 据库 连接 Close 方法 的一 个示 例如 下 Dim cn As New ADODB.Connection Perform work with the connect and then end it cn.Close 6.3 使用 Recordset 对象查 询 SQL Server 数据 库 ADO 允许 使用 Recordset 或者 Command 对象检索 数据 这两 种对 象都 可以 用于 一个 活动的 Connection 对象 或者 打开 它 们自己 的连 接 6.3.1 Recordset 分类 Recordset 对象 表示 一个 从数 据库 查询 中返 回的 结果 集 Recordset 对 象支持几 种 不同类 型的游 标 它们与不 同 类型 的 ODBC 游标 相对 应 ADO 支 持只向 前 静态 键 集和动 态 的 Recordset 对象 在 Recordset 打开之 前 必须 设置 在 Recordset 对象 中使 用的游 标 类型 如果没 有指 定希 望使 用的 Recordset 对象 类型 那么 ADO 自 动使用 一个 只向 前 游标 只向前游标 只向前游标 只向前游标 只向前游标 在缺 省情 况 下 ADO 使用 只向 前游 标 在 所有的 ADO 游 标类型中 该 游标提供了最好的性能和最低的开支 然而 它的功能也是最低的 使用只向前游标的 Recordset 对象 是可 修 改 的 但是 只 能 修改 当前 行 其他 用户 在 基 表中所作的任 何 变化 都 不 反映在 Recordset 对象 中 静态游标 静态游标 静态 游标 提供 了游 标打 开时 数据 的快 照 使 用静 态游标的 Recordset 对象是 静态游标 静态游标 不可修改 的 并且它们 不反映基表中 的任何变化 除 非该游 标关闭 并重新打开 由于其 静 态性质 所以 由静 态的 游 标创建 的 Recordset 对象 一 般比使 用键 集或 者动 态游 标 的 Recordset 对象的资 源强度低 然而 由于静态 游标制作了数据 的本地 拷贝 所以对 于大规 模的结 果 集 在使 用这 种游 标时 要 小心 键集 键集游标 游 标 键集游标创建了一组本地键 其中每一个 键是 结果 集内一 行的一个索引 键集 键集 游 游 标 标 当应用 程序 访问 使用 键集游 标 的 Recordset 对象时 来自本 地键 集的 键 值 从基表 中检索 相应 的行 使用 键集 游标 的 Recordset 对象是 可修 改的 但 是 当它们完全填满之 后 就不能动 态 地 反 映其 他用 户在 基表 中 所 作的变化 键集游标有很 强大的功能 但是他们相对 来说资 源 也是紧 张的 因 为客 户机系 统 必须 维护 整个 结果 集的关 键 值以 及包含实际数据值 的缓存 区 动态游标 动态游标 动态 游标 是功 能 最强大 的一 种 ADO 游标 但 是 它 们的资 源 也 是最紧张的 动态游标 动态游标 动态游标 非常类似于键 集游标 它们都使用与结果集 中每一 行相对应的一 组本地 键 并且 都是可 修改 的 然而 不 像使用 键集 游标 的 Recordset 对象 使用 动态 游标 的 Recordset 对 象可以自 动反映其他应 用程序在基表 中所作的变化 为 了 动 态 地 维护结果 集 使 用动态 游 标的 Recordset 对象 必须 连续 地刷 新结 果集 使用 任 何变化 自动 修改 本地 结果 集 显然 这 是一个 紧张 的进 程6.3.2 使用 只向 前的 Recordset 对象 Recordset 对象 可以 用于 一个 已经 存在 的 Connection 对象 也可 以打 开一 个对 目 标数据 源的连 接 提示 当 Recordset 对 象 打开其 自己 的 Connection 对象 时 ADO 对 象框架 自动 创建 一 个 Connection 对象 但是 这个对象 与 Visual Basic 的 程序变量 无关 这使得 使用 Recordset 对 象 更快更简 单 但是 它也增加 了 要求 为 每 一 个新 Recordset 对象创 建 Connection 对象 的开支 如果 应用 程序 要 创建多 个使 用相 同数 据库 的 Recordset 对象 那么 可 以更有效地 使用 Connection 对象 然后 使用 已经 存在 的Connection 对 象关 联每 一个新的 Recordset 对象 下面的 代码 清单 说明 如何使 用 Recordset 对象 和 ADO Connection 对象 Private Sub ForwardOnlyRecordset(cn As ADODB.Connection) Dim rs As New ADODB.Recordset Screen.MousePointer = vbHourglass Associate the Recordset with the Connection object named cn rs.ActiveConnection = cn Use the open method rs.Open"Select From stores",,,,adCmdText DisplayForwardGrid rs,Grid rs.Close Screen.MousePointer = vbDefault End Sub 在使用 Recordset 对象 之前 需要 把它 赋给 一个 Visual Basic 变量 在这 个子例程开 头 的 Dim 语句 创建 了一 个新 Recordset 对象 rs 接下 来 把 Recordset 对象 rs 的 ActiveConnection 属性设 置为 一个 活动 的 Connection 对象 必须 创建 该 Connection 对象并连接 到 SQL Server 就像在 前面 示例 中的 那样 ADO Connection 对 象 可以使 用 ODBC 的 OLE DB 提供者或者 使用 SQL Server 的 OLE DB 提供 者 这两 个 OLE DB 提供 者 的 ADO 代 码是相 同的 把一 个活动 的 Connection 对 象赋给 rs 对象 的 ActiveConnection 属性 就等 于 把新的 Recordset 对象和 连接 的 SQL Server 系统关 联 当设置 ActiveConnection 属性 之后 使用 Recordset 对象 的 Open 方法 打 开一 个只向前 游标 这个 Recordset 对象 的 Open 方法 带五 个可 选的 参数 第一个 参数 是一 个 Va r i a n t 数据 类型 就 像用 户认 为的那样 它可 以接 受许 多不 同的 值 ——例如 一个 已经 存在 的 Command 对象 的名称 一条 SQL 语句 一个 表名 或者一 个 存储 过程的 名称 在 前面 的示例 中 第一 个参 数包 含了 一 条简单 的 SQL Select 语句 该语 句将 创建一 个结 果集 这个 结 果集由 包含 在 存储 表中的 全 部行和 列组 成 Open 方法 的第 二个 可选 参 数可以 用 于关 联 Recordset 对 象和一 个 ADO Connection 对象 这个 参数 执行 的功 能 与 Recordset 对象的 ActiveConnection 属性完全 相 同 并且 可以 使 用这 个参 数作 为 设 置 ActiveConnection 属性 的 另 一种 方法 这个参 数既 可 以 接受 一个 包含 OLE DB 连接 字符 串的 字 符串 也可 以接受 一个 包含 活动 的 ADO Connection 对象名的 Va r i a n t 如果 指定 一个 OLE DB 连接字 符串 而不是 一个 Connection 对 象的名 称 那么 ADO 将隐含 地创 建一 个 Connection 对象 并且 使用 它建 立一 个到 目标 数据 源的 链接 Open 方法 的第 三个 可选 参 数指定 Recordset 对象 将要 使用 的游 标类 型 如果 没有 使用 这个参数 那么在缺省 情况下 游标 类型设置为只向 前类型 这是最简单 也是性 能最好 的 选项 表 6-4 提供 了一 些 ADO 常量 它 们用来指 定 Recordset 对象 将要 使用 的游标 类 型 表 6-4 Recordset 游标类型 ADO 常量 游标类型 adOpenForwardOnly 只向前游标( 默认) adOpenStatic 静态游标 键集游标 adOpenKeyset adOpenDynamic 动态游标 第四个 可选 参数 指定 OLE DB 提供者 将要 使用 的 锁定 类 型 如果 没有 使用 这个 参 数 那么在 默认 情 况 下 该 锁 定 的类型设 置为 只读 表 6-5 提供 了 ADO 常量 它们用来 指定 Recordset 对象 将要 使用 的锁定类 型 表 6-5 Recordset 锁定类型 锁定类型 描述 adLockReadOnly 只读( 默认) adLockPessimistic 保密锁定 adLockOptimistic 优化锁定 adLockBatchOptimistic 使用批模式修改的优化锁定 第五个 可选 参数 指定 Open 方法的 选项 这些 选项 参数 明确 地告 诉 ADO 如果第 一个 参数没 有包 含 Command 对象的 名称 该如 何处 理第一 个 参数 提示 这似 乎是 有些乏味 但 指定第五个 参数 的值 可 以提高 性 能 因为 ADO 不需要测 试数据 源来 确定 在 Open 方 法的第 一个参 数中 提供 了 哪一种 类型 的值 然而 如 果第五 个参 数 指 定了 一个 常 量 它与 第一 个参 数提 供 的值不 匹 配 那么 ADO 将 生成一 个错 误 表 6-6 提供 了 ADO 常量 它 们用来 指定 将由 Recordset 对 象使用 的选 项 表 6-6 Recordset 选项 选项 描述 源是未知的 ADO 必须测试源( 默认) adCmdUnknown 源是一个文件名称 adCmdFile 源是一个存储进程的名称 adCmdStoredProc 源是一个表名称 adCmdTable 源是一个命令( 或者 SQL 语句) adCmdText当 Open 方法完成之后 可 以处理 Recordset 对象 中的 数据 在前面的示例中 调用 DisplayForwardGrid 子例 程在网格中 显 示 Recordset 对象 rs 的内 容 在这 个代 码 的下一 段 将看到 如何 在 Recordset 对象的 行中 移 动 以及 如何访 问 在 Fields 集合中的列信息 Display- ForwardGrid 子例 程如 下 Private Sub DisplayForwardGrid _ (rs As ADODB.Recordset, Grid As MSFlexGrid) Dim fld As ADODB.Field Set up the grid Grid.Cols = rs.Fields.Count Grid.Rows = 1 Grid.Row = 0 Grid.Col = 0 Set up the Grid headings For Each fld In rs.Fields Grid.ColWidth(Grid.Col) = _ TextWidth(String(fld.ActualSize + 4, "a")) Grid.ColAlignment(Grid.Col) = 1 Grid.Text = fld.Name If Grid.Col < rs.Fields.Cunt - 1 Then Grid.Col = Grid.Col + 1 End If Next fld Move through each row in the record set Do Until rs.EOF Set the position in the grid Grid.Rows =Grid.Rows + 1 Grid.Rows = Grid.Rows - 1 Grid.Col = 0 Loop through all fields For Each fld In rs.Fields Grid.Text = fld.Value If Grid.Col < rs.Fields.Count-1 Then Grid.Col = Grid.Col + 1 End If Next fld rs.MoveNext Loop End Sub在这个 子例 程的 开头 可 以看到 一个 Recordset 对 象的实 例 rs 作 为第一 个参 数传 送 MSFlexGrid 对象的 实例 作为 DisplayRSGrid 子 例程的 第二 个参 数传 送 这样 就允 许用 许多 不同的 Recordset 和 Grid 对象重 复 使用同 一个 子 例程 在 这个子例程 中 的 Dim 语 句 创建了 一个 ADO Field 对象的实例 fld 注意 不像 前面 的那 些 ADO 示例 没必 要使 用 New 关 键字声 明 Recordset 对象 或者 ADO Field 对象 因为 这两 个 变量都引用 已经 创建 的 Recordset 对 象的实 例 当 ADO 对象 声明 以后 子例 程 DisplayForwardGrid 的下 一部 分建 立显 示 Recordset 对 象的网 格 首先 网格 的 列数由 Recordset 对象 中 Fields 集合的 Count 属 性设置 其次 设 置网格 的 Row 属性 使它 至 少有一 行包 含列 标题 信息 然后 使用 网格 的 Row 和 Col 属性 设置当 前网 格单 元在 第 0 行第 0 列(网格的左上 角) 一旦网格 的初始行 数和列 数设置之后 建 立每一个 网格列的 标题值和大小 结果集 中 的每一 列都 在 Recordset 对象 的 Fields 集合中有 一个对 应的 Field 对象 一个 For Each 循环 迭代包 含在 Fields 集合中的 全 部 Field 对象 在 For Each 循环 中 第一 个动 作是 使用 网格 的 ColWidth 属性 设置网 格的 列宽 为了 设置 合适 的 列宽 ColWidth 属性 要 求有网格的 Col 属性提 供的 当前 网格 列的索 引 ColWidth 属 性 必须 赋予一 个值(以 缇为单位 一个 缇是一个 打印机 点的 二十 分之 一) 使用 Visual Basic 的 TextWidth 函数返回每 一 个 Field 对象所需的 缇数 正确的 缇数 可以 这样 确定 使用 Field 对象的 ActualSize 属性 创 建一 个占位 符字符串 再加上四 个额外的字符 这样 有助于防止网格列过分 拥挤 接下 来 该网 格的 每 一个列 的 ColAlignment 属性 设置 为左 对齐的 单 元格文 本 然后 使用 Field 对象的 Name 属 性 作为该 网格列 的标 题文 本 最后 使用 网格 的 Col 属性增 加 当前列 If 语句确 保 Col 属 性 没有赋 予比最 大的 网格 列数 还大的 值 注意 因为 ADO 对像 框架不像 DAO 和 RDO 框架 那样 支 持 OrdinalPosition 属性 所以 必 须 增加 代码 手动 跟踪 当 前列的位置 接下来 Do Until 循环读取 Recordset 对象 中的 全部 列 Do Until 循环一直进行到 Recordset 的 EOF( 文件结尾)属性变 成真——它表示 已 经读取 Recordset 中的 全部行 在 Do Until 循环 内 增大 该网 格的 Row 属性来 扩大 网格 的 尺寸 增加 Row 属性将当前位置移动 到新的 网格 行 然后 将 当前网 格列 设置 为第 一列 For Each 循环将包含 在 Fields 集合中 的数 据值 移动 到网 格列 再 强调一下 If 测 试确保该 代码 不能 访问 一个 无效 的网 格列 当 Field 的全部值 都处 理完 之后 Recordset 对象 的 MoveNext 方 法移动游 标到 Recordset 对象 的下一 行 6.3.3 使用 键集 Recordset 对象 前面 的代 码示 例说 明如 何 使用 ADO 创建一个使用只向前游标的简单的 Recordset 对 象 只向 前游标速度快 效率 高 然而 它没有其他 类型的 游标功能强大 例如 只向 前 游标只 能按 照向 前顺 序一个 个 地传 送 Recordset 而 键 集游标允 许多 个传 送 以及 前后 滚动 下面的 代码 说明 如何 使用一 个 Recordset 对象 该 对 象使用 一个 键集 游标 Private Sub KeysetRecordset(cn As ADODB.Connection) Dim rs As New ADODB.Recordset Screen.MousePointer = vbHourglass Associate the Recordset with the open connection rs.ActiveConnection = cn rs.Source = "Select From employee" Pass the Open method the SQL and Recordset type parameters rs.Open , , adOpenKeyset, adLockReadOnly '' Display the grid -- use a 1 to display in forward order DisplayKeysetGrid rs, Grid, 1 rs.Close Screen.MousePointer = vbDefault End Sub 在这个示例中 创建了一个新的 Recordset 对象 rs 然后 Recordset 对象 rs 的 ActiveConnection 属性 设置为 cn 它是 已有 的带 有一 个活 动的 数据 库 连接的 ADO Connecton 对象的 名称 接下 来 Recordset 对象的 Source 属 性赋予了一个简单的 SQL Select 语句 该语句 将从 employee 表中返回 全部 行和 列 注意 本 章中 的几 个示 例 使用了 简单 的未 限定 的 SQL Select 语句 然而 除非知 道目 标表相 对较 小 否则 使用 SQL Select 语句 的 Where 子句 可 使 自己的结果 集 尽可能 的小 接下来 Open 方 法在 目标数 据 库上执 行源 SQL 语句 在这 个示 例中 Open 方法的 前 两个参 数不 需要 指定 因 为 它 们已经使用 Recordset 对象的 Source 和 ActiveConnection 属性 设置了 在第 三个 参数 中 的 adOpenKeyset 值表示这个 Recordset 对 象将使 用一 个 键集游 标 在第四 个参 数中 的 adLockReadOnly 值使这个 Recordset 为只 读 当 Open 方法 执行 了该 查询之 后 DisplayKeysetGrid 子 例 程在网 格中 显示 Recordset 对 象 rs 的内 容 DisplayKeysetGrid 子例 程使用 三个 参数 Recordset 对 象的名称 MSFlexGrid 对象的名 称和一个控制 数据的 显示方向的整数值 因 为键集 游标的功能大 于只向 前游标 所以该 子例 程包 含了 可以利 用 这些 功能 的增 强内 容 DisplayKeysetGrid 子例程 的代 码如 下 Private Sub DisplayKeysetGrid _ (rs As ADODB.Recordset,Grid As MSFlexGrid,nDirection As Integer) Dim fld As ADODB.Field Dim nForward As Integer Dim nReverse As Integer nForward = 1 nReverse = 2 Set up the grid Grid.Cols = rs.Fields.Count rs.MoveLast Grid.Rows = rs.RecordCount + 1 Grid.Row = 0 Grid.Col = 0 Set up the Grid headings For Each fld In rs.Fields Grid.ColWidth(Grid.Col) = _ TextWidth(String(fld.ActualSize + 4 "a")) Grid.ColAlignment(Grid.Col) = 1 Grid.Text = fld.Name If Grid.Col < rs.Fields.Count - 1 Then Grid.Col = Grid.Col + 1 End If Next fld If nDirection = nForward Then rs.MoveFirst Move through each row in the record set Do Until rs.EOF ` Set the position in the grid Grid.Row = Grid.Row + 1 Grid.Col = 0 Loop through all fields For Each fld In rs.Fields Grid.Text = fld.Value If Grid.Col < rs.Fields.Count - 1 Then Grid.Col = Grid.Col + 1 End If Next fld rs.MoveNext Loop Else rs.MoveLast Move through each row in the record set Do Until rs.BOF Set the position in the grid Grid.Row = Grid.Row + 1 Grid.Col = 0 Loop through all fields For Each fld In rs.Fields Grid.Text = fld.Value If Grid.Col Grid.Col = Grid.Col + 1 End If Next fld rs.MovePrevious Loop End If End Sub 与前面 提到 的 DisplayForwardGrid 子例 程一 样 由子例程 DisplayKeysetGrid 使用的 参 数允许 由许 多不 同的 Recordset 和 Grid 对 象 重复使 用 然而 由于这个子例程 主要是用于 键集游标 该种游标支 持向前或向后 滚动 所以它使 用了附 加的参数 控 制数 据 列出的 方 向 这个 子例 程的 内部 与 DisplayForwardGrid 子 例 程 也有点 儿不 同 由于 Keyset Recordset 对象有 不同 的功 能 在这个子例程 的开头 声明了两个 整型变量 并且赋了值 这些变量 只用来提高程序 后面部分的可读性 这些变量后面的代码建立网格 这段代码非常类似于前面显示的 DisplayForwardGrid 然而 这是一个 值 得 注意的差 别 由于键集 游 标 支持向后 移 动 所以 这个子 例程 可以 使用 Recordset 对象 的 MoveLast 方法移动到 该 Recordset 的结 尾 这将填充 Recordset 对象 然后 可以 根据 相应 的行 数确 定网 格的 大小 在 这 个示 例中 这样 确定 网 格 的大小 Recordset 的 Record Count 属性 的值 再加 上 用于列 标题 的一 个附 加行 接下来 确定 网格 列的 大 小 列标 题设 置为 在 Recordset 对象 的 Fields 集合中每一 个 Field 对象的数据库列名称 这 段 代码与子 例程 ForwardOnlyGrid 相同 这之 后的 下一 节代 码说 明如何 使用 键集 游标 的向前 和 向后 的滚 动功 能 传送到 DisplayKeysetGrid 子例程第 三个 参 数的值 控制 Recordset 数 据在 网格 中的显 示方 向 值 1 使 数据按照向 前顺 序列 出 值 2 使 Recordset 数据 按照 向后 顺序 列出 If 测试比较 nDirection 变 量的值和 整数 变量 nForward 的 值 如果 值相 等 那 么执行 If 语句 中第 一部 分的 代码 这 段 代 码与前面 DisplayFordGrid 子 例程中 的代 码基 本相 同 MoveNext 方法 和 Do 循环读 取 Recordset 对象 中的 全部行 For Each 循环从 Fields 集 合 中的 每 一 行中复制 数据 并放 入网格 If 语句的第二 部分 代码 在结 构上是 相 似的 但作 用相 反 Recordset 对象 的 Movelast 方 法 将游 标放 在 Recordset 的最 后一 行上 然后 Do 循环 和 Recordset 对象 的 MovePrevious 方法从 后向 前读 取 Recordset 再用 For Each 循环 从 Fields 集合的每 个 Field 对象提取 值 DisplayKeysetGrid 子例程能 按照 向前 顺序 或者 向后 顺序 在 Recordset 对象中显 示 数 据 而前 面的示 例只 是按 照向 前顺序 显 示数 据 下面 的子例 程说 明 如何 使用 DisplayKeysetGrid 子例 程和 Keyset 类型的 Recordset 按 照 相反的 顺序显 示 Recordset 数据 Private Sub KeysetRecordsetReverse() Dim rs As New ADODB.Recordset Screen.MousePointer = vbHourglass Pass the open method the SQL and Recordset type parameters rs.Open "Select From employee'',_ cn,adOpenKeyset,adLockReadOnly,adCmdText ''Display the grid -- use a 2 to display in reverse order DisplayKeysetGrid rs, Grid,2 rs.Close Screen.MousePointer = vbDefault End Sub 这个示 例说 明了 其与 前面的 那 些示 例的 明显 差别 除 了使用 DisplayKeysetGrid 子例程 按照相 反顺 序显 示 Recordset 之外 该子 例 程还 说明如何使 用 Recordset 对象 的 Open 方法 的第 一个 和第 二个 参数 传 送源 和 连接 信息 使用 Open 方法的 第一个和第二个参数也可以 明确地 对 Recordset 对象 的 ActiveConnection 和 Source 属 性赋值 第一 个参 数设置 Source 属性为 简单 的 SQL Server 语句 该语 句将 从 employee 表中检 索全 部行 第二 个 参数设 置 ActiveConnection 属 性为 一 个已经 存在 的 Connection 对象 cn 第三个参 数指 定键 集游 标 第四个 参数 设置 锁定 类型为 只 读 第五 个参 数识 别第一 个( 源 ) 参数 为 一个命令 文本 当 Open 方法 完成 之后 调用 DisplayKeysetGrid 函数 这个 打开 的 Recordset 对象的 名 称传送到 第一个参数中 某个已经存 在的网格名称用 在第二 个参数中 在 第三 个 参数中 用 值 2 来设 置显 示顺 序为 向 后 6.3.4 关闭 Recordset 在结束 应用 程序 之前 使用 Recordset 对象的 Close 方 法关闭任何 打开 的 Recordset 对 象 下面 是一 个 Close 方 法 的示例 rs.Close 另外 通过 设置 Recordset 对象 为空 也可 以关 闭连接 如下所 示 Set rs = Nothing 6.4 使用 Recordset 对象修 改 SQL Server 数据 库 可以采 用许 多方 法使 用 ADO 修改数据 首先 ADO 支 持可修改 的 Recordset 对象 它可以 使用 AddNew Update 和 Delete 方法 修改 包含 在一 个可 修改 的 Recordset 对象中 的 数据 ADO 还支 持使 用动态 的 和预准 备的 SQL 语 句 修改数 据 在本 章的 下一 部 分 将说 明如何 使用 Recordset 对象 修改 SQL Server 数据 然 后是几 个示 例 说明 如何 使 用预准 备 的 SQL 和 Command 对象修 改数 据 除了执 行查 询之 外 Recordset 对 象还 可以用来 修改 数据 然而 当看 到 Recordset 对象的 Open 方法 的各 种参 数 之后 并 不是全 部的 Recordset 对 象 都 是可修改的 修改 Recordset 对象的 能力 依赖 于该 Recordset 对象 使用 的游 标类 型 以 及使 用的 锁定类型 这两 种因素既 可以使用 Open 方法 的参 数 指定 也可以在打开 Recordset 之前指定为 Recordset 的对 象 CursorType 和 LockType 的属性 CursorType 和 LockType 两种属性 都影 响修 改 Recordset 对 象的能力 表 6-7 总结了 Recordset 对 象的 游标 和锁 定 类型以 及他 们支 持数 据修 改方 法的 能力 表 6-7 Recordset 的游标 锁定类型及修改性 Recordset 游标类型 可修改吗? adOpenForwardOnly 是( 仅当前行) adOpenStatic 否 adOpenKeyset 是 adOpenDynamic 是 adLockReadOnly 否 adLockPessimistic 是 adLockOptimistic 是 adLockBatchOptimistic 是 adLockReadOnly 锁定类 型参 数优 先于 游标类 型 参数 例如 如果 锁定类型 设 置为 那 么无论 使用 哪一 种游 标类型 结果 集都 是不 可修 改的 6.4.1 在Recordset 对 象 中插入 行 可以使 用 Recordset 对象 的 AddNew 方法和 Update 方 法在一个 可修 改的 ADO 结果集 中增加 行 下面 的代 码说明 如 何在 Recordset 对象 中 增加行 该 Recordset 对象是使用 一 个 键集游 标创 建的 Private Sub CursorAdd(cn As ADODB.Connection) Dim rs As New ADODB.Recordset Dim i As Integer Screen.MousePointer = vbHourglass pass in the SQL, Connection,Cursor type,lock type and source type rs.Open "Select Dep_ID,Dep_Name From department",_ cn, adOpenKeyset, adLockOptimistic, adCmdText Add 50 rows to the department table Note that the Bang ! notation is used to specify column names For i = 1 To 50 rs.AddNew rs!Dep_ID = i rs!Dep_Name = "Department" & CStr(i) rs.Update Next Display the new rows in a grid DisplayKeysetGrid rs,Grid,1 rs.Close Screen.MousePointer = vbDefault End Sub 该 Recordset 对象 的 Open 方法 的第 一个 参数 接受 一个 字符 串 该字 符串 包含 一 条定义 该结果 集的 SQL 语句 在 本例中 结果 集由 department 表的 Dep_ID 和 Dep_Name 两个列 组成 该表 是在 前面 的动态 SQL 示例 中创 建的 Open 方 法的第 二个 参数 包含 一 个活动 的 Connection 对象 cn 的名 称 第三 个参 数使 用常量 adOpenKeyset 指定该 Recordset 对象将 使 用一个 键集 游标 第四 个 参数包 含值 adLockOptimistic 这两个参数 表 示该 Recordset 对象 集是 可修 改的并且 使用 优化记录 锁 定 当该结 果集 打 开之后 使用一个 For Next 循环在 Recordset 对象 中增 加 50 行 在该 For Next 循环内 调用 AddNew 方 法创建一 个 行缓冲 区 该缓冲 区 包 含新行的 值 不 像 在 本 章前面的 示 例 中通 过 在 Fields 集 合 中循 环来 访 问列 这 个示例 说明 如何 使用 列名和 Bang(!)符号 来访 问一 个示 例 提示 这种符号是大小写敏感的 当使用 Bang 符号 时 必须用 完全 与数 据库 显 示 的 相同列 名来 指定 列名 否则 在运行 时会 生成 错误 通过 使用 循环 计数 器 获 取 的一个 唯一 的 整 数值 来设 置 Dep_ID 列的值 通过 使用 连接 文字 Department 和循环 计数 器的 字符 串表 示法 所形 成的 字符 串 来设置 Dep_Name 列 当行 值设 置之 后 调用 Update 方法在 Recordset 对象 和数据 源中 增加 行 接下 来 调用 DisplayKeysetGrid 子例程 在网 格中 显示 该新 行值 最后 使用 Close 方 法关闭 该 Recordset 对象 6.4.2 使用 Recordset 对象 修改 行 可以使 用 Recordset 对象 的 Update 方法来 修改 在可 修 改的 ADO 结 果集中 的行 下面的 代码说 明如 何修 改 Recordset 对象中 的行 该 对象 是 使用一 个键 集游 标创 建的 Private Sub CursorUpdate(cn As ADODB.Connection) Dim rs As New ADODB .Recordset Dim i As Integer Dim sTemp As String Screen.MousePointer = vbHourglass Pass in SQL,Connection,cursor type,lock type and source type rs.Open Select Dep_ID,Dep_Name From department ,_ cn,adOpenKeyset,adLockOptimistic,adCmdText Do Until rs.EOF `Trim off the blanks because ADO Doesn''t truncate the data sTemp = Trim(rs!Dep_Name) rs!Dep_Name = Updated & sTemp `Update the row rs.Update rs.MoveNext Loop Display the updated rows in a grid DisplayKeysetGrid rs,Grid,1 rs.Close Screen.MousePointer = vbDefault End Sub 这里同 样使 用 Recordset 对象 的 Open 方 法 创建 一个新的 Recordset 对象 rs Open 方法 的第 一个参数 接受 一个 字符串 该字 符串指定 结果 集 在这 个示例中 Recordset 对象由 Department 表中的 两个 列 Dep_ID 和 Dep_Name 组成 在第 二个 参数 中 使用 一个活 动的 Connection 对象 cn 在第 三个 和第 四个 参数 中 使用 常量 adOpenKeyset 和 adLockOptimistic 表示该 Recordset 对象 使用 一个 可修 改的 键集 游标 和 优化记 录锁 定 当 Recordset 对象 集创 建之 后 Do Until 循 环 将读取 该 Recordset 对象 中的 全部 行 当 Recordset 对象 的 EOF 属性为 真时 循环 结束 在该 Do 循环 内 Dep_Name 列的值 设置 为 一个新 的字 符串 值 该字符 串是 以由 文 字 Updated 和 当前的列 值连 接 而成开始的 然后 调用 Update 方 法修改 行 Recordset 对象 并且 MoveNext 方 法定位 游标 到下 一行 当 Recordset 对象中 的全 部行 修改 完之后 DisplayKeysetGrid 函数显 示已 经修 改的 department 表的内 容 最后 使用 Close 方法 关闭 该 Recordset 对象 6.4.3 从Recordset 对 象 中删除 行 Recordset 对象 的 Delete 方法 删 除在可 修改 的 Recordset 对象中的 行 下面 的代 码说 明 如何删 除一 个仅 向前 结果集 中 的行 Private Sub CursorDelete(cn As ADODB.Connection) Dim rs As New ADODB.Recordset Dim i As Integer Screen.MousePointer = vbHourglass Pass in the SQL, Connection,cursor type,lock type and source type.Note that this is a forward-only cursor but it can update the current row. rs.Open" Select Dep_ID,Dep_name From department", _ cn,adOpenForwardOnly,adLockOptimistic,adCmdText ''Delete all of the rows Do Until rs.EOF rs.Delete rs.MoveNext Loop ''Display the empty Recordset in a grid DisplayForwardGrid rs,Grid rs.Close Screen.MousePointer = vbDefault End Sub 就像在 前面 那些 示例 中一样 使用 Open 方法创 建一 个新 的 Recordset 对象 rs 它包含 表 department 中的 两个 列 Dep_ID 和 Dep_Name 第二 个参 数包 含一 个活 动的 Connection 对象 rs 的名 称 第三 和第四 个参 数包 含常 量 adOpenForwardOnly 和 adLockOptimistic 它 们指定 该结 果集 将使 用一个 仅 向前 游标 该游 标支 持 使用优 化记 录锁 定 进行 修 改 提示 仅向前 记录集常常被看作是只读的 因为 它们不支持像键集游 标的那种功能 然而 仅向 前 Recordset 对象确实支 持修 改当 前行 并且在 ADO 中 它们提 供 了 比 键集 或者 动态 的游 标 更好 的性 能 对数据源的 任何变化 都不 反映 到仅 向前 Recordset 对象 中 直 到 该对象被刷 新为 止 当 Recordset 对象 创建之后 Do Until 循环读取包 含在 Recordset 对象 中的全部行 Recordset 对象 rs 的 Delete 方法 删除 每一 行 MoveNext 方 法定位游 标到 结果 集 的下一 行 当全部 行都 被删 除之 后 DisplayForwardGrid 子例 程显 示空 的 department 表 最后 Close 方法关 闭该 Recordset 对象 6.5 使用 Command 对象 操纵 SQL Server 数据 库 使用预 准备 的 SQL 语 句 和 参数标 记的 能力 是允 许 ADO 用 于开发 高性 能数 据库 应 用程 序的特 征之 一 正像 前面讨 论 ODBC 指出的那 样 在数 据库应用 程 序中使 用 预准 备的语句 是可以 大幅 度提 高性 能的一 些 比较 小的 变化 之一 每 一次执 行动 态 SQL 语句 时 甚至 是该 语句重 新使 用时 必 须对动 态 SQL 语句 进行 语法 分析并创建数据访问 规划 虽然 动态 SQL 语句可 以很 好地 进行 ad hoc 查询 但是 它也 不是执 行重复 SQL 语 句类型的 最好 方法 这种 语句构 成在 线事 务处 理(OLTP) 类型 应用 程序 6.5.1 执行 静态 SQL 语句 预准备 的 SQL 有时 也称为 静态的 SQL 比较 适合 于 OLTP 应 用程序 其中可以反 复 使用 SQL 语句 使用 预准备 的 SQL 语句 SQL 的语法 分析 和数 据访 问规 划的 创建 只执 行 一次 以后 对预 准备 的语句 的 调用 非常 快 因 为已 经 有了编 译好 的数 据访 问规 划 提示 SQL Server 6.5 对于预 准备 SQL 语句 在 tempdb 数 据库中创 建临 时的 存 储过 程 而 SQL Server 7 及 2000 在其过 程缓 存 区 中创建数 据 访问规 划 过程 缓存 区是 SQL Server 缓存 区的 一部 分 它是 由 SQL Server 使 用的工 作内 存区 虽然存 储在过程缓存 区中的数据 访问规 划由所有用户 共享 但是每一个 用户都有一个单独执 行的 内容 另外 为 ad hoc SQL 语句 查询 创 建的访 问规 划也 可以 存储 在 SQL Server 的语 句缓 存区 中 不过 如果 执行 该规 划 的成本 超过 了一 定的 内 部 线程 它 们才能存储 并且它们只能在“ 安全” 情况 下重新使用 不能 依赖 于为 预准备SQL 语句 创建 的数据 访 问规 划 下面的 代码 示例 说明 如何创 建 一个 使用 预准 备 SQL 语句的 ADO 查询 Private Sub CommandPS() Dim cmd As New ADODB.Command Dim rs As New ADODB.Recordset Screen.MousePointer = vbHourglass With cmd Use a global Connection object -- cn .ActiveConnection = cn Set up the SQL statement . . CommandText = "Select From sales Where stor_id = ? " Add the parameter (optional) .CreateParameter, adchar, adParamInput, 4 Set the parameter Value .Parameters(0),Value = "7131" End With Set up the input parameter Set rs = cmd.Execute DisplayForwardGrid rs, Grid rs.Close Screen.MousePointer = vbDefault Ens Sub 在这个 子例 程的 开头 创 建了一 个新 的 Command 对象 cmd 和一 个 Recordset 对象 rs 该 Command 对 象将 用来 创 建和执 行预 准备 的 SQL 语句 而 Recordset 对 象用来 保 存 返回的 结果集 接下来 Visual Basic 的 With 语句 块 使用一 组 Command 对象的 属性 在 With 中 第 一行代 码设 置该 Command 对象 的 ActiveConnection 属 性为一 个活 动的 ADO Connection 对 象 cn 的名 称 然后 CommandText 属性 赋 予 了一个 字符串 该字 符串 包含 将要执 行 的 SQL 语句 该 SQL 语句将 返回 sales 表 中的全 部列 其中 stor_id 列 的值等于 在运 行 时提供 的值 问号(?) 是一 个参 数标 记 每一 个可 替代 的参 数都 必须 使用 一个 问号 表示 该示例 SQL 语句 在 Where 子 句中 使用一 个参 数 所以 只需 要一 个问 号标 记 接下 来 CresteParameter 方法 定义该 参数 的属 性该 CreateParameter 语句接受 四个 参 数 第一 个可 选参数 接受 一个 字符 串 它 用 于给该 参数命名 第二个参数接受一个 Long 变量 它 识别 该 参数使用 的数 据类 型 在前面的 示 例中 adChar 值表 示该 参 数包含 字符 数 据 CreateParameter 语句 的第 三个 参数 指定 该参数 是否用 于输 入 输出 或者两 者 都有 值 adParamInput 表示这 是一 个只 用于 输入的 参 数 表 6-8 列出了该 参数 允许 的值 表 6-8 ADO 参数方向常量 ADO 方向常量 描述 只输入参数 adPramInput 输出参数 adParamOutput 用于输入和输出的参数 adParamInputOutput 该参数包含了存储过程的返回值 它一般只用作第一个参数 adParamReturnValue (Parameter(0)) 第四个 参数 指定 该参 数的长 度 在前 面的 示例 中 值 4 表 示该参数 有 四 个字节长 7131 Parameters Parameter ( 当参数 的特 征指 定之 后 数值 放进 了在 集合 中第 一个 对象 在 本例中 是唯 一的) 的 Va l u e 属性 中 Parameters(0) 对应于 在 SQL Select 语 句中使用 的? 参数标 记 把数 值 7131 赋给 Parameter 对象 的 Va l u e 属 性则使 该 SQL 语句作出如下 判 断 Select From sales Where stor_id = 7131 接下来 Command 对象的 Execute 方法在 SQL Server 上运行该 Select 语句 由于 该 SQL Select 语句 返回 一个 结果 集 所以 cmd 对 象的 输出 赋给了一 个 Recordset 对象 然后 把 Recordset 对象 rs 传送 到 DisplayForwardGrid 子例程 中 这将显示 Recordset 对 象 的内容 最后 使用 Close 方法 关闭 该 Recordset 对象 如果该 cmd 对象 只打 算执行 一次 那 么使用 Recordset 对 象执行该 查询 不 能提高 性 能 然而 多次 执行 该 Command 对象可 以提 高性能 因为 SQL 语 句和访 问规 划已 经 准备好 了 为了多 次执 行一 个 Command 对象 只需为 Parameter 对象的 Va l u e 属性赋予一个新值 然 后重新 运行 该 Command 对象的 Execute 方法 6.5.2 执行 动态 SQL 语句 ADO 还可 以用 来在 远程 数 据库上 执 行动 态的 SQL 语句 动态 的 SQL 语 句可以 用于 各 种数据 管理 和数 据操 纵任务 下面 的示 例说 明如 何在 pubs 数 据库中创 建 department 表 Private Sub CreateTable(cn As ADODB.Connection) Dim sSQL As String On Error Resume Next Screen.MousePointer = vbHourglass Make certain that the table is created by dropping the table If the table doesn''t exist the code will move on to the next statement sSQL = Drop Table department cn.Execute sSQL Reset the error handler and create the table If an error is encountered it will be displayed On Error GoTo ErrorHandler sSQL = "Create Table department "_ & " (Dep_ID Char(4) Not Null,Dep_Name Char(25), "_ & "Primary Key(Dep_ID)) " cn.Execute sSQL Screen.MousePointer = vbDefauit Exit Sub ErrorHandler; DisplayADOError Screen.MousePointer = vbDefault End Sub 这个 CreateTable 子例 程实 际上 执行 两个 单独 的 SQL 查询 动作 第一 个语 句删 除 表 第二个 语句 重建 该表 SQL Dorp 语句确 保在 运行 SQL Create 语句 之前 该表 不存在 在该子 例程 的开 头 Visual Basic 的 On Error 语 句激 活该子例程 的 错 误处理 在这 第 一 个实例中 创建错误句柄 来捕捉 任何运行时的错误 然后 用 错 误 之后的 语 句重新 执行 该 子 例程 这种 方法 捕捉 当没有 存 在的 表时 执行 SQL Drop 语句可能生成 的 错 误 使用 ADO Connection 对象的 Execute 方法是 执行 动态 SQL 语 句最简 单的 方法 在这 个示例 中 一个 当前 连接到 SQL Server 的已 有 Connection 对 象执行 该 SQL 语句 Execute 方 法 的第 一个 参数 带一 个 字 符串 它包含了将要执行的命令 第一个实例使用 SQL Drop Table 语句 它将 删除 表 department 的任何已 有实 例 接下来 如果碰到任何运行时的错误 则 Visual Basic 的 错 误句 柄重 新设 置分 支 为 ErrorHandler 标签 这将 允许 在创 建 department 表 时 碰到的 任何 错误 都 由 DisplayADOError 子例程 显示 有关 ADO 错 误处理 的详 细内 容 参见 本章 后面 的“错 误处理” 一节 然后 使 用 Connection 对象 的 Execute 方法执 行 SQL Create Table 语句 注意 department 表不 是 pubs 样本 数据 库的 一部 分 为了 说明 数据 库修 改技 术 没 有 修改pubs 数据 库中 原有 表的 内 容 而 创建了 department 表 6.5.3 修改 数据 上一节 介绍 如何 使用 Recordset 对象 和游 标修 改 SQL Server 数据库 然而 对于编 码 来说 使用 Recordset 对象 修改 数据 是比 较简 单的 但是对 于性 能来 说 这 种方法通常不是 最优的 使用 预准 备 SQL 语句修改 数据 常常能提 供 更好 的 性能 特别 是对 于 OLTP 类型的 应用程 序 其中 SQL 语句有高 度的 重用 性 接下 来 将介绍如何 使用预准 备的 SQL 语句 和 Command 对象 的 Execute 方法 在 SQL Server 表中插 入 修改 和删 除数 据 使用 Command 对象 和预 准 备 SQL 语句 插入 行 SQL Insert 语句在 表中 增加 行 下面 的 示例说 明如 何使 用 Insert 语句和 Command 对象 Private Sub PreparedAdd(cn As ADODB.Connection) Dim cmd As New ADODB.Command Dim rs As New ADODB.Recordset Dim i As Integer Screen.MousePoiner = vbHourglass Set up the Command object''s Connection,SQL and parameter types With cmd .ActiveConnection = cn .CommandText = "Insert Into department Values(?,?)" .CreateParameter , adChar,adParamInput, 4 .CreateParameter , adChar, adParamInput, 25 End With Execute the prepared SQL statment to add 50 rows For i = 1 To 50 cmd.Parameters(0) = CStr(i) cmd.Parameters(1) = "Department" & CStr(i) cmd.Execute Next Create a recordset to display the new rows rs.Open"Select From department",cn, , , adCmdText DisplayForwardGrid rs, Grid rs.Close Screen.MousePointer = vbDefault End Sub 在这个示例中 创建新的 Command 和 Recordset 对象 然后 Command 对象的 ActiveConnection 属 性接 受 一个活 动的 Connection 对象 cn 的名 称 接下 来,CommandText 属 性赋予 了一 个 SQL Insert 语句 该语 句使 用两 个参 数标 记 然后 使用 CreateParameter 方 法指定 每个 参数的特 征 第 一个参 数包 含一个字 符值 其长 度为 4 个字 节 第 二 个参数 包 含一个 长度 为 25 个字 节的 字符 值 正如 所期 望的 Insert 语句那 样 这两 个参 数都 是只 输入 的 For Next 循环 在表 中增 加 50 行 在 For Next 循环 内 赋予了每 一个参数 使用 的 值 cmd.Parameter(0) 对 象引用 第 一 个参数标 记 cmd.Parameter(1) 对象引用第二个 参数标 记 就 像在前面那个使用游标增加行 的 示 例 那 样 第一个参 数(Dep_ID 列) 有一 个基 于循 环计 数器 的唯 一整 数值 第二个参数(Dep_Name) 有一个字符串 它包 含文 字“department” 和循环计 数器 的字符串 表 示 法 当 设置 参 数值之后 使用 Execute 方 法执 行 该预准备 语 句 Display ForwardGrid 子例 程在 网格中显 示 department 表的内容 然后 关闭 该 Recordset 对象 使用Command 对象 和预 准 备 SQL 语句 修改 数据 SQL Update 语 句修改 表中 的列 下面 的示例 说明 使用 SQL Update 语句 和 Command 对象修改 department 中的全部 行Private Sub PreparedUpdate(cn As ADODB.Connection) Dim cmd As New ADODB.Command Dim rs New ADODB.Recordset Dim i As Integer Screen.MousePointer = vbHourglass Set upthe Command object''s Connection, SQL and parameter types With cmd .ActiveConnection = cn .CommandText = _ "Update department Set Dep_Name = ? Where Dep_ID = ? " .CreateParameter , adChar, adParamInput, 25 .CreateParameter , adChar, adParamInput, 4 End With '' Execute the prepared SQL statement to update 50 rows For i = 0 To 50 cmd.Parameters(0).Value = "Updated Department "&CStr(i) cmd.Parameters(1).Value = CStr(i) cmd.Execute Next ''Create a recordset to display the updated rows rs.Open"Select From department",cn, , , adCmdText DisplayForwardGrid rs ,Grid rs.Close Screen.MousePointer = vbDefault End Sub 就像在 前面 的 Insert 示例那 样 在该子 例程 的 开头 创建 新的 Command 和 Recordset 对象 Command 对象的 Active Connection 属 性 有活动 的 Connection 对象 cn 的名称 这里 CommandText 属性 有一 条 SQL Update 语句 该 语句使 用两 个参 数标 记 在本例中 第一 个参数 引用 Dep_Name 列 第二 个参 数引 用 Dep_ID 列 然后,CreateParameter 方 法 指定每 一个参 数的 特征 For Next 循环 修改 表 department 中的全 部 50 行数据 在该 For Next 循环 内 赋予了每 一个参 数使 用的 值 并且使 用 Command 对象的 Execute 方 法运行 Update 语句 当修 改完 成之后 使用 DisplayForwardGrid 子例 程创 建一 个 Recordset 对象 并且 在网 格中 显示 该对 象 使用 Command 对象 和预 准 备 SQL 语句 删除 数据 就像 Insert 和 Update 操 作一样 可以 使用 Command 对象删除远程数据源中的一行或者多行数据 下面的代码清单说明如 何使 用预准 备 SQL Delete 语句 和 Command 对象从 SQL Server 数据库 中删 除数 据行 Private Sub PreparedDelete(cn As ADODB.Connection)Dim cmd As New ADODB.Command Dim rs As New ADODB.Recordset Dim i As Integer Screen.MousePointer = vbHourglass Set up the Command object''s Connection and SQL command With cmd .ActiveConnection = cn .CommandText = "Delete department" End With Execute the SQL once (that''s all that is needed) cmd.Execute ''Create a recordset to display the empty table rs.Open"Select From department",cn, , , adCmdText DisplayForwardGrid rs,Grid rs.Close Screen.MousePointer = vbDefault End Sub 由于使 用了 SQL 的 设 置 在某时 功能 这个 示例 比 前面的 插入 和修 改示 例简 单一 些 SQL 使 用 一条 语句 可修 改 多行的 功能 允许 使用 一条 SQL Update 语 句修改表 中的 全 部 50 行 数据 就像 在前 面那 些示例 一 样 首先 创建 新的 Command 和 Recordset 对象 然后 Command 对象的 ActiveConnection 属性 得到 一个 活动 的 Connection 对 象的名称 接下 来 把一条 SQL 语句赋 给该 Command 对象的 CommandText 属性 在本例 中 SQL Delete 语 句不使用 任何 参数 因为 该语 句没 有包含 任 何 Where 子句 当运 行 Execute 方法 时 将在 department 表 中的全 部行 上执 行 Delete 操作 注意 当使 用没 有 Where 子句的 SQL 操作 时 一定 要 非常小 心 这种 强大 的技 术 可以 无意中 修改 超出 人们 预想的 数 据 当这些 修改 完成 之后 使用 DisplayForwardGrid 子 例 程创建 一个 Recordset 对 象 并在网 格中显 示 然后 关闭 该 Recordset 对象 6.5.4 执行 存储 过程 存储过 程提 供了 访问 SQL Server 数据的最 快的 机制 当 创建 存储过程 时 预编 译的 数 据访问 规划 就被 增加 到 SQL Server 数据库 中 通过 使用 这个 已经 存在 的数据 访问 规划 该 应用程 序就 不必 对任 何到来 的 SQL 语句 进行 语法 分析 再 创 建新的数 据访 问规 划 这样 就 可以较 快地 执 行 查询 或者其他 数据 操纵动作 SQL Server 自动 地在 许多 用户之间共 享 存 储 过程 存储过程所能 实现的数据库安全性 比通 过直接在目标文件上设置许 可要强大得多 例如 可以 限制 直接 对 SQL Server 表的全 部访 问 而只 允许访问存储 过 程 当集 中控 制和管理时 存储 过程 可以 提 供对 SQL Server 数据 库访 问的 完全 控制 使用 ADO 可以 按照 与调用 预 准备 SQL 语 句 同样 的 方式调 用存 储过 程 Command 对 象调用存 储过程 问号 标记表示每一 个存储过程的输 入和输 出参数 下面 的示 例 是一个 简 单的存 储过 程 它 接受 一 个输入 参数 并返 回一 个输出参数 Create Procedure CountStoreQty ( @stor_id Char(4), @qty Int Output ) As Select @qty = Select Sum(qty) From sales Whee stor_id = @stor_id GO 在这个 示例 中 CountStoreQty 存 储 过程 接 受 一个字符 变量 它包 含 stor_id 作为输 入 并且返 回一 个整 数值 它 包含了 sales 表中匹 配提 供 的 stor_id 的 全部行 的 qty 列的总数 在 这个示 例中 使用 SQL Select sum() 函数汇总包 含在 qty 列中的 值 注意 在存 储过 程中 使用的 变 量名 称不必 与 在源 表中的列名相匹配 下面的 代码 示例 说明 如何使 用 Command 对象调 用 CountStoreQty 存储过程 Private Sub CallSP() Dim cmd As New ADODB.Command Dim parm0 As New ADODB.Parameter Dim parm1 As New ADODB.Parameter Dim sSQL As String On Error GoTo ErrorHandler Screen.MousePointer = vbHourglass Use the global cn Connection object cmd.ActiveConnection = cn cmd.CommandType = adCmdStoredProc cmd.CommandText =" CountStoreQty" parm0.Direction = adParamInput parm0.Type = adChar parm0.Size = 4 cmd.Parameters.Append parm0 parm1.Direction = adParamOutput parm1.Type = adInteger parm1.Size = 4 cmd.Parameters.Append parm1 parm0.Value = "7076" cmd.Execute Label_Mid.Caption = " Total Qty: "Text_Mid.Text = parm1.Value Screen.MousePointer = vbDefault ErrorHandler: DisplayADOError cn Screen.MousePointer = vbDefault End Sub 在这个 子例 程的 开头 可 以看到 创建 了一 个 Command 对象 cmd 和两 个 ADO Parameter 对象 param0 和 parm1 使用 Parameter 对象可 以替 代在 本章 使 用预准备 SQL 和 Command 对象 一节 中介 绍的 使用 CreateParameter 方法 这两种 技术 都可 以用 来指 定参数 标 记的特 征 并且 这两 种技 术都 可 以用来 执行 预准 备 SQL 语 句 和存储 过程 接下来 给 Command 对象的 ActiveConnection 属 性赋予一个已经存在 的 Connection 对 象 cn 的名 称 这样 就把 Command 对象和 一个 目标 数据源关联 了 然后 给该 Command 对象的 CommandType 属性赋予值 adCmdStoredProc CommandText 属性 赋予将要执 行 的 存 储过程 的名 称 因为 CommandType 属性告诉 ADO 使用 Command 对 象调用 存储 过程 所 以没有 必要 建立 一个 SQL 字符串来 包含 一条 ODBC Call 语句 该代码的下一 节说明如何初始化 Parameter 对象 对于每一个 Parameter 对象 设置 Direction Type 和 Size 属性 然后 使用 Parameters 集合的 Append 方 法增加 Parameter 对 象到 Parameters 集合中 注意 必须按照由存储过程或者预准备 SQL 语句使用参数的同样顺序 将每一个 Parameter 对象 添加 到 Parameters 集合中 换句 话说 在 为第二个 Parameter 对象( 表示 第二 个参 数)执行Append 方法 之前 必 须 为第一 个 Parameter 对象(表 示第一 个参 数) 执行Append 方法 当 Parameter 对象增 加到 Command 对象 的 Parameters 集合之 后 为 第 一个参数 的 Va l u e 属性赋 予一 个字 符串 它 包含一 个有 效的 stor_id 值 该值 将传 送到 CountStoreQty 存储过 程的第 一个 参数 中 然后 使用 Command 对象的 Execute 方 法调用该 存储 过程 当完 成对 该存储 过程 的调 用之 后 输出参 数的 值 可以在 第二 个 Parameter 对象(param1) 的 Va l u e 属性 中使用 在前 面的 示例 中 为 该值赋予 了一个 将要 显 示的文 本框 6.6 高 级 应 用 现在 已经 介绍 了如 何使用 基 本的 Connection Recordset 和 Command 对 象来查 询和 修改 SQL Server 数据库 在这 一节 将介 绍如 何使 用一 些更 高级 的 ADO 功能 例如 如何 使用批游 标执行修改 如何编码生成 多个结果集的查 询 提 交和 撤 消 事 务 以及使 用二进 制 图像数 据 6.6.1 批修 改 批修改允许将对 Recordset 对象的全部改变立即全部写回数据源 当使用不相关的Record 集时 例如 使用基 于 We b 的应用 程序 批修改是非常有用的 利用批修 改 可以 使用正 常的 AddNew Update 和 Delete 方法 修改 Recordset 对象 当完成 Recordset 对象的 全部修 改之 后 使用 BatchUpdate 方 法传 送整 个批修改到数 据 库 中 客户 的 Batch 游标库 生 成一个 SQL 查询 同 步本地 的 Recordset 对象 和远 程 SQL Server 系统上 的数 据 下面 的示 例说明 如何 使用 Recordset 对象 的 BatchUpdate 方法 Private Sub BatchUpdate (cn As ADODB.Connection) Dim rs As New ADODB.Recordset Dim i As Integer Screen.MousePointer = vbHourglass Pass in the SQL, Connection, Cursor type, lock type and source type rs.Open"Select Dep_ID, Dep_Name From department", _ cn, adOpenKeyset,adLockBatchOptimistic,adCmdText Add 50 rows to the department table For i = 1 To 50 rs.AddNew rs!Dep_ID = i rs!Dep_Name =" Add Batch Department " & CStr(i) rs.Update Next rs.UpdateBatch Display the new rows in a grid DisplayKeysetGrid rs,Grid, 1 rs.Close Screen.MousePointer = vbDefault ENd Sub 这段代 码非 常类 似于 在本章 前 面 使用 Recordset 对 象修改 行 一节 中 介绍的标准 ADO 游标修 改示 例 然而 有 两种重 要的 差别 首先 该 Recordset 对象 的锁定 类 型参 数 赋予了 常量 adLockBatchOptimistic 这就告诉 ADO 该 Recordset 对 象将使 用一 个批 游标 当打开 该 Recordset 对象 之后 使用 AddNew 和 Update 方法 向本 地的 Recordset 增加 50 行数据 记住下面 这一点非常重要 标准的键集游 标可以立即宣布新行到 数据源中 而这 种批游 标 直到执 行 UpdateBatch 方法 时 才修 改数 据源 然后 所有 修改 的行 都写 到基 表中 提示 CancelBatch 方 法 用来取 消由 BatchUpdate 操 作执行 的全 部悬 挂着 的修 改 6.6.2 使用 多个 结果 集 ADO 处理 多个 结果 集的 能 力允许 在 一个 Recordset 对象 中发 送多 个 SQL Select 语句以及调 用返 回多 个结 果集的 SQL Server 存储 过程 为了 提交 多个 SQL Select 语句 Recordset 对象必须使用一个本地的游标而不是一个服务器端的游标 下面的代码说明如何使用 Recordset 对象 处理 多个 结果 集 Private Sub MultipleRS(cn As ADODB.Connection) Dim rs As New ADODB.RECordset Dim fld As Field Dim sSQL As String Dim i As Ingeger Dim nFldCount As Integer Screen.MousePointer = vbHourglass Set up the three Select statments sSQL = "Select au_lname,au_fname From authors; " sSQL = sSQL & "Select title from titles; " sSQL = sSQL & "Select stor_name from stores" rs.Open sSQL,cn Set up the grid Grid.FixedRows = 0 Grid.Cols = 2 Grid.ColWidth(0 = _ TextWidth(String(rs.Fields(0).DefinedSize + 2, "a")) Grid.ColWidth(1) = _ TextWidth(String(rs.Fields(1).DefinedSize + 2, "a")) Grid.Rows = 1 Grid.Row = 0 i = 1 Do Grid.Col = 0 Grid.CellBackColor = &HC0C0C0 Grid.Text = "Recordset Number: " & i Grid.Col = 1 Grid.CellBackColor = &HC0C0C0 Grid.Text = " " Do Until rs.EOF Grid.Rows = Grid.Rows + 1 Grid.Row = Grid.Row + 1 nFldCount = 0 `Loop through all fields For Each fld In rs.Fields Grid.Col = nFldCount Grid.Text = fld.Value nFldCount = nFldCount + 1 Next fld rs.MoveNext Loop i = i + 1 Set rs = rs.NextRecordset Loop Until rs.State = adStateClosed Screen.MousePointer = vbDefault End Sub 在这个 多结 果集 的示 例中 可以 看到 sSQL 字符串 包含 了一 条复 合 SQL 语句 该语句 实际 上由 三 个 单独 的 SQL Select 语 句组成 它们 之 间 由分 号 分 开 第一个 Select 语句从 authors 表中 返回 姓和 名 第二个 Select 语句 返回 titles 表中 的 title 列 第三 个 Select 语句 返回一 个由 stores 表中 stro_name 列构 成的 结果 集 执行这 个复 合的 SQL 语句 返回 三 个不 同的结 果集 这个复 合的 SQL 语句由 Recordset 对象的 Open 方法 来执行 Open 方 法的第 一个 参数 包含复 合 SQL 语句 该 Open 方法的 第二个 参数 包含 一个 活动 的 ADO Connection 对象 cn 的名称 Open 方 法之 后的下 一段代 码 建立 一个 网格来包含这三个结 果集 的全 部 数据值 在 本例中 这些 查询 中的 列 数是已 知的 所以 Grid.Cols 属 性设置 为 2 然而 行 数是未 知的 为了容 纳各 种行 的数 量 网格初 始化 时 只包含 一行 然后 当从 Recordset 对 象 中读取每一 行时 增加 Grid.Rows 属性 动态 地扩 展网 格 Do 循环 处理 多个 结果 集 该 循 环连续 执行 直到 Recordset 对象 rs 的 State 属性等 于 adStateColsed 它表示没有更多的结果集可以使用 在这 个 DO 循环 内 建立 当 前网格 行 表示每 一个 结果 集的 开始 包含 字“Recordset Number” 的标 题值 是由 结果 集号 和第 一行 的列 标题组 合而 成 另外 当 前 单元格 的 CellBackColor 属 性设置为&HC0C0C0 它 以灰色而 不 是默认 的网 格颜 色来 显示 Recordset 标题 接下来 Do Until 循环 读取 每一 个结 果集 中的 全部 行 通过 对 Grid.Rows 增加 1 网格 动态地 扩展 以容 纳每 一行 网格 中的 当前 行使 用 Grid.Row 属 性增加 然后 使用一 个 For Each 循环取 出每 一行 的 Field 对象值 并 移 动到网格中 使用 MoveNext 方 法移动到 当前 Recordset 对象中 的下 一行 当该 Recordset 对象 rs 的 EOF 属性 变成 真时 Do Until 循环 结束 当前 结果集 中的 全部 行读 完之后 NextRecordset 方法 设置 Recordset 对象 rs 到 下一个可用 的结 果集 6.6.3 使用 事务 事务允 许组 合多 个 操 作 作为一 项 工作来 执行 这 有 助于确 保数 据库 的完 整性 例如 从储蓄帐 户到支票帐户 的转帐涉及到 多个数据库操作 只有 这些操作全部 成功完 成时 才 完成了这 次转帐 一般 从储蓄帐户到支票帐户的转帐 需要两 个单独的但却 是相关 的操作 从储蓄帐 户中转出以及转 入到支 票帐户 如果任何一个操作失败 这次转 帐都没 有完成 因此 在 本例中这两种 功能是同一个 逻辑事务的一部 分 它 们 被 组 合成一 个事 务 如果 转出操作 成功 而 转 入失 败 那么 整个 事务都被 取消 并 把 数 据库恢 复 到 转出进行 之 前的状 态 SQL Server 支持事 务 但是 并非所 有的 数据 库都 支持事 务 取消事 务 在 ADO 中的 Connection 对 象中 允许处 理事 务 Connection 对象 的 RollbackTrans 方法 可以 用 来 将 数 据库恢复到 该 事务 发 生 之 前 的 状 态 下 面 的示例说 明 如 何 使 用 RollbackTrans 方法 Private Sub TransRollBack(cn As ADODB.Connection) Dim rs As New ADODB.Recordset Screen.MousePointer = vbHourglass Start a transaction using the existing Connection object cn.BeginTrans Execute SQL to delete all of the rows from the table cn.Execute"Delete department" Now Roll back the transaction - the table is unchanged cn.RollbackTrans Create a recordset to display the unchanged table rs.Open"Select From department",cn, , , adCmdText DisplayForwardGrid rs, Grid rs.Close Screen.MousePointer = vbDefault End Sub 在这个 示例 中 执行 Connection 对象 cn 的 BeginTrans 方法 告诉这 个数据 库开 始一 个事务 然后 Connection 对象的 Execute 方法 用来 使用 一个 SQL Delete 语句删除 departmetn 表中的全部行 然而 不是将这种改变提交到数据库中 而是使用 Connection 对象的 RollbackTrans 方法 取消 事 务 把数 据 库 恢复为 department 表的原始 内容 创建 和 显示一 个 Recordset 对象 说明 该表 的内 容在执 行 RollbackTrans 方 法之后没 有改 变 提示 SQL Server 在一 个事 务日志文 件中维 护数 据库的 修改 该日 志文 件包 含了 对 数 据库所 作的 全部 修改 的记录 事务日 志包 含了 每一 个 事务之 前和 之后 的图 像 提交事 务 当事务 成功 完成 时 Connection 对象的 CommitTrans 方法把 事务 写到 数据 库中 在下 面的示 例中 说 明了 如何使 用 ADO 开始一 个事 务 然后 提交 到 SQL Server 数据库 中 Private Sub TransCommit(cn As ADODB.Connection) Dim rs As New ADODB.RecordsetScreen.MousePointer = vbHourglass Start a transaction using the existing Connection object cn.BeginTrans Execute SQL to delete all of the rows from the table cn.Execute "Delete department" Commit the transaction and update the table cn.CommitTrans Create a recordset to display the empty table rs.Open"Select" From department,cn, , , adCmdText DisplayForwardGrid rs, Grid rs.Close Screen.MousePointer = vbDefault End Sub 再次执 行 Connection 对象 的 BeginTrans 方法 通知 该数 据库开 始一 个事 务 Execute 方 法用来 执行 SQL Delete 语句 然而 这一 次 使用 Connection 对象 的 CommitTrans 方法把 这些改 变提 交到 数据 库中 最后 打开 一个 Recordset 对象 说 明在执行 CommitTrans 方法之 后该表 的内 容被 删除 了 6.6.4 存储 二进 制数 据 至今为 止 本 章介 绍的 这 些方法 都是 使用 存储 在 SQL Server 数据 库中的标准文本和 数 字数据类 型 然而 许多现代的数据 库应用程序还需 要处理图 形 和 声音 数据 图 形和声 音 数据实 际上 没有 什么 不同 这 两种 数据 类型 只是 特殊的二进制数据 格 式 一 般称为 BLOB( 二 进制大 对象) 然而 正像 BLOB 名称 所示 这种 类 型的数 据是 非常 大的 SQL Server 可以使 用 Image 或者 LongVarBinary 数据类型在 表中 存储 二进 制图 像数 据 ADO Field 对象提 供了 GetChunk 和 AppendChunk 方法 它 们 用来访问 存储 在 SQL Server 列中的二进 制 数 据 为了 管 理 二 进 制 对 象的大小 GetChunk 和 AppendChunk 方法 是 必要 的 标准的文本和数字数据可以在一个操作中设置和检索 而二进制数据可能有好几个 MB(megabyte) 这样 访问 这种数 据需 一 些时 间 而 GetChunk 和 AppendChunk 方法正 好 可以做 到这 一点 GetChunk 方法从 ADO Field 对象中检 索 二进 制块状数据 而 AppendChunk 方法在 Field 对象中 增加 块状数 据 下面 的 子 例程是 一个使 用 GetChunk 方法 在 SQL Server 表中检 索和 显示 二进 制数据 的 示例 Private Sub BinaryData(cn As ADODB.Connection) Dim rs As New ADODB.Recordset Dim fld As ADODB.Field Screen.MousePointer = vbHourglassrs.Open "Select"pub_id,logo From pub_info,_ cn, , , adCmdText Set up the grid Grid.Redraw = False Grid.Cols = rs.Fields.Count Grid.Rows = 1 Grid.Row = 0 Grid.Col = 0 Grid.Clear Size the grid columns bigger than normal Grid.ColWidth(0) = _ TextWidth(String(rs.Fields(0),ActualSize + 4, "a")) Grid.ColWidth(1) = _ TextWith(String(200, "a")) Grid.RowHeightMin = Grid.RowHeight(0) 3 Set up the headings For Each fld In rs.Fields Grid.Text = fld.Name If Grid.Col < rs.Fields.Count - 1 Then Grid.Col = Grid.Col + 1 End If Next fld Move through each row in the record set Do Until rs.EOF Set the position in the grid Grid.Rows = Grid.Rows + 1 Grid.Row = Grid.Rows - 1 Grid.Col = 0 Loop through all fields For Each fld In rs.Fields If fld.Type = adLongVarBinary Then Store the image into a temp bitmap file BintoFile"tempbmp.bmp",fld Grid.CellPictureAlignment = flexAlignLeftCenter Load the Temporary bitmap into the grid Set Grid.CellPicture = LoadPicture("tempbmp.bmp") Else Treat the column as regular data Grid.Text = fld.Value End If If Grid.Col < rs.Fields.Count - 1 Then Grid.Col = Grid.Col + 1 End If Next fld rsMoveNext Loop Grid.Redraw = True rs.Close Screen.MousePointer = vbDefault End Sub 使用 ADO 处理 二进 制数 据 非常像 处理 标准 的 字 符数 据 但是 也有 些不 同 这个样本 子例程 开始 先创 建一 个 Recordset 对象 它包 含 pubs 数 据库中 pub_info 表的 pub_id 和 logo 列 至今 为止 这似 乎是标 准 的 然而 在 本例中 logo 列 是一个 Image 数据类型 它包 含了每 一个 出版 商徽 标的图 形图 像 提示 当访问 二进制对象时要小心 它们一般适合于访问本地数据库 对远程数据库 的低速WAN 链 接对 于大 型 二进制对象 可能 会带 来性 能 问题 当该 Recordset 对象打开之后 初始化用来显示数据的网格 这里 两个网格列的 ColWidth 属性用来设置初始的 列 大小 因 为第一个列是一个 标 准 的 字 符 数 据类 型 所以使 用 Field 对象的 ActualSize 属性指定 列的 宽度 然而 由于 第二 个列 包含 了图 形数据 所以 ActualSize 属 性不能反 映显 示该 数据 所需的长 度 为了确保该列足够大 第二个列的大小 设置为 200 个字 符 接下来 使用 网格 的 RowHeightMin 属 性来提 高网 格中 所有 行的 高度 在本例 中 行高 度放 大为原 来 的三 倍 当网 格建 立之后 赋 予 网 格的列标 题 使用 For Each 循环迭 代 整 个 Fields 集合 用 于二 进 制 数据类型 的 列标 题与用 于标准字 符 列 的列 标 题 没 有 什么差 别 接下来 Do Until 循环 读取 Recordset 对象 中的 全部 行 对于 每一 行 For Each 循环检 索列数据 并且将数据 放进网格中 由于二进制数据 必须按 照与处理文本 数据不 同的方 式处理 所以 在将 数据 移动到 网 格中 之前 检查 每一 个 Field 对象的 Type 属性 如果 Field 对象包 含二 进制 数据 Field 对象的 Type 属性 将等 于常 量 adLongVarBinary 使用 BintoFile 子例程 转换 数 据 并 且把其放 在网 格的 CellPicture 属性 中 否则 该数 据将作为标 准 的 文 本数据 对待 Field 对象的 Va l u e 属性 将赋 给 grid.Text 属性 BintoFile 子 例 程把一 个二进制 列 的 内容 转 换 成一 个 字 节数 组 然后把这 个数组写 到一 个临时 的位 图文 件中 这 个 位图文件 可以 使用 LoadPicture 函数 赋给 CellPictwre 属性 该 BintoFile 子例程的 代码 如下 所示 Private Sub BintoFile(sFileName As String, fld As ADODB.Field) Dim bBuffer() As Byte Dim nLenLeft As Long Dim nChunkSize As Long Remove any existing destination file If Len(Dir$(sFileName)) > 0 Then Kill sFileName End If Re-create a new fiel Open sFileName For Binary As # 1 Use a 32K initial chuck size nChunkSize = 32768 nLenLeft = fld.ActualSize If nLenLeft < nChunkSize Then nChunkSize = nLenLeft End If Retrieve the binary data in chunks Do ReDim bBuffer(nChunkSize - 1) bBuffer = fld.GetChunk(nChunkSize) nLenLeft = nLenLeft - nChunkSize If nLenLeft < nChunkSize Then nChunkSize = nLenLeft End If Loop Until nLenLeft <= 0 Write the data to the file Put #1, , bBuffer Close #1 End Sub BintoFile 子例程接 受一 个字 符串 变量 它在 第一 个参 数中 包含 一个 临时 数 据文件 名 称 在第二 个参 数中 包含 一个 ADO Field 对象 接下 来 这个 子 例 程检查在 第一 个参 数中 命 名的这个 文件 的存 在性 如 果找到 这个 文件 那么 使用 Kill 语句删除 该文 件 然后 使用 Open 语句创 建一 个新 的临 时文件 注意 这个 文件 是以 二 进制模 式打 开的 当该临 时输 出文 件创 建之后 其大 小设 置为 最大 32K 并检查 ADO Field 对象的 ActualSize 属性 如果 在 Field 对象中的 数据 小于 最大 值 那 么 调整最 大值 如果 该数 据大于 32K 那 么二进 制数 据按 照 32K 检索 使用 一个 Do 循 环提 取二进 制 的块数据 并且把 它 放进一 个 字节数组中 当使 用 GetChunk 方法 把所有的二进制数据都拷贝到这个字节数组中之后 使用 Put 语 句 把 二 进 制数据写到打开的 文 件 中 然后 关闭该文件 包含 在临 时文 件中的 二进制 数据 可以 由其 他函数 访 问 6.7 错 误 处 理 使用 ADO 对象 框架 生成 的 运行时 的错 误放 在 ADO Errors 集合 中 当发 生一 个 ADO 运行时 的错 误时 调用 Visual Basic 的错 误句 柄 允 许捕捉 和回 应运 行时 的错 误 这种 与 Visual Basic 的紧 密集 成性 易于 处 理 ADO 错误 下面 的 ShowError 子 例 程说明 ADO 的 错 误处理 如何与 Visual Basic 的 On Error 函数集 成 Private Sub ShowError(cn As ADODB.Connection) Dim rs As New ADODB.Recordset On Error GoTo ErrorHandler Screen.MousePointer = vbHourglass rs.Open"Select From no_such_table",cn rs.Close Screen.MousePointer = vbDefault Exit Sub ErrorHandler: DisplayADOError cn Screen.MousePointer = vbDefault End Sub 该 ShowError 函 数试 图打开 一 个不存 在的表 的 Recordset 对象 在该 函数 的开 头 On Error 语句激 活 Visual Basic 的错 误句 柄 在本 例中 当碰 到 一个可 捕捉 的错 误时 On Error 语句使 该程序 分支 到 ErrorHandler 标签 中 对一个 不存 在的 表执 行 Open 方法 将使 ADO 对象框 架 生成一 个运 行时 的错 误 该错误使该 程序 重新 开始 执行 该标 签 后的第 一条语句 在这个示例中 DisplayADOError 子例程将在 无效的 Open 方法 之后 执行 下面的 程序 清单 说明 DisplayDAOError 子例 程 如 何使用 DAO 的 Error 对象和 Errors 集 合在一 个简 单的 消息 框中显 示 一个 有关 DAO 错误情 况的 信息 Private Sub DisplayADOError(cn As ADODB.Connection) Dim er As ADODB.Error For Each er In cn.Errors MsgBox "Number: " & er.Number & vbCrLf & _ "Source: " & er.Source & vbCrLf & _ "Text: " & er.Description Next End Sub 在这个 子例 程中 ADO Connection 对 象 作为 一个参数传 送 在 RDO 和 DAO 对象 框架 中 Errors 集合 由基 本的引 擎 对象 维护 而 ADO Errors 集合 包含 在 Connection 对象中 接 下来 声明 一个 新的 ADO Error 对象 er For Each 循环 在 ADO Errors 集合 中迭代 该循环 是必不 可少 的 因为 ADODB.Errors 集合可 以包 含多 个 Error 对象 每一 个对 象都 表示 一个 不同的 错误 情形 使用 For Each 循环 Number Source 和 Description 属 性的值 显示 在一 个消息 框中 ADO Error 对象的 Number 属性包 含 ADO 错 误消息数 字 Source 属性识别产 生该错误的源对象 正如所希望的那样 Description 属 性包含 了该 错误 情况 的文 本描 述 子例程 DisplayADOError 显示的 消息 框如 图 6.6 所示 图6.6 错误处理消息框 6.8 小 结 ADO 对象 模型 主要 是用 于 OLE DB 而 OLE DB 企图 对许多 不同 的数 据源 提供 多机 种 环境的 数据 访问 除 了访问 像 SQL Server 这样 的关 系型 数据 库之 外 OLE DB 还提供 了 对 各种其 他数 据源 的访 问 包括 Excel 电子表 格 Active Directory 和 Exchange 所以 基于 OLE DB 的 ADO 是 Microsoft 的主 流数 据访 问技 术第7 章 使用SQL-DMO 管理SQL Server 在本章 中 我们 将学 习如何 利 用 SQL-DMO 分布 式 管理对 象 从 Visual Basic 或者其 他 OLE 兼容 的开 发语 言中 编程 管理 SQL Server 首先 概述 SQL-DMO 并看看其 基 本 的 体系 结 构 然后介绍如 何从 Visual Basic 中使用 SQL-DMO 在这 一 节 中 将介 绍如 何 把 SQL-DMO 类型 库增 加 到 Visual Basic 的集成 开发 环境 中 还 将讨论如何 使 用 SQL-DMO 执 行一些 常见 任务 最后 通过提 供 两 个使用 Visual Basic 和 SQL-DMO 创建 的 示例实用程 序 结束本 章 7. 1 概 述 SQL Server 拥有管 理自 身的 强大能 力 图形 化管 理工具 MMC 使得 SQL Server 管理员 能够快速轻松地控制服务 器 的各个方面 这些 工具 的 底 层基 础就 是 SQL-DMO( 分布 式 管理 对象) SQL-DMO 并 不 是一种 新 的 数据库访问技术 它 不关心 数据 库存 储的 信息 主要 负 责数据 库本 身的 结构 和维护 以及 对包 含数 据库 的 SQL Server 操作 例如 程序 员经 常使 用 SQL-DMO 自动 化一 个接 口以 完成 数据 库管 理任 务 比如 备份 数据 库复 制及性 能 报告 SQL-DMO 允 许轻 松地开发适应用户 环境 的 定制 SQL Server 管理 应用 程序 使用 SQL- DMO 可以 使用 Visual Basic 或 者 任何其 他 OLE 兼容 语言来 创建 定制 的 SQL Server 管理界 面 该界 面允 许利 用 SQL Server 企业管理 器提 供的功 能 SQL-DMO 允 许应用 程序执 行那 些通过 SQL Server 企业管理 器手 动实 现的 功能集 事实上 SQL-DMO 是 SQL Server 企 业管理器 的基础 使用 SQL-DMO 可以列出 数据库 和表 增加登录帐户 控制 复制 引 入数据 引出 数据 以及 执 行许多 其他 管理 任 务 SQL-DMO 使 SQL Server 面向许多定 制 程 序 这些 程序 可以 显示 和 操纵 SQL Server 本身 7.1.1 初识 DMO DMO 是一 种 32 位 OLE COM 组件 对象 模型 对象 允许 应用 程序 访问 SQL Server 的管理 功能 SQL Server 的 DMO 是在 SQL Server 6 中首 先引 入的 并且 在 SQL Server 7 和 2000 中继 续得 到增 强 SQL Server 的 DMO 有 60 多 种不同的 对象 和 1000 多 种 不同的属 性和方 法 提供 了可 以访问 SQL Server 深层 功能 的方 法 SQL-DMO 使用 SQL Server ODBC 3.x 驱动 程序 连接 到 SQL Server 正如 其名 称所 示 SQL Server 的 DMO 企图促进在分布 式 环境中 使用 SQL Server SQL DMO 通过 将 SQL Server 的 管理 功 能扩展 到 网络中 的所 有客 户机 来做 到这 一点 可 以使用 SQL-DMO 访问 数据 但是 这不是 其真 正的 目的 其他 一 些应用 程序 编程 界面(API) 例如 Active Data Objects(ADO) Data Access Object(DAO) Open Database Connectivity(ODBC) 和 Remote Data Objects(RDO) 是比较好的数据访问机制 SQL-DMO 的真 正长 处 是 编程管 理联 网的 SQL Server 系统7.1.2 DMF SQL Server 2000 是通 过名 为 SQL 分布 式管 理框 架(SQL-DMF) 的集成对 象和 组件集 合 来管理 的 SQL-DMF 允许使 用 Microsoft Management Console(MMC) 和 SQL Server 企业管 理器 或者 通过 使用 SQL-NS SQL-DMO 和 DTS 编程 来交 互管 理 SQL Server 图 7-1 示意了 SQL-DMF 的主要组件 在 SQL-DMF 体系结构的顶 层 可以 看到 用来 管理 SQL Server 2000 的 不同的终 端用 户 应用程 序 SQL Server 企业管理 器是 作为 SQL Server 2000 的 一部分 提供 的 由 SQL Server 企业管理器使用的 同样的基本组件也可以用于定制 管理工具 SQL Namespace(SQL-NS) 把 SQL Server 企业管 理器 的用 户界 面元 素呈 现给 应用 程序 SQL 分布 式管 理对 象(SQL-DMO) 显示的 OLE 界面 可以 用于 控制 SQL Server Engine 和 SQL Server Agent 的各 个方 面 数 据转换 服务(DTS) 能转 换和 传输 包含 在 SQL Server 数据库 中的数据 这些 数 据可以用 于数 据市场 和数 据仓 库应 用程序 图7-1 SQL 的分布式管理框架(SQL-DMF) 7.1.3 SQL-DMO 的文 件 表 7-1 总结 了用 来在 SQL Server 上实现 SQL-DMO 的 客户机 文件表7- 1 SQL-DMO 的文件 目录 文件 描述 实现 SQL-DMO 对象的 DLL C:\Program Files\Microsoft SQL Server\ Sqldmo.dll 80\Tools\Binn SQL-DMO 帮助文件 C:\Program Files\Microsoft SQL Server Sqldmo80.hlp \80\Tools\Binn 本地化资源文件 资源目录根据 SQL Server 服 C:\Program Files\Microsoft SQL Server\ Sqldmo.rll 务器或者客户机的当地语言确定 比如 1033 80\Tools\Binn\ 的语言标识用十进制表示为 0X0409 这个数字 Resources\xxxx 代表美国英语 包含 SQL-DMO 的成员函数 类型 枚举数据类 C:\Program Files\Microsoft SQL Server\80\ Sqldmo.h 型和宏的 C/C++ 头文件 Tools\ Devtools\Include 包含接口和类标识的 C/C++ 头文件 C:\Program Files\Microsoft SQL Server\ Sqldmoid.h 80\Tools\ Devtools\Include Transact-SQL 脚本实现支持 SQL-DMO 的存储过 \Program Files\Microsoft SQL Server\ Sqldmo.sql 程 只在 SQL Server 服务器实例中存在 MSSQL\Install 7.2 SQL-DMO 的核 心 对象分 层结 构 SQL-DMO 核 心对 象是按照 SQL Server 企 业管 理器 同一 基本 组织 的分层顺序进行组织 的 核心 SQL-DMO 对象 的层 次如 图 7-2 所示 图7-2 SQL-DMO 的核心对象层次 SQL Server 的分布 式管 理对 象使用 了一 种层 次体 系结构 在 SQL Server 的 DMO 对象 框架的 顶部 是应 用程 序 应用程 序下 面是 SQLServer 对象 SQLServer 对象 表示一 种 物理 SQL Server 系统 在 SQLServer 对象下面 的各 种对 象集 合允许 使 用 SQL Server 系 统 的各个 方面 例如 RemoteServers 对象 集合 允许创建和管 理远 程服务器 同样 JobServer 对 象 集合 允 许控制 SQL Server 的任务 作业 和警 报 在 Databases 集 合中的每 一个 Database 对象表 示 一个物 理的 SQL Server 数据库 每一 个数 据库 对象 包含 一个 或者 多个 表对象 以及 其他 类 型的 SQL Server 对象 例如 触发 器 视 图 和存储 过程 类似 地 Table 对 象层包 括了 列 触发器 及键 和索 引对 象 最后 Replication 对 象允 许建立 和控 制 SQL Server 的 数 据库复制 下 一 节详 细研 究这 些领 域 的 每项内容 7.2.1 SQLServer 对象 层 SQL Server 对象是 最主 要的 SQL-DMO 对象 使用 SQLServer 对 象的实 例可 以访 问所 有 其他的 SQL-DMO 对象 SQLServers SQLServer 对象是 SQLServer 对象的集合 其中每一个对象都表 示 一个物 理的 SQL Server 系统 BackupDevices BackupDevices 对象 是 BackupDevice 对 象的集 合 其中 每一 个 对 象都表 示一 个包 含数 据库或 者 事务 日志 的文 件 Configurations Configurations 对象 包含 了 ConfigValue 集合 每一 个 ConfigValue 对像包 含了 一个 控制 SQL Server 的配置值 FullTextService FullTextService 对象把全 文本 索引 服务呈现给了 SQL-DMO 应用 程序 IntegratedSecurity IntegratedSecurity 包含了有关 映射 SQL Server 登录帐 户及 NT 用户和 组的 信息 Logins Logins 对象是 Login 对 象 的集 合 其中 每 一个 对 象 都 包含一个数 据库的 登录帐 号 ID 和口 令 Lanuages Lanuages 对象是 Language 对象 的集 合 其 中每一 个对 象都 代表 一种 由 本地 SQL Server 系统支 持的 语言 Registry Registry 对象包含 了存 储在 本地 系统 的注 册表 中所 有 SQL Server 信息 RemoteServers RemoteServers 对象是远 程 SQL Server 系统的集 合 其中 每一 个 RemoteServer 对象代表 一个 与本 地 SQL Server 系统连接 的 远程 SQL Server 系统 ServerRoles ServerRoles 对象是 ServerRole 对 象 的集合 其中 每一 个对 象包 含了 全部 SQL Server 系统角 色 7.3.2 Database 对象 层 SQL-DMO 的 Database 对 象允许 使用 各种 SQL Server 的数据库 对象 例如 缺省 规则 表和存 储过 程 图 7-3 示意 了 SQL-DMO 的 Database 对象 层 Database 对象 层位 于主 要的 SQLServer 对象之 后 SQL-DMO 的 主要对 象是 Database 对象 下面的 SQL-DMO 对象 包含 在 SQL-DMO 的 Database 对 象层中 Databases Databases 对象是 Database 对象的 集合 其中每 一个 对象 都包 含了 与 单 个 SQL Server 数据库相 关的 全部 信息 图7-3 SQL-DMO 的 Database 对象层次结构 图 7-4 SQL-DMO 的 Table 对象层次结构 DatabaseRoles DatabaseRoles 集合由 DatabaseRole 对 象组成 其中每一 个对 象 都 代表一 组有 类似 的安 全性属 性的 用 户 DBOption DBOption 对 象 包含了 一个 SQL Server 数据库选 项的 属性 数据 库选 项包括 了像 允许 Null 和 DMO 拥有 者 使用这 样的 属性 Defaults Defaults 对象 是 Default 对象的 集合 其中 每一 个对 象都 包含 了一 个缺 省 值 当没 有为 某个 列或 者 数据类 型提 供数 值时 使 用 该缺省 值 FileGroups FileGroups FileGroup FileGroup 对象 是 对象的 集合 每一个 对象 包含 了一个 数据 库文 件组 的信息 FullTextCatalogs FullTextCatalogs 对 象显示了一个 Microsoft Search 系 统 表的 属 性 Rules Rules 对象 是 Rule 对象的 集合 其 中每 一个 对 象指定 可以 存储 在数 据库 列 或者数 据类 型中 的有 效数据 StoredProcedures StoredProcedures 对象是 StoredProcedure 对 象的集合 其 中每一 个对 象要 么是 一组 Transact-SQL 语句 要 么是由 一个 动态 链接 库(DLL) 包含的扩 展存储 过程 SystemDatatypes SystemDatatypes 对象是 SystemDatatype 对象的集 合 其中 每一 个对象 代表 一个 由系 统提供 的 数据 类型 TransactionLog TransactionLog 对象代 表一 个数 据库的 事务 日志 UserDefinedDatatypes UserDefinedDatatypes 对象是 UserDefinedDatatype 对象的 集合 其 中每 一个 对象 代 表一个 用户 定义 的数 据类 型 Users Users 对象 是 User 对象 的集 合 其中 每一 个对 象代 表一 个单 独 的数 据库 帐 户 Views Views 对象 是 View 对象的 集合 其中 每一 个对 象代 表一 个包 括来 自一 个 表或者 多个 表的 记录 的 Select 语句 Tables Tables 对象 是 Table 对象 的集 合 其中 每一 个 对象包 含了 与单 个表 相关 的 信息 该表 包含 在一 个 SQL Server 的数据 库中 database 对象 的主 要对 象是 表对 象 其结 构具 体如 图 7-4 所示 Checks Checks 对象 是 Check 对象的集 合 其 中每 一 个对象 代表 一个 表的 检查 约 束 ClusteredIndex ClusteredIndex 对象代 表一 个与 单 给 定表相关 的聚 簇索 引 Columns Columns 对象 是 Column 对象 的集 合 其 中 每一个 对象 都代 表表 中的 一 个列 Indexes Indexes 对象 是 Column 对象 的集合 其中 每 一个对 象都 代表 表中 的一 个 列 Indexes Indexes 对象 包含了 Index 对象 的集 合 其中 每一 个对 象代 表表 中的 一 个 索引 Keys Keys 对象是 Key 对象的集合 其中 每一 个对 象代 表表 的一 个外 键 主键 或 者唯一 键 Permissions Permissions 对象是 Permission 对象的集合 其中 每一 个对 象代 表该 表的访 问权 限 PrimaryKey PrimaryKey 对象代表 一个 与指 定表 相关的 主 键 Triggers Triggers 对象 是 Trigger 对象的 集合 其 中 每一个 对象 代表 一组 Transact- SQL 语句 当修 改表 时执行 这 些语 句 7.2.3 JobServer 对象 层 SQL-DMO 的 JobServer 对象 允许 控制 SQL Server 的 Agent 功能 例如 任务 作 业和警 报 图 7-5 示意 了 SQL-DMO 的 JobServer 对象 层 图7-5 SQL-DMO 的 JobServer 对象层 SQL-DMO Agent 对象 的主 要对 象是 JobServer 对象 该 JobServer 对象 控制 SQL Server 的任务 和调 度功 能 JobServer JobServer 对象是 JobServer 对象 的集 合 其中每 一个 对象 代表 一个 SQL Server 的调度 引擎 Alerts Alerts 对象 是 Alert 对象 的集 合 其中 每一 个 对象代 表一 个 SQL Server 的 警报 警报 就是 要求 通知操 作 员的 事件 AlertSystem AlertSystem 对象包 含了 有关 警报引 擎的信 息 JobCategories JobCategories 集合 包含 Category 对象 包含 有 SQL Server Agent 的作业 组织 方法 JobFilter JobFilter 对象包含 了限 制将 要监 测的 作业 类型的信 息 JobHistoryFilter JobHistoryFilter 对象指定 将要 消除 的作 业历 史记 录 Jobs Jobs 对象 是 Job 对象 的集 合 其 中每 一个 对象包 含 了一个 调度 的 SQL Server 作业和 与其 相关 的作 业步骤 的 信息 OperatorCategories OperatorCategories 集合包 含 Category 对象 指定 SQL Server Agent 操作 员的 分类 方法 Operators Operators 对象 是 Operator 对象的 集合 其中 每一 个对 象代 表一 个 SQL Server 操作员 操作 员通 过电 子邮 件或 者页 面警 报来 接收 SQL 事 件的通 知 TargetServerGroups TargetServerGroups 对象是 TargetServerGroup 对 象的集合 其中每 一个 对象 包含 了有关 SQL Server 系统 组的 信息 TargetServers TargetServers 对象 是 TargetServer 对 象 的集合 其中 每一 个对 象包 含了作 为远 程作 业目 标的 SQL Server 系统的信 息 7.2.4 Replication 对象 层 顾名思 义 SQL-DMO 的 Replication 对象 允许 控制 SQL Server 的数据 复制 功能 图 7-6 示意 了 SQL-DMO 的 Replication 对象 层 SQL-DMO 的 Replication 对象允许 为 SQL Server 的 合并复 制和 事务 复制 建立和 控 制出 版 分布 和订 阅 Replication Replication 对象集合控制 SQL Server 的数据 复制 的出 版 分布 和订 阅角 色 每一个 Replication 对象包含 Publisher 图7-6 SQL-DMO 的 Replication 对象层 Subscriber 和 Distributor 对 象集合 Distributor Distributor 对象 包含 有关 分布 服务 器的 信息 Publisher Publisher 对 象 包含有 关出 版服 务器 的信 息 ReplicationDatabases ReplicationDatabases 对象是 ReplicationDatabase 对象的集 合 其中 每一 个对 象包 含 了有关 复制 的数 据库 的信 息 Subscriber Subscriber 对象 包含 有关 订阅 服务 器的 信息 演示 SQL-DMO 的示例程序 C:\Program Files\Microsoft SQL Server\80\Tools\ ALL Devtools\Samples\Sqldmo7.3 SQL-DMO 应用 初 步 为了获得其他 数据库平台的管理功能的编 程访问 可能需要掌握低级 的网络和系统界 面—— 如果 系统 界面 是可用 的 SQL-DMO 提 供 了一个 OLE 基础 使 SQL Server 的数据 库 管理功 能易 于访 问 因为 SQL-DMO 是作为 一组 OLE 对 象实现 的 所以 这些 对 象只能 在 32 位客户 应用 程序 中使 用 SQL Server 的 SQL-DMO 功 能可以由 任何 32 位 OLE 兼容的 开 Visual Basic Visual C++ Delphi 发工具 使用 包括 和 许多其他 工具 为了在 Visual Basic 中使 用 SQL-DMO 需要 执行 下列 基本 步骤 1) 包括 SQL-DMO 的 类型库(SQL DMO) 2) 创建 一个 SQL Server 对象 3) 将该 SQL Server 对象连接到 SQL Server 4) SQL-DMO 使用 的服 务 器对象 5) 与 SQL Server 断开连 接 7.3.1 将SQL-DMO 对象 添加到Visual Basic 中 开始在 Visual Basic 的开 发环 境中 使用 SQL-DMO 对 象之前 需要 安装 这些 对象 当 第一次 安装 SQL Server 的 32 位客 户程 序时 把 那些提 供对 DMO 的 基 本支持的 文 件 拷贝到 Visual Basic Visual 客户机 系统 中 然而 仍 然需要 在 的开 发环境 中进 行设 置参 考 以允许在 Basic 中使 用它 们 为了 在 Visual Basic 4 中增加 SQL-DMO 支持 需要从 Visual Basic 的 Tools 菜单中 选择 References 选项 以便在 Visual Basic 中参 考 SQL-DMO Type Library 为了在 Visual Basic 5 或者 6 中增加 SQL-DMO 参考 必须 从 Project 菜 单中选 择 References 选项 该操作 显示 References 对话框 如图 7-7 所示 图7-7 设置对 SQL-DMO Type Library 的参考在 References 对话 框上 滚动 直到 看到 选项 Microsoft SQLDMO Object Library 为止 单击该 选项 旁边 的复 选框 将 SQL-DMO OLE Object Library 文件添加到 Visual Basic 的交 互式开 发环 境(IDE) 中 当在 Visual Basic 的 IDE 中增加 参 考时 不会 在 Visual Basic Toolbox 中看到 添 加 的对象 而增 加 ActiveX 控件时 则 可 看到增 加的对 象 为了看 到 SQL-DMO 的属性 和方 法 必须 使用 Visual Basic 的 对象浏览 器 如图 7-8 所示 图7-8 在对象浏览器中查看 SQL-DMO 库 7.3.2 创建 SQL Server 对象 在 可 以使 用任 何 SQL-DMO 方法之 前 必须创建 一个 SQLServer 对 象的实例 这是 SQL-DMO 集中 最基 本 的 对象并 且必 须在 所有 的 SQL-DMO 应 用程序中 提供 使用 Visual Basic 可以 用三 种不 同的方 法创 建一 个 SQL Server 对象的 实例 使用 New 关键字 使用 CreateObject 函 数或 者使 用 一般的 对象 类型 创建新 的 SQLServer 对象的 首选方 法是 使用 New 关键 字 如下 所示 Dim oSQLServer As New SQLDMO.SQLServer 这种方 法创 建了 一个 名为 oSQLServer 的新 SQLServer 对象 它还 创建 了一 个 SQLServer 对象的 实例 注意 OLE 对 象的 一种 通 用命名 约定 是 在 名称前加 前 缀小写 字母 o 它 表 示 该变量 是一 个对象 另外 还可 以用 CreateObjects 函数 创建 一个 SQLServer 对象的示 例 如下 所示 Dim oSQLServer as SQLDMO.Server Set oSQLServer = CreateObject( SQLDMO.SQLServer ) 该方法 创建 了一 个名 为 oSQLServer 的 SQLServer 变量 然后 使用 CreateObject 函数生 成该对 象 通常 只在 不支持 New 关键 字的 老 Visual Basic 版 本 中使用这种 方法 另外 还可 以使 用 Visual Basic 的一 般对 象类 型创 建 一个新 的 SQLServer 对象 如下所 示 Dim oSQLServer as Object Set oSQLServer = CreateObject( SQLDMO.SQLServer ) 所有的Visual Basic 版本 都支 持这 种方 法 然而 这种方法没 有 使 用 CreateObject 函数 清楚 另外 这种 一般 的 对象类 型是 这三 种方 法中 最 慢的方 法 因为 它依 赖于 绑 定使用 OLEIDispatch 提示 当创 建 SQL Server 对象 使其 作为 一个SQLServer 对 象的实例 时 必须运行Connect 方法创 建一 个与 活动 的 SQL Server 系统 链接 后 才能 使用这 种对 象 7.3.3 连接 到 SQL Server 在 SQLServer 对象创建 之后 可以 使用 该对 象的 Connect 方法 把 SQL Server 对象连 接 到 SQL Server 系统上 下面 的代 码示 例说 明如 何使 用 SQL Server 对象 的 Connect 方法连 接 到 SQL Server oSQLServer.Connect sSQLServer$, sUserID$, sPassword$ 这个 Connect 方法 带有 三 个字符 串参 数 第一 个字 符串 参数 包含 SQL Server 的名称 第二个 参数 包含 一个 有效的 SQL Server 登录 帐户 的 ID 第三个 参数 包含 了该 帐 户的口 令 在 Connect 方法 成功 地完成 连 接之 后 该 SQL Server 对象(在 本 例中为 oSQLServer) 的 属性将 用第 一个 参数 所指定的 SQL Server 系统 中相 应的 值填 充 还可 以使用 SQLServer 对 象的所 有方 法 对象 和对象 集 合 提示 在 Connection 方法 成功 地执 行之 后 可以 使 用所有 的 SQL Server 的对象和集 合 直 到第一次 访问每一个 对象 时 才可以 检索到实际 的 SQLServer 值 当第 一次访问对象之后 SQLServer 对象把它们存储在本地的高速缓存中 使用 Refresh 方法 可使 SQLServer 对 象 修改存 储在 本 地高速缓存中的全 部 对 象 7.3.4 获取 属性 值 SQLServer 对象有 1000 多个 不同 的属 性 可以 从应 用程 序中 进行 访问 虽然 可以 阅读 到所有 的 SQLServer 属性 但是只 能写 一小 部分 的属性 SQL Server 的分布 式管理 对 象帮 助(sqldmo.hlp) 在线帮 助文件 列 出了 SQL-DMO 对 象的所有属性 并 且标明它们是只 读 的 还 是读写 的 提示 可以 使用Visual Basic 的 Object Browser 从 Visual Basic 的 IDE 中 列 出每一 个SQLServer 对象 的属 性 使用Visual Basic 的赋 值运 算符(=) 可 以检索 出是 标准 数据 类型 的全 部属 性的 属性 值 如下所 示 Dom sHostName As String sHostName = oSQLServer.HostName 在上 面的示 例中 可以 看到 一 个字符 串 sHostName 先用 Visual Basic Dim 语句声 明 然后使 用 Visual Basic 的赋 值运 算符 将 oSQLServer 的内容填充到 sHostName 字符串 变量中 这种技 术可 用于 标准 的 Visual Basic 数据 类型 例如 String Long 和 Integer 然而 对象 属性则 有一 点儿 不同 如 下所示 Dim oExecutive As SQLDMO.Executive Set oExecutive = oSQLServer.Executive 为了提 取 SQL-DMO 对象 属性 的内 容 必须 使用 Visual Basic 的 Set 语句 Set 语句可用来 把一 个对 象 参 考赋 值给一 个变量 在本例中 使用 Dim 语 句声 明一个名 为 oExecutive 的 SQLDMO.Executive 数据类 型的 对象 然后 使用 Set 语句 把 oSQLServer.Executive 对象的 内容赋 给 oExecutive 对象提示 当对象 超出范围时 与对象相关的 系统资源正常释放 然而 如果在全局函数 中 设 置对 象参 考 那么 必 须明确设置该对象为空 以便释放由该对象使用的系 统内存 和资 源 7.3.5 设置 属性 值 使用赋 值运 算符(=) 可以从 Visual Basic 中设 置 SQL-DMO 读 写属性 值 下 面的示 例 说明如 何设 置 SQLServer 对象的 ApplicationName 属性 Dim sApplicationName as String sApplicationName = MyApp oSQLServer.ApplicationName = sApplicationName 在上面 这个 示例 中 可 以 看到使 用 Visual Basic 的赋 值运算 符 将 oSQLServer.Applica tionName 的属性 设置 为 MyApp 该属 性值 包含 在 sApplicationName 字符串 变量 中 提示 不能 设置 SQL-DMO 对象 的任 何属 性 这 些对 象 属性是 只读 的 只 能设 置使用标 准数据 类型 的属 性 例如 String Boolean 或者 Long 7.3.6 SQL-DMO 的属 性集 合 在前面的图 7-2 中 SQL-DMO 的 核心对象层扩展了 对象集 合的 使 用范围 它是 有关 对象的 基本 组 例如 在 SQLServer 对象中 的 Databases 集合 是 Database 对 象的集合 提示 集合 对象 一般 用“s”结束 例如 Databases 表示 Database 对 象的集 合 表 7-2 列出 了 SQLServer 对象的部 分对 象集 合 表 7-2 SQLServer 的对象集合 对象集合名称 含义 Databases 数据库的清单 Languages 支持语言的清单 Logins 登录帐户 ID 的清单 RemoteServers 远程 SQL Server 的清单 ServerRoles SQL Server 的角色 同对 象 一 样 集 合 也 全 部包含在父对象中 在表 7-2 列 出 的对 象集 合中 所有 的对象 集合都 属于 SQLServer Core 对象 集合实际上是一系列有自己的属性和方法集的对象集合 下面的清单显示包含在 Databases 集合 中的 不同 属性 和方 法 Application Count Item ItemByID Parent Refresh Remove TypeOf UserData 可以看 到 Databases 集合 对象 的属 性都 对应 着数 据库组 的 应用 例如 Count 属性报 告包含在 该集 合中 的 Database 对象的 数量 而 ItemByID 属性 确认 在 Databases 集 合 中指定的 Database 对像 因为 所有 的集 合对 象包 含和 管理 多个 对象 所以所有集合的属性和方法都 是非常 类似 的 相比较 而言 下面 的清 单 列出了 单个 Database 对象的 一些 属性 Application CreateDate DataSpaceUsage Defaults FileGroups Name Owner Permissions Rules SpaceAvailable StoredProcedures Tables Views 可以看 到 Database 对象 的这 些属 性全 部与 SQL Server 的数 据库 直接 相关 例如 Size 属性报告该数据库在磁盘上占用的空间 Owner 属性包含数据库所有者的名称 注意 Database 对象 的某 些属 性也 是其 他的 集合 对象 例如 StoredProcedures 属 性是数据 库中 存 储过程 的集 合 同样 Tables 属性 是包 含在 该数 据库 中表 的集 合 可以 复 习一下 图 7-2 图 7-3 和图 7-4 看看 SQL-DMO 使用的 完整 的对 象和 集 合层 循环集 合 为了有效地使 用 SQL-DMO 首先需 要了解如何使 用集合对象 使用下面的代 码 可 以完成 在集 合中 进行 循环 For Each oDatabase in oSQLServer.Databases Debug.Print oDatabase.Name Next Visual Basic 的 For Each 语 句自动 在集 合的 全部 对象 中循 环 该示 例将 打印 一份 数据 库名 称 的清单 这些 数据 库包 含 在 oSQLServer 对象的 Databases 集合 中 在 For Each 块 中 的代码 引用了 集合 中的 当前 对象 获取指 定的 集合 对象 还需要理解如 何引用集合中的指定对象 根据对象名称或者根据集合 中的序数值 可 以引用 集合 中的 单个 对象 例如 根据 名称引 用一 个 Database 对象 可 以使 用 下面的 一条 语句 oSQLServer.Databases( model ) 或者 oSQLServer.Databases.Item( model ) 这两个 示例 是等 价的 它们 都引 用 了在 oSQLServer 对象中的 model 数据 库 因为 Item 方法是 默认 的 所以 可以省 略 使用 Item 方法 换 句话说 为了 根据 名称引用单个的 集 合 对象 使用 Item 方法传送包含 该 对象 名称 的字 符串 注意 这种 代码 章音暗示使 用了该 集 合对象 的 Item 方法 Item 方法 是 集合 中默认的 方法 因此 不 需 要明确 地使 用 oSQLServer.Databases.Item(“model”) Item 方法可 以接 受字 符串 或者序 号 为了根 据序 号引 用第 一个数 据 库对 象 可以 使用 下面的 语 句 oSQLServer.Databases(1) 再重复 一遍 这个 代码 暗 示 使用了 Databases 集合 的 Item 方法 在这 个示 例中 Item 方法传 送第 一个 序号 值 而不是 传送 包含 数据 库名 称 的字符 串 序数 值 1 返回 oSQLServer 对象中 的第 一个 数据 库名称 类似 地 序数 值 2 返回 第二个 数据 库名 称 等等 7.3.7 使用 SQL-DMO 访问 SQL Server 数据库 在本章 的第 一部 分 概述了 SQL-DMO 的对象层 次结 构 接 下来简 要介绍 了如何使 用 一些最 重要 的 SQL-DMO 集合 方法 和属 性 前面我 们提 及 SQL-DMO 具 有数 据访问 功能 下面我 们看 一个 用 SQL-DMO 访问 数据 库的 例子 Dim objMySvr As SQLDMO.SQLServer Dim objMy DB As SQLDMO.Database Dim objCustTbl As SQLDMO.Table Dim objNewCol As SQLDMO.Column ‘connect to server Set objMySvr=New SQLDMO.SQLServer ObjMySvr.Connect”ZHENGDONG”,”sa”,”sql” ‘get reference to customers table Set objMyDB= ObjMySvr.Databases(“NorthWind”) Set objCustTbl= ObjMyDB.Table(“Customer”) ‘create new column for Email address Set objNewCol=New SQLDMO.Column ObjNewCol.Name=”Email” ObjNewCol.Datatype=”nvarchr” ObjNewCol.Length- ObjNewCol.AllowNulls=Ttrue ‘add column to table ovjcustTbl.Columns.Add objNewCol objMySvr.DisConnect 程序首 先定 义了 四个 SQL-DMO 对象 objMySvr objMyDB objCustTbl 和 objNewCol 然后 objMySvr 的 connect 方法连接到所需的数据库 接着 将 objMyDB 的属性设置为 NorthWind 将 objCustTbl 的属性 设置 为 Customer 从而 定位 到表 Customer 最 后利用 Columns 的 Add 方法 向表 中插 入列 7.4 使用 SQL-DMO 管理 SQL Server 服务 器 通过 上面 的例 子我 们了 解 到使用 SQL-DMO 可以 访问 数据 库 但在前面我们说过 SQL-DMO 的优势在于实现服务器的 高级管理 功能 在本章的这一部分中 将讨 论如何 实现 SQL-DMO 的管 理功 能 首先介 绍一 个简 单的 例子 来说 明 SQL-DMO 是如 何工 作 的 以 便读者 对 SQL-DMO 编程 有个 感性 认识 图 7-9 是示 例运 行的 主窗体 上面 主要有 三个 文本 输入 框 txt_SQLServer txt_Login txt_Password 一个 复选框 chk_integrated 和两个 按 钮 cmdConnect cmdCancel 用 户通过 输入数 据库 名 帐号 和密码 程序 就会 连接 到选 定的数 据 库 连接 成功 后显 示如图 7-10 所 示的消 息框 7-9 7-10 图 程序主窗体 图 连接成功 消息框 下面是 程序 的源 代码 Private Sub cmdConnect_Click() On Error GoTo ErrorHandler Dim oSQLServer As New SQLDMO.SQLServer Dim bConnected As Boolean bConnected = False oSQLServer.LoginTimeout = 30 If chk_integrated.Value = 1 Then oSQLServer.LoginSecure = True oSQLServer.Connect txt_SQLServer.Text Else oSQLServer.Connect txt_SQLServer.Text, txt_Login.Text, _ txt_Password.Text End If reponse = MsgBox("Connect Successfully!", vbOKOnly) oSQLServer.DisConnect Exit Sub ErrorHandler: MsgBox (Err.Description) If bConnected = True Then oSQLServer.DisConnect End If End Sub 通过前面 的学习 上面的 代码不难看懂 然而就是 这段简单的代码揭示了 SQL-DMO 运行的 基本 模 式 下 面看一个 稍微 复杂 些 的 例子 通过这 个例 子我 们 可 以浏 览 SQL Server 服务器的 对象 如数据 库 表 视图 存储过程等 这个例 子所具有的功 能已经 初步体 现 了 SQL-DMO 编程 的价 值之 所在 程序在 Visual Basic 下编译 运行 的结 果如 图 7-11 所示 7-11 图 应用程序主窗体 这个应 用程 序示 例将 演示使 用 SQL-DMO 所需 的许多 关键 技术 包括 下面 内容 创建 SQLServer 对象 连接到 SQL Server 系统 SQL-DMO 使用 的对 象层 使用集 合 从集合 中获 取指 定的 对象 与 SQL Server 系统断开 连接 当完成 SQL Server 的名称 LoginID 和 Password 的内容 单击 Connect 按钮 之后 该 SQL Server 示例应 用程 序就 连接 到指定 的 系统 成功 连接 之后 该示 例应用 程序 列出 授权 该用户使 用的全部数据 库 在列表中 自动 填充所显 示的第 一 个数据库中 的表和 视图 存 储过程 双击任 何一 个 列 表项 修 改所有 互相 依赖 的列 表 例如 双击一个 不同 的 数 据库名 称 则使表 视图 及存 储过 程 列表 中的内 容也 会随 之更 新 类似地 双击 Tables 列 表 上的一个 Tables 不同的 表 视 图及 存储 过 程 列表中的 内容 会随 之更 新 然而 双击 列 表中的一 项并 不修改 Databases 列表 因为 在 SQL Server 对象层 次中表的 集 合 在数据 库 的下 面 7.4.1 创建 SQLServer 对象并连 接SQL Server 该示 例应 用程 序需 要做 的第一件 事情 是创 建一 个 SQLServer 对象 然后连接到 SQL Server 系统 在示例应用程序中 SQLServer 对象 在形式上是由所有对象共享 的 因此 Visual Basic Declaration 它可以 使用 主 形式 的 节 中 的下述代码 option Explicit Dim oSQLServer As SQLDMO.SQLServer Dim oCurrentDB As SQLDMO.Database Dim oCurrentTable As SQLDMO.Table Dim bConnected As Boolean 当 oSQLServer 对象创建 之后 可以 使用 该对 象的 Connect 方 法创建到 SQL Server 系统的连 接 在示 例应 用程序 中 当 用户填 充相 应 的 SQL Server 名 称以及 该 SQL Server 的 Login ID 和 Password 之后 单击 Connect 按钮 则启 动 SQL Server 的连接 单击 Connect 按钮执 行下面 的代 码 这些 代码在 cmdConnect_Click 子例 程中 Private Sub cmdConnect_Click() On Error GoTo HandleError ''If we''re connected, then disconnect and clear lists. If bConnected = True Then oSQLServer.DisConnect bConnected = False cmdConnect.Caption = "&Connect" ''Empty all lists and disable prompts. FillEmptyDatabaseList (False) Exit Sub End If ''Set mouse pointer to "Wait" cursor. frmIdxTest.MousePointer = 11 ''If no login name, use NT Integrated security in an attempt. ''to connect. If Not (Len(txtLogin.Text)) Then oSQLServer.LoginSecure = True End If oSQLServer.Connect txtServer.Text, txtLogin.Text, txtPassword.Text bConnected = True cmdConnect.Caption = "&Disconnect" ''Fill databases list. If any databases found, select first and ''fill tables collection. FillEmptyDatabaseList (True) If listDatabases.ListCount > 0 Then listDatabases.ListIndex = 0 listDatabases_Click End If frmIdxTest.MousePointer = 1 Exit Sub HandleError: frmIdxTest.MousePointer = 1 SQLDMOError Exit Sub End Sub Private Sub FillEmptyDatabaseList(bFill As Boolean) listDatabases.Clear If bFill = True Then Dim oDB As SQLDMO.Database For Each oDB In oSQLServer.Databases If oDB.SystemObject = False Then listDatabases.AddItem oDB.Name End If Next oDB End If End Sub oSQLServer 对象的 Connect 方法创建到 SQL Server 系统的连接 正如在 cmdConnect_Click 子例 程中 看到 的那 样 Connect 方 法使用 三个 参数 SQL Server 的系统 名 称 登录 帐户 ID 和口 令 这些 数值 都 由文本 框提 供 可以 在图 7-11 中看 到 SQL Server 的 名称包含在 txtServer.Text 属性中 登录帐户 ID 来自 txtLogin.Text 属性 口令则由 txtPassword.Text 属性 提供 如果用 户输 入一 个无 效的 SQL Server 名称 或者 其他 无效 的登 录信 息 那么 Connect 方 法就会 失败 如果 Connect 方法 失败 就 生成一 个实 时错 误 激活 Visual Basic 的 错 误句柄 这将使 cmdConnect_Click 子例 程 的 控制跳到 HandleError 标记 执行 PrintError 函数 如果 Connect 方法 成功 那么继 续 cmdConnect_Click 例程并禁止 cmdConnect 按钮 防止用 户企 图进 行第 二次连 接 接着 cmdConnect_Click 子例程调用FillEmptyDatabaseList 函数 用 SQL Server 系统中 的数 据库 名称 填充 Database 列表 FillEmptyDatabaseList 函 数主要 用AddItem 方法 到 连接成功 后 Connect 按钮 变为 Disconnect 以上简要介绍 了连接模块的主要内容 具 体细节相信读者在参照注释 语句后 很容易 会弄明 白 故此 不再 详述 7.4.2 数据 库模 块 oSQLServer 对象的 Databases 属 性包 含了用 于连 接 SQL Server 系统的 数据 库名 称集 合 在 Command_Connect_Click 子 例程中 可以 看到 使用 Visual Basic 的 For Each 操 作 循环搜 索数据库名称的集合 把每一 个 数 据 库 名 增 加到数据 库的列 表中 For Each 的 每一次 循环 都寻址 Databases 集合 中不 同的 Database 对象 例如 For Each 循 环第一次 执行 时 Databases 集合中 第一 个 Database 对象 将是 当前 的对 象 第二 次执 行 For Each 循环 时 Databases 集 合中的 第二 个 Databases 对象 将是 当前 的对 象 在 For Each 循环 内 使用 AddItem 方法把 包含在 当前 oDatabase 对象的 Name 属性 中的 数据 库 名称增 加到 List_Database 列表中 对 包含在 Databases 集合 中的 每一 个对 象 For Each 循 环执行 一次 为了自 动填 充 Tables 列表 Stored Proc or Views 列表 cmdConnect_Click 子例程执 行 listDatabases_Click 子例 程 该 子 例程可以 从给 定 的数据 库中 表的 清单 7.4.3 表模 块 在示例 应用 程序 中 检 索 数据库 表信 息的 代码 要么 是 在 cmdConnect_Click 子 例 程的末 尾自动执 行 要么当用 户双击数据库 清单中的某一个 数据库 名称时执行 这两 种 情况都 执 行listDatabases_Click 子例 程 如下 所示 Private Sub listDatabases_Click() ''If no item selected, simply return. If listDatabases.ListIndex = -1 Or listDatabases.ListCount = 0 Then Set oCurrentDB = Nothing Set oCurrentTable = Nothing FillEmptyTableList (False) Exit Sub End If ''Set to "Wait" pointer. frmIdxTest.MousePointer = 11 ''Get the selected database. Dim oDB As SQLDMO.Database Set oDB = oSQLServer.Databases(listDatabases.List(listDatabases.ListIndex)) ''If the database selected references the current one, return. Otherwise ''get the list of tables from the newly selected database. If oCurrentDB Is Nothing Then Set oCurrentDB = oDB Else If oCurrentDB.Name = oDB.Name Then Exit Sub End If Set oCurrentDB = Nothing Set oCurrentDB = oDB End If ''Get the list of tables and select the first one. FillEmptyTableList (True) If listTables.ListCount > 0 Then listTables.ListIndex = 0 End If ''Reset pointer. frmIdxTest.MousePointer = 1 End Sub Private Sub FillEmptyTableList(bFill As Boolean) listTables.Clear If bFill = True Then Dim oTable As SQLDMO.Table For Each oTable In oCurrentDB.Tables If oTable.SystemObject = False Then listTables.AddItem oTable.Name End If Next oTable End If End Sub 在 listDatabases_Click 子例程中首先创建一个 SQLDMO 表对象的实例 然后判断 databases 列表 是否 为空 若不为 空则 调用 FillEmptyTableList 函数填充 Tables 列表 正如使 用 Databases 集合 一样 使用 Visual Basic 的 For Each 操 作列出 Tables 集合的 成 员 For Each 操作 循环 搜 索 SQL Server 表名 称的 集合 这 些内 容表名在 oSQLServer 对象 的 Databases 集合 的某 个指 定 成员 中 指定 的 Database 对 象名称包 含在 sDatabase 字符串 变 量中 该变 量被 传送 到 oSQLServer.Databases 对象 的 addItem 方法中(addItem 方法是 Database 对象的 默认 方法 因此 不 需要 在代码 中明 确地 指出) 在 For Each 循环 内 每一 个 Tables 集 合的成 员用 Add Item 方法增加到 listTables 列表 中7.4.4 存储 过程 视图 模块 此模块的结构 及实现方法大体类似于前面 讲的表模块 阅读时请参照 前面的介绍 这 里就不 详细 介绍 了 只 给 出程序 清单 Private Sub listTables_Click() ''If no database or no tables, clear stored proc/view list and return. If oCurrentDB Is Nothing Or listTables.ListIndex = -1 Or listTables.ListCount = 0 Then Set oCurrentTable = Nothing FillEmptySProcList (False) End If ''Set to "Wait" pointer. frmIdxTest.MousePointer = 11 ''Get the selected table. Dim oTable As SQLDMO.Table Set oTable = oCurrentDB.Tables(listTables.List(listTables.ListIndex)) ''If the table selected references the current one, return. Otherwise ''get the list of dependent stored procedures and views. If oCurrentTable Is Nothing Then Set oCurrentTable = oTable Else If oCurrentTable.Name = oTable.Name Then Exit Sub End If Set oCurrentTable = Nothing Set oCurrentTable = oTable End If ''Get the list of dependent stored procedures and views. FillEmptySProcList (True) ''Reset pointer. frmIdxTest.MousePointer = 1 End Sub Private Sub FillEmptySProcList(bFill As Boolean) listSProcs.Clear If bFill = True Then Dim oQR As SQLDMO.QueryResults Set oQR = oCurrentTable.EnumDependencies(SQLDMODep_Children) ''If the object type is stored procedure or view, add it to ''the list of testable objects. Dim nRow As Integer Dim oType As SQLDMO.SQLDMO_OBJECT_TYPE For nRow = 1 To oQR.Rows oType = oQR.GetColumnLong(nRow, 1) If oType = SQLDMOObj_StoredProcedure Or oType = SQLDMOObj_View Then listSProcs.AddItem oQR.GetColumnString(nRow, 2) End If Next nRow End If ''Clean up dependent objects. End Sub 7.4.5 程序 的其 他模 块 另外 本程序还有其他模块 如窗体的装载 卸除等 这些 都比较简单 这里 只给出 它们的 程序 清单 Private Sub cmdExit_Click() Form_Unload (0) End End Sub Private Sub Form_Load() bConnected = False Set oSQLServer = New SQLDMO.SQLServer oSQLServer.LoginTimeout = 15 End Sub Private Sub Form_Unload(Cancel As Integer) On Error Resume Next If bConnected = True Then oSQLServer.DisConnect End If oSQLServer.Close Set oSQLServer = Nothing End Sub 7.4.6 SQL-DMO 的错 误句 柄 SQL-DMO 方法 生成 的 错 误可以 使用 Visual Basic 的 标准 错误句柄来获得 如果 SQL- DMO 方法 生成 了某 个错 误 并且 Visual Basic 的 错误处 理句柄实现 那 么用 户将会 看到 一 个 Visual Basic 的运 行时 错误 并且 该应 用程 序将 被 终止 提示 利用Visual Basic 的内 置错 误句 柄来 俘获 任 何 SQL-DMO 错误 并 且 防止没 有考 虑到的 应用 程序 错误 在示例 SQL-DMO 应用 程序 中提 供的 所有 例程 都使 用通 用的 SQLDMOError 例程 如 下所示 Public Function SQLDMOError () Dim errString As String If Err.Number <> 0 Then errString$ = Err.Source & " Error " & Err.Number & ": " & Err.Description MsgBox errString$ End If End Sub SQLDMOError 函数是一个相对简单的函数 它向用户显示一个消息框 提供了有关 所碰到 的错 误情 况的 详细信 息 使用 Visual Basic Err 对象 可以 显示 SQL-DMO 错误 这 种 Err.Source 属性 包含 了 引起该 错误 的 SQL-DMO 组 件名称 从 Err.Number 属 性 中减去 常数 vbObjectError 得到 SQL-DMO 的错误 号 Err.Description 属 性包含 该错 误的 文本 描述 7.5 小 结 正如在 代码 示例 中看 到的那 样 DMO 把 SQL Server 的管 理功 能展 示给了 Visual Basic 并且其 OLE 方法 使其 易于 在 Visual Basic 和其 他的 OLE 兼 容的应 用程 序中 使用第8 章 在C 中使 用嵌 入SQL 访问SQL Server 数据库 嵌入式 SQL Embedded SQL 是 SQL Server 提供 的用 于开发 客户 端应 用程 序的专 用 开发工 具 目前 只有 C 语言 版本 Embedded SQL for C 使用 它能 够在 C 语言程 序 中 嵌 入 Transact-SQL 语句进行数 据 库操 作 与 ODBC 和 DB Library 相比 嵌入 式 SQL 是通过 在 C 语言 程序 中自 接使 用 Transact-SQL 的语句而 不是使 用 函数 调用 来进 行数 据库 应用 程序 开发 因而它是一种简单高效的程序开发工具 它 使 用预编 译技 术将 C 源 程序中的 SQL 语句转 换为 对运 行库 的函数 调 用 运行 库用 调用 DB-Library 来访问 SQL Server 数据库 由 于在 C 源程 序中 直接 包含 了 SQL 语句 所以 嵌入 式 SQL 只能用于 开发 SQL Server 专用 的数据 库应 用程 序 适 用 于数据 库结 构明 确并 已变 化 不大的 应用 环境 8.l 嵌入式 SQL 应 用 程序 开发环 境 使用 嵌入 式 SQL 能 够开发多种环境下的 SQL Server 数 据库 应 用程 序 如 Windows 2000 Windows NT 和 Windows 95/98 下的 32 位应 用程 序 以及 Windows 3.x 和 MS-DOS 下的 16 位应 用程 序等 开发 不同 环境 下的 嵌入 式 SQL 应用程 序所 需要的 系统 环境 和编 译 器如表 8-l 所示 表8-1 嵌入式 SQL 开发环境 应用开发环境 操作系统 C 编译器 Windows NT 3.51 Microsoft Visual C++ 2.0 Windows NT 及其以上版本 及以上版本或其它兼容产品 Windows 95 Windows 95 Microsoft Visual C++ 2.0 及以上版本或其它兼容产品 Windows Windows 3.1 或 Windows for Microsoft Visual C++ 1.52 及以上版本或其它兼容产 品 Workgroups 3.11 MS-DOS MS-DOS 6.22 及其以上版本 Microsoft Visual C++ 1.52 及以上版本或其它兼容产 品 嵌入式 SQL 应用程序 在使用 常 规 C 编译器编 译 链 接之前 需要 用嵌 入式 SQL 预编 译器进 行预 处理 预编 译 器将源 程序 中的 嵌入 式 SQL 语句 转换 成能 被常 规 C 编译器 识 别 的 函数调 用 然后 C 编译 器才 能将 转换 后的 源程 序编 译成 可执 行文 件 使用嵌 入式 SQL 开发应用程 序 时 除需 要以 上系 统环境 和 预编 译器 之外 还需 要 C 语 言 头 文件 和函 数 库 以及 预 编译 和 运 行 时 使用的动态 链 接库 等 这些 文件 及 其 作 用如 表 8- 2 所示 表 8-2 中说 明的 函数 库和动 态 链接 库均是 用于 开发 32 位 嵌入式 SQL 应 用程序所 需要 的支持 文件 SQL Server 2000 没有 提供 开发 16 位应 用程序 所需 的支 持库 文件 如果需 要 开发运 行于 Windows 3.x 和 MS-DOS 下的 嵌入 式 SQL 应用程序 可从 微软 公司 经销 商处 得到 16 位代 码的 支持 库 文件 表 8-2 嵌入式 SQL 编程所需支持文件 文件名 作用 NSQLPREP.EXE Windows NT 和 Windows95 下的预编译器 SQLAIW32.DLL Windows NT 和 Windows95 下的预编译器支持动态链接库 SQLAKW32.DLL Windows NT 和 Windows95 下的嵌入式 SQL 应用程序运行时支持的动态链接库 SQLCA.H 定义SQLCA 数据结构 SQLDA.H 定义SQLDA 数据结构 CAW32.LIB Windows NT 和 Windows95 下的 SQLCA 输入库文件 SQLAKW32.LIB Windows NT 和 Windows95 下运行时支持输入库文件 Microsoft 公司提 供了 以下几 种 形式 的函 数库 以支 持 不同环 境下 的嵌 入式 SQL 程序的 运行 多线程 动态 链接 库 NTWDBLIB.DLL 支持 Windows NT 和 Windows 95 编程 动态链 接库 MSDBLIB3 DLL 支持 Windows 外 境下编 程 在具备 了以 上环 境和 支持文 件 后 就能 够使 用嵌 入式 SQL 开发 SQL Server 2000 数据 库应用 程序 了 8.2 嵌入式 SQL 数据 类型 嵌入式 SQL 是专用的 SQL Server 开发工具 所以 它 提供了 C 语 言数据类型 与 SQL Server 数据类 型之 间的 对应 关系 但嵌 入式 SQL 不支持 Unicode 数 据类型 即 SQL Server 中的 nvarchar nchar 和 ntext 等数 据类 型 由于 SQL Server 中的 datetime smalldatetime money smallmoney decimal 和 numeric 数据类型 在 C 语 言中没有 相应 的数 据 类型 所以 嵌入式 SQL 提供了 某些 C 数据 类型 到这 些数 据类 刑 的转换 C 语言 数 据 类型与 SQL Server 数据类 型间 的对 应关 系如表 8-3 所示 表 8-3 C 语言数据类型与SQL Server 数据类型间的对应关系如 C 数据类型 SQL Server 数据类型 可转换成 datetime 可转换成 money 或 可转换成 或smalldatetime smallmoney decimal 或 numeric Short Smallint 否否否 否否否 Int Smallint Long Int 否否否 否否否 Float Real Double Float 否否否 Char Varchar[x] 是是是 是是是 Viod p Binary Char byte tinint 否否否 如果接 收输 出结 果的 C 语言 应用 程序 的变 量的 数据 类型 宽度 较短 时 结果 数据将 被 截 断 同样 如果 接收 输入数 据 的 SQL Server 列的宽度 较 短 输入 数据 也将 被截断 另外 由于在 SQL Server 存储过程 中禁 止使 用 text 数据 类型 同此 在 SQL 语句中 不能使 用 超过 255 个字 节的 C 字符 串 字符数 据交 型 C 语言 中的 char 数据 类型 可对 应 SQL Server 的 char varchar 或 text 数据类 型 从 C 到 SQL Server 数据 类型 的转 换过 程中 数据 被拷 贝或 截断 SQL Server 数据宽度 较短 从 SQL Server 到 C 数 据类型的 转换 过程 中 数据 被拷贝 或截 断 C 数据 宽度 较短 并且 后面 跟一 个 NULL 字符 日期 时间 型数 据 C 语言 没有 日期 时间 数据 类型 SQL server 的 date 或 time 列按指 默认 的日 期格 式被转 换 成 C 字符串 如 mm dd yyyy hh mm:ss [am|pm] 同样 可使 用 C 字符 串向 SQL Server 传递日期 数据 字符 串中 的日 期格 式必 须 能被 SQL Server 识别 8.3 嵌入式 SQL 语法 嵌入式 SQL 程序就是 Transact-SQL 语句和 C 语句的 组合 体 在 C 语言 程 序 中嵌入 SQL 语句要 符合 一定 的语 法规则 SQL Server 的嵌入式 SQL 语法结合 了 IBM 标准 ANSI 嵌 入式 SQL 语法 和 Transact-SQL 语法规则 8.3.l 嵌入 式 SQL 语 句 及保留 字 在 C 源程 序中 任何 可以 放置 函数 的地 方均 能够 包含 嵌入 式 SQL 语句 也就 是说嵌 入 式 SQL 语句相当于 C 中的一 条语句 每一个 嵌 入 式 SQL 语 句都以 前导 关键 字 EXEC SQL 开头 以分 号 结束 将嵌 入式 SQL 语句与 C 语 句分开 一条 嵌入 式 SQL 语 句 的最大 长度是 8191 个字 符 16 位应 用程 序 和 19999 个字 符 32 位 应用程序 当嵌 入式 SQL 语句中的字符串一行 写 不下时 可 用反斜线 连接多 行字 符串 SQL 字 符 串需 用单 引 号括起 来 如 EXEC SQL INSERT INTO TEXT132 VALUES (''TEST 192 IS THE TEST FOR THE R\ ULE OF THE CONTINUATION OF LINES FROM ONE LINE TO THE NEXT LINE.''); 也可以 将嵌 入式 SQL 语句和 C语句 写在 同一 行上 如 EXEC SQL COMMIT TRAN; printf("\n"); 嵌入式 SQL 的关键字 和语句 是大小 写不 敏感的 例如 下面几 个嵌 入式语句 具有相同 的功能 EXEC SQL CONNECT TO exec sql connect to 然而嵌 入式 SQL 语句 所使用 的名字 比 如游标 预处理 语句和 连接 等名字是 大小写敏 感的 在 名字的定义和 使用中必须保 持一致的大小写 例如 下面语句定 义了两 个不同 的 游标 DECLARE CUR_NAME CURSOR DECLARE cur_name CURSOR Transact-SQL 的关键字 null 在嵌 入式 SQL 程序中不 能大 写 否则 会与 C 语言 关 键字NULL冲突 在 32 位应 用程 序中 嵌入 式 SQL 的关键 字 delete 和 Transact-SQL 的关键字 in 也 不能大 写 否则 与 32 位 Windows 定 义的常 量冲 突 下面 是嵌 入式 SQL 的保留字 这些保留字和 Transact-SQL 保留字均不能用作 C 语 言程序 中的 变量 名或 函数名 表 8-4 嵌入式 SQL 的保留字 APPLICATION FETCH OPTCCVAL CALL FETCHBUFFER OPTION CLOSE FOUND QUERYTIME CONCURRENCY GET READONLY CONNECT HOST SCROLLOPTION CONNECTION IMMEDIATE SECTION CUR_BROWSE INCLUDE SQLCA CUR_STANDARD INDICATOR SQLDA CURRENT KEYSET SQLERROR CURSOR LOCKCC SQLWARNING CURSORTYPE LOGINTIME USER DESCRIBE MIXED USING DESCRIPTOR NOT WHENEVER DISCONNECT OF WORK DYNAMIC OPEN FORWARD OPTCC 8.3.2 静态 SQL 语句 和动 态 SQL 语句 嵌入式 SQL 支持静 态 SQL 语句和 动态 SQL 语句 静态 SQL 语句在 程序 设计时 各 个 SQL 部分均 已确 定 而动 态 语句在 应用 程序 执行 时才被 建 立和 运行 静态 SQL 语句是嵌入到源程序中的一条完整的 Transact-SQL 语句 一个完整的 Transact-SQL 事务 包 括变量定 义 流控制语 句和 存储 过 程 调用 可被 放入 一条 静态 SQL 语句中 静态 SQL 语句可包 含 C 语言变量来 输入 和 输出数 据 在应 用程 序中 定 义的变 量可 被 C 和嵌 入式 SQL 访问 当一 条静 态语 句使 用 C 语 言变量输 入数 据时 在静 态语句 运行 SQL SQL 前变量 的值 被插 入到 语句中 同样 在 语句执 行 完后输 出值 被填 入输出 变 量中 如果 SQL 语句 返回的结 果集中数据行 多于 一行时 则只有第一行 的数 据被读取 到变 量之 中 而其 余数 据行 的数 据 被抛弃 对于 多行 数据 的结果 集 只能 使用 游标 来处 理 例如 下面 语句 即为一 条 静态 SQL 语句 EXEC SQL SELECT au_fname INTO :szFirstName FROM authors WHERE au_lname = :szLastName; 在编译 阶段 静态 SQL 语句可 被编 译成 访问计 划 中的存 储 过程 或以 动态 SQL 语句 的方式 执行 访问 计划 access plan 是存 储过 程的 集合 其中 的每 一个 存储 过程 对应 一条 静态 SQL 语句 使用 嵌入式 SQL 的预 编译 器 可以 连接 到 SQL Server 数据库 并建 立访 问 计划 此 应用程序被重 新编译时 程 序模块对应的旧 的存储 过程被删除 并被 新 的存储 过 程所取 代 如果 在编 译阶段 SQL Server 数据 库是 无效 的 则可 用嵌 入式 SQL 预 编 译器生成绑定 文件 绑定 文件 是 Transact-SQL 批命令文件 其中 包含 了建 立访 问计 划 中存储 过程 所使用 的 Transact-SQL 命令 这时 应在 运行 嵌入 式 SQL 应 用程序之 前连 接 SQL Server 并使用 osql 等工 具执 行绑 定文 件 在 SQL Server 数据库中 生成 应用 程序 的访 问计 划 当 静态 SQL 语句中只包含单个事务管理命令 如 BEGIN TRANSACTION COMMIT TRANSACTION ROLLBACK TRANSACTION 或 SAVE TRANSACTION 时 此静态 SQL 语句不 被编 译成 访问 计划 而是 在应 用程 序执 行时 按 动态 SQL 语 句的方式 执行 动态 SQL 语句不 是一 条完整 的 Transact-SQL 语句 其中 某些 部分 需要在 应 用程序 运 行 时才能 提供 动态 SQL 语句可 被存 储在 程序 变量 中 在程序 运行 时修 改 动态 地生 成需 要 的 SQL 语句 动态 SQL 语句中 使用 问号 作为 参数 标志 用来 说明 需要 应用 程序 输 入参数 例如 DELETE AUTHORS WHERE AU_FNAME AND AU_LNANE 在程序中 可使 用 PREPARE EXECUTE 和 EXECUTE IMMEDIATE 嵌入 式 SQL 语句来 处理 一条 动态 SQL 语句 通常 首先 用 PREPARE 语 句准备 动态 SQL 语句 然后再 用 EXECUTE 语句 执行 之 8.3.3 宿主 变量 在嵌入 式 SQL 中 用宿 主 变量来 处理 数据 的输 入 输出 宿主 变量 是标 准的 C 应用程 序变量 当 数据 库对 象 的个 数 和数 据类 型已知时 可 以使用 宿主 变量 比如 在静态 SQL 语 句中使 用宿 主变量来 输入和 输出数 据 在动态 SQL 语 句中结 合宿 主变 量和 参数 标 志 来进计 动态 的数 据处 理 宿主 变量 在使 用之 前必 须由嵌入 式 SQL 定义部 分定 义 宿主 变量 定义 部分 以 BEGIN DECLARE SECTION 语 句开始 以 END DECLARE SECTION 语 句结来 定义 部分 不能嵌 套 如 EXEC SQL BEGIN DECLARE SECTION int nID unsigned short usNumber char szName 3O EXEC SQL END DECLARE SECTION 宿主变 量名 称的 长度 不能超 过 30 个字符 在程 序中允 许 定义 C 变 量的地力均 可 以定 义 宿主变 量 已 定义 的宿 主 变量的 使用 范围 遵循 C 语言 的变量 使用 规则 在嵌入 式 SQL 语句中 使用宿 主变量 时 变量名前 必须加 冒号 用 来 区分 同名的宿 主变量 和数 据库 表 列名 下面 的语 句说 明宿 主变 量 的使用 方法 EXEC SQL SELECT price INTO :price:price nullflag FROM titles WHERE au_id = "mc3026" 因为 C 语言 不支 持空值 null 嵌入式 SQL 提 供宿主 指示 变量 来处 理数 据 库的空 值 所以 在嵌 入式 SQL 应 用 程序中 可使 用宿 主变 量和 宿 主指示 变量 来表 示一 个 SQL 值 当宿 主变量 为 NULL 时 它的 指示 变量 为-l 当 宿上 变量不 为 NULL 时 它的指 示变 量 的值为 宿 主变量 数据 的最大长 度 嵌入 式 SQL 语句 中将 指示 变 量直接 放在 对 应的宿 主变 量 后面 两 者间以 冒号 分隔 例如 下 面语句 中变 量 Address 利 Address_null 分别为 宿中变 量 和 宿上指示 变量 EXEC SQL UPDATE authors SET address=:Address:Address_null 也可在 指示 变量 的前 面加上 关 键字 INDICATOR 说明 其 后是一 个指 示变 量 例如 EXEC SQL UPDATE authors SET address=:Address INDICATOR:Address_null 在上面 的例 子中 当Address_null 的值 为-1时 嵌入 式 SQL 将 语句转换 为 EXEC SQL UPDATE Authors SET address=null 在搜 索条 件中 不能 使 用指示 变量 可 以使 用下 面 方法来 搜索 空值 If Address.null=-l EXEC SQL DELETE FROM authors WHERE address is null else EXEC SQL DELETE FROM authors WHERE address= address 在 使用 宿户 变 量 时要 注意它与 其对 应的 SQL Server 数 据类 型是否 匹配 否 则 可能会 引 起数据 丢失 8.3.4 嵌入 式 SQL 的 主 要数据 结构 嵌入式 SQL 应用程序 中 主要使 用两 个数 据结 构 SQLCA 和 SQLDA SQL Serve 使用 SQLCA SQL communications area 即 SQL 通信区 结 构 为嵌入 式 SQL 应用 程序捕获 和报告运 行 时 错误 应用程 序可查看 SQLCA 结构的错误域和 状态指示 符来 确定一条嵌入式 SQL 语句运行是否成功 SQLCA 结构中最重要和最广泛使用的城是 SQLCODE 变量 每当 SQL Server 运行一 条嵌 入式 SQL 语句 时 它将 设置 SQLCODE 变 量来指 示最 后一 条 SQL 语句是 否成 功执 行 SQLCODE 的值 为 0 说 明嵌入式 SQL 语句已 经成功 执行 否则 表明 在 执行嵌 入式 SQL 语 句 时产 生 了警告 或错 误信 息 嵌入 式 SQL 也 支持 ANSI 标准 的 SQLSTATE 代码 ODBC 错 误代码 SQLCA 结 构中的 SQLSTATE 域 表明所 产生 的警 告或 错误消 息 对应 的 SQLSTATE 代码 SQLSTATE 指 示的错 误有两 种 由 DB-Library 或嵌 入式 SQL 产生的 错 误 以及 由 SQL Server 产生的错误 SQLSTATE 代码与 SQLCODE 值相 对应 当编译时用来传递数据的宿主变量的个数或数据类型不确定时 嵌入式 SQL 使用 SQLDA SQL descriptor area 即 SQL 描述 区 结 构 来代替 宿主 变量 SQLDA 结 构 包含了 每个输入 参数或输出列 的描述信息 即名 称 数据 类 型 长 度以及指向输 入输出 缓冲区 的 指针等 SQLDA 结 构可与 DESCRIBE 或 PREPARE INTO 语 句联合使 用来 从一 条预 处理 检索语 句中 接收 数据 在静 态 SQL 语句 中不 能使 用 SQLDA 结构 而 只 能在动 态 SQL 语 句或游 标中 使用 SQLDA 结构 下 面分别 介绍 SQLCA 和 SQLDA 的结 构 8.3.5 SQLCA 嵌入式 SQL 使用 SQLCA 数据 结构 来捕 获并 报告 SQL 语句运 行时 所产 生的 错误消 息 对 SQL 命 令执行状态 的 检测 是通过 检 查 SQLCA 数 据结构中 的 各 个域来完 成 的 SQLCA 数据结构中的各个域反映了 SQL 命令的状态以及错误代码 错误信息等信息 它是 在 SQLCA.H 头文 件中 定义 的 由嵌 入式 SQL 预 编译 器 自动包 含到 C 源 文件中 SQLCA 结 构的定 义如 下 // SQL Communication Area – SQLCA 数据结 构 typedef struct sqlca { unsigned char sqlcaid[EYECATCH_LEN]; // SQLCA 标志 为 ''SQLCA '' long sqlcabc; // SQLCA 字节 数大 小为 136 long sqlcode; // SQL 返回码 short sqlerrml; // SQLERRMC 长度 unsigned char sqlerrmc[SQLERRMC_SIZ]; // 错误信息 unsigned char sqlerrp[8]; // 调试信息 long sqlerrd[6]; // 调试信息 unsigned char sqlwarn[8]; // 警告标志 unsigned char sqlext[3]; // 保留 unsigned char sqlstate[5]; // 新成员 } SQLCA; SQLCA 数据 结构 的各 个域的 含 义说 明如 下 Sqlcaid 和 sqlcabc 分别为 SQLCA 结构标志和结构字节长度 其值分别为 SQLCA 和 136 Sqlcode SQL 命令的返 回代 码 表明 了 SQL 命令的执 行 状态 SQL Server 每执 行一 条 SQL 语句 都要 设置 sqlca->sqlcode 的值 来 指 示 SQL 语句执行状态 sqlca->sqlcode 的值 为 0 表明 命令 执行 正确 值为 1 表明 SQL 语 句已执行 但有 异常情 况发 生 值为 100 表明在 游标 中执 行了 一条 FETCH 语句 但已 没有 记录 行可取 值为 负表 明 SQL 语句没有 执行 这可 能是 由 于应用 程序 数据 库 操作 系统或 网络 发生 错误 所致 为了简化对 sqlca->sqlcode 的引用 嵌入式 SQL 定义了 SQLCODE 变量 其值与 sqlca->sqlcode 的值 是一 样的 嵌入 式 SQL 预 编译 器 自动在 包含 main 或 WinMain 函数的.sqc 源程 序中 插入 SQLCODE 变量 的定 义语 句 在其 他所 有的.sqc 源 程序中插 入如 下语句 来引 用 SQLCODE 变量 extern long SQLCODE; 如果所 有.sqc 源程 序中 都 没有包 含 main 或 WinMain 函数 则应 该杂 某一 C 源 程序模 块中 明确 定义 SQLCODE 变量 Sqlderrml 和 sqlerrmc 分 别 说 明嵌入式 SQL 错 误 信息 的 长度和错误信息文本 sqlderrmc 域只保存 错误 信息 文本 中的 前 70 个字符 超过部 分将 被截 断 嵌入 式 SQL 定义了 如下 宏来 简化应 用 程序 对 sqlca->sqlderrml 和 sqlca->sqlderrmc 的引用 #define SQLERRMC sqlca->sqlerrmc #define SQLERRML sqlca->sqlerrml sqlerrp 为嵌 入式 SQL 的保留域 用于 内部 调试 sqlerrd 为 6 个整数型 的状态代 码 Sqlca->sqlerrd[0] 为 SQL Server 的错误号 Sqlca->sqlerrd[ 1] 为 SQL Server 错误的 严重 级别 Sqlca->sqlerrd[2] 为错 误影 响的记 录数 其它 未说 明 的代码 为嵌 入式 SQL 所保 留 嵌入 式 SQL 定 义了几个 宏 来简化 对Sqlca->sqlerrd 各个 域的 引用 #define SQLERRD1 sqlca->sqlerrd[0] #define SQLERRD2 sqlca->sqlerrd[1] #define SQLERRD3 sqlca->sqlerrd[2] #define SQLERRD4 sqlca->sqlerrd[3] sqlwarn 为嵌 入式 SQL 警告 标志 sqlca->sqlwarn[0]为 所有警告 域的 摘要 指 示 sqlca->sqlwarn[ 1] 值为 W 表示 输出 的字 符串 信息被 截 断 sqlca->sqlwarn[3] 值 为 W 表示 数据 表的 列数 与宿 主变 量的 个数 不匹 配 其它 未说 明的 域为 嵌入 式 SQL 所保留 嵌入 式 SQL 定 义 了如下 几个 宏来 简化 对 Sqlca->sqlwarn 各 个域的引 用 #define SQLWARN0 sqlca->sqlwarn[0] #define SQLWARN1 sqlca->sqlwarn[1] #define SQLWARN2 sqlca->sqlwarn[2] #define SQLWARN3 sqlca->sqlwarn[3] #define SQLWARN4 sqlca->sqlwarn[4] #define SQLWARN5 sqlca->sqlwarn[5] #define SQLWARN6 sqlca->sqlwarn[6] #define SQLWARN7 sqlca->sqlwarn[7] sqlstate 为运行 时产 生的 ANSI 标准 SQLSTATE 错误 代码 在嵌入 式 SQL 应用程 序中 通过 检查 SQLCODE 变 量来确定 SQL 语 句的执 行状 态 如果发 生警 告或 错误 可检 查 SQLWARN SQLERRD1 来 确定发生 何种 错误 SQLERRMC 包含了错误 处 理 信息 SQLERRD3 指出错 误 影 响的记录 数 嵌入式 SQL 应 用 程序根据这 些信息 可以 对错 误做 出相应 的 处理 8.3.6 SQLDA SQLDA 数据 结构 是在 SQLDA.H 头文 件中 定义 的 由 嵌入式 SQL 预 编译器 自动 包括 到 C 源文 件中 SQLDA 结构定 义如 下 // SQL Descriptor Area – SQLDA 数据 结构 struct sqlda { unsigned char sqldaid[8]; // SQLDA 标志 为 ''SQLDA '' long sqldabc; // SQLDA 字节 数大 小 为 16+44SQLN short sqln; // SQLVAR 元素 个数 short sqld; // SQLVAR 被使 用的 元素个 数 struct sqlvar // SQLVAR 元素 的数 据结构 { short sqltype; // 变量类型 short sqllen; // 变量长度 最大 不能 超过 32K unsigned char FAR sqldata; // 指向变量数 据的 指针 short FAR sqlind; // 指向 null 变量 的指 针 struct sqlname // 变量名 { short length; // 名称长度 为 1 到 30 unsigned char data[30]; // 变量或 数据列 表 的名 称 } sqlname; } sqlvar[1]; }; 表 8-5 sqltype 数据类型代码表 Sqltype 代码 数据类型描述 对应的 SQL Server 数据 例子 类型 26 392/393 字节日期时间类型 其格 Datetime,smalldatetime char date1[27] = 式必须满足使用 DB-Library Mar 7 1988 7:12PM; 函数 dbconvert 转换字符串到 日期时间函数的要求 444/445 二进制类型 binary, varbinary, image, char binary1[4097]; timestamp 注 444/445 自动用作列 的输出类型 长度小于 255 的字符串类型 char, varchar, 或 text 452/453 char mychar[255]; 不能自动以 null 作为结束符 char, varchar, text 456/457 带长度前缀的长字符串结构 或 struct TEXTVAR 不能自动以 null 作为结束符 { short len; char data[4097]; } textvar; null char, varchar, text 462/463 带 结束符的字符串类型 或 char mychar1[41]; char mychar2; 8 字节浮点数 480/481 float, real, int, smallint, double mydouble1; tinyint, decimal, numeric, money, smallmoney 482/483 4 字节浮点数 float, real, int, smallint, float myfloat1; tinyint, decimal, numeric, money, smallmoney 496/497 4 字节整数 int, smallint, tinyint, bit long myint1; 500/501 2 字节整数 smallint, tinyint, bit short myshort1; SQLDA 结构 中各 个域 的含义 说 明如 下 Sqldaid SQLDA 结构 标志 符 该字 段不 能用 于 FETCH OPEN EXECUTE 语句 中 Sqldabc SQLDA 结构的字 节 长度 Sqln SQLDA 数据 结构 中分 配的 sqlvar 元素的 总个 数 每一 个 sqlvar 结构 描述了 一 个 数据 库列 表的 名字 数据 类 型 长度 数据 缓冲区等 信息 Sqln 值 等 于输入 参数或 输出 列的 个数 Sqld 应用程 序中 使用 的 sqlvar 元素 数 Sqlvar 可变的 sqlvar 结构数组 在使用 中可根据输入参数 的个数或结果 集中 数 据库列 表的 个数 来分 配数据 空 间 Sqltype 代表数据库列表或宿 主变量数据类型 的短整数值 并指出其是否允许 空 值 Sqltype 中使用 代码 值来 表示 SQL Server 相应的数 据 类型 表 8-5 说 明了各 个 代码值所代表 的数据类型 其中每一对代码值 中 奇 数值表 示该类型的宿 主变 量 必须有 一个 指示 变量 来存放 空 值 偶数 值表 示不 允许有 空 值 Sqllen 列长 度 Sqldata 在 FETCH OPEN EXECUTE 语句中使用 的 宿 主变 量 的 地址 是存放 输出 输入 数据 的缓 冲区 Sqlind 如果 数据 库列 表允 许空 值 为在 FETCH OPEN EXECUTE 语 句中使 用 的指示 变量 的地 址 否 则 不使用 该元 素 如果 数据 库 表列允 许空 值 当数 据为 null 时 sqlind 的值为-1 如果 数据 值不 为 null sqlind 的值为 0 Sqlname 数据 库表 列的 名字 数据 结构 Length 列名的 字节 长度 Name 列名 在执行 FETCH 语句 之前 应用程 序必 须先 设置 好 接 收相应 数据 库表 列的 数据 的 宿主 变量 的地 址 即 sqldata 中必须有 有 效 的宿 主变量地 址 如果 数据库表 列允许空 值 sqlind 中也必 须有 有效 的指 示变量 地 址 使用 PREPARE INTO 或 DESCRIBE 语句 可以 从 SQL Server 获得数据库表列的名字 数据类型和长度 并自动填充 SQLDA 数据结构中的 sqlname sqltype sqllen 等域 在执 行 FETCH 语句 之前 应 用程 序 还 可以修改 sqltype 和 sqllen 值 在使用 DESCRIBE 或 PREPARE INTO 语句之 前 必 须设置 sqln 其值应 大于 等 于预 期输出 列的 个数 如果 不 知道输 出列 的个 数 可以 将 sqln 设置 为 0 然后执行 DESCRIBE 语句 输 出结 果集 中的 数 据库表 列的 个数 将会 填充 到 sqld 中 但数 据库 表列 的 详细信 息不 会被填 充到 SQLDA 数 据 结构中 如果使 用 SQLDA 数据 结 构输入 数据 应用 程序 必须 设置 SQLDA 数 据结构中的 全 部 域的值 包括 sqln sqld sqldabc sqllen sqldata 等域 如果 sqltype 的值为奇数 类 型代码 则必须 提供 指示 变量 8.4 嵌入式 SQL 数 据 库访 问过程 与 DB-Library 应用程序类 似 嵌入 式 SQL 程序访问 SQL Server 数据 库系 统的 过程 可 以分为 以下 几步 1) 连接 SQL Server 2) 向 SQL Server 发送用 户的操 作命 令 3) 处理 SQL Server 返回的命令 结果4) 断开 与 SQL Server 的连接 5) 处现 以上 步骤 执行 过程中 产 生的 错误 下面简 单介 绍前 四步 的实现 方 法 嵌入 式 SQL 的 错 误 处理将 在下 节介 绍 8.4.1 连接 SQL Server 嵌入 式 SQL 应程 序使用 CCONNEC ONNECT TO T TO 语句 建立 与 SQL Server 数 据库的连 接 CCONNEC ONNECT TO T TO CONNECT TO CONNECT TO CONNECT TO CONNECT TO 作句 的语 法格式 为 CONNECT TO {[server_name.]database_name} [AS connection_name] USER [login[.password] | $integrated] 其中 serve_name 为要 登录的 SQL Server 服务 器名 称 database_name 为要连 接的数 据 库名 connection_name 为连 接名 称 longin 和 password 说 明登录 SQL Server 所使用 的用 户名称 和口 令 $integrated 说明 在登 录 SQL Server 时使 用 Windows NT 集 成安全 认证模 式 如果应用程序 只使 用以个连接 就不 需要 提供 连 接名称 当 使用多个连接时 必须 为 每一个连 接指定连接名 称 已命名的 连接在一个进程 中是共 享的 可被程 序中的 各个模 块 和动态 链接 库使 用 在 CONNECT TO 语 句 中可 以使用 宿主 变量 下面 例子 说明 CONNECT TO 语句的 用法 这三条 语句 具有 相同 的功 能 EXEC SQL CONNECT TO :svr USER :usr; 或 EXEC SQL CONNECT TO "gizmo.pubs" USER "sa"; 或 EXEC SQL CONNECT TO gizmo.pubs USER sa; 如果 应用 程序 建立 多 个连接 可以 使用 SET CONNECTION 语 句在不同 连接 之 间转 换 SET CONNECTION 语句 的语 法格 式为 SET CONNECTI0N connection name 其中 connection name 为己建 立的 连 接 名称 它 可为连 接名 称字 符串 或 包 含 连接名 称 字符串 的宿 主变 量 使用 SET CONNECTI0N 语句设 置 了连接 后 其后 的所 有 SQL 语 句 均使用 此连接 直到 下一 个 SET CONNECTI0N 语句改 变了 连 接为止 如果 要在 不同 的编译 模块 中使 用同一 连接 则必 须使 用 命名连 接 下面 的例 了说 明 多个连 接的 使用 EXEC SQL CONNECT TO server1.pubs AS svr1 USER sa; EXEC SQL CONNECT TO server2.pubs AS svr2 USER sa; EXEC SQL SET CONNECTION svr1; EXEC SQL SELECT name FROM sysobjects INTO :name; EXEC SQL SET CONNECTION svr2; EXEC SQL SELECT name FROM sysobjects INTO :name; 第一 个 SELECT 语句 在服 务器 serverl 的 pubs 数 据库上执 行 第二 个 SELECT 语句则 在服务 器server2 的pubs 数据 库上 执行 GET CONNECTION 语句 为嵌 入式 SQL 程序提 供了 检 索指定 连接 的 DBPROCESS 结 构 指针的 方法 其语 法格 式为 GET CONNECTION connection_name INTO hvar 其中 connection_name 为一个已打开的连接名称 hvar 为宿主变量 它存储 GET CONNECTION 语句 返回 的 DB-Library 连接 指针 其 数 据类型 为 DBPROCESS 8.4.2 命令 处理 建立连 接后 嵌入 式 SQL 程序可 向 SQL Server 发送 SQL 命 令进行数 据库 操作 发送 SQL 命 令的方 法有 以下 几种 立即执 行方 式 即在 嵌入式 SQL 语句 中直 接使 用静 态 SQL 语句 其中 可包 括单 条 Transact-SQL 语句 Transact-SQL 批命 令 数据 库 事务或 存储 过程 准备执 行 方 式 使用 PREPARE EXECUTE EXECUTE IMMEDIATE 语句 执行 动态 SQL 语句 有关命 令执 行方 式将 在 8.5 节详 细介 绍 8.4.3 结果 处理 嵌入式 SQL 命令中的 SELECT 语句 或包 含 SELECT 语 句的存储过 程产 生的 结果 集合 分为单 结果 行和 多结 果行两 类 相应 的处 理方 法也 不 同 单结果 行 使用 SELECT INTO 语句将结 果行 中的 数 据拷 贝到 宿主 变量 中进 行处 理 多结果 行 使用 游标 处理 FETCH 语句 可以 一次 读 取一行 数据 单结果 行 单结果 行的 处理 很简 单 使用 SELECT INTO 命令来 处理 SELECT INTO 命 令 的格式 如下 SELECT [select_list] INTO {:hvar [,...]} select_options 其中 select_list 为查询 语句 的选 择列 表 选择 列表 中 可包括 数据 表的 列名 表达 式等 hvar 为存放检索结果数据的宿主变量 宿主变量的个数 数据 类型 长度 顺序 等应 与 select_list 选择列表 中 列 或表 达式 的 个 数 数 据类型 长度 和 顺 序相同 如果二者 不匹 配 或是数 据被 截断 时 SQLWARN3 的值将 被设 置为 W Select_options 为 Transact-SQL SELECT 语句的 检索 选 项 FROM 子句 或 WHERE 子 句 嵌入 式 SQL 不支持 Transact-SQL 中的 GROUP BY HAVING COMPUTE 等子句 下面的 例子 说明 SELECT INTO 语句的用 法 EXEC SQL SELECT au_lname INTO :name FROM authors WHERE stor_id=:id; 如果 SELECT INTO 返 回 的结果 集合 中的 数据 行数 大 于 1 时 则在宿主 变量 中存 放结 果集合 中的 第一 行数 据 并将 SQLCODE 的值置 为 1 表明有意 外发 生 在 SELECT INTO 中一 般 使用宿 主变 量来 存储 结果 集 合中 的各 列数 据 为了 简单 也 可 以使用 C 结构 来存 放结 果集 合数 据 结构 中各 个元 素的 数据 类型 和长 度应 与检索 结 果集 合 中各列 相匹 配 如 EXEC SQL BEGIN DECLARE SECTION; int id; char name[30]; EXEC SQL END DECLARE SECTION; 游标操 作 如果嵌 入式 SQL 向 SQL Server 发送检 索命 令 其 返 回结果 是包 含多 行数 据的 结 果集 合 要处 理结 果数 据 则 必须定 义和 使用 游标 然后使用 FETCH 语 句逐行处 理 结果集 合中的数 据行 嵌入 式 SQL 游标提供 了一 次处 理多 行数据 的 机制 但是 使用 游标 不能 处理 Transact-SQL 批命令或 存储过 程 返回 的多 个结 果集 合 当一 个语句返 回多个结果集 合时 只有第 一个结果集合能 够被识 别和处理 其 后面 的 结果集 会 将被忽 略 此外 嵌入 式 SQL 游标也不 能处 理 COMPUTE 子 句产生的 统计 数 据行 结果 集合中 的 COMPUTE 数 据 行也将 被忽 略 游标的 定义 嵌入式 SQL 游标包括 标准游 标 和浏 览游 标两 种类 型 标准游 标是 在程 序中 与别 的 SQL 语句共 共享 同一 个到SQL Serve 连接的游 标 使用 SET CURSORTYPE CUR_STANDARD 语句或 在游 标定 义语 句 DECLARE CURSOR 中使 用 FOR UPDATE 选 项可以 定义 一个 标准 游标 浏览游标则是每个游标均需要一个独立的到 SQL Server 的连接 使用 SET CURSORTYPE CUR_BROWSE 语句可 以定 义一 个浏 览游 标 标准游 标和 浏览 游标 的定义 和 使用 方法 是一 致的 标 准游标 允许 多个 游标 共享 一 个 SQL Server 连接 而每 个浏 览游 标则 需要 一个 独立 的 SQL Server 连接 对于 大多 数 应用程 序 建议使 用标 准游 标 而 且 对于嵌 入式 SQL 应用程 序 标准游 标是 默认 的 因为 一个 共享 的 SQL Server 连接可 以避 免游 标之间 可能 存在 的锁 定冲突 标准 游标 使用 了 DB-Library 的游 标函数 因此 使用 标准 游 标的嵌 入式 SQL 应用程 序可以 充 分利 用 DB-Library 游标的增强 功能 而且标准游标 同样支持 DB-Library 游 标 的许多 选项 如并发控 制 性 能 特点等 由于每 一个 浏览 游标 使用不 同 的 SQL Server 连接 SQL Server 把 每一个 浏览 游 标看作 是一 个独立 的用 户 这 可能 在 同一应 用程 序中 的不 同浏 览 游标之 间引 起锁 定冲 突 浏览 游标 在使 用前 必 须先定 义 嵌入 式 SQL 使用 DECLARE CURSOR 语 句定义 游标 其 语法格 式如 下 DECLARE cursor_name [INSENSITIVE] [SCROLL] CURSOR FOR {select_stmt | prepared_stmt_name} [FOR { READ ONLY | UPDATE [ OF column_list ] } ] 其中 cursor_name 为游 标的名 字 其长 度不 能超 过 30 个字 符 游标 名字 的第 一 个字符 必须是 字 母 在游标 名 字中 不能使 用 连 字符 - INSENSITIVE 选 项 指 定 游 标为不敏 感 游 标 即为 只读 游标 对 于 只读游 标 在游 标打 开时 SQL Server 为其 建 立一个 结果 集快 照 游标 的 所 有操作均在此 快照上 执 行 SCROLL 选项 设 置 游 标 的 滚动属性 它说 明游 标指针 可以定 位到 第一 个记 录 最后一 个记 录或 下一 个记 录 select_stmt 为 SELECT 语句 用 于定义 静态 游标 结果 集合 浏览 游标 还可 以使 用包 含 SELECT 语 句的存 储过 程 进行定 义 prepared_stmt_name 为 PREPARE 语句 准备 的 SELECT 语 句名称 它用 于定 义动 态 游标 FOR READ ONLY 选项定 义一个 标 准的 DB-Library 只读游标 FOR UPDATE 选 项说明通 过游 标 可以进 行修 改操 作 该 选 项是默 认设 置 下面的 例子 说明 如何 定义标 准 游标 和浏 览游 标 //定义 标准 游标c1 EXEC SQL DECLARE c 1 CURSOR FOR SELECT au_fname, au_lname FROM authors ; //定义 浏览 游标c2EXEC SQL DECLARE c2 CURSOR FOR SELECT au_fname, au_lname FROM authors FOR BROWSE; 游标的 打开 和关 闭 游标被 定义 以后 可 以使用 OPEN 语句打 开该 游标 进行检 索 删除 修改 等操作 操 作完成 后使 用 CLOSE 语 句 关闭游 标 释放 游标 所占 用 的资源 游标 打开 语句 的格 式 如下 OPEN cursor_name [USING DESCRIPTOR :sqlda | USING :hvar [,...]] 其中 cursor_name 为游标的名字 sqlda 为应用程序准备好的用于输入检索参数的 SQLDA 数据结构 它包含了每一 个输入参数的地址 数据类型和长度 hvar 为对应于游 标 SELECT 语句 中的 参数 标志 符的 宿主 变量 OPEN 语句 执行 游标 定义 语句 中的 SELECT 语句 产生游 标结 果集 合 对于静 态游 标 其 SELECT 语句可 以包 含宿 主变 量 但不 能包 含参 数标 志符 宿主变 量只能用 于常量可以使 用的地方 其 它像表名 列名 数据 库对象或关键 字等地 方不能 使 用宿主 变量 当 OPEN 语句 执行 时 宿主 变量 的值 被替 换到 SELECT 语句 中 静态游 标的 OPEN 语句 不能 使用 USING hvar 和 USING DESCRIPTOR sqlda 选项 对于动态游标 其 SELECT 语句可以包含参数标 志符 而不能包含宿主变量 如果 SELECT 语句中使用了参数标志符 OPEN 语句中必须使用 USING hvar 或 USING DESCRIPTOR Sqlda 选项为 参数 提供 数据 宿主 变 量的个 数或 Sqlda 数 据结构 中的 变员 个 数必须 与 SELECT 语句 中的 参数 标志 符的 个数 相对 应 浏览游 标使 用独 立的 SQL Server 连接 当打 开一 个浏 览游 标时 如果 试图建 立新 的 SQL Server 连接失败 或打开一个标准游标时没有有效的 SQL Server 连接存在 将 产生一 个 SQLCODE 值为-19521 的 运行时 错误 游标关 闭语 句的 格式 如下 CLOSE cursor_name CLOSE 语句放弃游 标结果 集合中尚未 处理的数据行 并释放游 标打开时所保 持的数 据库锁 应用 程序 结束 时 将自动 关闭 所有 打开 的游 标 下面的 例子 说明 如何 打开 关闭 游标 EXEC SQL DECLARE c1 CURSOR FOR SELECT au_fname,au_lname FROM authors FOR BROWSE; EXEC SQL OPEN c1; while (SQLCODE == 0) { // 如果数据读取成功 SQLCODE 为0 EXEC SQL FETCH c1 INTO :fname,:lname; } EXEC SQL CLOSE c1; 检索游 标数 据 游标被 定义 和 打 开后 可以使 用 FETCH 语 句一次 读取一 行结果 记录 记录的 值存放在 宿主变 量或 SQLDA 数据 结构 指定 的地 址中 FETCH 语 句的格 式如 下FETCH [ [ NEXT | PRIOR | FIRST | LAST ] FROM ] cursor_name [USING DESCRIPTOR :sqlda_struct | INTO :hvar [,...]] 其中 NEXT 指定 读取 游标 结果 集中 的下 一条 记录 如果 第一 次执 行 FETCH 语句 则 读取结 果 集 中的第一 条 记录 PRI0R 为 读取游 标 结 果集中的 前 一 条记录 FIRST 将游标指 针移到结果集 的第 一条记 录处 并返 回第 一条记录值 LAST 将 游标指针移到结果集的 最 后一条 记录 处 并 返回 最 后一条 记录 值 cursor_name 为游 标名称 sqlda_struct 为 存 放游标 输出数 据的 SQLDA 数据结 构 hvar 为存 放检 索数 据的 宿主 变量 使用 FETCH 语 句前 游标 必 须已定 义和 打开 如果没有 指 定 NEXT PRIOR FIRST LAST 选项 FETCH 语句 自动 采用 NEXT 方法 读取 游标 结果 集中 的下 一条 记录 宿主变 量的 数据 类型 和长度 必 须与 游标 SELECT 语句 中的 选择 列表 相匹 配 如 果游标 结果 集合 中的 列的 个数 与 宿主变 量的 个数 不匹 配 SQLWARN3 的 值被设 置为 W 如果发 生错误 其他 的数 据库 表 列不会 被处 理 当 SQLCODE 的值 为 100 时 指示游标 结果集 中没 有更多 的记 录可 被读 取 USING DESCRIPTOR sqlda_struct 选项在 静态 游标 和 动态游 标中 均可 使用 下面的 例子 说明FETCH 语 句的使 用 EXEC SQL DECLARE C1 CURSOR FOR SELECT au_fname, au_lname FROM authors FOR BROWSE; EXEC SQL OPEN C1; while (SQLCODE == 0) { EXEC SQL FETCH C1 INTO :fname, :lname; } 使用游 标修 改数 据 在游标中不仅 可以 读取和 处理数据 还可 以对结果集合中的 当前数据行进行删 除和修 改操作 与 FETCH 语 句一样 对 游 标的 删除 修改 操 作只能 一次 处理 一行 数据 因此把 游 标的删 除 修 改操 作称 为 定位删 除和 定位 修改 定位删 除语 句的 格式 如下 DELETE [FROM] {table_name | view_name} WHERE CURRENT OF cursor_name 嵌入式 SQL 的定位删 除与 Transact-SQL 的删除语句 DELETE 不 同之处在 于 定位 删 除只删 除游 标中 最近 由 FETCH 语句访 问的 记录 行 而 Transact-SQL 的删除语 句 DELETE 需要搜 索条 件子 句限 定删除 的 数据 行 不提 供 WHERE 子 句时删 除数 据库 表中 的 所有记 录 下面的 例子 说明 定位 删除操 作 的使 用 EXEC SQL DECLARE c1 CURSOR FOR SELECT au_fname, au_lname FROM authors FOR BROWSE; EXEC SQL OPEN c1; while (SQLCODE == 0) { EXEC SQL FETCH c1 INTO :fname, :lname; if (SQLCODE == 0) { printf("%12s %12s\n", fname, lname); printf("Delete? "); scanf("%c", &reply); if (reply == ''y'') { EXEC SQL DELETE FROM authors WHERE CURRENT OF c1; printf("delete sqlcode= %d\n", SQLCODE(ca)); } } } 定位修 改与 删除 类似 下 面不再 详细 讲述 8.4.4 关闭 连接 处理完数据库事务后 应用程序应关闭与 SQLServer 的连接 嵌入式 SQL 使用 DISCONNECT 语句 关闭 应 用程序 建立 的数 据库 连接 DISCONNECT 语 句的语 法格式 为 DISCONNECT [connection_name | ALL | CURRENT] 嵌入式 SQL 关闭一个 连接时 该连 接下 建立 的所 有游标 均 自动 被关 闭 为了 保证 在退 出时释 放程 序所 占用 的资源 嵌入 式 SQL 程 序 在退 出 主程序 之前 应调 用 DISCONNECT ALL 语句 8.4.5 错误 处理 嵌入式 SQL 提供 SQLCODE 变量 SQL 通信区 SQLCA 结 构中的一 个域 记录 SQL 语句的 执行 状态 每一 条 SQL 语句 执行 之后 应用 程 序可检 查 SQLCODE 变量的 值 如果 其值为 0 表明 SQL 语句执行 成功 否则 说明 语句 执 行时发 生错 误 表 8-6 列出了 SQLCODE 变量值 的意 义 表 8-6 SQLCODE 变量值的意义 条件 SQLCODE 变量取值 意义 SUCCESS 0 执行正确 没有错误发生 SELECT 语句或游标结果集中数据已取完或没有返回数据 NOT FOUND 100 SQLWARNING +1 发生嵌入式 SQL 警告或错误 如输出数据被截断 SQL Server message SQLERROR <0 发生 错误 8.5 SQL 命令执行方 式 嵌入 式 SQL 支持静态 SQL 语句 和动 态 SQL 语句 两种 SQL 语 句有不同 的 执行方 法 对于静 态 SQL 语句 可 使用立 即执 行方 式 对于 动态 SQL 语句 则使 用准 备执 行方 式 即使用 PREPARE EXECUTE 和 EXECUTE IMMEDIATE 语 句处理 8.5.l 立即 方式 执行 SQL 命令 立即方式执行 SQL 命令用于静态 SQL 语句 在嵌入式 SQL 源程序中直接使用 Transact-SQL 命令 包括 单条 Transact-SQL 语句 批命 令 事务 或存 储过 程等 立即方 式执行 SQL 命令可 通过 宿主变 量 来输 入 输出 数据 它 适用于 数据 库结 构明 确或 将来 改动 不 大的环 境 下面 的例 子说 明立 即 方式执 行 SQL 命令 它检 索 authors 表 并将 检索 结果 存入 宿主 变量 EXEC SQL SELECT au_lname,au_fname INTO : lname, : fname FROM authors WHERE au_id = : id; 该嵌入 式 SQL 语句 向 SQL Server 发送单条 Transact-SQL 检索命 令 并将 检索 结果 存 储到宿 主变 量 lname 和 fname 中 下 面的例 子以 立即执 行 方式 向 SQL SCfVef 数 据 库中插 入一条 记录 EXEC SQL INSERT authors au_id au_lname au_fname contract VALUES ”172-32-1176” ”White” ”Johnson” 1 ; 8.5.2 修改 数据 修改 数据 包括 记录 的 插入 修改 和删 除等 操作 插入记 录使 用 INSERT 语 句 来完成 修 改和删除 记录操作因处 理条件的不同 可分为检索修改 删除 和定位修改 删除 两种 定位修改 删除 主要 在游标中使用 这方面内容将 在游标 操作一节中详 细介绍 本节 主 要介绍 检索 修改 删除 操作 检索 修改 语句 的格 式 为 UPDATE {table_name | view_name} SET [table_name. | view_name.] {column_name= {expression | NULL | (select_statement)}[,...]} [FROM {table_name | view_name}[,...]] [WHERE search_condition] 其中 table_name 或 view_name 为要处理 的表 或视 图名称 column_name 为要 修改 的列 名 expression 为 C 表达 式 select_statement 为只返回 1 条 记录且只 包含 单列 的 SELECT 语句 search_condition 为 搜索条 件 它指 定要 修改 的 数据行 下面的 例子 说明 UPDATE 语句的 使用 EXEC SQL UPDATE authors SET au_fname=’Fred’ WHERE an_lname ’White’ 检索 删除 语句 的格 式 为 DELETE [FROM table name| viewname WHERE search_conditions 其中 table_name 或 view name 为要处理 的表 或视 图名 称 search_condition 为搜索 条 件 它指 出待 删除 的数 据 行 DELETE 是标 准的 Transact-SQL 语句 它删 除数 据表 中满 足 WHERE 条 件的记 录 如 果不使 用 WHEREf 句提 供 搜索条 件时 则删 除指 定表中 的 所有 记录 删除 了全 部记 录后 空 表仍然 存在 直到 使用 DROP TABLE 语句 删除 表为止 如果 DELETE 语句 所处 理 的视图 中包 含多 个数 据表 则不能 执行 DELETE 操作 因为 嵌入式 SQL 不支持对 多基表 视 图的 删除 操作 同样 UPDATE 语句 和 INSERT 语句也只 能 对 单基表 现图 操作 下面例 子说 明 DELETE 语 句的用 法 EXEC SQL DELETE FROM authors WHERE au_lname ’White’ 立即执行方式的修改和删除操作适用于搜索条件和修改值已知的情况下使用静态 SQL 语句 来处 理 如果 需 要用户 动态 地输 入搜 索条 件 或修改 值 则应 使用 动态 SQL 来处理 8.5.3 执行 SQL 批命 令和 事务 立即方 式执 行 SQL 命 令 不 仅可以 执行 单条 Transact-SQL 命令 同样 可以 执行 Transact- SQL 批命令 和事 务 例如 下面 嵌入 式 SQL 语句执 行一 个 Transact-SQL 批命令 它向 SQL Server 数据库 插入两 条记 录 EXEC SQL INSERT INTO authors VALUES ‘111-22-3333’,’Tom’,’Fozzy’, ‘123-444-5678’,’Mmuppet Show’,’Wall Street’,’wa’,’10002’,1 INSERT INTO authors VALUES ‘222-33-4444’,’Henry’,’Kermit’, ‘123-444-5678’,’Seame’,’Wall Street’,’wa’,’10002’,1 GO; 事务是 SQL Server 的单个工 作单 元 一个 事务 内的 所有 Transact-SQL 语句被 作为 整体 执行 下面 的例 子说 明事务 的使 用 方法 EXEC SQL BEGIN TRANSACTION INSERT INTO authors VALUES ‘111-22-3333’,’Tom’,’Fozzy’, ‘123-444-5678’,’Mmuppet Show’,’Wall Street’,’wa’,’10002’,1 INSERT INTO authors VALUES ‘222-33-4444’,’Henry’,’Kermit’, ‘123-444-5678’,’Seame’,’Wall Street’,’wa’,’10002’,1 COMMIT TRANSACTION 下面的 例子 说明 了数 据库回 滚 事务 操作 EXEC SQL BEGIN TRANSACTION rollback_authors INSERT INTO authors VALUES ‘111-22-3333’,’Tom’,’Fozzy’, ‘123-444-5678’,’Mmuppet Show’,’Wall Street’,’wa’,’10002’,1 INSERT INTO authors VALUES ‘222-33-4444’,’Henry’,’Kermit’, ‘123-444-5678’,’Seame’,’Wall Street’,’wa’,’10002’,1 ROLLBACK TRANSACTION rollback_authors 在示例 中 首先 向 authors 表插 入 2 条记录 然后 回 滚该事 务 authors 表恢 复到 事 务执行 前的 状态 8.5.4 执行 存储 过程 嵌入式 SQL 支持 SQL Server 存储过程 的调 用 存储过 程 可接受 参数 并返 回状 态值 和参数 值 下 面的 例子 说 明存储 过程 的执 行方 法 EXEC SQL exec master.sp_addtype demo_new_type,int; IF (SQLCODE = = 0){ EXEC SQL SELECT name into :added_type FROM master.systype WHERE name =”demo_new_type”; IF (SQLCODE = = 0 | |SQLCODE = = 1) { Printf(“%s\n”,added_type );EXEC SQL exec master.sp_droptype demo_new_type; } } 在该 例中 首先执行 SQL Server 系统存储过程 sp_addtype 定义一个新的数据类型 demo_new_type 然后使用 SELECT 检索语句检 索该 数据 类型 如果存 在显 示新 数据 类型 的名称 调用 sp_droptype 存储过 程删 除它 8.5.5 准备 方式 执行 SQL 命令 对于动 态 SQL 语句 嵌入式 SQL 提供 了准 备方 式来 执行 应用 程序 运行 时生 成的 动态 SQL 语句 准备 方式 执行 SQL 命令 时 首先 需调 用 PREPARE 语 句准备 要执 行 的动态 SQL 语句 然后使用 EXECUTE 语句来执行准备好的动态 SQL 语句 或使用 EXECUTE IMMEDIATE 语句 直接 执行 动态 SQL 语句 下面 分 别介绍 执行 动态 SQL 语句要 用到 的几 个嵌入 式 SQL 命令 PREPARE 语句 的格 式 为 PREPARE stmt_name [INTO :sqlda] FROM :hvar 其中 stmt_name 是 将 要执行 的动 态 SQL 语 句 的名称 hvar 是包 含 SQL 语 句 的字符串 型 宿主变 量 sqlda 是用 来 输出数 据的 SQLDA 数据结 构 PREPARE 语 句从 字 符串 型 宿主变 量中 接受 动态 SQL 语句 并给 该语 句赋 一个 名字 便于以 后执 行 PREPARE 语句定 义的 语句 名在 一个 程序 模块 中是 全局 性的 一个 程序 模块 不能用 多个 PREPARE 语 句定义 同一 个语 句名 语句名 也 不能在 不同 的程 序模 块中 共享 如果 使用 EXECUTE 语 句执行 动态 SQL 语句 则 宿 主变量 中包 含的 SQL 语句 不能 返 结果 动态 SQL 语句 要返 回结 果 只能 通过 定义 动态 游标 来 实现 PREPARE 语句 准备的 动态 SQL 语句 中不 能包 含宿 主变 量或 注释 但可 包含 参数 标志 符 需要 输入 参数 的地 方用 代替 动态 SQL 语句 中 也 不能包 含嵌 入式 SQL 的 保留字 EXECUTE 语句 的格 式为 EXECUTE prepared_stmt_name [USING DESCRIPTOR :sqlda_struct | USING :hvar [,...]] 其中 prepared_stmt_name 是 PREPARE 语句 定义 的动 态 SQL 语句 名 hvar 是 一 个或多 个用来 输入 数据 的宿 主变量 sqlda_struct 是已 定义 并 包含输 入数 据的 SQLDA 数据结 构 EXECUTE 语句 执行 已 准 备好的 动态 SQL 语句 使用 宿主 变量 或 SQLDA 数据结 构 输 入数据 如果 动态 SQL 语句中 包含 参数 标志 符 EXECUTE 语 句必须使 用 USING hvar 选项或 使用 USING DESCRIPTOR sqlda_struct 选 项提供 参数 参数 的个 数 数据 类 型和顺 序必 须与 动态 SQL 语句中定 义的 相同 EXECUTE 语 句不支持 有返 回结 果的 动态 SQL 语句 下面的 例子 说明 如何 使用 PREPARE 和 EXECUTE 语 句执行 动态 SQL 语句 EXEC SQL BEGIN DECLARE SECTION; char prep[] = "INSERT INTO mf_table VALUES(?,?,?)"; char name[30]; char car[30]; double num; EXEC SQL END DECLARE SECTION; EXEC SQL PREPARE FROM :prep; while (SQLCODE == 0) { strcpy(name, "Elaine"); strcpy(car, "Lamborghini"); num = 4.9; EXEC SQL EXECUTE prep_stat USING :name, :car, :num; } 在该 例中 由 PREPARE 语句 定义 了一 个动 态 SQL 语句 prep_stat 该动态 SQL 语句完 成向 SQL Server 数据 库 authors 表中插 入一 条记 录 的操作 然后 使用 strcpy 函 数 为宿主 变量赋 值 最后 用 EXECUTE 语句 执行 准备 好的 动态 SQL 语句 并提供 插入 语句 所 需的 3 个 参数值 8.6 嵌入式 SQL 选项 设置 嵌入 式 SQL 使用 SET OPTION 语句设 置 SQL Server 的 查询处理 选项 SET OPTION 语 句的语 法格 式为 SET OPTION {QUERYTIME | LOGINTIME | APPLICATION | HOST} value 其中 各选 项的 意义 如 下 QUERYTIME 设 置命 令 响应超 时时 限 即执 行 Transact-SQL 语句后 嵌入 式 SQL 应用程 序等 待 SQL Server 对该语 句响 应的 时间 以秒 为单 位 默认 值为 0 表明 等待时 间没 有限 制 设置 QUERYTIME 选项 与 DB-Library 中的函数 dbsettime 功 能相同 LOGINTIME 设置 登录 超时 时限 即应 用程 序执 行 CONNECT TO 语 句等录 SQL Server 服务器 时 嵌入 式 SQL 应用程 序等 待 SQL Server 响应的时 间 单位 为秒 默认值 为 10 秒 LOGINTIME 选项 的作 用与 DB-Library 中的函数 dbsetlogintime 的功能 相同 APPLICATION 指出 应用 程序 名称 它设 置 DB-Library LOGINREC 结 构中的应 用程序 名称 值 设置 APPLICATION 与 DB-Library 中 dbsetapp 函 数的功能 相同 HOST 指出用 户主 机名 称 它设 置 DB-Library LOGINREC 结 构中的 用户 主机 名 称值 HOST 选项 的作 用与 DB-Library 中 dbsethost 函 数 的 功能相同 嵌入 式 SQL 不支持其它 的 DB-Library 选项和 SQL Server 选项设 置 但 SQL Server 选项可 通过 Transact-SQL 语句进行 设置 8.7 建立嵌 入式SQL 应用程 序 如果 要开 发某 一环 境 如 Windows 2000 Windows NT 或 Windows 95 下的 应用程 序 必须在 此应 用环 境下 编译 链接 嵌入 式 SQL 源程序 本节将 详细 介绍 嵌入 式 SQL 应用程 序 的建立 步骤 和运 行方 式 并给出 一个 例子 说明 如何 编 译 链接 嵌入 式 SQL 程序 8.7.l 应用 程序 建立 步骤 所有的 嵌入 式 SQL 应 用 程 序都应 按以 下步 骤来 建立 使用 C 集成 开发 环境 或文 本编 辑器 建立 嵌入 式 SQL 源程 序 所有 的嵌 入式 SQL 源程序 均以.sqc 作文 件扩展 名 运行相 应的 预编 译器 sqlprep.exe(Windows 3.x 或 MS-DOS 环境 下) 或 nsqlprep.exe Windows NT Windows95/98 等 32 位操 作系 统 将 嵌入式 SQL 源 程序转 换为 C 源程序 运行 C 编译 器将 嵌入 式 SQL 预编译器 产生 的 C 源程序编 译为 目标 文件 运行链 接程 序将 目标 文件 嵌入 式 SQL 库文 件及 其它需 要 链接 的库 文件 链接起来 生成可 执行 文件 8.7.2 应用 程序 运行 方式 当运行 一个 嵌入 式 SQL 应用程 序时 程序 中的 SQL 语句按 如下 方式 执行 对于所 有的 SQL 语句 应 用程序 调用 嵌入 式 SQL 运行 时动 态链 接库 如果 SQL 语句是 静态 的 嵌入式 SQL 运行库 将使 用给 定的 参数 执行 已编 译好 的 存储过 程 如果 SQL 语句是动 态的 嵌入 式 SQL 运行 库直 接将 动态 生成 的 SQL 语句发 送到 SQL Server 执行 嵌入式 SQL 运行库调 用 DB-Library 函数向 SQL Server 发送 SQL 命令 运行库 将检 索到 的数 据填入 C 宿主变量 或 SQLDA 数 据结构 中 执计 状态 和 错误信 息填 入 SQLCA 数据 结构 中 图8-1 嵌入式 SQL 应用程序建立 运行过程 图 8-l 说明了嵌 人式 SQL 应用程序 的编 译 运行 过程 以及 各个 过程 中所 使用 的 输入 库和运 行库 动态 链接 库 嵌入 式 SQL 支持所有的 Transact-SQL 扩展性能 包括 存储 过程 局 部变 量 以及流 控制 语句 为了 避免 语法 冲突 使用 Transact-SQL 扩展性能 需遵 循如 下限 制 将 Transact-SQL 的 EXECUTE 语句缩 写为 EXEC 以避免与 嵌入 式 SQL 的 EXECUTE 语句冲 突 避免 在静 态 SQL 语句中使 用 Transact-SQL 语句标号 因为 它已 与宿 主变 量 的语法 产生 冲突 但在 动态 SQL 语句中可 以使 用 Transact-SQL 语句标号 因为嵌 入式 SQL 预编译器将 静 态 SQL 语句转 换到 存储 过程 所以 问 SQL Server 对存 储过程的限制同样适用于静态 SQL 语句 包含单个事务管理语句 如 COMMIT 或 SAVPOINT 的静 态 SQL 语句不 被编 译为 存储 过程 而是 在运 行时 被动 态执 行 8.7.3 使用 预编 译器 嵌入 式 SQL 预编 译器 sqlprep.exe 或 nsqlprep.exe 在源程序 中查 找并 分 析 SQL 语 句 然后 建立 能被 相应 的 C 编译器编 译的 C 源程序 在使用 嵌入 式 SQL 预 编译器 之前 需 要做如 下准 备工 作 如果 所使 用的 C 编 译 器不是 cl.exe 或 C 编译程 序不在 系统 环境 变量 PATH 指定的 搜 索路径 中时 必须 使用 COMPILER 环境变 量指 出编 译 器的名 称和 存储 路径 如 SET COMPILER=D \mybin \cl.exe 设置 INCLUDE 环 境变量 使其 指向 存储 嵌入 式 SQL 头文 件 Sqlca.h 和 Sqlda.h 的目录 路径 如 SEET INCLUDE D \PROGRAM FILES\MICROSOFT SQL SERVER DEVT00LS INCLUDE C \MSDEV INCLUDE 嵌入 式SQL 预编 译器使 用C 编译器来 处理 头文 件 并自 动将 这些 头文 件包 含到C 源程 SQL 序中 所以 不 需要 在嵌入 式 源程序 中包 含这 些 头文件 在编 译时 如果 使用/DB 和/PASS 选项 为了 能够连 接 到SQL Server 要 确保相应的 Net-Library 文件 已被装 载 如果 使用C 集成 开发 环境 以上 的路 径设 置可 先在C 集 成环境的选 项菜 单 中设置 嵌入 式 SQL 预 编译 器 的语法 格式 如下 sq 1prep [/SQLACCESS | /NOSQLACCESS [/FLAGGER {ENTRY|NONE}] [/DB [server_name.]database_name /PASS login[.password]$INTEGRATED ][/BIND file_name] [/MSG file_name] [/NOLOGO] [/PLAN name][/NOLINES] [/user_defined_option] 各编 译选 项的 作用 为 /SQLACCESS 指 定预 编 译器自 动为 静态 SQL 语 句 建 立存储 过程 同时 必须 使 用 /DB 和/PASS 选项 来连 接 SQL Seryer 或使用 FIND 选 项建立 一个 绑定 文 件 /NOSQLACCESS /DB 指定预编译器不自动建立存储过程 如果 同时 使用 了 和 /PASS 选项 预编 译器 将提 示一 个信 息 然后 连接 到 SQL Server 并删 除以 前建 立 的存储 过程 /FLAGGER 指定 预编 译 器在编 译阶 段将 静态 SQL 语句发 送到 SQL Server 进行语 法检查 SQL Server 将显 示 SQL 语句 编 译 过 程 中 所发 现的语 法 和编译错误 使 用/FLAGGER 选项 时 必 须同时 使用/DB 和/PASS 选项 但 FLAGGER 选 项不能 与/PLAN 或/SOLACCESSA 选项同 时使 用 其中 ENTRY 参数 说明 静态 SQL 语句 必须 同时 遵循 FIPS 127-2 标准 定义 的 SQL 一致 性要求 而 NONE 参数 则 说明只 对静 态 SQL 语句 进行 语法 检查 而 不要 求它符合 FIPS 127-2 标准定 义 /DB 指定 要连 接并 存放 存 储过程 的 SQL Server 服务器 和数 据库 名 名称 必须 与 源程序 中 CONNECT TO 语句使 用的 名称 相同 如果 SQL Server 是 运行在 本地 计 算机上 可不 指定 SQL Server 名 当使 用/DB 选项时 必须 同时 使用/PASS 选项 /PASS 指定 连接 SQL Server 时使用 的用 户名 和口 令 其中$INTEGRATED 指出 使用 Windows NT 集成 安 全模式 登录 /BIND 指定 预编 译器 产生 的绑 定文 件名 文件 名称 后缀.bnd 被 自动添 加 /PLAN 指定 使用 非默 认 的访问 计划 名 默认 的访 问 计划名 与嵌 入式 SQL 源程序 名相同 /MSG 指定 记录 预编 译 器 产生的 警告 和错 误信 息的 文件 名称 /user_defined_option 指定 将传 递给 C 编译器的 用户 定义 选项 在预编 译时 如果 使用 了/DB 和/PASS 选项 预编 译器 将连 接到 SQL Server 为每 一 个独立 的程 序模 块建 立一个 访 问计 划 访 问计 划包 含 在编译 过程 中为 每一 个静 态 SQL 语句 所产生 的存 储过 程 默 认 情况下 存储 过程 名由 以下几 部 分组 成 程序模 块名 8 个可 打印 字符 的日 期/ 时间 标志 一个美 元符 号 $ 访问计 划编 号 如果编 译时 要连 接的 SQL Server 无效或 不需 要连 接 SQL Server 时 可使 用/BIND 选 项建立 一个 绑定 文件 绑定 文 件是 一个 为访 问计 划建立存储过程的 Transact-SQL 批命令 文 件 用户 可以 在晚 些时 候 使用 SQL Server 的 ISQL 工 具运行 绑定 文件 在数 据库上 建 立相 应的存 储过 程 要 注意 的 是在运 行应 用程 序之 前必 须 先运行 绑定 文件 8.7.4 编译 链接 及调 试 下面举 一个 简单 的例 子说明 嵌 入式 SQL 应用 程序 的编译 链接 过程 //FILENAME :sqldemo.sqc viod error_handler (void) #include #include int main() { // 定义宿主变量 EXEC SQL BEGIN DECLARE SECTION; Char id[] = “111-22-3333”; Char lname[40]; Char fname[20]; EXEC SQL END DECLARE SECTION; // 错误处理函数 EXEC SQL WHENEVER SQLERROR CALL error_handler(); // 选项设置 EXEC SQL SET OPTION LOGINTIME 10; EXEC SQL SET OPTION QUERYTIME 60; // 连接SQL Server EXEC SQL CONNECT TO WebServer.pubs USER sa; If (SQLCODE= =0) Printf (“SQL Server 连接已经建立\n”); Else { printf (“ 错误 SQL Server 连接失败\n”); return (1); } // 使用静态SQL 语句 发送检索命令 EXEC SQL SELECT au_lname,au_fname INTO :lname,:fname FROM authors WHERE au_id =:id; // 显示检索结果 printf (“ 作者 %s %s\n”,lname,fname); // 关闭连接 EXEC SQL DISCONNECT ALL; Return (0); } // 定义错误处理函数 void errror_handler (void) { printf (“ 错误处理函数被调用 \n”); printf (“ SQL Code = %l \n”,SQLCODE); printf (“ SQL Server 信息 %l %Fs’\n”,SQLERRD1,SQLERRMC); } 对于嵌 入式 SQL 源程序 首先使 用 sqlprep.exe 预编 译器 生成 对应 的 C 源程 序 然后 使用 C 编译 器将 C 源程 序 编译成.obj 目标文 件 最 后使 用链 接程 序将所 有目 标模 块和 链接 库链接 成执 行文 件 使 用 链接程 序时 要指 明链 接库 文 件的详 细路 径 可设 置 LIB 环境变 量 来指定 如 SEET LIB D \PROGRAM FILES\MICROSOFT SQL SERVER DEVT00LS LIB C \MSDEV LIB 链接嵌 入式SQL 应 用程 序 时有以 下几 个链 接库 必须 链 接 32 位 Windows 环境 caw32.lib 和 sqlakw32.lib 16 位 Windows 环境 caw.lib 和 sqlakw.lib MS-DOS 环境 car.lib rldb.lib 和 sqlakd.lib 如果在 编译 和链 接时 设置相 应 的调 试选 项 嵌入 式 SQL 应 用程序 可使 用 Microsoft 的调试程 序或 其兼 容的 调试程 序 进行 源程 序级 调试 并 可在嵌 入式 SQL 语 句上设 置断 点对 宿 主变量 进行 监控 按以上 方法 将 sqldemo.sqc 编译 链接 后 生成 明 sqldemo.exe 文件 其 执行 结果如 下 SQL Server 连接已建立 作者 White 8.8 小 结 本章介 绍了 一些 嵌入 式 SQL 语句的 程序 开发 方法 具 体讲了 嵌入 式 SQL 的 程序开发 环境 数 据类型及语法 数据库访问 过程和选项设置 等内容 最后以一个 实例结 尾 使读 者对整个 过程有清晰的 印象 这种数 据库编程方法的 优点在 于可移植好 只要 有 预编译 器 和 C 编译 器 就能 运行 程序 对相 关构 件的 支持 要求 不高 |