访问和更改关系数据概述

访问和更改关系数据包含如何从 SQL Server 表检索数据和如何修改 SQL Server 表中数据的信息。SQL Server 应用程序通常以下列两种方式之一处理 SQL Server 数据：

  • 应用程序使用 ADO、OLE DB 或 ODBC 等数据库应用程序接口 (API) 来执行 Transact-SQL 语句，以处理表格格式结果集形式的 SQL Server 数据。

  • Internet 应用程序使用统一资源定位器 (URL) 或 ADO 或 OLE DB API 来执行 Xpath 查询或 Transact-SQL 语句，以处理 XML 文档形式的 SQL Server 数据。

访问和更改关系数据涉及生成和执行 Transact-SQL 语句以及处理关系（或表格格式）结果集形式的结果的基础知识。有关使用 Xpath 查询或 Transact-SQL 语句来处理 XML 文档的更多信息，请参见"XML 和 Internet 访问概述"。

主题 | 描述
查询工具和编程接口 | 描述在 SQL Server 中使用的各种工具。概括介绍 Transact-SQL 如何与实用工具或数据库 API 连接。
Transact-SQL 语法元素 | 描述 Transact-SQL 语句中使用的主要语法元素。
访问和更改数据基础知识 | 描述 SQL Server 应用程序必须解决的、不是任何 Transact-SQL 语句所特有的基本问题： 选择数据库 使用 Transact-SQL 批处理和脚本 使用变量和参数 控制逻辑流程 理解权限
查询基础知识 | 描述生成 Transact-SQL 语句的基本过程。描述 SELECT、INSERT、DELETE 和 UPDATE 语句中使用的主要子句： 选择列表 FROM 子句 WHERE 子句 ORDER BY 子句 JOIN 子句
高级查询概念 | 描述一些高级概念，例如： 使用聚合函数 使用 GROUP BY 和 UNION 分区视图 子查询 汇总数据 错误处理 Transact-SQL 提示
修改数据 | 描述插入新行、更新或删除现有行的方法。
事务 | 描述如何将多个数据修改语句组成一个事务。
游标 | 描述 SELECT 语句如何始终返回行集，而应用程序有时需要逐行处理结果集。游标支持每次处理结果集中的一行或一个行块。
锁定 | 描述 SQL Server 如何防止多个用户同时修改相同的数据。
分布式查询 | 描述如何对引用不同 SQL Server 示例上的数据、甚至是引用非 SQL Server OLE DB 或 ODBC 数据源中的数据的 Transact-SQL 语句进行编码。
全文检索 | 描述如何使用比 SQL-92 中支持的功能强大的文本检索功能，以及如何在文本检索中包括数据库的外部文件。


查询工具和编程接口

访问和更改 Microsoft® SQL Server™ 实例中的数据的用户需要不同级别的 Transact-SQL 知识，具体取决于用户访问数据库的方式：

  • 图形报表生成器和常规业务应用程序的用户几乎不需要 Transact-SQL 知识。应用程序或者提供易于使用的图表和图形（这几乎不需要数据库知识），或者提供基于用户业务功能的对话框。

  • 通用 SQL 应用程序（如 SQL 查询分析器和 osql 实用工具）的用户，必须了解如何从实用工具或在 Transact-SQL 语句的脚本中使用 Transact-SQL。

  • 应用程序设计人员必须完全了解 SQL 应用程序中的 Transact-SQL 功能，以及如何使用数据库应用程序接口 (API) 为 Transact-SQL 语句提供数据值并检索关系（表格格式）结果集中的数据。

  • Internet 应用程序设计人员必须了解 Transact-SQL 语句如何使用关系行集的基础知识，此外还必须了解 SQL Server 如何使用 XML 文档。他们必须了解：
    • 合并架构如何表示 SQL Server 表中基于 XML 的数据视图，以及如何使用 XPath 查询检索 XML 文档形式的数据。

    • 如何使用 FOR XML 子句指示 SELECT 语句作为 XML 文档而非表格格式结果集返回其结果。

    • 如何将 XML 文档添加到数据库，然后使用 OPENXML 子句将文档中的数据表示成关系结果集。

    • 如何使用统一资源定位器 (URL)、ADO 或 OLE DB 执行 Transact-SQL 语句、查询模板和 XPath 查询。

许多用户可能在不同的时间在所有这四类应用程序中工作。应用程序设计人员可以使用 SQL Server 工具（如 SQL 查询分析器），在将 Transact-SQL 语句编入应用程序之前测试这些语句。当数据库管理员与用户和程序员一起设计新特性并解决数据库问题时，将在所有这四个级别上工作。

"访问和更改关系数据"中的主题主要适合普通 SQL 工具的用户和应用程序设计人员。另外，在相关地方，还提供有关应用程序设计人员可用的其它功能的信息。 Internet 程序设计人员必须了解"访问和更改关系数据"中的许多概念，才能理解所执行的许多 Transact-SQL 语句的作用。 有关如何处理 XML 文档格式的数据的特定信息，请参见 XML 和 Internet 支持概述。


查询工具

Microsoft® 提供下列工具访问和更改 Microsoft SQL Server™ 实例中的数据。

  • SQL 查询分析器

  • SQL Server 企业管理器

  • osql 实用工具

  • bcp 实用工具

使用这些工具所需的 Transact-SQL 知识水平各不相同。


SQL Server 工具

SQL 查询分析器和 osql 实用工具支持交互地使用 Transact-SQL 访问和更改数据，而 bcp 实用工具可用于将大量新行快速插入表中。SQL Server 企业管理器用于从单个控制台管理多个 SQL Server 实例。

SQL 查询分析器和 osql 实用工具用于：

  • 执行一条或多条 Transact-SQL 语句。

  • 向用户显示查询结果，或将结果保存到文本文件中。

通过使用 SQL 查询分析器，可以同时连接到多个 SQL Server 企业管理器实例。SQL Server 企业管理器还支持同时使用多个 SQL Server 实例。osql 和 bcp 实用工具仅支持每次使用一个实例。


使用 SQL 查询分析器

SQL 查询分析器是 Microsoft® Win32® 应用程序，是一种特别用于交互式执行 Transact-SQL 语句和脚本的极好工具。若要使用 SQL 查询分析器，用户必须了解 Transact-SQL。

在 SQL 查询分析器中，用户可在全文窗口中输入 Transact-SQL 语句，执行语句并在结果窗口中查看结果。用户也可以打开包含 Transact-SQL 语句的文本文件，执行语句并在结果窗口中查看结果。

SQL 查询分析器还提供工具以确定 Microsoft SQL Server 是如何解释并处理 Transact-SQL 语句的。用户可以：

  • 显示语句所生成的执行计划的图形表示。

  • 启动索引优化向导确定可为基础表定义哪些索引，以优化语句的性能。

  • 显示有关语句性能的统计信息。


使用 SQL Server企业管理器

可以从 SQL Server 企业管理器启动 SQL 查询分析器。

在 SQL Server 企业管理器的"工具"菜单上，单击"SQL 查询分析器"命令运行 SQL 查询分析器。通过 SQL 查询分析器，用户可以交互式地设计和执行查询。

SQL Server 企业管理器还包括查询设计器，即一种用于在特定对象中设计查询的图形用户界面 (GUI) 工具：

  • 在控制台树中正使用的数据库内右击"视图"，然后单击"新视图"命令。查询设计器可用来设计视图的 SELECT 语句。有关更多信息，请参见创建视图。

  • 在 DTS 设计器中打开 DTS 包并添加"执行 SQL"任务，然后单击"生成查询"以访问 DTS 查询设计器。有关更多信息，请参见 DTS 查询设计器。

  • 在右面的窗格中右击一个表，单击"打开表"命令，然后右击"返回所有行"以查看表中的所有行。可以使用查询设计器更改查询，以便查看特定行。 有关更多信息，请参见执行查询的基本操作。

查询设计器窗口有四个单独的窗格，用来指定或显示与查询相关的不同项目。

窗格 | 描述
关系图 | 有显示查询所引用表的关系图。此关系图显示每个表的所有列并检查结果集内用到的列。
SQL | 显示 SQL 语句语法。
网格 | 显示结果集列中的详细信息，如名称、数据类型和大小。
结果 | 显示上次执行查询所返回的结果集。

有关使用"查询设计器"窗格的更多信息，请参见查询设计器和视图设计器的布局。


使用 osql 实用工具

osql 实用工具是一个 Microsoft® Win32® 命令提示实用工具，特别用于交互式执行 Transact-SQL 语句和脚本。若要使用 osql，用户必须了解 Transact-SQL。

通常有以下方式使用 osql 实用工具：

  • 用户通过类似于处理命令提示的方式交互式地输入 Transact-SQL 语句。结果将显示在命令提示窗口中。

  • 用户提交 osql 作业，同时指定要执行的单个 Transact-SQL 语句，或将实用工具指向包含要执行的 Transact-SQL 语句的文本文件。输出通常指向文本文件，但也可以显示在命令提示窗口中。

osql 实用工具使用 ODBC 数据库应用程序接口 (API)。它是基于 DB-Library API 的 isql 命令提示实用工具的替代工具。两个实用工具都随 Microsoft SQL Server™ 2000 提供。DB-Library API 依然停留在 SQL Server 6.5 的水平上；因此，依赖 DB-Library 的应用程序（如 isql）不支持 SQL Server 2000 版的某些功能。例如，isql 无法访问用 ntext 数据类型定义的列，并截断任何超过 255 字节的 char、varchar、nchar 或 nvarchar 列。 它也无法检索 XML 文档形式的结果。除了 isql 中的这些限制外，osql 和 isql 支持相同的功能。有关 isql 所支持的功能的更多信息，请参见将早期版本的客户端连接到 SQL Server 2000。

请参见

isql 实用工具

osql 实用工具


使用 bcp 实用工具

bcp 大容量复制实用工具可用于将大量新行插入 Microsoft® SQL Server™ 表中。此实用工具不要求具有 Transact-SQL 知识，但用户必须了解正将新行复制到其中的表的结构以及对表中的行有效的数据类型。

请参见

bcp 实用工具


编程接口

Microsoft® SQL Server™ 支持很多数据库应用程序接口 (API)，而这些接口可用于编写在 SQL Server 数据库中存储数据的应用程序。虽然这些应用程序的用户需要很少（如果有）的数据库知识，但应用程序开发人员必须了解如何使用数据库 API 执行 Transact-SQL 语句或 XPath 查询，以使应用程序能够访问存储在 SQL Server 数据库中的数据。


Microsoft 编程环境

Microsoft® Visual Studio® 包括主要的 Microsoft 开发系统：Microsoft Visual Basic®、Microsoft Visual C++®、Microsoft Visual J++®、Microsoft Visual InterDev™ 和 Microsoft Visual FoxPro®。

这些开发系统都支持开发 Microsoft Windows® 应用程序和基于 Web 的应用程序。Microsoft SQL Server™ 支持这些语言及其它语言生成在数据库中存储数据的应用程序时所用到的数据库应用程序接口 (API)：ADO、OLE DB、ODBC、嵌入式 SQL 和传统 API（如 DB-Library）。SQL Server 2000 还支持通过统一资源定位器 (URL) 访问 SQL Server 实例以及处理 XML 文档形式的数据。

除了要熟悉如何使用 Transact-SQL 以外，程序员还必须了解数据库 API 与 Transact-SQL 的交互方式。他们必须了解如何向参数提供数据值以及如何将结果集内的数据值移入应用程序的变量中。

Internet 应用程序设计人员还必须了解如何使用 XML 文档形式的 SQL Server 2000 数据。有关更多信息，请参见 XML 和 Internet 支持概述。


应用程序接口

Microsoft® SQL Server™ 支持多种数据库应用程序接口 (API)：活动数据对象 (ADO)、OLE DB、开放式数据库连接 (ODBC)、远程数据对象 (RDO)、数据访问对象 (DAO)、Microsoft 基础类 (MFC) 数据库类、嵌入式 SQL 和 DB-Library。SQL Server 通常以动态链接库 (DLL) 的形式（称为提供程序或驱动程序）支持这些 API。提供程序或驱动程序将应用程序对数据库 API 的调用转换成可发送到 SQL Server 实例的命令。

若要使用数据库，则 API：

  1. 打开到数据库的连接。

  2. 设置控制某些行为的选项，如是否要使用游标、要使用何种类型的游标以及是否允许更新。

  3. 执行 Transact-SQL 语句。应用程序也可以使用程序变量为所执行的语句提供参数值（可选）。在每次连接时应用程序一次可执行一条语句，或同时执行多条语句。

  4. 将返回代码的数据值、输出参数和结果集移入程序变量中，并通过程序变量由应用程序逻辑使用。如果语句以 XML 文档形式返回其结果集，则应用程序可以将结果流输出到使用 XML 文档的组件。

  5. 在完成数据库中的工作后断开连接。

应用程序编程人员必须了解 Transact-SQL 并正确使用数据库 API。

请参见

应用程序开发构架

生成 SQL Server 应用程序概述


连接到实例和与实例断开

应用程序在可以使用 SQL Server 数据库之前，必须连接到 Microsoft® SQL Server™ 实例。 通过共享内存或网络这类组件建立连接。应用程序可以打开多个到某个 SQL Server 实例的连接。

建立连接之后，应用程序可以通过连接执行 Transact-SQL 语句。应用程序完成在 SQL Server 实例中必须执行的全部工作后将断开连接。这样可释放该连接在服务器中占用的所有资源，并终止应用程序和实例之间的网络或共享内存连接。

通常：

  • 连接与数据库相关，应用程序可根据需要更改数据库。假设没有指定数据库的对象引用都在与此连接相关联的当前数据库中。

  • 连接与特定的登录帐户相关。登录帐户与 SQL Server 数据库中的用户 ID 相关。如果与连接的登录帐户相关的用户 ID 不允许执行数据库中的某项操作，则连接无法执行该操作。

  • 连接是事务控制单元。如果连接断开，将回滚通过该连接执行的语句所进行的全部未提交更改，但不影响同一应用程序打开的其它连接所执行的未提交更改。应用程序所打开的某个连接所持有的锁定，会阻止同一应用程序所打开的其它连接处理其锁定的行。

  • 连接具有属性，如事务隔离级别，可通过应用程序设置事务隔离级别以指定连接级别的行为。

Internet 应用程序还通过 ADO 或 OLE DB 连接或通过在统一资源定位器 (URL) 中指定 SQL Server 虚拟根连接到 SQL Server 实例。虽然 Internet 应用程序使用的 Transact-SQL 语句或 XPath 查询使用 XML 文档而非关系结果集，但是以上所列的所有特征仍适用于该连接。


准备和执行语句

使用 Microsoft® SQL Server™ 工具（如 SQL 查询分析器和 osql 实用工具），用户可以键入并执行一条或多条 Transact-SQL 语句。Transact-SQL 语句所需的全部内容都必须在执行的批处理语句中指定。所有内容都必须是 Transact-SQL 语句字符文本的一部分。

数据库 API 支持执行 Transact-SQL 语句的许多选项。最重要的选项是：

  • 使用由常用语句组成的预编译执行计划。如果要多次执行 Transact-SQL 语句，则应用程序可以准备语句一次，然后任意多次执行该语句。准备语句是通知服务器将这条语句编译成执行计划。由于后续执行使用预编译计划，并且语句只需编译一次，因此执行速度更快。

    为获得更好的性能，可能要将语句准备推迟到执行该语句时或执行元属性操作（如 ODBC 中的 SQLDescribeCol 或 SQLDescribeParam）时。这是默认行为。在执行准备的语句或执行元属性操作之前，该语句中的任何错误都是未知的。设置适当的语句选项可以关闭此默认行为。

  • 将参数绑定到程序变量。应用程序不必将输入参数的实际数据值包含到 Transact-SQL 语句中，而可以将参数与程序变量关联或绑定。这意味着参数值不必转换为字符串包含在 Transact-SQL 语句的文本中，而是可以使用参数值的本机格式。

准备的语句不能用于在 SQL Server 2000 或 SQL Server 7.0 中创建临时对象。准备的语句不能引用创建临时对象（如临时表）的系统存储过程。必须直接执行这些过程。


处理结果

利用 Microsoft® SQL Server™ 工具（如 SQL 查询分析器或 osql 实用工具），Transact-SQL 语句的结果既可以显示成字符文本，也可以保存为文本文件。SQL Server 会一次显示全部结果集，而不是一次仅提取几行结果集。

当应用程序执行返回关系结果集的 Transact-SQL 语句时，数据库 API 使应用程序能够将结果集的列关联或绑定到应用程序中的变量。当检索到结果集内某一行时，列中的数据将移入绑定变量，以便应用程序使用这些数据。并且，可以本机格式检索数据，而不必将数据转换为字符文本。

数据库 API 还支持关系结果集的游标处理。这样应用程序就可以一次检索结果集内的多行或行块。也不必强制应用程序在处理结果集之前检索并存储整个结果集。

当应用程序执行 XPath 查询或执行返回 XML 文档的 Transact-SQL 语句时，该文档作为流对象返回。有关更多信息，请参见 XML 和 Internet 支持概述。

数据类型转换

程序员在生成使用关系结果集的数据库应用程序时，必须处理两层数据转换：

  • 应用程序中编码的所有 Transact-SQL 语句在将对象与运算符和函数结合时，必须遵守 Transact-SQL 数据转换规则。Transact-SQL 数据转换和优先规则也决定这些操作的最终输出数据类型。

  • 程序在程序变量和数据库对象（如结果集列、参数和返回代码）之间移动数据时，必须遵守数据库 API 数据转换规则。

Transact-SQL 支持数据值从一种数据类型转换为另一种。例如，下面的语句可以将整数转换为字符串：

CAST ( 123 AS VARCHAR(5) )

可以使用 CAST 函数进行显式或隐性转换。例如，如果将 int 列与 char 列比较，则在比较之前，char 的值会隐式地转换为 int 数据类型。《Transact-SQL 参考》定义了 SQL Server 所允许的隐式或显式转换。这些规则只适用于 Transact-SQL 对象之间的转换。

在 Transact-SQL 对象（如参数、返回代码和结果集列）及其绑定程序变量之间进行转换时，会应用另一套规则。这套规则在支持 API 的提供程序或驱动程序的文档中进行了定义。不同的 API，规则也随之不同。例如，SQL Server ODBC 驱动程序支持将数据从 datetime 结果集列转换为 ODBC timestamp 数据结构，而 DB-Library 接口不允许这种转换，因为 DB-Library 接口不支持 ODBC timestamp 数据结构。

请参见

数据类型转换

CAST 和 CONVERT

数据类型

映射数据类型


Transact-SQL 语法元素

Transact-SQL 具有一些大多数语句都使用或受之影响的语法元素：

标识符

诸如表、视图、列、数据库和服务器等对象的名称。

数据类型

定义数据对象（如列、变量和参数）所包含的数据类型。大多数 Transact-SQL 语句并不显式引用数据类型，但是其结果由于语句中所引用的对象数据类型间的互相作用而受到影响。

函数

语法元素，它可以有零个、一个或多个输入值，并返回一个标量值或表格形式的值的集合。函数示例包括将多个值相加的 SUM 函数、确定两个日期之间相差多少个时间单位的 DATEDIFF 函数、获取 Microsoft ® SQL Server™ 实例名称的 @@SERVERNAME 函数或在远程服务器上执行 Transact-SQL 语句和检索结果集的 OPENQUERY 函数。

表达式

是 Microsoft SQL Server 可解析为单个值的语法单元。表达式的示例包括常量、返回单值的函数、列或变量的引用。

运算符

与一个或多个简单表达式一起使用构造一个更为复杂的表达式。例如，将"-"（负号）运算符和常量 12 组合在一起得到常量 -12。表达式 PriceColumn * 1.1 中的"*"（乘号）运算符使价格提高百分之十。

注释

插入到 Transact-SQL 语句或脚本中解释语句作用的文本。SQL Server 不执行注释。

保留关键字

保留下来由 SQL Server 使用的词。数据库中的对象名不应使用这些字词。


使用标识符

数据库对象的名称被看成是该对象的标识符。Microsoft® SQL Server™ 中的每一内容都可带有标识符。服务器、数据库和数据库对象（例如表、视图、列、索引、触发器、过程、约束、规则等）都有标识符。大多数对象要求带有标识符，但对有些对象（如约束）标识符是可选项。

对象标识符是在定义对象时创建的。标识符随后用于引用该对象。例如，下面的语句创建一个标识符为 TableX 的表，该表中有两列的标识符分别是 KeyCol 和 Description：

CREATE TABLE TableX
(KeyCol INT PRIMARY KEY, Description NVARCHAR(80))

此表还有一个未命名的约束。PRIMARY KEY 约束没有标识符。

标识符的种类

标识符有两类：

常规标识符

符合标识符的格式规则。在 Transact-SQL 语句中使用常规标识符时不用将其分隔。

SELECT *
FROM TableX
WHERE KeyCol = 124

分隔标识符

包含在双引号 (") 或者方括号 ([ ]) 内。符合标识符格式规则的标识符可以分隔，也可以不分隔。

SELECT *
FROM [TableX]         --Delimiter is optional.
WHERE [KeyCol] = 124  --Delimiter is optional.

在 Transact-SQL 语句中，对不符合所有标识符规则的标识符必须进行分隔。

SELECT *
FROM [My Table]      --Identifier contains a space and uses a reserved keyword.
WHERE [order] = 10   --Identifier is a reserved keyword.

常规标识符和分隔标识符包含的字符数必须在 1 到 128 之间。对于本地临时表，标识符最多可以有 116 个字符。

常规标识符规则

常规标识符的格式规则取决于数据库的兼容级别，兼容级别可以用 sp_dbcmptlevel 来设置。有关更多信息，请参见 sp_dbcmptlevel。当兼容级别为 80 时，规则是：

  1. 第一个字符必须是下列字符之一：
    • Unicode 标准 2.0 所定义的字母。Unicode 中定义的字母包括拉丁字母 a-z 和 A-Z，以及来自其它语言的字母字符。

    • 下划线 (_)、at 符号 (@) 或者数字符号 (#)。

      在 SQL Server 中，某些处于标识符开始位置的符号具有特殊意义。以 at 符号开始的标识符表示局部变量或参数。以一个数字符号开始的标识符表示临时表或过程。以双数字符号 (##) 开始的标识符表示全局临时对象。

      某些 Transact-SQL 函数的名称以双 at 符号 (@@) 开始。为避免混淆这些函数，建议不要使用以 @@ 开始的名称。

  2. 后续字符可以是：
    • Unicode 标准 2.0 所定义的字母。

    • 来自基本拉丁字母或其它国家/地区脚本的十进制数字。

    • at 符号、美元符号 ($)、数字符号或下划线。
  3. 标识符不能是 Transact-SQL 的保留字。SQL Server 保留其保留字的大写和小写形式。

  4. 不允许嵌入空格或其它特殊字符。

当标识符用于 Transact-SQL 语句时，必须用双引号或括号分隔不符合规则的标识符。

请参见

ALTER TABLE

CREATE DATABASE

CREATE DEFAULT

CREATE PROCEDURE

CREATE RULE

CREATE TABLE

CREATE TRIGGER

CREATE VIEW

DECLARE @local_variable

DELETE

分隔标识符

INSERT

保留关键字

SELECT

UPDATE


使用标识符作为对象名称

一个对象的完整名称包括四个标识符：服务器名称、数据库名称、所有者名称和对象名称。其格式如下：

[ [ [ server. ] [ database ] .] [ owner_name ] .] object_name

服务器、数据库和所有者的名称即所谓的对象名称限定符。当引用一个对象时，不需要指定服务器、数据库和所有者。可以利用句号标出它们的位置，从而省略限定符。对象名的有效格式是：

server.database.owner_name.object_name
server.database..object_name
server..owner_name.object_name
server...object_name

database.owner_name.object_name
database..object_name

owner_name.object_name

object_name

指定了所有四个部分的对象名称被称为完全合法名称。Microsoft® SQL Server™ 中所创建的每一个对象必须具有唯一的完全合法名称。例如，如果所有者不同，在同一个数据库中可以有两个名为 xyz 的表。

同一个表或视图中的列名必须唯一。假设 customer 数据库中的一个表和一个视图具有相同的名为 telephone的列。若要在 employees 表中引用 telephone 列，请指定 customer..employees.telephone；若要在 mktg_view 视图（市场部的视图）中引用 telephone 列，请指定 customer..mktg_view.telephone。

大多数对象引用使用三部分名称并默认使用本地服务器。四部分名称通常用于分布式查询或远程存储过程调用，其格式为：

linkedserver.catalog.schema.object_name

下表给出各部分的名称以及对它们的描述。

名称的各部分 | 描述
linkedserver | 包含分布式查询所引用对象的链接服务器名。
catalog | 包含分布式查询所引用对象的目录名。
schema | 包含分布式查询所引用对象的架构名。
object_name | 对象名或表名。

对于分布式查询，四部分名称的服务器部分是指链接服务器。链接服务器是指由 sp_addlinkedserver 所定义的服务器名称。链接服务器指定一个 OLE DB 提供程序和一个 OLE DB 数据源，数据源返回一个记录集，SQL Server 把它作为 Transact-SQL 语句的一部分来使用。

请查阅有关为链接服务器指定的 OLE DB 提供程序的文档，确定 OLE DB 数据源的哪些组件可以用于目录和架构部分的名称。如果链接服务器正在运行 SQL Server 实例，则目录名是包含对象的数据库，架构是对象的所有者。有关四部分名称和分布式查询的更多信息，请查阅分布式查询。

对于远程过程调用，四部分名称中的服务器部分是指一个远程服务器。使用 sp_addserver 指定的远程服务器是通过本地服务器访问的 SQL Server 实例。利用以下格式的过程名执行远程服务器上的存储过程：

server.database.owner_name.procedure

当使用远程存储过程时，要求对象名称包含所有四部分。有关远程服务器的更多信息，请参见配置远程服务器。

请参见

FROM

Transact-SQL 语法规则


对象可见性和限定规则

当创建对象时，Microsoft® SQL Server™ 2000 对未指定的名称部分使用下列默认值：

  • Server 默认为本地服务器。

  • Database 默认为当前数据库。

  • Owner_name 默认为与当前连接的登录 ID 相关联的指定数据库中的用户名称。

例如，如果用户作为数据库所有者 (dbo) 用户登录到 Northwind，则下面两个语句的任何一个都将创建一个名为 Northwind.dbo.TableX 的表：

CREATE TABLE TableX (cola INT PRIMARY KEY, colb NCHAR(3))

-或-

CREATE TABLE Northwind.dbo.TableX
      (cola INT PRIMARY KEY, colb NCHAR(3))

说明 建议指定完整的表名或视图名，以避免产生与提及对象有关的混淆。

同样，当引用对象时，Microsoft® SQL Server™ 对未指定的名称部分使用下列默认值：

  • Server 默认为本地服务器。

  • Database 默认为当前数据库。

  • owner_name 默认为与当前连接的登录 ID 相关联的指定数据库中的用户名称。如果该用户不拥有具有指定名称的对象，SQL Server 将查找一个具有指定名称的对象，该对象为数据库所有者 (dbo) 用户所拥有。

例如，假定 LoginX 所连接的服务器上有两个数据库：DBY 和 DBZ。LoginX 与数据库 DBY 中的 UserA 以及数据库 DBZ 中的 UserB 相关联。

LoginX 在当前数据库中执行一个 SELECT 语句：

USE DBY
SELECT * FROM DBY..TableX

由于 LoginX 与 DBY 中的 UserA 相关联，SQL Server 首先查找 DBY.UserA.TableX。如果没有具有这个名称的表，SQL Server 将查找表 DBY.dbo.TableX。

在下一个例子中，LoginX 在当前数据库中没有的表上执行 SELECT 语句：

USE DBY
SELECT * FROM DBZ..TableY

由于 LoginX 与 DBZ 中的 UserB 相关联，SQL Server 首先查找 DBZ.UserB.TableY。如果没有具有这个名称的表，SQL Server 将查找表 DBZ.dbo.TableY。

说明 SQL Server不会尝试根据当前的登录来推断远程表的所有者。为确保分布式查询能够正确执行，请使用完全合法的名称。

以 sp_ 开始的存储过程的可见性与标准存储过程的可见性不同。有关更多信息，请参见 CREATE PROCEDURE。


分隔标识符

符合所有标识符格式规则的标识符可以使用分隔符，也可以不使用分隔符。不符合标识符格式规则的标识符必须使用分隔符。

分隔标识符在下列情况下使用：

  • 当在对象名称或对象名称的组成部分中使用保留字时。

    推荐不要使用保留关键字作为对象名称。从 Microsoft® SQL Server™ 早期版本升级的数据库可能含有标识符，这些标识符包括早期版本中未保留而在 SQL Server 2000 中保留的字。可用分隔标识符引用对象直到可改变其名称。

  • 当使用未被列为合法标识符的字符时。

    SQL Server 允许在分隔标识符中使用当前代码页中的任何字符。但是，不加选择地在对象名称中使用特殊字符将使 SQL 语句和脚本难以阅读和维护。

Transact-SQL 所使用的分隔符类型：

说明 分隔符仅用于标识符。分隔符不能用于关键字，不论它们在 SQL Server 中是否被标记为保留字。

  • 被引用的标识符用双引号 (") 分隔开：
    SELECT * FROM "Blanks in Table Name"
  • 括在括号中的标识符用方括号 ([ ]) 分隔：
    SELECT * FROM [Blanks In Table Name]

仅当 QUOTED_IDENTIFIER 选项设置为 ON 时，被引用的标识符才有效。默认情况下，当用于 SQL Server 的 Microsoft OLE DB 提供程序和 SQL Server ODBC 驱动程序连接时，将 QUOTED_IDENTIFIER 设置为 ON。默认情况下，DB-Library 不将 QUOTED_IDENTIFIER 设置为 ON。不管使用何种接口，个别应用程序或用户可随时更改设置。SQL Server 提供了多种方法来指定该选项。例如，在 SQL Server 企业管理器和 SQL 查询分析器中，该选项可在对话中设置。在 Transact-SQL 中，可以使用 SET QUOTED_IDENTIFIER、sp_dboption 的 quoted identifier 选项或 sp_configure 的 user options 选项将此选项设为多种级别。

当 QUOTED_IDENTIFIER 为 ON 时，对于 SQL 语句中的双引号和单引号 (') 的使用，SQL Server 遵循 SQL-92 规则：

  • 双引号只能用于分隔标识符，不能用于分隔字符串。

    为保持与现有应用程序的兼容性，SQL Server 并不完全强制该规则。如果字符串没有超过标识符的长度，则该字符串可包含在双引号内。但不建议这样做。

  • 单引号必须用来包含字符串，不能用于分隔标识符。

    如果字符串包含单引号，则需要在单引号前再增加一个单引号：

    SELECT * FROM "My Table"
WHERE "Last Name" = 'O''Brien'

当 QUOTED_IDENTIFIER 为 OFF 时，对于双引号和单引号的使用，SQL Server 遵循如下规则：

  • 引号不能用于分隔标识符，而是用括号作为分隔符。

  • 单引号或双引号可用于包含字符串。

    如果使用双引号，嵌入的单引号不需要用两个单引号来表示：

    SELECT * FROM [My Table]
WHERE [Last Name] = "O'Brien"

无论 QUOTED_IDENTIFIER 的设置如何，都可以在括号中使用分隔符。

分隔标识符规则

分隔标识符的格式规则是：

  1. 分隔标识符可以包含与常规标识符相同的字符数（1 到 128 个，不包括分隔符字符）。本地临时表标识符最多可以包含 116 个字符。

  2. 标识符的主体可以包含当前代码页内字母（分隔符本身除外）的任意组合。例如，分隔符标识符可以包含空格、对常规标识符有效的任何字符以及下列任何字符：
    代字号 (~) | 连字符 (-)
    惊叹号 (!) | 左括号 ({)
    百分号 (%) | 右括号 (})
    插入号 (^) | 撇号 (')
    and 号 (&) | 句号 (.)
    左圆括号 (() | 反斜杠 (\)
    右圆括号 ()) | 重音符号 (`)

这些例子在表名和列名中使用被引起来的标识符。指定分隔标识符的两种方法如下所示：

SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE "$Employee Data"
(
 "^First Name"   varchar(25) NOT NULL,
 "^Last Name"   varchar(25) NOT NULL,
 "^Dept ID"   int
)
-- INSERT statements go here.
SET QUOTED_IDENTIFIER OFF
GO
CREATE TABLE [^$Employee Data]
(
 [^First Name]   varchar(25) NOT NULL,
 [^Last Name]   varchar(25) NOT NULL,
 [^Dept ID]   int
)
-- INSERT statements go here.

在创建 $Employee Data 和 ^$Employee Data 两表以及输入数据之后，就可以进行行检索：

SET QUOTED_IDENTIFIER ON
GO
SELECT *
FROM "$Employee Data"
SET QUOTED_IDENTIFIER OFF
GO
-- Or
SELECT *
FROM [^$Employee Data]

在下面这个例子中，名为 table 的表中包含 tablename、user、select、insert 等列。由于 TABLE、SELECT、INSERT、UPDATE 和 DELETE 均为保留关键字，因此在每次访问对象时都必须分隔标识符。

SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE "table"
(
 tablename char(128) NOT NULL,
 "USER"    char(128) NOT NULL,
 "SELECT"  char(128) NOT NULL,
 "INSERT"  char(128) NOT NULL,
 "UPDATE"  char(128) NOT NULL,
 "DELETE"  char(128) NOT NULL
)

如果 SET QUOTED_IDENTIFIER 不是 ON，除非使用方括号分隔符，否则不能访问表和列。

SET QUOTED_IDENTIFIER OFF
GO
SELECT *
FROM "table"

下面是结果集：

Msg 170, Level 15, State 1
Line 1: Incorrect syntax near 'table'.

下面是结果集（使用括号分隔符）：

SET QUOTED_IDENTIFIER OFF
GO
SELECT *
FROM [table]
将标识符分隔为多个部分

当使用合法对象名称时，可能要分隔组成对象名的多个标识符。必须分别分隔每个标识符，例如：

/* SQL-92 quoted identifier syntax */
SELECT *
FROM "My DB"."My#UserID"."My.Table"

-或-

/* Transact-SQL bracketed identifier syntax */
/* Not available in SQL Server 6.5 or earlier */
SELECT *
FROM [My DB].[My#UserID].[My.Table]

关于如何分隔 ODBC CALL 语句中由多部分组成的存储过程名有一些特殊规则。有关更多信息，请参见调用存储过程。

在 SQL Server 中将标识符用作参数

许多系统存储过程、函数和 DBCC 语句都把对象名当作参数。其中一些参数接受多部分对象名，另一些则只接受单部分名称。接受单部分对象名称还是多部分对象名称决定了 SQL Server 在内部如何分析和使用参数。

单部分参数名

如果参数为单部分标识符，则可以这样指定名称：

  • 无引号或分隔符。

  • 包含在单引号内。

  • 包含在双引号内。

  • 包含在括号内。

对于单部分名称，单引号内的字符串代表对象名称。如果在单引号内使用分隔符，则分隔符字符将被视为名称的一部分。

如果名称包含句号或常规分隔符所定义的字符集合以外的其它字符，必须将对象名称包含在单引号、双引号或括号内。

多部分参数名称

多部分名称为合法的名称，这种名称除了对象名称外，还包含数据库名称或所有者名称。SQL Server 要求在使用多部分名称作为参数时，必须将组成多部分名称的完整字符串包含在一组单引号内。

EXEC MyProcedure @name = 'dbo.Employees'

如果个别的名称部分要求分隔符，则名称的每一部分都应根据要求单独分隔。例如，如果名称某部分包含句号、双引号、左括号或右括号，则需要使用双引号来分隔这部分。用单引号包含整个名称。

例如，表名 tab.one 包含一个句号。为防止该名称被解释为一个三部分的名称 dbo.tab.one，所以用分隔符分隔表名部分。

EXEC sp_help 'dbo.[tab.one]'

下面给出一个用双引号分隔的相同表名的例子。

SET QUOTED_IDENTIFIER ON
GO
EXEC sp_help 'dbo."tab.one"'
GO

下表列出了一些使用多部分名称的 Transact-SQL 函数、DBCC 语句和系统存储过程。

函数或存储过程 | 参数名
COL_LENGTH | table
DBCC CHECKIDENT | table_name
DBCC CHECKTABLE | table_name
DBCC DBREINDEX | database.owner.table_name
DBCC SHOW_STATISTICS | table
DBCC TEXTALLOC | table_name
DBCC UPDATEUSAGE | table_name
IDENT_INCR | table_or_view
IDENT_SEED | table_or_view
INDEX_COL | table
OBJECT_ID | object
sp_addextendedproc | procedure
sp_autostats | table_name
sp_bindefault | default object_name
sp_bindrule | rule object_name
sp_changeobjectowner | object
sp_depends | object
sp_dropextendedproc | procedure
sp_fulltext_column | qualified_table_name
sp_fulltext_table | qualified_table_name
sp_help | name
sp_helpconstraint | table
sp_help_fulltext_columns | table_name
sp_help_fulltext_columns_cursor | table_name
sp_help_fulltext_tables | table_name
sp_help_fulltext_tables_cursor | table_name
sp_helpindex | name
sp_helprotect | object_statement
sp_helptext | name
sp_helptrigger | table
sp_procoption | procedure
sp_recompile | table
sp_rename | object_name
sp_spaceused | objname
sp_tableoption | table
sp_unbindefault | object_name
sp_unbindrule | object_name

请参见

ALTER DATABASE

ALTER PROCEDURE

ALTER TABLE

ALTER TRIGGER

ALTER VIEW

CREATE DATABASE

CREATE DEFAULT

CREATE PROCEDURE

CREATE RULE

CREATE TABLE

CREATE TRIGGER

保留关键字

SET QUOTED_IDENTIFIER


使用数据类型

包含数据的对象都具有一个相关的数据类型，此数据类型定义对象所能包含的数据种类（字符、整数、二进制数等）。以下对象具有数据类型：

  • 表和视图中的列。

  • 存储过程中的参数。

  • 变量。

  • 返回一个或多个特定数据类型数据值的 Transact-SQL 函数。

  • 具有一个返回代码的存储过程。返回代码总是具有 integer 数据类型。

指定对象的数据类型定义了该对象的四个特性：

  • 对象所含的数据类型，如字符、整数或二进制数。

  • 所存储值的长度或它的大小。

    image、binary 和 varbinary 数据类型的长度以字节定义。任何数字数据类型的长度是指保存此数据类型所允许的数字个数所需要的字节数。string 和 Unicode 数据类型的长度以字符数定义。

  • 数字精度（仅用于数字数据类型）。

    精度是数字可以包含的数字个数。例如，smallint 对象最多能拥有 5 个数字，所以其精度为 5。

  • 数值小数位数（仅用于数字数据类型）。

    小数位数是能够存储在小数点右边的数字个数。例如，int 对象不能含有小数点，小数位数为 0。money 对象的小数点右边最多可以有 4 个数字，小数位数为 4。

如果一个对象被定义为 money 类型，它最多可以包含 19 个数字，其中 4 个数字可以在小数点的右边。这个对象用 8 个字节存储数据。因此 money 数据类型的精度是 19，小数位数是 4，长度是 8。

Transact-SQL 含有以下基本数据类型：

bigint | Binary | bit | char | cursor
datetime | Decimal | float | image | int
money | Nchar | ntext | nvarchar | real
smalldatetime | Smallint | smallmoney | text | timestamp
tinyint | Varbinary | Varchar | uniqueidentifier |

存储在 Microsoft® SQL Server™ 中的所有数据必须与上述这些基本数据类型之一相兼容。cursor 数据类型是唯一的不能赋值给表的列的基本数据类型。它只能用于变量和存储过程的参数。

一些基本数据类型具有同义词（比如，rowversion 是 timestamp 的同义词，national character varying 是 nvarchar 的同义词）。有关同义词行为的更多信息，请参见数据类型同义词。

也可以创建用户定义的数据类型，比如：

-- Create a birthday datetype that allows nulls.
EXEC sp_addtype birthday, datetime, 'NULL'
GO
-- Create a table using the new data type.
CREATE TABLE employee

emp_id

char(5)

emp_first_name

char(30)

emp_last_name

char(40)

emp_birthday

birthday

用户定义的数据类型总是根据基本数据类型进行定义的。它们提供了一种机制，可以将一个名称用于一个数据类型，这个名称更能清楚地说明该对象中保存的值的类型。这样程序员和数据库管理员就能更容易地理解以该数据类型定义的对象的意图。

SQL Server 实例包括一个名为 sysname 的用户定义数据类型。sysname 用于表列、变量和存储对象名的存储过程参数。sysname 的精确定义与标识符规则有关，因此在 SQL Server 的不同实例之间有所不同。sysname 在功能上与 nvarchar(128) 等效。SQL Server 6.5 版或更早版本仅支持更小的标识符；这样，在早期版本中 sysname 被定义为 varchar(30)。

请参见

CREATE TABLE

数据类型

设计表

sp_addtype


使用二进制数据

binary 和 varbinary 数据类型存储位串。尽管字符数据根据 Microsoft® SQL Server™ 的代码页进行解释，但 binary 和 varbinary 数据仅仅是一个位流，binary 和 varbinary 数据的长度最多只能是 8,000 字节。

二进制常量以 0x（一个零和小写字母x）开始，后面跟着位模式的十六进制表示。例如，0x2A 表示十六进制的值 2A，它等于十进制的数 42 或单字节位模式 00101010。

当存储十六进制的数值，比如安全标识号（SID）、GUID（使用 uniqueidentifier 数据类型）或可以用十六进制方式存储的复杂数字时，可使用二进制数据。

这个 Transact-SQL 的例子存储了一个 SID 和十六进制字面值：

USE pubs
CREATE TABLE mycustomertable
(
 user_login DEFAULT SUSER_SID,
 data_value
)

INSERT mycustomertable (data_value)
   VALUES (0x4F)

请参见

二进制数据

数据类型


使用 char 和 varchar 数据

char 和 varchar 数据类型的数据包括：

  • 大写字母或小写字母，比如 a、b 和 C。

  • 数字，比如 1，2 或 3。

  • 特殊字符，如 at 符号 (@)、"与"符号 (&) 和惊叹号 (!)。

char 或 varchar 数据可以是单个字符，或者是最长可达 8,000 个字符的字符串。

每个 char 和 varchar 数据值都有排序规则。排序规则定义特性，如用于表示每个字符的位模式、比较规则以及是否区分大小写或重音。每个数据库有默认排序规则。当定义列或指定常量时，除非使用 COLLATE 子句指派特定的排序规则，否则将为它们指派数据库的默认排序规则。当合并或比较两个具有不同排序规则的 char 或 varchar 值时，排序规则的优先顺序规则决定操作所使用的排序规则。

字符常量必须包括在单引号 (') 或双引号 (") 中。建议用单引号括住字符常量。当 QUOTED IDENTIFIER 选项设为 ON 时，有时不允许用双引号括住字符常量。

这个 Transact-SQL 例子给一个字符变量赋值：

DECLARE @MyCharVar CHAR(25)
SET @MyCharVar = 'Ricardo Adocicados'

当使用单引号分隔一个包括嵌入单引号的字符常量时，用两个单引号表示嵌入单引号，例如：

SET @MyCharVar = 'O''Leary'

如果要存储的数据比允许的字符数多，则数据就会被截断。例如，如果某列被定义为 char(10) 并且值"This is a really long character string"被存储到该列中，则 Microsoft® SQL Server™ 将该字符串截断为"This is a"。

当指定了 NOT NULL 子句时，char 数据类型是固定长度数据类型。如果一个比列的长度小的值被插入到 char NOT NULL 列中，则值的右边以空格填补剩余的位置。例如，如果一个列被定义为 char(10) 并且要存储的数据是"music"，则 SQL Server 将这个数据存储为"music_____"，这里"_"表示空格。

当创建一个 char NULL 列时，如果 ANSI_PADDING 是ON，它的表现就如同一个 char NOT NULL列：值的右边按照列的大小进行填补。当创建一个 char NULL 列时，如果 ANSI_PADDING 是 OFF，它的表现就如同一个将 ANSI_PADDING 设置为 OFF 的 varchar 列：尾随空格将被截断。

varchar 是可变长度的数据类型。比列的长度小的值，不会按照列的长度在其右边填补。当创建列时，如果 ANSI_PADDING 选项设为 OFF，则所有存储在列中的字符值的尾随空格都将被截断。当创建列时，如果 ANSI_PADDING 设为 ON，则尾随空格不被截断。

CHAR 函数可以把一个整数转换为 ASCII 字符。当确定控制字符时（比如回车或换行），这是很有用的。在字符串中用 CHAR(13) 和 CHAR(10) 产生一个回车并生成一个新行。

PRINT 'First line.' + CHAR(13) + CHAR(10) + 'Second line.'

一个字符串的字节中位的存储格式是按照安装时所指定的 Microsoft SQL Server 代码页来解释的。一个 char 或 varchar 对象可以包含 SQL Server 代码页中的任何字符。有关更多信息，请参见排序规则。

DB-Library 应用程序和使用来自 SQL Server 6.5 版或更早版本的 SQL Server ODBC 驱动程序的应用程序最多仅支持 255 个字节的字符数据。如果这些应用程序尝试检索 SQL Server 7.0 版或更新版本的字符参数或包含超过 255 个字节的数据的结果集，字符数据将在 255 个字节处截断。

请参见

字符数据

排序规则

排序规则的优先顺序

数据类型

SET ANSI_PADDING

sp_dbcmptlevel


使用日期和时间数据

Microsoft® SQL Server™ 2000 使用 datetime 和 smalldatetime 数据类型存储日期和时间数据。

没有只存储时间或日期的独立的时间和日期数据类型。当设置 datetime 或 smalldatetime 值时，如果只指定时间，则日期默认为 1900 年 1 月 1 日。如果只指定日期，则时间默认为 12:00 AM（午夜）。

对于 datetime 数据，可以执行如下操作：

  • 键入新的数据或更改现有的数据。

  • 进行日期和时间的计算，比如日期的加或减。有关日期算法的更多信息，请参见 +（加）和 -（减）。

  • 搜索特定的日期和/或时间。

可以使用系统日期函数进行一些关于 datetime 数据的算术运算。有关更多信息，请参见函数。

下面是一些使用日期和时间数据的准则：

  • 若要对日期和时间进行精确搜索匹配，请使用等号（=）。Microsoft SQL Server 以 12:00:00:000 AM（默认值）的精确时间形式返回年、月、日都完全匹配的日期和时间值。

  • 若要搜寻日期或时间的一部分，请使用 LIKE 运算符。SQL Server 首先把数据转换为 datetime 格式然后再转换为 varchar 格式。由于标准时间显示格式不包括秒和毫秒，不能使用 LIKE 和匹配模式对其搜索，除非使用 CONVERT 函数并把 style 参数设为 9 或 109。有关部分日期和时间搜索的更多信息，请参见 LIKE。

  • SQL Server 在运行时计算 datetime 常量。用于某种语言期望的日期格式的日期字符串，如果由使用不同语言和日期格式设置的连接来执行查询，则可能无法识别。例如，下面的视图对于语言设置为美国英语的连接可以工作正常，但对于其它语言设置的连接就不能工作正常：
    CREATE VIEW USA_Dates AS
SELECT *
FROM Northwind.dbo.Orders
WHERE OrderDate < 'May 1, 1997'

    当在查询中使用 datetime 常量，而且该查询由使用不同语言设置的连接执行时，需确保日期对于所有语言设置均是可以接受的。对于国际数据库中永久对象中的 datetime 常量，比如表约束和查看 WHERE 子句，必须同样小心。有关所有语言设置都解释为相同的日期格式的更多信息，请参见编写国际 Transact-SQL 语句。

SQL Server 可以识别以下列格式括在单引号 (') 中的日期和时间：

  • 字母日期格式（例如，'April 15, 1998'）

  • 数字日期格式（例如，'4/15/1998'、'April 15，1998'）

  • 未分隔的字符串格式（例如'19981207'、'December 12， 1998'）


字母日期格式

Microsoft® SQL Server™ 2000 允许使用以当前语言给出的月的全名（如 April）或月的缩写（如 Apr）来指定日期数据；逗号是可选的，而且忽略大小写。

以下是使用字母日期格式的一些指导：

  • 把日期和时间数据括在单引号中 (')。

  • 下面是 SQL Server 日期数据的合法字母格式（括号内的字符是可选字符）：
    Apr[il] [15][,] 1996
Apr[il] 15[,] [19]96
Apr[il] 1996 [15]

[15] Apr[il][,] 1996
15 Apr[il][,][19]96
15 [19]96 apr[il]
[15] 1996 apr[il]

1996 APR[IL] [15]
1996 [15] APR[IL]
  • 如果只指定年份的最后两位数字，则小于 two digit year cutoff 配置选项值最后两位数字的值与缩略形式的年份位于同一个世纪。大于或等于该选项值的值位于缩略形式年份的上一世纪。例如，如果 two digit year cutoff 为 2050（默认），则 25 被解释为 2025，50 被解释为 1950。为避免模糊，请使用四位数字的年份。

  • 如果没有指定日，则默认值为当月第一天。

  • 当按字母形式指定月时，SET DATEFORMAT 会话设置不起作用。

请参见

日期和时间数据

datetime 和 smalldatetime

LIKE

SET DATEFORMAT


数字日期格式

Microsoft® SQL Server™ 2000 允许用指定的数字月份指定日期数据。例如，5/20/97 表示 1997 年 5 月的第 20 天，当使用数字日期格式时，在字符串中以斜杠（/）、连字符（-）或句号（.）作为分隔符来指定月、日、年。字符串必须以下面的形式出现：

数字 分隔符 数字 分隔符 数字 [时间] [时间]

下面的数字日期格式是有效的：

[0]4/15/[19]96 -- (mdy)
[0]4-15-[19]96 -- (mdy)
[0]4.15.[19]96 -- (mdy)
[04]/[19]96/15 -- (myd)

15/[0]4/[19]96 -- (dmy)
15/[19]96/[0]4 -- (dym)
[19]96/15/[0]4 -- (ydm)
[19]96/[04]/15 -- (ymd)

当语言被设置为 us_english 时，默认的日期顺序是 mdy。可以使用 SET DATEFORMAT 语句改变日期的顺序，根据所用的语言，它也会影响日期顺序。

对 SET DATEFORMAT 的设置决定了如何解释日期数据。如果顺序和设置不匹配，则该值不会被解释为日期（因为它们超出了范围），或者被错误地解释。例如，根据不同的 DATEFORMAT 设置，12/10/08 能被解释为六种日期的一种。

请参见

日期和时间数据

datetime 和 smalldatetime

SET DATEFORMAT


未分隔字符串格式

Microsoft® SQL Server™ 2000 允许将日期数据指定为未分隔字符串。日期数据能够用四、六或八个数字，空的字符串或没有日期值的时间值来指定。

SET DATEFORMAT 会话设置并不适用于全数字日期项目（无分隔符的数字项目）。六或八个数字的字符串总是被解释为 ymd。月和日必须是两个数字。

以下是有效的未分隔字符串格式：

[19]960415

只有四个数字的字符串被解释为年。月和日被设为 1 月 1 日。当只指定四个数字时，必须包括世纪。

请参见

日期和时间数据

datetime 和 smalldatetime

SET DATEFORMAT


时间格式

Microsoft® SQL Server™ 2000 可识别以下时间数据格式。用单引号（'）把每一种格式括起来。

14:30
14:30[:20:999]
14:30[:20.9]
4am
4 PM
[0]4[:30:20:500]AM

可以用一个 AM 或 PM 后缀来表明时间值是在中午 12 点之前还是之后。AM 或 PM 的大小写可忽略。

小时可以用 12 小时或 24 小时的时钟来指定。小时值解释如下：

  • 小时值 0 表示午夜 (AM) 后的小时，不论是否指定 AM。当小时值等于 0 时不能指定 PM。

  • 如果未指定 AM 和 PM，小时值 1 到 11 表示中午以前的小时。当指定 AM 时，也表示中午以前的小时。当指定 PM 时，则表示中午以后的小时。

  • 如果未指定 AM 和 PM，小时值 12 表示始于中午的小时。如果指定为 AM，则表示始于午夜的小时。如果指定为 PM，则表示始于中午的小时。例如：12:01 是指中午过后 1 分钟，即 12:01 PM，而 12:01 AM 是指午夜过后 1 分钟。指定为 12:01 AM 与指定为 00:01 或 00:01 AM 相同。

  • 如果指定 AM 或 PM，小时值 13 到 23 表示中午以后的小时。当指定 PM 时，也表示中午以后的小时。当小时值为 13 到 23 时，不能指定为 AM。

  • 小时值 24 无效，用 12:00 AM 或 00:00 表示午夜。

可以在毫秒之前加上冒号 (:) 或者句号 (.) 。如果前面加冒号，这个数字表示千分之一秒。如果前面加句号，单个数字表示十分之一秒，两个数字表示百分之一秒，三个数字表示千分之一秒。例如，12:30:20:1 表示 12:30 过了 20 又千分之一秒；12:30:20.1 表示 12:30 过了 20 又十分之一秒。

请参见

日期和时间数据

datetime 和 smalldatetime


ODBC 日期时间格式

ODBC API 定义了转义序列来表示日期和时间值，ODBC 称之为时间戳数据。用于 SQL Server 的 Microsoft OLE DB 提供程序所支持的 OLE DB 语言定义 (DBGUID-SQL) 也支持这种 ODBC 时间戳格式。使用 ADO、OLE DB 和基于 ODBC 的 API 的应用程序可以使用这种 ODBC 时间戳格式来表示日期和时间。

ODBC 时间戳的转义序列格式为：

{ literal_type 'constant_value' }

literal_type

指定转义序列的类型。时间戳有三种 literal_type 指定符：

d = 仅日期

t = 仅时间

ts = 时间戳（时间 + 日期）

'constant_value'

是转义序列的值。constant_value 的每个 literal_type 必须遵循这些格式。

literal_type | constant_value 格式
D | yyyy-mm-dd
T | hh:mm:ss[.fff]
Ts | yyyy-mm-dd hh:mm:ss[.fff]

这些是 ODBC 时间和日期常量的例子：

{ ts '1998-05-02 01:23:56.123' }

{ d '1990-10-02' }

{ t '13:33:41'}

不要混淆 ODBC 和 OLE DB 时间戳数据类型名称与 Transact-SQL timestamp 数据类型名称。ODBC 和 OLE DB 时间戳数据类型记录日期和时间。Transact-SQL timestamp 数据类型是一个与时间无关的二进制数据类型。


使用整型数据

整型是完整的数字并不包含小数或分数。

Microsoft® SQL Server™ 2000 具有以下大小的整型数据类型：

  • bigint

    长度为 8 个字节，存储从 –2^63 (-9,223,372,036,854,775,808) 到 2^63-1 (9,223,372,036,854,775,807) 的数字。

  • integer 或 int

    长度为 4 个字节，存储从 -2.147.483.648 到 2.147.483.647 的数字。

  • smallint

    长度为 2 个字节，存储从 –32,768 到 32,767 的数字。

  • tinyint

    长度为 1 个字节，存储从 0 到 255 的数字。

整型对象和表达式可用于任何数学操作。任何由这些操作生成的分数都将被舍去，而不是四舍五入。例如，SELECT 5/3 的返回值为 1，而不是四舍五入后的 2。

整型数据类型是可与 IDENTITY 属性一起使用的唯一类型，该属性是一种可以自动增加的数字。IDENTITY 属性通常用于自动生成唯一标识数字或主键。

整型数据与字符、日期和时间数据不同，它不需要被包含在单引号内。

请参见

CAST 和 CONVERT

int、bigint、smallint 和 tinyint

数字数据


使用 bigint 数据

整型值超过 int 数据类型支持的范围时，将使用 bigint 数据类型。为了实现兼容性，int 数据类型仍保留为 Microsoft® SQL Server™ 中的主要整型数据类型。

除非明确说明，否则，那些接受 int 表达式作为其参数的函数、语句和系统存储过程，都没有改变为支持将 bigint 表达式隐性转换为这些参数。这样，只有当 bigint 值在 int 数据类型所支持的范围内时，SQL Server 才将 bigint 隐性转换为 int。如果 bigint 表达式包含了一个在 int 数据类型支持范围之外的值，就会在运行时出现转换错误。

Transact-SQL 函数中的 bigint

虽然 SQL Server 有时会将 tinyint 或 smallint 值提升为 int 数据类型，但是它不会自动将 tinyint、smallint 或 int 提升为 bigint。例如，如果参数表达式的数据类型是 tinyint 或 smallint，某些聚合函数会把返回值的数据类型升级为 int。而这些聚合函数将不会返回 bigint，除非参数表达式本身就是 bigint 类型。

当指定 bigint 参数并且返回值也是 bigint 类型时，可以使用下列 Transact-SQL 函数。

ABS | FLOOR | POWER
AVG | IDENTITY | RADIANS
CEILING | MAX | ROUND
COALESCE | MIN | SIGN
DEGREES | NULLIF | SUM

当引用 bigint 列或变量，但不希望返回值也为 bigint 数据类型时，可以使用下列函数。

IDENTITY | ISNULL | VARP
COL_LENGTH | ISNUMERIC |
DATALENGTH | STDEV[P] |

SQL Server 提供下列专用于 bigint 值的函数。

COUNT_BIG

当对组中的项目计数时，如果值超过 int 数据类型支持的范围，并且返回 bigint，则使用此函数。除了返回类型外，COUNT_BIG 类似于 COUNT 函数。

ROWCOUNT_BIG

当对执行的最后一条语句所影响的行数进行计数，而且值超过 int 数据类型支持范围时，使用此函数。除了 ROWCOUNT_BIG 返回 bigint 数据类型外，此函数类似于 ROWCOUNT 函数。

其它 Transact-SQL 元素中的 bigint

CAST 和 CONVERT 子句支持 bigint。这些子句对 bigint 使用与其它整型数据类型相同的转换规则。在数据类型优先表中，bigint 数据类型位于 int 之上和 smallmoney 之下。有关 bigint 转换的更多信息，请参见 CAST 和 CONVERT 。

在使用 CASE 表达式时，如果 result_expression 或可选的 else_result_expression 的值是 bigint，将得到 bigint 类型的结果。

可以在 Transact-SQL 语句中指定了整型数据的所有语法位置使用 bigint 数据类型：

  • ALTER PROCEDURE

  • ALTER TABLE

  • CREATE PROCEDURE

  • CREATE TABLE

  • DECLARE 变量

此外，SQL Server 目录组件报告有关 bigint 列的信息。

指定 bigint 常量

在 int 数据类型支持的范围之外的整数常量仍被解释为 numeric，这些数字的小数位数为 0，其精度为足以保存所指定的值。例如，常量 3000000000 被解释为 numeric。这些 numeric 常量被隐性转换为 bigint，并可对 bigint 列和变量赋值：

CREATE TABLE BigintTable (ColA bigint)

INSERT INTO BigintTable VALUES (3000000000)

SELECT *
FROM BigintTable
WHERE ColA = 3000000000

还可以将常量转换为 bigint：

CAST(3000000000 AS bigint)

若要将 bigint 值放入 sql_variant 列中，请使用此方法：

CREATE TABLE VariantTable (ColA sql_variant)

-- Inserts a value with a numeric base data type.
INSERT INTO VariantTable VALUES (3000000000)
-- Inserts a value with a bigint base data type.
INSERT INTO VariantTable VALUES ( CAST(3000000000 AS bigint) )

请参见

CASE

CAST 和 CONVERT

COUNT_BIG

int、bigint、smallint 和 tinyint

数字数据

sql_variant


使用 decimal、float 和 real 数据

decimal 数据类型最多可存储 38 个数字，所有数字都能够放到小数点的右边。decimal 数据类型存储了一个确定的数字表达法；没有存储值的近似值。

定义 decimal 的列、变量和参数的两种特性如下：

  • p

    指定精度或对象能够控制的数字个数。

  • s

    指定可放到小数点右边的小数位数或数字个数。

    p 和 s 必须遵守以下规则：0 <= s <= p <= 38。

numeric 和 decimal 数据类型的默认最大精度值是 38。在 Transact-SQL 中，numeric 与 decimal 数据类型在功能上等效。

当数据值一定要按照指定精确存储时，可以用带有小数的 decimal 数据类型来存储数字。

有关数学运算如何影响结果的精度和小数位数的信息，请参见精度、小数位数和长度。

使用 float 和 real 数据

float 和 real 数据类型被称为近似的数据类型。在近似数字数据类型方面，float 和 real 数据的使用遵循 IEEE 754 标准。

近似数字数据类型并不存储为多数数字指定的精确值，它们只储存这些值的最近似值。在很多应用程序中，指定值与存储值之间的微小差异并不明显。但有时这些差异也值得引起注意。由于 float 和 real 数据类型的这种近似性，当要求精确的数字状态时，比如在财务应用程序中，在那些需要舍入的操作中，或在等值核对的操作中，就不使用这些数据类型。这时就要用 integer、decimal、money 或 smallmone 数据类型。

在 WHERE 子句搜索条件中（特别是 = 和 <> 运算符），应避免使用 float 或 real 列。最好限制使用 float 和 real 列做 > 或 < 的比较。

IEEE 754 规格提供了四种舍入模式：舍入到最接近的值、上舍入、下舍入和舍入到零。Microsoft® SQL Server™ 使用上舍入。所有的数值必须精确到确定的精度，但会产生细小的浮点值变化。因为浮点数字的二进制表示法可以采用很多合法舍入规则中的任意一条，因此我们不可能可靠地量化一个浮点值。

请参见

数据类型

数字数据


使用货币数据

Microsoft® SQL Server™ 使用两种数据类型存储货币数据（货币值）：money 和 smallmoney。这些数据类型可以使用以下货币符号中的任意一种。

货币数据不需要用单引号 (') 括起来。但是，货币数值之前必须带有适当的货币符号。例如，若要指定 100 英镑，请使用 £100。

money 和 smallmoney 被限制到小数点后 4 位。如果要求小数点后有更多位，请使用 decimal 数据类型。

用句点将局部的货币单位（如分）从总体货币单位中分隔出来。例如，2.15 表示 2 元 15 分。

尽管这些数据类型的显示形式中包括逗号分隔符，但是逗号分隔符不能用在 money 或 smallmoney 常量中。只能在显式转换为 money 或 smallmoney 的字符串中指定逗号分隔符，比如：

USE Northwind
GO
CREATE TABLE TestMoney (cola INT PRIMARY KEY, colb MONEY)
GO
SET NOCOUNT ON
GO

-- The following three INSERT statements work.
INSERT INTO TestMoney VALUES (1, $123.45)
GO
INSERT INTO TestMoney VALUES (2, $123123.45)
GO
INSERT INTO TestMoney VALUES (3, CAST('$444,123.45' AS MONEY) )
GO

-- This INSERT statement gets an error because of the comma
-- separator in the money string.
INSERT INTO TestMoney VALUES (3, $555,123.45)
GO
SET NOCOUNT OFF
GO
SELECT * FROM TestMoney
GO

请参见

数据类型

货币数据

money 和 smallmoney


使用 Text 和 Image 数据

Microsoft® SQL Server™ 2000 将大于 8,000 个字符的字符串和大于 8,000 个字节的二进制数据存储为称作 text 和 image 的特殊数据类型。大于 4000 个字符的 Unicode 字符串被存储在 ntext 数据类型中。

例如，必须将一个有关用户信息的大文本文件 (.txt) 导入到 SQL Server 数据库中。应将这些数据作为一个数据块存储起来，而不是集成到数据表的多个列中。为了达到这个目的，可以创建一个 text 数据类型的列。然而，如果必须存储当前作为标记图象文件格式 (TIFF) 图像 (.tif) 存储的、每个大小为 10 KB 的公司徽标，则需创建一个 image 数据类型的列。

如果要存储的文本数据是 unicode 形式，那么应使用 ntext 数据类型。例如，一个为国际客户创建的套用信函很可能包含用于各种不同语言的国际拼写和字符。这种数据应存储到一个 ntext 列中。

每个 text 和 ntext 数据值都有排序规则。排序规则定义特性，如比较规则以及是否区分大小写或重音。text 值的排序规则还指定代码页，后者定义用于表示每个字符的位模式。每个 ntext 值使用对所有排序规则都相同的 Unicode 代码页。每个数据库有默认排序规则。当创建 text 或 ntext 列时，除非使用 COLLATE 子句指派特定的排序规则，否则将为它们指派数据库的默认排序规则。当合并或比较两个具有不同排序规则的 text 或 ntext 值时，排序规则的优先顺序规则决定操作所使用的排序规则。

image 数据中的数据被存储为位串，SQL Server 不对它进行解释。image 列数据的解释必须由应用程序完成。例如，应用程序可以使用 BMP、TIFF、GIF 或 JPEG 格式把数据存储在 image 列中。读取 image 列的数据的应用程序必须识别该数据格式并正确显示数据。image 列所做的全部工作就是提供一个位置，用来存储组成图像数据值的位流。

使用行中的文本存储 text、ntext 和 image 值

通常情况下，text、ntext 或 image 字符串是存储在数据行外的大型（可达 2GB）字符或二进制字符串。数据行只包括一个 16 字节的文本指针，该指针指向一个树的根节点，该树由映射存储串片段的页的内部指针构成。

使用 Microsoft SQL Server，可将小型到中型的 text、ntext 和 image 值存储在一个数据行中，从而提高访问这些值的查询速度。

当 text、ntext 或 image 串被存储在数据行时，SQL Server 就无须访问单独的页或页的集合来读写这些串。这使得读写 text、ntext 或 image 这些行中的串就象读写 varchar、nvarchar 或 varbinary 串一样快。

为了在数据行中存储 text、ntext 或 image 字符串，必须使用 sp_tableoption 存储过程启动 text in row 选项。

sp_tableoption N'MyTable', 'text in row', 'ON'

可以为在数据行中存储的 text、ntext 和 image 字符串指定一个从 24 到 7000 字节的最大长度（可选）：

sp_tableoption N'MyTable', 'text in row', '1000'

如果指定的是"ON"而不是一个特定的限度，则此限度的默认值为 256 字节。这个默认值提供大部分的性能收益：这个值足够大，可以保证小型字符串和根文本指针存储在行中；但又不是过大，以致将每页的行减少得太多，从而影响了性能。

尽管在通常情况下，不应将此值设为低于 72，但也不应把此值设得过高，特别是对那些大多数语句都不引用 text、ntext 和 image 列的表格或者具有多个 text、ntext 和 image 列的表格。

也可以使用 sp_tableoption 关闭此选项，这可以通过指定"OFF" 或 0 选项值来实现。

sp_tableoption N'MyTable', 'text in row', 'OFF'

请参见

字符数据

数据类型

sp_tableoption


使用 uniqueidentifier 数据

uniqueidentifier 数据类型存储 16 字节的二进制值，该值的使用与全局唯一标识符 (GUID) 一样。GUID 是一个唯一的二进制数字；世界上的任何两台计算机都不会生成重复的 GUID 值。GUID 主要用于在拥有多个节点、多台计算机的网络中，分配必须具有唯一性的标识符。

uniqueidentifier 列的 GUID 值通常由以下方式获得：

  • 在 Transact-SQL 语句、批处理或脚本中调用 NEWID 函数。

  • 在应用程序代码中，调用返回 GUID 值的应用程序 API 函数或方法。

Transact-SQL NEWID 函数以及应用程序 API 函数和方法从它们网卡上的标识数字以及 CPU 时钟的唯一数字生成新的 uniqueidentifier 值。每个网卡都有唯一的标识号。由 NEWID 返回的 uniqueidentifier 使用服务器上的网卡生成。由应用程序 API 函数和方法返回的 uniqueidentifier 使用客户机上的网卡生成。

一般不将 uniqueidentifier 定义为常量，因为很难保证实际创建的 uniqueidentifier 具有唯一性。指定 uniqueidentifier 常量的方法有两种：

  • 字符串格式
    '6F9619FF-8B86-D011-B42D-00C04FC964FF'
  • 二进制格式
    0xff19966f868b11d0b42d00c04fc964ff

uniqueidentifier 数据类型不象IDENTITY 属性那样为新插入的行自动生成新的ID。为了得到新的 uniqueidentifier 值，表必须具有一个指定 NEWID 函数的 DEFAULT 子句，或使用 NEWID 函数的 INSERT 语句：

CREATE TABLE MyUniqueTable
   (UniqueColumn   UNIQUEIDENTIFIER      DEFAULT NEWID(),
   Characters      VARCHAR(10) )
GO
INSERT INTO MyUniqueTable(Characters) VALUES ('abc')
INSERT INTO MyUniqueTable VALUES (NEWID(), 'def')
GO

uniqueidentifier 列可以包含多次出现的 uniqueidentifier 值，除非也对此列指定了 UNIQUE 或 PRIMARY KEY 约束。当有多行引用源表中的同一主键时，引用其它表的 uniqueidentifier 主键的外键列将包含多次出现的个别 uniqueidentifier 值。

一个表可以有多个 uniqueidentifier 列。每个表中可以指定一个具有 ROWGUIDCOL 属性的 uniqueidentifier 列。ROWGUIDCOL 属性表明此列的 uniqueidentifier 值唯一地标识表中的行。但是，该属性并没有执行该唯一性。唯一性必须通过其它机制来执行，比如为列指定 PRIMARY KEY 约束。ROWGUIDCOL 属性主要用于 SQL Server 复制。

uniqueidentifier 数据类型的主要优点是保证由 Transact-SQL NEWID 函数或应用程序 GUID 函数生成的值在全球是唯一的。

uniqueidentifier 数据类型的具有几个缺点：

  • 值长且难懂。这使用户难以正确键入它们，并且更难记住。

  • 这些值是随机的，而且它们不能接受任何使它们对用户变得更有意义的模式。

  • 没有任何方式可以决定生成 uniqueidentifier 值的顺序。它们不适用于那些依赖递增的键值的现有应用程序。

  • uniqueidentifier 数据类型具有 16 个字节，与其它那些诸如 4 字节的整数相比要相对大一些。这意味着使用 uniqueidentifier 键建立的索引可能会比使用 int 键实现的索引相对慢一些。

如果全局唯一性并不是必须的，或者需要一个连续递增的键，则可以考虑使用 IDENTITY 属性。

请参见

数据类型

uniqueidentifier


使用特殊数据

特殊数据类型是指那些不适合其它数据类型分类的数据类型。例如，若要将客户调查中"是"或"否"的值相应地用 1 或 0 进行存储，则可使用 bit 数据类型。Microsoft® SQL Server™ 2000 在该分类中有几个数据类型：

  • bit

    bit 数据不必放在单引号中。它是与 SQL Server 的整型和数字数据相似的数字数据，但 bit 列只能存储 0 和 1。

  • sql_variant

    SQL Server 中的 sql_variant 数据类型允许单个列、参数或变量存储不同数据类型的数据值。sql_variant 列的每个实例都记录数据值和描述该值的元数据：该值的基本数据类型、最大大小、小数位数、精度和排序规则。

    下例中的第二个表包含一个 sql_variant 列：

    CREATE TABLE ObjectTable
    (ObjectID            int
                            CONSTRAINT PKObjectTable PRIMARY KEY,
    ObjectName            nvarchar(80),
    ObjectWeight        decimal(10,3),
    ObjectColor        nvarchar(20)
    )

CREATE TABLE VariablePropertyTable
    (ObjectID            int REFERENCES ObjectTable(ObjectID),
    PropertyName        nvarchar(100),
    PropertyValue        sql_variant,
    CONSTRAINT PKVariablePropertyTable
                    PRIMARY KEY(ObjectID, PropertyName)
    )

    若要获得任何一个特定 sql_variant 实例的元数据信息，请使用 SQL_VARIANT_PROPERTY 函数。

  • table

    table 数据类型类似是一个临时表，可以用于存储一个结果集以便以后处理。这种数据类型只能用于定义 table 类型的局部变量和用户定义函数的返回值。

    一个 table 变量或返回值的定义包括列、数据类型、精度、每列的小数位数以及可选的 PRIMARY KEY、UNIQUE 和CHECK 约束。

    存储在 table 变量中或用户定义函数返回值中的行的格式，必须在声明变量或创建函数时定义。其语法基于 CREATE TABLE 的语法。例如：

    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

    返回一个 table 的 table 变量和用户定义函数只能用于某些 SELECT 和 INSERT 语句，而且其中 UPDATE、DELETE 和 DECLARE CURSOR 语句支持表。返回 table 的 table 变量和用户定义函数不能用于任何其它 Transact-SQL 语句。

    在该表中使用的索引或其它约束必须被定义为 DECLARE variable 或 CREATE FUNCTION 语句的一部分。不得过后再使用它们，因为 CREATE INDEX 或 ALTER TABLE 语句不能引用表变量和用户定义的函数。

    有关用于定义 table 变量和用户定义函数的语法的更多信息，请参见 DECLARE @local_variable (T-SQL) 和 CREATE FUNCTION (T-SQL)。

  • timestamp

    SQL Server timestamp 数据类型与时间和日期无关。SQL Server timestamp 是二进制数字，它表明数据库中数据修改发生的相对顺序。实现 timestamp 数据类型最初是为了支持 SQL Server 恢复算法。每次修改页时，都会使用当前的 @@DBTS 值对其做一次标记，然后 @@DBTS 加1。这样做足以帮助恢复过程确定页修改的相对次序，但是 timestamp 值与时间没有任何关系。

    在 SQL Server 7.0 版和 SQL Server 2000 中，@@DBTS 只在 timestamp 列中使用时其值才会递增。如果一个表包含 timestamp 列，则每次由 INSERT、UPDATE 或DELETE 语句修改一行时，此行的 timestamp 值就被置为当前的 @@DBTS 值，然后 @@DBTS 加1。

    因为每一次修改时 timestamp 值都会改变，所以千万不要在键（特别是主键）中使用 timestamp 列。

    若要记录表中发生的数据修改次数，可用 datetime 或 smalldatetime 数据类型来记录这些事件和触发器，这样，当发生修改时，就可以自动更新其值。

  • 用户定义的数据类型

    用户定义数据类型使您得以使用针对特定用途定制的描述性名称和格式，来扩展 SQL Server 的基本数据类型（如 varchar）。例如，下述语句实现了一个 birthday 用户定义数据类型，它允许 NULL，并使用了 datetime 基本数据类型：

    EXEC sp_addtype birthday, datetime, 'NULL'

    当选取实现用户定义数据类型的基本数据类型时，一定要小心。例如，在美国，社会保险号的形式为 nnn-nn-nnnn。虽然社会保险号包含数字，但是这些数字只是用来构成一个标识符，而不能用于数学运算。因此通常的做法是：先将用户定义社会保险号数据类型创建为 varchar 数据类型，然后创建一个 CHECK 约束来执行存储在表中的社会保险号格式。

    EXEC sp_addtype SSN, 'VARCHAR(11)', 'NOT NULL'
GO
CREATE TABLE ShowSSNUsage
    (EmployeeID        INT PRIMARY KEY,
    EmployeeSSN        SSN,
    CONSTRAINT CheckSSN CHECK ( EmployeeSSN LIKE
        '[0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9][0-9][0-9]' )
    )
GO

    若通常将 SSN 列用作索引，特别使聚集索引中的键，如果 SSN 用户定义数据类型是使用 int 数据类型实现的，则键的大小可以从 11 个字节缩减到 4 个字节。这种键的大小的缩减能改善数据检索。提高数据检索效率以及避免要求 CHECK 约束，这一点通常比显示或修改 SSN 值时进行从 int 到字符格式的额外的转换处理更为重要。

请参见

CREATE FUNCTION

数据类型

DECLARE @local_variable

特殊数据

SQL_VARIANT_PROPERTY

table


使用 sql_variant 数据

sql_variant 数据类型的运行过程与 Microsoft® Visual Basic® 中的 variant 数据类型相似。它允许单一的列、参数或变量存储不同数据类型的数据值。例如，一个单一的 sql_variant 列能保存 int、decimal、char、binary 和 nchar 值。sql_variant 列的每个实例记录数据值和元数据信息，其中包括基本数据类型、最大大小、小数位数、精度和排序规则。

sql_variant 数据类型遵循以下规则：

  1. 常规赋值
    • sql_variant 对象能够保存除 text、ntext、image 和 timestamp 以外的任何 SQL Server 数据类型的数据。sql_variant 数据的实例也不能用 sql_variant 作为其基本数据类型。

    • 所有类型的常量均可在引用 sql_variant 列的谓词或赋值中指定。

    • 如果 sql_variant 的值是 NULL，则不认为它具有基础的基本数据类型。即使空值来自具有特殊数据类型的变量或列，这项规则也适用。

      在这个例子中，VariantCol 的值被设为无关联数据类型，即使空值来自一个 int 变量。

      DECLARE @IntVar int
SET @IntVar = NULL
UPDATE SomeTable SET VariantCol = @IntVar WHERE PriKey = 123
    • 在将一个 sql_variant 对象赋值到另外一种数据类型时，sql_variant 值必须明确地转换为目标数据类型。当 sql_variant 的值被赋给具有其它数据类型的对象时，不支持任何隐性转换。

    • 为了与其它数据类型兼容，报告 sql_variant 对象长度的目录对象（列如 DATALENGTH 函数）将报告数据的长度。不返回包含在 sql_variant 对象中的元数据长度。

    • sql_variant 列始终与 ANSI_PADDING ON 配合使用。如果 char、nchar、varchar、nvarchar 或 varbinary 值从具有 ANSI_PADDING OFF 的源赋值，则该值将不被填充。
  2. 表中的 sql_variant
    • 只要键列中的数据长度不超过 900 字节，在索引和唯一键中就可以使用 sql_variant 列。

    • sql_variant 列不支持 IDENTITY 属性，但是 sql_variant 列允许作为主键和外键的一部分。

    • 在计算列中不能用 sql_variant 列。

    • 使用 ALTER TABLE 将除了 text、ntext、image、timestamp 或 sql_variant 之外的任何数据类型的列更改为 sql_variant。所有现有的值都被转换成 sql_variant 值，这些值的基本数据类型与执行 ALTER TABLE 语句之前列的数据类型相同。由于不存在支持从 sql_variant 到其它数据类型的隐性转换，因此不能使用 ALTER TABLE 将 sql_variant 列的数据类型更改为任何其它数据类型。
  3. 排序规则
    • COLLATE 子句不能用于将列排序规则指派给 sql_variant 列。sql_variant 列中基于字符的值（char、nchar、varchar 和 nvarchar）可以具有任何排序规则，而且单个 sql_variant 列可以包含具有混合排序规则的基于字符的值。

    • 当将一个值赋给 sql_variant 实例时，会将源的数据值和基本数据类型都赋给它。如果源值有排序规则，则排序规则也会赋给它。如果源值具有用户定义数据类型，则会把用户定义数据类型的基本数据类型而不是用户定义数据类型赋给它。sql_variant 实例不继承任何用户定义数据类型的规则和默认值。如果具有 IDENTITY 属性的列中的值被赋给 sql_variant 实例，则 sql_variant 将继承源列的基本数据类型，而不继承 IDENTITY 属性。将 text、ntext 或 image 值赋给 sql_variant 实例是错误的。将具有其它数据类型的对象值赋给 sql_variant 对象时，支持隐性转换。
sql_variant 比较

sql_variant 列可以包含具有几种基本数据类型和排序规则的值，所以当比较 sql_variant 操作数时，要应用一些特殊规则。这些规则适用于各种比较操作，例如：

  • Transact-SQL 比较运算符

  • ORDER BY、GROUP BY

  • 索引

  • MAX 和 MIN 聚合函数

  • UNION（不包括 ALL）

  • CASE 表达式

对于 sql_variant 比较，SQL Server 数据类型层次结构顺序被分组为数据类型系列（ sql_variant 系列有最高的系列优先顺序）。

数据类型层次结构 | 数据类型系列
sql_variant | sql_variant
datetime | Datetime
smalldatetime | Datetime
float | 近似数
real | 近似数
decimal | 精确数
money | 精确数
smallmoney | 精确数
bigint | 精确数
int | 精确数
smallint | 精确数
tinyint | 精确数
bit | 精确数
nvarchar | Unicode
nchar | Unicode
varchar | Unicode
char | Unicode
varbinary | Binary
binary | Binary
uniqueidentifier | Uniqueidentifier

适用于 sql_variant 比较的规则如下：

  • 当具有不同基本数据类型的 sql_variant 值进行比较，而且基本数据类型属于不同的数据类型系列时，在层次结构图中数据类型系列较高的值，被认为在二者中值较大。

  • 当具有不同基本数据类型的 sql_variant 值进行比较，而且基本数据类型属于相同的数据类型系列，在层次结构图中基本数据类型比较低的值先隐性转换成其它数据类型，然后再进行比较。

  • 当比较具有 char、varchar、nchar 或 varchar 数据类型的 sql_variant 值时，将基于以下准则对它们进行比较：LCID、LCID 版本、比较标志和排序 ID。这些准则中的每一个都作为整型值并按列出的顺序进行比较。

在 sql_variant 值之间用这些规则进行比较，与用同样的规则在具有相同的基本数据类型的值之间进行比较相比，会产生不同的结果。

操作数 A | 操作数 B | Non-variant 比较结果 | sql_variant 比较结果
'123' char | 111t | A > B | B > A
50000 int | 5E1 float | A > B | B > A

因为不同数据类型系列的值在比较谓词中引用前必须显式转换，只有当排序 sql_variant 列上的结果集时才能看出这些规则的效果。这个表中的值是数据类型优先顺序的例子。

PriKey | VariantCol
1 | 50.0（基本类型 float）
2 | 5000（基本类型 int）
3 | '124000'（基本类型 char(6)）

这是 SELECT * FROM VariantTest ORDER BY VariantCol ASC 语句的结果。

PriKey | VariantCol
3 | '124000'（基本类型 char(6)）
2 | 5000（基本类型 int）
1 | 50.0（基本类型 float）

这个表中的值是使用不同的排序规则时有关排序规则优先顺序的例子。

IntKey | VariantCol
1 | qrs (varchar SQL_Latin1_General_Pref_Cp1_CI_AS)
2 | abc (varchar SQL_Latin1_General_Pref_Cp1_CI_AS)
3 | qrs (varchar SQL_Latin1_General_CP1_CS_AS)
4 | 17.5 (decimal)
5 | abc (varchar SQL_Latin1_General_CP1_CS_AS)
6 | klm (varchar SQL_Latin1_General_CP1_CS_AS)
7 | 1,2 (decimal)

这是 SELECT * FROM CollateTest ORDER BY VariantCol 语句的结果。下表显示的是组合在一起的来自确定数字数据类型系列的值和在各自的排序规则中组合的 varchar 值。

IntKey | VariantCol
5 | abc (varchar SQL_Latin1_General_CP1_CS_AS)
6 | klm (varchar SQL_Latin1_General_CP1_CS_AS)
3 | qrs (varchar SQL_Latin1_General_CP1_CS_AS)
2 | abc (varchar SQL_Latin1_General_Pref_Cp1_CI_AS)
1 | qrs (varchar SQL_Latin1_General_Pref_Cp1_CI_AS)
7 | 1,2 (decimal)
4 | 17.5 (decimal)

函数和 sql_variant 数据

当指定 sql_variant 参数时，下面的 Transact-SQL 函数支持 sql_variant 参数并返回 sql_variant 值：

  • COALESCE

  • MAX

  • MIN

  • NULLIF

这些函数支持引用 sql_variant 列或变量并且不使用 sql_variant 作为它们返回值的数据类型。

COL_LENGTH | DATALENGTH | TYPEPROPERTY
COLUMNPROPERTY | ISNULL |

这些 Transact-SQL 函数不支持 sql_variant 参数。

AVG | RADIANS | STDEV[P]
IDENTITY | ROUND | SUM
ISNUMERIC | SIGN | VAR[P]
POWER |  |

CAST 和 CONVERT 函数支持 sql_variant。

新函数 SQL_VARIANT_PROPERTY()：用来得到关于 sql_variant 值的属性信息，例如数据类型、精度和小数位数。

其它 Transact-SQL 元素和 sql_variant 数据

在 LIKE 谓词中不支持sql_variant 列。

在全文本索引中不支持 sql_variant 列。它们不能在全文本函数（例如 CONTAINSTABLE 和 FREETEXTTABLE）中指定。

这些 Transact-SQL 语句支持在指定其它整型数据类型相同的语法位置上指定 sql_variant：

  • ALTER PROCEDURE

  • ALTER TABLE

  • CREATE PROCEDURE

  • CREATE TABLE

  • DECLARE variable

Microsoft® SQL Server™ 2000 目录组件报告有关 sql_variant 列的信息。

如果任何输入和结果表达式的值是 sql_variant，则 CASE 表达式的结果就是 sql_variant。结果的基础数据类型是运行时所求得的表达式的基础数据类型。

数字或字符串串联的运算符不能是 sql_variant：

-- Generates an error:
SELECT VariantCol + @CharacterVar
FROM MyTable

转换 sql_variant 操作数可以执行下面的操作：

-- Does not generates an error:
SELECT CAST(VariantCol AS varchar(25)) + @CharacterVar
FROM MyTable
应用程序和 sql_variant 数据

如果应用程序要求一个结果集，其中一个给定列返回具有单一的基本数据类型的 sql_variant 数据，则该应用程序可以在 Transact-SQL 语句中用 CAST 或 CONVERT 函数通过使用基本数据类型返回 sql_variant 数据。在这种情况下，应用程序处理数据的方式与它处理基本数据类型的结果的方式相同。本主题描述 Microsoft® SQL Server™ 如何返回尚未投影或未转换为特定基本数据类型的 sql_variant 数据。

用于 SQL Server 的 OLE DB 提供程序引入了一个提供程序专用的 OLE DB 类型 DBTYPE_SQLVARIANT，用于 sql_variant 列和参数。

SQL Server ODBC 驱动程序引入了一个提供程序专用的 ODBC 数据库数据类型 SQL_SS_VARIANT，用于 sql_variant 列和参数。

当与连接到下列接口的应用程序一起工作时，SQL Server 将 sql_variant 数据转换成 nvarchar(4000)：

  • 用于 SQL Server 7.0 版的 OLE DB 提供程序。

  • SQL Server 7.0 版的 SQL Server ODBC 驱动程序。

如果所得到的字符串超过 4000 个字符，则 SQL Server 返回前 4000 个字符。

当与连接到下列接口的应用程序一起工作时，SQL Server 将 sql_variant 值转换成 varchar(255)：

  • SQL Server 6.5 或更低版本提供的 SQL Server ODBC 驱动程序。

  • 任何版本的 DB-Library dll。

如果所得到的字符串超过 255 个字符，则 SQL Server 返回前 255 个字符。

请参见

CAST 和 CONVERT

sql_variant

SQL_VARIANT_PROPERTY


使用 Unicode 数据

Unicode 标准为全球商业领域中广泛使用的大部分字符定义了一个单一编码方案。所有的计算机都用单一的 Unicode 标准 Unicode 数据中的位模式一致地翻译成字符。这保证了同一个位模式在所有的计算机上总是转换成同一个字符。数据可以随意地从一个数据库或计算机传送到另一个数据库或计算机，而不用担心接收系统是否会错误地翻译位模式。

对于用一个字节编码每个字符的数据类型，存在的问题之一就是此数据类型只能表示 256 个不同的字符。这就迫使对于不同的字母表（例如相对较小的欧洲字母表）采用多重编码规格（或者代码页）。而且也不可能处理象日文汉字或韩国文字这样具有数千个字符的字母表。

每个 Microsoft® SQL Server™ 排序规则都有一个对表示 char、varchar 和 text 值中的每个字符定义位模式进行定义的代码页。可为个别的列和字符常量指派不同的代码页。客户端计算机使用与操作系统区域设置相关联的代码页解释字符位模式。有很多种不同的代码页。一些字符出现在某些代码页上，但并不出现在其它的代码页上。某些字符在一些代码页上用一个位模式定义，而在其它的代码页上却用另一个位模式定义。当您设计必须处理不同语言的国际性系统时，为了满足不同国家/地区的语言需求，给所有的计算机挑选代码页就变得困难了。要保证每一台计算机与使用不同代码页的系统交互时都进行正确的翻译也是困难的。

Unicode 规格通过采用两个字节编码每个字符使这个问题迎刃而解。转换最通用商业语言的单一规格具有足够多的 2 字节的模式 (65,536)。因为所有的 Unicode 系统均一致地采用同样的位模式来代表所有的字符，所以当从一个系统转到另一个系统时，将不会存在未正确转换字符的问题。通过在整个系统中使用 Unicode 数据类型，可尽量减少字符转换问题。

在 Microsoft SQL Server 中，以下数据类型支持 Unicode 数据：

  • nchar

  • nvarchar

  • ntext

    说明 这些数据类型的前缀 n 来自 SQL-92 标准中的 National（Unicode）数据类型。

nchar、nvarchar 和 ntext 的用法分别与 char、varchar 和 text 的用法一样，但在以下方面不同：

  • Unicode支持的字符范围更大。

  • 存储 Unicode 字符所需要的空间更大。

  • nchar 和 nvarchar 列最多可以有 4,000 个字符，而不象 char 和 varchar 字符那样可以有 8,000 个字符。

  • Unicode 常量使用 N 开头来指定：N'A Unicode string'。

  • 所有 Unicode 数据都使用相同的 Unicode 代码页。排序规则不控制用于 Unicode 列的代码页，仅控制比较规则和是否区分大小写等特性。

请参见

排序规则

数据类型

NCHAR

UNICODE

Unicode 数据


数据类型转换

在 Transact-SQL 中，可能有两个级别的数据类型转换：

  • 当一个对象的数据移动到另一个对象，或两个对象之间的数据进行比较或组合时，数据可能不得不从一个对象的数据类型转换为另一个对象的数据类型。

  • 当将来自 Transact-SQL 结果列、返回代码或输出参数的数据移动到程序变量中时，必须将这些数据从 Microsoft® SQL Server™ 数据类型转换成该变量的数据类型。

数据类型转换有两种：

  • 隐性转换对于用户是不可见的。

    SQL Server 自动将数据从一种数据类型转换成另一种数据类型。例如，如果一个 smallint 变量和一个 int 变量相比较，这个 smallint 变量在比较前即被隐性转换成 int 变量。

  • 显式转换使用 CAST 或 CONVERT 函数。

CAST 和 CONVERT 函数将数值从一个数据类型（局部变量、列或其它表达式）转换到另一个数据类型。例如，下面的 CAST 函数将数值 $157.27 转换成字符串 '$157.27'：

CAST ( $157.27 AS VARCHAR(10) )

CAST 函数基于 SQL-92 标准并且优先于 CONVERT。

当从一个 SQL Server 对象的数据类型向另一个转换时，一些隐性和显式数据类型转换是不支持的。例如，nchar 数值根本就不能被转换成 image 数值。nchar 只能显式地转换成 binary，隐性地转换到 binary 是不支持的。nchar 可以显式地或者隐性地转换成 nvarchar。

当处理 sql_variant 数据类型时，SQL Server 支持将具有其它数据类型的对象隐性转换成 sql_variant 类型。然而，SQL Server 并不支持从 sql_variant 数据隐性地转换到其它数据类型的对象。

有关在 SQL Server 对象之间所支持的转换的更多信息，请参见 CAST 和 CONVERT。

在应用程序变量和 SQL Server 结果集列、返回代码、参数或参数标记之间转换时，所支持的数据类型转换是由数据库应用程序接口定义。


将数据移动到程序变量

通过数据库应用程序接口 (API) 访问 Microsoft® SQL Server™ 2000 数据库的应用程序，必须在应用程序变量和以下对象之间移动数据：

  • 结果集列。

    应用程序必须从结果集中的一个提取行的各列中将数据移动到应用程序变量。

  • 返回代码。

    应用程序必须从一个存储过程返回代码将数据移动到应用程序变量。

  • 参数

    应用程序必须在存储过程参数和应用程序变量之间移动数据。参数可以是输入或输出参数，这样数据移动可以是从变量到参数，也可以是从参数到变量。

  • 参数标记。

    ODBC 和 OLE DB 参数标记用在 SQL 语句中，代替输入表达式（如在 WHERE 子句搜索条件中）或者存储过程参数和返回代码。应用程序必须在应用程序变量和由参数标记代替的表达式之间移动数据。有关更多信息，请参见参数标记。

许多数据库 API 使用绑定的概念来指定如何在应用程序变量和 SQL Server 对象之间移动数据。数据库 API 提供了一些函数，应用程序可以调用它们来完成以下功能：

  • 决定数据类型、大小、精度和结果集列的小数位数、返回值、参数或参数标记。当应用程序接收到这些信息后，它可以分配变量或者具有兼容特性的变量数组。

  • 将结果集、返回代码、参数或者参数标记绑定到特定变量或者变量数组。绑定信息一般包括：
    • 变量的地址和特性（数据类型、大小、精度和小数位数）。

    • 数据库对象的名称和特性。

数据移动一般发生于：

  • 当执行 Transact-SQL 语句或批处理时。OLE DB 提供程序或 ODBC 驱动程序取得绑定到任何输入参数或参数标记的数据，并将它们包括进发送给 SQL Server 的数据包中。

  • 当提取结果集的某行时。OLE DB 提供程序或 ODBC 驱动程序将每列的数据移动到绑定的变量中。

  • 当所有存储过程的结果集被提取或取消后。OLE DB 提供程序或 ODBC 驱动程序将所有输出参数或返回代码的数据移动到绑定的变量中。

被绑定的应用程序变量不必和它们所绑定到的SQL Server 对象数据类型一致。如果数据类型不同的话，当移动数据时，OLE DB 提供程序或 ODBC 驱动程序将转换数据。OLE DB 提供程序和 ODBC 驱动程序支持的转换集在提供程序和驱动程序的文档中都有说明。

请参见

使用变量和参数


转换 binary 和 varbinary 数据

当数据从字符串数据类型（char、varchar、nchar、nvarchar、binary、varbinary、text、ntext 或 image）转换为不同长度的 binary 或 varbinary 数据类型时，Microsoft® SQL Server™ 将在数据的右边进行填充或截断。当其它数据类型转换为 binary 或 varbinary 时，将在数据左边进行填充或截断。填充是使用十六进制的零来完成的。

如果二进制数据是移动数据的最容易的方式，则将数据转换为 binary 和 varbinary 数据类型对于数据移动很有用。对于任何类型的任何值，将该值转换为足够大的二进制值，然后转换回原类型，如果是在同一 SQL Server 版本上进行这两种转换，则始终导致相同的值。值的二进制表示法可能在 SQL Server 的版本间有所变更。

因为存储表达的改变，在 money、datetime、smalldatetime 和 numeric 数据类型和 binary 或 varbinary 之间的转换与早期版本的 SQL Server 有所不同。

可以将 int、smallint 和 tinyint 转换到 binary 或者 varbinary，但是如果将 binary 数据转回到整型数据，如果发生截断的话，将会得到与原来不同的整型数值。例如，下面这个 SELECT 语句显示整型数据通常是以二进制 0x0001e240 存储的：

SELECT CAST( 123456 AS BINARY(4) )

本 SELECT 语句显示如果这个二进制目标太小，不能保存整个数值，那么前导位就会被静默地截断，这样该数字就被存为 0xe240：

SELECT CAST( 123456 AS BINARY(2) )

本批处理显示这种静默截断会影响算术操作却不产生错误：

DECLARE @BinaryVariable2 BINARY(2)

SET @BinaryVariable2 = 123456
SET @BinaryVariable2 = @BinaryVariable2 + 1

SELECT CAST( @BinaryVariable2 AS INT)
GO

最终结果为 57921，而不是 123457。

说明 在 SQL Server 各个版本中，并不保证任何数据类型和 binary 数据类型之间的转换是一致的。

请参见

CAST 和 CONVERT

数据类型


转换 bit 数据

转换到 bit 会将任何非零值升为 1。

请参见

CAST 和 CONVERT

数据类型


转换字符数据

在将字符表达式转换为不同大小的字符数据类型时，新数据类型的值太长将被截断。

当将某个字符表达式转换为不同数据类型或大小的字符表达式时（如从 char（5） 转换为 varchar（5），或从 char（20） 转换为 char（15）），为转换的值指派输入值的排序规则。当将非字符表达式转换为字符数据类型，给转换的值指派当前数据库的默认排序规则。在两种情况中，都可以使用 COLLATE 子句指派特定的排序规则。

说明 代码页转换适用于 char 和 varchar 数据类型，但不适用于 text 数据类型。与 Microsoft® SQL Server™ 以前的版本一样，不报告代码页转换期间的数据丢失。

转换为近似的 numeric 数据类型的字符表达式包括可选的指数符号 [在 (+) 号或 (-) 号之后紧跟一个大写或小写的字母 E 和一个数字]。

转换为精确的 numeric 数据类型的字符表达式必须包含数字、小数点和可选的加号 (+) 或减号 (-)。将忽略前导空格。逗号分隔符（如 123,456.00 中的千分隔符）在字符串中禁用。

转换为 money 或 smallmoney 数据类型的字符表达式还包括可选的小数点和美元符号 ($)。可以使用逗号分隔符（如在 $123,456.00 中）。

此示例说明如何转换数据以进行显示。此示例在执行字符串比较前将 sales 数据转换为字符数据，同时将当前日期转换为样式 3，即 dd/mm/yy。

USE pubs
GO
SELECT title,
   CAST(ytd_sales AS CHAR(12)),
   CAST(GETDATE() AS CHAR(12))
FROM titles
WHERE CAST(ytd_sales AS CHAR(20) ) LIKE '1%'
GO

此示例将 uniqueidentifier 值转换为 char 数据类型。

DECLARE @myid uniqueidentifier
SET @myid = NEWID()
SELECT CONVERT(char(255), @myid) AS 'Char'
GO

此示例将当前日期转换为样式 3，即 dd/mm/yy。

SELECT CONVERT(char(12), GETDATE(), 3)
GO

请参见

CAST 和 CONVERT

数据类型


转换 datetime 和 smalldatetime 数据

转换为 datetime 时，Microsoft® SQL Server™ 2000 将拒绝所有无法识别为日期的值（包括 1753 年 1 月 1 日以前的日期）。当日期在适当的范围内（1900 年 1 月 1 日到 2079 年 6 月 6 日）时，可将 datetime 值转换为 smalldatetime。时间值被四舍五入为最接近的分钟数。

此示例分别将 smalldatetime 和 datetime 值转换为 varchar 和 binary 数据类型。

DECLARE @mydate_sm  SMALLDATETIME
SET  @mydate_sm  = '4/05/98'

SELECT  CAST(@mydate_sm AS VARCHAR) AS SM_DATE_VARCHAR
GO

DECLARE @mydate  DATETIME
SET @mydate     = '4/05/98'

SELECT  CAST(@mydate AS BINARY) AS DATE_BINARY
GO

下面是结果集：

(1 row(s) affected)

SM_DATE_VARCHAR
------------------------------
Apr  5 1998 12:00AM

(1 row(s) affected)

DATE_BINARY
--------------------------------------------------------------
0x0000000000000000000000000000000000000000000000008c3000000000

(1 row(s) affected)

请参见

CAST 和 CONVERT

数据类型


转换 float 和 real 数据

float 在转换为任何整型类型时，其值将被截断。

在将 float 或 real 转换为字符数据时，最好选用字符串函数 STR( )，而不要选用 CAST( )，因为 STR( ) 能对格式有更多的控制。有关更多信息，请参见函数。

请参见

CAST 和 CONVERT

数据类型


转换 money 数据

从整型数据类型转换为 money 时，将采用货币单位。例如，整型值 4 被转换为相当于 4 个货币单位的 money 值。

下例分别将 smallmoney 和 money 值转换为 varchar 和 decimal 数据类型。

USE pubs
GO
DECLARE @mymoney_sm SMALLMONEY
SET  @mymoney_sm = 3148.29
SELECT  CAST(@mymoney_sm AS VARCHAR) AS "SM_MONEY VARCHAR"
GO
DECLARE @mymoney    MONEY
SET  @mymoney    = 3148.29
SELECT  CAST(@mymoney AS DECIMAL)    AS "MONEY DECIMAL"
GO

下面是结果集：

SM_MONEY VARCHAR
------------------------------
3148.29

(1 row(s) affected)

MONEY DECIMAL
----------------------
3148

(1 row(s) affected)

请参见

CAST 和 CONVERT

数据类型


转换 decimal 和 numeric 数据

对于 decimal 和 numeric 数据类型，Microsoft® SQL Server™ 将精度和小数位数的每个特定组合看作是不同的数据类型。例如，decimal(5,5) 和 decimal(5,0) 被当作不同的数据类型。

在 Transact-SQL 语句中，带有小数点的常量自动转换为 numeric 数据值，且必然使用最小的精度和小数位数。例如，常量 12.345 被转换为 numeric 值，其精度为 5，小数位为 3。

从 decimal 或 numeric 向 float 或 real 转换会导致精度损失。从 int、smallint、tinyint、float、real、money 或 smallmoney 向 decimal 或 numeric 转换会导致溢出。

默认情况下，在将数字转换为较低精度和小数位数的 decimal 或 numeric 值时，SQL Server 使用舍入法。然而，如果 SET ARITHABORT 选项为 ON，当发生溢出时，SQL Server 会出现错误。若仅损失精度和小数位数，则不会产生错误。

请参见

CAST 和 CONVERT

数据类型


使用 OLE 自动化存储过程进行数据类型转换

由于 Microsoft® SQL Server™ 使用 Transact-SQL 数据类型，而且 OLE 自动化使用 Microsoft Visual Basic® 数据类型，OLE 自动化存储过程必须转换在二者之间传递的数据。

下表描述 SQL Server 到 Visual Basic 的数据类型转换。

SQL Server 数据类型 | Visual Basic 数据类型
char、varchar、text、nvarchar、ntext | 字符串
decimal、numeric | 字符串
bit | 布尔型
binary、varbinary、image | 一维 Byte() 数组
int | Long
smallint | 整型
tinyint | Byte
float | 双精度
real | 单精度
money、smallmoney | 货币
datetime、smalldatetime | 日期
所有都设置为 NULL | 设置为 Null 的 Variant

除 binary、varbinary 和 image 外，所有单个 SQL Server 值都被转换为单个 Visual Basic 值。在 Visual Basic 中这些值被转换为一维 Byte() 数组。此数组的字节范围为 Byte(0 To length–1)，其中，length 为 SQL Server 的 binary、varbinary 或 image 值中的字节数。

以下为 Visual Basic 到 SQL Server 数据类型转换。

Visual Basic 数据类型 | SQL Server 数据类型
Long、整型、Byte、布尔值、对象 | int
双精度、单精度 | float
货币 | money
日期 | datetime
小于或等于 4000 个字符的字符串 | varchar/nvarchar
大于 4000 个字符的字符串 | text/ntext
小于或等于 8000 字节的一维 Byte() 数组 | varbinary
大于 8000 字节的一维 Byte() 数组 | image

请参见

如何创建 OLE 自动化对象 (Transact-SQL)

OLE 自动化脚本示例

如何调试自定义 OLE 自动化服务器 (Transact-SQL)

系统存储过程


使用常量

常量是表示特定数据值的符号。常量的格式取决于它所表示的值的数据类型。常量也称为字面值。下面是一些常量的示例：

  • 字符串：
    'O''Brien'
'The level for job_id: %d should be between %d and %d.'
  • Unicode 字符串：
    N'Michél'
  • 二进制字符串常量：
    0x12Ef
0x69048AEFDD010E
  • bit 常量由数字 0 或 1 表示。

  • datetime 常量：
    'April 15, 1998'
'04/15/98'
'14:30:24'
'04:24 PM'
  • integer 常量：
    1894
2
  • decimal 常量：
    1894.1204
2.0
  • float 和 real 常量：
    101.5E5
0.5E-2
  • money 常量：
    $12
$542023.14
  • uniqueidentifier 常量：
    0xff19966f868b11d0b42d00c04fc964ff
'6F9619FF-8B86-D011-B42D-00C04FC964FF'

对于数字常量，若要指定数字值的符号，可使用一元的 + 和 – 运算符：

+$156.45
-73.52E8
-129.42
+442

字符和 Unicode 常量被指派为使用当前数据库的默认排序规则，除非使用 COLLATE 子句指派特定的排序规则：

'abc' COLLATE French_CI_AI
N'lustig' COLLATE German_Phonebook_CS_AS
在 Transact-SQL 中使用常量

在 Transact-SQL 中，可以用多种方式使用常量。这里是一些示例：

  • 作为算术表达式中的常量值：
    SELECT Price + $.10
FROM MyTable
  • 在 WHERE 子句中，作为与列进行比较的数据值：
    SELECT *
FROM MyTable
WHERE LastName = 'O''Brien'
  • 作为将置于变量中的数据值：
    SET @DecimalVar = -1200.02
  • 作为应置于当前行的某列中的数据值。可使用 UPDATE 语句的 SET 子句或 INSERT 语句的 VALUES 子句来指定：
    UPDATE MyTable
SET Price = $99.99
WHERE PartNmbr = 1234

INSERT INTO MyTable VALUES (1235, $88.88)
  • 作为指定 PRINT 或 RAISERROR 语句发出的消息文本的字符串：
    PRINT 'This is a message.'
  • 作为条件语句（例如：IF 语句或 CASE 函数）中进行测试的值：
    IF (@@SALESTOTAL > $100000.00)
    EXECUTE Give_Bonus_Procedure

请参见

常量

INSERT

表达式

LIKE

运算符

PRINT

ALTER TABLE

RAISERROR

CREATE TABLE

UPDATE

DELETE

WHERE


使用函数

Microsoft® SQL Server™ 2000 具有执行某些运算的内置函数。函数分类有：

聚合函数。

执行的操作是将多个值合并为一个值。例如 COUNT、SUM、MIN 和 MAX。

配置函数。

是一种标量函数，可返回有关配置设置的信息。

游标函数。

返回有关游标状态的信息。

日期和时间函数。

操作 datetime 和 smalldatetime 值。

数学函数。

执行三角、几何和其它数字运算。

元数据函数。

返回数据库和数据库对象的特性信息。

行集函数。

返回行集，这些行集可用在 Transact-SQL 语句中表引用所在的位置。

安全性函数。

返回有关用户和角色的信息。

字符串函数。

操作 char、varchar、nchar、nvarchar、binary 和 varbinary 值。

系统函数。

对系统级别的各种选项和对象进行操作或报告。

系统统计函数。

返回有关 SQL Server 性能的信息。

文本和图像函数。

操作 text 和 image 值。

函数的使用

函数可用于或包含在：

  • 使用 SELECT 语句的查询的选择列表中，以返回一个值。
    SELECT DB_NAME()
  • SELECT 或数据修改（SELECT、INSERT、DELETE 或 UPDATE）语句的 WHERE 子句搜索条件中，以限制合乎查询条件的行。
    SELECT *
FROM [Order Details]
WHERE Quantity =
    (SELECT MAX(Quantity) FROM [Order Details])
  • 视图的搜索条件（WHERE 子句）中，以使视图在运行时与用户或环境动态地保持一致。
    CREATE VIEW ShowMyEmploymentInfo AS
SELECT * FROM Employees
WHERE EmployeeID = SUSER_SID()
GO
  • 任一表达式中。

  • CHECK 约束或触发器中，以在插入数据时检查指定的值。
    CREATE TABLE SalesContacts
    (SalesRepID    INT PRIMARY KEY CHECK (SalesRepID = SUSER_SID() ),
    ContactName    VARCHAR(50) NULL,
    ContactPhone    VARCHAR(13) NULL)
  • DEFAULT 约束或触发器中，以便在 INSERT 语句未指定值的情况下提供一个值。
    CREATE TABLE SalesContacts
    (
    SalesRepID    INT PRIMARY KEY CHECK (SalesRepID = SUSER_SID() ),
    ContactName    VARCHAR(50) NULL,
    ContactPhone    VARCHAR(13) NULL,
    WhenCreated    DATETIME DEFAULT GETDATE(),
    Creator        INT DEFAULT SUSER_SID()
    )
GO

使用函数时总是带有圆括号，即使没有参数也是如此。例外情况是与 DEFAULT 关键字一起使用的 niladic 函数（不带参数的函数）。有关 DEFAULT 关键字的更多信息，请参见 ALTER TABLE 和 CREATE TABLE 或默认值。

有时，用来指定数据库、计算机、登录或数据库用户的参数是可选的。如果未指定这些参数，就默认地将这些参数赋值为当前的数据库、主机、登录或数据库用户。

函数可以嵌套（一个函数用于另一个函数内部）。

使用确定性函数和非确定性函数

函数可以是确定性的，也可以是非确定性的。如果任何时候使用特定的输入值集调用函数时总是返回相同的结果，则称该函数为确定性函数。如果每次使用特定的输入值集调用函数时返回不同的结果，则该函数是非确定性函数。

函数的确定性可限制其使用的位置。只能在视图和索引化的计算列中唤醒调用确定性函数。

有关更多信息，请参见确定性函数和非确定性函数。


使用系统函数

系统函数使您得以访问 Microsoft® SQL Server™ 系统表中的信息，而不必直接访问系统表。

下面是用于数据库、主机、对象、登录和用户的五对系统函数，若给定了标识符 (ID)，则可用这组函数返回名称；若给定了名称，则可用这组函数返回 ID：

  • DB_ID 和 DB_NAME

  • HOST_ID 和 HOST_NAME

  • OBJECT_ID 和 OBJECT_NAME

  • SUSER_ID 和 SUSER_NAME（或 SUSER_SID 和 SUSER_SNAME）

  • USER_ID 和 USER_NAME

例如，使用 DB_ID 函数可获得数据库的 ID 号，而不用对 sysobjects 表执行 SELECT。

下例显示如何检索当前登录用户的用户名（使用 SQL Server 身份验证）：

SELECT SUSER_NAME()

下面的函数与此类似，但它们不是成对出现的，而且带有的输入参数不止一个：

  • COL_NAME

    返回列名。

  • COL_LENGTH

    返回列长度。

  • INDEX_COL

    返回索引列名称。

COL_LENGTH 返回列长度，而不是列中存储的任何单个字符串的长度。可用 DATALENGTH 函数来确定特定值中的字符总数。

下面的示例返回 Employees 表中 LastName 列的列长度和数据长度：

SELECT COL_LENGTH('Employees', 'LastName') AS Col_Length,
   DATALENGTH(LastName) AS DataLength
FROM Employees
WHERE EmployeeID > 6

说明 建议使用系统函数、信息架构视图或系统存储过程来获取对系统信息的访问，而不必直接查询系统表。不同版本 SQL Server 的系统表可能会有极大的不同。

请参见

系统函数

ALTER TABLE

DEFAULT 定义

CREATE TABLE


使用字符串函数

字符串函数用于对字符和二进制字符串进行各种操作，它们返回对字符数据进行操作时通常所需要的值。大多数字符串函数只能用于 char、nchar、varchar 和 nvarchar 数据类型，或隐性转换为上述数据类型的数据类型。少数字符串函数也可用于 binary 和 varbinary 数据。

可使用字符串函数执行以下操作：

  • 仅检索字符串的一部分 (SUBSTRING)。

  • 搜索字符串的发音相似性（SOUNDEX 和 DIFFERENCE）。

  • 查找列或表达式中特定字符串的开始位置。例如，字母 A 在"What a beautiful day!"中的位置号。

  • 将多个字符串联成一个字符串。例如，将名、姓和中间名或名的首字母合并为全名。

  • 将非字符串值转换为字符串值（例如，将以 float 类型存储的值 15.7 转换为 char 类型）。

  • 将特定字符串插入现有字符串。例如，将字符串"Once"插入现有字符串"upon a time"中，以生成字符串"Once upon a time"。

当在具有不同排序规则的字符串上操作字符串函数时，结果的排序规则将使用排序规则优先顺序规则确定。有关更多信息，请参见排序规则的优先顺序。


使用 SUBSTRING

SUBSTRING 函数返回字符串、二进制字符串或文本串的一部分，并带有三个参数：

  • 字符串或二进制字符串、列名或包括列名的字符串值表达式。

  • 子字符串的开始位置。

  • 要返回的字符串的长度（以字符数表示，对于 binary 类型以字节数表示）。

下面的示例显示了每个职员的名的首字母和姓，例如 A Fuller：

USE Northwind
SELECT SUBSTRING(FirstName, 1, 1), LastName
FROM Employees

下面的示例显示字符串常量 abcdef 的第二个、第三个和第四个字符：

SELECT x = SUBSTRING('abcdef', 2, 3)

x
----------
bcd

(1 row(s) affected)

请参见

字符串函数


比较 CHARINDEX 和 PATINDEX

CHARINDEX 和 PATINDEX 函数都返回指定模式的开始位置。PATINDEX 可使用通配符，而 CHARINDEX 不可以。

这两个函数都带有两个参数：

  • 希望获取其位置的模式。使用 PATINDEX，模式是可以包含通配符的字面字符串。使用 CHARINDEX，模式是字面字符串（不能包含通配符）。

  • 字符串值表达式（通常为列名），Microsoft® SQL Server™ 在其中搜索指定的模式。

例如，查找模式"wonderful"在 titles 表中 notes 列的某一特定行中的开始位置。

USE pubs

SELECT CHARINDEX('wonderful', notes)

FROM titles

WHERE title_id = 'TC3218'

下面是结果集：

----------------

46

(1 row(s) affected)

如果未限制搜索的行，查询将返回表中的所有行，并对在其中查找到该模式的所有行报告非零值，对其它行报告零值。

例如，使用通配符查找模式"candies"在 Categories 表中的 Description 列的任一行中的开始位置：

USE Northwind
GO
SELECT CategoryID, PATINDEX('%candies%', Description)AS POSITION
FROM Categories
WHERE PATINDEX('%candies%', Description) <> 0

如果没有限制要搜索的行，查询将返回表中的所有行，对在其中找到该模式的所有行报告非零值。

PATINDEX 对 text 数据类型很有用；除 IS NULL、IS NOT NULL 和 LIKE（这些是 WHERE 子句中对 text 类型有效的仅有的其它比较运算）外，PATINDEX 也可用于 WHERE 子句中。

请参见

字符串函数


使用 STR

STR 函数将数字转换为字符，用可选的参数来指定结果的总长度（包括小数点和小数点后的位数）。

STR 的长度和小数参数（如果提供）应该是正数。默认长度是 10。在默认情况下或者小数参数为 0 时，数字四舍五入为整数。指定长度应该大于或等于小数点前面的数字加上数字符号（如果有的话）的长度：

下面的示例将 123.45 的浮点表达式转换为字符长度为 6 和小数位数为 2 的字符。

SELECT STR(123.45, 6, 2)

下面是结果集：

------
123.45

(1 row(s) affected)

如果转换为字符串的表达式的整数部分超过 STR 中指定的长度，STR 将为指定的长度返回 **。例如，数字 1234567.89 的小数点的左边有 7 位数字。如果 STR 中的长度参数为 7 或更大，结果字符串将包含整数和可以容纳的尽可能多的小数位数。如果 STR 中的长度参数为 6 或更小，则返回星号。例如下面的批处理：

SELECT STR(1234567.89, 7, 2)
SELECT STR(1234567.89, 6, 2)

下面是结果集：

-------
1234568

(1 row(s) affected)

------
******

(1 row(s) affected)

将 decimal 数据类型转换为字符数据时，STR 能提供比 CAST 更大的灵活性，因为它能显式控制格式化。

请参见

字符串函数


使用 STUFF

STUFF 函数将字符串插入另一字符串。它在第一个字符串中从开始位置删除指定长度的字符；然后将第二个字符串插入第一个字符串的开始位置。

如果开始位置或长度的值是负数，或者如果开始位置大于第一个字符串的长度，将返回空字符串。如果要删除的长度大于第一个字符串的长度，将删除到第一个字符串中的第一个字符。

下面的示例将字符串"xyz"置于字符表达式"abc"的第二个字符处，并总共替换三个字符。

SELECT STUFF('abc', 2, 3, 'xyz')

下面是结果集：

----
axyz

(1 row(s) affected)

请参见

字符串函数


比较 SOUNDEX 和 DIFFERENCE

SOUNDEX 函数将字符串转换为四位数字代码，以在比较中使用。比较中忽略元音。使用非字母字符来终止比较。此函数总会返回值。

下面的示例显示 SOUNDEX 函数对相似字符串"Smith"和"Smythe"的比较结果。当两个字符串相似时，它们的 SOUNDEX 代码相同。

SELECT SOUNDEX ('smith'), SOUNDEX ('smythe')

下面是结果集：

-----          -----

S530 S530

(1 row(s) affected)

DIFFERENCE 函数比较两个字符串的 SOUNDEX 值，并评估它们之间的相似性，最后返回 0 到 4 之间的一个值，其中 4 表示匹配性最高。下面的示例对第一个 SELECT 返回的 DIFFERENCE 为 4，因为"Smithers"和"Smothers"只有一个字符不同。

SELECT DIFFERENCE('smithers', 'smothers')

下面是结果集：

------------
           4
(1 row(s) affected)

下面的示例返回的 DIFFERENCE 为 3，表示虽然两个字符串中有几个字符不同，但它们具有相似的发音。

SELECT DIFFERENCE('Jeff', 'Geoffe')

请参见

字符串函数


使用 text、ntext 和 image 函数

有两个 text、ntext 和 image 函数专门用于对 text、ntext 和 image 数据所进行的操作：

  • TEXTPTR 返回 binary(16) 对象，该对象包含指向 text、ntext 或 image 实例的指针。指针一直有效，直到删除该行。

  • TEXTVALID 函数用来检查指定的文本指针是否有效。

文本指针被传递到用于操作 text、ntext 和 image 数据的 READTEXT、UPDATETEXT、WRITETEXT、PATINDEX、DATALENGTH 和 SET TEXTSIZE Transact-SQL 语句。

在 Transact-SQL 语句中，总是使用数据的指针或地址来引用 text、ntext 和 image 数据。

下面的示例使用 TEXTPTR 函数来查找与 pubs 数据库的 pub_info 表中 pub_id 0736 相关联的 text 列 (pr_info)。下例首先声明一个局部变量 @val。然后将文本指针（长二进制字符串）置于 @val 中，并将其作为参数提供给 READTEXT 语句，该语句将返回从第五个字节（偏移量为 4）开始的 10 个字节。

USE pubs
DECLARE @val varbinary(16)
SELECT @val = textptr(pr_info) FROM pub_info
WHERE pub_id = '0736'
READTEXT pub_info.pr_info @val 4 4

下面是结果集：

(1 row(s) affected)

pr_info
----------------------------------------
 yet

支持使用 CAST 函数进行从 text 到 varchar、从 ntext 到 nvarchar 和从 image 到 varbinary 或 binary 的显式转换，但 text 或 image 数据将截断为 8,000 个字节，ntext 数据将截断为 4,000 个字符（8,000 个字节)。从 text、ntext 或 image 到其它数据类型的转换（无论是显式的还是隐性的）都不支持。但是，可以对 text、ntext 或 image 数据进行间接转换，例如：

CAST( CAST( text_column_name AS VARCHAR(10) ) AS INT )。

请参见

文本和图像函数


使用数学函数

数学函数对数字表达式进行数学运算并返回运算结果。数学函数可对 Microsoft® SQL Server™ 系统提供的数字数据（decimal、integer、float、real、money、smallmoney、smallint 和 tinyint）进行处理。默认情况下，对 float 数据类型数据的内置运算的精度为六个小数位。

默认情况下，传递到数学函数的数字将被解释为 decimal 数据类型。可用 CAST 或 CONVERT 函数将数据类型更改为其它数据类型，例如 float 类型。例如，FLOOR 函数返回值的数据类型与输入值的数据类型相同。此 SELECT 语句的输入值是 decimal 类型，FLOOR 返回值为 123，也是 decimal 值：

SELECT FLOOR(123.45)

---------------------
123

(1 row(s) affected)

但是，下面的示例使用 float 值，FLOOR 也返回 float 值：

SELECT FLOOR (CONVERT (float, 123.45))

-------------------------------------
123.000000

(1 row(s) affected)

当数学函数的 float 或 real 结果太小而无法显示时，将发生浮点下溢错误。返回的结果为 0.0，而且不显示错误信息。例如，2 的 -100.0 次幂数学计算将得到结果 0.0。

若向数学函数提供的值不是有效值，将发生域错误。例如，为 ASIN 函数指定的值必须在 -1.00 到 1.00 之间。如果指定的范围为 –2，将发生域错误。

若指定的值超出了允许值，则发生范围错误。例如，POWER(10.0, 400) 超出了 float 数据类型的最大范围 ~2e+308，而 POWER(-10.0, 401) 超出了 float 数据类型的最小范围 ~ -2e+308。

下表显示了产生域错误或范围错误的数学函数。

数学函数 | 结果
SQRT(-1) | 域错误。
POWER(10.0, 400) | 算术溢出错误。
POWER(10.0, -400) | 值 0.0（浮点下溢）。

我们可以用错误捕获的方法来处理这些函数的域错误或范围错误。您可以使用：

  • SET ARITHABORT ON，用来终止查询并退出用户定义的事务。SET ARITHABORT 的设置将替代 SET ANSI_WARNINGS 的设置。

  • SET ANSI_WARNINGS ON，用来停止命令。

  • SET ARITHIGNORE ON，用来使警告消息不显示。SET ARITHABORT 和 SET ANSI_WARNINGS 的设置都会替代 SET ARITHIGNORE 的设置。

如果没有设置上述任何选项，执行查询后，SQL Server 将返回 NULL 并返回一个警告消息。有关更多信息，请参见 SET ARITHABORT、SET ANSI_WARNINGS 和 SET ARITHIGNORE。

如果使用 money 或 numeric 数据类型，那么，转换为 float 的内部转换可能会降低精度。


使用三角函数

Microsoft® SQL Server™ 2000 提供返回弧度值的三角函数。

返回弧度的函数 | 使用弧度作为输入值
ACOS | TAN
COS | SIN
ATAN | ASIN
ATN2 |
COT |

ACOS 和 COS

ACOS 和 COS 都是三角函数。ACOS 函数返回以弧度为单位的角度，它的余弦是所给的 float 表达式。在给定 float 表达式的情况下，COS 函数返回指定角度（以弧度为单位）的余弦。例如，下面的 SELECT 语句计算 -.997 的 ACOS 值和 1.134 的 COS 值：

SELECT ACOS(-.997), COS(1.134)

因此，弧度为 3.06411360866591 的角度的余弦值为 -.997，弧度为 1.134 的角度的余弦值为 1.134。

ACOS 的有效范围为 -1 到 1。

ASIN 和 SIN

ASIN 和 SIN 都是使用 float 表达式的三角函数。ASIN 函数计算以弧度为单位的角度，它的正弦是所给的 float 表达式。SIN 函数计算 float 表达式给出的、以弧度为单位的角度的三角正弦值。

下面的示例计算 -.7582 的 ASIN 值和 5 的 SIN 值。弧度为 -0.860548023283932 的角度的正弦值为 -.7582，弧度为 5 的角度的正弦值为 -0.958924274663138。

SELECT ASIN(-.7582), SIN(5)

ASIN 的有效范围为 -1 到 1。

ATAN、ATN2、TAN 和 COT

ATAN、ATN2、TAN 和 COT 函数都是数学函数。ATAN 函数返回角度的度量值（以弧度为单位），其正切值是所给的 float 表达式。正切值为 -27.29 的角度，其弧度值为
-1.53416925536896。

ATN2 函数返回以弧度为单位的角度，此角度的正切值在所给的两个 float 表达式之间。正切值在 3.273 和 15 之间的角度的弧度值为 0.214832755968629。

TAN 函数返回所给 float 表达式的三角正切值。弧度值为 27.92 的角度，其正切值为 -0.36994766163616。

COT 函数返回指定角度（用所给的 float 表达式以弧度为单位指定）的三角余切值。97.1928 弧度角的余切值为 -5.02149424849997。

DEGREES

DEGREES 函数从角度的弧度值返回 numeric 表达式，该表达式是以度为单位的角度值。弧度值为
-14.578 的角度的度数为 -835.257873741714090000。

SELECT DEGREES(-14.578)
RADIANS

RADIANS 函数计算以度为单位给出度量值的角度的弧度值。若要计算 10.75 度的角度的弧度值，请使用：

SELECT RADIANS(10.75)

请参见

数学函数


比较 CEILING 和 FLOOR

CEILING 函数返回大于或等于所给数字表达式的最小整数。FLOOR 函数返回小于或等于所给数字表达式的最大整数。例如，对于数字表达式 12.9273，CEILING 将返回 13，FLOOR 将返回 12。FLOOR 和 CEILING 返回值的数据类型都与输入的数字表达式的数据类型相同。

请参见

数学函数


比较 LOG 和 LOG10

LOG 函数返回所给 float 表达式的自然对数。自然对数是使用底数为 2 的体系计算的。但是，LOG10 函数返回底数为 10 的对数。LOG 和 LOG10 都可用于三角应用程序。例如，下面的 SELECT 语句计算 1.75 的 LOG 和 LOG10 值：

SELECT LOG(1.75), LOG10(1.75)

请参见

数学函数


使用 POWER 和 EXP 指数函数

POWER 函数返回所给数字表达式值的指定次幂的值。POWER(2,3) 返回 2 的 3 次幂，即 8。可以指定负数幂次，所以 POWER(2.000, -3) 返回 0.125。请注意，POWER(2, -3) 的结果是 0，这是因为返回的结果与所给数字表达式的数据类型相同。因此，如果结果有三位小数，则计算其所给幂次的数字必须也有三位小数。

EXP 函数返回所给 float 表达式的以科学计数法表示的指数值。所以，对于值 198.1938327，EXP 函数返回的值为 1.18710159597953e+086。

SELECT EXP(198.1938327)

请参见

数学函数


使用 RAND

RAND 函数计算 0 到 1 之间的随机浮点数，而且可选择以 tinyint、int 或 smallint 值作为要计算的随机数的起始点。

下面的示例计算两个随机数。第一个 RAND() 函数让 Microsoft® SQL Server™ 选取种子值，第二个 RAND() 函数使用值 3 作为起始位置。

SELECT RAND(), RAND(3)

RAND 函数是伪随机的数字生成器，它用与 C 运行时库 rand 函数类似的方法进行操作。如果没有提供种子值，系统将生成它自己的不定种子值。如果用种子值调用 RAND，则必须使用不定的种子值来生成随机数。如果用同一种子值多次调用 RAND，它将返回相同的生成值。下面的脚本中，对 RAND 的调用都返回相同的值，这是因为它们都使用了相同的种子值：

SELECT RAND(159784)
SELECT RAND(159784)
SELECT RAND(159784)

通常用 RAND 生成随机数的方法是，用相对不定的某个值作为种子值，例如累加 GETDATE 的几个部分：

SELECT RAND( (DATEPART(mm, GETDATE()) * 100000 )
           + (DATEPART(ss, GETDATE()) * 1000 )
           + DATEPART(ms, GETDATE()) )

使用基于 GETDATE 的算法生成种子值时，如果在算法中所用的最小 datepart 的时间间隔内多次调用 RAND，RAND 仍会生成重复值。如果对 RAND 的调用包括在单个批处理中，尤其可能出现这种情况。在单个批处理中对 RAND 的多次调用可在同一毫秒（DATEPART 的最小增量）内执行。这种情况下，请用基于除时间之外的值以生成种子值。

请参见

数学函数


日期函数

日期函数用来显示关于日期和时间的信息。这些函数操作 datetime 和 smalldatetime 值，并对这些值执行算术运算。可将 Date 函数用于可使用表达式的任何地方。

SQL Server 可识别各种 datetime 数据项格式。可以使用 SET DATEFORMAT 语句来设置输入 datetime 或 smalldatetime 数据时所用的日期部分（月/日/年）顺序。请将 datetime 或 smalldatetime 值用单引号引起来。

请参见

日期和时间函数


使用 GETDATE

GETDATE 函数生成以 datetime 值的 Microsoft® SQL Server™ 内部格式表示的当前日期和时间。GETDATE 带有空参数 ( )。

下面的示例得出当前系统日期和时间：

SELECT GETDATE()

下面是结果集：

-------------------------
July 29 1995   2:50    PM

(1 row(s) affected)

设计报告时可使用 GETDATE，以便在每次生成报告时都打印当前日期和时间。GETDATE 对于诸如记录事务在某一帐户上发生的时间的函数也很有用。

无论何处都可以使用 GETDATE 返回当前系统日期。例如，可将 GETDATE 用作数据项的默认值，用于局部变量，或将订单日期与今天的日期进行比较。

请参见

日期和时间函数


比较 DATEPART 和 DATENAME

DATEPART 和 DATENAME 函数将 datetime 值的指定部分（年、季度、天、小时等）生成为整数值或 ASCII 字符串。由于 smalldatetime 只能精确到分钟，所以在这两个函数中使用 smalldatetime 值时，返回的秒和毫秒部分总是为零。

下面的示例假定日期为 5 月 29 日：

SELECT DATEPART(month, GETDATE())

下面是结果集：

------------
5

(1 row(s) affected)

SELECT DATENAME(month, GETDATE())

下面是结果集：

------------
May

(1 row(s) affected)

请参见

日期和时间函数


比较 DATEADD 和 DATEDIFF

DATEADD 函数向指定日期添加一段时间间隔。例如，如果 titles 表中所有书籍的发行日期都推迟三天，则可使用以下语句获得新的发行日期：

USE pubs
SELECT DATEADD(day, 3, pubdate)
FROM titles

如果日期参数的数据类型是 smalldatetime，则结果的数据类型也是 smalldatetime。可以使用 DATEADD 对 smalldatetime 值添加秒或毫秒，但只有当 DATEADD 返回的结果日期至少改变了 1 分钟时，这样做才有意义。

DATEDIFF 函数计算指定的两个日期中第二个日期与第一个日期之间各日期部分相差的时间量。换句话说，它得出两个日期之间的间隔。结果是等于 date2 - date1 的带符号整数值，以各日期部分为单位。

下面的查询使用日期 1995 年 11 月 30 日，并得出 pubdate 和该日期之间相差的天数。

USE pubs
SELECT DATEDIFF(day, pubdate, 'Nov 30 1995')
FROM titles

对于 titles 中 pubdate 为 1995 年 10 月 21 日的行，上述查询生成的结果为 40。（10 月 21 日和 11 月 30 日之间相差 40 天。）要计算以月为单位的间隔，请使用以下查询：

USE pubs
SELECT interval = DATEDIFF(month, pubdate, 'Nov 30 1995')
FROM titles

该查询对 pubdate 值在 10 月的行生成的值为 1，对 pubdate 值在 6 月的行生成的值为 5。

若 DATEDIFF 函数中的第一个日期晚于指定的第二个日期，则产生的结果为负数。由于 titles 中有两行使用 GETDATE 函数指派 pubdate 的值，所以这些值被设置为 pubs 数据库的创建日期，这样在前面的两个查询中这两行返回负数。

如果一个或两个日期参数是 smalldatetime 值，它们将在内部转换为 datetime 值进行计算。为了进行计算，smalldatetime 值中的秒和毫秒将自动设置为 0。

请参见

CAST 和 CONVERT

DATENAME

日期和时间函数

DATEPART

DATEADD

GETDATE

DATEDIFF

SET DATEFORMAT


返回用户名和用户 ID 的函数

有几个系统函数可返回用户名和用户 ID。要理解这些函数的参数和输出结果，首先必须理解 Microsoft® SQL Server™ 2000 中使用的名称和 ID 的类型。

登录到 SQL Server 的每个用户在 SQL Server 中都有两个级别的名称，每个名称都与一个唯一的 ID 相关联：

  • 登录名。

    被授权登录到 SQL Server 的每个用户都有一个登录名，使他们得以访问 SQL Server 实例。有两种类型的登录名：

    • Microsoft Windows® 帐户名。

      sysadmin 或 securityadmin 固定服务器角色成员可以使用 sp_grantlogin 对 Windows 组或 Windows 帐户的个别用户授权，使其可登录到 SQL Server 实例。于是，Windows 帐户所标识的用户或 Windows 组中的任何组员都可以使用 Windows 身份验证连接 SQL Server 实例。master.dbo.syslogins.loginname 中存储了所有 Windows 帐户名或组名。用于 Windows 帐户或组的 Windows security_identifier 存储在 syslogins.sid 中。

    • SQL Server 登录名。

      使用 SQL Server 身份验证登录时将用到这些登录名。SQL Server 登录名由 sysadmin 或 securityadmin 固定服务器角色的成员使用 sp_addlogin 进行定义。所有 SQL Server 登录名都存储在 master.dbo.syslogins.loginname 中。SQL Server 生成用作 security_identifier 的 GUID 并将其存储在 syslogins.sid 中。

    SQL Server 2000 将 master.dbo.syslogins.sid 作为登录名的 security_identifier。

  • 数据库用户名。

    所有 Windows 帐户或 SQL Server 登录都必须与已授权用户访问的每个数据库中的用户名相关联，或者该数据库必须已启用客户访问。数据库用户名由 db_owner 或 db_accessadmin 固定数据库角色的成员来定义，并存储在每个数据库所建的 sysusers 表中。每个数据库用户名都与存储在 sysusers.uid 中的一个数据库用户 ID 相关联。

    每个用户的 security_identifier 都存储在 sysusers.sid 中；因此，可将用户映射回他们相应的登录。如果使用同一数据库用户名作为 SQL Server 登录名或 Windows 帐户名，则可减少混淆；但是，不要求一定这样做。

有关登录和数据库用户帐户的更多信息，请参见登录、用户、角色和组。

获得登录帐户或 ID

连接到 SQL Server 2000 后，使用：

  • SUSER_SNAME，用来获得与 security_identifier 关联的 SQL Server 登录名或 Windows 帐户。

  • SUSER_SID，用来获得与 SQL Server 登录名或 Windows NT 帐户关联的 security_identifier。

  • SUSER_SID()（不带 login_account 参数的指定 SUSER_SID），用于获得当前连接的 security_identifier，不管是否使用 SQL Server 身份验证或 Windows 身份验证。

  • SQL-92 函数 SYSTEM_USER，以获得用于 Windows 身份验证连接的 Windows 帐户，或用于 SQL Server 身份验证连接的 SQL Server 登录名。在 Transact-SQL 中，SYSTEM_USER 实现为 SUSER_SNAME()（不指定 security_identifier 的 SUSER_SNAME）的同义词。

在 SQL Server 2000 中，返回登录名或帐户的函数以下面的方式操作：

  • SUSER_SNAME(security_identifier)

    SUSER_SNAME 具有以下选项之一：

    • 用于 Windows 帐户或组的 security_identifier，这种情况下，该函数返回 Windows 帐户名或组名。

    • 为 SQL Server 登录生成的伪 security_identifier，这种情况下，函数返回 SQL Server 登录名。

    如果没有为使用 Windows 身份验证进行的连接指定 security_identifier，则 SUSER_SNAME 将返回与该连接关联的 Windows 帐户名。如果是使用 SQL Server 身份验证进行的连接，SUSER_SNAME 将返回与该连接关联的 SQL Server 登录。

  • SYSTEM_USER

    此 SQL-92 函数在 Transact-SQL 中实现为 SUSER_SNAME()（不指定 security_identifier 参数的 SUSER_SNAME）的同义词。

获得数据库用户名或用户 ID

连接到 SQL Server 2000 后，使用：

  • USER_ID，用来获得与数据库用户名关联的数据库用户 ID。

  • USER_ID()，用来获得与当前连接关联的数据库用户 ID。

  • USER_NAME，用来获得与数据库用户 ID 关联的数据库用户名。

  • SQL-92 CURRENT_USER 或 SESSION_USER 函数，都是用来获得与当前连接关联的数据库用户名。在 Transact-SQL 中，这些函数实现为 USER_NAME()（不指定 database_user_ID 参数的 USER_NAME）的同义词。Transact-SQL 函数 USER 也实现为 USER_NAME() 的同义词。

    SQL-92 允许在这样的 SQL 模块中对 SQL 语句进行编码：该模块的授权标识符与已连接到 SQL 数据库的用户的授权标识符可以相互独立。SQL-92 指定 SESSION_USER 总是返回进行连接的用户的授权标识符。对于从 SQL 模块执行的任何语句，CURRENT_USER 返回 SQL 模块的授权标识符；如果 SQL 语句不是从 SQL 模块执行的，则返回进行连接的用户的授权标识符。如果 SQL 模块没有单独的授权标识符，则 SQL-92 指定 CURRENT_USER 返回与 SESSION_USER 相同的值。Microsoft SQL Server 没有单独用于 SQL 模块的授权标识符，因此，CURRENT_USER 和 SESSION_USER 总是返回相同的值。USER 函数是由 SQL-92 为向后兼容性而定义的函数，用于为早期版本的标准所编写的应用程序。USER 被指定为与 CURRENT_USER 返回相同的值。

在 SQL Server 中，返回登录名或帐户的函数以下面的方式操作：

  • USER_ID('database_user_name')

    USER_ID 返回与指定数据库用户名关联的数据库用户 ID。如果未指定 database_user_name，则 USER_ID 返回与当前连接关联的数据库用户 ID。

  • USER_NAME(database_user_ID)

    USER_NAME 返回与指定的数据库用户 ID 关联的数据库用户名。如果未指定 database_user_ID，则 USER_NAME 返回与当前连接关联的数据库用户名。

  • CURRENT_USER、SESSION_USER、USER

    这些函数都是 USER_NAME()（不指定 database_user_ID 参数的 USER NAME）的同义词。

请参见

CURRENT_USER

SYSTEM_USER

USER_ID

SUSER_SID

USER_NAME

SUSER_SNAME


转换函数

当 Microsoft® SQL Server™ 2000 不自动执行不同数据类型的表达式转换时，可使用转换函数 CAST 和 CONVERT 进行转换。这些转换函数还可用来获得各种特殊的数据格式。这两个转换函数都可用于选择列表、WHERE 子句和允许使用表达式的任何地方。

如果希望 Transact-SQL 程序代码遵从 SQL-92 标准，请使用 CAST 而不要使用 CONVERT。如果要利用 CONVERT 中的样式功能，请使用 CONVERT 而不要使用 CAST。

使用 CAST 或 CONVERT 时，需要两条信息：

  • 要转换的表达式（例如，销售报告要求销售数据从货币型数据转换为字符型数据）。

  • 要将所给表达式转换到的数据类型，例如，varchar 或 SQL Server 提供的任何其它数据类型。

除非将被转换的值存储起来，否则转换仅在 CAST 或 CONVERT 函数的作用期内有效。

在下面的示例中，第一个 SELECT 语句中使用 CAST，第二个 SELECT 语句中使用 CONVERT，将 title 列转换为 char(50) 列，以使结果更可读：

USE pubs
SELECT CAST(title AS char(50)), ytd_sales
FROM titles
WHERE type = 'trad_cook'

或

USE pubs
SELECT CONVERT(char(50), title), ytd_sales
FROM titles
WHERE type = 'trad_cook'

下面是结果集：（对任何一个查询）

                                                  ytd_sales
-----------------------------------------       -----------
Onions, Leeks, and Garlic: Cooking Secrets of the       375
Fifty Years in Buckingham Palace Kitchens             15096
Sushi, Anyone?                                         4095

(3 row(s) affected)

在下面的示例中，int 类型的ytd_sales 列转换为 char(20) 列，从而可以对该列使用 LIKE 谓词：

USE pubs
SELECT title, ytd_sales
FROM titles
WHERE CAST(ytd_sales AS char(20)) LIKE '15%'
   AND type = 'trad_cook'

下面是结果集：

Title ytd_sales

-----------------------------------------      ---------

Fifty Years in Buckingham Palace Kitchens 15096

(1 row(s) affected)

SQL Server 自动处理某些数据类型的转换。例如，如果比较 char 和 datetime 表达式、smallint 和 int 表达式、或不同长度的 char 表达式，SQL Server 可将它们自动转换。这种转换称为隐性转换。对这些转换不必使用 CAST 函数。但是，在下列情况下使用 CAST 都是可以接受的：

  • 两个表达式的数据类型完全相同。

  • 两个表达式可隐性转换。

  • 必须显式转换数据类型。

如果试图进行不可能的转换（例如，将含有字母的 char 表达式转换为 int 类型），SQL Server 将显示一条错误信息。

如果转换时没有指定数据类型的长度，则 SQL Server 自动提供长度为 30。

转换为 datetime 或 smalldatetime 时，SQL Server 将拒绝所有无法识别为日期的值（包括 1753 年 1 月 1 日以前的日期）。当日期处于适当的范围内（1900 年 1 月 1 日到 2079 年 6 月 6 日）时，可将 datetime 值转换为 smalldatetime。时间值被四舍五入为最接近的分钟数。

转换为 bit 将把任何非零值都更改为 1。

转换为 money 或 smallmoney 时，整数后将加上货币单位。例如，整数值 4 被转换为货币时相当于 4 美元（对于默认语言 us_english）。浮点值的小数部分将四舍五入为四位小数以用于 money 值。将要转换为整数数据类型的 char 或 varchar 数据类型的表达式中，只能包含数字和可选的加号或减号（+ 或 -）。将忽略前导空格。要转换为 money 的 char 或 varchar 数据类型的表达式，还可包含可选的小数点和前导美元符号 ($)。

要转换为 float 或 real 数据类型的 char 或 varchar 类型表达式还可选择性地包含指数符号（e 或 E，后面有可选的 + 或 – 符号，再后面是数字）。

将字符表达式转换为其它大小的数据类型时，对于新的数据类型过长的值将被截断，SQL Server 将在 osql 实用工具和 SQL 查询分析器中显示星号 (*)。若数字表达式对于新的数据类型太长而无法显示，值将截断。下面是字符截断的示例：

USE pubs
SELECT SUBSTRING(title, 1, 25) AS Title, CONVERT(char(2), ytd_sales)
FROM titles
WHERE type = 'trad_cook'

下面是结果集：

Title
------------------------- --
Onions, Leeks, and Garlic *
Fifty Years in Buckingham *
Sushi, Anyone?            *

(3 row(s) affected)

在进行数据类型转换时，若目标数据类型的小数位数小于源数据类型的小数位数，则该值将被截断。例如，CAST(10.3496 AS money) 的结果是 $10.35。

可将 text 数据显式转换为 char 或 varchar，将 image 显式转换为 binary 或 varbinary。由于这些数据类型被限制为 8,000 个字符，所以转换也限制为 character 和 binary 数据类型的最大长度，即 8,000 个字符。可将 ntext 数据显式转换为 nchar 或 nvarchar，但最大长度为 4,000 个字符。如果没有指定长度，被转换的值的默认长度为 30 个字符。不支持隐式转换。

style 参数

将 datetime 数据转换为 char 或 varchar 数据时，CONVERT 的 style 参数提供了各种日期显示格式。为 style 参数提供的数值确定了 datetime 数据的显示方式。年份可以显示为两位或四位数。默认情况下，SQL Server 将年份显示为两位数。若要显示包括世纪的四位数年份 (yyyy)（即使年份数据是使用两位数的年份格式存储的），请给 style 值加 100 以获得四位数年份。

以下示例显示用 style 参数进行转换：

SELECT CONVERT(char(12), GETDATE(), 3)

此语句把当前日期转换为样式 3，dd/mm/yy。

请参见

CAST 和 CONVERT

函数


唤醒调用用户定义函数

当引用或唤醒调用用户定义函数时，应在括号后面指定函数名。而在括号内可指定称为参数的表达式，以提供将传递给参数的数据。当唤醒调用函数时不能在参数中指定参数名。当唤醒调用函数时，必须提供所有参数的参数值，并且必须以 CREATE FUNCTION 语句定义参数的相同序列指定参数值。例如，如果使用整型参数和 nchar(20) 参数定义名为 fn_MyIntFunc 的函数以返回整数，可利用以下方法唤醒调用该函数：

SELECT *
FROM SomeTable
WHERE PriKey = dbo.fn_MyIntFunc( 1, N'Anderson' )

以下是唤醒调用名为 fn_MyTableFunc 的函数的示例，该函数被定义为返回表：

SELECT *
FROM dbo.fn_MyTableFunc( 123.09, N'O''Neill' )

请参见

用户定义函数

CREATE FUNCTION


唤醒调用返回标量值的用户定义函数

在 Transact-SQL 语句中允许相同数据类型的标量表达式的任何位置，可唤醒调用返回标量值的用户定义函数：

查询

在以下位置允许返回标量值的用户定义函数：

  • 作为 SELECT 语句 select_list 中的表达式：
    SELECT *, dbo.fn_CalculateDaysLate(RequiredDate) AS DaysLate
FROM Northwind.dbo.Employees
  • 作为 WHERE 或 HAVING 子句的谓词 中的表达式 或 string_expression：
    SELECT *
FROM Northwind.dbo.[Order Details]
WHERE UnitPrice < dbo.fn_MeanUnitPrice()
  • 作为 GROUP BY 子句中的 group_by_expression：

  • 作为 ORDER BY 子句中的 order_by_expression：

  • 作为 UPDATE 语句 SET 子句中的表达式：
    UPDATE Orders
 SET ShipVia = dbo.fn_FindLeastCostShipper(ShipCity)
 WHERE OrderID = 10274
  • 作为 INSERT 语句 VALUES 子句中的表达式：
    INSERT INTO Shippers
   VALUES (4, dbo.fn_GetShipperName(), n'(503)555-9931'

    在这些位置中引用的用户定义函数逻辑上每行执行一次。

CHECK 约束

如果传递给函数的参数值仅引用表中的列或常量，则可以在 CHECK 约束中唤醒调用返回标量值的用户定义函数。查询处理器每次检查约束时，都使用与所检查的当前行相关联的参数值调用函数。表的所有者还必须是由表的 CHECK 约束唤醒调用的用户定义函数的所有者。

DEFAULT 定义

如果传递给函数的参数值仅包含常量，则用户定义函数可作为 DEFAULT 定义的 constant_expression 唤醒调用。表的所有者还必须是由表的 DEFAULT 定义调用的用户定义函数的所有者。

计算列

如果传递给函数的参数值仅引用表中的列或常量，则可由计算列唤醒调用函数。表的所有者还必须是由表的计算列调用的用户定义函数的所有者。

赋值运算符

赋值运算符 (left_operand = right_operand) 可唤醒调用用户定义函数，以便在指定为右操作数的表达式中返回标量值。

控制流语句

布尔表达式中的控制流语句可唤醒调用返回标量值的用户定义函数。

CASE 表达式

在任何 CASE 表达式中可唤醒调用返回标量值的用户定义函数。

PRINT 语句

返回字符串的用户定义函数可作为 PRINT 语句的 string_expr 表达式唤醒调用。

函数和存储过程

  • 返回标量值的用户定义函数也可以引用函数参数。

  • 存储过程中的 RETURN integer_expression 语句可唤醒调用作为 integer_expression 返回整数的用户定义函数。

  • 用户定义函数中的 RETURN return_type_spec 语句可唤醒调用返回标量数据类型（如 return_type_spec）的用户定义函数，只要被唤醒调用的用户定义函数返回的值可隐性转换为唤醒调用函数的返回数据类型。
执行返回标量值的用户定义函数

可采用与存储过程相同的方式执行返回标量值的用户定义函数。当执行返回标量值的用户定义函数时，指定参数的方式与指定存储过程的方式相同：

  • 参数值不括在括号中。

  • 可指定参数名。

  • 如果指定参数名，参数值不必与参数具有相同的序列。

以下是返回小数的用户定义函数的定义：

CREATE FUNCTION fn_CubicVolume
-- Input dimensions in centimeters.
   (@CubeLength decimal(4,1), @CubeWidth decimal(4,1),
    @CubeHeight decimal(4,1) )
RETURNS decimal(12,3) -- Cubic Centimeters.
WITH SCHEMABINDING
AS
BEGIN
   RETURN ( @CubeLength * @CubeWidth * @CubeHeight )
END

以下是执行 fn_CubicVolume 函数的示例。使用 Transact-SQL EXECUTE 语句时，参数的标识次序与函数定义中的参数标识次序不同：

DECLARE @MyDecimalVar decimal(12,3)
EXEC @MyDecimalVar = dbo.fn_CubicVolume @CubeLength = 12.3,
                        @CubeHeight = 4.5, @CubeWidth = 4.5

以下是执行 fn_CubicVolume 函数时不指定参数名的示例：

DECLARE @MyDecimalVar decimal(12,3)
EXEC @MyDecimalVar = dbo.fn_CubicVolume 12.3, 4.5, 4.5

还可使用 ODBC CALL 语法从 OLE DB 或 ODBC 应用程序执行 fn_CubicVolume 函数：

-- First use SQLBindParam to bind the return value parameter marker
-- to a program variable of the appropriate type
SQLExecDirect(hstmt,
              "{ CALL ? = fn_CubicVolume(12.3, 4.5, 4.5) }",
              SQL_NTS);


唤醒调用返回表数据类型的用户定义函数

可唤醒调用返回表 (table) 的用户定义函数，其中在 SELECT、INSERT、UPDATE 或 DELETE 语句的 FROM 子句中允许表表达式。可在调用返回表的用户定义函数后加上可选的表别名。下例说明如何唤醒调用函数 fn_Products 和指派别名：

SELECT OD.OrderID, OD.ProductID, fnPr.Price
FROM OrderDetails as OD, fn_Products('Discontinued') AS fnPr
WHERE OD.ProductID = fnPr.ProductID
ORDER BY OD.OrderID, OD.ProductID

当在子查询的 FROM 子句中唤醒调用返回表的用户定义函数时，函数参数不能引用外部查询中的任何列。

若 SELECT 语句的 FROM 子句引用了返回表的用户定义函数，则静态的只读游标是可在该 SELECT 语句上打开的唯一游标类型。

引用返回表 (table) 的用户定义函数的 SELECT 语句唤醒调用该函数一次。

请参见

返回 table 数据类型的用户定义函数

内嵌用户定义函数

table


唤醒调用内置用户定义函数

Microsoft® SQL Server™ 2000 将多种内置函数作为返回表 (table) 的用户定义函数来实现。这些内置用户定义函数的调用遵循特殊的规则：

  • 对于返回表 (table) 的内置用户定义函数，必须以起始双冒号 (::) 指定函数名，以将其与非内置的用户定义函数区分开。此外，还必须将函数名指定为不具数据库或所有者资格的由一部分组成的名称。例如：
    SELECT *
FROM ::fn_helpcollations()
  • 对于返回标量值的内置用户定义函数，必须将函数名指定为由一部分组成的名称（不指定数据库或所有者）。不要指定起始双冒号 (::)。


表达式

表达式是标识符、值和运算符的组合，Microsoft® SQL Server™ 可以对其求值以得到结果。访问或更改数据时，可在多个不同的位置使用数据。例如，可以将表达式用作要搜索的数据的一部分（在查询中）或查找满足一组条件的数据的搜索条件。

表达式可以是：

  • 常量

  • 函数

  • 列名

  • 变量

  • 子查询

  • CASE、NULLIF 或 COALESCE

还可以用运算符对这些实体进行组合以生成表达式。

在下面的 SELECT 语句中，对于结果集的每一行，SQL Server 可以让 LastName 具有一个值，因此它是一个表达式。

SELECT LastName
FROM Northwind..Employees

表达式还可以是计算，如 (price * 1.5) 或 (price + sales_tax)。

在表达式中，请用英文单引号将字符日期值引起来。在下面的 SELECT 语句中，用作 LIKE 子句模式的字符文字 B% 必须在单引号中：

SELECT LastName, FirstName
FROM Northwind..Employees
WHERE LastName LIKE 'B%'

在下面的 SELECT 语句中，日期值用引号引起来：

SELECT *
FROM Northwind..Orders
WHERE OrderDate = 'Sep 13 1996'

本示例中查询使用了多个表达式。例如，col1、SUBSTRING、col3、price 和 1.5 均为表达式。

SELECT col1, SUBSTRING('This is a long string', 1, 5), col3, price * 1.5
FROM mytable

请参见

CASE

INSERT

COALESCE

UPDATE

函数

DELETE

SELECT

表达式


在表达式中使用运算符

运算符使您得以执行算术、比较、连接或赋值操作。例如，可以测试数据以验证客户数据的 country 列是否已填充（或非 NULL）。

在查询中，可以查看表中数据（需要与某种运算符结合使用）的任何人员均可进行运算。需要具有相应的权限才能成功地更改数据。

在 Microsoft® SQL Server™ 中使用运算符完成下列工作：

  • 永久或临时更改数据。

  • 搜索满足指定条件的行或列。

  • 在数据列之间或表达式之间进行判断。

  • 在开始或提交事务之前，或在执行特定代码行之前测试指定条件。

SQL Server 有七类运算符。

执行这种运算 | 使用此类运算符
将数值与另一个数值或表达式进行比较 | 比较运算符
测试条件的真假，如 AND、OR、NOT、LIKE、ANY、ALL 或 IN。 | 逻辑
加法、减法、乘法、除法、求模。 | 算术运算符
执行有一个操作数的运算，如正、负或求余。 | 一元
暂时将常规数值（如 150）转换为整数并执行按位（0 和 1）运算。 | 按位运算符
永久或临时将两个字符串（字符或二进制数）合并为一个字符串。 | 字符串串联运算符
将值赋给变量，或将结果集列与别名相关联。 | 赋值

表达式可由多个小表达式经运算符合并而成。在这些复杂表达式中，运算符将根据 SQL Server运算符优先顺序定义的顺序进行计算。具有较高优先顺序的运算符先于低优先顺序的运算符计算。有关更多信息，请参见运算符。

当将简单表达式组合为复杂表达式时，结果的数据类型取决于运算符规则与数据类型优先顺序规则的组合方式。如果结果是一个字符或 Unicode 值，则结果的排序规则取决于运算符规则与排序规则优先顺序的组合方式。还有规则用于根据简单表达式的精度、小数位数和长度确定结果的精度、小数位数和长度。

请参见

排序规则的优先顺序

数据类型优先顺序

精度、小数位数和长度


算术运算符

算术运算符可用于任何算术计算，如：

  • 加法。

  • 减法。

  • 乘法。

  • 除法。

  • 求余（除法运算的余数）。

下面是有关算术运算符的一些信息：

  • 如果表达式中有多个算术运算符，那么先计算乘法、除法和求余，然后计算减法和加法。

  • 如果表达式中的所有算术运算符都具有相同的优先顺序，则执行顺序为从左到右。

  • 括号中的表达式比所有其它运算都要优先。

下面的 SELECT 语句从总销售额中减去作者收到的本年度截至目前的销售额部分（销售额 (sale) * 作者的版税百分比 (author's royalty percentage) / 100）。结果为出版商所得的金额。首先计算 ytd_sales 与 royalty 的结果，因为运算符是乘法。下一步，将结果除以 100，然后从 ytd_sales 中减去除得的结果。

USE pubs
SELECT title_id, ytd_sales - ytd_sales * royalty / 100
FROM titles

为清楚起见，可以使用括号：

USE pubs
SELECT title_id, ytd_sales - ((ytd_sales * royalty) / 100)
FROM titles

也可以使用括号更改执行的顺序。括号内的计算要首先执行。如果括号有嵌套，则最内层的计算优先。例如，如果使用括号强迫先计算减法再计算乘法，就会更改上面查询的结果和意义：

USE pubs
SELECT title_id, (ytd_sales - ytd_sales) * royalty / 100
FROM titles

请参见

-（减）

+（加）

*（乘）

/（除）


按位运算符

位运算符可用于 int、smallint 或 tinyint 数据。~（按位 NOT）运算符还可用于 bit 数据。所有位运算符对一个或多个指定的整型值在 Transact-SQL 语句中转换为二进制表达式时执行计算。例如，~（按位 NOT）运算符将二进制 1 变为 0 而将 0 变为 1。若要检查位运算，请转换或计算十进制数值。

例如，要将 150 与 75 相加，而且不只对十进制数值 225 感兴趣，还要使用二进制算法（0 和 1 的加法）。为此请使用按位 AND 运算符 (&)。

若要存储整数（象前面提到的 150 和 75 这样的普通十进制数值）并要执行内部转换来进行二进制数学运算，请使用位运算符。位运算符还可用于在不需要精确求反的情况下获得非值。

请参见

&（按位 AND）

~（按位 NOT）

|（按位 OR）

^（按位互斥 OR）

数据类型


比较运算符

比较运算符可用于字符、数字或日期数据，并可用在查询的 WHERE 或 HAVING 子句中。比较运算符计算结果为布尔数据类型，它们根据测试条件的输出结果返回 TRUE 或 FALSE。

例如，要计算在 1998 年 3 月 15 日或在此之前聘用员工的奖金，通过比较员工的 hire_date 是否小于或等于 1998 年 3 月 15 日 (March 15, 1998)，可提供应该获得奖金的员工列表。

有效的比较运算符为：

  • >（大于）。

  • <（小于）。

  • =（等于）。

  • <=（小于或等于）。

  • >=（大于或等于）。

  • !=（不等于）。

  • <>（不等于）。

  • !<（不小于）。

  • !>（不大于）。

比较运算符还可用于在程序逻辑中检测条件。例如，如果 country（国家）列为 UK（英国）而不是 Spain（西班牙），则应用不同的运费。在这种情况下，可使用比较运算符、表达式（列名）、文字 ('UK') 和流程控制编程关键字 (IF) 的组合来达到上述目的。

访问实际数据（查询）的任何人均可在附加查询中使用比较运算符。对于那些进行数据修改的语句，建议只在具有相应权限并且只有有限的人员才能更改数据的情况下使用比较运算符（以维护数据的完整性）。

查询还使用字符串比较，对本地变量、游标或列中的值与常量进行比较。例如，所有国家为 UK 的客户 (customer) 行均要打印。下表是 Unicode 与非 Unicode 数据之间的字符串比较示例，其中 ST1 为 char，ST2 为 nchar。

比较 | 描述
ST1 = ST2 | 等价于 CONVERT(nchar, ST1) = ST2 或 CAST(ST1 as nchar) = ST2。
ST1 = 'non-Unicode string' | 常规 SQL-92 字符串比较。
ST2 = 'non-Unicode string' | 等价于 ST2 = CONVERT(nchar, 'non-Unicode string') 或 ST2 = CAST('non-Unicode string'AS nchar)。
ST2 = N'' | Unicode 比较。
CONVERT(nchar, ST1) = ST2或
CAST(ST1 AS nchar) = ST2 		Unicode 比较。
ST1 = CONVERT(char, ST2)
或
ST1 = CAST(ST2 AS char) 		常规 SQL-92 字符串比较。
N'' (Unicode empty string in parentheses) | 空字符串。
'' (non-Unicode empty string) | 空字符串或包含一个空格字符的字符串（取决于 SQL-92 设置）。

请参见

=（等于）

<>（不等于）

>（大于）

!< （不小于）

<（小于）

!=（不等于）

>=（大于或等于）

!> （不大于）

<=（小于或等于）

WHERE


字符串串联运算符

字符串连接运算符为加号 (+)。可以将两个或多个字符串合并或连接成一个字符串。还可以连接二进制字符串。下面是一个连接示例：

SELECT ('abc' + 'def')

下面是结果集：

------
abcdef

(1 row(s) affected)

本查询在 Moniker 列中按姓、名顺序（姓后加逗号和空格）显示地址为 California 的作者名字。

USE Northwind
GO
SELECT LastName + ', ' + FirstName AS Moniker
FROM Employees
WHERE Region = 'WA'

下面是结果集：

Moniker
-------------------------
Davolio, Nancy
Fuller, Andrew
Leverling, Janet
Peacock, Margaret
Callahan, Laura

(15 row(s) affected)

其它数据类型，如 datetime 和 smalldatetime，在与字符串连接之前必须使用 CAST 转换函数转换成字符字符串。

USE pubs
SELECT 'The due date is ' + CAST(pubdate AS varchar(128))
FROM titles
WHERE title_id = 'BU1032'

下面是结果集：

---------------------------------------
The due date is Jun 12 1991 12:00AM

(1 row(s) affected)

空字符串 ('') 取值为一个空格：

SELECT 'abc' + '' + 'def'

下面是结果集：

-------
abcdef

(1 row(s) affected)

说明 将空字符串 ('') 解释成一个空格字符还是空字符，这取决于 sp_dbcmptlevel 的兼容级别设置。此示例中，如果 sp_dbcmptlevel 是 65，则空文字将视为一个空格。

当输入的两个字符串具有相同的排序规则时，输出字符串将具有与输入相同的排序规则。当输入字符串的排序规则不同时，排序规则优先顺序规则确定输出字符串的排序规则。还可以使用 COLLATE 子句指派特定的排序规则。

请参见

COLLATE

排序规则的优先顺序

运算符

sp_dbcmptlevel

+（字符串串联）


空值

空 (NULL) 值表示数值未知。空值不同于空白或零值。没有两个相等的空值。比较两个空值或将空值与任何其它数值相比均返回未知，这是因为每个空值均为未知。

空值通常表示未知、不可用或将在以后添加的数据。例如，客户的中间名首字母在客户下定单时可能不知道。

下面是有关空值的一些信息：

  • 若要测试查询中的空值，请在 WHERE 子句中使用 IS NULL 或 IS NOT NULL。

  • 在 SQL 查询分析器中查看查询结果时，空值在结果集内显示为 (null)。

  • 可通过以下方法在列中插入空值：在 INSERT 或 UPDATE 语句中显式声明 NULL，或不使此列进入 INSERT 语句，或使用 ALTER TABLE 语句在现有表中新添一列。

  • 不能将空值用于区分表中两行所需的信息（例如，外键或主键）。

在程序代码中，可以检查空值以便只在具有有效（或非空）数据的行上进行某些计算。例如，报表可以只打印社会安全列中数据不为空的列。执行计算时消除空值很重要，因为如果包含空值列，某些计算（如平均值）会不准确。

如果数据中可能包含空值，创建查询或数据修改语句消除空值或将空值转换成其它值是一个好办法（如果不想在数据中出现空值）。

重要 为了减少对已有查询或报表的维护和可能的影响，建议尽量少使用空值。对查询和数据修改语句进行规划，使空值的影响降到最小。

如果数据出现空值，则逻辑运算符和比较运算符有可能返回 TRUE 或 FALSE 以外的第三种结果 UNKNOWN。需要三值逻辑是导致许多应用程序出错之源。下面这些表概括了引入空值比较的效果。

下表显示将 AND 运算符应用到两个布尔操作数的结果。

AND | TRUE | UNKNOWN | FALSE
TRUE | TRUE | UNKNOWN | FALSE
UNKNOWN | UNKNOWN | UNKNOWN | FALSE
FALSE | FALSE | FALSE | FALSE

下表显示将 OR 运算符应用到两个布尔操作数的结果。

OR | TRUE | UNKNOWN | FALSE
TRUE | TRUE | TRUE | TRUE
UNKNOWN | TRUE | UNKNOWN | UNKNOWN
FALSE | TRUE | UNKNOWN | FALSE

下表显示 NOT 运算符求反或反转方式，以及布尔运算符的结果。

应用了 NOT 运算符的布尔表达式 | 取值为
TRUE | FALSE
UNKNOWN | UNKNOWN
FALSE | TRUE

SQL-92 标准引入了关键字 IS NULL 和 IS NOT NULL 来测试空值。

应用了 IS NULL 运算符的布尔表达式 | 取值为 | 应用了 IS NOT NULL 运算符的布尔表达式 | 取值为
TRUE | FALSE | TRUE | TRUE
NULL | TRUE | NULL | FALSE
FALSE | FALSE | FALSE | TRUE

Transact-SQL 还提供扩展空值处理。如果 ANSI_NULLS 选项设为关，则空值之间的比较（如 NULL = NULL）等于 TRUE。空值与任何其它数据类型之间的比较等于 FALSE。

请参见

AND

NOT

CREATE TABLE

OR

允许空值

修改列属性

ISNULL

WHERE


使用注释

注释是程序代码中不执行的文本字符串（也称为注解）。注释可用于说明代码或暂时禁用正在进行诊断的部分 Transact-SQL 语句和批处理。使用注释对代码进行说明，可使程序代码更易于维护。注释通常用于记录程序名称、作者姓名和主要代码更改的日期。注释可用于描述复杂计算或解释编程方法。

Microsoft® SQL Server™ 支持两种类型的注释字符：

  • --（双连字符）。这些注释字符可与要执行的代码处在同一行，也可另起一行。从双连字符开始到行尾均为注释。对于多行注释，必须在每个注释行的开始使用双连字符。有关使用注释字符的更多信息，请参见 --（注释）。

  • /* ... */（正斜杠-星号对）。这些注释字符可与要执行的代码处在同一行，也可另起一行，甚至在可执行代码内。从开始注释对 (/*) 到结束注释对 (*/) 之间的全部内容均视为注释部分。对于多行注释，必须使用开始注释字符对 (/*) 开始注释，使用结束注释字符对 (*/) 结束注释。注释行上不应出现其它注释字符。有关使用 /* ...*/ 注释字符的更多信息，请参见 /* ...*/（注释）。

多行 /* */ 注释不能跨越批处理。整个注释必须包含在一个批处理内。例如，在 SQL 查询分析器和 osql 实用工具中，GO 命令标志批处理的结束。当实用工具在一行的前两个字节中读到字符 GO 时，则把从上一 GO 命令开始的所有代码作为一个批处理发送到服务器。如果 GO 出现在 /* 和 */ 分隔符之间的一行行首，则在每个批处理中都发送不匹配的注释分隔符，从而导致语法错误。例如，以下脚本包含语法错误：

USE Northwind
GO
SELECT * FROM Employees
/* The
GO in this comment causes it to be broken in half */
SELECT * FROM Products
GO

下面是一些有效注释：

USE Northwind
GO
-- First line of a multiple-line comment.
-- Second line of a multiple-line comment.
SELECT * FROM Employees
GO

/* First line of a multiple-line comment.
   Second line of a multipl-line comment. */
SELECT * FROM Products
GO

-- Using a comment in a Transact-SQL statement
-- during diagnosis.
SELECT EmployeeID, /* FirstName, */ LastName
FROM Employees

-- Using a comment after the code on a line.
USE Northwind
GO
UPDATE Products
SET UnitPrice = UnitPrice * .9 -- Try to build market share.
GO

下面是关于注释的一些基本信息：

  • 所有字母数字字符或符号均可用于注释。SQL Server 忽略注释中的所有字符，而 SQL 查询分析器、osql 和 isql 将在多行注释中搜索前两个字符是 GO 的行。

  • 批处理中的注释没有最大长度限制。一条注释可由一行或多行组成。


使用保留关键字

Microsoft® SQL Server™ 2000 保留某些关键字用于其它用途。例如，在 osql 或 SQL 查询分析器会话中使用 Transact-SQL DUMP 或 BACKUP 关键字，通知 SQL Server 备份全部或部分数据库或者备份日志。

除 SQL Server 定义的位置以外，其它任何位置上的 Transact-SQL 语句中包括保留关键字均为非法。数据库中对象的名称不能与保留关键字相同。如果有这样的名称，则必须始终使用分隔标识符来引用这个对象。尽管这个方法允许存在名称为保留关键字的对象，还是建议不要用与保留关键字相同的名称命名任何数据库对象。

通常由系统管理员角色和数据库管理员角色或数据库创建者负责检查 Transact-SQL 代码和数据库名称中的保留关键字。

请使用能避免使用保留关键字的命名规则。如果一个对象名必须与某个保留关键字类似，则可以删除辅音或元音。例如，一个名为 bckup 的过程对所有用户定义数据库都执行 BACKUP 语句。

请参见

保留关键字

SET QUOTED_IDENTIFIER

分隔标识符

CREATE TABLE

ALTER TABLE

CREATE RULE

CREATE DATABASE

ALTER DATABASE


访问和更改数据基础知识

Microsoft® SQL Server™ 2000 数据库的主要用途是存储数据并使授权的应用程序和用户能够使用这些数据。数据库管理员创建和维护数据库时，用户使用的数据库的内容包括：

  • 访问或检索现有数据

  • 更改或更新现有数据

  • 添加或插入新数据

  • 删除现有数据

通过使用应用程序或实用工具向 SQL Server 发送数据检索和修改请求，从而实现访问和更改 Microsoft SQL Server 中的数据。例如，可以使用 SQL Server 企业管理器、SQL 查询分析器或者 osql 实用程序来连接到 SQL Server，然后开始使用 SQL Server 中的数据。

应用程序和实用工具使用两个组件来访问 SQL Server：

  • 数据库应用编程接口 (API) 向 SQL Server 发送命令并检索这些命令的结果。API 可以是用于一般目的的数据库 API，例如 ADO、OLE DB、ODBC 或 DB-Library。也可以是为使用 SQL Server 中的特殊功能而专门设计的 API，例如 SQL-DMO、SQL-DTS 或 SQL Server 复制组件。

  • 发送给 SQL Server 的命令是 Transact-SQL 语句。

    Transact-SQL 语句是使用"Transact-SQL 参考"中定义的 SQL 语言生成的。大多数操作是使用以下四个 Transact-SQL 语句之一实现的：

    • SELECT 语句用于检索现有数据。

    • UPDATE 语句用于更改现有数据。

    • INSERT 语句用于添加新的数据行。

    • DELETE 语句用于删除不再需要的行。

这四个语句组成了 SQL 语言的核心。理解 SQL 如何工作的很重要的一点在于理解这四个语句是如何工作的。

图形化或基于窗体的查询工具不要求任何 SQL 的知识。它们向用户提供了表格的图形化表示。用户可以通过图形方式选择要检索的列，然后方便地指定要检索的行的条件。

一些应用程序（如 SQL 查询分析器和 osql 实用工具）是执行 Transact-SQL 语句的工具。这些语句是通过交互方式输入或从文件读入的。若要使用这些工具，您必须能够生成 Transact-SQL 语句。

写入通用数据库 API（如 ADO、OLE DB、ODBC 或 DB-Library）的应用程序，也向 SQL Server 发送 Transact-SQL 语句。这些应用程序显示给用户的是反映它们所支持的业务功能的界面。当用户指示执行某个业务功能时，此应用程序使用一个数据库 API 将 SQL 语句传递给 SQL Server。您必须能够生成 Transact-SQL 语句来为这些类型的应用程序编码。

其它应用程序（如 SQL Server 企业管理器）使用对象模型来增加使用 SQL Server 的效率。SQL Server 企业管理器使用对象模型来减轻管理 SQL Server 的工作。API（如 SQL-DMO、SQL-DTS和复制组件）也使用相似的对象模型。对象本身使用 Transact-SQL 与 SQL Server 通讯。了解 Transact-SQL 语言有助于您理解这些对象。

建立 Transact-SQL 语句

"访问和更改数据基础知识"包含用于生成 Transact-SQL 语句的基本元素的信息。还提供关于 Transact-SQL 可执行函数的信息以及由数据库 API 提供的相似的功能。

SELECT 语句包含 Transact-SQL 语句中使用的常用元素。例如，若要从 Northwind 数据库中的 Customers 表选择住在 USA 的客户的名称 (name)、联系人姓名 (contact name) 和电话号码 (telephone number)，请使用下列元素：

  • 包含此表的数据库名称 (Northwind)

  • 包含数据的表名称 (Customers)

  • 返回其数据的列的列表（CompanyName、ContactName、Phone）

  • 选择标准（仅限于居住在 USA 的客户）

下面是检索此信息的 Transact-SQL 语法：

SELECT CompanyName, ContactName, Phone
FROM Northwind.dbo.Customers
WHERE Country = 'USA'

Transact-SQL 语句中使用的其它元素包括：

  • 函数：

    在 SQL Server 查询、报表和许多 Transact-SQL 语句中使用函数来返回信息，这与其它编程语言中使用的函数相似。它们使用输入参数并返回可以用于表达式的值。例如，DATEDIFF 函数使用两个 date（日期）和一个 datepart（日期单位）（weeks（周）、days（天）、months（月）等）作为参数，并且返回两个 date 之间的 datepart 单位的个数。

  • 标识符：

    标识符是提供给对象（如表、视图、数据库和索引）的名称。可以指定标识符不带分隔符（例如 TEST）、带引号分隔符 ("TEST") 或位于方括号中 ([TEST])。

  • 注释：

    注释是在程序代码中非执行的备注。

  • 表达式：

    表达式包含常量或字面值（例如 5 是数字）、函数、列名、算法、按位运算、标量子查询、CASE 函数、COALESCE 函数或 NULLIF 函数。

  • 保留关键字：

    SQL Server 为自己的功能而保留的字词。建议避免使用这些保留关键字作为标识符。

  • 空值：

    空值是未知的值。可以使用 NULL 值来表明以后将提供此信息。例如，如果 Leka Trading 公司的联系人已更改并且不知道新联系人，则可以用 NULL 值来表示未知的联系人。

  • 数据类型：

    数据类型定义了数据存储的格式。例如，可以使用任何字符或 Unicode 数据类型（char、varchar、nchar 或 nvarchar）来存储字符数据（如客户名）。

  • 批处理：

    批处理是作为一个单元传输和执行的语句组。某些 Transact-SQL 语句不能组合在一个批处理中。例如，若要在 pubs 数据库中创建五个新表，每个 CREATE TABLE 语句均必须在自己单独的批处理或单元中。下面是 Transact-SQL 批处理的示例：

    USE Northwind
SELECT *
FROM Customers
WHERE Region = 'WA'
  AND Country = 'USA'
ORDER BY PostalCode ASC, CustomerID ASC
UPDATE Employees
SET City = 'Missoula'
WHERE CustomerID = 'THECR'
GO
  • 控制流语言：

    控制流语言允许程序代码根据条件是否满足来执行操作。例如，如果订购的产品数量等于或少于现有的产品数量，则我们必须订购更多产品。

  • 运算符：

    SQL Server 包含的运算符允许对数据执行特定操作。例如，使用算术运算符可以对数据执行数学运算（如加和减）。

请参见

数据类型

CREATE TABLE

表达式

ALTER TABLE

使用标识符

运算符

函数


选择数据库

在 Microsoft® SQL Server™ 中，所有对象都存储在数据库内。所有对 SQL Server 对象的引用必须解析到其所在的特定数据库。

  • 当 Transact-SQL 语句提到数据所在数据库的名称时为显式数据库引用。下例指定 Northwind 数据库，为显式引用：
    SELECT *
FROM Northwind..Employees
  • 当 Transact-SQL 语句不指定数据库时为隐式数据库引用：
    SELECT *
FROM Employees

为了解析隐式数据库引用，SQL Server 使用当前数据库的概念。每个与 SQL Server 的连接总有一个数据库设为当前数据库。如果未指定数据库名称，则假定所有对象引用均为引用当前数据库。例如，如果一个连接将 Northwind 作为它的当前数据库，则所有引用名为 Products 的对象的语句都被解析到 Northwind 中的 Products 表。

每个 SQL Server 登录都有一个默认数据库。在由 sysadmin固定服务器角色的成员定义登录时，可以指定登录的默认数据库。如果未指定默认数据库，则 master 成为登录的默认数据库。登录的默认数据库可以在以后使用 sp_defaultdb 存储过程进行更改。

当您第一次连接到 SQL Server 时，登录的默认数据库通常成为当前数据库。但也可以指定特定的数据库，使其成为连接时的当前数据库。此请求覆盖为该登录所指定的默认数据库。以下是在连接请求时指定数据库的方法：

  • 在 osql 和 isql 实用工具中，使用 /d 开关指定数据库名称。

  • 在 ADO 中，在 ADO 连接对象的 Initial Catalog 属性里指定数据库名称。

  • 在 OLE DB 中，在 DBPROP_INIT_CATALOG 属性里指定数据库名称。

  • 在 ODBC 中，可以使用 Microsoft SQL Server DSN 配置向导中的"数据库"框或者 SQLConfigDataSource 调用中的 DATABASE = 参数在 ODBC 数据源中设置数据库名称。也可以指定调用 SQLDriverConnect 或 SQLBrowseConnect中的"DATABASE ="。

连接到 SQL Server 后，您在任何时候都可以切换当前数据库的设置。这称为使用或选择数据库。下面为切换当前数据库的方法：

  • 不管应用程序当前所使用的数据库 API，您均可以执行 Transact-SQL 的 USE database_name 语句。

  • 在 SQL 查询分析器中，可以在"查询"窗口顶端的"数据库"列表框中选择数据库。

  • 在 ODBC 中可以调用 SQLSetConnectAttr 以设置 SQL_ATTR_CURRENT_CATALOG 连接特性。

  • 在 DB-Library 中可以调用 dbuse 函数。

说明 在许多 Transact-SQL 引用示例中，"USE pubs"或"USE Northwind"是示例的第一行。这确保示例是在显式指定的数据库上执行而不是在另一个数据库上执行。

请参见

osql 实用工具

USE


使用多个语句

为执行无法使用单个 Transact-SQL 语句完成的进程，Microsoft® SQL Server™ 允许按下列几种方式将 Transact-SQL 语句组合在一起：

  • 使用批处理

    批处理是一个或多个 Transact-SQL 语句构成的组，它们是作为一个单元从应用程序发送到服务器的。SQL Server 将每个批处理作为单个可执行单元执行。

  • 使用存储过程

    存储过程是已在服务器上预定义并预编译的一组 Transact-SQL 语句。存储过程可以接受参数，并可以将结果集、返回代码和输出参数返回给调用应用程序。

  • 使用触发器

    触发器是特殊类型的存储过程。它不被应用程序直接调用。而是在用户对表执行指定的修改（INSERT、UPDATE 或 DELETE）时执行。

  • 使用脚本

    脚本是存储在文件中的一系列 Transact-SQL 语句。可以将此文件用作 osql 实用工具或 SQL 查询分析器的输入。然后，这些实用工具将执行存储在文件中的 Transact-SQL 语句。

下面的 SQL Server 功能使您得以一次控制多个 Transact-SQL 语句的使用：

  • 控制流语句

    允许加入条件逻辑。例如，如果国家为 Canada（加拿大），则执行一系列 Transact-SQL 语句。如果国家为 U.K.（英国），则执行其它的 Transact-SQL 语句。

  • 变量

    允许存储数据以便以后作为 Transact-SQL 语句的输入使用。例如，可以对一个查询进行编码，使其在每次执行时需要在 WHERE 子句中指定的不同数据值。可以编写查询在 WHERE 子句中使用变量，并对逻辑进行编码以使用适当的数据来填充变量。存储过程的参数是一种特殊的变量类。

  • 错误处理

    允许自定义 SQL Server 响应问题的方式。可以指定发生错误时要采取的相应操作，或显示自定义的错误信息，这些错误信息与一般的 SQL Server 错误相比可以给用户提供更多信息。


批处理

批处理是包含一个或多个 Transact-SQL 语句的组，从应用程序一次性地发送到 Microsoft® SQL Server™ 执行。SQL Server 将批处理语句编译成一个可执行单元，此单元称为执行计划。执行计划中的语句每次执行一条。

编译错误（如语法错误）使执行计划无法编译，从而导致批处理中的任何语句均无法执行。

运行时错误（如算术溢出或违反约束）会产生以下两种影响之一：

  • 大多数运行时错误将停止执行批处理中当前语句和它之后的语句。

  • 少数运行时错误（如违反约束）仅停止执行当前语句。而继续执行批处理中其它所有语句。

在遇到运行时错误之前执行的语句不受影响。唯一的例外是如果批处理在事务中而且错误导致事务回滚。在这种情况下，回滚运行时错误之前所进行的未提交的数据修改。

假定在批处理中有 10 条语句。如果第五条语句有一个语法错误，则不执行批处理中的任何语句。如果编译了批处理，而第二条语句在执行时失败，则第一条语句的结果不受影响，因为它已经执行。

以下规则适用于批处理：

  • CREATE DEFAULT、CREATE PROCEDURE、CREATE RULE、CREATE TRIGGER 和 CREATE VIEW 语句不能在批处理中与其它语句组合使用。批处理必须以 CREATE 语句开始。所有跟在该批处理后的其它语句将被解释为第一个 CREATE 语句定义的一部分。

  • 不能在同一个批处理中更改表，然后引用新列。

  • 如果 EXECUTE 语句是批处理中的第一句，则不需要 EXECUTE 关键字。如果 EXECUTE 语句不是批处理中的第一条语句，则需要 EXECUTE 关键字。


指定批处理

批处理作为数据库 API 的一部分执行。

  • 在 ADO 中，批处理是包括在 Command 对象 CommandText 属性中的 Transact-SQL 语句字符串：
    Dim Cmd As New ADODB.Command
Set Cmd.ActiveConnection = Cn
Cmd.CommandText = "SELECT * FROM Suppliers; SELECT * FROM Products"
Cmd.CommandType = adCmdText
Cmd.Execute
  • 在 OLE DB 中，批处理是包含在用于设置命令文本的字符串中的 Transact-SQL 语句字符串：
    WCHAR* wszSQLString =
L"SELECT * FROM Employees; SELECT * FROM Products";
hr = pICommandText->SetCommandText
        (DBGUID_DBSQL, wszSQLString)
  • 在 ODBC 中，批处理是包含在 SQLPrepare 或 SQLExecDirect 调用中的 Transact-SQL 语句字符串：
    SQLExecDirect(hstmt1,
    "SELECT * FROM Employees; SELECT * FROM Products",
    SQL_NTS):
  • 在 DB-Library 中，批处理由下列 Transact-SQL 语句组成，这些语句是在调用 dbsqlsend 或 dbsqlexec 之前用 dbcmd 或 dbfcmd 存储在命令缓冲区中的：
    dbcmd (dbproc,
    "SELECT * FROM Suppliers; SELECT * FROM Products");
dbsqlexec (dbproc);

一些数据访问工具（如 Microsoft® Access）没有显式批处理终止符。

GO 命令

SQL 查询分析器、osql 实用工具和 isql 实用工具使用 GO 命令作为批处理结尾的信号。GO 不是 Transact-SQL 语句；它只是向实用工具表明批处理中包含多少条 SQL 语句。在 SQL 查询分析器和 osql 中，在 GO 命令之间的所有 Transact-SQL 语句放在发送给 SQLExecDirect 的字符串中。在 isql 中，所有在两个 GO 命令之间的 Transact-SQL 语句在执行之前放入命令缓冲区。

例如，如果在 SQL 查询分析器中执行下列语句：

SELECT @@VERSION
SET NOCOUNT ON
GO

SQL 查询分析器将进行下列等效操作：

SQLExecDirect(hstmt,
"SELECT @@VERSION SET NOCOUNT ON",
SQL_NTS);

因为一个批处理是编译到一个执行计划中，所以在逻辑上必须完整。为一个批处理创建的执行计划不能引用任何在另一个批处理中声明的变量。注释必须在一个批处理中开始并结束。有关更多信息，请参见 SQL 查询分析器。

请参见

GO

osql 实用工具


批处理示例

以下示例是使用 SQL 查询分析器和 osql 实用工具的 GO 命令定义批处理边界的脚本。

下例创建一个视图。因为 CREATE VIEW 必须是批处理中的唯一语句，所以需要 GO 命令将 CREATE VIEW 语句与其周围的 USE 和 SELECT 语句隔离。

USE pubs
GO /* Signals the end of the batch */

CREATE VIEW auth_titles
AS
SELECT *
FROM authors
GO /* Signals the end of the batch */

SELECT *
FROM auth_titles
GO /* Signals the end of the batch */

下例说明将几个批处理组合成一个事务。BEGIN TRANSACTION 和 COMMIT 语句分隔事务边界。BEGIN TRANSACTION、USE、CREATE TABLE、SELECT 和 COMMIT 语句都包含在它们各自的单语句批处理中。所有的 INSERT 语句包含在一个批处理中。

BEGIN TRANSACTION
GO
USE pubs
GO
CREATE TABLE mycompanies
(
 id_num int IDENTITY(100, 5),
 company_name nvarchar(100)
)
GO
INSERT mycompanies (company_name)
   VALUES ('New Moon Books')
INSERT mycompanies (company_name)
   VALUES ('Binnet & Hardley')
INSERT mycompanies (company_name)
   VALUES ('Algodata Infosystems')
INSERT mycompanies (company_name)
   VALUES ('Five Lakes Publishing')
INSERT mycompanies (company_name)
   VALUES ('Ramona Publishers')
INSERT mycompanies (company_name)
   VALUES ('GGG&G')
INSERT mycompanies (company_name)
   VALUES ('Scootney Books')
INSERT mycompanies (company_name)
   VALUES ('Lucerne Publishing')
GO
SELECT *
FROM mycompanies
ORDER BY company_name ASC
GO
COMMIT
GO

下列脚本说明两个问题。首先，变量 @MyVar 在第二个批处理中定义而在第三个批处理中引用。而且第二个批处理中有注释的开始但没有结尾。第三个批处理有注释的结尾，但是当 osql 读取 GO 命令时，它将第一个批处理发送到 Microsoft® SQL Server™，由于"/*"没有与之匹配的"*/"而产生语法错误。

USE Northwind
GO
DECLARE @MyVar INT
/* Start of the split comment.
GO
End of the split comment. */
SELECT @MyVar = 29
GO


存储过程和触发器

存储过程是一组 Transact-SQL 语句，在一次编译后可以执行多次。因为不必重新编译 Transact-SQL 语句，所以执行存储过程可以提高性能。

触发器是一种特殊类型的存储过程，不由用户直接调用。创建触发器时会对其进行定义，以便在对特定表或列作特定类型的数据修改时执行。

CREATE PROCEDURE 或 CREATE TRIGGER 语句不能跨越批处理。即存储过程或触发器始终只能在一个批处理中创建并编译到一个执行计划中。

请参见

存储过程

用触发器强制执行业务规则


使用多个语句

 访问和更改关系数据

Transact-SQL 脚本

脚本是存储在文件中的一系列 Transact-SQL 语句。此文件可以用作 SQL 查询分析器或 osql 和 isql 实用工具的输入。然后，实用工具执行存储在文件中的 SQL 语句。

Transact-SQL 脚本包含一个或多个批处理。GO 命令作为批处理结束的信号。如果 Transact-SQL 脚本没有 GO 命令，则将它作为单个批处理执行。

Transact-SQL 脚本可以用于：

  • 保存用于创建和填充服务器上的数据库的操作的永久复本（备份机制）。

  • 必要时将语句从一台计算机转移到另一台计算机。

  • 通过让新雇员发现代码中的问题、了解代码或更改代码从而快速对其进行培训。


使用变量和参数

Transact-SQL 在其语句间传递数据有几种方式。其中有：

  • Transact-SQL 局部变量：

    Transact-SQL 变量是 Transact-SQL 批处理和脚本中能够保存数据值的对象。声明或定义变量后，批处理中的一个 Transact-SQL 语句可以设置变量值，而该批处理中后面的语句可以从变量获得此值。例如：

    DECLARE @EmpIDVar INT

SET @EmpIDVar = 1234

SELECT *
FROM Employees
WHERE EmployeeID = @EmpIDVar
  • Transact-SQL 参数：

    参数是在存储过程和执行该存储过程的批处理或脚本之间传递数据的对象。参数可以是输入参数也可以是输出参数。例如：

    CREATE PROCEDURE ParmSample @EmpIDParm INT AS
SELECT *
FROM Employees
WHERE EmployeeID = @EmpIDParm
GO

EXEC ParmSample @EmpIDParm = 1234
GO

应用程序使用应用程序变量和参数标记来处理 Transact-SQL 语句中的数据。

  • 应用程序变量

    应用程序编程语言如 C、C++、Basic 和 Java 都有它们自己保存数据的变量。使用数据库 API 的应用程序必须在使用数据之前将由 Transact-SQL 语句返回的数据移动到应用程序变量中。该操作通常通过称为绑定的进程来完成。应用程序使用 API 函数将结果集列绑定到程序变量。提取行时，API 提供程序或驱动程序将数据从列移动到绑定程序变量。

  • 参数标记

    ADO、OLE DB 和基于 ODBC 的数据库 API 支持参数标记。参数标记是放置在 Transact-SQL 语句中输入表达式位置的一个问号 (?)。该参数标记随后可绑定到应用程序变量。从而使应用程序变量的数据能够用作 Transact-SQL 语句的输入。参数标记也允许存储过程输出参数和返回代码绑定到应用程序变量。执行此过程后输出数据将返回到绑定变量。DB-Library API 也支持将存储过程参数和返回代码绑定到程序变量。

请参见

DECLARE @local_variable

SELECT

函数

SET @local variable


Transact-SQL 变量

Transact-SQL 局部变量是可以保存特定类型的单个数据值的对象。批处理和脚本中的变量通常用于：

  • 作为计数器计算循环执行的次数或控制循环执行的次数。

  • 保存数据值以供控制流语句测试。

  • 保存由存储过程返回代码返回的数据值。

下列脚本创建一个小的测试表并向其写入 26 行。脚本使用变量来执行下列三个操作：

  • 通过控制循环执行的次数来控制插入的行数。

  • 提供插入整数列的值。

  • 作为表达式一部分生成插入字符列的字母。
-- Create the table.
CREATE TABLE TestTable (cola INT, colb CHAR(3))
GO
SET NOCOUNT ON
GO
-- Declare the variable to be used.
DECLARE @MyCounter INT

-- Initialize the variable.
SET @MyCounter = 0

-- Test the variable to see if the loop is finished.
WHILE (@MyCounter < 26)
BEGIN
   -- Insert a row into the table.
   INSERT INTO TestTable VALUES
       -- Use the variable to provide the integer value
       -- for cola. Also use it to generate a unique letter
       -- for each row. Use the ASCII function to get the
       -- integer value of 'a'. Add @MyCounter. Use CHAR to
       -- convert the sum back to the character @MyCounter
       -- characters after 'a'.
       (@MyCounter,
        CHAR( ( @MyCounter + ASCII('a') ) )
       )
   -- Increment the variable to count this iteration
   -- of the loop.
   SET @MyCounter = @MyCounter + 1
END
GO
SET NOCOUNT OFF
GO
声明 Transact-SQL 变量

DECLARE 语句可以通过以下操作初始化 Transact-SQL 变量：

  • 指派名称。名称的第一个字符必须为 @。

  • 指派系统提供或用户定义的数据类型和长度。对于数字变量还指定精度和小数位数。

  • 将值设置为 NULL。

说明 对局部变量使用系统提供的数据类型可尽可能减少将来的维护问题。

例如，下面的 DECLARE 语句使用 int 数据类型创建名称为 @mycounter 的局部变量。

DECLARE @MyCounter INT

若要声明多个局部变量，请在定义的第一个局部变量后使用一个逗号，然后指定下一个局部变量名称和数据类型。

例如，下面的 DECLARE 语句创建三个局部变量，名称分别为 @last_name、@fname 和 @state，并将每个变量初始化为 NULL：

DECLARE @LastName NVARCHAR(30), @FirstName NVARCHAR(20), @State NCHAR(2)

变量的作用域为可以引用该变量的 Transact-SQL 语句范围。变量的作用域从声明变量的地方开始到声明变量的批处理或存储过程的结尾。例如，下面脚本产生语法错误，因为在一个批处理中所引用的变量是在另一个批处理中定义的：

DECLARE MyVariable INT
SET @MyVariable = 1
GO -- This terminates the batch.
-- @MyVariable has gone out of scope and no longer exists.

-- This SELECT statement gets a syntax error because it is
-- no longer legal to reference @MyVariable.
SELECT *
FROM Employees
WHERE EmployeeID = @MyVariable
设置 Transact-SQL 变量中的值

第一次声明变量时将此变量的值设为 NULL。若要为变量赋值，请使用 SET 语句。这是为变量赋值的较好的方法。也可以通过 SELECT 语句的选择列表中当前所引用值为变量赋值。

若要通过使用 SET 语句为变量赋值，请包含变量名和需要赋给变量的值。这是为变量赋值的较好的方法。例如，下面的批处理声明两个变量、对它们赋值并在 SELECT 语句的 WHERE 子句中予以使用：

USE Northwind
GO
-- Declare two variables.
DECLARE @FirstNameVariable NVARCHAR(20),
   @RegionVariable NVARCHAR(30)

-- Set their values.
SET @FirstNameVariable = N'Anne'
SET @RegionVariable = N'WA'

-- Use them in the WHERE clause of a SELECT statement.
SELECT LastName, FirstName, Title
FROM Employees
WHERE FirstName = @FirstNameVariable
   OR Region = @RegionVariable
GO

变量也可以通过选择列表中当前所引用的值赋值。如果在选择列表中引用变量，则它应当被赋以标量值或者 SELECT 语句应仅返回一行。例如：

USE Northwind
GO
DECLARE @EmpIDVariable INT

SELECT @EmpIDVariable = MAX(EmployeeID)
FROM Employees
GO

如果 SELECT 语句返回多行而且变量引用一个非标量表达式，则变量被设置为结果集最后一行中表达式的返回值。例如，在此批处理中将 @EmpIDVariable 设置为返回的最后一行的 EmployeeID 值，此值为 1：

USE Northwind
GO
DECLARE @EmpIDVariable INT

SELECT @EmpIDVariable = EmployeeID
FROM Employees
ORDER BY EmployeeID DESC

SELECT @EmpIDVariable
GO

请参见

DECLARE @local_variable

SET @local_variable

SELECT


参数

参数用于在存储过程和调用存储过程的应用程序或工具之间交换数据：

  • 输入参数允许调用方将数据值传递到存储过程。

  • 输出参数允许存储过程将数据值或游标变量传递回调用方。

  • 每个存储过程向调用方返回一个整数返回代码。如果存储过程没有显式设置返回代码的值，则返回代码为 0。

下面的存储过程说明了输入参数、输出参数和返回代码的使用：

USE Northwind
GO
-- Create a procedure that takes one input parameter
-- and returns one output parameter and a return code.
CREATE PROCEDURE SampleProcedure @EmployeeIDParm INT,
         @MaxQuantity INT OUTPUT
AS
-- Declare and initialize a variable to hold @@ERROR.
DECLARE @ErrorSave INT
SET @ErrorSave = 0

-- Do a SELECT using the input parameter.
SELECT FirstName, LastName, Title
FROM Employees
WHERE EmployeeID = @EmployeeIDParm

-- Save any nonzero @@ERROR value.
IF (@@ERROR <> 0)
   SET @ErrorSave = @@ERROR

-- Set a value in the output parameter.
SELECT @MaxQuantity = MAX(Quantity)
FROM [Order Details]

IF (@@ERROR <> 0)
   SET @ErrorSave = @@ERROR

-- Returns 0 if neither SELECT statement had
-- an error; otherwise, returns the last error.
RETURN @ErrorSave
GO

执行存储过程时，输入参数既可以将它们的值设置为常量也可以使用变量的值。输出参数和返回代码必须将其值返回变量。参数和返回代码可以与 Transact-SQL 变量或应用程序变量交换数据值。

如果从批处理或脚本调用存储过程，则参数和返回代码值可以使用在同一个批处理中定义的 Transact-SQL 变量。下面示例是执行以前创建的过程的批处理。输入参数被指定为常量，输出参数和返回代码将它们的值放在 Transact-SQL 变量中：

-- Declare the variables for the return code and output parameter.
DECLARE @ReturnCode INT
DECLARE @MaxQtyVariable INT

-- Execute the stored procedure and specify which variables
-- are to receive the output parameter and return code values.
EXEC @ReturnCode = SampleProcedure @EmployeeIDParm = 9,
   @MaxQuantity = @MaxQtyVariable OUTPUT

-- Show the values returned.
PRINT ' '
PRINT 'Return code = ' + CAST(@ReturnCode AS CHAR(10))
PRINT 'Maximum Quantity = ' + CAST(@MaxQtyVariable AS CHAR(10))
GO

应用程序可以通过绑定到程序变量的参数标记在应用程序变量、参数和返回代码之间交换数据。

请参见

SQL 存储过程

存储过程

参数标记

CREATE PROCEDURE

DECLARE @local_variable


应用程序变量

应用程序编程语言如 C、C++、Basic 和 Java 使用变量保存数据。变量是可以保存数据值的存储区域。每一个应用程序变量都有数据类型和大小。数字变量还有精度（变量可以有的数字个数）和小数位数（小数点右边的数字个数）。

为了使应用程序可以处理从 Transact-SQL 语句返回的数据，必须具有将 Transact-SQL 数据移动到应用程序变量的机制。数据库 API 支持如下概念，可以将 Transact-SQL 语句中的结果集列、参数、返回代码或参数标记绑定到应用程序变量。

若要检索结果集内的数据，应用程序使用与下文所示相似的进程：

  1. 执行 Transact-SQL 语句。

  2. 调用数据库 API 函数以查明结果集内的列数。

  3. 对于每个结果集列，应用程序可以：
    • 调用返回列的特性（数据类型、大小等）的数据库 API 函数。

    • 分配应用程序变量，其特性与列的特性相兼容。

    • 调用数据库 API 函数将结果集列与应用程序变量绑定或映射。
  4. 使用数据库 API 函数一次提取一行结果集行。每次提取时，每个结果集列的值均放入该列所绑定的应用程序变量中。

应用程序可以改变此进程。例如，如果应用程序正在已知表上执行硬编码 Transact-SQL 语句，由于预先知道结果集列的特性，所以应用程序不必调用数据库 API 来获得这些特性。

如果应用程序将结果集列绑定到应用程序变量，而此变量的数据类型与相关的数据库对象不同，则 OLE DB 提供程序或 ODBC 驱动程序必须转换数据。例如，如果应用程序将 money 列绑定到字符数组，则 OLE DB 提供程序或 ODBC 驱动程序必须将 money 数据转换为字符串。用于 SQL Server 的 Microsoft OLE DB 提供程序和 SQL Server ODBC 驱动程序的文档定义了其所支持的数据类型转换。

有关在应用程序中检索参数和返回代码的更多信息，请参见参数标记。

请参见

使用字段集合和字段对象

行集

处理结果

结果处理


参数标记

ADO、OLE DB 和基于 ODBC 的数据库 API 支持参数标记。参数标记是放置在 Transact-SQL 语句中输入或输出表达式位置的一个问号 (?)。对于要多次执行的同一 Transact-SQL 语句，但用于其输入和输出表达式的值不同的情况，参数标记使应用程序得以进行优化。

例如，用户可能给应用程序提供了五个不同的 stock 标记，并且应用程序必须调用存储过程来获得每个 stock 当前的数据。应用程序可以：

  • 准备下面的 Transact-SQL 语句：
    EXEC GetQuoteProcedure @StockSymbolParameter = ?
  • 将应用程序变量绑定到参数标记 (?)。

  • 执行循环：
    • 将下一个 stock 标记移动到绑定的变量。

    • 执行语句以检索该 stock 的报价。（有标记)

参数标记不仅可以用于映射到存储过程参数。它们还可以用于任何使用输入表达式的地方：

UPDATE Employees
SET Title = ?
WHERE EmployeeID = ?

而且，参数标记可以用于映射存储过程输出参数和返回代码。应用程序执行存储过程时，OLE DB 提供程序或 ODBC 驱动程序将数据值从任意输出参数或返回代码移动到该参数标记所绑定的变量中。例如，应用程序可以执行下列进程，该进程将返回一个整数返回代码和一个字符输出参数：

  1. 准备语句：
    {? = CALL MyProc (?)}
  2. 将第一个参数标记绑定到整数变量，将第二个绑定到字符数组。

  3. 执行语句。

  4. 提取或取消存储过程所返回的所有结果集。

此时，用于 SQL Server 的 Microsoft OLE DB 提供程序或 SQL Server ODBC 驱动程序会将返回代码和输出参数值放置在绑定的变量中。在返回给客户端的最后一个数据包中，Microsoft® SQL Server™ 返回输出参数和返回代码。因此，只有在应用程序处理或取消存储过程所返回的所有结果集后，才可以访问返回代码和输出参数值。

此进程中，ADO API 执行存储过程可以有变化。ADO 应用程序可以：

  1. 将 Command 对象类型设置为 adCmdStoredProc。

  2. 将命令文本设置为过程名称。

  3. 生成将所有参数和返回代码绑定到应用程序变量的 Parameters 集合。

  4. 执行 Command 对象。

参数标记与具有特定数据类型的数据库对象相关联。如果应用程序将参数标记绑定到某个变量，而此变量的数据类型与相关的数据库对象不同，则 OLE DB 提供程序或 ODBC 驱动程序必须转换数据。例如，如果应用程序将整数返回代码绑定到字符数组，则 OLE DB 提供程序或 ODBC 驱动程序必须将返回代码整数数据转换为字符串。有关支持的数据类型转换的信息，请参见用于 SQL Server 的 OLE DB 提供程序和 SQL Server ODBC 驱动程序的文档。

请参见

执行已准备的语句

参数连接

参数对象

运行存储过程 (OLE DB)

命令参数

使用语句参数


控制流

Transact-SQL 提供称为控制流语言的特殊关键字，用于控制 Transact-SQL 语句、语句块和存储过程的执行流。这些关键字可用于 Transact-SQL 语句、批处理和存储过程中。

不使用控制流语言，则各 Transact-SQL 语句按其出现的顺序分别执行。控制流语言使用与程序设计相似的构造使语句得以互相连接、关联和相互依存。

当您需要 Transact-SQL 进行某种操作时，这些控制流关键字非常有用。例如，当在一个逻辑块中包含多个 Transact-SQL 语句时，请使用 BEGIN...END 语句对。使用 IF...ELSE 语句对的情况是：IF（如果）满足某条件，则执行某些语句或语句块，而如果不满足此条件（ELSE 条件）则执行另一条语句或语句块。

控制流语句不能跨越多个批处理或存储过程。

以下是控制流关键字。

BEGIN...END | WAITFOR
GOTO | WHILE
IF...ELSE | BREAK
RETURN | CONTINUE

请参见

使用 CASE 语句

CONTINUE

使用 GOTO

DECLARE @local_variable

使用 RETURN

ELSE (IF...ELSE)

使用 WAITFOR

END (BEGIN...END)

控制流语言

EXECUTE

--（注释）

IF...ELSE

/*...*/（注释）

PRINT

BEGIN...END

RAISERROR

BREAK

WHILE


使用 BEGIN...END

BEGIN 和 END 语句用于将多个 Transact-SQL 语句组合为一个逻辑块。任何时候当控制流语句必须执行一个包含两条或两条以上 Transact-SQL 语句的语句块时，请使用 BEGIN 和 END 语句。

例如，当 IF 语句仅控制一条 Transact-SQL 语句的执行时，不需要使用 BEGIN 和 END 语句：

IF (@@ERROR <> 0)
   SET @ErrorSaveVariable = @@ERROR

如果 @@ERROR 为 0，则仅跳过 SET 语句。

用 BEGIN 和 END 语句可以使 IF 语句在取值为 FALSE 时跳过语句块：

IF (@@ERROR <> 0)
BEGIN
   SET @ErrorSaveVariable = @@ERROR
   PRINT 'Error encountered, ' +
         CAST(@ErrorSaveVariable AS VARCHAR(10))
END

BEGIN 和 END 语句必须成对使用：任何一条语句均不能单独使用。BEGIN 语句行后为 Transact-SQL 语句块。最后，END 语句行指示语句块结束。

BEGIN 和 END 语句用于下列情况：

  • WHILE 循环需要包含语句块。

  • CASE 函数的元素需要包含语句块。

  • IF 或 ELSE 子句需要包含语句块。

请参见

BEGIN...END

END (BEGIN...END)

控制流语言


使用 GOTO

GOTO 语句使 Transact-SQL 批处理的执行跳转到标签。不执行 GOTO 语句和标签之间的语句。使用下列语法定义标签名：

label_name:

尽量少使用 GOTO 语句。过多使用 GOTO 语句可能会使 Transact-SQL 批处理的逻辑难于理解。使用 GOTO 实现的逻辑几乎完全可以使用其它控制流语句实现。GOTO 最好用于跳出深层嵌套的控制流语句。

标签是 GOTO 的目标，它仅标识了跳转的目标。标签不隔离其前后的语句。执行标签前面语句的用户将跳过标签并执行标签后的语句。除非标签前面的语句本身是控制流语句（如 RETURN），这种情况才会发生。

下面为 GOTO 的示例：

IF (SELECT SYSTEM_USER()) = 'payroll'
   GOTO calculate_salary
-- Other program code would appear here.
-- When the IF statement evaluates to TRUE, the statements
-- between the GOTO and the calculate_salary label are
-- ignored. When the IF statement evaluates to FALSE the
-- statements following the GOTO are executed.
calculate_salary:
   -- Statements to calculate a salary would appear after the label.

请参见

GOTO


使用 IF...ELSE

IF 语句用于条件的测试。结果流的控制取决于是否指定了可选的 ELSE 语句：

  • 指定 IF 而无 ELSE

    IF 语句取值为 TRUE 时，执行 IF 语句后的语句或语句块。IF 语句取值为 FALSE 时，跳过 IF 语句后的语句或语句块。

  • 指定 IF 并有 ELSE

    IF 语句取值为 TRUE 时，执行 IF 语句后的语句或语句块。然后控制跳到 ELSE 语句后的语句或语句块之后的点。IF 语句取值为 FALSE 时，跳过 IF 语句后的语句或语句块，而执行 ELSE 语句后的语句或语句块。

例如，如果存储过程一直保存事务中 @@ERROR 返回的错误代码，则在过程结尾可能有与下面语句相似的 IF 语句：

IF (@ErrorSaveVariable <> 0)
BEGIN
   PRINT 'Errors encountered, rolling back.'
   PRINT 'Last error encountered: ' +
      CAST(@ErrorSaveVariable AS VARCHAR(10))
   ROLLBACK
END
ELSE
BEGIN
   PRINT 'No Errors encountered, committing.'
   COMMIT
END
RETURN @ErrorSaveVariable

请参见

ELSE (IF...ELSE)

IF...ELSE


使用 RETURN

RETURN 语句无条件终止查询、存储过程或批处理。存储过程或批处理中 RETURN 语句后面的语句都不执行。

当在存储过程中使用 RETURN 语句时，此语句可以指定返回给调用应用程序、批处理或过程的整数值。如果 RETURN 未指定值，则存储过程返回 0。

大多数存储过程按常规使用返回代码表示存储过程的成功或失败。没有发生错误时存储过程返回值 0。任何非零值表示有错误发生。例如：

USE Northwind
GO
-- Create a procedure that takes one input parameter
-- and returns one output parameter and a return code.
CREATE PROCEDURE SampleProcedure @EmployeeIDParm INT,
         @MaxQuantity INT OUTPUT
AS
-- Declare and initialize a variable to hold @@ERROR.
DECLARE @ErrorSave INT
SET @ErrorSave = 0

-- Do a SELECT using the input parameter.
SELECT FirstName, LastName, Title
FROM Employees
WHERE EmployeeID = @EmployeeIDParm

-- Save any nonzero @@ERROR value.
IF (@@ERROR <> 0)
   SET @ErrorSave = @@ERROR

-- Set a value in the output parameter.
SELECT @MaxQuantity = MAX(Quantity)
FROM [Order Details]

IF (@@ERROR <> 0)
   SET @ErrorSave = @@ERROR

-- Returns 0 if neither SELECT statement had
-- an error, otherwise returns the last error.
RETURN @ErrorSave
GO

执行存储过程的 Transact-SQL 批处理或存储过程可以将返回代码检索至整型变量：

DECLARE @ReturnStatus INT
DECLARE @MaxQtyVariable INT
EXECUTE @ReturnStatus = SampleProcedure @EmployeeIDParm = 9,
         @MaxQtyVariable = @MaxQuantity OUTPUT

-- Show the values returned.
PRINT ' '
PRINT 'Return code = ' + CAST(@ReturnStatus AS CHAR(10))
PRINT 'Maximum Quantity = ' + CAST(@MaxQtyVariable AS CHAR(10))
GO

调用存储过程的应用程序可以将返回代码所对应的参数标记与整型变量绑定。

请参见

EXECUTE

RETURN

参数标记


使用 WAITFOR

WAITFOR 语句挂起执行连接，直到发生下列两种情况之一：

  • 已超过指定的时间间隔。

  • 到达一天中指定的时间。

WAITFOR 语句由下面两个子句之一指定：

  • DELAY 关键字后为 amount_of_time_to_pass，是在完成 WAITFOR 语句之前等待的时间。完成 WAITFOR 语句之前等待的时间最多为 24 小时。

  • TIME 关键字后为 time_to_execute，它指定 WAITFOR 语句完成的时间。

下面示例使用 DELAY 关键字指定在执行 SELECT 语句之前等待两秒：

WAITFOR DELAY '00:00:02'
SELECT EmployeeID FROM Northwind.dbo.Employees

下面示例使用 TIME 关键字指定在 10 P.M 以后对指定数据库 pubs 进行检查，以确保所有页的分配和使用正确：

USE pubs
BEGIN
   WAITFOR TIME '22:00'
   DBCC CHECKALLOC
END

WAITFOR 语句的缺点是与应用程序的连接一直挂起直到 WAITFOR 完成为止。当应用程序或存储过程的处理必须挂起相对有限的时间时最好使用WAITFOR。在一天中的特定时间执行某种操作较好的方法是使用 SQL Server 代理或 SQL-DMO 来调度任务。

请参见

WAITFOR


使用 WHILE...BREAK 或 CONTINUE

只要指定的的条件为真，则 WHILE 语句重复语句或语句块。

有两条 Transact-SQL 语句通常与 WHILE 同时使用：BREAK 或 CONTINUE。BREAK 语句退出最内层 WHILE 循环，CONTINUE 语句重新开始 WHILE 循环。如果没有其它行可以处理，则程序可能执行 BREAK 语句。如果要继续执行代码，则可执行 CONTINUE 语句。

说明 如果将 SELECT 语句用作 WHILE 语句的条件，则 SELECT 语句必须在英文括号中。

下面的示例使用 WHILE 语句控制执行的提取数：

USE Northwind
GO
DECLARE abc CURSOR FOR
SELECT * FROM Shippers

OPEN abc

FETCH NEXT FROM abc
WHILE (@@FETCH_STATUS = 0)
   FETCH NEXT FROM abc

CLOSE abc
DEALLOCATE abc
GO

其它有效的 WHILE 条件测试可能如下所示：

WHILE (@ACounterVariable < 100)

或

WHILE EXISTS(SELECT au_lname FROM authors WHERE au_fname = 'Anne')

请参见

BEGIN...END

END (BEGIN...END)

BREAK

WHILE

CONTINUE


使用 CASE

CASE 函数是特殊的 Transact-SQL 表达式，它允许按列值显式可选值。数据中的更改是临时的，没有对数据进行永久更改。例如，CASE 函数可以在 state 列中有 CA 值的行的查询结果集内显示 California。

CASE 函数包含：

  • CASE 关键字。

  • 需要转换的列名称。

  • 指定要搜索的表达式的 WHEN 子句和指定要替换它们的表达式的 THEN 子句。

  • END 关键字。

  • 可选的、定义 CASE 函数别名的 AS 子句。

下面示例在查询结果集内显示每个作者所居住州的全名：

SELECT au_fname, au_lname,
   CASE state
      WHEN 'CA' THEN 'California'
      WHEN 'KS' THEN 'Kansas'
      WHEN 'TN' THEN 'Tennessee'
      WHEN 'OR' THEN 'Oregon'
      WHEN 'MI' THEN 'Michigan'
      WHEN 'IN' THEN 'Indiana'
      WHEN 'MD' THEN 'Maryland'
      WHEN 'UT' THEN 'Utah'
        END AS StateName
FROM pubs.dbo.authors
ORDER BY au_lname

请参见

CASE

NULLIF

COALESCE

使用 CASE 进行条件数据处理


运行时生成语句

在调用数据库 API 函数或方法执行 SQL 语句前，大多数 Microsoft® SQL Server™ 应用程序必须在运行时动态生成这些语句。例如，使用 ODBC 的 C 语言应用程序动态地将一个或多个 SQL 语句生成到字符数组中，然后将此数组传递给 ODBC SQLPrepare 或 SQLExecDirect 函数。

Transact-SQL 支持使用以下两种方法在运行时生成 Transact-SQL 脚本、存储过程和触发器中的 SQL 语句：

  • 使用 sp_executesql 系统存储过程执行 Unicode 字符串。sp_executesql 支持与 RAISERROR 语句相似的参数替换。

  • 使用 EXECUTE 语句执行字符串。EXECUTE 语句不支持在执行的字符串中的参数替换。

下面是使用 sp_executesql 执行动态生成包含 SQL 语句的字符串的简单示例：

USE Northwind
DECLARE @SQLString NVARCHAR(500)

/* Set column list. CHAR(13) is a carriage return, line feed.*/
SET @SQLString = N'SELECT FirstName, LastName, Title' + CHAR(13)

/* Set FROM clause with carriage return, line feed. */
SET @SQLString = @SQLString + N'FROM Employees' + CHAR(13)

/* Set WHERE clause. */
SET @SQLString = @SQLString + N'WHERE LastName LIKE ''D%'''

EXEC sp_executesql @SQLString
GO


使用 sp_executesql

建议使用 sp_executesql 而不要使用 EXECUTE 语句执行字符串。支持参数替换不仅使 sp_executesql 比 EXECUTE 更通用，而且还使 sp_executesql 更有效，因为它生成的执行计划更有可能被 SQL Server 重新使用。

自包含批处理

sp_executesql 或 EXECUTE 语句执行字符串时，字符串被作为其自包含批处理执行。SQL Server 将Transact-SQL 语句或字符串中的语句编译进一个执行计划，该执行计划独立于包含 sp_executesql 或 EXECUTE 语句的批处理的执行计划。下列规则适用于自含的批处理：

  • 直到执行 sp_executesql 或EXECUTE 语句时才将sp_executesql 或 EXECUTE 字符串中的 Transact-SQL 语句编译进执行计划。执行字符串时才开始分析或检查其错误。执行时才对字符串中引用的名称进行解析。

  • 执行的字符串中的 Transact-SQL 语句，不能访问 sp_executesql 或 EXECUTE 语句所在批处理中声明的任何变量。包含 sp_executesql 或 EXECUTE 语句的批处理不能访问执行的字符串中定义的变量或局部游标。

  • 如果执行字符串有更改数据库上下文的 USE 语句，则对数据库上下文的更改仅持续到 sp_executesql 或 EXECUTE 语句完成。

通过执行下列两个批处理来举例说明：

/* Show not having access to variables from the calling batch. */
DECLARE @CharVariable CHAR(3)
SET @CharVariable = 'abc'
/* sp_executesql fails because @CharVariable has gone out of scope. */
sp_executesql N'PRINT @CharVariable'
GO

/* Show database context resetting after sp_executesql completes. */
USE pubs
GO
sp_executesql N'USE Northwind'
GO
/* This statement fails because the database context
   has now returned to pubs. */
SELECT * FROM Shippers
GO
替换参数值

sp_executesql 支持对 Transact-SQL 字符串中指定的任何参数的参数值进行替换，但是 EXECUTE 语句不支持。因此，由 sp_executesql 生成的 Transact-SQL 字符串比由 EXECUTE 语句所生成的更相似。SQL Server 查询优化器可能将来自 sp_executesql 的 Transact-SQL 语句与以前所执行的语句的执行计划相匹配，以节约编译新的执行计划的开销。

使用 EXECUTE 语句时，必须将所有参数值转换为字符或 Unicode 并使其成为 Transact-SQL 字符串的一部分：

DECLARE @IntVariable INT
DECLARE @SQLString NVARCHAR(500)
/* Build and execute a string with one parameter value. */
SET @IntVariable = 35
SET @SQLString = N'SELECT * FROM pubs.dbo.employee WHERE job_lvl = ' +
                 CAST(@IntVariable AS NVARCHAR(10))
EXEC(@SQLString)
/* Build and execute a string with a second parameter value. */
SET @IntVariable = 201
SET @SQLString = N'SELECT * FROM pubs.dbo.employee WHERE job_lvl = ' +
                 CAST(@IntVariable AS NVARCHAR(10))
EXEC(@SQLString)

如果语句重复执行，则即使仅有的区别是为参数所提供的值不同，每次执行时也必须生成全新的 Transact-SQL 字符串。从而在下面几个方面产生额外的开销：

  • SQL Server 查询优化器具有将新的 Transact-SQL 字符串与现有的执行计划匹配的能力，此能力被字符串文本中不断更改的参数值妨碍，特别是在复杂的 Transact-SQL 语句中。

  • 每次执行时均必须重新生成整个字符串。

  • 每次执行时必须将参数值（不是字符或 Unicode 值）投影到字符或 Unicode 格式。

sp_executesql 支持与 Transact-SQL 字符串相独立的参数值的设置：

DECLARE @IntVariable INT
DECLARE @SQLString NVARCHAR(500)
DECLARE @ParmDefinition NVARCHAR(500)

/* Build the SQL string once. */
SET @SQLString =
     N'SELECT * FROM pubs.dbo.employee WHERE job_lvl = @level'
/* Specify the parameter format once. */
SET @ParmDefinition = N'@level tinyint'

/* Execute the string with the first parameter value. */
SET @IntVariable = 35
EXECUTE sp_executesql @SQLString, @ParmDefinition,
                      @level = @IntVariable
/* Execute the same string with the second parameter value. */
SET @IntVariable = 32
EXECUTE sp_executesql @SQLString, @ParmDefinition,
                      @level = @IntVariable

此 sp_executesql 示例完成的任务与前面的 EXECUTE 示例所完成的相同，但有下列额外优点：

  • 因为 Transact-SQL 语句的实际文本在两次执行之间未改变，所以查询优化器应该能将第二次执行中的 Transact-SQL 语句与第一次执行时生成的执行计划匹配。这样，SQL Server 不必编译第二条语句。

  • Transact-SQL 字符串只生成一次。

  • 整型参数按其本身格式指定。不需要转换为 Unicode。

说明 为了使 SQL Server 重新使用执行计划，语句字符串中的对象名称必须完全符合要求。

重新使用执行计划

在 SQL Server 早期的版本中要重新使用执行计划的唯一方式是，将 Transact-SQL 语句定义为存储过程然后使应用程序执行此存储过程。这就产生了管理应用程序的额外开销。使用 sp_executesql 有助于减少此开销，并使 SQL Server 得以重新使用执行计划。当要多次执行某个 Transact-SQL 语句，且唯一的变化是提供给该 Transact-SQL 语句的参数值时，可以使用 sp_executesql 来代替存储过程。因为 Transact-SQL 语句本身保持不变仅参数值变化，所以 SQL Server 查询优化器可能重复使用首次执行时所生成的执行计划。

下例为服务器上除四个系统数据库之外的每个数据库生成并执行 DBCC CHECKDB 语句：

USE master
GO
SET NOCOUNT ON
GO
DECLARE AllDatabases CURSOR FOR
SELECT name FROM sysdatabases WHERE dbid > 4

OPEN AllDatabases

DECLARE @DBNameVar NVARCHAR(128)
DECLARE @Statement NVARCHAR(300)

FETCH NEXT FROM AllDatabases INTO @DBNameVar
WHILE (@@FETCH_STATUS = 0)
BEGIN
   PRINT N'CHECKING DATABASE ' + @DBNameVar
   SET @Statement = N'USE ' + @DBNameVar + CHAR(13)
      + N'DBCC CHECKDB (' + @DBNameVar + N')'
   EXEC sp_executesql @Statement
   PRINT CHAR(13) + CHAR(13)
   FETCH NEXT FROM AllDatabases INTO @DBNameVar
END

CLOSE AllDatabases
DEALLOCATE AllDatabases
GO
SET NOCOUNT OFF
GO

当目前所执行的 Transact-SQL 语句包含绑定参数标记时，SQL Server ODBC 驱动程序使用 sp_executesql 完成 SQLExecDirect。但例外情况是 sp_executesql 不用于执行中的数据参数。这使得使用标准 ODBC 函数或使用在 ODBC 上定义的 API（如 RDO）的应用程序得以利用 sp_executesql 所提供的优势。定位于 SQL Server 2000 的现有的 ODBC 应用程序不需要重写就可以自动获得性能增益。有关更多信息，请参见使用语句参数。

用于 SQL Server 的 Microsoft OLE DB 提供程序也使用 sp_executesql 直接执行带有绑定参数的语句。使用 OLE DB 或 ADO 的应用程序不必重写就可以获得 sp_executesql 所提供的优势。

请参见

DECLARE @local_variable

SELECT

EXECUTE


权限

Microsoft® SQL Server™ 2000 数据库中的每个对象都有所有者，而所有者通常是对创建该对象的连接有效的用户 ID。其他用户只有在相应所有者对其用户 ID 进行了访问授权后，方可访问该对象。

某些 Transact-SQL 语句也要受到特定用户 ID的限制。例如，CREATE DATABASE 仅限于 sysadmin 和 dbcreator 固定服务器角色的成员使用。除非得到授权，否则用户不能访问对象或执行语句。

用户发出的所有 Transact-SQL 语句均受限于给用户赋予的权限。sysadmin 固定服务器角色的成员、db_owner 固定数据库角色的成员以及数据库对象的所有者都可授予、拒绝或废除某人或某个角色的权限。当使用 Transact-SQL 时，使用 GRANT、DENY 和 REVOKE 语句可指定谁可使用哪种数据修改语句：

  • GRANT 赋予使用或执行其它 Transact-SQL 语句的权限。

  • DENY 拒绝权限并防止指定的用户、组或角色从组和角色成员的关系中继承权限。

  • REVOKE 取消以前授予或拒绝了的权限。

可授予对象的权限有：

  • SELECT

    允许用户对表或视图发出 SELECT 语句。

  • INSERT

    允许用户对表或视图发出 INSERT 语句。

  • UPDATE

    允许用户对表或视图发出 UPDATE 语句。

  • DELETE

    允许用户对表或视图发出 DELETE 语句。

  • REFERENCES

    允许用户对表的主键和唯一列生成外键引用。还用于允许从视图和函数中引用 SCHEMABINDING。

  • EXECUTE

    允许用户对存储过程发出 EXECUTE 语句。

也可通过授予权限来执行通常限于具有特定角色的成员使用的 Transact-SQL 语句。例如，sysadmin 固定服务器角色的成员可授予通常无法创建数据库的用户 CREATE DATABASE 权限。

请参见

登录

拒绝

登录、用户、角色和组

授权

安全管理

废除


使用 SQL Server 中的选项

Microsoft® SQL Server™ 2000 提供了影响 SQL 语句的结果和性能的选项。Transact-SQL 使您得以通过下列方式设置这些选项：

  • 服务器范围的配置选项（服务器选项）可通过执行 sp_configure 存储过程来设置。

  • 数据库级的选项（数据库选项）可通过执行 sp_dboption 存储过程来进行设置。

  • 数据库兼容级别可通过执行 sp_dbcmptlevel 存储过程来设置。

  • 连接级的选项（SET 选项）可用 SET 语句来指定，例如 SET ANSI_PADDING 和 SET ANSI_NULLS。

  • 语句级的选项（查询提示、表提示和连接提示）可在单独的 Transact-SQL 语句中指定。

ODBC 应用程序可指定控制一些 ANSI SET 选项的连接选项。用于 SQL Server 的 Microsoft OLE DB 提供程序和 SQL Server ODBC 驱动程序在默认状态下均可设置多个 SET 选项。也可通过使用 SQL Server 企业管理器设置选项。

避免更改 SET 选项和通过 SET 语句设置这些选项。建议通过 ODBC 或 OLE DB 的连接属性在连接级别上设置 SET 选项。或者使用 sp_configure 存储过程更改 SET 选项设置。

选项层次结构

当某一选项在多个级别受到支持时：

  1. 数据库选项替代服务器选项。

  2. SET 选项替代数据库选项。

  3. 提示替代 SET 选项。

    说明 sp_configure 提供了一个user options 选项，该选项使您得以更改几个 SET 选项的默认值。尽管user options 看似一个服务器选项，但它其实是 SET 选项。


SET 选项

下表包含按字母顺序列出的 SET 选项和 Microsoft® SQL Server™ 2000 中所支持的相应的数据库和服务器选项。

SET 选项 		数据库选项 服务器
选项 		默认设置
ANSI_DEFAULTS | 无 | 无 | 暂缺
ANSI_NULL_DFLT_OFF
ANSI_NULL_DFLT_ON 		ANSI null default user options 指定默认设置 OFF
ANSI_NULLS | ANSI nulls | user options 指定默认设置 | OFF
ANSI_PADDING | 无 | user options 指定默认设置 | ON
ANSI_WARNINGS | ANSI warnings | user options 指定默认设置 | OFF
ARITHABORT | 无 | user options 指定默认设置 | OFF
ARITHIGNORE | 无 | user options 指定默认设置 | OFF
CONCAT_NULL_YIELDS_NULL | concat null yields null | 无 | OFF
CONTEXT_INFO | 无 | 无 | OFF
CURSOR_CLOSE_ON_COMMIT | cursor close on commit | user options 指定默认设置 | OFF
DATEFIRST | 无 | 无 | 7
DATEFORMAT | 无 | 无 | mdy
DEADLOCK_PRIORITY | 无 | 无 | NORMAL
DISABLE_DEF_CNST_CHK | 无 | user options 指定默认设置 | OFF
FIPS_FLAGGER | 无 | 无 | OFF
FMTONLY | 无 | 无 | OFF
SET FORCEPLAN | 无 | 无 | OFF
IDENTITY_INSERT | 无 | OFF
IMPLICIT_TRANSACTIONS | 无 | user options 指定默认设置 | OFF
LANGUAGE | 无 | 无 | us_english
LOCK_TIMEOUT 无 无
  • 无限制
NOCOUNT | 无 | user options 指定默认设置 | OFF
NOEXEC | 无 | 无 | OFF
NUMERIC_ROUNDABORT | 无 | 无 | OFF
OFFSETS | 无 | 无 | OFF
PARSEONLY | 无 | 无 | OFF
QUERY_GOVERNOR_COST_LIMIT | 无 | query governor cost limit | OFF
QUOTED_IDENTIFIER | quoted identifier | user options 指定默认设置 | OFF
REMOTE_PROC_TRANSACTIONS | 无 | 无 | OFF
ROWCOUNT | 无 | 无 | OFF
SHOWPLAN_ALL | 无 | 无 | OFF
SHOWPLAN_TEXT | 无 | 无 | OFF
STATISTICS IO | 无 | 无 | OFF
STATISTICS PROFILE | 无 | 无 | 暂缺
STATISTICS TIME | 无 | 无 | OFF
TEXTSIZE | 无 | 无 | OFF
TRANSACTION ISOLATION LEVEL | 无 | 无 | 暂缺
XACT_ABORT | 无 | 无 | OFF

分析时和执行时的 SET 选项

SET 选项生效的时机取决于该选项是分析时选项还是执行时选项。分析时选项在分析过程中，在文本中遇到这些选项时生效，而不必考虑流语句的控制。执行时选项在执行指定这些选项的代码的过程中生效。如果在执行 SET 语句前执行失败，将不设置该选项。如果在执行 SET 语句后执行失败，将设置该选项。

QUOTED_IDENTIFIER、PARSEONLY、OFFSETS 和 FIPS_FLAGGER 选项均为分析时选项。所有其它 SET 选项均为执行时选项。

出现于批处理或存储过程中的 SET QUOTED_IDENTIFIER 和 SET ANSI_NULLS 语句将不影响该批处理或存储过程。相反，用于批处理或存储过程内部语句的设置则是在创建批处理或存储过程时生效的设置。

SET 选项的持续时间

本节讲述了 SET 选项的持续时间。

  • 由用户通过脚本设置的 SET 选项将在重置或用户与服务器的会话终止时应用。

  • 在存储过程或触发器中设置的 SET 选项将在该存储过程或触发器中重置时或控制返回到唤醒调用该存储过程或触发器的代码时生效。

  • 除非明确进行重置，否则在存储过程或触发器中应用来自所有更高级代码的 SET 选项值。

  • 除非显式或隐性重置，否则为连接设置的 SET 选项将在连接到其它数据库后应用。

    说明 需要额外考虑的是，当用户连接到数据库时，根据以前使用 user options 服务器选项所指定的值或应用于所有 ODBC 和 OLE DB 连接的值，一些选项可能被自动设置为 ON。

SET 选项快捷方式

Transact-SQL 提供了 SET ANSI_DEFAULTS 语句，作为设置这些 SQL-92 标准选项的快捷方式：

  • SET ANSI_NULLS

  • SET CURSOR_CLOSE_ON_COMMIT

  • SET ANSI_NULL_DFLT_ON

  • SET IMPLICIT_TRANSACTIONS

  • SET ANSI_PADDING

  • SET QUOTED_IDENTIFIER

  • SET ANSI_WARNINGS

该快捷方式可重置这些选项的值。使用该快捷方式后所设置的任何单个选项都可用来替代由该快捷方式所设置的相应的值。

说明 SET ANSI_DEFAULTS 并不设置所有遵从 SQL-92 标准所需的选项。


数据库选项

下表包含按字母顺序列出的数据库选项和 Microsoft® SQL Server™ 2000 中所支持的相应的 SET 和服务器选项。

数据库选项 | SET 选项 | 服务器 选项 | 默认 设置
ANSI null default | ANSI_NULL_DFLT_ON ANSI_NULL_DFLT_OFF | user options 指定默认设置 | OFF
ANSI nulls | ANSI_NULLS | user options 指定默认设置 | OFF
ANSI warnings | ANSI_WARNINGS | user options 指定默认设置 | OFF
auto create statistics | 无 | 无 | ON
auto update statistics | 无 | 无 | ON
autoclose | 无 | 无 | FALSE1
autoshrink | 无 | 无 | FALSE
concat null yields null | CONCAT_NULL_YIELDS_NULL | 无 | OFF
cursor close on commit | CURSOR_CLOSE_ON_COMMIT | user options 指定默认设置 | OFF
dbo use only | 无 | 无 | FALSE
default to local cursor | 无 | 无 | FALSE
merge publish | 无 | 无 | FALSE
offline | 无 | 无 | FALSE
published | 无 | 无 | FALSE
quoted identifier | QUOTED_IDENTIFIER | user options 指定默认设置 | OFF
read only | 无 | 无 | FALSE
recursive triggers | 无 | 无 | FALSE
select into/bulkcopy | 无 | 无 | FALSE
single user | 无 | 无 | FALSE
subscribed | 无 | 无 | TRUE
torn page detection |  | 无 | TRUE
trunc. log on chkpt. | 无 | 无 | TRUE

1 默认情况下，在 SQL Server 2000 Desktop Engine 中将 autoclose 设置为 TRUE。

用于新数据库的默认数据库选项在 model 数据库中定义。在 SQL Server 的新安装中，model 和 master 数据库中的设置相同。

更改数据库选项将强制重新编译高速缓存中的所有内容。

选项和数据库上下文

脚本的数据库上下文和脚本中的批处理由最近的连接决定。连接可使用 USE 语句在 Transact-SQL 中显式设置，并可在诸如 OBDC 和 OLE DB 的其它环境中以显式和隐性方式进行设置。有关更多信息，请参见选择数据库。

当存储过程从批处理或另一个存储过程中执行时，它将根据所存储的数据库中的选项设置执行。例如，当存储过程 db1.dbo.sp1 调用存储过程 db2.dbo.sp2 时，sp1 根据 db1 的当前兼容级别设置执行，而 sp2 根据 db2 的当前兼容级别设置执行。

当 Transact-SQL 语句引用多个数据库中的对象时，当前数据库上下文和当前连接上下文（如果位于批处理中，则是由 USE 语句所定义的数据库；如果位于存储过程中，则是包含该存储过程的数据库）将应用于该语句。


服务器选项

下表包含按字母顺序列出的服务器选项和 Microsoft® SQL Server™ 2000 中所支持的相应的 SET 和数据库选项。

服务器选项 | SET 选项 | 数据库 选项 | 默认 设置
affinity mask | 无 | 无 | 0
allow updates | 无 | 无 | 0
awe enabled | 无 | 无 | 0
c2 audit mode | 无 | 无 | 0
cost threshold for parallelism | 无 | 无 | 5
cursor threshold | 无 | 无 | -1
default full-text language | 无 | 无 | 1033
default language | 无 | 无 | 0
fill factor | 无 | 无 | 0
index create memory | 无 | 无 | 0
lightweight pooling | 无 | 无 | 0
locks | 无 | 无 | 0
max degree of parallelism | 无 | 无 | 0
max server memory | 无 | 无 | 2147483647
max text repl size | 无 | 无 | 65536
max worker threads | 无 | 无 | 255
media retention | 无 | 无 | 0
min memory per query | 无 | 无 | 1024
min server memory | 无 | 无 | 0
nested triggers | 无 | 无 | 1
network packet size | 无 | 无 | 4096
open objects | 无 | 无 | 0
priority boost | 无 | 无 | 0
query governor cost limit | QUERY_GOVERNOR_COST_ LIMIT | 无 | 0
query wait | 无 | 无 | -1
recovery interval | 无 | 无 | 0
remote access | 无 | 无 | 1
remote login timeout | 无 | 无 | 20
remote proc trans | 无 | 无 | 0
remote query timeout | 无 | 无 | 600
scan for startup procs | 无 | 无 | 0
set working set size | 无 | 无 | 0
show advanced options | 无 | 无 | 0
two digit year cutoff | 无 | 无 | 2049
user connections | 无 | 无 | 0
user options | ANSI_NULL_DFLT_ON ANSI_NULL_DFLT_OFF | ANSI null default | OFF
 | ANSI_NULLS | ANSI nulls | OFF
 | ANSI_PADDING | 无 | ON
 | ANSI_WARNINGS | ANSI warnings | OFF
 | CURSOR_CLOSE_ON_COMMIT | cursor close on commit | OFF
 | IMPLICIT_TRANSACTIONS | 无 | OFF
 | QUOTED_IDENTIFIER | quoted identifier | OFF
 | ARITHABORT | 无 | OFF
 | ARITHIGNORE | 无 | OFF
 | DISABLE_DEF_CNST_CHK | 无 | OFF
 | NOCOUNT | 无 | OFF


提示

下表列出了可用于 Microsoft® SQL Server™ 2000 中的联接提示、查询提示和表提示的选项。

提示类型 | 选项 | 描述 | 默认 设置
联接 | LOOP | HASH | MERGE | REMOTE | 指定联接两个表中的行时所使用的策略。 | 由 SQL Server 选择。
查询 | { HASH | ORDER } GROUP | 指定是否将哈希或排列用于计算 GROUP BY 和 COMPUTE 聚合。 | 由 SQL Server 选择。
查询 | { MERGE | HASH | CONCAT } UNION | 指定查询中用于所有 UNION 操作的策略。 | 由 SQL Server 选择。
查询 | FAST integer | 优化用于检索指定数目的行的查询。 | 无此类优化。
查询 | FORCE ORDER | 以查询中表的顺序进行联接。 | 由 SQL Server 选择。
查询 | ROBUST PLAN | 创建可容纳最大潜在行大小的计划。 | 由 SQL Server 选择。
表 | FASTFIRSTROW | 与指定 FAST 1 查询提示有相同的作用。 | 无此类优化。
表 | INDEX = | 引导 SQL Server 对一个表使用指定的索引。 | 由 SQL Server 选择。
表 | HOLDLOCK | SERIALIZABLE | REPEATABLEREAD | READCOMMITTED | READUNCOMMITTED | NOLOCK | 指定表的隔离级别。 | 默认为事务隔离级别。
表 | ROWLOCK | PAGLOCK | TABLOCK | TABLOCKX | NOLOCK | 指定表的锁定粒度。 | 由 SQL Server 选择。
表 | READPAST | 完全跳过锁定的行。 | 等待锁定的行。
表 | UPDLOCK | 使用更新锁而非共享锁。不能与 NOLOCK 或 XLOCK 一起使用。 | 使用共享锁。
表 | XLOCK | 使用排它锁，并一直控制该锁直到事务结束。不能与 NOLOCK 或 UPDLOCK 一起使用。 | 由 SQL Server 选择。


数据库兼容级别选项

Transact-SQL 提供了 sp_dbcmptlevel 存储过程来设置某些数据库行为，以使数据库与指定的 Microsoft® SQL Server™ 2000 早期版本保持兼容。

对于数据库兼容级别选项、"与空连接生成空"数据库选项和 CONCAT_NULL_YIELDS_NULL SET 选项之间的关系，适用一条特殊规则。当兼容级别的值设定为早于 SQL Server 7.0 的版本时，"与空连接生成空"和 CONCAT_NULL_YIELDS_NULL 的设置将被忽略。

兼容级别将影响指定数据库的使用，但不会影响整个服务器。有关更多信息，请参见 sp_dbcmptlevel。


在 ARITHABORT 和 ARITHIGNORE 均被设置为 ON 时使用

如果 ARITHABORT 和 ARITHIGNORE 查询处理选项都设置为 ON，则 ARITHABORT 优先。

ARITHABORT 和 ARITHIGNORE 是两种不同的选项；将其中一个设置为 ON 并不能自动将另一个设置为 OFF。例如，如果应用程序包含下面这些语句，则两种选项将均被设置为 ON：

SET ARITHABORT ON
SET ARITHIGNORE ON
GO

当在存储过程中执行 SET 语句时，新设置只在过程结束之前保持有效。在过程结束之后，该选项的连接设置将恢复为执行过程前的状态。

ANSI_WARNINGS 设置的效果

ANSI_WARNINGS 设置将影响查询处理器的行为，而无论 ARITHABORT 和 ARITHIGNORE 的当前设置如何。

例如，甚至当 SET ARITHABORT 或 SET ARITHIGNORE 为 OFF，并且 SET ANSI_WARNINGS 为 ON 时，SQL Server 在遇到被零除或溢出错误时仍返回错误信息。

下表对该行为进行了总结。

ARITHABORT | ANSI_WARNINGS | 行为
ON | ON | 只终止语句。
ON | OFF | 终止批处理。
OFF | ON | 只终止语句。
OFF | OFF | 继续；值为 NULL。

请参见

SET ANSI_WARNINGS

SET ARITHABORT

SET ARITHIGNORE


查询基础知识

查询是对存储于 Microsoft® SQL Server™ 2000 中的数据的请求。可以使用下面几种形式发出查询：

  • MS Query 或 Microsoft Access 用户可使用图形用户界面 (GUI) 从一个或多个 SQL Server 表中选择想要查看的数据。

  • 使用 SQL 查询分析器或 osql 实用工具的用户可发出 SELECT 语句。

  • Microsoft Visual Basic® 应用程序可将 SQL Server 表中的数据映射到绑定控件（如网格）中。

虽然查询与用户之间有多种交互的方式，但是它们完成的任务都相同：将 SELECT 语句的结果集提供给用户。即使用户从不指定 SELECT 语句，与使用 MS Query 那样的图形化工具所经常遇到的情况一样，客户端软件可将每个用户查询转换成 SELECT 语句，然后发送到 SQL Server。

SELECT 语句从 SQL Server 中检索出数据，然后以一个或多个结果集的形式将其返回给用户。结果集是对来自 SELECT 语句的数据的表格排列。与 SQL 表相同，结果集也包括行和列。

虽然 SELECT 语句的完整语法比较复杂，但是大多数 SELECT 语句都描述结果集的四个主要属性：

  • 结果集中的列的数量和属性。对于每个结果集列来说，必须定义下列属性：
    • 列的数据类型。

    • 列的大小以及数值列的精度和小数位数。

    • 返回到列中的数据值的源。
  • 结果集从中检索数据的表，以及这些表之间的所有逻辑关系。

  • 为了符合 SELECT 语句的要求，源表中的行所必须达到的条件。不符合条件的行会被忽略。

  • 结果集的行的排列顺序。

下面的 SELECT 语句查找单价超过 40 美元的产品的产品 ID、名称以及单价：

SELECT ProductID, ProductName, UnitPrice
FROM Products
WHERE UnitPrice > $40
ORDER BY UnitPrice ASC

在 SELECT 关键字之后所列出的列名（ProductID、ProductName 和 UnitPrice）形成选择列表。它指定结果集有三列，并且每一列都具有 Products 表中相关列的名称、数据类型和大小。因为 FROM 子句仅指定了一个基表，所以 SELECT 语句中的所有列名都引用该表中的列。

FROM 子句仅列出 Products 这一个表，该表用来检索数据。

WHERE 子句指定在 Products 表中，只有 UnitPrice 列中的值超过 40 美元，该值所在的行才符合这一 SELECT 语句的要求。

ORDER BY 子句指定结果集将基于 UnitPrice 列中的值按照升序进行排序。

请参见

FROM

SELECT

分布式查询

WHERE


SELECT 语句的各部分

虽然 SELECT 语句的完整语法较复杂，但是其主要的子句可归纳如下：

SELECT select_list
[INTO new_table_name]
FROM table_list
[WHERE search_conditions]
[GROUP BY group_by_list]
[HAVING search_conditions]
[ORDER BY order_list [ASC | DESC] ]

select_list

描述结果集的列。它是一个逗号分隔的表达式列表。每个表达式同时定义格式（数据类型和大小）和结果集列的数据来源。每个选择列表表达式通常是对从中获取数据的源表或视图的列的引用，但也可能是其它表达式，例如常量或 Transact-SQL 函数。在选择列表中使用 * 表达式指定返回源表中的所有列。

INTO new_table_name

指定使用结果集来创建新表。new_table_name 指定新表的名称。

FROM table_list

包含从中检索到结果集数据的表的列表。这些来源可能包括：

  • 运行 Microsoft® SQL Server™ 的本地服务器中的基表。

  • 本地 SQL Server 中的视图。SQL Server 将一个视图引用内部解析为针对组成视图的基表的多个引用。

  • 链接表是可从 SQL Server 进行访问的 OLE DB 数据源中的表。这种访问方式称为分布式查询。通过将 OLE DB 数据源链接为链接服务器或者在 OPENROWSET 或 OPENQUERY 函数中引用数据源，可实现从 SQL Server 访问 OLE DB 数据源。

    FROM 子句还可包含联接说明，该说明定义了 SQL Server 用来在表之间进行导航的特定路径。

    FROM 子句还用在 DELETE 和 UPDATE 语句中以定义要修改的表。

WHERE search_conditions

WHERE 子句是一个筛选，它定义了源表中的行要满足 SELECT 语句的要求所必须达到的条件。只有符合条件的行才向结果集提供数据。不符合条件的行中的数据不会被使用。

WHERE 子句还用在 DELETE 和 UPDATE 语句中以定义目标表中要修改的行。

GROUP BY group_by_list

GROUP BY 子句根据 group_by_list 列中的值将结果集分成组。例如，Northwind Orders 表在 ShipVia 中有三个值。GROUP BY ShipVia 子句将结果集分成三组，每组对应于 ShipVia 的一个值。

HAVING search_conditions

HAVING 子句是应用于结果集的附加筛选。逻辑上讲，HAVING 子句从中间结果集对行进行筛选，这些中间结果集是用 SELECT 语句中的 FROM、WHERE 或 GROUP BY 子句创建的。HAVING 子句通常与 GROUP BY 子句一起使用，尽管 HAVING 子句前面不必有 GROUP BY 子句。

ORDER BY order_list [ ASC | DESC ]

ORDER BY 子句定义结果集中的行排列的顺序。order_list 指定组成排序列表的结果集的列。ASC 和 DESC 关键字用于指定行是按升序还是按降序排序。

ORDER BY 之所以重要，是因为关系理论规定除非已经指定 ORDER BY，否则不能假设结果集中的行带有任何序列。如果结果集行的顺序对于 SELECT 语句来说很重要，那么在该语句中就必须使用 ORDER BY 子句。

必须按照正确的顺序指定 SELECT 语句中的子句。

对数据库对象的每个引用都不得引起歧义。下列情况可能会导致多义性：

  • 在系统中，可能有多个对象带有相同的名称。例如，User1 和 User2 可能都指定了一个名为 TableX 的表。若要解析多义性并且指定 TableX 为 User1 所有，请至少使用用户 ID 来限定表的名称：
    SELECT *
FROM User1.TableX
  • 在执行 SELECT 语句时，对象所驻留的数据库不一定总是当前数据库。若要确保总是使用正确的对象，则不论当前数据库是如何设置的，均应使用数据库和所有者来限定对象名称：
    SELECT *
FROM Northwind.dbo.Shippers
  • 在 FROM 子句中所指定的表和视图可能有相同的列名。外键很可能具有和相关主键相同的列名。若要解析重复名称之间的多义性，必须使用表或视图名称来限定列名：
    SELECT DISTINCT Customers.CustomerID, Customers.CompanyName
FROM Customers JOIN Orders ON
       ( Customers.CustomerID = Orders.CustomerID)
WHERE Orders.ShippedDate > 'May 1 1998'

    当必须完全限定表和视图名称本身时，上述语法会变得很烦琐。通过在 FROM 子句中使用 AS 关键字为表指派一个相关名称（也称为范围变量或别名）即可解决此问题。只能在 FROM 子句中定义完全合法的表或视图名称。然后所有其它表或视图引用都可使用此相关名称。在前一个示例中应用相关名称并且完全限定表，会有如下的 SELECT 语句：

    SELECT DISTINCT Cst.CustomerID, Cst.CompanyName
FROM Northwind.dbo.Customers AS Cst
   JOIN
     Northwind.dbo.Orders AS Ord
   ON ( Cst.CustomerID = Ord.CustomerID)
WHERE Ord.ShippedDate > 'May 1 1998'

有关对象限定的更多信息，请参见使用标识符。

SQL Server 联机丛书中的许多 Transact-SQL 示例为了进行简化而没有使用合法名称。尽管这些示例为了提高可读性并未使用这些元素，但是建议在生产系统中 Transact-SQL 语句使用合法名称。

请参见

表达式

SELECT

IDENTITY（属性）


使用选择列表

选择列表可定义 SELECT 语句的结果集中的列。选择列表是以逗号分隔的一系列表达式。每个表达式定义结果集中的一列。结果集中列的排列顺序与选择列表中表达式的排列顺序相同。

结果集列的这些特性由选择列表中的下列表达式定义：

  • 结果集列的数据类型、大小、精度以及小数位数与定义列的表达式相同。

  • 结果集列的名称与定义列的表达式名称相关联。可选的 AS 关键字可用于更改名称，或当表达式没有名称时为其指定一个名称。

  • 结果集列的数据值出自结果集的每一行的表达式的取值。

选择列表还可包含控制结果集的最终格式的关键字：

  • DISTINCT

    DISTINCT 关键字可从结果集中除去重复的行。例如，在 Northwind Orders 表中有许多行的 ShipCity 值是相同的。若要获得已删除重复内容的 ShipCity 值列表，请用如下语句：

    SELECT DISTINCT ShipCity, ShipRegion
FROM Orders
ORDER BY ShipCity
  • TOP n

    TOP 关键字指定返回结果集的前 n 行。如果指定了 ORDER BY，行将在结果集排序之后选定。除非指定了 PERCENT 关键字，否则 n 即为返回的行数。PERCENT 指定 n 为结果集中返回的行的百分比。例如，SELECT 语句从 Orders 表中按照字母顺序返回前 10 个城市：

    SELECT DISTINCT TOP 10 ShipCity, ShipRegion
FROM Orders
ORDER BY ShipCity

选择列表中的项目可包括：

  • 简单表达式：对函数、局部变量、常量或者表或视图中的列的引用。

  • 标量子查询是对结果集的每一行求得单值的 SELECT 语句。

  • 通过对一个或多个简单表达式使用运算符创建的复杂表达式。

  • 使用 * 关键字将返回表中的所有列。

  • 以 @local_variable = expression 形式的变量赋值。SET @local_variable 语句还可用于变量赋值。

  • IDENTITYCOL 关键字将被解析为对具有 IDENTITY 属性的表中的列的引用。例如，已经为 Northwind Orders 表中的 OrderID 列定义了 IDENTITY 属性，这样，表达式 Orders.IDENTITYCOL 等同于 Orders.OrderID。

  • ROWGUILDCOL 关键字被解析为对表中具有 ROWGUIDCOL 属性的列的引用。

  • 按照规定的语法，创建一个使用 IDENTITY 属性的新列（使用 SELECT INTO）。例如，若要在 authors 表中新建一列，其名称为 counter，数据类型为 int，并从数值 100 开始使后面的数字依次递增 1，应该使用 counter = IDENTITY(int, 100, 1)。

  • 临时为查询结果添加一列以指定 CUBE 或 ROLLUP 操作是否添加行。使用 GROUPING 关键字。

如下示例显示了选择列表中可能包含的许多项目：

SELECT FirstName + ' ' + LastName AS "Employee Name",
       IDENTITYCOL AS "Employee ID",
       HomePhone,
       Region,
       10 AS Constant
FROM Northwind.dbo.Employees
ORDER BY LastName, FirstName ASC

请参见

SELECT


选择所有列

在 SELECT 语句中，星号 (*) 具有特殊的意义：

  • 当未使用限定符指定时，星号解析为对 FROM 子句中所指定的所有表或视图中的所有列的引用。如下示例检索 Shippers 表中的所有书籍信息：
    USE Northwind
GO
SELECT *
FROM Shippers
ORDER BY CompanyName
GO
  • 当使用表或视图名称进行限定时，星号解析为对表或视图中的所有列的引用。如下示例使用星号来引用 Shippers 表中的所有列：
    USE Northwind
GO
SELECT Orders.OrderID, Shippers.*
FROM Shippers
   JOIN
     Orders
   ON (Shippers.ShipperID = Orders.ShipVia)
ORDER BY Orders.OrderID
GO

当使用 * 时，结果集中的列的顺序与 CREATE TABLE、ALTER TABLE 或 CREATE VIEW 语句中所指定的顺序相同。

由于 SELECT * 会查找当前表中的所有列，因此每次执行 SELECT * 语句时，表结构的更改（添加、删除或重命名列）都会自动反映出来。

如果在一个与结果集中的列数具有逻辑相关性的应用程序或脚本中使用 SELECT，最好在选择列表中指定所有列而不是指定一个星号。如果稍后在 SELECT 语句所引用的表或视图中添加了列，那么，当单独列出列时，应用程序将不会受更改的影响。如果指定了 *，则新的列成为结果集的一部分后可能影响应用程序或脚本的逻辑。

如下示例首先检索 publishers 表中的所有列，然后按照 publishers 表最初创建时所定义的顺序显示这些列：

USE Northwind
GO
SELECT *
FROM [Order Details]
ORDER BY OrderID ASC
GO

若要获得完全相同的结果，需要在 SELECT 语句之后按顺序显式列出表中所有列名：

USE Northwind
GO
SELECT OrderID, ProductID, UnitPrice, Quantity, Discount
FROM [Order Details]
ORDER BY OrderID ASC
GO

说明  若要查找表的列名，则可使用 sp_help、SELECT column_name FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = table 或 SELECT TOP 0 * FROM table。

请参见

SELECT


选择特定列

若要选择表中的特定列，则需在选择列表中明确地列出每一列。例如，若要仅列出作者的名字和他们的电话号码，可使用：

SELECT FirstName, HomePhone
FROM Northwind.dbo.Employees
ORDER BY FirstName ASC

在选择列表中，对列的指定还可包括指定别名（例如，proj_sales AS "Projected Sales"）或其它表达式，例如 price = price * 1.15 或 SUM(SalesAmount)。

请参见

SELECT


查询结果集中的常量

常量通常不作为单独的列在结果集中指定。对于应用程序本身而言，与要求服务器将常量值合并到跨网络返回的每一个结果集的行中相比，在显示结果时将常量值内置于结果中更为有效。

此常规规则的例外情况包括：

  • 存储过程可以由许多不同的应用程序或脚本调用。这些存储过程对应包含在结果中的常量值没有访问权限。过程本身中的 SELECT 语句随后应将常量指定为选择列表的一部分。

  • 当一个节点需要实施一个格式或显示标准时，该格式可内置于视图或存储过程中。

  • 当结果集已从服务器返回之后，某些脚本或工具不支持将常量合并到结果集中，而 SELECT 语句可能正好是从这样的脚本或工具执行的。

当字符列串联起来时，为了保证正确的格式和可读性，需要在其中包含字符串常量。如下示例将 LastName 和 FirstName 列合并成一列。在合并后的新列中，字符串 ', ' 将名称的两个部分分开，如下所示：

SELECT LastName + ', ' + FirstName AS EmployeeName
FROM Northwind.dbo.Employees
ORDER BY LastName, FirstName ASC

请参见

+（加）

字符串串联运算符

SELECT


选择列表中的计算值

选择列表可包含通过对一个或多个简单表达式应用运算符而创建的表达式。这使结果集中得以包含基表中不存在，但是由存储在基表中的值计算而来的值。这些结果集列被称为导出列，并且包括：

  • 对数字列或常量使用算术运算符或函数进行的计算和运算：
    SELECT ROUND( (UnitPrice * .9), 2) AS DiscountPrice
FROM Products
WHERE ProductID = 58
  • 数据类型转换：
    SELECT ( CAST(ProductID AS VARCHAR(10)) + ': '
       + ProductName ) AS ProductIDName
FROM Products
  • CASE 函数：
    SELECT ProductID, ProductName,
    CASE CategoryID
        WHEN 1 THEN ROUND( (UnitPrice * .6), 2)
        WHEN 2 THEN ROUND( (UnitPrice * .7), 2)
        WHEN 3 THEN ROUND( (UnitPrice * .8), 2)
        ELSE ROUND( (UnitPrice * .9), 2)
    END AS DiscountPrice
FROM Products
  • 子查询：
    SELECT Prd.ProductID, Prd.ProductName,
       (   SELECT SUM(OD.UnitPrice * OD.Quantity)
           FROM Northwind.dbo.[Order Details] AS OD
           WHERE OD.ProductID = Prd.ProductID
       ) AS SumOfSales
FROM Northwind.dbo.Products AS Prd
ORDER BY Prd.ProductID

通过在带有算术运算符、函数、转换或嵌套查询的选择列表中使用数字列或数字常量，对数据进行计算和运算。算术运算符使您得以对数字数据进行加、减、乘和除运算。

支持下列算术运算符。

符号 | 操作
+ | 加
- | 减
/ | 除
* | 乘
% | 模

执行加、减、除和乘的算术运算符可用于任何数字列或表达式（int、smallint、tinyint、decimal、numeric、float、real、money 或 smallmoney）中。模运算符只能用于 int 列、smallint 列、tinyint 列或表达式中。

也可使用日期函数或常规加或减数学运算符对 datetime 和 smalldatetime 列进行算术运算。

可使用算术运算符执行涉及一个或多个列的计算。在算术表达式中常量的使用是可选的，如下所示：

SELECT ProductID, ProductName,
       UnitPrice * UnitsInStock AS InventoryValue
FROM Northwind.dbo.Products

请参见

FROM

SELECT

运算符

联接基础知识

子查询基础知识

+（加）

-（减）

函数


指定结果集的列名

AS 子句可用来更改结果集列名或为导出列指定名称。

当结果集列由对表或视图中的列的引用进行定义时，结果集列的名称与所引用列的名称相同。AS 子句可用于为结果集列指定不同的名称或别名。这样可增加可读性。例如：

SELECT EmpSSN AS "Employee Social Security Number"
FROM EmpTable

在选择列表中，有些列进行了具体指定，而不是简单的对列的引用，这些列便是导出列。除非使用 AS 子句指定了一个名称，否则导出列没有名称。在如下示例中，如果移去 AS 子句，那么使用 DATEDIFF 函数指定的导出列就没有名称：

SELECT OrderID,
       DATEDIFF(dd, ShippedDate, GETDATE() ) AS DaysSinceShipped
FROM Northwind.dbo.Orders
WHERE ShippedDate IS NOT NULL

AS 子句是在 SQL-92 标准中所定义的语法，用来为结果集列指派名称。下面是在 Microsoft® SQL Server™ 中首选的语法：

column_name AS column_alias

或

result_column_expression AS derived_column_name

Transact-SQL 为了兼容早期版本的 SQL Server，还支持下列语法：

column_alias = column_name

或

derived_column_name = result_column_expression

例如，上一个示例可用下列代码替换：

SELECT OrderID,
       DaysSinceShipped = DATEDIFF(dd, ShippedDate, GETDATE() )
FROM Northwind.dbo.Orders
WHERE ShippedDate IS NOT NULL

请参见

SELECT


结果集列名的分隔

结果集列名是一个标识符。如果该名称是遵循标识符规则的常规标识符，那么就不需要分隔。如果该名称不遵循标识符规则，则必须使用方括号 ([]) 或双引号 (" ") 对其进行分隔。不论 QUOTED_IDENTIFIER 选项是如何设置的，都可使用双引号对结果集列名进行分隔。

说明 每个结果集的列名可最多使用 128 个字符。但是，DB-Library 应用程序（例如 isql 工具）在查询输出中最多将结果集列名截取为 30 个字符。SQL Server 6.5 或更低版本的 SQL Server ODBC 驱动程序也最多将结果集列名截取为 30 个字符。

如下示例在 publishers 表中根据 Book Publisher 列标题而不是默认的 pub_name 列标题检索出版商名称：

USE pubs
SELECT pub_name AS "Book Publisher"
FROM publishers
ORDER BY pub_name ASC

另外，Transact-SQL 的保留关键字可通过加引号而被用于列标题。例如，以下查询使用保留字 SUM 作为列标题：

USE pubs
SELECT SUM(ytd_sales) AS "sum"
FROM titles

Transact-SQL 还支持使用单引号 ('') 来分隔结果集列名。这可保持与 SQL Server 早期版本的兼容性，如下所示：

USE pubs
SELECT SUM(ytd_sales) AS 'sum'
FROM titles

请参见

使用标识符

SELECT


使用 DISTINCT 消除重复项

DISTINCT 关键字可从 SELECT 语句的结果中除去重复的行。如果没有指定 DISTINCT，那么将返回所有行，包括重复的行。例如，如果在 titleauthor 中选择所有作者 ID 时未使用 DISTINCT，那么将会返回下列行（其中包括一些重复的行）：

USE pubs
SELECT au_id
FROM titleauthor

下面是结果集：

au_id
-----------
172-32-1176
213-46-8915
213-46-8915
238-95-7766
267-41-2394
267-41-2394
274-80-9391
409-56-7008
427-17-2319
472-27-2349
486-29-1786
486-29-1786
648-92-1872
672-71-3249
712-45-1867
722-51-5454
724-80-9391
724-80-9391
756-30-7391
807-91-6654
846-92-7186
899-46-2035
899-46-2035
998-72-3567
998-72-3567

(25 row(s) affected)

而使用了 DISTINCT 后，就能够除去重复项，而只查看唯一的作者 ID：

USE pubs
SELECT DISTINCT au_id
FROM titleauthor

下面是结果集：

au_id
-----------
172-32-1176
213-46-8915
238-95-7766
267-41-2394
274-80-9391
409-56-7008
427-17-2319
472-27-2349
486-29-1786
648-92-1872
672-71-3249
712-45-1867
722-51-5454
724-80-9391
756-30-7391
807-91-6654
846-92-7186
899-46-2035
998-72-3567

(19 row(s) affected)

重要 涉及 DISTINCT 的语句的输出取决于列的排序规则或应用 DISTINCT 的表达式。有关不同排序规则的效果的更多信息，请参见 SQL Server 排序规则基础知识。

对于 DISTINCT 关键字来说，各空值将被认为是相互重复的内容。当 SELECT 语句中包括 DISTINCT 时，不论遇到多少个空值，在结果中只返回一个 NULL。

说明 为了与 SQL-92 标准和其它的 Microsoft® SQL Server™ 版本兼容，ALL 关键字可以显式请求所有行。但是，由于 ALL 是默认的，所以无需指定它。


使用 TOP 和 PERCENT 限制结果集

TOP 子句限制返回到结果集中的行数。

TOP n [PERCENT]

n 指定返回的行数。如果未指定 PERCENT，n 就是返回的行数。如果指定了 PERCENT，n 就是返回的结果集行的百分比，如下所示：

TOP 120 /*Return the top 120 rows of the result set. */
TOP 15 PERCENT /* Return the top 15% of the result set. */.

如果一个 SELECT 语句既包含 TOP 又包含 ORDER BY 子句，那么返回的行将会从排序后的结果集中选择。整个结果集按照指定的顺序建立并且返回排好序的结果集的前 n 行。

限制结果集大小的另一种方法是在执行一个语句之前执行 SET ROWCOUNT n 语句。SET ROWCOUNT 与 TOP 的不同之处在于：

  • SET ROWCOUNT 限制适用对 ORDER BY 取值后在结果集中生成行。如果指定了 ORDER BY，SELECT 语句将在从某个已根据指定的 ORDER BY 分类进行了排序的值集中选择 n 行后终止。

  • TOP 子句适用于指定了该子句的单个 SELECT 语句。在执行另一个 SET ROWCOUNT 语句之前，SET ROWCOUNT 会一直有效，例如执行 SET ROWCOUNT 0 将会关闭此选项。

请参见

SELECT

SET ROWCOUNT


使用 FROM 子句

在每一条要从表或视图中检索数据的 SELCET 语句中，都需要使用 FROM 子句。用 FROM 子句可以：

  • 列出选择列表和 WHERE 子句中所引用的列所在的表和视图。可用 AS 子句为表和视图的名称指定别名。

  • 联接类型。这些类型由 ON 子句中指定的联接条件限定。

FROM 子句是用逗号分隔的表名、视图名和 JOIN 子句的列表。

Transact-SQL 具有扩展功能，可支持在 FROM 子句中指定除表或视图之外的其它对象。这些对象返回结果集，也就是 OLE DB 术语中所说的行集，该结果集构成了虚拟表。然后 SELECT 语句就像操作表一样操作这些结果集。

FROM 子句可以指定：

  • 一个或多个表或视图。例如：
    SELECT *
FROM Shippers
  • 两个表或视图之间的联接：
    SELECT Cst.CustomerID, Cst.CompanyName, Cst.ContactName,
       Ord.ShippedDate, Ord.Freight
FROM Northwind.dbo.Orders AS Ord
  JOIN
     Northwind.dbo.Customers AS Cst
  ON (Cst.CustomerID = Ord.CustomerID)
  • 一个或多个派生表，这些派生表是 FROM 子句中的 SELECT 语句，以别名或用户指定的名称来引用这些派生表。FROM 子句中 SELECT 语句的结果集构成了外层 SELECT 语句所用的表。例如，下面的 SELECT 语句使用派生表查找是否有哪家书店备有 pubs 数据库中所有种类的书籍：
    SELECT ST.stor_id, ST.stor_name
FROM stores AS ST,
     (SELECT stor_id, COUNT(DISTINCT title_id) AS title_count
      FROM sales
      GROUP BY stor_id
     ) AS SA
WHERE ST.stor_id = SA.stor_id
  AND SA.title_count = (SELECT COUNT(*) FROM titles)
  • 用 sp_addlinkedserver 定义的链接服务器中的一个或多个表或视图。链接服务器可以是任何 OLE DB 数据源。

  • 用 OPENROWSET 或 OPENQUERY 函数返回的 OLE DB 行集。

Microsoft® SQL Server™ 2000 分布式查询的基础是链接服务器、OPENROWSET 和 OPENQUERY。它们提供了在 Transact-SQL 语句中查询或修改任意 OLE DB 数据源中数据的能力。

没有 FROM 子句的 SELECT 语句

不需要 FROM 子句的 SELECT 语句是那些不从数据库内的任何表中选择数据的 SELECT 语句。这些 SELECT 语句只从局部变量或不对列进行操作的 Transact-SQL 函数中选择数据，例如：

SELECT @MyIntVariable
SELECT @@VERSION
SELECT DB_ID('Northwind')

请参见

分布式查询

OPENQUERY

FROM

使用联接

OPENROWSET


使用表别名

SELECT 语句的可读性可通过为表指定别名来提高，别名也称为相关名称或范围变量。指派表的别名时，可以使用也可以不使用 AS 关键字：

  • table_name AS table alias

  • table_name table_alias

在下例中，为 publishers 指派了别名 p。

USE pubs
SELECT p.pub_id, p.pub_name
FROM publishers AS p

如果为表指派了别名，那么在该 Transact-SQL 语句中对该表的所有显式引用都必须使用别名，而不能使用表名。例如，下列 SELECT 语句将产生语法错误，因为该语句在已指派别名的情况下又使用了表名：

SELECT Customers.CustomerID, /* Illegal reference to Customers. */
       Cst.FirstName, Cst.LastName
FROM Northwind.dbo.Customers AS Cst

请参见

FROM


用 WHERE 和 HAVING 筛选行

SELECT 语句中的 WHERE 和 HAVING 子句控制用源表中的那些行来构造结果集。WHERE 和 HAVING 是筛选。这两个子句指定指定一系列搜索条件，只有那些满足搜索条件的行才用来构造结果集。我们称满足搜索条件的行符合参与行集的限定条件。例如，下列 SELECT 语句中的 WHERE 子句将限定只选择地区为华盛顿州 (WA) 的行：

SELECT CustomerID, CompanyName
FROM Northwind.dbo.Customers
WHERE Region = 'WA'

HAVING 子句通常与 GROUP BY 子句结合使用，尽管指定该子句时也可以不带 GROUP BY。HAVING 子句指定在应用 WHERE 子句的筛选后要进一步应用的筛选。例如，下列 WHERE 子句仅限定以高于 $100 的单价销售产品的订单，而 HAVING 子句进一步将结果限制为只包括 100 件以上的订单：

SELECT OrdD1.OrderID AS OrderID,
       SUM(OrdD1.Quantity) AS "Units Sold",
       SUM(OrdD1.UnitPrice * OrdD1.Quantity) AS Revenue
FROM [Order Details] AS OrdD1
WHERE OrdD1.OrderID in (SELECT DISTINCT OrdD2.OrderID
                        FROM [Order Details] AS OrdD2
                        WHERE OrdD2.UnitPrice > $100)
GROUP BY OrdD1.OrderID
HAVING SUM(OrdD1.Quantity) > 100

WHERE 和 HAVING 子句中的搜索条件或限定条件可包括：

  • 比较运算符（如 =、< >、< 和 >）。例如，下列查询从 Products 表中检索产品分类为 2 的行：
    SELECT ProductID, ProductName
FROM Northwind.dbo.Products
WHERE CategoryID = 2
ORDER BY ProductID
  • 范围（BETWEEN 和 NOT BETWEEN）。例如，下列查询从 Products 表中检索产品分类从 2 到 4 的行：
    SELECT CategoryID, ProductID, ProductName
FROM Northwind.dbo.Products
WHERE CategoryID BETWEEN 2 and 4
ORDER BY CategoryID, ProductID
  • 列表（IN、NOT IN）。例如，下列查询从 Products 表中检索产品分类 ID 与列表中某一 ID 匹配的行：
    SELECT CategoryID, ProductID, ProductName
FROM Northwind.dbo.Products
WHERE CategoryID IN (1,4,5,7)
ORDER BY CategoryID, ProductID
  • 模式匹配（LIKE 和 NOT LIKE）。例如，下列查询从 Products 表中检索产品名称以 Ch 开头的行：
    SELECT CategoryID, ProductID, ProductName
FROM Northwind.dbo.Products
WHERE ProductName LIKE 'Ch%'
ORDER BY CategoryID, ProductID

    说明 可用于 text 列的 WHERE 条件只有返回其它数据类型的函数（如 PATINDEX()）或运算符（如 IS NULL、IS NOT NULL、LIKE 和 NOT LIKE）。

  • 空值（IS NULL 和 IS NOT NULL）。例如，下列查询从 Customers 表中检索客户地区不为 NULL 的行：
    SELECT CompanyName, City, Region, Country
FROM Northwind.dbo.Customers
WHERE Region IS NOT NULL
ORDER BY CompanyName

    说明 比较空值时请谨慎从事。例如，指定 = NULL 与指定 IS NULL 是不同的。有关更多信息，请参见空值。

  • 所有记录（=ALL、>ALL、<= ALL、ANY）。例如，下列查询从 Order Details 表中检索装运产品数量大于分类 1 中任意产品的装运数量的订单和产品 ID：
    USE Northwind
GO
SELECT OrdD1.OrderID, OrdD1.ProductID
FROM "Order Details" OrdD1
WHERE OrdD1.Quantity > ALL
      (SELECT OrdD2.Quantity
       FROM "Order Details" OrdD2 JOIN Products Prd
             ON OrdD2.ProductID = Prd.ProductID
       WHERE Prd.CategoryID = 1)
GO
  • 上述条件的组合（AND、OR、NOT）。例如，下列查询检索库存水平低于再订购点，或来自供应商 15 并属于分类 4 的所有产品：
    SELECT ProductID, ProductName
FROM Northwind.dbo.Products
WHERE UnitsInStock < ReorderLevel
   OR (SupplierID = 15 AND CategoryID = 4)

说明 当在 WHERE 子句中搜索 Unicode 字符串时，请在搜索字符串之前加字符 N，例如：

SELECT CompanyName, ContactName, Phone, Fax
FROM Northwind.dbo.Customers
WHERE CompanyName = N'Berglunds snabbköp'

请参见

IS [NOT] NULL

运算符

ISNULL

WHERE


比较搜索条件

Microsoft® SQL Server™ 2000 使用下列比较运算符。

运算符 | 含义
= | 等于
> | 大于
< | 小于
>= | 大于等于
<= | 小于等于
< > | 不等于（SQL-92 兼容）
!> | 不大于
!< | 不小于
!= | 不等于

在两个表达式之间指定比较运算符。例如，若要检索单价大于 $50 的产品的名称，请使用：

SELECT ProductName
FROM Northwind.dbo.Products
WHERE UnitPrice > $50.00

比较字符串数据时，字符的逻辑顺序由字符数据的排序规则定义。比较运算符（如 < 和 >）的结果由排序规则所定义的字符顺序控制。对于 Unicode 数据和非 Unicode 数据，相同的 SQL 排序规则可能有不同的排序行为。（有关更多信息，请参见 SQL Server 排序规则基础知识。）

比较非 Unicode 数据时，将忽略尾随空格；例如，下列语句等价：

WHERE au_lname = 'White'
WHERE au_lname = 'White '
WHERE au_lname = 'White' + SPACE(1)

可用 NOT 对一个表达式求反。例如，下列查询查找单价为 $50 或更高的所有产品，这在逻辑上等价于查找单价不小于 $50 的所有产品：

SELECT ProductID, ProductName, UnitPrice
FROM Northwind.dbo.Products
WHERE NOT UnitPrice < $50
ORDER BY ProductID

请参见

字符串串联运算符

+（字符串串联）

数据类型

运算符

SQL Server 排序规则基础知识


范围搜索条件

范围搜索返回介于两个指定值之间的所有值。内含范围返回与两个指定值匹配的值。排除范围不返回与两个指定值匹配的值。

BETWEEN 关键字指定用于搜索的内含范围。例如，下列 SELECT 语句返回库存单位数量介于 15 到 25 之间的所有产品：

SELECT UnitsInStock, ProductID, ProductName
FROM Northwind.dbo.Products
WHERE UnitsInStock BETWEEN 15 AND 25
ORDER BY UnitsInStock

下列 SELECT 语句包含库存单位数量为 15 或 25 的所有产品。

SELECT UnitsInStock, ProductID, ProductName
FROM Northwind.dbo.Products
WHERE UnitsInStock = 15 OR UnitsInStock = 25
ORDER BY UnitsInStock

若要指定排除范围，请使用大于和小于运算符（> 和 <）。下列查询使用大于和小于运算符，所返回的结果与上面的示例不同，因为这些运算符不包括与限定范围的值相匹配的行。

SELECT UnitsInStock, ProductID, ProductName
FROM Northwind.dbo.Products
WHERE UnitsInStock > 15 AND UnitsInStock < 25
ORDER BY UnitsInStock

NOT BETWEEN 查找指定范围之外的所有行。用下列查询可查找库存单位数量不在 15 到 25 范围内的所有产品：

SELECT UnitsInStock, ProductID, ProductName
FROM Northwind.dbo.Products
WHERE UnitsInStock NOT BETWEEN 15 AND 25
ORDER BY UnitsInStock

请参见

WHERE

数据类型


列表搜索条件

IN 关键字使您得以选择与列表中的任意一个值匹配的行。例如，如果不用 IN，当要获得居住在 California、Indiana 或 Maryland 州的所有作者的姓名和州的列表时，就需要下列查询：

SELECT ProductID, ProductName
FROM Northwind.dbo.Products
WHERE CategoryID = 1 OR CategoryID = 4 OR CategoryID = 5

然而，如果使用 IN，少键入一些字符也可以得到同样的结果：

SELECT ProductID, ProductName
FROM Northwind.dbo.Products
WHERE CategoryID IN (1, 4, 5)

IN 关键字之后的项目必须用逗号隔开，并且括在括号中。

可能 IN 关键字最重要的应用是在嵌套查询（也称子查询）中。有关子查询的更多信息，请参见子查询基础知识。

下列查询在 titleauthor 表中查找在任一种书中得到的版税少于 50% 的所有作者的 au_id，然后从 authors 表中选择 au_id 与 titleauthor 查询结果匹配的所有作者的姓名：

USE pubs
SELECT au_lname, au_fname
FROM authors
WHERE au_id IN
   (SELECT au_id
   FROM titleauthor
   WHERE royaltyper < 50)

结果显示有一些作者属于少于 50% 的一类。

下列查询查找任何一种书的版税都不少于 50% 的作者的姓名：

USE pubs
SELECT au_lname, au_fname
FROM authors
WHERE au_id NOT IN
   (SELECT au_id
   FROM titleauthor
   WHERE royaltyper < 50)

NOT IN 将找到那些与值列表中的项目不匹配的作者。

请参见

WHERE

数据类型


搜索条件中的模式匹配

LIKE 关键字搜索与指定模式匹配的字符串、日期或时间值。有关更多信息，请参见数据类型。LIKE 关键字使用常规表达式包含值所要匹配的模式。模式包含要搜索的字符串，字符串中可包含四种通配符的任意组合。

通配符 | 含义
% | 包含零个或更多字符的任意字符串。
_ | 任何单个字符。
[ ] | 指定范围（例如 [a-f]）或集合（例如 [abcdef]）内的任何单个字符。
[^] | 不在指定范围（例如 [^a - f]）或集合（例如 [^abcdef]）内的任何单个字符。

请将通配符和字符串用单引号引起来，例如：

  • LIKE 'Mc%' 将搜索以字母 Mc 开头的所有字符串（如 McBadden）。

  • LIKE '%inger' 将搜索以字母 inger 结尾的所有字符串（如 Ringer、Stringer）。

  • LIKE '%en%' 将搜索在任何位置包含字母 en 的所有字符串（如 Bennet、Green、McBadden）。

  • LIKE '_heryl' 将搜索以字母 heryl 结尾的所有六个字母的名称（如 Cheryl、Sheryl）。

  • LIKE '[CK]ars[eo]n' 将搜索下列字符串：Carsen、Karsen、Carson 和 Karson（如 Carson）。

  • LIKE '[M-Z]inger' 将搜索以字符串 inger 结尾、以从 M 到 Z 的任何单个字母开头的所有名称（如 Ringer）。

  • LIKE 'M[^c]%' 将搜索以字母 M 开头，并且第二个字母不是 c 的所有名称（如 MacFeather）。

下列查询在 authors 表中查找所有区号为 415 的电话号码：

SELECT phone
FROM pubs.dbo.authors
WHERE phone LIKE '415%'

可以用同样的通配符使用 NOT LIKE。若要在 authors 表中查找区号不是 415 的所有电话号码，请使用下列等价查询中的任意一个：

SELECT phone
FROM pubs.dbo.authors
WHERE phone NOT LIKE '415%'

-- Or

SELECT phone
FROM pubs.dbo.authors
WHERE NOT phone LIKE '415%'

IS NOT NULL 子句可与通配符和 LIKE 子句结合使用。例如，下列查询从 authors 表中检索以 415 开头且 IS NOT NULL 的所有电话号码：

USE pubs
SELECT phone
FROM authors
WHERE phone LIKE '415%' and phone IS NOT NULL

重要 包含 LIKE 关键字的语句的输出结果取决于安装过程中所选的排序次序。有关不同排序次序所产生的影响的信息，请参见排序规则。

可用于 text 列的 WHERE 条件只有 LIKE、IS NULL 或 PATINDEX。

不与 LIKE 一同使用的通配符将解释为常量而非模式，换言之，这些通配符仅代表其本身的值。下列查询试图查找只由四个字符 415% 组成的电话号码。该查询并不会查找以 415 开头的电话号码。有关常量的更多信息，请参见使用常量。

SELECT phone
FROM pubs.dbo.authors
WHERE phone = '415%'

使用通配符时应着重考虑的另一个问题是对性能的影响。如果表达式以通配符开头，就不能使用索引。（就如同给定了姓名"%mith"而非"Smith"时，将无法知道应从电话簿的哪一页开始查找。）表达式中间或结尾处的通配符不妨碍使用索引，如同在电话簿中一样，如果姓名为"Samuel%"，则不论 Samuels 和 Samuelson 是否都在电话簿上，都应知道该从何处开始查找。

搜索通配符字符

可以搜索通配符字符。有两种方法可指定平常用作通配符的字符：

  • 使用 ESCAPE 关键字定义转义符。在模式中，当转义符置于通配符之前时，该通配符就解释为普通字符。例如，要搜索在任意位置包含字符串 5% 的字符串，请使用：
    WHERE ColumnA LIKE '%5/%%' ESCAPE '/'

    在上述 LIKE 子句中，前导和结尾百分号 (%) 解释为通配符，而斜杠 (/) 之后的百分号解释为字符 %。

  • 在方括号 ([ ]) 中只包含通配符本身。要搜索破折号 (-) 而不是用它指定搜索范围，请将破折号指定为方括号内的第一个字符：
    WHERE ColumnA LIKE '9[-]5'

    下表显示了括在方括号内的通配符的用法。

    符号 | 含义
    LIKE '5[%]' | 5%
    LIKE '5%' | 5 后跟 0 个或更多字符的字符串
    LIKE '[_]n' | _n
    LIKE '_n' | an, in, on (and so on)
    LIKE '[a-cdf]' | a, b, c, d, or f
    LIKE '[-acdf]' | -, a, c, d, or f
    LIKE '[ [ ]' [
    LIKE ']' ]

如果使用 LIKE 进行字符串比较，模式字符串中的包括起始空格和/或尾随空格在内的所有字符都有意义。如果查询比较要求返回包含"abc "（abc 后有一个空格）的所有行，则不会返回列值为"abc"（abc 后没有空格）行。但是反过来，情况并非如此。可以忽略模式所要匹配的表达式中的末尾空格。如果查询比较要求返回包含"abc"（abc 后没有空格）的所有行，则将返回以"abc"开始且具有零个或多个末尾空格的所有行。

请参见

LIKE

WHERE


空值比较搜索条件

NULL 值表示列的数据值未知或不可用。NULL 值与零（数字值或二进制值）、零长度的字符串或空白（字符值）的含义不同。相反，空值可用于区分输入的是零（数字列）或空白（字符列）还是无数据输入（NULL 可用于数字列和字符列）。

可以通过以下两种方式在允许空值的列中输入 NULL 值（根据 CREATE TABLE 语句的指定）：

  • 如果无数据输入并且列或数据类型上无默认的或 DEFAULT 约束，Microsoft® SQL Server™ 2000 将自动输入 NULL 值。

  • 通过键入不带引号的 NULL 显式输入 NULL 值。如果在字符列中键入带引号的 NULL，则将其视为字母 N、U、L、L 而非空值。

当检索到空值时，应用程序通常会在适当的位置显示 NULL 或 (NULL) 或 (null) 这样的字符串。例如，titles 表中的 advance 列允许空值：

SELECT title_id, type, advance
FROM pubs.dbo.titles
WHERE advance IS NULL

下面是结果集：

title_id type         advance
-------- ------------ --------------------------
MC3026   UNDECIDED    (null)
PC9999   popular_comp (null)

(2 row(s) affected)
比较空值

比较空值时必须小心。比较行为取决于选项 SET ANSI_NULLS 的设置。

当 SET ANSI_NULLS 为 ON 时，如果比较中有一个或多个表达式为 NULL，则既不输出 TRUE 也不输出 FALSE，而是输出 UNKNOWN。这是因为，未知值不能与其它任何值进行逻辑比较。这种情况发生在一个表达式与 NULL 单词进行比较，或者两个表达式相比，而其中一个表达式取值为 NULL 时。例如，当 ANSI_NULLS 为 ON 时，以下比较总是输出 UNKNOWN：

ytd_sales > NULL

只要变量包含 NULL 值，下列比较也输出 UNKNOWN：

ytd_sales > @MyVariable

使用 IS NULL 或 IS NOT NULL 子句测试 NULL 值。这将增加 WHERE 子句的复杂性。例如，表 Northwind Customers 中的 Region 列允许空值。如果使用 SELECT 语句不仅测试其它值还测试空值，则必须包括 IS NULL 子句：

SELECT CustomerID, CompanyName, Region
FROM Northwind.dbo.Customers
WHERE Region IN ('WA', 'SP', 'BC')
   OR Region IS NULL

Transact-SQL 支持在与空值进行比较时，允许比较运算符返回 TRUE 或 FALSE。通过设置 ANSI_NULLS OFF 可将该选项激活。当 ANSI_NULLS 为 OFF 时，如果 ColumnA 包含空值，则比较操作 ColumnA = NULL 返回 TRUE；如果 ColumnA 除包含 NULL 外还包含某些值，则比较操作返回 FALSE。此外，两个都取空值的表达式的比较也输出 TRUE。当 ANSI_NULLS 设置为 OFF 时，SELECT 语句将返回 Customer 表中所有 Region 为空值的行：

SELECT CustomerID, CompanyName, Region
FROM Northwind.dbo.Customers
WHERE Region = NULL

不论 ANSI_NULLS 如何设置，对于 ORDER BY、GROUP BY 和 DISTINCT 关键字空值总被视为是相等的。此外，允许 NULL 的唯一索引或 UNIQUE 约束只能包含一个带有 NULL 键值的行。后面带有 NULL 的行将被拒绝。属于主键的任何列中都不能含有 NULL。

涉及 NULL 的计算均取值为 NULL，这是因为只要有一个因子为未知，结果肯定是 UNKNOWN。例如，如果 column1 为 NULL，则 column1 + 1 取值为 NULL。

当搜索的列中包括定义为允许空值的列时，可以通过下列模式查找数据库中的空值或非空值：

WHERE column_name IS [NOT] NULL

请参见

空值

IS [NOT] NULL


逻辑运算符

逻辑运算符为 AND、OR 和 NOT。AND 和 OR 用于连接 WHERE 子句中的搜索条件。NOT 用于反转搜索条件的结果。

AND 连接两个条件，只有当两个条件都符合时才返回 TRUE。例如，以下查询只返回客户 ID 的开头字母为 F、联系人姓名为 Lino Rodriguez 的行：

SELECT CustomerID, CompanyName, ContactName
FROM Northwind.dbo.Customers
WHERE CustomerID LIKE N'F%'
  AND ContactName = N'Lino Rodriguez'

OR 也用于连接两个条件，但只要有一个条件符合便返回 TRUE。以下查询返回两行，一行包含客户 ID CACTU，另一行包含联系人姓名 Lino Rodriguez：

SELECT CustomerID, CompanyName, ContactName
FROM Customers
WHERE CustomerID = N'CACTU'
   OR ContactName = N'Lino Rodriguez'

请参见

WHERE

运算符


逻辑运算符的优先顺序

当一个语句中同时包含多个逻辑运算符时，取值的优先顺序依次为：NOT、AND 和 OR。算术（及按位）运算符先于逻辑运算符被处理。

在下例中，高级条件属于心理书籍而不属于商业书籍，因为 AND 优先于 OR：

SELECT title_id, type, advance
FROM pubs.dbo.titles
WHERE type = 'business' OR type = 'psychology'
  AND advance > $5500

可以通过添加括号强制首先对 OR 取值的办法来改变查询的含义。以下查询查找所有价格超过 $5,500 的商业书籍和心理书籍：

SELECT title_id, type, advance
FROM titles
WHERE (type = 'business' OR type = 'psychology')
   AND advance > $5500

由于存在运算符优先顺序，使用括号（即使不要求）可以提高查询的可读性，并能减少微小错误的发生。使用括号不会造成重大的性能损失。尽管在语法构成上是相同的，但是下例比原例具有更强的可读性：

SELECT title_id, type, advance
FROM pubs.dbo.titles
WHERE type = 'business'
   OR (type = 'psychology' AND advance > $5500)

请参见

运算符

WHERE


Transact-SQL 联接

在早期的 Microsoft® SQL Server™ 2000 版本中，使用 *= 和 =* 在 WHERE 子句中指定左、右外部联接条件。有时，该语法会导致有多种解释的不明确查询。FROM 子句中指定遵从 SQL-92 的外部联接，不会导致上述不确定性。因为 SQL-92 语法更为精确，所以，本版中未包括有关在 WHERE 子句中使用旧的 Transact-SQL 外部联接语法的详细信息。以后的 SQL Server 版本可能不再支持该语法。任何使用 Transact-SQL 外部联接的语句都应改为使用 SQL-92 语法。

SQL-92 标准支持 FROM 或 WHERE 子句中的内部联接规范。WHERE 子句中指定的内部联接不会出现与 Transact-SQL 外部联接语法相同的不确定性问题。

请参见

FROM

联接基础知识

SELECT


用 ORDER BY 对行进行排序

ORDER BY 子句按查询结果中的一列或多列对查询结果进行排序，用作排序依据的列总长度可达 8,060。有关 ORDER BY 子句最大大小的更多信息，请参见 SELECT。

排序可以是升序的 (ASC)，也可以是降序的 (DESC)。如果没有指定升序还是降序，就假定为 ASC。

重要 ORDER BY 子句的确切结果取决于安装过程中所选的排序规则。有关不同排序规则所产生的影响的更多信息，请参见 SQL Server 排序规则基础知识。

下列查询返回按 pub_id 升序排列的结果。

USE pubs
SELECT pub_id, type, title_id
FROM titles
ORDER BY pub_id

下面是结果集：

pub_id type         title_id
------ ------------ --------
0736   business     BU2075
0736   psychology   PS2091
0736   psychology   PS2106
0736   psychology   PS3333
0736   psychology   PS7777
0877   mod_cook     MC2222
0877   mod_cook     MC3021
0877   UNDECIDED    MC3026
0877   psychology   PS1372
0877   trad_cook    TC3218
0877   trad_cook    TC4203
0877   trad_cook    TC7777
1389   business     BU1032
1389   business     BU1111
1389   business     BU7832
1389   popular_comp PC1035
1389   popular_comp PC8888
1389   popular_comp PC9999

(18 row(s) affected)

如果在 ORDER BY 子句中指定了不止一列，排序就是嵌套的。下列语句对 titles 表中的行进行排序，首先按出版商降序排列，然后在各出版商范围内按类型升序排列，最后按价格排序（同样是升序，因为未指定 DESC）。

USE pubs
SELECT pub_id, type, title_id, price
FROM titles
ORDER BY pub_id DESC, type, price

说明 不能对数据类型为 text 或 image 的列使用 ORDER BY。同样，在 ORDER BY 列表中也不允许使用子查询、聚合和常量表达式；不过，可以在聚合或表达式的选择列表中使用用户指定的名称，例如：

SELECT type, sum (ytd_sales) AS sales_total
FROM titles
GROUP BY type
ORDER BY sales_total


联接基础知识

通过联接，可以根据各个表之间的逻辑关系从两个或多个表中检索数据。联接表示 Microsoft® SQL Server™ 2000 应如何使用一个表中的数据来选择另一个表中的行。

联接条件通过以下方法定义两个表在查询中的关联方式：

  • 指定每个表中要用于联接的列。典型的联接条件在一个表中指定外键，在另一个表中指定与其关联的键。

  • 指定比较各列的值时要使用的逻辑运算符（=、<> 等）。

可在 FROM 或 WHERE 子句中指定联接。联接条件与 WHERE 和 HAVING 搜索条件组合，用于控制 FROM 子句引用的基表中所选定的行。

在 FROM 子句中指定联接条件有助于将这些联接条件与 WHERE 子句中可能指定的其它搜索条件分开，指定联接时建议使用这种方法。简化的 SQL-92 FROM 子句联接语法如下：

FROM first_table join_type second_table [ON (join_condition)]

join_type 指定所执行的联接类型：内联接、外联接或交叉联接。join_condition 定义要为每对联接的行选取的谓词。下面是 FROM 子句联接规范示例：

FROM Suppliers JOIN Products
     ON (Suppliers.SupplierID = Products.SupplierID)

下面是使用此联接的简单 SELECT 语句：

SELECT ProductID,
       Suppliers.SupplierID,
       CompanyName
FROM Suppliers JOIN Products
     ON (Suppliers.SupplierID = Products.SupplierID)
WHERE UnitPrice > $10
  AND CompanyName LIKE N'F%'
GO

此选择会返回某公司所提供的一组产品和供应商信息，该公司名以 F 字母开头，并且产品价格在 10 美元以上。

当单个查询引用多个表时，所有列引用都必须明确。上例中，Products 和 Suppliers 表都有名为 SupplierID 的列。在查询所引用的两个或多个表之间，任何重复的列名都必须用表名限定。本例中对 SupplierID 列的所有引用均已限定。

如果某个列名在查询用到的两个或多个表中不重复，则对这一列的引用不必用表名限定。如上例所示。由于没有指明提供每个列的表，因此这样的 SELECT 语句有时会比较难懂。如果所有的列都用表名限定，则能提高查询的可读性。如果使用表的别名，则会进一步提高可读性，特别是在表名自身必须由数据库和所有者名称限定时。下例与上例相同，只是指定了表的别名并用表的别名限定列，从而提高可读性：

SELECT P.ProductID,
       S.SupplierID,
       S.CompanyName
FROM Suppliers AS S JOIN Products AS P
     ON (S.SupplierID = P.SupplierID)
WHERE P.UnitPrice > $10
  AND S.CompanyName LIKE N'F%'

上例在 FROM 子句中指定联接条件，这是值得推崇的方法。以下查询包含相同的联接条件，该联接条件在 WHERE 子句中指定：

SELECT P.ProductID,
       S.SupplierID,
       S.CompanyName
FROM Suppliers AS S, Products AS P
WHERE S.SupplierID = P.SupplierID
  AND P.UnitPrice > $10
  AND S.CompanyName LIKE N'F%'

联接的选择列表可以引用联接表中的所有列或列的任何子集。选择列表不必包含联接中的每个表的列。例如，在三表联接中，只能用一个表联接另外两个表，而选择列表不必引用此中间表中的任何列。

虽然联接条件通常使用相等比较 (=)，但也可以像指定其它谓词一样指定其它比较或关系运算符。有关更多信息，请参见在表达式中使用运算符和 WHERE。

SQL Server 处理联接时，查询引擎从多种可能的方法中选择最高效的方法处理联接。尽管不同联接的物理执行采用多种不同的优化，但是逻辑序列都：

  • 应用 FROM 子句中的联接条件。

  • 应用 WHERE 子句中的联接条件和搜索条件。

  • 应用 HAVING 子句中的搜索条件。

如果在 FROM 和 WHERE 子句间移动条件，则这个序列有时会影响查询结果。

联接条件中用到的列不必具有相同的名称或相同的数据类型。但是如果数据类型不相同，则必须兼容或可由 SQL Server 进行隐性转换。如果不能隐性转换数据类型，则联接条件必须用 CAST 函数显式地转换数据类型。有关隐性和显式转换的更多信息，请参见数据类型转换。

大多数使用联接的查询可以用子查询（嵌套在其它查询中的查询）重写，并且大多数子查询可以重写为联接。有关子查询的更多信息，请参见子查询基础知识。

说明 无法在 ntext、text 或 image 列上直接联接表。不过，可以用 SUBSTRING 在 ntext、text 或 image 列上间接联接表。例如，SELECT * FROM t1 JOIN t2 ON SUBSTRING(t1.textcolumn, 1, 20) = SUBSTRING(t2.textcolumn, 1, 20) 在表 t1 和 t2 中的每个文本列前 20 个字符上进行两表内联接。此外，另一种比较两个表中的 ntext 或 text 列的方法是用 WHERE 子句比较列的长度，例如在 pub_info 表上执行自联接：

WHERE DATALENGTH(p1.pr_info) = DATALENGTH(p2.pr_info)

请参见

逻辑运算符的优先顺序

SELECT 示例


使用联接

联接条件可在 FROM 或 WHERE 子句中指定，建议在 FROM 子句中指定联接条件。WHERE 和 HAVING 子句也可以包含搜索条件，以进一步筛选联接条件所选的行。

联接可分为以下几类：

  • 内联接（典型的联接运算，使用像 = 或 <> 之类的比较运算符）。包括相等联接和自然联接。

    内联接使用比较运算符根据每个表共有的列的值匹配两个表中的行。例如，检索 students 和 courses 表中学生标识号相同的所有行。

  • 外联接。外联接可以是左向外联接、右向外联接或完整外部联接。

    在 FROM 子句中指定外联接时，可以由下列几组关键字中的一组指定：

    • LEFT JOIN 或 LEFT OUTER JOIN。

      左向外联接的结果集包括 LEFT OUTER 子句中指定的左表的所有行，而不仅仅是联接列所匹配的行。如果左表的某行在右表中没有匹配行，则在相关联的结果集行中右表的所有选择列表列均为空值。

    • RIGHT JOIN 或 RIGHT OUTER JOIN。

      右向外联接是左向外联接的反向联接。将返回右表的所有行。如果右表的某行在左表中没有匹配行，则将为左表返回空值。

    • FULL JOIN 或 FULL OUTER JOIN。

      完整外部联接返回左表和右表中的所有行。当某行在另一个表中没有匹配行时，则另一个表的选择列表列包含空值。如果表之间有匹配行，则整个结果集行包含基表的数据值。

  • 交叉联接。

    交叉联接返回左表中的所有行，左表中的每一行与右表中的所有行组合。交叉联接也称作笛卡尔积。

例如，下面的内联接检索与某个出版商居住在相同州和城市的作者：

USE pubs
SELECT a.au_fname, a.au_lname, p.pub_name
FROM authors AS a INNER JOIN publishers AS p
   ON a.city = p.city
   AND a.state = p.state
ORDER BY a.au_lname ASC, a.au_fname ASC

FROM 子句中的表或视图可通过内联接或完整外部联接按任意顺序指定；但是，用左或右向外联接指定表或视图时，表或视图的顺序很重要。有关使用左或右向外联接排列表的更多信息，请参见使用外联接。

请参见

运算符

CAST 和 CONVERT

在表达式中使用运算符

SELECT


使用内联接

内联接是用比较运算符比较要联接列的值的联接。

在 SQL-92 标准中，内联接可在 FROM 或 WHERE 子句中指定。这是 WHERE 子句中唯一一种 SQL-92 支持的联接类型。WHERE 子句中指定的内联接称为旧式内联接。

下面的 Transact-SQL 查询是内联接的一个示例：

USE pubs
SELECT *
FROM authors AS a INNER JOIN publishers AS p
   ON a.city = p.city
ORDER BY a.au_lname DESC

此内联接称为相等联接。它返回两个表中的所有列，但只返回在联接列中具有相等值的行。

下面是结果集：

au_id        au_lname  au_fname phone         address          city
-----------  --------  -------- ------------  ---------------  --------
238-95-7766  Carson    Cheryl   415 548-7723  589 Darwin Ln.    Berkeley
409-56-7008  Bennet    Abraham  415 658-9932  6223 Bateman St.  Berkeley

state zip   contract pub_id pub_name              city     state country
----- ----- -------- ------ --------------------- -------- ----- -------
CA    94705 1        1389   Algodata Infosystems  Berkeley CA    USA
CA    94705 1        1389   Algodata Infosystems  Berkeley CA    USA

(2 row(s) affected)

在结果集中，city 列出现两次。由于重复相同的信息没有意义，因此可以通过更改选择列表消除两个相同列中的一个。其结果称为自然联接。可以重新表述前面的 Transact-SQL 查询以形成自然联接。例如：

USE pubs
SELECT p.pub_id, p.pub_name, p.state, a.*
FROM publishers p INNER JOIN authors a
   ON p.city = a.city
ORDER BY a.au_lname ASC, a.au_fname ASC

下面是结果集：

pub_id pub_name              state    au_id        au_lname  au_fname
------ ---------------       -------- -----------  --------  -------- 1389   Algodata Infosystems  CA       409-56-7008  Bennet    Abraham
1389   Algodata Infosystems  CA       238-95-7766  Carson    Cheryl

phone         address          city      state zip   contract
---------------  ------------- --------  ----- ----- ---------
415 658-9932  6223 Bateman St. Berkeley  CA    94705 1
415 548-7723  589 Darwin Ln.   Berkeley  CA    94705 1

(2 row(s) affected)

本示例中，publishers.city 没有出现在结果中。

使用等号以外的运算符的联接

也可以联接两个不相等的列中的值。用于内联接的运算符和谓词同样也可用于不相等联接。有关联接中可用的运算符和谓词的更多信息，请参见在表达式中使用运算符和 WHERE。

下面的 Transact-SQL 示例是一个大于 (>) 联接，可用于查找住在 Massachusetts 之后（按字母顺序排列）的州的 New Moon 作家，Massachusetts 是 New Moon Books 的所在地。

USE pubs
SELECT p.pub_name, p.state, a.au_lname, a.au_fname, a.state
FROM publishers p INNER JOIN authors a
   ON a.state > p.state
WHERE p.pub_name = 'New Moon Books'
ORDER BY au_lname ASC, au_fname ASC

下面是结果集：

pub_name         state   au_lname             au_fname             state
---------------- ------- -------------------- -------------------- -----
New Moon Books   MA    Blotchet-Halls         Reginald             OR
New Moon Books   MA    del Castillo           Innes                MI
New Moon Books   MA    Greene                 Morningstar          TN
New Moon Books   MA    Panteley               Sylvia               MD
New Moon Books   MA    Ringer                 Albert               UT
New Moon Books   MA    Ringer                 Anne                 UT

(6 row(s) affected)
使用不等运算符的联接

很少使用不等联接 (< >)。通常不等联接只有与自联接同时使用才有意义。例如，可以使用下面的不等 Transact-SQL 联接和自联接查找包含不同价格的两本或多本廉价（低于 $15）书的类别：

USE pubs
SELECT DISTINCT t1.type, t1.price
FROM titles t1 INNER JOIN titles t2
   ON t1.type = t2.type
   AND t1.price <> t2.price
WHERE t1.price < $15 AND t2.price < $15

说明 表达式 NOT column_name = column_name 与表达式 column_name < > column_name 等效。

下面的 Transact-SQL 示例中，使用不等联接和自联接的组合查找 titleauthor 表中的所有行，在该表中有两行或多行具有相同的 title_id 但 au_id 号不同（即一本书有多个作者）：

USE pubs
SELECT DISTINCT t1.au_id, t1.title_id
FROM titleauthor t1 INNER JOIN titleauthor t2
   ON t1.title_id = t2.title_id
WHERE t1.au_id <> t2.au_id
ORDER BY t1.au_id

下面是结果集：

au_id            title_id
-----------         --------
213-46-8915         BU1032
267-41-2394         BU1111
267-41-2394         TC7777
409-56-7008         BU1032
427-17-2319         PC8888
472-27-2349         TC7777
672-71-3249         TC7777
722-51-5454         MC3021
724-80-9391         BU1111
724-80-9391         PS1372
756-30-7391         PS1372
846-92-7186         PC8888
899-46-2035         MC3021
899-46-2035         PS2091
998-72-3567         PS2091

(15 row(s) affected)

请参见

转换函数

WHERE

SELECT 示例

SELECT


使用外联接

仅当至少有一个同属于两表的行符合联接条件时，内联接才返回行。内联接消除与另一个表中的任何行不匹配的行。而外联接会返回 FROM 子句中提到的至少一个表或视图的所有行，只要这些行符合任何 WHERE 或 HAVING 搜索条件。将检索通过左向外联接引用的左表的所有行，以及通过右向外联接引用的右表的所有行。完整外部联接中两个表的所有行都将返回。

Microsoft® SQL Server™ 2000 对在 FROM 子句中指定的外联接使用以下 SQL-92 关键字：

  • LEFT OUTER JOIN 或 LEFT JOIN

  • RIGHT OUTER JOIN 或 RIGHT JOIN

  • FULL OUTER JOIN 或 FULL JOIN

SQL Server 支持 SQL-92 外联接语法，以及在 WHERE 子句中使用 *= 和 =* 运算符指定外联接的旧式语法。由于 SQL-92 语法不容易产生歧义，而旧式 Transact-SQL 外联接有时会产生歧义，因此建议使用 SQL-92 语法。

使用左向外联接

假设在 city 列上联接 authors 表和 publishers 表。结果只显示在出版商所在城市居住的作者（本例中为 Abraham Bennet 和 Cheryl Carson）。

若要在结果中包括所有的作者，而不管出版商是否住在同一个城市，请使用 SQL-92 左向外联接。下面是 Transact-SQL 左向外联接的查询和结果：

USE pubs
SELECT a.au_fname, a.au_lname, p.pub_name
FROM authors a LEFT OUTER JOIN publishers p
   ON a.city = p.city
ORDER BY p.pub_name ASC, a.au_lname ASC, a.au_fname ASC

下面是结果集：

au_fname             au_lname                       pub_name
-------------------- ------------------------------ -----------------
Reginald             Blotchet-Halls                 NULL
Michel               DeFrance                       NULL
Innes                del Castillo                   NULL
Ann                  Dull                           NULL
Marjorie             Green                          NULL
Morningstar          Greene                         NULL
Burt                 Gringlesby                     NULL
Sheryl               Hunter                         NULL
Livia                Karsen                         NULL
Charlene             Locksley                       NULL
Stearns              MacFeather                     NULL
Heather              McBadden                       NULL
Michael              O'Leary                        NULL
Sylvia               Panteley                       NULL
Albert               Ringer                         NULL
Anne                 Ringer                         NULL
Meander              Smith                          NULL
Dean                 Straight                       NULL
Dirk                 Stringer                       NULL
Johnson              White                          NULL
Akiko                Yokomoto                       NULL
Abraham              Bennet                         Algodata Infosystems
Cheryl               Carson                         Algodata Infosystems

(23 row(s) affected)

不管是否与 publishers 表中的 city 列匹配，LEFT OUTER JOIN 均会在结果中包含 authors 表的所有行。注意：结果中所列的大多数作者都没有相匹配的数据,因此，这些行的 pub_name 列包含空值。

使用右向外联接

假设在 city 列上联接 authors 表和 publishers 表。结果只显示在出版商所在城市居住的作者（本例中为 Abraham Bennet 和 Cheryl Carson）。SQL-92 右向外联接运算符 RIGHT OUTER JOIN 指明：不管第一个表中是否有匹配的数据，结果将包含第二个表中的所有行。

若要在结果中包括所有的出版商，而不管城市中是否还有出版商居住，请使用 SQL-92 右向外联接。下面是 Transact-SQL 右向外联接的查询和结果：

USE pubs
SELECT a.au_fname, a.au_lname, p.pub_name
FROM authors AS a RIGHT OUTER JOIN publishers AS p
   ON a.city = p.city
ORDER BY p.pub_name ASC, a.au_lname ASC, a.au_fname ASC

下面是结果集：

au_fname             au_lname                 pub_name
-------------------- ------------------------ --------------------
Abraham              Bennet                   Algodata Infosystems
Cheryl               Carson                   Algodata Infosystems
NULL                 NULL                     Binnet & Hardley
NULL                 NULL                     Five Lakes Publishing
NULL                 NULL                     GGG&G
NULL                 NULL                     Lucerne Publishing
NULL                 NULL                     New Moon Books
NULL                 NULL                     Ramona Publishers
NULL                 NULL                     Scootney Books

(9 row(s) affected)

使用谓词（如将联接与常量比较）可以进一步限制外联接。下例包含相同的右向外联接，但消除销售量低于 50 本的书籍的书名：

USE pubs
SELECT s.stor_id, s.qty, t.title
FROM sales s RIGHT OUTER JOIN titles t
   ON s.title_id = t.title_id
   AND s.qty > 50
ORDER BY s.stor_id ASC

下面是结果集：

stor_id qty    title
------- ------ ---------------------------------------------------------
(null) (null) But Is It User Friendly?
(null) (null) Computer Phobic AND Non-Phobic Individuals: Behavior
            Variations
(null) (null) Cooking with Computers: Surreptitious Balance Sheets
(null) (null) Emotional Security: A New Algorithm
(null) (null) Fifty Years in Buckingham Palace Kitchens
7066   75     Is Anger the Enemy?
(null) (null) Life Without Fear
(null) (null) Net Etiquette
(null) (null) Onions, Leeks, and Garlic: Cooking Secrets of the
            Mediterranean
(null) (null) Prolonged Data Deprivation: Four Case Studies
(null) (null) Secrets of Silicon Valley
(null) (null) Silicon Valley Gastronomic Treats
(null) (null) Straight Talk About Computers
(null) (null) Sushi, Anyone?
(null) (null) The Busy Executive's Database Guide
(null) (null) The Gourmet Microwave
(null) (null) The Psychology of Computer Cooking
(null) (null) You Can Combat Computer Stress!

(18 row(s) affected)

有关谓词的更多信息，请参见 WHERE。

使用完整外部联接

若要通过在联接结果中包括不匹配的行保留不匹配信息，请使用完整外部联接。Microsoft® SQL Server™ 2000 提供完整外部联接运算符 FULL OUTER JOIN，不管另一个表是否有匹配的值，此运算符都包括两个表中的所有行。

假设在 city 列上联接 authors 表和 publishers 表。结果只显示在出版商所在城市居住的作者（本例中为 Abraham Bennet 和 Cheryl Carson）。SQL-92 FULL OUTER JOIN 运算符指明：不管表中是否有匹配的数据，结果将包括两个表中的所有行。

若要在结果中包括所有作者和出版商，而不管城市中是否有出版商或者出版商是否住在同一个城市，请使用完整外部联接。下面是 Transact-SQL 完整外部联接的查询和结果：

USE pubs
SELECT a.au_fname, a.au_lname, p.pub_name
FROM authors a FULL OUTER JOIN publishers p
   ON a.city = p.city
ORDER BY p.pub_name ASC, a.au_lname ASC, a.au_fname ASC

下面是结果集：

au_fname             au_lname                     pub_name
-------------------- ---------------------------- --------------------
Reginald             Blotchet-Halls               NULL
Michel               DeFrance                     NULL
Innes                del Castillo                 NULL
Ann                  Dull                         NULL
Marjorie             Green                        NULL
Morningstar          Greene                       NULL
Burt                 Gringlesby                   NULL
Sheryl               Hunter                       NULL
Livia                Karsen                       NULL
Charlene             Locksley                     NULL
Stearns              MacFeather                   NULL
Heather              McBadden                     NULL
Michael              O'Leary                      NULL
Sylvia               Panteley                     NULL
Albert               Ringer                       NULL
Anne                 Ringer                       NULL
Meander              Smith                        NULL
Dean                 Straight                     NULL
Dirk                 Stringer                     NULL
Johnson              White                        NULL
Akiko                Yokomoto                     NULL
Abraham              Bennet                       Algodata Infosystems
Cheryl               Carson                       Algodata Infosystems
NULL                 NULL                         Binnet & Hardley
NULL                 NULL                         Five Lakes Publishing
NULL                 NULL                         GGG&G
NULL                 NULL                         Lucerne Publishing
NULL                 NULL                         New Moon Books
NULL                 NULL                         Ramona Publishers
NULL                 NULL                         Scootney Books

(30 row(s) affected)

请参见

运算符

SELECT

在表达式中使用运算符

SELECT 示例

编写可读性强的代码


使用交叉联接

没有 WHERE 子句的交叉联接将产生联接所涉及的表的笛卡尔积。第一个表的行数乘以第二个表的行数等于笛卡尔积结果集的大小。下面是 Transact-SQL 交叉联接示例：

USE pubs
SELECT au_fname, au_lname, pub_name
FROM authors CROSS JOIN publishers
ORDER BY au_lname DESC

结果集包含 184 行（authors 有 23 行，publishers 有 8 行；23 乘以 8 等于 184）。

不过，如果添加一个 WHERE 子句，则交叉联接的作用将同内联接一样。例如，下面的 Transact-SQL 查询得到相同的结果集：

USE pubs
SELECT au_fname, au_lname, pub_name
FROM authors CROSS JOIN publishers
WHERE authors.city = publishers.city
ORDER BY au_lname DESC

— 或
USE pubs
SELECT au_fname, au_lname, pub_name
FROM authors INNER JOIN publishers
ON authors.city = publishers.city
ORDER BY au_lname DESC

请参见

WHERE

SELECT

运算符

SELECT 示例

在表达式中使用运算符


使用自联接

表可以通过自联接与自身联接。例如，可以使用自联接查找居住在 California 的 Oakland 相同邮码区域中的作者。

由于此查询涉及 authors 表与其自身的联接，因此 authors 表以两种角色显示。若要区分这两个角色，必须在 FROM 子句中为 authors 表提供两个不同的别名（au1 和 au2）。这些别名用来限定其余查询中的列名。下面是自联接 Transact-SQL 语句示例：

USE pubs
SELECT au1.au_fname, au1.au_lname, au2.au_fname, au2.au_lname
FROM authors au1 INNER JOIN authors au2
   ON au1.zip = au2.zip
WHERE au1.city = 'Oakland'
ORDER BY au1.au_fname ASC, au1.au_lname ASC

下面是结果集：

au_fname             au_lname            au_fname             au_lname
-------------------- ------------------- -------------------- ---------
Dean                 Straight            Dean                 Straight
Dean                 Straight            Dirk                 Stringer
Dean                 Straight            Livia                Karsen
Dirk                 Stringer            Dean                 Straight
Dirk                 Stringer            Dirk                 Stringer
Dirk                 Stringer            Livia                Karsen
Livia                Karsen              Dean                 Straight
Livia                Karsen              Dirk                 Stringer
Livia                Karsen              Livia                Karsen
Marjorie             Green               Marjorie             Green
Stearns              MacFeather          Stearns              MacFeather

(11 row(s) affected)

若要消除结果中作者与自身匹配的行以及完全相同的行（作者顺序颠倒除外），请对 Transact-SQL 自联接查询进行如下更改：

USE pubs
SELECT au1.au_fname, au1.au_lname, au2.au_fname, au2.au_lname
FROM authors au1 INNER JOIN authors au2
   ON  au1.zip = au2.zip
WHERE au1.city = 'Oakland'
   AND au1.state = 'CA'
   AND au1.au_id < au2.au_id
ORDER BY au1.au_lname ASC, au1.au_fname ASC

下面是结果集：

au_fname     au_lname          au_fname             au_lname
------------ ----------------- -------------------- --------------------
Dean         Straight          Dirk                 Stringer
Dean         Straight          Livia                Karsen
Dirk         Stringer          Livia                Karsen

(3 row(s) affected)

从上例可以看出 Dean Straight、Dirk Stringer 和 Livia Karsen 都有相同的邮码并且都住在 California 的 Oakland。

请参见

WHERE

SELECT

运算符

SELECT 示例

在表达式中使用运算符


联接三个或更多的表

虽然每个联接规范只联接两个表，但 FROM 子句可包含多个联接规范。这样一个查询可以联接若干个表。

pubs 数据库的 titleauthor 表提供了一个很好的示例，该示例说明联接两个以上的表非常有用。下面的 Transact-SQL 查询可找到特定类型的所有书名及相应的作者姓名：

USE pubs
SELECT a.au_lname, a.au_fname, t.title
FROM authors a INNER JOIN titleauthor ta
   ON a.au_id = ta.au_id JOIN titles t
   ON ta.title_id = t.title_id
WHERE t.type = 'trad_cook'
ORDER BY t.title ASC

下面是结果集：

au_lname          au_fname             title
----------------- -------------------- ----------
Blotchet-Halls    Reginald             Fifty Years in Buckingham Palace
   Kitchens
Panteley          Sylvia               Onions, Leeks, and Garlic:
   Cooking Secrets of the Mediterranean
O'Leary           Michael              Sushi, Anyone?
Gringlesby        Burt                 Sushi, Anyone?
Yokomoto          Akiko                Sushi, Anyone?

(5 row(s) affected)

注意，FROM 子句中的 titleauthor 表不会为结果提供任何列。而且联接列 au_id 和 title_id 都不会出现在结果中。尽管如此，只有将 titleauthor 用作中间表，此联接才有可能实现。

由于 titleauthor 表是参与联接的其它表之间的连接中间点，因此联接的中间表（titleauthor 表）可称为转换表或中间表。

如果同一语句包含多个联接运算符，用于联接多于两个的表或联接多于两对的列，那么联接表达式可以用 AND 或 OR 连接。

请参见

WHERE

SELECT

运算符

SELECT 示例


空值和联接

如果要联接的表的列中有空值，则这些空值互相不匹配。如果其中一个联接表的列中出现空值，只能通过外联接返回这些空值（除非 WHERE 子句不包括空值）。

下面的两个表中，每个表在要参与联接的列中均包含 NULL 值：

table1                          table2
a           b                   c            d
-------     ------              -------      ------
      1        one                 NULL         two
   NULL      three                    4        four
      4      join4

将列 a 中的值与列 c 中的值进行比较的联接在包含 NULL 值的列上不能获得匹配结果：

SELECT *
FROM table1 t1 JOIN table2 t2
   ON t1.a = t2.c
ORDER BY t1.a

只返回列 a 和 c 值为 4 的一行：

a           b      c           d
----------- ------ ----------- ------
4           join4  4           four

(1 row(s) affected)

从基表返回的空值和从外联接返回的空值也很难区分。例如，下面的 SELECT 语句对这两个表进行左向外联接：

SELECT *
FROM table1 t1 LEFT OUTER JOIN table2 t2
   ON t1.a = t2.c
ORDER BY t1.a

下面是结果集：

a           b      c           d
----------- ------ ----------- ------
NULL        three  NULL        NULL
1           one    NULL        NULL
4           join4  4           four

(3 row(s) affected)

结果并不能使数据中的 NULL 值和表示联接失败的 NULL 值容易区分。如果要联接的数据出现空值，最好用常规联接将这些空值从结果中省略。

请参见

sp_dbcmptlevel

WHERE


在 FROM 或 WHERE 子句中指定联接

查询所选的行首先通过 FROM 子句联接条件进行筛选，其次由 WHERE 子句搜索条件筛选，然后由 HAVING 子句搜索条件筛选。可以在 FROM 或 WHERE 子句中指定内联接而不影响最后结果。

然而，根据联接条件是在 FROM 子句中还是在 WHERE 子句中，外联接条件与 WHERE 搜索条件的交互可能会不同。因此，建议不要在 WHERE 子句中指定 Transact-SQL 外联接，这种方法不再写入文档并将在以后的版本中除去。

例如，下面两个查询对 SELECT 23 行指定左向外联接，用以显示书名标识号、书名以及已出售的书的数量：

-- Join in WHERE clause.
USE pubs
SELECT t.title_id, t.title, s.qty
FROM titles AS t, sales AS s
WHERE t.title_id *= s.title_id

-- Join in FROM clause.
USE pubs
SELECT t.title_id, t.title, s.qty
FROM titles AS t LEFT OUTER JOIN sales AS s
   ON t.title_id = s.title_id

在下面的查询中，WHERE 子句也指定搜索条件：

-- Join and search condition in WHERE clause.
USE pubs
SELECT t.title_id, t.title, s.qty
FROM titles AS t, sales AS s
WHERE t.title_id *= s.title_id
   AND s.stor_id = '7066'

stor_id = '7066' 条件在联接中取值。联接只从 sales 表中选择 stor_id 7066 的行，但由于这是外联接，因此空值在所有其它行中作为存储信息提供。此查询将返回 18 行。

可以将联接条件移动到 FROM 子句，而 stor_id 条件留在 WHERE 子句中：

USE pubs
SELECT t.title_id, t.title, s.qty
FROM titles AS t LEFT OUTER JOIN sales AS s
   ON t.title_id = s.title_id
WHERE s.stor_id = '7066'

由于执行左向外联接之后应用 stor_id = '7066' 限制条件，因此此查询只返回两行。这就消除外联接中 stor_id 为 NULL 值的所有行。若要通过 FROM 子句中的联接条件返回相同的信息，请将 stor_id = '7066' 条件指定为 FROM 子句中 ON join_criteria 部分，然后删除 WHERE 子句：

USE pubs
SELECT t.title_id, t.title, s.qty
FROM titles AS t LEFT OUTER JOIN sales AS s
   ON t.title_id = s.title_id
   AND s.stor_id = '7066'

请参见

WHERE

在表达式中使用运算符


高级查询概念

在掌握查询的基础知识后，您就可以探知查询解决方案中使用的下列高级查询概念：

  • 在选择列表中使用聚合函数

  • 使用 GROUP BY 对多行分组

  • 使用 UNION 组合结果

  • 子查询基础知识

  • 使用 CASE 处理条件数据

  • 并行查询

  • 汇总数据

请参见

查询基础知识


在选择列表中使用聚合函数

聚合函数（例如 SUM、AVG、COUNT、COUNT(*)、MAX 和 MIN）在查询结果集中生成汇总值。聚合函数（除 COUNT(*) 以外）处理单个列中全部所选的值以生成一个结果值。聚合函数可以应用于表中的所有行、WHERE 子句指定的表的子集或表中一组或多组行。应用聚合函数后，每组行都将生成一个值。

下面的示例计算 titles 表中所有书籍的本年度截止到目前的销售总额：

USE pubs
SELECT SUM(ytd_sales)
FROM titles

下面是结果集：

------------------
97446

(1 row(s) affected)

使用下面的查询，若将价格乘以二，可以得到所有书籍的平均价格：

USE pubs
SELECT avg(price * 2)
FROM titles

下面是结果集：

------------------
29.53

(1 row(s) affected)

下表显示了聚合函数的语法及结果（expression 几乎总为列名）。

聚合函数 | 结果
SUM([ALL | DISTINCT] expression) | 数字表达式中所有值的和
AVG([ALL | DISTINCT] expression) | 数字表达式中所有值的平均值
COUNT([ALL | DISTINCT] expression) | 表达式中值的个数
COUNT(*) | 选定的行数
MAX(expression) | 表达式中的最高值
MIN(expression) | 表达式中的最低值

SUM、AVG、COUNT、MAX 和 MIN 忽略空值，而 COUNT(*) 不忽略。

可选关键字 DISTINCT 可以与 SUM、AVG 和 COUNT 一同使用，以便在应用聚合函数之前消除重复值（默认为 ALL）。

SUM 和 AVG 只能对数字列使用，例如 int、smallint、tinyint、decimal、numeric、float、real、money 和 smallmoney 数据类型。MIN 和 MAX 不能对 bit 数据类型使用。除 COUNT(*) 外，其它聚合函数均不能对 text 和 image 数据类型使用。

除了上述例外情况，聚合函数可以对任何类型的列使用。例如，在 character 数据类型列中，使用 MIN（最小值）查找最低值（即最接近字母表开头的值）：

USE pubs
SELECT MIN(au_lname)
FROM authors

下面是结果集：

------------------
Bennet

(1 row(s) affected)

聚合函数返回的结果类型的精度可能比输入的类型要高，从而使结果类型大得足以容纳聚合后的结果值。例如，SUM 或 AVG 函数在输入的数据类型为 smallint 或 tinyint 时返回一个 int 值。有关聚合函数返回的数据类型的更多信息，请参见 Microsoft® SQL Server™ 2000 Transact-SQL 参考中有关该函数的主题。

说明 对于涉及字符列上的 MIN 或 MAX 的语句，其输出结果取决于在安装过程中选择的排序规则。有关不同排序规则的效果的更多信息，请参见 SQL Server 排序规则基础知识。

如果某个选择列表中使用了聚合函数，则该选择列表只能包含：

  • 聚合函数。

  • 通过 GROUP BY 子句分组的列。

  • 为结果集中每一行返回同一值（例如一个常量）的表达式。

有关为包含多个行的结果集生成聚合值的更多信息，请参见使用 GROUP BY 分组多行。

WHERE 子句中不能使用聚合函数。但是，在其选择列表中包含聚合函数的 SELECT 语句经常包括 WHERE 子句，该子句限制要应用聚合函数的行。如果 SELECT 语句中包含 WHERE 子句（但不包含 GROUP BY 子句），那么聚合函数将为 WHERE 子句指定的行的子集产生一个值。不论聚合函数是应用于表中的所有行还是 WHERE 子句定义的行的子集，都会产生该值。这类函数可称为标量聚合。

以下查询只返回商务书籍的预付款平均值和 year-to-date 的销售额总和：

USE pubs
SELECT AVG(advance), SUM(ytd_sales)
FROM titles
WHERE type = 'business'

下面是结果集：

---------     -------
6,281.25      30788

(1 row(s) affected)

可以在同一个选择列表中使用多个聚合函数，即在一个 SELECT 语句中产生多个标量聚合。

请参见

聚合函数

SELECT


使用 COUNT(*) 函数

COUNT(*) 函数不需要 expression 参数，因为该函数不使用有关任何特定列的信息。该函数计算符合查询限制条件的总行数。COUNT(*) 函数返回符合查询中指定的搜索条件的行的数目，而不消除重复值。它对每行分别进行计数，包括含有空值的行。以下查询查找 titles 中的书籍总数：

USE pubs
SELECT COUNT(*)
FROM titles

下面是结果集：

------------------
18

(1 row(s) affected)

COUNT(*) 可以与其它聚合函数组合使用。以下查询显示与 AVG 函数组合使用的 COUNT(*)，其中两个聚合函数都只聚合满足 WHERE 子句搜索条件的行中的数据：

USE pubs
SELECT COUNT(*), AVG(price)
FROM titles
WHERE advance > $1000

下面是结果集：

----------- ------
15          14.42

(1 row(s) affected)

请参见

COUNT


使用 DISTINCT

DISTINCT 是 SUM、AVG 和 COUNT 的可选关键字。如果使用 DISTINCT，那么在计算总和、平均值或计数之前，先消除重复的值。

如果使用 DISTINCT 关键字，表达式必须只包含列名。而不能包含算术表达式。

以下查询返回商务书籍的平均价格（不包括重复的值）：

USE pubs
SELECT AVG(DISTINCT price)
FROM titles
WHERE type = 'business'

下面是结果集：

------------------
14.64

(1 row(s) affected)

如果不使用 DISTINCT 关键字，则 AVG 函数将计算出所有商务书名的平均价格：

USE pubs
SELECT AVG(price)
FROM titles
WHERE type = 'business

下面是结果集：

------------------
13.73

(1 row(s) affected)

请参见

聚合函数

SELECT


空值

运行聚合函数时，列中的空值将被忽略。例如，titles 表中预付款的计数与书名的计数不同，因为 advance 列中的空值不计算在内。

USE pubs
SELECT COUNT(advance)
FROM titles

下面是结果集：

------------------
16

(1 row(s) affected)

USE pubs
SELECT COUNT(title)
FROM titles

下面是结果集：

------------------
18

(1 row(s) affected)

如果没有符合 WHERE 子句中指定的条件的行，那么 COUNT 函数返回的值为零。其它函数均返回 NULL。COUNT(*) 函数对每一行进行计数，即使所有的列值都为 NULL。下面是一些示例：

USE pubs
SELECT COUNT(DISTINCT title)
FROM titles
WHERE type = 'poetry'

下面是结果集：

------------------
0

(1 row(s) affected)

USE pubs
SELECT AVG(advance)
FROM titles
WHERE type = 'poetry'

下面是结果集：

------------------
(null)

(1 row(s) affected)


使用 GROUP BY 分组多行

GROUP BY 子句用来为结果集中的每一行产生聚合值。如果聚合函数没有使用 GROUP BY 子句，则只为 SELECT 语句报告一个聚合值。

以下示例返回分类 2 中每种产品已销售的单位数量：

USE Northwind
SELECT OrdD.ProductID AS ProdID,
       SUM(OrdD.Quantity) AS AmountSold
FROM [Order Details] AS OrdD JOIN Products as Prd
     ON OrdD.ProductID = Prd.ProductID
     AND Prd.CategoryID = 2
GROUP BY OrdD.ProductID

下面是结果集：

ProdID      AmountSold
----------- -----------
3           328
4           453
5           298
6           301
8           372
15          122
44          601
61          603
63          445
65          745
66          239
77          791

(12 row(s) affected)

GROUP BY 关键字后面跟着列的列表，称为分组列。GROUP BY 子句限制结果集中的行；对于分组列中的每个非重复值只有一行。每个结果集行都包含与其分组列中特定值相关的汇总数据。

当 SELECT 语句中包含 GROUP BY 关键字时，对可以在选择列表中指定的项有一些限制。在该选择列表中所允许的项目是：

  • 分组列。

  • 为分组列中的每个值只返回一个值的表达式，例如将列名作为其中一个参数的聚合函数。这些函数称为矢量聚合。

例如，TableX 包含：

ColumnA | ColumnB | ColumnC
------- | ------- | -------
1 | abc | 5
1 | def | 4
1 | ghi | 9
2 | jkl | 8
2 | mno | 3

如果 ColumnA 是分组列，则结果集中将有两行，其中一行汇总值 1 的信息，而另一行汇总值 2 的信息。

如果 ColumnA 是分组列，要引用 ColumnB 或 ColumnC，这两列必须能为 ColumnA 中的每个值返回单个值的聚合函数中的参数。选择列表中包含诸如 MAX (ColumnB)、SUM (ColumnC) 或 AVG (ColumnC) 之类的表达式是合法的：

SELECT ColumnA,
       MAX(ColumnB) AS MaxB,
       SUM(ColumnC) AS SumC
FROM TableX
GROUP BY ColumnA

该选择语句返回两行，为 ColumnA 中的每个唯一值各返回一行：

ColumnA     MaxB SumC
----------- ---- -----------
1           ghi  18
2           mno  11

(2 row(s) affected)

但是，选择列表中只包含 ColumnB 表达式是不合法的：

SELECT ColumnA,
       ColumnB,
       SUM(ColumnC) AS SumC
FROM TableX
GROUP BY ColumnA

由于 GROUP BY 关键字只能返回一行，该行 ColumnA 中的值为 1，因此无法返回与 ColumnA 中的值 1 关联的 ColumnB 的三个值（abc、def 和 ghi）。

不能对 ntext、text、image 或 bit 列使用 GROUP BY 或 HAVING 子句，除非它们所在的函数返回的值具有其它数据类型。这样的函数包括 SUBSTRING 和 CAST。

请参见

CAST 和 CONVERT

SUBSTRING

SELECT


GROUP BY 组件

GROUP BY 子句包含以下组件：

  • 一个或多个自由聚合的表达式。通常是对分组列的引用。

  • ALL 关键字（可选），该关键字指定返回由 GROUP BY 子句产生的所有组，即使某些组没有符合搜索条件的行。

  • CUBE 或 ROLLUP。

  • 通常情况下，HAVING 子句与 GROUP BY 子句一起使用，不过也可以单独指定 HAVING 子句。

只要表达式中不包括聚合函数，就可通过该表达式分组，例如：

SELECT DATEPART(yy, HireDate) AS Year,
       COUNT(*) AS NumberOfHires
FROM Northwind.dbo.Employees
GROUP BY DATEPART(yy, HireDate)

下面是结果集。

Year     NumberOfHires
1992     3
1993     3
1994     3

(3 row(s) affected)

在 GROUP BY 子句中，必须指定表或视图列的名称，而不是使用 AS 子句指派的结果集列的名称。例如，以 GROUP BY Year 替换 GROUP BY DATEPART(yy, HireDate) 子句是不合法的。

可以在 GROUP BY 子句中列出多个列以嵌套组，即可以通过列的任意组合分组表。例如，以下查询按类型和出版商 ID 分组，得到平均价格和 year-to-date 的销售额总和：

USE pubs
SELECT type, pub_id, 'avg' = AVG(price), 'sum' = sum(ytd_sales)
FROM titles
GROUP BY type, pub_id

下面是结果集：

type         pub_id avg                    sum
------------ ------ ---------------------- -----------
business     0736    2.99                  18722
psychology   0736   11.48                  9564
mod_cook     0877   11.49                  24278
psychology   0877   21.59                  375
trad_cook    0877   15.96                  19566
UNDECIDED    0877    NULL                  NULL
business     1389   17.31                  12066
popular_comp 1389   21.48                  12875

(8 row(s) affected)

请参见

SELECT


GROUP BY 和 WHERE 子句

可以在包含 GROUP BY 子句的查询中使用 WHERE 子句。在进行任何分组之前，将消除不符合 WHERE 子句条件的行。例如：

USE pubs
SELECT type, AVG(price)
FROM titles
WHERE advance > $5000
GROUP BY type

下面是结果集：

type
------------ --------------------------
business     2.99
mod_cook     2.99
popular_comp 21.48
psychology   14.30
trad_cook    17.97

(5 row(s) affected)

只有预付款超过 $5,000 的行才包含在查询结果所显示的组中。

请参见

SELECT

WHERE


使用 HAVING 子句选择行

HAVING 子句对 GROUP BY 子句设置条件的方式与 WHERE 子句和 SELECT 语句交互的方式类似。WHERE 子句搜索条件在进行分组操作之前应用；而 HAVING 搜索条件在进行分组操作之后应用。HAVING 语法与 WHERE 语法类似，但 HAVING 可以包含聚合函数。HAVING 子句可以引用选择列表中出现的任意项。

下面的查询得到本年度截止到目前的销售额超过 $40,000 的出版商：

USE pubs
SELECT pub_id, total = SUM(ytd_sales)
FROM titles
GROUP BY pub_id
HAVING SUM(ytd_sales) > 40000

下面是结果集：

pub_id total
------ -----------
0877   44219

(1 row(s) affected)

为了确保对每个出版商的计算中至少包含六本书，下面示例使用 HAVING COUNT(*) > 5 消除返回的总数小于六本书的出版商：

USE pubs
SELECT pub_id, total = SUM(ytd_sales)
FROM titles
GROUP BY pub_id
HAVING COUNT(*) > 5

下面是结果集：

pub_id total
------ -----------
0877   44219
1389   24941

(2 row(s) affected)

理解应用 WHERE、GROUP BY 和 HAVING 子句的正确序列对编写高效的查询代码会有所帮助：

  • WHERE 子句用来筛选 FROM 子句中指定的操作所产生的行。

  • GROUP BY 子句用来分组 WHERE 子句的输出。

  • HAVING 子句用来从分组的结果中筛选行。

对于可以在分组操作之前或之后应用的搜索条件，在 WHERE 子句中指定它们更有效。这样可以减少必须分组的行数。应当在 HAVING 子句中指定的搜索条件只是那些必须在执行分组操作之后应用的搜索条件。

Microsoft® SQL Server™ 2000 查询优化器可处理这些条件中的大多数。如果查询优化器确定 HAVING 搜索条件可以在分组操作之前应用，那么它就会在分组之前应用。查询优化器可能无法识别所有可以在分组操作之前应用的 HAVING 搜索条件。建议将所有这些搜索条件放在 WHERE 子句中而不是 HAVING 子句中。

以下查询显示包含聚合函数的 HAVING 子句。该子句按类型分组 titles 表中的行，并且消除只包含一本书的组：

USE pubs
SELECT type
FROM titles
GROUP BY type
HAVING COUNT(*) > 1

下面是结果集：

type
------------------
business
mod_cook
popular_comp
psychology
trad_cook

(5 row(s) affected)

以下是没有聚合函数的 HAVING 子句的示例。该子句按类型分组 titles 表中的行，并且消除不是以字母 p 开头的那些类型。

USE pubs
SELECT type
FROM titles
GROUP BY type
HAVING type LIKE 'p%'

下面是结果集：

type
------------------
popular_comp
psychology

(2 row(s) affected)

如果 HAVING 中包含多个条件，那么这些条件将通过 AND、OR 或 NOT 组合在一起。以下示例显示如何按出版商分组 titles，只包括那些标识号大于 0800、支付的总预付款已超过 $15,000 且销售书籍的平均价格小于 $20 的出版商。

SELECT pub_id, SUM(advance) AS AmountAdvanced,
       AVG(price) AS AveragePrice
FROM pubs.dbo.titles
WHERE pub_id > '0800'
GROUP BY pub_id
HAVING SUM(advance) > $15000
   AND AVG(price) < $20

ORDER BY 可以用来为 GROUP BY 子句的输出排序。下面的示例显示使用 ORDER BY 子句以定义返回 GROUP BY 子句中的行的顺序：

SELECT pub_id, SUM(advance) AS AmountAdvanced,
       AVG(price) AS AveragePrice
FROM pubs.dbo.titles
WHERE pub_id > '0800'
  AND price >= $5
GROUP BY pub_id
HAVING SUM(advance) > $15000
   AND AVG(price) < $20
ORDER BY pub_id DESC

请参见

SELECT


GROUP BY 子句和 ALL 关键字

Transact-SQL 在 GROUP BY 子句中提供 ALL 关键字。只有在 SELECT 语句还包括 WHERE 子句时，ALL 关键字才有意义。

如果使用 ALL 关键字，那么查询结果将包括由 GROUP BY 子句产生的所有组，即使某些组没有符合搜索条件的行。没有 ALL 关键字，包含 GROUP BY 子句的 SELECT 语句将不显示没有符合条件的行的组。

下面是一些示例：

USE pubs
SELECT type, AVG(price)
FROM titles
WHERE royalty = 10
GROUP BY type

下面是结果集：

type
------------ --------------------------
business     17.31
popular_comp 20.00
psychology   14.14
trad_cook    17.97

(4 row(s) affected)

USE pubs
SELECT type, AVG(price)
FROM titles
WHERE royalty = 10
GROUP BY ALL type

下面是结果集：

type
------------ --------------------------
business     17.31
mod_cook     (null)
popular_comp 20.00
psychology   14.14
trad_cook    17.97
UNDECIDED    (null)

(6 row(s) affected)

第一个查询只为取得 %10 版税的那些书籍产生组。由于没有含 10% 版税的现代烹调书籍，因此结果中没有 mod_cook 类型的组。

第二个查询为所有类型均生成组，包括现代烹调书籍和 UNDECIDED，即使现代烹调书籍组不包含任何符合 WHERE 子句中指定的条件的行。

对于没有符合条件的行的组，容纳聚合值的列（平均价格）为 NULL。

请参见

ALL

SELECT


GROUP BY 子句和空值

如果分组列包含一个空值，那么该行将成为结果中的一个组。如果分组列包含多个空值，那么这些空值将放入一个组中。此行为在 SQL-92 标准中定义。

titles 表中的 royalty 列包含一些空值，例如：

SELECT royalty, AVG(price * 2) AS AveragePrice
FROM pubs.dbo.titles
GROUP BY royalty

下面是结果集：

royalty     AveragePrice
----------- --------------------------
(null)      (null)
10          32.89
12          30.94
14          23.90
16          45.90
24          5.98

(6 row(s) affected)

下面的 SELECT 语句通过添加一个 WHERE 子句，可以更改为删除空值：

SELECT royalty, AVG(price * 2) AS AveragePrice
FROM pubs.dbo.titles
WHERE royalty IS NOT NULL
GROUP BY royalty


使用 UNION 运算符组合多个结果

UNION 运算符使您得以将两个或多个 SELECT 语句的结果组合成一个结果集。使用 UNION 组合的结果集都必须具有相同的结构。而且它们的列数必须相同，并且相应的结果集列的数据类型必须兼容。有关更多信息，请参见 UNION 运算符使用指南。

UNION 的指定方式如下：

select_statement UNION [ALL] select_statement

例如，Table1 和 Table2 具有相同的两列结构。

Table1 |  | Table2 |
ColumnA | ColumnB | ColumnC | ColumnD
char(4) | int | char(4) | int
------- | --- | ------- | ---
abc | 1 | ghi | 3
def | 2 | jkl | 4
ghi | 3 | mno | 5

下面的查询在这两个表之间创建 UNION 运算：

SELECT * FROM Table1
UNION
SELECT * FROM Table2

下面是结果集：

ColumnA  ColumnB
-------  --------
abc      1
def      2
ghi      3
jkl      4
mno      5

UNION 的结果集列名与 UNION 运算符中第一个 SELECT 语句的结果集中的列名相同。另一个 SELECT 语句的结果集列名将被忽略。

默认情况下，UNION 运算符从结果集中删除重复的行。如果使用 ALL 关键字，那么结果中将包含所有行并且将不删除重复的行。

UNION 运算的准确结果取决于安装过程中选择的排序规则和 ORDER BY 子句。有关不同排序规则的效果的更多信息，请参见 SQL Server 排序规则基础知识。

Transact-SQL 语句中可以出现任意数目的 UNION 运算符，例如：

SELECT * FROM TableA
UNION
SELECT * FROM TableB
UNION
SELECT * FROM TableC
UNION
SELECT * FROM TableD

默认情况下，Microsoft® SQL Server™ 2000 从左到右对包含 UNION 运算符的语句进行取值。使用圆括号指定求值的顺序。例如，以下语句并不等价：

/* First statement. */
SELECT * FROM TableA
UNION ALL
(   SELECT * FROM TableB
   UNION
   SELECT * FROM TableC
)
GO

/* Second statement. */
(SELECT * FROM TableA
 UNION ALL
 SELECT * FROM TableB
)
UNION
SELECT * FROM TableC)
GO

在第一个语句中，将消除 TableB 和 TableC 之间的联合中的重复行。而在该集与 TableA 之间的并集中，不消除重复行。在第二个语句中，TableA 和 TableB 之间的联合中包含重复行，但在随后与 TableC 的联合中将消除。ALL 关键字对此表达式的最终结果没有影响。

如果使用 UNION 运算符，那么单独的 SELECT 语句不能包含其自己的 ORDER BY 或 COMPUTE 子句。只能在最后一个 SELECT 语句的后面使用一个 ORDER BY 或 COMPUTE 子句；该子句适用于最终的组合结果集。GROUP BY 和 HAVING 子句只能在单独的 SELECT 语句中指定。

请参见

UNION


UNION 运算符使用准则

使用 UNION 运算符时请遵循以下准则：

  • 在使用 UNION 运算符组合的语句中，所有选择列表的表达式数目必须相同（列名、算术表达式、聚合函数等）。

  • 在使用 UNION 组合的结果集中的相应列、或个别查询中使用的任意列的子集必须具有相同数据类型，并且两种数据类型之间必须存在可能的隐性数据转换，或提供了显式转换。例如，在 datetime 数据类型的列和 binary 数据类型的列之间不可能存在 UNION 运算符，除非提供了显式转换，而在 money 数据类型的列和 int 数据类型的列之间可以存在 UNION 运算符，因为它们可以进行隐性转换。

  • 用 UNION 运算符组合的各语句中对应的结果集列出现的顺序必须相同，因为 UNION 运算符是按照各个查询给定的顺序逐个比较各列。

    示例如下：

    table3 | table4
    a | b | -c | a | b
    int | char(4) | char(4) | char(4) | float
    --- ------- ------- ------- -------
    1 | abc | jkl | jkl | 1.000
    2 | def | mno | mno | 5.000
    3 | ghi | pqr

    执行以下查询：

    SELECT a, b FROM table3
UNION
SELECT b, a FROM table4

    下面是结果集：

    a          b
--------   -----
1.000000   abc
2.000000   def
3.000000   ghi
1.000000   jkl
5.000000   mno

    在 UNION 操作中组合不同的数据类型时，这些数据类型将使用数据类型优先级的规则进行转换。在前面的示例中，int 值转换成 float 值，因为 float 类型的优先权比 int 类型高。有关更多信息，请参见数据类型的优先顺序。

    以下查询将产生错误信息，因为相应列的数据类型不兼容：

    SELECT b, c FROM table3
UNION
SELECT a, b FROM table4

    •

  • 通过 UNION 生成的表中的列名来自 UNION 语句中的第一个单独的查询。若要用新名称引用结果集中的某列（例如在 ORDER BY 子句中），必须按第一个 SELECT 语句中的方式引用该列：

    SELECT city AS Cities FROM stores_west
UNION
SELECT city FROM stores_east
ORDER BY city

    •

请参见

UNION


与其它 Transact-SQL 语句一起使用 UNION 语句

与其它 Transact-SQL 语句一起使用 UNION 语句时，请遵循以下指南：

  • UNION 语句中的第一个查询可以包含一个 INTO 子句，该子句用来创建容纳最终结果集的表。只有第一个查询可以使用 INTO 子句。如果该子句出现在别处，Microsoft® SQL Server™ 2000 将显示错误信息。此外请记住：如果没有设置 select into/bulkcopy 选项，那么 SELECT INTO 子句只能创建临时表。

  • 只有在 UNION 语句的结尾，才允许使用 ORDER BY 和 COMPUTE 子句以定义最终结果的顺序或计算汇总值。不能在组建 UNION 语句的单独查询中使用这些子句。

  • GROUP BY 和 HAVING 子句只能在单独的查询中使用；它们不能用来影响最终的结果集。

  • UNION 运算符可用于 INSERT 语句中。

  • FOR BROWSE 子句不能在包含 UNION 运算符的语句中使用。

请参见

SELECT

UNION


使用分区视图

分区视图允许将大型表中的数据拆分成较小的成员表。根据其中一列中的数据值范围，将数据在各个成员表之间进行分区。每个成员表的数据范围都在为分区列指定的 CHECK 约束中定义。然后定义一个视图，以使用 UNION ALL 将选定的成员表组合成单个结果集。引用该视图的 SELECT 语句为分区列指定搜索条件后，查询优化器将使用 CHECK 约束定义确定哪个成员表包含这些行。

例如，记录 1998 年销售额的 sales 表分区成 12 个成员表，每个月是一个成员表。每个成员表在 OrderMonth 列上都定义了约束：

CREATE TABLE May1998Sales
   (OrderID   INT      PRIMARY KEY,
   CustomerID      INT         NOT NULL,
   OrderDate      DATETIME      NULL
      CHECK (DATEPART(yy, OrderDate) = 1998),
   OrderMonth      INT
      CHECK (OrderMonth = 5),
   DeliveryDate      DATETIME      NULL,
      CHECK (DATEPART(mm, OrderDate) = OrderMonth)
   )

填充 May1998Sales 的应用程序必须确保所有行在 OrderMonth 列中均为 5，并且订购日期指定 1998 年 5 月的某个日期。该表上定义的约束强制实现这一要求。

然后定义一个视图，以使用 UNION ALL 选定全部 12 个成员表中的数据作为单个结果集：

CREATE VIEW Year1998Sales
AS
SELECT * FROM Jan1998Sales
UNION ALL
SELECT * FROM Feb1998Sales
UNION ALL
SELECT * FROM Mar1998Sales
UNION ALL
SELECT * FROM Apr1998Sales
UNION ALL
SELECT * FROM May1998Sales
UNION ALL
SELECT * FROM Jun1998Sales
UNION ALL
SELECT * FROM Jul1998Sales
UNION ALL
SELECT * FROM Aug1998Sales
UNION ALL
SELECT * FROM Sep1998Sales
UNION ALL
SELECT * FROM Oct1998Sales
UNION ALL
SELECT * FROM Nov1998Sales
UNION ALL
SELECT * FROM Dec1998Sales

例如，下面的 SELECT 语句查询特定月的信息。

SELECT *
FROM Year1998Sales
WHERE OrderMonth IN (5,6) AND CustomerID = 64892

SQL Server 查询优化器识别出此 SELECT 语句中的搜索条件只引用 May1998Sales 和 Jun1998Sales 表中的行，从而将其搜索范围限制在这些表上。

分区视图返回正确的结果并不一定非要 CHECK 约束。但是，如果未定义 CHECK 约束，则查询优化器必须搜索所有表，而不是只搜索符合分区列上的搜索条件的表。如果不使用 CHECK 约束，则视图的操作方式与带有 UNION ALL 的任何其它视图相同。查询优化器不能对存储在不同表中的值作出任何假设，也不能跳过对参与视图定义的表的搜索。

如果分区视图所引用的所有成员表都在同一服务器上，则该视图是本地分区视图。如果成员表在多台服务器上，则该视图是分布式分区视图。分布式分区视图可用于在一组服务器间分布系统的数据库处理工作量。

分区视图使独立地维护成员表变得更容易。例如，在某个阶段结束时：

  • 可以更改当前结果的分区视图定义以添加最新的阶段和除去最早的阶段。

  • 可以更改以前结果的分区视图定义以添加刚从当前结果视图中除去的阶段。也可以更新以前的结果视图以删除或存档该视图所包含的最早阶段。

将数据插入到分区视图中后，就可以使用 sp_executesql 系统存储过程创建 INSERT 语句，该语句带有在有许多并发用户的系统中重新使用几率较高的执行计划。

请参见

创建分区视图

解析分布式分区视图

联合 SQL Server 2000 服务器

ALTER VIEW

SELECT

CREATE VIEW

sp_executesql


设计使用联合数据库服务器的应用程序

可更新的分布式分区视图支持将多台 Microsoft® SQL Server™ 2000 服务器组合起来，协同处理最大型的多层 Web 站点的数据库工作负荷。虽然各服务器分开管理，但各服务器上的 SQL Server 2000 实例使用分布式分区视图共享工作。一组进行协作以共享工作的自主服务器称为联合体。

通过在每台服务器上创建数据库，然后在各数据库间对表进行分区来生成联合数据库服务器。在每台成员服务器上，将每个原始表拆分为成员表。每个成员表拥有原始表中的行的子集；将原始表在各成员表间进行水平分区。当设计联合数据库系统时，分区所有的表以使所有相关的数据都位于同一台成员服务器上。

在一组联合数据库服务器间分区表的一个结果是一套数据路由规则。应用程序可将从用户请求推断出的某个数据块与路由规则进行匹配，以确定哪台成员服务器拥有 SQL 语句所需的且应用程序必须生成以满足用户请求的大多数数据。有关更多信息，请参见设计联合数据库服务器。

在多层 Windows DNA 构架中，系统在以下各层中实现：

  • 用户服务层。一组着重于管理应用程序用户接口的瘦客户机。用户服务层调用下一层执行支持用户请求所需的业务功能。

  • 业务服务层。一组封装单位的业务逻辑的 COM+ 组件。业务服务层使用下一层存储需要完成的任何永久数据。

  • 数据服务层。一组可将数据存储在永久媒体上的组件，如 SQL Server 数据库。这也称为保持数据的持续性。

在 Windows DNA 中，业务层被设计成一组在应用程序服务器上运行的 COM+ 组件。这使 Microsoft® Windows® 2000 网络负荷平衡可以在业务层上均匀分布用户请求。因为任何用户请求都可能是任何应用程序服务器上的进程，所以业务组件必须具有某种机制，可将其生成的 SQL 语句路由到适当的成员服务器。业务组件必须能够将从客户端接收的数据中的某个信息块与数据路由规则匹配，以确定应由哪台成员服务器处理请求。

在业务服务层实现数据路由的灵活机制是将路由规则存储在永久存储中，如 SQL Server 2000 或 Windows 2000 Active Directory，并使业务组件在运行时检索它们。可对 COM+ 组件进行编码，使其将键与路由规则进行匹配，以确定哪台成员服务器可最有效地处理查询。这样，需要访问分区数据的业务服务层中的任何其它 COM+ 组件都可调用该 COM+ 路由组件。例如，在访问客户 ID 上分区的客户数据的系统中，可以：

  • 创建一个路由规则表记录在各成员服务器上维护的键。

  • 创建一个数据路由业务组件，以采用一个键值或键值范围的起始键或结束键作为输入。COM+ 组件将读取该路由规则表，将输入键或键范围与记录的各成员服务器的键范围进行比较，然后返回最匹配调用组件或应用程序的成员服务器名。

  • 对常规业务服务层组件或应用程序进行编码，使其在执行引用分区视图的 SQL 语句时始终调用数据路由组件。业务组件将使用数据路由组件所返回的服务器名称来选择在其上执行 SQL 语句的数据库连接。

该方法要求在更改数据分区时不更改应用程序代码。在应用程序正在运行时可更改数据路由规则。


子查询基础知识

子查询是一个 SELECT 查询，它返回单个值且嵌套在 SELECT、INSERT、UPDATE、DELETE 语句或其它子查询中。任何允许使用表达式的地方都可以使用子查询。下例中，一个子查询用作 SELECT 语句中名为 MaxUnitPrice 的列表达式。

SELECT Ord.OrderID, Ord.OrderDate,
       (SELECT MAX(OrdDet.UnitPrice)
        FROM Northwind.dbo.[Order Details] AS OrdDet
        WHERE Ord.OrderID = OrdDet.OrderID) AS MaxUnitPrice
FROM Northwind.dbo.Orders AS Ord

子查询也称为内部查询或内部选择，而包含子查询的语句也称为外部查询或外部选择。

许多包含子查询的 Transact-SQL 语句都可以改为用联接表示。而其它一些问题只能由子查询提出。在 Transact-SQL 中，包括子查询的语句和不包括子查询但语义上等效的语句在性能方面通常没有区别。但是，在一些必须检查存在性的情况中，使用联接会产生更好的性能。否则，为确保消除重复值，必须为外部查询的每个结果都处理嵌套查询。所以在这些情况下，联接方式会产生更好的效果。下面的示例显示返回相同结果集的 SELECT 子查询和SELECT 联接：

/* SELECT statement built using a subquery. */
SELECT ProductName
FROM Northwind.dbo.Products
WHERE UnitPrice =
      (SELECT UnitPrice
       FROM Northwind.dbo.Products
       WHERE ProductName = 'Sir Rodney''s Scones')

/* SELECT statement built using a join that returns
   the same result set. */
SELECT Prd1.ProductName
FROM Northwind.dbo.Products AS Prd1
     JOIN Northwind.dbo.Products AS Prd2
       ON (Prd1.UnitPrice = Prd2.UnitPrice)
WHERE Prd2.ProductName = 'Sir Rodney''s Scones'

嵌套在外部 SELECT 语句中的子查询包括以下组件：

  • 包含标准选择列表组件的标准 SELECT 查询。

  • 包含一个或多个表或者视图名的标准 FROM 子句。

  • 可选的 WHERE 子句。

  • 可选的 GROUP BY 子句。

  • 可选的 HAVING 子句。

子查询的 SELECT 查询总是使用圆括号括起来。且不能包括 COMPUTE 或 FOR BROWSE 子句，如果同时指定 TOP 子句，则可能只包括 ORDER BY 子句。

子查询可以嵌套在外部 SELECT、INSERT、UPDATE 或 DELETE 语句的 WHERE 或 HAVING 子句内，或者其它子查询中。尽管根据可用内存和查询中其它表达式的复杂程度不同，嵌套限制也有所不同，但嵌套到 32 层是可能的。个别查询可能会不支持 32 层嵌套。任何可以使用表达式的地方都可以使用子查询，只要它返回的是单个值。

如果某个表只出现在子查询中而不出现在外部查询中，那么该表中的列就无法包含在输出中（外部查询的选择列表）。

包括子查询的语句通常采用以下格式中的一种：

  • WHERE expression [NOT] IN (subquery)

  • WHERE expression comparison_operator [ANY | ALL] (subquery)

  • WHERE [NOT] EXISTS (subquery)

在某些 Transact-SQL 语句中，子查询可以像一个独立的查询一样进行评估。从概念上讲，子查询结果将代入外部查询中（尽管不必知道 Microsoft® SQL Server™ 实际上如何通过子查询处理 Transact-SQL 语句）。

有三种基本的子查询。它们是：

  • 在通过 IN 引入的列表或者由 ANY 或 ALL 修改的比较运算符的列表上进行操作。

  • 通过无修改的比较运算符引入，并且必须返回单个值。

  • 通过 EXISTS 引入的存在测试。

请参见

SELECT


子查询规则

子查询受以下条件的限制：

  • 通过比较运算符引入的子查询的选择列表只能包括一个表达式或列名称（分别对 SELECT * 或列表进行 EXISTS 和 IN 操作除外）。

  • 如果外部查询的 WHERE 子句包括某个列名，则该子句必须与子查询选择列表中的该列在联接上兼容。

  • 子查询的选择列表中不允许出现 ntext、text 和 image 数据类型。

  • 由于必须返回单个值，所以由无修改的比较运算符（指其后未接关键字 ANY 或 ALL）引入的子查询不能包括 GROUP BY 和 HAVING 子句。

  • 包括 GROUP BY 的子查询不能使用 DISTINCT 关键字。

  • 不能指定 COMPUTE 和 INTO 子句。

  • 只有同时指定了 TOP，才可以指定 ORDER BY。

  • 由子查询创建的视图不能更新。

  • 按约定，通过 EXISTS 引入的子查询的选择列表由星号 (*) 组成，而不使用单个列名。由于通过 EXISTS 引入的子查询进行了存在测试，并返回 TRUE 或 FALSE 而非数据，所以这些子查询的规则与标准选择列表的规则完全相同。


在子查询中限定列名

在下面的示例中，外部查询的 WHERE 子句中的 pub_id 列是由外部查询的 FROM 子句中的表名（publishers）隐性限定的。对子查询的选择列表中 pub_id 的引用则通过子查询的 FROM 子句（即通过 titles 表）来限定。

USE pubs
SELECT pub_name
FROM publishers
WHERE pub_id NOT IN
   (SELECT pub_id
   FROM titles
   WHERE type = 'business'

一般的规则是，语句中的列名通过同级 FROM 子句中引用的表来隐性限定。

下例是使用这些隐性假设限定后查询的样式：

USE pubs
SELECT pub_name
FROM publishers
WHERE publishers.pub_id NOT IN
   (SELECT titles.pub_id
   FROM titles
   WHERE type = 'business')

显式表述一个表名绝对不会出错，而且用显式限定替代有关表名的隐性假定总是可能的。

请参见

FROM

WHERE


子查询类型

可以在许多地方指定子查询：

  • 使用别名时

  • 使用 IN 或 NOT IN 时

  • 在 UPDATE、DELETE 和 INSERT 语句中

  • 使用比较运算符时

  • 使用 ANY、SOME 或 ALL 时

  • 使用 EXISTS 或 NOT EXISTS 时

  • 在有表达式的地方


使用别名的子查询

许多其中的子查询和外部查询引用同一表的语句可被表述为自联接（将某个表与自身联接）。例如，通过使用子查询，可以找到与 Livia Karsen 住在同一城市的作者：

USE pubs
SELECT au_lname, au_fname, city
FROM authors
WHERE city IN
   (SELECT city
   FROM authors
   WHERE au_fname = 'Livia'
      AND au_lname = 'Karsen')

下面是结果集：

au_lname                                 au_fname             city
---------------------------------------- -------------------- ----------
Green                                    Marjorie             Oakland
Straight                                 Dean                 Oakland
Stringer                                 Dirk                 Oakland
MacFeather                               Stearns              Oakland
Karsen                                   Livia                Oakland

(5 row(s) affected)

也可以使用自联接：

USE pubs
SELECT au1.au_lname, au1.au_fname, au1.city
FROM authors AS au1 INNER JOIN authors AS au2 ON au1.city = au2.city
   AND au2.au_lname = 'Karsen'
   AND au2.au_fname = 'Livia'

由于自联接的表会以两种不同的角色出现，所以必须有表别名。别名也可用于引用内部和外部查询中同一表的嵌套查询。

USE pubs
SELECT au1.au_lname, au1.au_fname, au1.city
FROM authors AS au1
WHERE au1.city in
   (SELECT au2.city
   FROM authors AS au2
   WHERE au2.au_fname = 'Livia'
      AND au2.au_lname = 'Karsen')

显式别名清楚地表明，对子查询中 authors 的引用并不等同于外部查询中的引用。


使用 IN 的子查询

通过 IN（或 NOT IN）引入的子查询结果是一列零值或更多值。子查询返回结果之后，外部查询将利用这些结果。

下列查询会找到所有曾出版过商业书籍的出版商的名称。

USE pubs
SELECT pub_name
FROM publishers
WHERE pub_id IN
   (SELECT pub_id
   FROM titles
   WHERE type = 'business')

下面是结果集：

pub_name
----------------------------------------
Algodata Infosystems
New Moon Books

(2 row(s) affected)

该语句分两步进行评估。首先，内部查询返回出版过商业书籍的出版商的标识号（1389 和 0736）。然后，这些值被代入外部查询中，在 publishers 中查找与上述标识号相配的名字。

USE pubs
SELECT pub_name
FROM publishers
WHERE pub_id in ('1389', '0736')

使用联接而不使用子查询处理该问题及类似问题的一个不同之处在于，联接使您可以在结果中显示多个表中的列。例如，如果要在结果中包括商业书籍的书名，就必须使用联接来查询。

USE pubs
SELECT pub_name, title
FROM publishers INNER JOIN titles ON publishers.pub_id = titles.pub_id
   AND type = 'business'

下面是结果集：

pub_name               title
---------------------- -------------------------------------------------
Algodata Infosystems   The Busy Executive's Database Guide
Algodata Infosystems   Cooking with Computers: Surreptitious Balance
                     Sheets
New Moon Books         You Can Combat Computer Stress!
Algodata Infosystems   Straight Talk About Computers

(4 row(s) affected)

该查询显示出联接产生四行，而不是像前面的子查询那样产生两行。

下面是查询的另一个示例，它既可用子查询亦可用联接来表达。该查询查找所有住在 California，并且收到的某本书的版税低于 30 % 的第二作者的姓名。

USE pubs
SELECT au_lname, au_fname
FROM authors
WHERE state = 'CA'
   AND au_id IN
      (SELECT au_id
      FROM titleauthor
      WHERE royaltyper < 30
         AND au_ord = 2)

下面是结果集：

au_lname                                 au_fname
---------------------------------------- --------------------
MacFeather                               Stearns

(1 row(s) affected)

评估内部查询后，产生符合子查询限定条件的三个作者的 ID 号。然后评估外部查询。注意，在内部和外部查询的 WHERE 子句中，都可以包括多个条件。

使用联接，同一查询可以用如下方式表示：

USE pubs
SELECT au_lname, au_fname
FROM authors INNER JOIN titleauthor ON authors.au_id = titleauthor.au_id
WHERE state = 'CA'
   AND royaltyper < 30
   AND au_ord = 2

联接总是可以表示为子查询。子查询经常（但不总是）可以表示为联接。这是因为联接是对称的：可以以任意顺序将表 A 联接到表 B，而且会得到相同的答案。而对子查询来说，情况则并非如此。


使用 NOT IN 的子查询

通过 NOT IN 关键字引入的子查询也返回一列零值或更多值。

以下查询查找没有出版过商业书籍的出版商的名称。

USE pubs
SELECT pub_name
FROM publishers
WHERE pub_id NOT IN
   (SELECT pub_id
   FROM titles
   WHERE type = 'business')

除了用 NOT IN 代替 IN，该查询与"使用 IN 的子查询"中的查询完全相同。但是，该语句无法转换为一个联接。这种类似的不等于联接有不同的含义：它会查找曾出版过一些书但不是商业书籍的出版商的名称。有关如何解释不基于等于的联接含义的信息，请参见联接三个或更多的表。


UPDATE、DELETE 和 INSERT 语句中的子查询

子查询可以嵌套在 UPDATE、DELETE 和 INSERT 语句以及 SELECT 语句中。

下面的查询使由 New Moon Books 出版的所有书籍的价格加倍。该查询更新 titles 表；其子查询引用 publishers 表。

UPDATE titles
SET price = price * 2
WHERE pub_id IN
   (SELECT pub_id
   FROM publishers
   WHERE pub_name = 'New Moon Books')

下面是使用联接的等效 UPDATE 语句：

UPDATE titles
SET price = price * 2
FROM titles INNER JOIN publishers ON titles.pub_id = publishers.pub_id
   AND pub_name = 'New Moon Books'

通过下面嵌套的查询，可以删除商业书籍的所有销售记录：

DELETE sales
WHERE title_id IN
   (SELECT title_id
   FROM titles
   WHERE type = 'business')

下面是使用联接的等效 DELETE 语句：

DELETE sales
FROM sales INNER JOIN titles ON sales.title_id = titles.title_id
   AND type = 'business'


使用比较运算符的子查询

子查询可由一个比较运算符（=、< >、>、> =、<、!>, ! < 或 < =）引入。

与使用 IN 引入的子查询一样，由未修改的比较运算符（后面不跟 ANY 或 ALL 的比较运算符）引入的子查询必须返回单个值而不是值列表。如果这样的子查询返回多个值，Microsoft® SQL Server™ 将显示错误信息。

要使用由无修改的比较运算符引入的子查询，必须对数据和问题的本质非常熟悉，以了解该子查询实际是否只返回一个值。

例如，如果假定每个出版商只位于一个城市，而您要找到居住在 Algodata Infosystems 所在城市中的作者的姓名，就可以通过由简单的等号 (=) 比较运算符引入的子查询编写一条语句。

USE pubs
SELECT au_lname, au_fname
FROM authors
WHERE city =
   (SELECT city
   FROM publishers
   WHERE pub_name = 'Algodata Infosystems')

下面是结果集：

au_lname      au_fname
--------      --------
Carson      Cheryl
Bennet      Abraham

(2 row(s) affected)

但是，如果 Algodata Infosystems 位于多个城市中，则会产生一条错误信息。可以使用 IN 表达式（= ANY 也可以）代替 = 比较运算符。

由未修改的比较运算符引入的子查询经常包括聚合函数，因为这些子查询要返回单个值。例如，下面的语句查找所有价格高于当前最低价的书籍的名称。

USE pubs
SELECT DISTINCT title
FROM titles
WHERE price >
   (SELECT MIN(price)
   FROM titles)

下面是结果集：

title
----------------------------------------------------------
But Is It User Friendly?
Computer Phobic and Non-Phobic Individuals: Behavior Variations
Cooking with Computers: Surreptitious Balance Sheets
Emotional Security: A New Algorithm
Fifty Years in Buckingham Palace Kitchens
Is Anger the Enemy?
Life Without Fear
Onions, Leeks, and Garlic: Cooking Secrets of the Mediterranean
Prolonged Data Deprivation: Four Case Studies
Secrets of Silicon Valley
Silicon Valley Gastronomic Treats
Straight Talk About Computers
Sushi, Anyone?
The Busy Executive's Database Guide

(14 row(s) affected)

因为由未修改的比较运算符引入的子查询必须返回单个值，所以除非知道 GROUP BY 或 HAVING 子句本身会返回单个值，否则不能包括 GROUP BY 或 HAVING 子句。例如，下面的查询查找价格高于类型为"trad_cook"的书籍的最低价格的书。

USE pubs
SELECT DISTINCT title
FROM titles
WHERE price >
   (SELECT MIN(price)
   FROM titles
   GROUP BY type
   HAVING type = 'trad_cook')

下面是结果集：

title
------------------------------------------------------------------------
But Is It User Friendly?
Computer Phobic AND Non-Phobic Individuals: Behavior Variations
Onions, Leeks, and Garlic: Cooking Secrets of the Mediterranean
Prolonged Data Deprivation: Four Case Studies
Secrets of Silicon Valley
Silicon Valley Gastronomic Treats
Straight Talk About Computers
Sushi, Anyone?
The Busy Executive's Database Guide

(9 row(s) affected)


用 ANY、SOME 或 ALL 修改的比较运算符

可以用 ALL 或 ANY 关键字修改引入子查询的比较运算符。SOME 是 SQL-92 标准的 ANY 的等效物。

由带修改的比较运算符引入的子查询返回一列零值或更多值，并且可以包括 GROUP BY 或 HAVING 子句。这些子查询可通过 EXISTS 重新表述。

以 > 比较运算符为例，>ALL 表示大于每一个值；换句话说，大于最大值。例如，>ALL (1, 2, 3) 表示大于 3。>ANY 表示至少大于一个值，也就是大于最小值。因此 >ANY (1, 2, 3) 表示大于 1。

要使带有 >ALL 的子查询中的某行满足外部查询中指定的条件，引入子查询的列中的值必须大于由子查询返回的值的列表中的每个值。

同样，>ANY 表示要使某一行满足外部查询中指定的条件，引入子查询的列中的值必须至少大于由子查询返回的值的列表中的一个值。

说明 下例可以用多种方法运行，只要内部查询只返回一个值即可。

USE pubs
-- Option 1 using MAX in the inner query
SELECT title
FROM titles
HAVING MAX(advance) > ALL
WHERE advance > ALL
   (
    SELECT MAX(advance)
    FROM publishers INNER JOIN titles ON
      titles.pub_id = publishers.pub_id
    WHERE pub_name = 'Algodata Infosystems'
   )

-- Option 2 using GROUP BY and HAVING and no ALL
USE pubs
SELECT title
FROM titles
GROUP BY title
HAVING MAX(advance) >
   (
    SELECT MAX(advance)
    FROM publishers INNER JOIN titles ON
      titles.pub_id = publishers.pub_id
    WHERE pub_name = 'Algodata Infosystems'
   )

下面的查询提供一个由 ANY 修改的比较运算符引入的子查询的示例。它将查找收到的预付款大于 Algodata Infosystems 支付的最低预付款金额的书名。

USE pubs
SELECT title
FROM titles
WHERE advance > ANY
   (SELECT advance
   FROM publishers INNER JOIN titles
   ON titles.pub_id = publishers.pub_id
      AND pub_name = 'Algodata Infosystems')

下面是结果集：

title
---------------------------------------------------------------
You Can Combat Computer Stress!
The Gourmet Microwave
But Is It User Friendly?
Secrets of Silicon Valley
Computer Phobic and Non-Phobic Individuals: Behavior Variations
Life Without Fear
Onions, Leeks, and Garlic: Cooking Secrets of the Mediterranean
Sushi, Anyone?

(8 row(s) affected)

对于每种书名，内部查询查找一列由 Algodata 支付的预付款金额。外部查询查看列表中的所有值，并确定当前考虑的书名是否已收到大于任何其它金额的预付款。换句话说，将查找预付款等于或大于 Algodata 支付的最低金额的书名。

如果子查询不返回任何值，那么整个查询将不会返回任何值。

=ANY 运算符与 IN 等效。例如，要查找与出版商住在同一个城市的作者，可以使用 IN 或 =ANY。

USE pubs
SELECT au_lname, au_fname
FROM authors
WHERE city IN
   (SELECT city
   FROM publishers)

USE pubs
SELECT au_lname, au_fname
FROM authors
WHERE city = ANY
   (SELECT city
   FROM publishers)

下面是任一查询的结果集：

au_lname         au_fname
--------         ---------
Carson         Cheryl
Bennet         Abraham

(2 row(s) affected)

但是，< >ANY 运算符与 NOT IN 有所不同：< >ANY 表示不等于 a，或不等于 b，或不等于 c。而 NOT IN 表示不等于 a，且不等于 b，且不等于 c。但 <>ALL 与 NOT IN 意义相同。

例如，下面的查询查找在没有出版商的城市中居住的作者。

USE pubs
SELECT au_lname, au_fname
FROM authors
WHERE city <> ANY
   (SELECT city
   FROM publishers)

下面是结果集：

au_lname                                 au_fname
---------------------------------------- --------------------
White                                    Johnson
Green                                    Marjorie
Carson                                   Cheryl
O'Leary                                  Michael
Straight                                 Dean
Smith                                    Meander
Bennet                                   Abraham
Della Buena                              Ann
Gringlesby                               Burt
Locksley                                 Charlene
Greene                                   Morningstar
Blotchet-Halls                           Reginald
Yokomoto                                 Akiko
del Covello                              Innes
DeFrance                                 Michel
Stringer                                 Dirk
MacFeather                               Stearns
Karsen                                   Livia
Panteley                                 Sylvia
Hunter                                   Sheryl
McBadden                                 Heather
Ringer                                   Anne
Ringer                                   Albert

(23 row(s) affected)

因为每个作者所在的城市中都有一个或多个出版商不在那里居住，所以结果包括所有 23 个作者。内部查询找出所有住有出版商的城市，然后对于每个城市，外部查询查找不住在该城市的作者。

但是，如果在该查询中使用 NOT IN，那么结果将包括除了 Cheryl Carson 和 Abraham Bennet 以外的所有作者，因为他们住在 Algodata Infosystems 所处的 Berkeley。

USE pubs
SELECT au_lname, au_fname
FROM authors
WHERE city NOT IN
   (SELECT city
   FROM publishers)

下面是结果集：

au_lname                                 au_fname
---------------------------------------- --------------------
White                                    Johnson
Green                                    Marjorie
O'Leary                                  Michael
Straight                                 Dean
Smith                                    Meander
Della Buena                              Ann
Gringlesby                               Burt
Locksley                                 Charlene
Greene                                   Morningstar
Blotchet-Halls                           Reginald
Yokomoto                                 Akiko
del Covello                              Innes
DeFrance                                 Michel
Stringer                                 Dirk
MacFeather                               Stearns
Karsen                                   Livia
Panteley                                 Sylvia
Hunter                                   Sheryl
McBadden                                 Heather
Ringer                                   Anne
Ringer                                   Albert

(21 row(s) affected)

还可以通过使用 < >ALL 运算符获得相同的结果，该运算符与 NOT IN 等效。

USE pubs
SELECT au_lname, au_fname
FROM authors
WHERE city <> ALL
   (SELECT city
   FROM publishers)


使用 EXISTS 的子查询

使用 EXISTS 关键字引入一个子查询时，就相当于进行一次存在测试。外部查询的 WHERE 子句测试子查询返回的行是否存在。子查询实际上不产生任何数据；它只返回 TRUE 或 FALSE 值。

使用 EXISTS 引入的子查询语法如下：

WHERE[NOT]EXISTS(subquery)

下面的查询查找所有出版商业书籍的出版商的名称：

USE pubs
SELECT pub_name
FROM publishers
WHERE EXISTS
   (SELECT *
   FROM titles
   WHERE pub_id = publishers.pub_id
      AND type = 'business')

下面是结果集：

pub_name
--------------------
New Moon Books
Algodata Infosystems

(2 row(s) affected)

若要确定该查询的结果，请按照顺序考虑每个出版商的名称。该值是否会使子查询至少返回一行？换句话说，该值是否会使存在测试的计算值为 TRUE？

在此例中，第一个出版商的名称为 Algodata Infosystems，标识号为 1389。titles 表中是否有 pub_id 为 1389 并且 type 为 business 的行？如果有，那么 Algodata Infosystems 应为所选值之一。对其它每个出版商名称重复相同的过程。

注意，使用 EXISTS 引入的子查询在以下几方面与其它子查询略有不同：

  • EXISTS 关键字前面没有列名、常量或其它表达式。

  • 由 EXISTS 引入的子查询的选择列表通常几乎都是由星号 (*) 组成。由于只是测试是否存在符合子查询中指定条件的行，所以不必列出列名。

由于通常没有备选的、非子查询的表示法，所以 EXISTS 关键字很重要。尽管一些使用 EXISTS 表示的查询不能以任何其它方法表示，但所有使用 IN 或由 ANY 或 ALL 修改的比较运算符的查询都可以通过 EXISTS 表示。

以下是使用 EXISTS 和等效的备选方法的查询示例。

有两种方法可以找到与出版商住在同一城市中的作者：

USE pubs
SELECT au_lname, au_fname
FROM authors
WHERE city =ANY
   (SELECT city
   FROM publishers)
-- Or
USE pubs
SELECT au_lname, au_fname
FROM authors
WHERE exists
   (SELECT *
   FROM publishers
   WHERE authors.city = publishers.city)

下面是任一查询的结果集：

au_lname         au_fname
--------         --------
Carson         Cheryl
Bennet         Abraham

(2 row(s) affected)

以下两个查询查找由位于以字母 B 开头的城市中的任一出版商出版的书名：

USE pubs
SELECT title
FROM titles
WHERE pub_id IN
   (SELECT pub_id
   FROM publishers
   WHERE city LIKE 'B%')
— 或
USE pubs
SELECT title
FROM titles
WHERE EXISTS
   (SELECT *
   FROM publishers
   WHERE pub_id = titles.pub_id
      AND city LIKE 'B%')

下面是任一查询的结果集：

title
----------------------------------------------------
The Busy Executive's Database Guide
Cooking with Computers: Surreptitious Balance Sheets
You Can Combat Computer Stress!
Straight Talk About Computers
But Is It User Friendly?
Secrets of Silicon Valley
Net Etiquette
Is Anger the Enemy?
Life Without Fear
Prolonged Data Deprivation: Four Case Studies
Emotional Security: A New Algorithm

(11 row(s) affected)

请参见

EXISTS


使用 NOT EXISTS 的子查询

NOT EXISTS 与 EXISTS 的工作方式类似，只是如果子查询不返回行，那么使用 NOT EXISTS 的 WHERE 子句会得到令人满意的结果。

例如，要查找不出版商业书籍的出版商的名称：

USE pubs
SELECT pub_name
FROM publishers
WHERE NOT EXISTS
   (SELECT *
   FROM titles
   WHERE pub_id = publishers.pub_id
      AND type = 'business')

下面是结果集：

pub_name
----------------------------------------
Binnet & Hardley
Five Lakes Publishing
Ramona Publishers
GGG&G
Scootney Books
Lucerne Publishing

(6 row(s) affected)

下面的查询查找已经不销售的书的名称。

USE pubs
SELECT title
FROM titles
WHERE NOT EXISTS
   (SELECT title_id
   FROM sales
   WHERE title_id = titles.title_id)

下面是结果集：

title
----------------------------------
The Psychology of Computer Cooking
Net Etiquette

(2 row(s) affected)

请参见

EXISTS

NOT


使用 EXISTS 和 NOT EXISTS 查找交集与差集

使用 EXISTS 和 NOT EXISTS 引入的子查询可用于两种集合原理的操作：交集与差集。两个集合的交集包含同时属于两个原集合的所有元素。差集包含只属于两个集合中的第一个集合的元素。

city 列中 authors 和 publishers 的交集是作者和出版商共同居住的城市的集合。

USE pubs
SELECT DISTINCT city
FROM authors
WHERE EXISTS
   (SELECT *
   FROM publishers
   WHERE authors.city = publishers.city)

下面是结果集：

city
--------
Berkeley

(1 row(s) affected)

当然，该查询可以写成一个简单的联接。

USE pubs
SELECT DISTINCT authors.city
FROM authors INNER JOIN publishers
ON authors.city = publishers.city

city 列中 authors 和 publishers 的差集是作者所居住的、但没有出版商居住的所有城市的集合，也就是除 Berkeley 以外的所有城市。

USE pubs
SELECT DISTINCT city
FROM authors
WHERE NOT EXISTS
   (SELECT *
   FROM publishers
   WHERE authors.city = publishers.city)

该查询也可以写成：

USE pubs
SELECT DISTINCT city
FROM authors
WHERE city NOT IN
   (SELECT city
   FROM publishers)


用于替代表达式的子查询

在 Transact-SQL 中，除了在 ORDER BY 列表中以外，在 SELECT、UPDATE、INSERT 和 DELETE 语句中任何可以使用表达式的地方都可以使用子查询来替代。

下面的示例将说明如何使用该增强功能。该查询查找热门计算机书籍的价格、全部书的平均价格，以及每本书的价格与全部书的平均价格之间的差价。

USE pubs
SELECT title, price,
(SELECT AVG(price) FROM titles) AS average,
price-(SELECT AVG(price) FROM titles) AS difference
FROM titles
WHERE type='popular_comp'

下面是结果集：

title                     price          average        difference
------------------------  -------------- -------------- ----------------
But Is It User Friendly?  22.95          14.77          8.18
Secrets of Silicon Valley 20.00          14.77          5.23
Net Etiquette             (null)         14.77          (null)

(3 row(s) affected)


多层嵌套

子查询自身可以包括一个或多个子查询。一个语句中可以嵌套任意数量的子查询。

下面的查询查找至少曾参与编写一本热门计算机书的作者姓名。

USE pubs
SELECT au_lname, au_fname
FROM authors
WHERE au_id IN
   (SELECT au_id
   FROM titleauthor
   WHERE title_id IN
      (SELECT title_id
      FROM titles
      WHERE type = 'popular_comp'))

下面是结果集：

au_lname                                 au_fname
---------------------------------------- --------------------
Carson                                   Cheryl
Dull                                     Ann
Locksley                                 Charlene
Hunter                                   Sheryl

(4 row(s) affected)

最内部的查询返回书名 ID 号 PC1035、PC8888 和 PC9999。再上一层的查询使用这些书名 ID 进行评估，并返回作者的 ID 号。最后，外部查询使用作者的 ID 号查找作者的姓名。

也可以使用一个联接表示该查询：

USE pubs
SELECT au_lname, au_fname
FROM authors INNER JOIN titleauthor ON authors.au_id = titleauthor.au_id
   JOIN titles ON titleauthor.title_id = titles.title_id
WHERE type = 'popular_comp'


相关子查询

许多查询都可以通过执行一次子查询并将结果值代入外部查询的 WHERE 子句进行评估。在包括相关子查询（也称为重复子查询）的查询中，子查询依靠外部查询获得值。这意味着子查询是重复执行的，为外部查询可能选择的每一行均执行一次。

下面的查询查找获得某本书 100 % 共享版税 (royaltyper) 的所有作者名。

USE pubs
SELECT au_lname, au_fname
FROM authors
WHERE 100 IN
   (SELECT royaltyper
   FROM titleauthor
   WHERE titleauthor.au_ID = authors.au_id)

下面是结果集：

au_lname                                 au_fname
---------------------------------------- --------------------
White                                    Johnson
Green                                    Marjorie
Carson                                   Cheryl
Straight                                 Dean
Locksley                                 Charlene
Blotchet-Halls                           Reginald
del Castillo                             Innes
Panteley                                 Sylvia
Ringer                                   Albert

(9 row(s) affected)

与以前提到的大多数子查询不同，该语句中的子查询无法独立于主查询而得到解决。该子查询需要一个 authors.au_id 值，而该值是个变量，随 Microsoft® SQL Server™ 检查 authors 表中的不同行而更改。

下面准确说明如何评估该查询：SQL Server 考虑 authors 表中的每一行是否都包括在结果中，方法是将每一行的值都代入内部查询中。例如，如果 SQL Server 首先检查 Cheryl Carson 行，那么变量 authors.au_id 将取值 238-95-7766，SQL Server 将该值代入到内部查询中。

USE pubs
SELECT royaltyper
FROM titleauthor
WHERE au_id = '238-95-7766'

结果为 100，所以外部查询评估为：

USE pubs
SELECT au_lname, au_fname
FROM authors
WHERE 100 IN (100)

由于这是真的，因此 Cheryl Carson 行包括在结果中。对 Abraham Bennet 行运行相同的过程，会发现该行没有包括在结果中。


使用别名的相关子查询

相关子查询可以用于从外部查询引用的表中选择数据之类的操作中。在这种情况下，必须使用表的别名（也称为相关名）明确指定要使用哪个表引用。例如，可以使用相关子查询查找已由多个出版商出版的书的类型。需要用别名来区分在其中出现 titles 表的两个不同角色。

USE pubs
SELECT DISTINCT t1.type
FROM titles t1
WHERE t1.type IN
   (SELECT t2.type
   FROM titles t2
   WHERE t1.pub_id <> t2.pub_id)

下面是结果集：

type
----------
business
psychology

(2 row(s) affected)

上面的嵌套查询等同于下面的自联接：

USE pubs
SELECT DISTINCT t1.type
FROM titles t1 INNER JOIN titles t2 ON t1.type = t2.type
   AND t1.pub_id <> t2.pub_id


使用比较运算符的相关子查询

使用带有比较运算符的相关子查询可以查找销售数量低于该书平均销售数量的销售。

USE pubs
SELECT s1.ord_num, s1.title_id, s1.qty
FROM sales s1
WHERE s1.qty <
   (SELECT AVG(s2.qty)
   FROM sales s2
   WHERE s2.title_id = s1.title_id)

下面是结果集：

ord_num              title_id qty
-------------------- -------- ------
6871                 BU1032   5
722a                 PS2091   3
D4482                PS2091   10
N914008              PS2091   20
423LL922             MC3021   15

(5 row(s) affected)

外部查询依次选择 sales 的行（即 s1）。子查询为外部查询中的选择计算正在考虑的每个销售的平均数量。对于 s1 的每个可能值，如果数量小于计算的平均值，Microsoft® SQL Server™ 将评估子查询并将所考虑的记录放入结果中。

有时，相关子查询与 GROUP BY 子句极相似。下面的示例查找价格高于其同类书的平均值的所有书名。

USE pubs
SELECT t1.type, t1.title
FROM titles t1
WHERE t1.price >
   (SELECT AVG(t2.price)
   FROM titles t2
   WHERE t1.type = t2.type)

下面是结果集：

type         title
------------ -----------------------------------------------------------
business     The Busy Executive's Database Guide
business     Straight Talk About Computers
mod_cook     Silicon Valley Gastronomic Treats
popular_comp But Is It User Friendly?
psychology   Computer Phobic AND Non-Phobic Individuals: Behavior
            Variations
psychology   Prolonged Data Deprivation: Four Case Studies
trad_cook    Onions, Leeks, and Garlic: Cooking Secrets of the
            Mediterranean

(7 row(s) affected)

对于 t1 每个可能的值，如果该行的 price 值大于计算的平均值，Microsoft_ SQL Server_ 将评估子查询并将该行放入结果中。由于计算平均价格的行受子查询中的 WHERE 子句限制，所以不必显式地根据类型分组。


HAVING 子句中的相关子查询

相关子查询还可以用于外部查询的 HAVING 子句。该结构可用于查找最高预付款超过给定组中平均预付款两倍的书籍类型。

在下例中，为外部查询中定义的每一个组各评估一次子查询（每次一种书籍类型）。

USE pubs
SELECT t1.type
FROM titles t1
GROUP BY t1.type
HAVING MAX(t1.advance) >=ALL
   (SELECT 2 * AVG(t2.advance)
   FROM titles t2
   WHERE t1.type = t2.type)

下面是结果集：

type
--------
mod_cook

(1 row(s) affected)


使用 CASE 处理条件数据

CASE 函数用于计算多个条件并为每个条件返回单个值。CASE 函数通常的用途是使用可读性更强的值替换代码或缩写。下面的查询使用 CASE 函数重命名书籍的分类，以使之更易理解。

USE pubs
SELECT
   CASE type
      WHEN 'popular_comp' THEN 'Popular Computing'
      WHEN 'mod_cook' THEN 'Modern Cooking'
      WHEN 'business' THEN 'Business'
      WHEN 'psychology' THEN 'Psychology'
      WHEN 'trad_cook' THEN 'Traditional Cooking'
      ELSE 'Not yet categorized'
   END AS Category,
CONVERT(varchar(30), title) AS "Shortened Title",
price AS Price
FROM titles
WHERE price IS NOT NULL
ORDER BY 1

下面是结果集：

category            shortened title                Price
------------------- ------------------------------ -------
Business            Cooking with Computers: Surrep 11.95
Business            Straight Talk About Computers  19.99
Business            The Busy Executive's Database  19.99
Business            You Can Combat Computer Stress 2.99
Modern Cooking      Silicon Valley Gastronomic Tre 19.99
Modern Cooking      The Gourmet Microwave          2.99
Popular Computing   But Is It User Friendly?       22.95
Popular Computing   Secrets of Silicon Valley      20.00
Psychology          Computer Phobic AND Non-Phobic 21.59
Psychology          Emotional Security: A New Algo 7.99
Psychology          Is Anger the Enemy?            10.95
Psychology          Life Without Fear              7.00
Psychology          Prolonged Data Deprivation: Fo 19.99
Traditional Cooking Fifty Years in Buckingham Pala 11.95
Traditional Cooking Onions, Leeks, and Garlic: Coo 20.95
Traditional Cooking Sushi, Anyone?                 14.99

(16 row(s) affected)

CASE 函数的另一个用途给数据分类。下面的查询使用 CASE 函数对价格分类。

SELECT
   CASE
      WHEN price IS NULL THEN 'Not yet priced'
      WHEN price < 10 THEN 'Very Reasonable Title'
      WHEN price >= 10 and price < 20 THEN 'Coffee Table Title'
      ELSE 'Expensive book!'
   END AS "Price Category",
CONVERT(varchar(20), title) AS "Shortened Title"
FROM pubs.dbo.titles
ORDER BY price

下面是结果集：

Price Category        Shortened Title
--------------------- --------------------
Not yet priced        The Psychology of Co
Not yet priced        Net Etiquette
Very Reasonable Title You Can Combat Compu
Very Reasonable Title The Gourmet Microwav
Very Reasonable Title Life Without Fear
Very Reasonable Title Emotional Security:
Coffee Table Title    Is Anger the Enemy?
Coffee Table Title    Cooking with Compute
Coffee Table Title    Fifty Years in Bucki
Coffee Table Title    Sushi, Anyone?
Coffee Table Title    The Busy Executive's
Coffee Table Title    Straight Talk About
Coffee Table Title    Silicon Valley Gastr
Coffee Table Title    Prolonged Data Depri
Expensive book!       Secrets of Silicon V
Expensive book!       Onions, Leeks, and G
Expensive book!       Computer Phobic AND
Expensive book!       But Is It User Frien

(18 row(s) affected)

请参见

CASE


汇总数据

为决策支持系统生成聚合事务的汇总报表会是一项复杂并且相当消耗资源的工作。Microsoft® SQL Server™ 2000 提供两个灵活且强大的组件，用于生成 SQL Server 2000 Analysis Services。这些组件是程序员在执行 SQL Server 数据的多维分析时应当使用的主要工具。

  • 数据转换服务 (DTS)

    DTS 支持提取事务数据并将这些数据转换到数据仓库或数据集市中的汇总聚合中。有关更多信息，请参见 DTS 概述。

  • Microsoft SQL Server Analysis Services

    Analysis Services 将数据仓库中的数据组织到含有预先计算好的汇总信息的多维数据集中，以对复杂的分析查询提供快速响应。PivotTable® 服务向客户端提供对多维数据的访问。Analysis Services 还提供一套向导，用于定义分析处理过程中所用的多维结构，并提供用于管理分析结构的 Microsoft 管理控制台管理单元。这样，应用程序就可使用用于分析 API 的 OLE DB 或 Microsoft ActiveX® 数据对象（多维）(ADO MD) API 分析来"分析"数据。有关更多信息，请参见 Analysis Services 概述。

对简单汇总报表使用 Transact-SQL

生成简单汇总报表的应用程序可使用下列 Transact-SQL 元素：

  • CUBE 或 ROLLUP 运算符，这两者均是 SELECT 语句的 GROUP BY 子句的一部分。

  • COMPUTE 或 COMPUTE BY 运算符，这两者也与 GROUP BY 相关联。

这些运算符生成的结果集中，既包含每个项目的明细行，也包含每个组的汇总行，汇总行显示了该组的聚合合计。GROUP BY 子句可用于生成只包含各组聚合而不包含明细行的结果。

建议应用程序使用 Analysis Services，而不要使用 CUBE、ROLLUP、COMPUTE 或 COMPUTE BY。CUBE 和 ROLLUP 应当只用在无法访问 OLE DB 或 ADO 的环境中，例如脚本或存储过程中。

支持 COMPUTE 和 COMPUTE BY 是为了向后兼容。应优先选用 ROLLUP 而非 COMPUTE 或 COMPUTE BY。COMPUTE 或 COMPUTE BY 生成的汇总值作为分离的结果集返回，这些结果集之间还插入了各组明细行结果集；或者作为包含合计的结果集返回，追加在主结果集之后。处理这些多重结果集增加了应用程序代码的复杂性。COMPUTE 和 COMPUTE BY 都不支持服务器游标，而 ROLLUP 则支持。CUBE 和 ROLLUP 将生成单个结果集，其中包含嵌入的小计和合计行。并且，有时查询优化器为 ROLLUP 生成的执行计划比为 COMPUTE 和 COMPUTE BY 生成的更为高效。

当使用不带这些运算符的 GROUP BY 时，将返回单个结果集，其中每组一行，行中包含该组的聚合小计。结果集中没有明细行。


用 CUBE 汇总数据

CUBE 运算符生成的结果集是多维数据集。多维数据集是事实数据的扩展，事实数据即记录个别事件的数据。扩展建立在用户打算分析的列上。这些列被称为维。多维数据集是一个结果集，其中包含了各维度的所有可能组合的交叉表格。

CUBE 运算符在 SELECT 语句的 GROUP BY 子句中指定。该语句的选择列表应包含维度列和聚合函数表达式。GROUP BY 应指定维度列和关键字 WITH CUBE。结果集将包含维度列中各值的所有可能组合，以及与这些维度值组合相匹配的基础行中的聚合值。

例如，一个简单的表 Inventory 中包含：

Item                 Color                Quantity
-------------------- -------------------- --------------------------
Table                Blue                 124
Table                Red                  223
Chair                Blue                 101
Chair                Red                  210

下列查询返回的结果集中，将包含 Item 和 Color 的所有可能组合的 Quantity 小计：

SELECT Item, Color, SUM(Quantity) AS QtySum
FROM Inventory
GROUP BY Item, Color WITH CUBE

下面是结果集：

Item                 Color                QtySum
-------------------- -------------------- --------------------------
Chair                Blue                 101.00
Chair                Red                  210.00
Chair                (null)               311.00
Table                Blue                 124.00
Table                Red                  223.00
Table                (null)               347.00
(null)               (null)               658.00
(null)               Blue                 225.00
(null)               Red                  433.00

我们着重考查下列各行：

Chair                (null)               311.00

这一行报告了 Item 维度中值为 Chair 的所有行的小计。对 Color 维度返回了 NULL 值，表示该行所报告的聚合包括 Color 维度为任意值的行。

Table                (null)               347.00

这一行类似，但报告的是 Item 维度中值为 Table 的所有行的小计。

(null)               (null)               658.00

这一行报告了多维数据集的总计。Item 和 Color 维度的值都是 NULL，表示两个维度中的所有值都汇总在该行中。

(null)               Blue                 225.00
(null)               Red                  433.00

这两行报告了 Color 维度的小计。两行中的 Item 维度值都是 NULL，表示聚合数据来自 Item 维度为任意值的行。

使用 GROUPING 区分空值

CUBE 操作所生成的空值带来一个问题：如何区分 CUBE 操作所生成的 NULL 值和从实际数据中返回的 NULL 值？这个问题可用 GROUPING 函数解决。如果列中的值来自事实数据，则 GROUPING 函数返回 0；如果列中的值是 CUBE 操作所生成的 NULL，则返回 1。在 CUBE 操作中，所生成的 NULL 代表全体值。可将 SELECT 语句写成使用 GROUPING 函数将所生成的 NULL 替换为字符串 ALL。因为事实数据中的 NULL 表明数据值未知，所以 SELECT 语句还可译码为返回字符串 UNKNOWN 替代来自事实数据的 NULL。例如：

SELECT CASE WHEN (GROUPING(Item) = 1) THEN 'ALL'
            ELSE ISNULL(Item, 'UNKNOWN')
       END AS Item,
       CASE WHEN (GROUPING(Color) = 1) THEN 'ALL'
            ELSE ISNULL(Color, 'UNKNOWN')
       END AS Color,
       SUM(Quantity) AS QtySum
FROM Inventory
GROUP BY Item, Color WITH CUBE
多维数据集

CUBE 运算符可用于生成 n 维的多维数据集，即具有任意数目维度的多维数据集。只有一个维度的多维数据集可用于生成合计，例如：

SELECT CASE WHEN (GROUPING(Item) = 1) THEN 'ALL'
            ELSE ISNULL(Item, 'UNKNOWN')
       END AS Item,
       SUM(Quantity) AS QtySum
FROM Inventory
GROUP BY Item WITH CUBE
GO

此 SELECT 语句返回的结果集既显示了 Item 中每个值的小计，也显示了 Item 中所有值的总计：

Item                 QtySum
-------------------- --------------------------
Chair                311.00
Table                347.00
ALL                  658.00

包含带有许多维度的 CUBE 的 SELECT 语句可能生成很大的结果集，因为这些语句会为所有维度中值的所有组合生成行。这些大结果集包含的数据可能过多而不易于阅读和理解。这个问题有一种解决办法是将 SELECT 语句放在视图中：

CREATE VIEW InvCube AS
SELECT CASE WHEN (GROUPING(Item) = 1) THEN 'ALL'
            ELSE ISNULL(Item, 'UNKNOWN')
       END AS Item,
       CASE WHEN (GROUPING(Color) = 1) THEN 'ALL'
            ELSE ISNULL(Color, 'UNKNOWN')
       END AS Color,
       SUM(Quantity) AS QtySum
FROM Inventory
GROUP BY Item, Color WITH CUBE

然后即可用该视图来只查询您感兴趣的维度值：

SELECT *
FROM InvCube
WHERE Item = 'Chair'
  AND Color = 'ALL'

Item                 Color                QtySum
-------------------- -------------------- --------------------------
Chair                ALL                  311.00

(1 row(s) affected)

请参见

SELECT

用 ROLLUP 汇总数据


用 ROLLUP 汇总数据

在生成包含小计和合计的报表时，ROLLUP 运算符很有用。ROLLUP 运算符生成的结果集类似于 CUBE 运算符所生成的结果集。有关更多信息，请参见用 CUBE 汇总数据。

CUBE 和 ROLLUP 之间的区别在于：

  • CUBE 生成的结果集显示了所选列中值的所有组合的聚合。

  • ROLLUP 生成的结果集显示了所选列中值的某一层次结构的聚合。

例如，简单表 Inventory 中包含：

Item                 Color                Quantity
-------------------- -------------------- --------------------------
Table                Blue                 124
Table                Red                  223
Chair                Blue                 101
Chair                Red                  210

下列查询将生成小计报表：

SELECT CASE WHEN (GROUPING(Item) = 1) THEN 'ALL'
            ELSE ISNULL(Item, 'UNKNOWN')
       END AS Item,
       CASE WHEN (GROUPING(Color) = 1) THEN 'ALL'
            ELSE ISNULL(Color, 'UNKNOWN')
       END AS Color,
       SUM(Quantity) AS QtySum
FROM Inventory
GROUP BY Item, Color WITH ROLLUP

Item                 Color                QtySum
-------------------- -------------------- --------------------------
Chair                Blue                 101.00
Chair                Red                  210.00
Chair                ALL                  311.00
Table                Blue                 124.00
Table                Red                  223.00
Table                ALL                  347.00
ALL                  ALL                  658.00

(7 row(s) affected)

如果查询中的 ROLLUP 关键字更改为 CUBE，那么 CUBE 结果集与上述结果相同，只是在结果集的末尾还会返回下列两行：

ALL                  Blue                 225.00
ALL                  Red                  433.00

CUBE 操作为 Item 和 Color 中值的可能组合生成行。例如，CUBE 不仅报告与 Item 值 Chair 相组合的 Color 值的所有可能组合（Red、Blue 和 Red + Blue），而且报告与 Color 值 Red 相组合的 Item 值的所有可能组合（Chair、Table 和 Chair + Table）。

对于 GROUP BY 子句中右边的列中的每个值，ROLLUP 操作并不报告左边一列（或左边各列）中值的所有可能组合。例如，ROLLUP 并不对每个 Color 值报告 Item 值的所有可能组合。

ROLLUP 操作的结果集具有类似于 COMPUTE BY 所返回结果集的功能；然而，ROLLUP 具有下列优点：

  • ROLLUP 返回单个结果集；COMPUTE BY 返回多个结果集，而多个结果集会增加应用程序代码的复杂性。

  • ROLLUP 可以在服务器游标中使用；COMPUTE BY 不可以。

  • 有时，查询优化器为 ROLLUP 生成的执行计划比为 COMPUTE BY 生成的更为高效。

请参见

SELECT

用 COMPUTE 和 COMPUTE BY 汇总数据


用 COMPUTE 和 COMPUTE BY 汇总数据

提供 COMPUTE 和 COMPUTE BY 是为了向后兼容。请改为使用下列组件：

  • Microsoft® SQL Server™ 2000 Analysis Services 和用于 Analysis Services 的 OLE DB 或 Microsoft ActiveX® 数据对象（多维）(ADO MD) 一起使用。有关更多信息，请参见 Microsoft SQL Server™ 2000 Analysis Services。

  • ROLLUP 运算符。有关更多信息，请参见用 ROLLUP 汇总数据。

COMPUTE BY 子句使您得以用同一 SELECT 语句既查看明细行，又查看汇总行。可以计算子组的汇总值，也可以计算整个结果集的汇总值。

COMPUTE 子句需要下列信息：

  • 可选的 BY 关键字，该关键字可按对一列计算指定的行聚合。

  • 行聚合函数名称；例如，SUM、AVG、MIN、MAX 或 COUNT。

  • 要对其执行行聚合函数的列。
COMPUTE 生成的结果集

COMPUTE 所生成的汇总值在查询结果中显示为分离的结果集。包括 COMPUTE 子句的查询的结果类似于控制中断报表，即汇总值由指定的组（或称中断）控制的报表。可以为各组生成汇总值，也可以对同一组计算多个聚合函数。

当 COMPUTE 带有可选的 BY 子句时，符合 SELECT 条件的每个组都有两个结果集：

  • 每个组的第一个结果集是明细行集，其中包含该组的选择列表信息。

  • 每个组的第二个结果集有一行，其中包含该组的 COMPUTE 子句中所指定的聚合函数的小计。

当 COMPUTE 不带可选的 BY 子句时，SELECT 语句有两个结果集：

  • 每个组的第一个结果集是包含选择列表信息的所有明细行。

  • 第二个结果集有一行，其中包含 COMPUTE 子句中所指定的聚合函数的合计。
COMPUTE 用法示例

下列 SELECT 语句使用简单 COMPUTE 子句生成 titles 表中 price 及 advance 的求和总计：

USE pubs
SELECT type, price, advance
FROM titles
ORDER BY type
COMPUTE SUM(price), SUM(advance)

下列查询在 COMPUTE 子句中加入可选的 BY 关键字，以生成每个组的小计：

USE pubs
SELECT type, price, advance
FROM titles
ORDER BY type
COMPUTE SUM(price), SUM(advance) BY type

此 SELECT 语句的结果用 12 个结果集返回，六个组中的每个组都有两个结果集。每个组的第一个结果集是一个行集，其中包含选择列表中所请求的信息。每个组的第二个结果集包含 COMPUTE 子句中两个 SUM 函数的小计。

说明 一些实用工具（如 osql）显示多个小计或合计聚合汇总的方式可能会使用户以为每个小计都是结果集中的单独一行。这是由于该实用工具设置输出格式的方式；小计或合计聚合返回时单独占用一行。其它应用程序（如 SQL 查询分析器）将多个聚合设置在同一行。

比较 COMPUTE 和 GROUP BY

COMPUTE 和 GROUP BY 之间的区别汇总如下：

  • GROUP BY 生成单个结果集。每个组都有一个只包含分组依据列和显示该组子聚合的聚合函数的行。选择列表只能包含分组依据列和聚合函数。

  • COMPUTE 生成多个结果集。一类结果集包含每个组的明细行，其中包含选择列表中的表达式。另一类结果集包含组的子聚合，或 SELECT 语句的总聚合。选择列表可包含除分组依据列或聚合函数之外的其它表达式。聚合函数在 COMPUTE 子句中指定，而不是在选择列表中。

下列查询使用 GROUP BY 和聚合函数；该查询将返回一个结果集，其中每个组有一行，该行中包含该组的聚合小计：

USE pubs
SELECT type, SUM(price), SUM(advance)
FROM titles
GROUP BY type

说明 在 COMPUTE 或 COMPUTE BY 子句中，不能包含 ntext、text 或 image 数据类型。

请参见

查询基础知识

SELECT


错误处理

在 Microsoft® SQL Server™ 2000 中出现的错误具有以下几个特性：

  • 错误号。

    每个错误状态都对应唯一的错误号。

  • 错误信息字符串。

    错误信息提供了有关错误原因的诊断信息。许多错误信息都有替换变量，其中包含了一些信息，如产生错误的对象名称。每个错误号都对应唯一的错误信息。

  • 严重度。

    严重度表示错误的严重程度。严重度较低的错误，如 1 级或 2 级，为信息性消息或低级警告。严重度较高的错误表明问题需要尽快解决。

  • 状态代码。

    一些错误代码可能在 SQL Server 源代码中多处出现。例如，几种不同情况下都可能发生 1105 错误。出现错误代码的每一处都指派了唯一的状态代码。Microsoft 支持工程师可以使用错误的状态代码在源代码中查找错误代码的位置，这可以为问题的诊断提供附加意见。

  • 过程名。

    如果在存储过程中发生错误，则该存储过程的名称也许可用。

  • 行号。

    行号指出存储过程中产生错误的语句。

SQL Server 的所有错误都存储在系统表 master.dbo.sysmessages 中。用户定义的消息也可以存储在 sysmessages 中。如果需要，可以使用 RAISERROR 语句将这些用户定义的错误返回到一个应用程序。

所有的数据库 API，如 ADO、OLE DB、ODBC、DB-Library 和嵌入式 SQL，均报告基本的错误属性：错误号和消息字符串。然而，每个数据库所能报告的其它错误特性的数量是不同的。

其它 SQL Server 组件也可以发生错误：

  • OLE DB 提供程序、ODBC 驱动程序和 DB-Library的动态链接库自身也会发生错误。这些错误的格式与 API 规范中定义的格式是一致的。

  • Net-Libraries 自身会发生错误。

  • 开放式数据服务 (Open Data Services) 在其自身格式中会发生错误。

  • SQL Server 向导、应用程序和实用工具（例如索引优化向导、SQL Server 企业管理器和 osql 实用程序）自身也会发生错误。

  • 嵌入式 SQL 可能产生 SQL-92 错误。

请参见

sysmessages


处理应用程序中的错误和消息

由 Microsoft® SQL Server™ 2000 或 RAISERROR 语句产生的错误不是结果集的一部分。错误通过独立于结果集处理的错误处理机制返回给应用程序。

每个数据库 API 都包括一些函数、接口、方法、对象或结构的集合，通过这些集合返回错误和消息。每个 API 函数或方法通常返回表示操作成功的状态代码。如果此状态不是成功状态，则应用程序可以调用错误函数、方法或对象来检索错误信息。

实际上 SQL Server 有两种返回错误信息的机制：

  • 错误
    • 严重度为 11 或高于 11 的 sysmessages 中的错误。

    • 严重度为 11 或高于 11 的任意 RAISERROR 语句。
  • 邮件
    • PRINT 语句的输出。

    • 几个 DBCC 语句的输出。

    • 严重度为 10 或低于 10 的 sysmessages 中的错误。

    • 严重度为 10 或低于 10 的任意 RAISERROR 语句。

使用 API 的应用程序如 ADO 和 OLE DB 通常不能区分错误和消息。在 ODBC 应用程序中，消息产生 SQL_SUCCESS_WITH_INFO 函数返回代码，而错误通常产生 SQL_ERROR 返回代码。两者的差别在 DB-Library 中最为明显，其中错误返回给应用程序的错误处理程序函数，而消息则返回给应用程序的消息处理程序函数。

ODBC 的错误处理

ODBC 规范引入的错误模型一直是一般数据库 API 的错误模型的基础，如 ADO、OLE DB 和通过 ODBC 建立的 API （RDO、DAO 和 MFC 数据库类）。在 ODBC 模型中，错误具有以下属性：

  • SQLSTATE

    SQLSTATE 是一个 5 个字符的错误代码，它最初是在 ODBC 规范中定义的。SQLSTATE 对所有 ODBC 驱动程序是通用的，并为应用程序提供了对不同数据库返回的所有不同的错误代码进行无需测试的基本错误处理编码的方法。ODBC SQLSTATE 与 SQL Server 错误信息的状态属性无关。

    ODBC 2.x 返回一个 SQLSTATE 代码的集合，ODBC 3.x 返回与 X/Open 数据管理并列的一组 SQLSTATE 代码：结构化查询语言 (SQL)，标准版本 2。因为所有 ODBC 驱动程序都返回相同的 SQLSTATE 代码集合，所以将错误处理基于 SQLSTATE 代码的应用程序更易于移植。

  • 本机错误号

    本机错误号是来自基础数据库的错误号。ODBC 应用程序将收到的 SQL Server 错误号作为本机错误号。

  • 错误信息字符串

    错误信息通过错误信息字符串参数返回。

当 ODBC 函数返回的状态不是 SQL_SUCCESS 时，应用程序可以调用 SQLGetDiagRec 以获得错误信息。例如，如果 ODBC 应用程序收到一个语法错误（SQL Server 错误 170），则 SQLGetDiagRec 返回：

szSqlState = 42000, pfNative = 170
szErrorMsg =
'[Microsoft][ODBC SQL Server Driver][SQL Server]
                                     Line 1: Incorrect syntax near *'

ODBC SQLGetDiagField 函数允许 ODBC 驱动程序在此驱动程序返回的诊断记录中指定驱动程序特定的诊断字段。SQL Server ODBC 驱动程序指定驱动程序特定的字段以放置 SQL Server 错误信息，如 SQL Server 严重度和状态代码。

有关在 ODBC 应用程序中检索错误信息的更多信息，请参见处理错误和消息。

ADO 的错误处理

ADO 使用 Errors 对象和 Errors 集合返回标准错误信息，如 SQLSTATE、本机错误号和错误信息字符串。这些信息与 ODBC 中的相同。ADO 不支持任何提供程序特定的错误接口，因此 SQL Server 特定的错误信息，如严重度或状态，也可用于 ADO 应用程序。

有关在 ADO 应用程序中检索错误信息的更多信息，请参见处理错误和消息。

OLE DB 的错误处理

OLE DB 使用 IErrorInfo 接口返回标准错误信息，如 SQLSTATE、本机错误号和错误字符串。这些信息与 ODBC 中的相同。SQL Server 的 Microsoft OLE DB 提供程序定义了一个 ISQLServerErrorInfo 接口，用来返回 SQL Server 特定信息，如严重度、状态、过程名称和行号。

有关在 OLE DB 应用程序中检索错误信息的更多信息，请参见错误。

DB-Library 的错误处理

DB-Library 使用一种不同的机制将错误信息返回给应用程序。应用程序定义了两种回调函数——错误处理程序和消息处理程序。当 DB-Library 的动态链接库有错误或消息要返回时，它会调用应用程序的错误处理程序或消息处理程序函数。由于 DB-Library 为 SQL Server 所特有，所有的 SQL Server 错误信息在错误和消息处理程序中都是可用的。DB-Library 把 PRINT 消息和低严重度错误或 RAISERROR 消息返回给消息处理程序，而把高严重度错误和 RAISERROR 消息返回给错误处理程序函数。

有关 DB-Library 错误处理的更多信息，请参见错误和消息处理。


使用 @@ERROR

如果最后的 Transact-SQL 语句执行成功，则 @@ERROR 系统函数返回 0；如果此语句产生错误，则 @@ERROR 返回错误号。每一个 Transact-SQL 语句完成时，@@ERROR 的值都会改变。

因为每个 Transact-SQL 语句执行完毕时，@@ERROR 都会得到一个新的值，@@ERROR 可用以下两种方法处理：

  • 在 Transact-SQL 语句后，马上检测或使用 @@ERROR。

  • 在 Transact-SQL 语句完成后，马上把 @@ERROR 存储到一个整型变量中。此变量的值可供以后使用。

在生成错误的批处理、存储过程或触发器中，@@ERROR 是 Microsoft® SQL Server™ 2000 错误的唯一可用部分。错误的所有其它部分，如严重度、状态、包含替换字符串（如对象名称）的消息文本，都只返回到那些能够用 API 错误处理机制进行错误处理的应用程序中。同时，@@ERROR 只由错误产生，而不由警告产生；因此，批处理、存储过程和触发器对于那些可能已经产生了的警告没有可见性。

@@ERROR 通常用于表示存储过程的成功或失败。整型变量初始化为 0。完成每个 Transact-SQL 语句后，都要测试 @@ERROR 是否为 0。如果 @@ERROR 不是 0，将被存储在变量中。存储过程然后在 RETURN 语句中返回变量。如果过程中的 Transact-SQL 语句都没有错误，变量保持为 0。如果一个或多个语句生成错误，则变量包含最后的错误号。下面是有此逻辑的简单存储过程：

USE Northwind
GO

DROP PROCEDURE SampleProcedure
GO
-- Create a procedure that takes one input parameter
-- and returns one output parameter and a return code.
CREATE PROCEDURE SampleProcedure @EmployeeIDParm INT,
            @MaxQuantity INT OUTPUT
AS
-- Declare and initialize a variable to hold @@ERROR.
DECLARE @ErrorSave INT
SET @ErrorSave = 0

-- Do a SELECT using the input parameter.
SELECT FirstName, LastName, Title
FROM Employees
WHERE EmployeeID = @EmployeeIDParm

-- Save any non-zero @@ERROR value.
IF (@@ERROR <> 0)
    SET @ErrorSave = @@ERROR

-- Set a value in the output parameter.
SELECT @MaxQuantity = MAX(Quantity)
FROM [Order Details]

-- Save any non-zero @@ERROR value.
IF (@@ERROR <> 0)
    SET @ErrorSave = @@ERROR

-- Returns 0 if neither SELECT statement had
-- an error, otherwise returns the last error.
RETURN @ErrorSave
GO

也有 @@ERROR 与 @@ROWCOUNT 一起使用的时候。在以下的示例中，@@ERROR 用于确定是否出现违反约束的错误，而 @@ROWCOUNT 则在一些行被成功更改的情况下，用于确定由 UPDATE 语句修改的行数。

BEGIN TRAN
  UPDATE Northwind.dbo.Products
  SET UnitPrice = UnitPrice * 1.1
  WHERE CategoryID IN (1, 2, 5, 6)

IF @@ERROR = 547
  PRINT 'A CHECK CONSTRAINT violation occurred'
IF @@ROWCOUNT = 0
  PRINT 'No rows updated.'
ELSE
  PRINT STR(@@ROWCOUNT) + ' rows updated.'
COMMIT -- Commits rows successfully updated.

请参见

@@ERROR

@@ROWCOUNT


使用 PRINT

PRINT 语句用一个字符或 Unicode 字符串表达式作为参数。它把这个字符串作为一个消息返回给应用程序。该消息作为 ADO，OLE DB 和 ODBC 应用程序中的消息性错误被返回。SQLSTATE 设为 01000，本机错误设为 0，而错误信息字符串被设为在 PRINT 语句中指定的字符串。字符串则返回给 DB-Library 应用程序的消息处理程序回调函数中。

PRINT 语句接受任何字符串表达式，包括字符或 Unicode常量、字符或 Unicode 局部变量名，或返回字符或 Unicode 字符串的一个函数。在 Microsoft® SQL Server&trade 2000 中，PRINT 还接受由两个或更多的常量、局部变量或函数串联起来所生成的复杂字符串。

PRINT 可帮助我们在 Transact-SQL 代码中发现并解决问题、检查数据值或生成报告。

下面的示例把 PRINT 用在一个 IF 语句中，以将消息返回给应用程序：

IF (SELECT COUNT(au_lname) FROM authors WHERE state = 'UT') > 0
  PRINT 'More than one author resides in the state of Utah.'

这个示例使用串联打印出局部变量、系统函数和文本字符串的组合。

USE Northwind
GO
DECLARE @MyObject NVARCHAR(128)

SET @MyObject = 'Products'

PRINT 'Object Name: ' + @MyObject
PRINT '   Object ID: ' + STR(Object_ID(@MyObject))
PRINT 'The computer ' + RTRIM(@@SERVERNAME) + ' is running '
  + RTRIM(@@VERSION)
GO
-- This shows building a character variable into a print
-- message. This is required for earlier versions of SQL
-- Server, in which the PRINT statement did not support
-- concatenation.
DECLARE @Msg VARCHAR(255)
SELECT @Msg = 'The computer ' + RTRIM(@@SERVERNAME) + ' is running '
  + RTRIM(@@VERSION)
PRINT @Msg


使用 RAISERROR

与 PRINT 相比，RAISERROR 在把消息返回给应用程序方面的功能更强大。RAISERROR 能以下列方式中的任意一种返回消息：

  • 已通过 sp_addmessage 系统存储过程添加到 master.dbo.sysmessages 上的由用户定义的错误信息。

  • 在 RAISERROR 语句中指定的消息字符串。

RAISERROR 也有 PRINT 功能的一些扩展：

  • RAISERROR 能够指派一个特定的错误号、严重度和状态。

  • RAISERROR 能够请求将错误记入 Microsoft® SQL Server™ 2000 错误日志和 Microsoft Windows NT® 应用程序日志中。

  • 消息字符串可以包含替代变量和参量，这与 C 语言中的 printf 功能相似。

当 RAISERROR 在sysmessages 中与用户定义消息的 msg_id 一同使用时，msg_id 被作为 SQL Server 的错误号码或本机错误代码返回。而当 RAISERROR 与 msg_str 而不是 msg_id 一同使用时，返回的 SQL Server 的错误号和本机错误号为 50000。

当使用 RAISERROR 返回一个用户定义的错误信息时，在每个引用该错误的 RAISERROR 中使用不同的状态号码。这可以在发生错误时帮助进行错误诊断。

RAISERROR 可以帮助我们发现并解决 Transact-SQL 代码中的问题、检查数据值或返回包含变量文本的消息。

下面的示例在返回给应用程序的消息中替换了 DB_ID 和 DB_NAME 函数的值：

DECLARE @DBID INT
SET @DBID = DB_ID()

DECLARE @DBNAME NVARCHAR(128)
SET @DBNAME = DB_NAME()

RAISERROR
   ('The current database ID is:%d, the database name is: %s.',
    16, 1, @DBID, @DBNAME)

而以下示例使用由用户定义的消息完成了同样的处理：

sp_addmessage 50005, 16,
    'The current database ID is:%d, the database name is: %s.'
GO
DECLARE @DBID INT
SET @DBID = DB_ID()

DECLARE @DBNAME NVARCHAR(128)
SET @DBNAME = DB_NAME()

RAISERROR (50005, 16, 1, @DBID, @DBNAME)
GO

第二个 RAISERROR 示例显示，替换参数可以在由用户定义的错误中指定，并且当 RAISERROR 语句执行时由替换参变量填充。


查询 SQL Server 系统目录

对没有进行硬编码以与特定的表和视图集一起工作的动态应用程序来说，它必须有一种机制用于确定任何与之相连的数据库中对象的结构和属性。应用程序可能需要如下信息：

  • 数据库中表和视图的数目和名称。

  • 表或视图中的列数以及每一列的名称、数据类型、小数位数和精度。

  • 表中定义的约束。

  • 为表定义的索引和键。

Microsoft® SQL Server™ 2000 系统目录提供关于 SQL Server 数据库的这方面信息。SQL Server 系统目录的核心是一个包含描述 SQL Server 数据库中对象的元数据的系统表集。元数据是描述系统中对象特性的数据。

SQL Server 应用程序可以通过几种方法访问系统目录中的信息：

  • 信息架构视图

    信息架构视图的基础是 SQL-92 标准中的目录视图定义。它们以一种独立于所有目录表实现的格式来表示目录信息，因此不受基础目录表变化的影响。使用这些视图的应用程序可异类 SQL-92 兼容的数据库系统之间移植。有关更多信息，请参见信息架构视图。

  • OLE DB 架构行集

    OLE DB 规范定义的 IDBSchemaRowset 接口显示了一套包含目录信息的架构行集。OLE DB 架构行集是显示不同的 OLE DB 提供程序所支持的目录信息的标准方法。行集独立于基础目录表的结构。有关更多信息，请参见 SQLOLEDB 中的架构行集支持。

    SQL Server 的 OLE DB 提供程序支持 IDBSchemaRowset 扩展，该 IDBSchemaRowset 为用于分布式查询的链接服务器报告目录信息。有关更多信息，请参见 LINKEDSERVERS 行集。

  • ODBC 目录函数

    ODBC 规范定义了一套目录函数，该目录函数能够返回包含目录信息的结果集。这些函数是显示不同的 ODBC 驱动程序所支持的目录信息的标准方法。结果集独立于基础目录表结构。

    SQL Server ODBC 驱动程序支持两种驱动程序特有的功能，这些功能为用于分布式查询的链接服务器报告目录信息。有关更多信息，请参见使用目录函数。

  • 系统存储过程和函数

    Transact-SQL 定义了返回目录信息的服务器系统存储过程和系统函数。虽然这些存储过程和函数为 SQL Server 所特有，但它们使用户与基础系统目录表的结构隔离开了。有关更多信息，请参见元数据函数和系统存储过程。

不推荐用户直接查询系统目录表。此方法仅在上面介绍的方法未提供所需信息时才应使用。系统目录表的结构取决于 SQL Server 的基础结构，并且不同版本之间有变化。即使一个只发出 SELECT 语句的应用程序在移植到一个新版本的 SQL Server 中时也可能至少需要重写部分程序，如果它需要直接查询在新版本中已经更改或不复存在的系统表。

在系统表中更新、删除或插入数据可能在 SQL Server 系统中导致不可预见的后果。此种更新不为 Microsoft 所支持。


管理 ntext、text 和 image 数据

Microsoft® SQL Server™ 的 ntext、text 和 image 数据类型在单个值中可以包含非常大的数据量（最大可达 2 GB）。单个数据值通常比应用程序在一个步骤中能够检索的大；某些值可能还会大于客户端的可用虚拟内存。因此，在检索这些值时，通常需要一些特殊的步骤。

如果 ntext、text 和 image 数据值不超过 Unicode 串、字符串或二进制串的长度（分别为 4,000 个字符、8,000 个字符和 8,000 个字节），就可以在 SELECT、UPDATE 和 INSERT 语句中引用它们，其引用方式与较小的数据类型相同。例如，包含短值的 ntext 列可以在 SELECT 语句的选择列表中引用，这与 nvarchar 列的引用方式相同。引用时必须遵守一些限制，例如不能在 WHERE 子句中直接引用 ntext、text 或 image 列。这些列可以作为返回其它数据类型（例如 ISNULL、SUBSTRING 或 PATINDEX）的某个函数的参数包含在 WHERE 子句中，也可以包含在 IS NULL、IS NOT NULL 或 LIKE 表达式中。

处理较大的数据值

但是，如果 ntext、text 和 image 数据值较大，则必须逐块处理。Transact-SQL 和数据库 API 均包含使应用程序可以逐块处理 ntext、text 和 image 数据的函数。

数据库 API 按照一种通用的模式处理长 ntext、text 和 image 列：

  • 若要读取一个长列，应用程序只需在选择列表中包含 ntext、text 或 image 列，并将该列绑定到一个程序变量，该变量应足以容纳适当的数据块。然后，应用程序就可以执行该语句，并使用 API 函数或方法将数据逐块检索到绑定的变量中。

  • 若要写入一个长列，应用程序可使用参数标记 (?) 在相应位置代替 ntext、text 或 image 列中的值，以执行 INSERT 或 UPDATE 语句。参数标记（对 ADO 而言则为参数）被绑定到一个足以容纳数据块的程序变量上。应用程序进入循环，在循环中先将下一组数据移到绑定的变量中，然后调用 API 函数或方法写入数据块。这一过程将反复进行，直到整个数据值发送完毕。
使用 text in row

在 Microsoft SQL Server 2000 中，用户可以在表上启用 text in row 选项，以使该表能够在其数据行中存储 text、ntext 或 image 数据。

若要启用该选项，请执行 sp_tableoption 存储过程，将 text in row 指定为选项名并将 on 指定为选项值。BLOB（二进制大对象：text、ntext 或 image 数据）行中可以存储的默认最大大小为 256 字节，但是值的范围可以从 24 到 7000。若要指定默认值以外的最大大小，请指定该范围内的整数作为选项值。

如果应用下列条件，则将 text、ntext 或 image 字符串存储在数据行中：

  • 启用 text in row。

  • 字符串的长度比 @OptionValue 所指定的限制短

  • 数据行中有足够的可用空间。

当 BLOB 字符串存储在数据行中时，读取和写入 text、ntext 或 image 字符串可以与读取或写入字符串和二进制字符串一样快。SQL Server 不必访问单独的页以读取或写入 BLOB 字符串。

如果 text、ntext 或 image 字符串比行中所指定的限制或可用空间大，则将指针存储在该行中。在行中存储 BLOB 字符串的条件仍然适用，但是：数据行中必须有足够的空间容纳指针。

有关更多信息，请参见 sp_tableoption。

使用文本指针

如果未指定 text in row 选项，text、ntext 或 image 字符串将存储在数据行外；只有这些字符串的文本指针驻留在数据行中。文本指针指向由内部指针生成的树的根节点，而这些内部指针映射到实际存储（text、ntext 或 image 数据的）字符串段的页。

SQL Server 2000 中的行文本指针与 SQL Server 早期版本中的文本指针不同。行文本指针的行为就象 BLOB 数据的文件句柄；早期的文本指针功能则象 BLOB 数据的地址。因此，在使用行文本指针时，请记住下列特性：

重要 虽然游标中允许有行文本，但却不允许有行文本指针。如果尝试声明包含行文本指针的游标，SQL Server 将返回错误信息（8654、16、1、"A cursor plan could not be generated for the given statement because it contains textptr(inrow lob)."、1033）。

  1. 数字

    对于每个数据库，每个事务最多允许 1024 个活动行文本指针。

  2. 锁定

    当用户获取活动文本指针时，SQL Server 2000 在第一个用户控制文本指针时锁定数据行，并确保没有其他用户修改或删除该行。锁在文本指针变为无效时被释放。若要使文本指针无效，请使用 sp_invalidate_textptr。

    当事务的隔离级别是未提交读或者数据库为"只读"模式时，文本指针不能用于更新 BLOB 值。

    当数据库为"单用户"模式时，SQL Server 2000 不锁定数据行。

    为举例说明，给出下面的表：

    CREATE TABLE t1 (c1 int, c2 text)
EXEC sp_tableoption 't1', 'text in row', 'on'
INSERT t1 VALUES ('1', 'a')

    下面的事务将会成功：

    INSERT t1 VALUES ('1','This is text.')
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
GO
BEGIN TRAN
DECLARE @ptr varbinary(16)
SELECT @ptr = textptr(c2)
FROM t1
WHERE c1 = 1
READTEXT t1.c2 @ptr 0 5
COMMIT TRAN
GO

    下面的事务将会失败：

    SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
GO
BEGIN TRAN
DECLARE @ptr varbinary(16)
SELECT @ptr = textptr(c2)
FROM t1
WHERE c1 = 1
WRITETEXT t1.c2 @ptr 'xx'
COMMIT TRAN
GO
  3. 持续时间

    行文本指针仅在事务内有效。提交事务时，文本指针变为无效。

    在某个事务内，当发生下列任一操作时，行文本指针可能无效：

    • 会话结束。

    • 删除该事务中的数据行。（其它事务无法删除数据行，因为该行包含锁。）

    • 文本指针所在的表的架构已更改。使文本指针无效的架构更改操作包括：创建或除去聚集索引，改变或除去表，截断表，通过 sp_tableoption 更改 text in row 选项，以及执行 sp_indexoption。

    使用前面的示例，下列脚本在 SQL Server 早期版本中有效，但在 SQL Server 2000 中将生成错误。

    DECLARE @ptrval varbinary(16)
PRINT 'get error here'
SELECT @ptrval = TEXTPTR(c2)
FROM t1
WHERE c1 = 1
READTEXT t1.c2 @ptrval 0 1

    在 SQL Server 2000 中，必须在事务内使用行文本指针：

    BEGIN TRAN
DECLARE @ptrval varbinary(16)
SELECT @ptrval = TEXTPTR(c2)
FROM t1
WHERE c1 = 1
READTEXT t1.c2 @ptrval 0 1
COMMIT
  4. NULL 文本

    可以在由 INSERT 生成的 NULL 文本上获得行文本指针。而在以前，只有将 BLOB 更新为 NULL 后才能获得文本指针。

    例如，下列代码在 SQL Server 7.0 中无效，但在 SQL Server 2000 中有效。

    SET TRANSACTION ISOLATION LEVEL READ COMMITTED
GO
INSERT INTO t1 VALUES (4, NULL)
BEGIN TRAN
DECLARE @ptrval VARBINARY(16)
SELECT @ptrval = TEXTPTR(c2)
FROM t1
WHERE c1 = 4
WRITETEXT t1.c2 @ptrval 'x4'
COMMIT

    在 SQL Server 7.0 中，必须执行下列操作：

    INSERT INTO t1 VALUES (4, NULL)
UPDATE t1
    SET c2 = NULL
    WHERE c1 = 4
DECLARE @ptrval VARBINARY(16)
SELECT @ptrval = TEXTPTR(c2)
FROM t1
WHERE c1 = 4
WRITETEXT t1.c2 @ptrval 'x4'

下表汇总差别。

差别 | 行文本指针 | 非行文本指针
数字 | 对于每个数据库，每个事务最多允许 1024 个活动行文本指针。 | 无限制。
锁定 | 将数据行一直 S 锁定到指针变为无效为止。 当事务为"未提交读"或数据库为"单用户"或"只读"模式时不获取锁。 | 不锁定数据行。
持续时间 | 事务或会话结束、删除行或更改表的架构时变为无效。 | 删除行时变为无效。
NULL 文本 | 插入 NULL 文本后可立即获取。 | 只有更新后才能获取。

通过数据库 API 使用 ntext、text 和 image 数据

这一部分概述数据库 API 处理 ntext、text 和 image 数据的方式：

  • ADO

    ADO 可以将 ntext、text 或 image 列或参数映射为 Field 或 Parameter 对象。使用 GetChunk 方法逐块检索数据，使用 AppendChunk 方法逐块写数据。有关更多信息，请参见管理 Long 数据类型。

  • OLE DB

    OLE DB 使用 ISequentialStream 接口支持 ntext、text 和 image 数据类型。ISequentialStream::Read 方法逐块读取长数据，ISequentialStream::Write 方法将长数据逐块写入数据库。有关更多信息，请参见 BLOB 和 OLE 对象。

  • ODBC

    ODBC 具有一种称为"执行中的数据"的功能，可用于处理长数据的 ODBC 数据类型：SQL_WLONGVARCHAR (ntext)、SQL_LONGVARCHAR (text) 和 SQL_LONGVARBINARY (image)。这些数据类型被绑定到某个程序变量上。这样一来，就可以调用 SQLGetData 逐块检索长数据，调用 SQLPutData 逐块发送长数据。有关更多信息，请参见管理 text 和 image 列。

  • DB-Library

    DB-Library 应用程序也是将 ntext、text 和 image 列绑定到程序变量上。DB-Library 函数 dbtxtptr 用于获取指向数据库中长列出现位置的指针，dbreadtext 则用来逐块读取长数据。dbwritetext、dbupdatetext 和 dbmoretext 之类的函数用于逐块写入长数据。

    说明 不支持使用 DB-Library 访问行文本。

有关更多信息，请参见 Text 和 Image 函数。


检索 ntext、text 或 image 值

可通过以下方式检索 ntext、text 或 image 值：

  • 在 SELECT 语句中引用该列。

    例如，以下查询将返回每个出版商在 pr_info 列中的所有信息：

    USE pubs
SELECT pr_info
FROM pub_info

    这是在使用 API（例如 ADO、OLE DB、ODBC 或 DB-Library）的数据库应用程序中所使用的方法。该列被绑定到一个程序变量上，然后使用特殊的 API 函数或方法逐块检索数据。

    如果在 Transact-SQL 脚本、存储过程和触发器中使用这种方法，则只能用于相对较短的值。如果数据的长度大于 SET TEXTSIZE 中指定的长度，则必须增大 TEXTSIZE 或使用其它方法。当前的 TEXTSIZE 设置通过 @@TEXTSIZE 函数报告，并使用 SET TEXTSIZE 语句进行更改：

    SET TEXTSIZE 64512

    TEXTSIZE 的默认设置为 4096 (4 KB)。以下语句将 TEXTSIZE 重置为默认值：

    SET TEXTSIZE 0

    如果数据长度小于 TEXTSIZE，则将返回所有数据。

    DB-Library API 还支持 dbtextsize 参数，该参数可控制可以选择的 ntext、text 和 image 数据的长度。用于 SQL Server 的 Microsoft OLE DB 提供程序和 SQL Server ODBC 驱动程序自动将 @@TEXTSIZE 设置为最大值 2 GB。

  • 使用 TEXTPTR 函数可获得传递给 READTEXT 语句的文本指针。

    READTEXT 语句用于读取 ntext、text 或 image 数据块。例如，以下查询将返回每个出版商的示例文本数据的前 25 个字符（或第一行）：

    USE pubs
DECLARE @textpointer varbinary(16)
SELECT @textpointer = TEXTPTR(pr_info)
FROM pub_info
READTEXT pub_info.pr_info @textpointer 1 25
  • 使用 SUBSTRING 函数可检索从列开头特定偏移位置开始的数据块。

    例如，以下查询将返回每个出版商的示例文本数据的前 25 个字符（或第一行）：

    USE pubs
SELECT SUBSTRING(pr_info, 1, 25) AS pr_info
FROM pub_info
  • 使用 PATINDEX 函数可检索一些特定字节组合的偏移量。

    然后可以在 SUBSTRING 函数或 READTEXT 语句中使用该值检索数据。例如，以下查询将在 pub_info 表的 pr_info 列中搜索 Germany 字符串，并返回起始位置 103（字符串 Germany 的 G 在 pr_info 列的第 103 个字符处开始）：

    USE pubs
SELECT PATINDEX('%Germany%', pr_info) AS pr_info
FROM pub_info

    PATINDEX 只在文本和字符数据类型上操作；它不接受image 值。

检索 ntext、text 或 image 的各部分值

这些方法不局限于检索从第一个字节开始的整个 ntext、text 或 image 值。可以将这些方法组合起来进行更灵活的处理，检索 ntext、text 或 image 值的不同部分。例如，以下 SELECT 语句将检索 text 值的起始标记和结束标记之间的部分：

USE Northwind
GO
CREATE TABLE TextParts (ColA INT PRIMARY KEY, ColB TEXT)
GO
INSERT INTO TextParts
   VALUES( 1,
           'Sample string START TAG What I want END TAG Trailing text.')
GO
SELECT SUBSTRING(   ColB,
                    /* Calculate start as start of tag + tag length. */
                    (PATINDEX('%START TAG%', ColB) + 10),
                   /* Calculate SUBSTRING length as end - start. */
                    (
                      PATINDEX('%END TAG%', ColB) -
                      ( PATINDEX('%START TAG%', ColB) + 10 )
                    )
                )
FROM TextParts
GO

下面是结果集：

------------------------
What I want

(1 row(s) affected)

说明 当选择 image 数据时，返回的值包含字符 0x，表示该数据是十六进制。这两个字符算作是 TEXTSIZE 的一部分。

请参见

READTEXT

SET TEXTSIZE

SELECT

Text 和 Image 函数

BLOB 和 OLE 对象

管理 Long 数据类型

管理 text 和 image 列


修改 ntext、text 或 image 值

可通过下列方式修改 ntext、text 或 image 值：

  • 使用数据库 API（例如 ADO、OLE DB 或 ODBC）执行 UPDATE 或 INSERT 语句，并将程序变量与 ntext、text 或 image 列的参数标记绑定。然后调用相应的数据库 API 函数，逐块向数据库发送长数据。DB-Library 支持与其 text 和 image 函数相同的功能。

  • 使用 WRITETEXT 语句重写该列的整个数据值。

    例如，以下查询将更改 New Moon Books 中 pr_info 列的内容：

    USE pubs
sp_dboption 'pubs', 'select into/bulkcopy', 'true'
DECLARE @ptrval varbinary(16)
SELECT @ptrval = TEXTPTR(pr_info)
FROM pub_info pr INNER JOIN publishers p
    ON p.pub_id = pr.pub_id
    AND p.pub_name = 'New Moon Books'
WRITETEXT pub_info.pr_info @ptrval 'New Moon Books (NMB) '
sp_dboption 'pubs', 'select into/bulkcopy', 'true'
  • 使用 UPDATETEXT 语句更新 ntext、text 或 image 列的特定数据块。

    例如，以下查询用字母 z 取代 New Moon Books 中 text 列的第 88 个字符（Moon 中的第二个字母 o）：

    USE pubs
sp_dboption 'pubs', 'select into/bulkcopy', 'true'
DECLARE @ptrval varbinary(16)
SELECT @ptrval = TEXTPTR(pr_info)
    FROM pub_info pr INNER JOIN publishers p
        ON p.pub_id = pr.pub_id
        AND p.pub_name = 'New Moon Books'
UPDATETEXT pub_info.pr_info @ptrval 88 1 'z'
sp_dboption 'pubs', 'select into/bulkcopy', 'false'

请参见

WRITETEXT

UPDATETEXT

管理 Long 数据类型

BLOB 和 OLE 对象

管理 text 和 image 列

Text 和 Image 函数


Transact-SQL 中的 OLE 自动化对象

Transact-SQL 包括几个系统存储过程，这些过程允许在 Transact-SQL 批处理、存储过程和触发器中引用 OLE 自动化对象。这些系统存储过程作为扩展存储过程运行，而且通过存储过程执行的 OLE 自动化对象在 Microsoft® SQL Server™ 2000 地址空间中的运行方式与扩展存储过程相同。

OLE 自动化存储过程使 Transact-SQL 批处理得以引用 SQL DMO 对象和自定义 OLE 自动化对象，例如表现 IDispatch 接口的对象。使用 Microsoft Visual Basic® 创建的自定义进程内 OLE 服务器必须有一个错误处理程序（使用 On Error GoTo 语句指定）用于 Class_Initialize 和 Class_Terminate 子例程。该错误处理程序可以防止这些子例程中出现未处理的错误。Class_Initialize 和 Class_Terminate 子例程中未处理的错误可能导致不可预知的错误，如违反 SQL Server 访问限制。建议其它子例程也使用错误处理程序。

在 Transact-SQL 中使用 OLE 自动化对象的第一步是调用 sp_OACreate 系统存储过程，以便在 SQL Server 地址空间内创建一个对象实例。

创建对象实例后，可调用下列存储过程以使用与该对象相关的属性、方法和错误信息：

  • sp_OAGetProperty 包含属性值。

  • sp_OASetProperty 设置属性值。

  • sp_OAMethod 调用方法。

  • sp_OAGetErrorInfo 获得最新的错误信息。

当不再需要该对象时，可调用 sp_OADestroy，释放用 sp_OACreate 创建的对象实例。

OLE 自动化对象通过属性值和方法返回数据。而 sp_OAGetProperty 和 sp_OAMethod 以结果集的形式返回这些数据值。

OLE 自动化对象的作用域是一个批处理。对该对象的所有引用都必须包含在单个批处理、存储过程或触发器中。

在引用对象时，SQL Server OLE 自动化对象支持从该对象到它所包含的其它对象的遍历。例如，在使用 SQL-DMO SQLServer 对象时，可以引用该服务器上包含的数据库和表。有关更多信息，请参见对象层次结构语法。

请参见

sp_OACreate

sp_OAGetProperty

sp_OASetProperty

sp_OAMethod

sp_OAGetErrorInfo

sp_OADestroy


OLE 自动化返回代码和错误信息

OLE 自动化系统存储过程返回一个 int 返回代码，该代码是基础 OLE 自动化操作返回的 HRESULT。HRESULT 为 0 表示成功。非零的 HRESULT 是 OLE 错误代码，其形式为十六进制 0x800nnnnn，但是当作为存储过程返回代码中的一个 int 值返回时，它的形式为 -214nnnnnnn。

例如，给 sp_OACreate 传递一个无效的对象名 (SQLDMO.Xyzzy) 会导致该过程返回一个值为 -2147221005 的 int HRESULT，该值用十六进制表示则为 0x800401f3。

可以使用 CONVERT(binary(4), @hresult) 将一个 int HRESULT 转换为二进制值。但是，使用 CONVERT(char(10), CONVERT(binary(4), @hresult)) 将产生一个不可读的字符串，因为 HRESULT 的每个字节都转换成了单个 ASCII 字符。可以使用下面示例的 sp_hexadecimal 存储过程，将 int HRESULT 转换成包含可读十六进制字符串的 char 值。

CREATE PROCEDURE sp_hexadecimal
    @binvalue varbinary(255),
    @hexvalue varchar(255) OUTPUT
AS
DECLARE @charvalue varchar(255)
DECLARE @i int
DECLARE @length int
DECLARE @hexstring char(16)
SELECT @charvalue = '0x'
SELECT @i = 1
SELECT @length = DATALENGTH(@binvalue)
SELECT @hexstring = '0123456789abcdef'
WHILE (@i <= @length)
BEGIN
DECLARE @tempint int
DECLARE @firstint int
DECLARE @secondint int
SELECT @tempint = CONVERT(int, SUBSTRING(@binvalue,@i,1))
SELECT @firstint = FLOOR(@tempint/16)
SELECT @secondint = @tempint - (@firstint*16)
SELECT @charvalue = @charvalue +
SUBSTRING(@hexstring, @firstint+1, 1) +
SUBSTRING(@hexstring, @secondint+1, 1)
SELECT @i = @i + 1
END
SELECT @hexvalue = @charvalue

当其中一个 OLE 自动化过程返回非零的 HRESULT 返回代码时，可以使用下例的 sp_displayoaerrorinfo 存储过程显示 OLE 自动化错误信息。该存储过程示例使用了 sp_hexadecimal。

CREATE PROCEDURE sp_displayoaerrorinfo
    @object int,
    @hresult int
AS
DECLARE @output varchar(255)
DECLARE @hrhex char(10)
DECLARE @hr int
DECLARE @source varchar(255)
DECLARE @description varchar(255)
PRINT 'OLE Automation Error Information'
EXEC sp_hexadecimal @hresult, @hrhex OUT
SELECT @output = '  HRESULT: ' + @hrhex
PRINT @output
EXEC @hr = sp_OAGetErrorInfo @object, @source OUT, @description OUT
IF @hr = 0
BEGIN
SELECT @output = '  Source: ' + @source
PRINT @output
SELECT @output = '  Description: ' + @description
PRINT @output
END
ELSE
BEGIN
    PRINT ' sp_OAGetErrorInfo failed.'
    RETURN
END

请参见

sp_OAGetErrorInfo


OLE 自动化结果集

如果某个 OLE 自动化属性或方法返回的数据是一维或二维数组，那么该数组将作为结果集返回给客户端：

  • 一维数组作为单行结果集返回给客户端，其中的列数与数组中的元素数相等。例如，一个 array(10) 作为 10 列单行返回。

  • 二维数组作为结果集返回给客户端，其中的列数与数组第一维中的元素数相同，行数与数组第二维中的元素数相同。例如，array(2,3) 作为 2 列 3 行返回。

当属性返回值或方法返回值是数组时，sp_OAGetProperty 或 sp_OAMethod 将向客户端返回结果集。（方法输出参数不能是数组。）这些过程扫描数组中的所有数据值，以便为该结果集的每一列确定正确的 Microsoft® SQL Server™ 数据类型和数据长度。对于某个特定的列，这些过程将使用表示该列中的所有数据值所需要的数据类型和长度。

当一列中的所有数据值都具有相同的数据类型时，此数据类型将用于整个列。当一列中的数据值使用不同的数据类型时，将根据下表选择整个列的数据类型。

 | int | float | money | datetime | varchar | nvarchar
int | int | float | money | varchar | varchar | nvarchar
float | float | float | money | varchar | varchar | nvarchar
money | money | money | money | varchar | varchar | nvarchar
datetime | varchar | varchar | varchar | datetime | varchar | nvarchar
varchar | varchar | varchar | varchar | varchar | varchar | nvarchar
nvarchar | nvarchar | nvarchar | nvarchar | nvarchar | nvarchar | nvarchar

有关 OLE 自动化存储过程如何在 Microsoft Visual Basic® 和 SQL Server 数据类型之间转换数据的更多信息，请参见使用 OLE 自动化存储过程进行数据类型转换。


诊断 Transact-SQL 中的 OLE 自动化对象

当开发调用自定义 OLE 自动化对象的 Transact-SQL 批处理时，在处理从该对象返回的数据的过程中可能会出现错误。如果该对象的源代码可用，则可以通过 Microsoft® Visual Basic® 诊断这些问题。若要执行该操作，必须在运行 Microsoft SQL Server™ 2000 实例的计算机上安装 Visual Basic，而且 SQL Server 和 Visual Basic 必须正在同一个 Microsoft Windows® 用户帐户下运行。SQL Server 必须在 Windows 服务控制管理器以外从命令提示符启动（使用 sqlservr /c 命令），或者 SQL Server 服务必须在登录到系统时使用的同一 Windows 用户帐户下启动。

当在同一个 Windows 用户帐户下运行 Visual Basic 和 SQL Server 时：

  1. 将自定义 OLE 自动化服务器工程装入 Visual Basic。

  2. 在需要的源代码行上设置断点。

  3. 在 Visual Basic 的"运行"菜单上，单击"全编译执行"命令。

    此步骤将注册并运行自定义 OLE 自动化服务器。

  4. 使用 OLE 自动化存储过程调用由自定义 OLE 自动化服务器显示出的 OLE 对象。

    遇到断点时，将激活 Visual Basic 调试程序。

有关更多信息，请参见 Microsoft Visual Basic 文档。


OLE 自动化脚本示例

这是一个 Transact-SQL 语句批处理示例，它使用 OLE 自动化存储过程创建和使用 SQL-DMO SQLServer 对象。该代码的一些部分被用作存储过程引用示例。

DECLARE @object int
DECLARE @hr int
DECLARE @property varchar(255)
DECLARE @return varchar(255)
DECLARE @src varchar(255), @desc varchar(255)

-- Create an object.
EXEC @hr = sp_OACreate 'SQLDMO.SQLServer', @object OUT
IF @hr <> 0
BEGIN
   EXEC sp_OAGetErrorInfo @object, @src OUT, @desc OUT
   SELECT hr=convert(varbinary(4),@hr), Source=@src, Description=@desc
    RETURN
END

-- Set a property.
EXEC @hr = sp_OASetProperty @object, 'HostName', 'Gizmo'
IF @hr <> 0
BEGIN
   EXEC sp_OAGetErrorInfo @object, @src OUT, @desc OUT
   SELECT hr=convert(varbinary(4),@hr), Source=@src, Description=@desc
    RETURN
END

-- Get a property using an output parameter.
EXEC @hr = sp_OAGetProperty @object, 'HostName', @property OUT
IF @hr <> 0
BEGIN
   EXEC sp_OAGetErrorInfo @object, @src OUT, @desc OUT
   SELECT hr=convert(varbinary(4),@hr), Source=@src, Description=@desc
    RETURN
END
PRINT @property

-- Get a property using a result set.
EXEC @hr = sp_OAGetProperty @object, 'HostName'
IF @hr <> 0
BEGIN
   EXEC sp_OAGetErrorInfo @object, @src OUT, @desc OUT
   SELECT hr=convert(varbinary(4),@hr), Source=@src, Description=@desc
    RETURN
END

-- Get a property by calling the method.
EXEC @hr = sp_OAMethod @object, 'HostName', @property OUT
IF @hr <> 0
BEGIN
   EXEC sp_OAGetErrorInfo @object, @src OUT, @desc OUT
   SELECT hr=convert(varbinary(4),@hr), Source=@src, Description=@desc
    RETURN
END
PRINT @property

-- Call a method.
EXEC @hr = sp_OAMethod @object, 'Connect', NULL, 'my_server', 'my_login', 'my_password'
IF @hr <> 0
BEGIN
   EXEC sp_OAGetErrorInfo @object, @src OUT, @desc OUT
   SELECT hr=convert(varbinary(4),@hr), Source=@src, Description=@desc
    RETURN
END

-- Call a method that returns a value.
EXEC @hr = sp_OAMethod @object, 'VerifyConnection', @return OUT
IF @hr <> 0
BEGIN
   EXEC sp_OAGetErrorInfo @object, @src OUT, @desc OUT
   SELECT hr=convert(varbinary(4),@hr), Source=@src, Description=@desc
    RETURN
END
PRINT @return

-- Destroy the object.
EXEC @hr = sp_OADestroy @object
IF @hr <> 0
BEGIN
   EXEC sp_OAGetErrorInfo @object, @src OUT, @desc OUT
   SELECT hr=convert(varbinary(4),@hr), Source=@src, Description=@desc
    RETURN
END

请参见

sp_OACreate

sp_OAGetProperty

sp_OASetProperty

sp_OAMethod

sp_OADestroy


Transact-SQL 窍门

数据库程序员有时遇到的一些棘手的查询问题可能已由其他程序员解决了。本节列出了一些常见的棘手问题和提高查询性能的准则。

  • 交叉数据报表创建汇总信息。大多数交叉数据报表只需使用 CASE 函数或 SELECT 的 CUBE 或 ROLLUP 选项就可以生成。

  • 展开层次结构显示得到特定结果的层次结构步骤（例如，追踪一个从曾曾祖父到您本人的家谱树）。

  • 展开网络显示多个层次结构步骤（例如，所有从西雅图飞抵纽约的航班）。

  • 编写国际化 Transact-SQL 语句列出编写适合全球使用的应用程序的指导方针。

  • 编写可读性强的代码列出使代码便于使用、灵活和容易理解的良好编程方法。
用于提高性能的 Transact-SQL 编程

在编写 Transact-SQL 语句、批处理、存储过程和触发器时，使用 Microsoft® SQL Server™ 2000 中的编程特性可创建高效代码。

重新使用执行计划

如果遵从以下准则编写 Transact-SQL 语句的执行计划，SQL Server 2000 将有更好的机会重新使用这些计划。

  • 使用对象（如表和视图）的完全合法名称。

  • 不要直接指定存储过程参数值或搜索条件谓词中的值，而应使用参数化查询并提供参数值。在 sp_executesql 中使用参数替代或者将参数与 ADO、OLE DB、ODBC 和 DB-Library API 绑定在一起使用。

    例如，不要编写以下 SELECT 语句：

    SELECT * FROM Northwind.dbo.Shippers WHERE ShipperID = 3

    而是使用 ODBC 作为示例，利用 SQLBindParameter ODBC 函数将参数标记 (?) 绑定到程序变量并编写如下的 SELECT 语句代码：

    SELECT * FROM Northwind.dbo.Shippers WHERE ShipperID = ?

    在 Transact-SQL 脚本、存储过程或触发器中，可使用 sp_executesql 执行 SELECT 语句：

    DECLARE @IntVariable INT
DECLARE @SQLString NVARCHAR(500)
DECLARE @ParmDefinition NVARCHAR(500)

/* Build the SQL string. */
SET @SQLString =
     N'SELECT * FROM Northwind.dbo.Shippers WHERE ShipperID = @ShipID'
/* Specify the parameter format once. */
SET @ParmDefinition = N'@ShipID int'

/* Execute the string. */
SET @IntVariable = 3
EXECUTE sp_executesql @SQLString, @ParmDefinition,
                      @ShipID = @IntVariable
  • 如果不需要负担定义存储过程的开销，可使用 sp_executesql。应始终使用 sp_executesql 而不是临时存储过程。

有关更多信息，请参见执行计划的高速缓存和重新使用和运行时生成语句。

重新使用批处理的执行计划

当多个并行应用程序将使用一组已知的参数执行同一个批处理时，请将该批处理作为将被这些应用程序调用的存储过程来实现。

当 ADO、OLE DB 或 ODBC 应用程序将多次执行同一个批处理时，请使用 PREPARE/EXECUTE 模型执行该批处理。还应使用与程序变量绑定的参数标记来提供所有需要的输入值，例如 UPDATE VALUES 子句或搜索条件谓词中使用的表达式。

使用 ODBC 转义序列

从 ADO、OLE DB 或 ODBC 应用程序中调用存储过程时，请使用 ODBC { CALL procedure_name } 转义序列代替 Transact-SQL EXECUTE 语句。有关更多信息，请参见调用存储过程。

未解决的事务和结果集

不要让某个事务长时间等待处理。长时间等待的事务由于长时间把持行上的锁，从而阻止了其它连接及时访问那些行，因此有可能减少吞吐量。

不要让某个结果集长时间等待处理。在执行了一个 Transact-SQL 批处理之后，应尽快完全处理或者从批处理中取消所有的结果集。

将行和操作数减到最少

使用 WHERE 和 HAVING 子句只选择需要的行，可以将 SELECT 语句返回的行数减到最少。

尽量少用不等于运算符 <> 或 !=。SQL Server 将必须在表或索引中扫描所有的值，以查看它们是否不等于表达式中给定的值。可以使用范围重写表达式：

WHERE KeyColumn < 'TestValue' AND KeyColumn > 'TestValue'

用以下方法减少应用程序和服务器之间的往返次数：

  • 在从应用程序发送给服务器的一个批处理中包含多个语句。有关更多信息，请参见批处理。

  • 将多个 Transact-SQL 语句放在单个存储过程中。这将减少必须从应用程序发送的信息量。

  • 只有当应用程序需要游标功能时才应使用服务器游标；而是使用默认结果集。有关更多信息，请参见游标。

    对于 ODBC 应用程序，应考虑使用带自动提取选项的快速只进游标。有关更多信息，请参见快速只进游标 (ODBC)。

使用高级功能

使用 Transact-SQL 中提供的高级功能，在服务器上的一个批处理中完成工作，而不是将结果拉到应用程序，然后使用它们向 SQL Server 发送另一个 Transact-SQL 语句。

  • 使用变量和控制流语句在批处理、存储过程和触发器中建立逻辑，不要将大的结果集拉到客户端并在客户端执行该逻辑。有关更多信息，请参见使用多个语句。

  • 使用构造（如 CASE）在个别 Transact-SQL 语句中包含逻辑。有关更多信息，请参见使用 CASE。

  • 在一个操作中使用 UPDATE 语句与 FROM 子句，用其它表中的值更新某个表中的值，而不是将源结果集选择到客户端，然后再逐行更新目标表。

  • 利用 SQL Server 2000 的联接功能。有关更多信息，请参见联接基础知识。
将数据定义语言语句放在一起

在批处理中，将临时表的所有数据定义语言 (DDL) 语句放在一起。例如：

/* Example 1. */
CREATE TABLE #temp1 (ColA INT NOT NULL)
CREATE UNIQUE INDEX MyIndex ON #temp1(ColA)
INSERT INTO #temp1 SELECT IntCol FROM SomeTable
SELECT * FROM #temp1
GO

/* Example 2. */
CREATE TABLE #temp1 (ColA INT UNIQUE NOT NULL)
INSERT INTO #temp1 SELECT IntCol FROM SomeTable
SELECT * FROM #temp1
GO

Do not code:
/* Example 3. */
CREATE TABLE #temp1 (ColA INT NOT NULL)
INSERT INTO #temp1 SELECT IntCol FROM SomeTable
CREATE UNIQUE INDEX MyIndex ON #temp1(ColA)
SELECT * FROM #temp1
GO

每当在临时表上执行 DDL 操作时，所有引用它的批处理都必须重新编译。查询优化器确保例 1 和例 2 中的 CREATE 语句可在一个操作中完成，且只重新编译批处理一次。在例 3 中，CREATE 语句之间的 INSERT 语句强迫每个 CREATE 语句都分别重新编译。

尽量少使用临时表

尽量少用临时表存储一系列 Transact-SQL 语句的中间结果。有些逻辑过于复杂，无法在一个 Transact-SQL 语句中执行。在这种情况下，必须编写多个 Transact-SQL 语句，并使用临时表将一个语句的结果传递给下一个语句。创建和维护临时表需要开销；如果可能，请考虑将该操作编写为一个更复杂的 Transact-SQL 语句。

在 SQL Server 2000 中，在存储过程和触发器中使用临时表可能导致存储过程或触发器在每次使用时都重新编译。若要避免此类重新编译，使用临时表的存储过程或触发器必须满足以下要求：

  • 在存储过程或触发器中，所有包含临时表名的语句都必须引用在同一存储过程中创建的临时表。临时表不能在正在调用或已经调用的存储过程中创建，也不能在使用 EXECUTE 或 sp_executesql 执行的字符串中创建。

  • 在存储过程或触发器中，所有包含临时表名的语句在语法上都必须出现在其创建之后。

  • 存储过程或触发器不能包含其 SELECT 语句引用了临时表的任何 DECLARE CURSOR 语句。

  • 包含任何临时表名的所有语句，都必须在引用临时表的任何 DROP TABLE 语句之前。在存储过程中创建的临时表不需要 DROP TABLE 语句；当该过程终止时，这些表将自动除去。

  • 创建临时表的语句（如 CREATE TABLE 或 SELECT INTO）不能出现在控制流语句中，如 IF...ELSE 或 WHILE。
防止日期问题

为了防止发生日期中世纪的解释问题，请不要用两个数字指定年份。例如：

/* Do this. */
SELECT *
FROM Northwind.dbo.Orders
WHERE OrderDate > '12/31/1997'
/* Do not do this. */
SELECT *
FROM Northwind.dbo.Orders
WHERE OrderDate > '12/31/97'


交叉数据报表

有时候需要旋转结果以便在水平方向显示列，而在垂直方向显示行。这就是所谓的创建 PivotTable®、创建交叉数据报表或旋转数据。

假定有一个表 Pivot，其中每季度占一行。对 Pivot 的 SELECT 操作在垂直方向上列出这些季度：

Year      Quarter      Amount
----      -------      ------
1990      1           1.1
1990      2           1.2
1990      3           1.3
1990      4           1.4
1991      1           2.1
1991      2           2.2
1991      3           2.3
1991      4           2.4

生成报表的表必须是这样的，其中每年占一行，每个季度的数值显示在一个单独的列中，如：

Year

Q1

Q2

Q3

Q4

1990

1.1

1.2

1.3

1.4

1991

2.1

2.2

2.3

2.4

下面的语句用于创建 Pivot 表并在其中填入第一个表中的数据：

USE Northwind
GO

CREATE TABLE Pivot
( Year      SMALLINT,
  Quarter   TINYINT,
  Amount      DECIMAL(2,1) )
GO
INSERT INTO Pivot VALUES (1990, 1, 1.1)
INSERT INTO Pivot VALUES (1990, 2, 1.2)
INSERT INTO Pivot VALUES (1990, 3, 1.3)
INSERT INTO Pivot VALUES (1990, 4, 1.4)
INSERT INTO Pivot VALUES (1991, 1, 2.1)
INSERT INTO Pivot VALUES (1991, 2, 2.2)
INSERT INTO Pivot VALUES (1991, 3, 2.3)
INSERT INTO Pivot VALUES (1991, 4, 2.4)
GO

下面是用于创建旋转结果的 SELECT 语句：

SELECT Year,
    SUM(CASE Quarter WHEN 1 THEN Amount ELSE 0 END) AS Q1,
    SUM(CASE Quarter WHEN 2 THEN Amount ELSE 0 END) AS Q2,
    SUM(CASE Quarter WHEN 3 THEN Amount ELSE 0 END) AS Q3,
    SUM(CASE Quarter WHEN 4 THEN Amount ELSE 0 END) AS Q4
FROM Northwind.dbo.Pivot
GROUP BY Year
GO

该 SELECT 语句还处理其中每个季度占多行的表。GROUP BY 语句将 Pivot 中一年的所有行合并成一行输出。当执行分组操作时，SUM 聚合中的 CASE 函数的应用方式是这样的：将每季度的 Amount 值添加到结果集的适当列中，在其它季度的结果集列中添加 0。

如果该 SELECT 语句的结果用作电子表格的输入，那么电子表格将很容易计算每年的合计。当从应用程序使用 SELECT 时，可能更易于增强 SELECT 语句来计算每年的合计。例如：

SELECT P1.*, (P1.Q1 + P1.Q2 + P1.Q3 + P1.Q4) AS YearTotal
FROM (SELECT Year,
             SUM(CASE P.Quarter WHEN 1 THEN P.Amount ELSE 0 END) AS Q1,
             SUM(CASE P.Quarter WHEN 2 THEN P.Amount ELSE 0 END) AS Q2,
             SUM(CASE P.Quarter WHEN 3 THEN P.Amount ELSE 0 END) AS Q3,
             SUM(CASE P.Quarter WHEN 4 THEN P.Amount ELSE 0 END) AS Q4
     FROM Pivot AS P
     GROUP BY P.Year) AS P1
GO

带有 CUBE 的 GROUP BY 和带有 ROLLUP 的 GROUP BY 都计算与本例显示相同的信息种类，但格式稍有不同。

请参见

SELECT


展开层次结构

数据库经常存储层次信息。例如，下面的数据是全球一些地区的层次表示形式。这种表示形式并未清楚地显示出数据中隐含的结构。

Parent                             Child
---------------------------------- ----------------------------------
World                              Europe
World                              North America
Europe                             France
France                             Paris
North America                      United States
North America                      Canada
United States                      New York
United States                      Washington
New York                           New York City
Washington                         Redmond

而下面的示例则更容易解释：

World
   North America
      Canada
      United States
         Washington
            Redmond
         New York
            New York City
   Europe
      France
         Paris

下面的 Transact-SQL 过程将一个编码的层次展开到任意深度。尽管 Transact-SQL 支持递归，但是使用临时表作为堆栈来跟踪所有正在处理中的项目（已经开始但尚未结束），将更加有效。某个项目一旦处理完毕，将被从堆栈中删除。当发现新的项目时，这些项目将被添加到堆栈中。

CREATE PROCEDURE expand (@current char(20)) as
SET NOCOUNT ON
DECLARE @level int, @line char(20)
CREATE TABLE #stack (item char(20), level int)
INSERT INTO #stack VALUES (@current, 1)
SELECT @level = 1

WHILE @level > 0
BEGIN
   IF EXISTS (SELECT * FROM #stack WHERE level = @level)
      BEGIN
         SELECT @current = item
         FROM #stack
         WHERE level = @level
         SELECT @line = space(@level - 1) + @current
         PRINT @line
         DELETE FROM #stack
         WHERE level = @level
            AND item = @current
         INSERT #stack
            SELECT child, @level + 1
            FROM hierarchy
            WHERE parent = @current
         IF @@ROWCOUNT > 0
            SELECT @level = @level + 1
      END
   ELSE
      SELECT @level = @level - 1
END -- WHILE

输入参数 (@current) 表示层次中的开始位置。它还跟踪主循环中的当前项目。

使用的两个局部变量分别是 @level（用于跟踪层次结构中的当前级别）和 @line（是用于构造缩进行的工作区）。

SET NOCOUNT ON 语句避免输出中夹杂每个 SELECT 产生的 ROWCOUNT 消息。

使用层次中开始点的项目标识符来创建和整理临时表 #stack，而 @level 被设置为与之匹配。#stack 中的 level 列允许同一个项目出现在数据库的多个级别中。虽然这种情况不适用于该示例中的地理数据，但是它可以用于其它示例。

在下面的示例中，当 @level 大于 0 时，该过程执行以下步骤：

  1. 如果当前级别 (@level) 的堆栈中有任何项目，该过程将选择其中一个，并称之为 @current。

  2. 缩进项目 @level 空格，然后打印该项目。

  3. 从堆栈中删除该项目以免重复处理它，然后将其所有子项目添加到堆栈的下一级 (@level + 1) 中。这是唯一使用层次表 (#stack) 的地方。

    如果使用传统的编程语言，就必须找到每个子项目并将其逐个添加到堆栈中。而使用 Transact-SQL，只用一个语句就能找到并添加所有的子项目，以免又使用一个嵌套循环。

  4. 如果有子项目 (IF @@ROWCOUNT > 0)，则下降一级处理它们 (@level = @level + 1)；否则，继续在当前级别上处理。

  5. 最后，如果在当前级别的堆栈中没有待处理的项目，则返回到上一级，看上一级是否有待处理的项目 (@level = @level - 1)。当再没有上一级时，则展开完毕。


展开网络

在网络中，一个项目可以有多个上级。例如，下面的数据显示一些城市间的航班：

Departure                          Destination
---------------------------------- ----------------------------------
Chicago                            New York
Chicago                            Milwaukee
Denver                             Chicago
Seattle                            Chicago
Seattle                            Denver
Seattle                            San Francisco

对于上述数据而言，一个常见的问题就是找到给定的两个城市之间的所有航线：

Itineraries
----------------------------------
Seattle, Chicago, New York
Seattle, Denver, Chicago, New York

若要解决这一问题，可以将"展开层次结构"一节中的示例作如下更改：

  • 需要增加两个输入参数：目标城市和搜索深度限制。

  • 当前的路线保存在另一个临时表中，仅当到达目标时才显示。

  • 为避免在网络中循环展开，不应展开出现在当前路线中的城市。

下面的示例（不是来自 pubs 数据库）显示了这些更改：

CREATE PROCEDURE route
(@current char(20), @dest char(20), @maxlevel int = 5) AS
SET NOCOUNT ON
DECLARE @level int
CREATE TABLE #stack (city char(20), level int)
CREATE TABLE #list (city char(20), level int)
INSERT #stack VALUES (@current, 1)
SELECT @level = 1

WHILE @level > 0
BEGIN
   IF EXISTS (SELECT * FROM #stack WHERE level = @level)
      BEGIN
         SELECT @current = city
         FROM #stack
         WHERE level = @level
         DELETE FROM #stack
         WHERE level = @level
            AND city = @current
         DELETE FROM #list
         WHERE level >= @level
         IF EXISTS (SELECT * FROM #list WHERE city = @current)
            CONTINUE
         INSERT #list VALUES(@current, @level)
         IF(@current = @dest)
         BEGIN
            SELECT city AS itinerary
            FROM #list
            CONTINUE
         END

         INSERT #stack
         SELECT destination, @level + 1
         FROM flights
         WHERE departure = @current
            AND @level < @maxlevel
         IF @@rowcount > 0
            SELECT @level = @level + 1
      END
   ELSE
      SELECT @level = @level - 1
END -- WHILE

在下面的示例中，当 @level 大于 0 时，该过程执行以下步骤：

  1. 通过清除当前级或其下级的所有项目 (DELETE FROM #list WHERE level > = @level)，然后添加当前城市 (INSERT #list VALUES(@current, @level))，将当前城市添加到 #list 中。

  2. 只要到达目标城市 (@current = @dest)，该过程就会显示路径 (SELECT itinerary = city FROM #list)，而且不再进一步展开该路径 (CONTINUE)。

  3. 通过在将城市添加到堆栈的 INSERT 语句中添加一个条件 (@level < @maxlevel)，限制搜索深度。

如果当前城市已经在当前路线中，WHILE 循环中最开始的 IF EXISTS 语句将跳过当前城市。

如果 flights 表还包括费用信息，就可以找到最低费用路线，方法是如果当前路线的总费用低于到目前为止的最低费用，就保存当前路线：

SELECT @cost = sum(cost)
FROM #list
IF @cost < @lowest_cost
BEGIN
   @lowest_cost = @cost
   TRUNCATE TABLE #best_route
   INSERT #best_route
      SELECT *
      FROM #list
END

为了获得更高的效率，如果当前成本超过最佳路线的成本，则可以停止展开当前路线：

IF (SELECT SUM(cost) FROM #list) > @lowest_cost
   CONTINUE

如果 flights 表包括起飞和到达时间，则可以添加一个 IF 语句，以便只展开起飞时间至少比当前路线的到达时间晚一个小时的路线：

IF ((SELECT SUM(cost) FROM #list) > @lowest_cost)
   AND datediff(hh, departuretime, @arrivaltime) > 1)
CONTINUE


编写国际化 Transact-SQL 语句

如果遵从以下准则，则使用 Transact-SQL 语句的数据库和数据库应用程序将更便于在语言间进行移植，或将支持多种语言：

  • 用 ncha、nvarchar 和 ntext 代替所有的 char、varchar 和 text 数据类型。这样就可以不必考虑代码页的转换问题。

  • 当执行月份和星期的比较与操作时，请使用数字日期，不要使用名称字符串。不同语言设置返回的月份和星期名称是不同的。例如，当语言设置为美国英语时，DATENAME(MONTH,GETDATE()) 返回 May，而当语言设置为德语时，返回 Mai，语言设置为法语时则返回 mai。应使用以数字而非名称表示月份的函数，如 DATEPART。当生成要显示给用户的结果集时，可使用DATEPART名称，因为日期名称通常比数字表示形式更有意义；但是，编写逻辑代码时不要使用任何依赖于特定语言显示的名称。

  • 当指定用于比较操作的日期或者用于 INSERT 或 UPDATE 语句的输入的日期时，请使用对于所有的语言设置解释都相同的常量：
    • ADO、OLE DB 和 ODBC 应用程序应该使用以下的 ODBC 时间戳、日期和时间转义子句：
      { ts 'yyyy-mm-dd hh:mm:ss[.fff] '}，例如 { ts '1998-09-24 10:02:20' }
      { d 'yyyy-mm-dd'}，例如 { d '1998-09-24' }
      { t 'hh:mm:ss'}，例如 { t '10:02:20'}

    • 使用其它 API 的应用程序或 Transact-SQL 脚本、存储过程和触发器都应该使用连在一起的数字字符串（例如 yyyymmdd 为 19980924）。

    • 使用其它 API 的应用程序或 Transact-SQL 脚本、存储过程和触发器都应该使用带有显式参数的 CONVERT 语句，以进行 date 和 smalldate 数据类型及字符串数据类型之间的所有转换。例如，下列语句的解释对于所有语言或日期格式连接设置都是相同的：
      SELECT *
FROM Northwind.dbo.Orders
WHERE OrderDate = CONVERT(DATETIME, '7/19/1996', 101)

      有关更多信息，请参见 CAST 和 CONVERT。


编写可读性强的代码

以下是编写可读性强的代码的准则：

  • 使用注释描述程序或脚本，包括作者、日期和修改说明。

  • 让每个主要的 Transact-SQL 子句单独占一行，以使语句更易于阅读：
    USE pubs
SELECT au_fname, au_lname
FROM authors
WHERE state = 'CA'
  • Transact-SQL 关键字（如 SELECT 和 FROM）、函数名（如 SUM、AVG、DATEPART、CASE 和 CONVERT）以及数据类型（如 INT、CHAR、NTEXT）都用大写：
    USE pubs
CREATE TABLE myauthors
(
 first VARCHAR(30) NOT NULL,
 last VARCHAR(40) NOT NULL,
 address VARCHAR(40) NOT NULL,
 city VARCHAR(30) NOT NULL,
 state VARCHAR(2) NOT NULL,
 zip CHAR(9) NOT NULL,
 phone VARCHAR(20) NULL
)
  • 定义并统一使用对象名的风格约定。以下是两项典型的约定：
    • 名称的每个部分的首字母大写；名称的各部分不用下划线隔开：TableName。

    • 所有的字符都小写，并用下划线 (_) 隔开名称的各部分：table_name。

    即使当前的 Microsoft® SQL Server™ 实例不区分大小写，如果使用一致的风格，也可以提高可读性。在编写 Transact-SQL 语句中的对象名时，始终与定义该对象所用的大小写严格保持一致，也是一个好方法。

  • 对于您单位中常用的对象，定义一组标准缩写，统一用作对象名称。

  • 所有字符、字符串、二进制数据和 Unicode 常量都使用单引号，以便引用的标识符是唯一使用双引号 (") 的项目。

  • 当使用多表联接时，用易写和易记的别名。例如，将 t 作为 titles 表的别名，将 a 作为 authors 表的别名。

  • 如果某个 Transact-SQL 关键字后面的信息要折到下一行，请考虑将第二行和其后的行缩进一个制表位（通常是 5 个空格），使查找主关键字更方便。

  • 使用圆括号表示复杂数学计算的执行顺序。这将提高可读性。例如，使用"(price * 1.15) + sales"而不是"price * 1.15 + sales"。

请参见

批处理

函数


修改数据

本节主题描述关系数据库表中数据的操作技术。可添加新数据行、更改现有行中的数据和删除行。

请参见

添加数据

更改数据

删除数据


添加数据

Microsoft® SQL Server™ 支持使用下列方法向表中添加数据：

  • 带有下列两个选项之一的 INSERT 语句：
    • VALUES 子句，将特定值集合插入一行。

    • SELECT 子查询，用来插入选自表或视图中的数据。
  • 数据库应用程序接口 (API)

    处理结果集时插入数据的支持选项。

  • WRITETEXT 语句和几个数据库 API 选项

    可用于向一行添加 ntext、text 或 image 数据。

  • 数据库 API（ADO、OLE DB、ODBC 和 DB-Library）中的函数

    支持在一行中添加新的 ntext、text 和 image 数据。

  • SELECT INTO 语句

    可用于创建一个包含 SELECT INTO 结果集所有行的新表。

  • 用于插入许多行的大容量复制组件

    指定大容量复制操作主要有三种方法：

    • 大容量复制程序（bcp 实用工具），一个命令行提示实用工具。

    • 用在 Transact-SQL 批处理、存储过程和触发器中的 BULK INSERT 语句。

    • 用于 OLE DB、ODBC 和 DB-Library 应用程序的大容量复制 API。

INSERT 语句在视图中工作与在表中工作一样，要受到一些限制。有关更多信息，请参见创建视图。


使用 INSERT 添加行

INSERT 语句可给表添加一个或多个新行。INSERT 语句在简单的情况下有如下形式：

INSERT [INTO] table_or_view [(column_list)] data_values

此语句将使 data_values 作为一行或者多行插入已命名的表或视图中。column_list 是由逗号分隔的列名列表，用来指定为其提供数据的列。如果没有指定 column_list，表或者视图中的所有列都将接收数据。

如果 column_list 没有为表或视图中的所有列命名，将在列表中没有命名的任何列中插入一个 NULL 值（或者在默认情况下为这些列定义的默认值）。在列的列表中没有指定的所有列都必须允许 null 值或者指定的默认值。

由于 Microsoft® SQL Server™ 为以下类型的列生成值，INSERT 语句将不为这些类型的列指定值：

  • 具有 IDENTITY 属性的列，该属性为列生成值。

  • 有默认值的列，该列用 NEWID 函数生成一个唯一的 GUID 值。

  • 计算列。

    这些是虚拟列，被定义为 CREATE TABLE 语句中从另外一列或多列计算的表达式，例如：

    CREATE TABLE TestTable
  (ColA INT PRIMARY KEY,
   ColB INT NOT NULL,
   ColC AS (ColA + ColB) * 2)

所提供的数据值必须与列的列表匹配。数据值的数目必须与列数相同，每个数据值的数据类型、精度和小数位数也必须与相应的列匹配。有两种方法指定数据值：

  • 用 VALUES 子句为一行指定数据值：
    INSERT INTO MyTable (PriKey, Description)
       VALUES (123, 'A description of part 123.')
  • 用 SELECT 子查询为一行或多行指定数据值。
    INSERT INTO MyTable  (PriKey, Description)
       SELECT ForeignKey, Description
       FROM SomeView


使用 INSERT...Values 插入行

VALUES 关键字为表的某一行指定值。值被指定为逗号分隔的标量表达式列表，表达式的数据类型、精度和小数位数必须与列的列表对应的列一致，或者可以隐性地转换为列的列表中的对应列。如果没有指定列的列表，指定值的顺序必须与表或视图中的列顺序一致。

例如，下面的语句用 VALUES 子句将一个新的 shipper 插入到 Shippers 表中：

INSERT INTO Northwind.dbo.Shippers (CompanyName, Phone)
   VALUES (N'Snowflake Shipping', N'(503)555-7233')

由于 ShipperID 列具有 IDENTITY 属性，要求插入列的列表，因此不能插入值。

用 INSERT 插入数据

Transact-SQL


使用 INSERT...SELECT 插入行

INSERT 语句中的 SELECT 子查询可用于将一个或多个其它的表或视图的值添加到表中。使用 SELECT 子查询可同时插入多行。

下面的 INSERT 语句将 titles 中数据的 type 是 modern cooking 的所有行的数据插入到一个单独的表中：

USE pubs
INSERT INTO MyBooks
   SELECT title_id, title, type
   FROM titles
   WHERE type = 'mod_cook'

子查询的选择列表必须与 INSERT 语句列的列表匹配。如果没有指定列的列表，选择列表必须与正向其插入的表或视图的列匹配。

INSERT...SELECT 语句的另一个作用是从 Microsoft® SQL Server™ 的外部数据源插入数据。INSERT 语句中的 SELECT 可以：

  • 使用由四部分组成的名称引用链接服务器上的远程表。有关更多信息，请参见使用链接服务器名标识数据源。

  • 使用 OPENROWSET 引用远程表。有关更多信息，请参见使用特殊名称标识数据源。

  • 使用在远程服务器上执行的查询结果集。有关更多信息，请参见将直接传递查询作为表使用。


使用 SELECT INTO 插入行

SELECT INTO 语句创建一个新表，并用 SELECT 的结果集填充该表。新表的结构由选择列表中表达式的特性定义，例如：

SELECT Shippers.*, Link.Address, Link.City,
                   Link.Region, Link.PostalCode
INTO NewShippers
FROM Shippers
     JOIN LinkServer.DB.dbo.Shippers AS Link
       ON (Shippers.ShipperID = Link.ShipperID)

SELECT INTO 可将几个表或视图中的数据组合成一个表。也可用于创建一个包含选自链接服务器的数据的新表。

请参见

SELECT


使用结果集位置添加行

ADO、OLE DB 和 ODBC 应用程序接口 (API) 支持在处理查询结果集时添加行。其基本过程如下：

  1. 将结果集的各列绑定到程序变量上。

  2. 执行查询。

  3. 执行 API 函数或方法，将应用程序定位在结果集的某一行上。

  4. 对要插入的新行使用数据值填充绑定的程序变量。

  5. 执行以下函数或方法之一插入行：
    • 在 ADO 中，调用 Recordset 对象的 AddNew 方法。

    • 在 OLE DB 中，调用 IRowsetChange 接口的 InsertRow 方法。

    • 在 ODBC 3.x 中，调用带有 SQL_ADD 选项的 SQLBulkOperations 函数。

新行的插入位置不必基于结果集中应用程序的位置。新行可以插入到与指定的任何聚集键值的值相关的基表中的某个位置。


将 ntext、text 或 image 数据添加到插入的行中

在行中添加 ntext、text 或 image 值的方法如下：

  • 与 char、nchar 或者 binary 数据类似，在 INSERT 语句中指定相对较短的数据。

  • 使用 WRITETEXT 语句。有关更多信息，请参见 WRITETEXT。

  • ADO 应用程序可以使用 AppendChunk 方法指定较长的 ntext、text 或 image 数据。有关更多信息，请参见管理长数据类型。

  • OLE DB 应用程序可以使用 ISequentialStream 接口写入新的 ntext、text 或 image 值。有关更多信息，请参见 BLOB 和 OLE 对象。

  • ODBC 应用程序可以使用 SQLPutData 的执行中的数据形式写入新的 ntext、text 或 image 值。有关更多信息，请参见管理 text 和 image 列。

  • DB-Library 应用程序可以使用 dbwritetext 函数。有关更多信息，请参见 Text 和 Image 函数。

请参见

BACKUP

UPDATETEXT

INSERT


使用大容量复制操作添加行

Microsoft® SQL Server™ 大容量复制组件支持：

  • 在表或视图中插入多行。

  • 从表、视图或查询中检索多行。

大容量复制是在 SQL Server 中添加大量行的最快的方法。执行大容量复制操作的方法有三种：

  • 使用大容量复制程序（bcp 实用工具）。

    bcp 是一种命令提示实用工具，利用 bcp 可以在 .bat 和 .cmd 脚本中运行大容量复制。在 SQL Server 数据库中，bcp 用于向表或视图中大容量复制大文件。

  • 在 Transact-SQL 批处理、存储过程和触发器中，使用 BULK INSERT 语句从文件中将数据大容量复制到 SQL Server 数据库的表或视图中。

    BULK INSERT 语句运行在 MSSQLServer 服务环境中的服务器上，而不是在客户机上。如果大容量复制的文件也在服务器上，数据就根本不用通过网络传送。这使得 BULK INSERT 成为服务器文件中最快的大容量复制选项。有关更多信息，请参见 BULK INSERT。

  • 为 OLE DB、ODBC 和 DB-Library 应用程序使用大容量复制 API。

    bcp 实用工具是 ODBC 命令提示实用工具，它使用 SQL Server ODBC 驱动程序大容量复制函数。任何应用程序都可以在 ODBC 或 DB-Library 应用程序中使用这些发布的大容量复制函数来执行大容量复制操作。应用程序可将数据从文件大容量复制到 SQL Server 的表或视图中。也可以从程序变量中将数据大容量复制到 SQL Server 的表或视图中。有关 OLE DB 大容量复制的更多信息，请参见大容量复制行集。有关 ODBC 大容量复制的更多信息，请参见执行大容量复制操作。有关 DB-Library 大容量复制的更多信息，请参见大容量复制函数。

使用 bcp 实用工具添加数据

客户实用工具


更改数据

在创建表并添加数据之后，更改或更新表中的数据就成为维护数据库的一个日常过程。Microsoft® SQL Server™ 提供下列方法更改现有表中的数据：

  • UPDATE 语?

    可用来更新表或视图中特定行的数据。

  • 数据库应用程序接口 (API)

    支持用来更新某个结果集当前位置数据的选项。Transact-SQL 服务器游标也支持更新游标当前行的数据。

  • UPDATETEXT 语句

    可用来更新特定的 ntext、text 和 image 值。

更新操作适用于视图和表，但是有一些限制条件。有关更多信息，请参见创建视图。


使用 UPDATE 语句更改数据

UPDATE 语句可以更改表或视图中单行、行组或所有行的数据值。还可以用该语句更新远程服务器上的行（使用链接服务器名称或 OPENROWSET、OPENDATASOURCE 和 OPENQUERY 函数），前提是用来访问远程服务器的 OLE DB 提供程序支持更新操作。引用某个表或视图的 UPDATE 语句每次只能更改一个基表中的数据。

UPDATE 语句包括以下主要子句：

  • SET

    包含要更新的列和每个列的新值的列表（用逗号分隔），格式为 column_name = expression。表达式提供的值包含多个项目，如常量、从其它表或视图的列中选择的值或使用复杂的表达式计算出来的值。

  • FROM

    指定为 SET 子句中的表达式提供值的表或视图，以及各个源表或视图之间可选的联接条件。

  • WHERE

    指定搜索条件，该搜索条件定义源表和视图中可以为 SET 子句中的表达式提供值的行。

此更新语句将类别 (category) 2 中所有 Northwind 产品的价格提高了 10%：

UPDATE Northwind.dbo.Products
SET UnitPrice = UnitPrice * 1.1
WHERE CategoryID = 2

使用 UPDATE 更改数据

Transact-SQL

说明 UPDATE 语句有日志记录；如果要更改大量的 text 或 image 数据，请考虑使用 UPDATETEXT 或 WRITETEXT 语句，这两个语句默认情况无日志记录。有关更多信息，请参见将 ntext、text 或 image 数据添加到插入的行中。


使用 SET 子句更改数据

SET 子句指定要更改的列和这些列的新值。对所有符合 WHERE 子句搜索条件的行，将使用 SET 子句中指定的值更新指定列中的值。如果没有指定 WHERE 子句，则更新所有行。例如，如果 publishers 表中的所有出版社都将总部迁移到乔治亚 (Georgia) 州的亚特兰大 (Atlanta)，则可使用以下 UPDATE 语句：

UPDATE publishers SET city = 'Atlanta', state = 'Georgia'

计算列的值可在更新操作中计算和使用。例如，要使 titles 表中的所有价格加倍，titles 表的 price 列可以设置为等于 price * 2。

SET 子句中使用的表达式也可以是只返回一个值的子查询；例如，如果 Northwind 数据库有一个 OrderSummary 表：

UPDATE OrderSummary
SET Last30Days =
    (SELECT SUM(OrdDet.UnitPrice * OrdDet.Quantity)
     FROM [Order Details] AS OrdDet
          JOIN Orders AS Ord
          ON (OrdDet.OrderID = Ord.OrderID
              AND Ord.OrderDate > DATEADD(dd,-30,GETDATE()) )
    )


使用 WHERE 子句更改数据

WHERE 子句执行两种功能：

  • 指定要更新的行。

  • 如果同时指定了 FROM 子句，则指定源表中可以为更新语句提供值的行。

如果没有指定 WHERE 子句，则将更新表中的所有行。

以下 UPDATE 语句更改其中一个运输商的名称：

UPDATE Northwind.dbo.Shippers
SET CompanyName = 'United Shippers'
WHERE CompanyName = 'United Packages'


使用 FROM 子句更改数据

使用 FROM 子句可将数据从一个或多个表或视图拉入要更新的表中。例如，当作者 Dirk Stringer 获得一项合同，在 titles 表中为他的书 The Psychology of Computer Cooking 指定一个书名标识号时，就可以通过为这本最新的书添加书名标识号来更新 titleauthor 表中 Dirk 的行。

下例更新 titleauthor 表中 Dirk Stringer 的行，以便为作者最新的书添加书名标识号：

UPDATE titleauthor
   SET title_id = titles.title_id
   FROM titles INNER JOIN titleauthor
      ON titles.title_id = titleauthor.title_id
      INNER JOIN authors
      ON titleauthor.au_id = authors.au_id
   WHERE titles.title = 'Net Etiquette'
      AND au_lname = 'Locksley'

使用 UPDATE 更新数据

Transact-SQL

请参见

FROM

WHERE

UPDATE


使用游标更改数据

ADO、OLE DB 和 ODBC 应用程序接口 (API) 支持对结果集内应用程序所处的当前行进行更新。其基本过程如下：

  1. 将结果集的各列绑定到程序变量上。

  2. 执行查询。

  3. 执行 API 函数或方法，将应用程序定位在结果集的某一行上。

  4. 使用要更新的列的新数据值填充绑定的程序变量。

  5. 执行以下函数或方法之一插入行：
    • 在 ADO 中，调用 Recordset 对象的 Update 方法。

    • 在 OLE DB 中，调用 IRowsetChange 接口的 SetData 方法。

    • 在 ODBC 中，调用带 SQL_UPDATE 选项的 SQLSetPos 函数。

使用 Transact-SQL 服务器游标时，可以使用包含 WHERE CURRENT OF 子句的 UPDATE 语句更新当前行。使用此子句所做的更改只影响游标所在行。如果游标基于某个联接，则只修改 UPDATE 语句中指定的 table_name。而不影响其它参与该游标的表。

USE Northwind
GO
DECLARE abc CURSOR FOR
SELECT CompanyName
FROM Shippers

OPEN abc
GO

FETCH NEXT FROM abc
GO

UPDATE Shippers SET CompanyName = N'Speedy Express, Inc.'
WHERE CURRENT OF abc
GO

CLOSE abc
DEALLOCATE abc
GO

有关联接的信息，请参见联接基础知识。

请参见

UPDATE


更改 ntext、text 或 image 数据

有多种方法可以在替换整个值时更新行中的 ntext、text 或 image 值：

  • 在 UPDATE 语句中指定相对较短的数据，其方式与 char、nchar 或 binary 数据相同。

  • 使用 Transact-SQL WRITETEXT 语句。有关更多信息，请参见 WRITETEXT。

  • ADO 应用程序可以使用 AppendChunk 方法指定较长的 ntext、text 或 image 数据。有关更多信息，请参见管理长数据类型。

  • OLE DB 应用程序可以使用 ISequentialStream 接口写入新的 ntext、text 或 image 值。有关更多信息，请参见 BLOB 和 OLE 对象。

  • ODBC 应用程序可以使用 SQLPutData 的执行中的数据形式写入新的 ntext、text 或 image 值。有关更多信息，请参见管理 text 和 image 列。

  • DB-Library 应用程序可以使用 dbwritetext 函数。有关更多信息，请参见 Text 和 Image 函数。

Microsoft® SQL Server™ 还支持仅更新 ntext、text 或 image 值的一部分。在 DB-Library 中，该操作可以使用 dbupdatetext 函数实现。有关更多信息，请参见 dbupdatetext。所有其它应用程序和 Transact-SQL 脚本、批处理、存储过程和触发器可以使用 UPDATETEXT 语句只更新 ntext、text 或 image 列的一部分。

以下脚本显示同时使用 UPDATETEXT 和 PATINDEX 查找和替换 text 值中特定字符串的方法：

USE Northwind
GO
CREATE TABLE TextParts (ColA INT PRIMARY KEY, ColB TEXT)
GO
INSERT INTO TextParts
   VALUES( 1,
           'Sample string START TAG Text to go END TAG Trailing text.')
GO
DECLARE @PtrVar BINARY(16)
DECLARE @InsertPos INT
DECLARE @DeleteLen INT

SELECT @PtrVar = TEXTPTR(ColB),
       @InsertPos = (PATINDEX('%START TAG%', ColB) + 9),
       @DeleteLen = (
                      PATINDEX('%END TAG%', ColB) -
                      ( PATINDEX('%START TAG%', ColB) + 9
                              + 2 /* allow for blanks */ )
                    )
FROM TextParts
WHERE ColA = 1

UPDATETEXT TextParts.ColB
           @PtrVar
           @InsertPos
           @DeleteLen
           WITH LOG
           'The new text'
GO

SELECT * FROM TextParts
GO

由最后的 SELECT 语句得出的结果集为：

ColA        ColB
----------- ------------------------------------------------------------
1           Sample string START TAG The new text END TAG Trailing text.

使用 UPDATETEXT 更新数据

Transact-SQL


删除数据

Microsoft® SQL Server™ 支持下列删除现有表中数据的方法：

  • DELETE 语句

  • 删除结果集或游标中的当前行

  • TRUNCATE TABLE 语句

数据修改语句适用于视图和表，但是有一些限制条件。有关更多信息，请参见创建视图。


使用 DELETE 语句删除行

DELETE 语句可删除表或视图中的一行或多行。DELETE 语法的简化形式为：

DELETE table_or_view FROM table_sources WHERE search_condition

table_or_view 指定要从中删除行的表或视图。table_or_view 中所有符合 WHERE 搜索条件的行都将被删除。如果没有指定 WHERE 子句，将删除 table_or_view 中的所有行。FROM 子句指定删除时用到的额外的表或视图及联接条件，WHERE 子句搜索条件中的谓词使用它们限定要从 table_or_view 中删除的行。该语句不从 FROM 子句指定的表中删除行，而只从 table_or_view 指定的表中删除行。

任何已删除所有行的表仍会保留在数据库中。DELETE 语句只从表中删除行，要从数据库中删除表，必须使用 DROP TABLE 语句。

使用 DELETE 语句删除行

Transact-SQL

对于 Northwind 数据库中名为 Lyngbysild 的公司所提供的产品，以下脚本显示了删除与这些产品有关的行时所需要的三个 DELETE 语句。因为该脚本涉及了从现有订单中删除行，所以它不是一个典型的商业操作，但是它确实显示了一系列复杂程度不同的删除操作。

USE Northwind
GO
DELETE [Order Details]
FROM Suppliers, Products
WHERE Products.SupplierID = Suppliers.SupplierID
  AND Suppliers.CompanyName = 'Lyngbysild'
  AND [Order Details].ProductID = Products.ProductID
GO
DELETE Products
FROM Suppliers
WHERE Products.SupplierID = Suppliers.SupplierID
  AND Suppliers.CompanyName = 'Lyngbysild'
GO
DELETE Suppliers
WHERE CompanyName = 'Lyngbysild'
GO

请参见

DROP TABLE


删除结果集中的行

ADO、OLE DB 和 ODBC 应用程序接口 (API) 支持删除结果集中应用程序所处的当前行。应用程序执行某个语句，然后从结果集中提取行。应用程序提取行后，就可以使用以下函数或方法删除该行：

  • ADO 应用程序使用 Recordset 对象的 Delete 方法。

  • OLE DB 应用程序使用 IRowsetChange 接口的 DeleteRows 方法。

  • ODBC 应用程序使用带 SQL_DELETE 选项的 SQLSetPos 函数。

  • DB-library 应用程序使用 dbcursor 执行 CRS_DELETE 操作。

Transact-SQL 脚本、存储过程和触发器可以使用 DELETE 语句中的 WHERE CURRENT OF 子句删除它们当前所处的游标行，例如：

DECLARE abc CURSOR FOR
  SELECT * FROM MyTable

OPEN abc

FETCH NEXT FROM abc

DELETE MyTable WHERE CURRENT OF abc

CLOSE abc

DEALLOCATE abc


使用 TRUNCATE TABLE 删除所有行

若要删除表中的所有行，则 TRUNCATE TABLE 语句是一种快速、无日志记录的方法。该语句总是比不带条件的 DELETE 语句要快，因为 DELETE 语句要记录对每行的删除操作，而 TRUNCATE TABLE 语句只记录整个数据页的释放。TRUNCATE TABLE 语句立即释放由该表的数据和索引占用的所有空间。所有索引的分发页也将释放。

与 DELETE 语句相同，使用 TRUNCATE TABLE 清空的表的定义，同其索引和其它相关的对象一起仍保留在数据库中。必须使用 DROP TABLE 语句才能除去表的定义。

使用 TRUNCATE TABLE 语句删除表中的所有行

Transact-SQL

请参见

DROP TABLE


事务

事务是作为单个逻辑工作单元执行的一系列操作。一个逻辑工作单元必须有四个属性，称为 ACID（原子性、一致性、隔离性和持久性）属性，只有这样才能成为一个事务：

原子性

事务必须是原子工作单元；对于其数据修改，要么全都执行，要么全都不执行。

一致性

事务在完成时，必须使所有的数据都保持一致状态。在相关数据库中，所有规则都必须应用于事务的修改，以保持所有数据的完整性。事务结束时，所有的内部数据结构（如 B 树索引或双向链表）都必须是正确的。

隔离性

由并发事务所作的修改必须与任何其它并发事务所作的修改隔离。事务查看数据时数据所处的状态，要么是另一并发事务修改它之前的状态，要么是另一事务修改它之后的状态，事务不会查看中间状态的数据。这称为可串行性，因为它能够重新装载起始数据，并且重播一系列事务，以使数据结束时的状态与原始事务执行的状态相同。

持久性

事务完成之后，它对于系统的影响是永久性的。该修改即使出现系统故障也将一直保持。

指定和强制事务处理

SQL 程序员要负责启动和结束事务，同时强制保持数据的逻辑一致性。程序员必须定义数据修改的顺序，使数据相对于其组织的业务规则保持一致。然后，程序员将这些修改语句包括到一个事务中，使 Microsoft® SQL Server™ 能够强制该事务的物理完整性。

企业数据库系统（如 SQL Server）有责任提供一种机制，保证每个事务物理的完整性。SQL Server 提供：

  • 锁定设备，使事务相互隔离。

  • 记录设备，保证事务的持久性。即使服务器硬件、操作系统或 SQL Server 自身出现故障，SQL Server 也可以在重新启动时使用事务日志，将所有未完成的事务自动地回滚到系统出现故障的位置。

  • 事务管理特性，强制保持事务的原子性和一致性。事务启动之后，就必须成功完成，否则 SQL Server 将撤消该事务启动之后对数据所作的所有修改。


控制事务

应用程序主要通过指定事务启动和结束的时间来控制事务。这可以使用 Transact-SQL 语句或数据库 API 函数。系统还必须能够正确处理那些在事务完成之前便终止事务的错误。

事务是在连接层进行管理。当事务在一个连接上启动时，在该连接上执行的所有的 Transact-SQL 语句在该事务结束之前都是该事务的一部分。

启动事务

在 Microsoft® SQL Server™ 中，可以按显式、自动提交或隐性模式启动事务。

显式事务

通过发出 BEGIN TRANSACTION 语句显式启动事务。

自动提交事务

这是 SQL Server 的默认模式。每个单独的 Transact-SQL 语句都在其完成后提交。不必指定任何语句控制事务。

隐性事务

通过 API 函数或 Transact-SQL SET IMPLICIT_TRANSACTIONS ON 语句，将隐性事务模式设置为打开。下一个语句自动启动一个新事务。当该事务完成时，再下一个 Transact-SQL 语句又将启动一个新事务。

连接模式在连接层进行管理。如果一个连接从一种事务模式改变到另一种，那么它对任何其它连接的事务模式没有影响。

结束事务

可以使用 COMMIT 或 ROLLBACK 语句结束事务。

COMMIT

如果事务成功，则提交。COMMIT 语句保证事务的所有修改在数据库中都永久有效。COMMIT 语句还释放资源，如事务使用的锁。

ROLLBACK

如果事务中出现错误，或者用户决定取消事务，可回滚该事务。ROLLBACK 语句通过将数据返回到它在事务开始时所处的状态，来恢复在该事务中所作的所有修改。ROLLBACK 还会释放由事务占用的资源。

指定事务边界

可以用 Transact-SQL 语句或 API 函数和方法确定 SQL Server 事务启动和结束的时间。

Transact-SQL 语句

使用 BEGIN TRANSACTION、COMMIT TRANSACTION、COMMIT WORK、ROLLBACK TRANSACTION、ROLLBACK WORK 和 SET IMPLICIT_TRANSACTIONS 语句来描述事务。这些语句主要在 DB-Library 应用程序和 Transact-SQL 脚本（如使用 osql 命令提示实用工具运行的脚本）中使用。

API 函数和方法

数据库 API（如 ODBC、OLE DB 和 ADO）包含用来描述事务的函数和方法。它们是 SQL Server 应用程序中用来控制事务的主要机制。

每个事务都必须只由其中的一种方法管理。在同一事务中使用两种方法可能导致不确定的结果。例如，不应先使用 ODBC API 函数启动一个事务，再使用 Transact-SQL COMMIT 语句完成该事务。这样将无法通知 SQL Server ODBC 驱动程序该事务已被提交。在这种情况下，应使用 ODBC SQLEndTran 函数结束该事务。

事务处理过程中的错误

如果服务器错误使事务无法成功完成，SQL Server 将自动回滚该事务，并释放该事务占用的所有资源。如果客户端与 SQL Server 的网络连接中断了，那么当网络告知 SQL Server 该中断时，将回滚该连接的所有未完成事务。如果客户端应用程序失败或客户计算机崩溃或重启，也会中断该连接，而且当网络告知 SQL Server 该中断时，也会回滚所有未完成的连接。如果客户从该应用程序注销，所有未完成的事务也会被回滚。

如果批处理中出现运行时语句错误（如违反约束），那么 SQL Server 中默认的行为将是只回滚产生该错误的语句。可以使用 SET XACT_ABORT 语句改变该行为。在 SET XACT_ABORT ON 语句执行之后，任何运行时语句错误都将导致当前事务自动回滚。编译错误（如语法错误）不受 SET XACT_ABORT 的影响。

如果出现运行时错误或编译错误，那么程序员应该编写应用程序代码以便指定正确的操作（COMMIT 或 ROLLBACK）。

请参见

BEGIN TRANSACTION

ROLLBACK TRANSACTION

COMMIT TRANSACTION

ROLLBACK WORK

COMMIT WORK

SET IMPLICIT_TRANSACTIONS

在 ADO 中执行事务

事务

执行事务 (ODBC)

SET XACT_ABORT


显式事务

显式事务可以显式地在其中定义事务的启动和结束。在 Microsoft® SQL Server™ 的早期版本中，显式事务也称为用户定义或用户指定的事务。

DB-Library 应用程序和 Transact-SQL 脚本使用 BEGIN TRANSACTION、COMMIT TRANSACTION、COMMIT WORK、ROLLBACK TRANSACTION 或 ROLLBACK WORK Transact-SQL 语句定义显式事务。

BEGIN TRANSACTION

为连接标记显式事务的起始点。

COMMIT TRANSACTION 或 COMMIT WORK

如果没有遇到错误，可使用该语句成功地结束事务。该事务中的所有数据修改在数据库中都将永久有效。事务占用的资源将被释放。

ROLLBACK TRANSACTION 或 ROLLBACK WORK

用来清除遇到错误的事务。该事务修改的所有数据都返回到事务开始时的状态。事务占用的资源将被释放。

还可以在 OLE DB 中使用显式事务。调用 ITransactionLocal::StartTransaction 方法可启动事务。将 fRetaining 设置为 FALSE，则调用 ITransaction::Commit 或 ITransaction::Abort 方法结束事务时，不会自动启动另一事务。

在 ADO 中，对 Connection 对象使用 BeginTrans 方法可启动隐性事务。若要结束该事务，可调用该 Connection 对象的 CommitTrans 或 RollbackTrans 方法。

ODBC API 不支持显式事务，只支持自动提交和隐性事务。

显式事务模式持续的时间只限于该事务的持续期。当事务结束时，连接将返回到启动显式事务前所处的事务模式，或者是隐性模式，或者是自动提交模式。

请参见

BEGIN TRANSACTION

ROLLBACK TRANSACTION

COMMIT TRANSACTION

ROLLBACK WORK

COMMIT WORK

在 ADO 中执行事务

支持本地事务


自动提交事务

自动提交模式是 Microsoft® SQL Server™ 的默认事务管理模式。每个 Transact-SQL 语句在完成时，都被提交或回滚。如果一个语句成功地完成，则提交该语句；如果遇到错误，则回滚该语句。只要自动提交模式没有被显式或隐性事务替代，SQL Server 连接就以该默认模式进行操作。自动提交模式也是 ADO、OLE DB、ODBC 和 DB-Library 的默认模式。

SQL Server 连接在 BEGIN TRANSACTION 语句启动显式事务，或隐性事务模式设置为打开之前，将以自动提交模式进行操作。当提交或回滚显式事务，或者关闭隐性事务模式时，SQL Server 将返回到自动提交模式。

编译和运行时错误

在自动提交模式下，有时看起来 SQL Server 好像回滚了整个批处理，而不是仅仅一个 SQL 语句。这种情况只有在遇到的错误是编译错误而不是运行时错误时才会发生。编译错误将阻止 SQL Server 建立执行计划，这样批处理中的任何语句都不会执行。尽管看起来好像是产生错误之前的所有语句都被回滚了，但实际情况是该错误使批处理中的任何语句都没有执行。在此例中，由于编译错误，第三个批处理中的任何 INSERT 语句都没有执行。但看上去好像是前两个 INSERT 语句没有执行便进行了回滚。

USE pubs
GO
CREATE TABLE TestBatch (Cola INT PRIMARY KEY, Colb CHAR(3))
GO
INSERT INTO TestBatch VALUES (1, 'aaa')
INSERT INTO TestBatch VALUES (2, 'bbb')
INSERT INTO TestBatch VALUSE (3, 'ccc')  /* Syntax error */
GO
SELECT * FROM TestBatch   /* Returns no rows */
GO

在下面的示例中，第三个 INSERT 语句产生运行时重复键错误。由于前两个 INSERT 语句成功地执行并且提交，因此它们在运行时错误之后被保留下来。

USE pubs
GO
CREATE TABLE TestBatch (Cola INT PRIMARY KEY, Colb CHAR(3))
GO
INSERT INTO TestBatch VALUES (1, 'aaa')
INSERT INTO TestBatch VALUES (2, 'bbb')
INSERT INTO TestBatch VALUES (1, 'ccc')  /* Duplicate key error */
GO
SELECT * FROM TestBatch   /* Returns rows 1 and 2 */
GO

SQL Server 使用延迟的名称解析，其中对象名直到执行时才被解析。在下面的示例中，前两个 INSERT 语句执行并提交，当第三个 INSERT 语句由于引用了一个并不存在的表而产生运行时错误之后，前两行将仍然保留在 TestBatch 表中。

USE pubs
GO
CREATE TABLE TestBatch (Cola INT PRIMARY KEY, Colb CHAR(3))
GO
INSERT INTO TestBatch VALUES (1, 'aaa')
INSERT INTO TestBatch VALUES (2, 'bbb')
INSERT INTO TestBch VALUES (3, 'ccc')  /* Table name error */
GO
SELECT * FROM TestBatch   /* Returns rows 1 and 2 */
GO

请参见

事务

ODBC 中的事务


隐性事务

当连接以隐性事务模式进行操作时，Microsoft® SQL Server™ 将在提交或回滚当前事务后自动启动新事务。无须描述事务的开始，只需提交或回滚每个事务。隐性事务模式生成连续的事务链。

在为连接将隐性事务模式设置为打开之后，当 SQL Server 首次执行下列任何语句时，都会自动启动一个事务：

ALTER TABLE | INSERT
CREATE | OPEN
DELETE | REVOKE
DROP | SELECT
FETCH | TRUNCATE TABLE
GRANT | UPDATE

在发出 COMMIT 或 ROLLBACK 语句之前，该事务将一直保持有效。在第一个事务被提交或回滚之后，下次当连接执行这些语句中的任何语句时，SQL Server 都将自动启动一个新事务。SQL Server 将不断地生成一个隐性事务链，直到隐性事务模式关闭为止。

隐性事务模式可以通过使用 Transact-SQL SET 语句，或通过数据库 API 函数和方法进行设置。


Transact-SQL 隐性事务

DB-Library 应用程序和 Transact-SQL 脚本使用 Transact-SQL SET IMPLICIT_TRANSACTIONS ON 语句启动隐性事务模式。使用 SET IMPLICIT_TRANSACTIONS OFF 语句关闭隐性事务模式。使用 COMMIT TRANSACTION、COMMIT WORK、ROLLBACK TRANSACTION 或 ROLLBACK WORK 语句结束每个事务。

SET QUOTED_IDENTIFIER OFF
GO
SET NOCOUNT OFF
GO
USE pubs
GO
CREATE TABLE ImplicitTran (Cola int PRIMARY KEY,
         Colb char(3) NOT NULL)
GO
SET IMPLICIT_TRANSACTIONS ON
GO
/* First implicit transaction started by an INSERT statement */
INSERT INTO ImplicitTran VALUES (1, 'aaa')
GO
INSERT INTO ImplicitTran VALUES (2, 'bbb')
GO
/* Commit first transaction */
COMMIT TRANSACTION
GO
/* Second implicit transaction started by a SELECT statement */
SELECT COUNT(*) FROM ImplicitTran
GO
INSERT INTO ImplicitTran VALUES (3, 'ccc')
GO
SELECT * FROM ImplicitTran
GO
/* Commit second transaction */
COMMIT TRANSACTION
GO
SET IMPLICIT_TRANSACTIONS OFF
GO

请参见

COMMIT TRANSACTION

ROLLBACK WORK

COMMIT WORK

SET IMPLICIT_TRANSACTIONS

ROLLBACK TRANSACTION

BEGIN TRANSACTION


API 隐性事务

用来设置隐性事务的 API 机制是 ODBC 和 OLE DB。

ODBC

  • 调用 SQLSetConnectAttr 函数启动隐性事务模式，其中 Attribute 设置为 SQL_ATTR_AUTOCOMMIT，ValuePtr 设置为 SQL_AUTOCOMMIT_OFF。

  • 在调用 SQLSetConnectAttr 之前，连接将一直保持为隐性事务模式。其中 Attribute 设置为 SQL_ATTR_AUTOCOMMIT，ValuePtr 设置为 SQL_AUTOCOMMIT_ON。

  • 调用 SQLEndTran 函数提交或回滚每个事务，其中 CompletionType 设置为 SQL_COMMIT 或 SQL_ROLLBACK。

  • 当 SQL_AUTOCOMMIT_OFF 由 ODBC 应用程序设置时，Microsoft® SQL Server™ ODBC 驱动程序发出 SET IMPLICIT_TRANSACTION ON 语句。

OLE DB

OLE DB 没有专门用来设置隐性事务模式的方法。

  • 调用 ITransactionLocal::StartTransaction 方法启动显式模式。

  • 当调用 ITransaction::Commit 或 ITransaction::Abort 方法（其中 fRetaining 设置为 TRUE）时，OLE DB 将完成当前的事务并进入隐性事务模式。只要将 ITransaction::Commit 或 ITransaction::Abort 中的 fRetaining 设置为 TRUE，那么连接就将保持隐性事务模式。

  • 调用 ITransaction::Commit 或 ITransaction::Abort（其中 fRetaining 设置为 FALSE）停止隐性事务模式。

ADO

ADO 不支持隐性事务。ADO 应用程序使用自动提交模式或显式模式。

请参见

事务

执行事务


分布式事务

分布式事务跨越两个或多个称为资源管理器的服务器。被称为事务管理器的服务器组件必须在资源管理器之间协调事务管理。如果分布式事务由 Microsoft 分布式事务处理协调器 (MS DTC)）这类事务管理器或其它支持 X/Open XA 分布式事务处理规范的事务管理器进行协调，则 Microsoft® SQL Server™ 可以作为资源管理器运行。有关更多信息，请参见 Microsoft 分布式事务处理协调器文档。

实际上跨越两个或多个数据库的单个 SQL Server 中的事务就是分布式事务。但是，SQL Server 对分布式事务进行内部管理；对于用户而言，其操作就像本地事务一样。

对于应用程序，管理分布式事务很像管理本地事务。事务结束时，应用程序请求提交或回滚事务。不同的是，分布式提交必须由事务管理器管理，以尽量避免出现因网络故障而导致一个事务由某些资源管理器成功提交，但由另一些资源管理器回滚的情况。通过分两个阶段（准备阶段和提交阶段）管理提交进程可避免这种情况，这称为两阶段提交 (2PC)。

准备阶段

当事务管理器收到提交请求时，它给该事务所涉及的所有资源管理器发送一个准备命令。然后，每个资源管理器将尽力使该事务持久，并且所有保存该事务日志映象的缓冲区将被刷新到磁盘中。当每个资源管理器完成准备阶段时，它会向事务管理器返回准备成功或准备失败的消息。

提交阶段

如果事务管理器收到所有资源管理器发来的准备成功消息，它将给每个资源管理器发送提交命令。然后资源管理器就可以完成提交。如果所有资源管理器都报告提交成功，那么事务管理器则向应用程序发送一个成功提示。如果有资源管理器报告准备失败，那么事务管理器将给每个资源管理器发送一个回滚命令，并向应用程序表示提交失败。

SQL Server 应用程序可以通过 Transact-SQL 或数据库 API 管理分布式事务。


Transact-SQL 分布式事务

在 Transact-SQL 中启动的分布式事务的结构相对比较简单：

  1. Transact-SQL 脚本或应用程序连接执行启动分布式事务的 Transact-SQL 语句。

  2. 执行该语句的 Microsoft® SQL Server™ 成为事务中的主控服务器。

  3. 然后脚本或应用程序对链接的服务器执行分布式查询，或对远程服务器执行远程存储过程。

  4. 当执行了分布式查询或远程过程调用后，主控服务器将自动调用 MS DTC 以便登记分布式事务中链接的服务器和远程服务器。

  5. 当脚本或应用程序发出 COMMIT 或 ROLLBACK 语句时，主控 SQL Server 将调用 MS DTC 管理两阶段提交过程，或者通知链接的服务器和远程服务器回滚其事务。
需要的 Transact-SQL 语句

控制分布式事务的 Transact-SQL 语句很少，因为多数工作都由 Microsoft® SQL Server™ 和 MS DTC 在内部完成。Transact-SQL 脚本或应用程序中所需的 Transact-SQL 语句只须：

  • 启动分布式事务。

  • 对链接的服务器执行分布式查询，或对远程服务器执行远程过程调用。

  • 调用标准 Transact-SQL COMMIT TRANSACTION、COMMIT WORK、ROLLBACK TRANSACTION 或 ROLLBACK WORK 语句完成事务。

    对于任意一个 Transact-SQL 分布式事务，处理 Transact-SQL 脚本或连接的 SQL Server 将自动调用 MS DTC 以协调事务的提交或回滚。

启动分布式事务

可以用下列方法在 Transact-SQL 中启动分布式事务：

  • 使用 BEGIN DISTRIBUTED TRANSACTION 语句启动显式分布式事务。

    还可以对链接的服务器执行分布式查询。所连接的 SQL Server 将调用 MS DTC，通过链接的服务器管理分布式事务。还可以将远程 SQL Server 上的远程存储过程作为分布式事务的一部分进行调用。

  • 当在本地事务中时，执行分布式查询。

    如果 OLE DB 数据源支持 ITransactionJoin 接口，则即使查询是只读查询，该事务也将升级为分布式事务。如果数据源不支持 ItransactionJoin，则只允许只读语句。

  • 如果已经执行了 SET REMOTE_PROC_TRANSACTIONS ON，而且本地事务调用了另一个 SQL Server 上的远程存储过程，那么该本地事务将升级为分布式事务。

    SQL Server 使用 MS DTC 与远程服务器协调该事务。

    • 如果将 REMOTE_PROC_TRANSACTIONS 设置为 OFF，则对远程存储过程的调用在本地事务的作用域之外执行。如果回滚本地事务，则不回滚远程过程所完成的工作。远程存储过程所作的工作在过程完成时提交,而不是在提交本地事务时提交。

REMOTE_PROC_TRANSACTIONS 选项是一个兼容性选项，只影响对使用 sp_addserver 定义的远程服务器所进行的远程存储过程调用。有关远程存储过程的更多信息，请参见远程存储过程构架。该选项不适用于在使用 sp_addlinkedserver 定义的链接服务器上执行存储过程的分布式查询。有关分布式查询的更多信息，请参见分布式查询。

请参见

BEGIN DISTRIBUTED TRANSACTION

ROLLBACK TRANSACTION

COMMIT TRANSACTION

ROLLBACK WORK

COMMIT WORK

SET REMOTE_PROC_TRANSACTIONS


MS DTC 分布式事务

用 OLE DB、ODBC、ADO 或 DB-Library 编写的应用程序使用 Transact-SQL 分布式事务的方法可以是，发出 Transact-SQL 语句启动和停止 Transact-SQL 分布式事务。但是，OLE DB 和 ODBC 还包含在 API 层对管理分布式事务的支持。OLE DB 和 ODBC 应用程序可以使用这些 API 函数管理包括其它 COM 资源管理器（支持 MS DTC 事务而非 Microsoft® SQL Server™）的分布式事务。它们也可以使用 API 函数获取对包括多个 SQL Server 的分布式事务边界的更多控制。

ODBC 分布式事务

通过将连接特性 SQL_ATTR_AUTOCOMMIT 设置为 SQL_AUTOCOMMIT_OFF，然后调用 ODBC SQLEndTran 函数提交或回滚每个事务，可以控制 ODBC API 层的本地事务。不要使用这些函数管理 ODBC 应用程序中的分布式事务。而应该使用 MS DTC COM 方法：

  • 调用 DtcGetTransactionManager 连接到 MS DTC。

  • 调用 ITransactionDispenser::BeginTransaction 启动分布式事务并获取事务对象。

  • 对每个参与分布式事务的 ODBC 连接，调用 ODBC 函数 SQLSetConnectAttr，其中 fOption 设置为 SQL_COPT_SS_ENLIST_IN_DTC，vParam 控制来自 ITransactionDispenser::BeginTransaction 的事务对象的地址。

  • 当事务完成时，对于从 ITransactionDispenser::BeginTransaction 获得的事务对象，不要调用 ODBC SQLEndTran 函数，应该调用 ITransaction::Commit 或 ITransaction::Rollback 方法。
OLE DB 分布式事务

控制 OLE DB 中的分布式事务的方法与控制本地事务的方法相似。若要控制本地事务，则 OLE DB 的使用者应该：

  • 使用 ITransactionLocal::StartTransaction 方法启动本地事务，并获得事务对象。

  • 然后使用者在从 ITransactionLocal::StartTransaction 获得的事务对象上，调用 ITransaction::Commit 或 ITransaction::Rollback 方法。

若要控制分布式事务，使用者应该：

  • 调用 DtcGetTransactionManager 连接到 MS DTC。

  • 调用 ITransactionDispenser::BeginTransaction 启动分布式事务，并获得事务对象。

  • 对每个参与分布式事务的连接，调用分布式事务对象的 ITransactionJoin 接口。

  • 调用分布式事务对象的 ITransaction::Commit 或 ITransaction::Rollback 方法，完成该事务。

请参见

支持分布式事务

执行分布式事务


分布式查询和分布式事务

Microsoft® SQL Server™ 允许创建与称为链接服务器的 OLE DB 数据源的链接。在链接到 OLE DB 数据源之后，可以：

  • 从 OLE DB 数据源引用行集，作为 Transact-SQL 语句中的表。

  • 将命令传递给 OLE DB 数据源，并包含结果行集，作为 Transact-SQL 语句中的表。

每个分布式查询都可以引用多个链接的服务器，而且可以对每个链接的服务器分别执行更新或读取操作。单个分布式查询可以对某些链接的服务器执行读取操作，并且对其它链接的服务器执行更新操作。通常情况下，每当某个事务可能更新多个链接服务器中的数据时，Microsoft SQL Server 都要求相应的 OLE DB 提供程序支持分布式事务。因此，链接服务器上所支持的查询类型取决于 OLE DB 提供程序中对事务的支持级别。OLE DB 为事务管理定义了两个可选的接口：

  • ITransactionLocal 支持 OLE DB 数据源中的本地事务。

  • ITransactionJoin 允许提供程序联结包含其它资源管理器的分布式事务。

    所有支持 ITransactionJoin 的提供程序也都支持 ITransactionLocal。

如果在连接是自动提交模式时执行分布式查询，则应用以下规则：

  • 对于不支持 ItransactionLocal 的提供程序，只允许执行读取操作。

  • 对于支持 ITransactionLocal 的提供程序，允许执行所有更新操作。

    主控 SQL Server 会自动调用每个参与更新操作的链接的服务器中的 ITransactionLocal，以启动本地事务，并在语句执行成功时提交或在语句执行失败时回滚。

如果分布式查询是针对分布式分区视图或者是在连接为显式或隐性事务时执行，则应用下列规则：

  • 对于不支持 ITransactionJoin 的提供程序，只允许执行读取操作。不支持任何事务或只支持 ITransactionLocal 的提供程序不能参与更新操作。

  • 如果 SET XACT_ABORT 设置为 ON，则对于支持 ITransactionJoin 的任意提供程序都允许执行所有的更新操作。主控 SQL Server 会自动调用每个参与更新操作的链接服务器中的 ITransactionJoin，以便在分布式事务中登记该服务器。然后当主控服务器表示要提交或回滚事务时，MS DTC 将提交或者回滚。

  • 如果 SET XACT_ABORT 设置为 OFF，则链接服务器还必须支持嵌套事务，才能对其执行更新操作。当会话已经有一个现有事务时，如果提供程序支持调用 ITransactionLocal::StartTransaction，则支持嵌套事务。这使 SQL Server 得以回滚分布式查询中的单个语句，而不是回滚整个事务。

上述规则意味着提供程序的下列限制不支持嵌套事务：仅在 XACT_ABORT 选项设置为 ON 时，分布式事务中才允许更新操作。

请参见

分布式查询


高级主题

事务管理不善常常导致具有许多用户的系统中出现连接和性能问题。随着系统中用户数量的增加，拥有能够高效地使用事务的应用程序也变得更为重要。Microsoft® SQL Server™ 还支持嵌套事务、事务保存点和绑定事务，这为程序员编写高效事务提供了其它选择。


嵌套事务

显式事务可以嵌套。这主要是为了支持存储过程中的一些事务，这些事务可以从事务中已有的进程中调用，也可以从没有活动事务的进程中调用。

下面的示例显示了嵌套事务的用途。TransProc 过程强制执行其事务，而不管执行该事务的进程的事务模式如何。如果在事务活动时调用 TransProc，则 TransProc 中的嵌套事务几乎被忽略，而且其 INSERT 语句将根据对外部事务采取的最终操作提交或回滚。如果不含有未完成事务的进程执行 TransProc，则在该过程结束时，COMMIT TRANSACTION 将有效地提交 INSERT 语句。

SET QUOTED_IDENTIFIER OFF
GO
SET NOCOUNT OFF
GO
USE pubs
GO
CREATE TABLE TestTrans(Cola INT PRIMARY KEY,
               Colb CHAR(3) NOT NULL)
GO
CREATE PROCEDURE TransProc @PriKey INT, @CharCol CHAR(3) AS
BEGIN TRANSACTION InProc
INSERT INTO TestTrans VALUES (@PriKey, @CharCol)
INSERT INTO TestTrans VALUES (@PriKey + 1, @CharCol)
COMMIT TRANSACTION InProc
GO
/* Start a transaction and execute TransProc */
BEGIN TRANSACTION OutOfProc
GO
EXEC TransProc 1, 'aaa'
GO
/* Roll back the outer transaction, this will
   roll back TransProc's nested transaction */
ROLLBACK TRANSACTION OutOfProc
GO
EXECUTE TransProc 3,'bbb'
GO
/* The following SELECT statement shows only rows 3 and 4 are
   still in the table. This indicates that the commit
   of the inner transaction from the first EXECUTE statement of
   TransProc was overridden by the subsequent rollback. */
SELECT * FROM TestTrans
GO

Microsoft® SQL Server™ 忽略提交内部事务。根据最外部事务结束时采取的操作，将提交或者回滚事务。如果提交外部事务，则内层嵌套的事务也会提交。如果回滚外部事务，则不论此前是否单独提交过内层事务，所有内层事务都将回滚。

对 COMMIT TRANSACTION 或 COMMIT WORK 的每个调用都应用于最后执行的 BEGIN TRANSACTION。如果嵌套 BEGIN TRANSACTION 语句，那么 COMMIT 语句只应用于最后一个嵌套的事务，也就是在最内层的事务。即使嵌套事务内部的 COMMIT TRANSACTION transaction_name 语句引用外部事务的事务名，该提交也只应用于最内层的事务。

ROLLBACK TRANSACTION 语句的 transaction_name 参数引用一组命名的嵌套事务的内层事务是非法的，transaction_name 只能引用最外部事务的事务名。如果在一组嵌套事务的任意级别执行使用外部事务名称的 ROLLBACK TRANSACTION transaction_name 语句，那么所有的嵌套事务都将回滚。如果在一组嵌套事务的任意级别执行没有 transaction_name 参数的 ROLLBACK WORK 或 ROLLBACK TRANSACTION 语句，那么它将回滚所有嵌套事务，包括最外部事务。

@@TRANCOUNT 函数记录当前事务的嵌套级。每个 BEGIN TRANSACTION 语句使 @@TRANCOUNT 加 1。每个 COMMIT TRANSACTION 或 COMMIT WORK 语句使 @@TRANCOUNT 减 1。没有事务名的 ROLLBACK WORK 或 ROLLBACK TRANSACTION 语句将回滚所有嵌套事务，并使 @@TRANCOUNT 减小到 0。使用一组嵌套事务中最外部事务的事务名称的 ROLLBACK TRANSACTION 将回滚所有嵌套事务，并使 @@TRANCOUNT 减到 0。在无法确定是否已经在事务中时，可以用 SELECT @@TRANCOUNT 语句确定 @@TRANCOUNT 是 1 还是更大。如果 @@TRANCOUNT 是 0，则表明不在事务中。

请参见

@@TRANCOUNT

COMMIT WORK

BEGIN TRANSACTION

ROLLBACK TRANSACTION

COMMIT TRANSACTION

ROLLBACK WORK


事务保存点

保存点提供了一种机制，用于回滚部分事务。可以使用 SAVE TRANSACTION savepoint_name 语句创建一个保存点，然后再执行 ROLLBACK TRANSACTION savepoint_name 语句回滚到该保存点，从而无须回滚到事务的开始。

在不可能发生错误的情况下，保存点很有用。在很少出现错误的情况下使用保存点回滚部分事务，比让每个事务在更新之前测试更新的有效性更为有效。更新和回滚操作代价很大，因此只有在遇到错误的可能性很小，而且预先检查更新的有效性的代价相对很高的情况下，使用保存点才会非常有效。

下面的示例显示保存点在一个订购系统中的使用情况。该系统中存货不足的可能性很小，因为该公司具备有效的供应商和再订购点。通常应用程序在尝试更新订购记录时，会先验证手边是否有足够的存货。该示例假定由于某种原因（如连接到一个低速的调制解调器或广域网上），先验证可用存货的数量代价相对较大。可将应用程序编写为只进行更新，而且如果收到错误信息，表明库存不足时，将回滚该更新。在这种情况下，在插入之后快速检查 @@ERROR 比在更新之前验证库存数量速度要快得多。

InvCtrl 表有一个 CHECK 约束，如果 QtyInStk 列低于 0，就会触发 547 号错误。OrderStock 过程创建一个保存点。如果出现 547 错误，它将回滚到该保存点，并将当前手边有的项目数返回给调用进程。然后调用进程可以决定是否需要针对手边现有的数量重新下订单。如果 OrderStock 返回一个 0，则调用进程知道当前有足够的存货，可以满足订购需要。

SET NOCOUNT OFF
GO
USE pubs
GO
CREATE TABLE InvCtrl
      (WhrhousID      int,
      PartNmbr      int,
      QtyInStk      int,
      ReordrPt      int,
      CONSTRAINT InvPK PRIMARY KEY
      (WhrhousID, PartNmbr),
      CONSTRAINT QtyStkCheck CHECK (QtyInStk > 0) )
GO
CREATE PROCEDURE OrderStock @WhrhousID int, @PartNmbr int,
            @OrderQty int
AS
DECLARE @ErrorVar int
SAVE TRANSACTION StkOrdTrn
UPDATE InvCtrl SET QtyInStk = QtyInStk - @OrderQty
WHERE WhrhousID = 1
   AND PartNmbr = 1
SELECT @ErrorVar = @@error
IF (@ErrorVar = 547)
BEGIN
   ROLLBACK TRANSACTION StkOrdTrn
   RETURN (SELECT QtyInStk
          FROM InvCtrl
          WHERE WhrhousID = @WhrhousID
           AND PartNmbr = @PartNmbr)
END
ELSE
   RETURN 0
GO

请参见

ROLLBACK TRANSACTION

SAVE TRANSACTION


使用绑定连接

绑定连接允许两个或多个连接共享同一个事务和锁定。绑定连接可以对同一个数据进行操作，而不会有锁定冲突。绑定连接可以从同一个应用程序内的多个连接中创建，也可以从使用不同连接的多个应用程序中创建。绑定连接使得协调多个连接上的操作更加容易。

若要加入到绑定连接中，连接必须调用 sp_getbindtoken 或 srv_getbindtoken（开放数据服务），以获得一个绑定令牌。绑定令牌是一个字符串，它唯一地标识每个绑定事务。然后绑定令牌将发送给加入到绑定连接的其它连接。其它连接通过调用 sp_bindsession，并使用从第一个连接中接收到的绑定令牌绑定到事务上。

必须从创建第一个连接的应用程序代码将绑定令牌传送到创建随后每个绑定连接的应用程序代码中。应用程序不能使用 Transact-SQL 语句或 API 函数获取由另一个进程启动的事务绑定令牌。可以用来传送绑定令牌的方法有：

  • 如果所有连接都是从同一个应用程序进程创建而来，那么绑定令牌可以存储在全局内存中，也可以作为参数传递到函数中。

  • 如果连接是从不同的应用程序进程创建而来，那么绑定令牌可以使用进程间通讯 (IPC)，如远程过程调用 (RPC) 或动态数据交换 (DDE)来传输。

  • 在 Microsoft® SQL Server™ 中可以将绑定令牌存储在某个表中，该表应能够被要绑定到第一个连接的进程读取。

在一组绑定连接中，任意时刻只能有一个连接是活动的。如果一个连接正在服务器上执行一个语句，或者包含由服务器挂起的结果，那么共享同一事务空间的其它连接都不能访问该服务器，直到当前的连接完成处理或取消了当前的语句为止。如果服务器很忙，那么就会出现错误，表明事务空间正在使用，该连接应该稍后再试。

绑定连接的类型

有两种绑定连接类型：本地和分布式。

  • 本地绑定连接

    允许绑定连接共享单个服务器上的单个事务的事务空间。

  • 分布式绑定连接

    允许绑定连接在用 Microsoft 分布式事务处理协调器 (MS DTC) 提交或回滚整个事务之前，共享跨越两个或多个服务器的同一事务。

分布式绑定连接的标识不是用字符串绑定令牌，而是用分布式事务标识号。如果本地事务中涉及到绑定连接，而且该连接使用 SET REMOTE_PROC_TRANSACTIONS ON 执行远程服务器上的 RPC，则 MS DTC 将该本地绑定事务自动升级到分布式绑定事务，并且 MS DTC 会话也会启动。

何时使用绑定连接

绑定连接在开发必须执行 Transact-SQL 语句的扩展存储过程方面很有用，这些 Transact-SQL 语句代表调用这些绑定连接的进程。让调用进程在绑定令牌中作为扩展存储过程的一个参数进行传递，可使该过程加入到调用进程的事务空间中，从而将扩展存储过程与该调用进程结合在一起。

绑定连接可以用来开发三层应用程序，其中，商业逻辑用单独的程序表示，而这些程序在单个商业事务上协同工作。

下面的绑定连接示例阐明了两个连接如何能够访问同一个事务。某个顾客决定在本地的一个百货商店购买产品。售货员访问销售事务系统，在销售事务表中插入一行，其中包括信用卡授权号码。对同一服务器建立两个连接，连接 C1 和连接 C2。C1 开始一个事务，将产品销售行添加到销售表中。必须将信用卡授权号码添加到新的销售事务行中。在信用卡授权进程中，扩展存储过程创建连接 C2，通过电话线拨出到信用卡公司并使用信用卡授权号码修改销售事务行。只有使用绑定连接，才能实现两个连接访问同一行而不出现锁定冲突。

请参见

sp_bindsession

srv_getbindtoken

sp_getbindtoken

SET REMOTE_PROC_TRANSACTIONS


调整事务隔离级别

隔离属性是 ACID 的四个属性之一，逻辑工作单元必须具备这四个属性才能称为事务。该属性能够使事务免受其它并发事务所执行的更新的影响。每个事务的隔离级别实际上都是可以自定义的。

Microsoft® SQL Server™ 支持 SQL-92 中定义的事务隔离级别。设置事务隔离级别虽然使程序员承担了某些完整性问题所带来的风险，但可以换取对数据更大的并发访问权。与以前的隔离级别相比，每个隔离级别都提供了更大的隔离性，但这是通过在更长的时间内占用更多限制锁换来的。事务隔离级别有：

  • READ UNCOMMITTED

  • READ COMMITTED

  • REPEATABLE READ

  • SERIALIZABLE

可以使用 Transact-SQL 或通过数据库 API 来设置事务隔离级别：

Transact-SQL

Transact-SQL 脚本和 DB-Library 应用程序使用 SET TRANSACTION ISOLATION LEVEL 语句。

ADO

ADO 应用程序将 Connection 对象的 IsolationLevel 属性设置为 adXactReadUncommitted、adXactReadCommitted、adXactRepeatableRead 或 adXactReadSerializable。

OLE DB

OLE DB 应用程序调用 ITransactionLocal::StartTransaction，其中 isoLevel 设置为 ISOLATIONLEVEL_READUNCOMMITTED、ISOLATIONLEVEL_READCOMMITTED、ISOLATIONLEVEL_REPEATABLEREAD 或 ISOLATIONLEVEL_SERIALIZABLE。

ODBC

ODBC 应用程序调用 SQLSetConnectAttr，其中 Attribute 设置为 SQL_ATTR_TXN_ISOLATION，ValuePtr 设置为 SQL_TXN_READ_UNCOMMITTED、SQL_TXN_READ_COMMITTED、SQL_TXN_REPEATABLE_READ 或 SQL_TXN_SERIALIZABLE。

请参见

SQLOLEDB 中的隔离级别

SET TRANSACTION ISOLATION LEVEL


存储过程和触发器中回滚

如果 @@TRANCOUNT 的值在存储过程完成时与过程执行时不同，则会生成一个 266 信息类错误。该错误不是由触发器中同一个条件生成的。

当调用存储过程时，如果 @@TRANCOUNT 为 1 或更大，并且该过程执行 ROLLBACK TRANSACTION 或 ROLLBACK WORK 语句，则会产生 266 号错误。这是因为 ROLLBACK 回滚所有未完成的事务，并将 @@TRANCOUNT 减到 0，该值比调用过程时要小。

如果在触发器中发出 ROLLBACK TRANSACTION：

  • 对当前事务中的那一点所做的所有数据修改都将回滚，包括触发器所做的修改。

  • 触发器继续执行 ROLLBACK 语句之后的所有其余语句。如果这些语句中的任意语句修改数据，则不回滚这些修改。执行其余的语句不会激发嵌套触发器。

  • 在批处理中，所有位于激发触发器的语句之后的语句都不被执行。

  • 触发器中的 ROLLBACK 关闭并释放所有在包含激发触发器的语句的批处理中声明和打开的游标。这其中包括了在激发触发器的批处理所调用的存储过程中声明和打开的游标。在激发触发器的批处理之前的批处理中所声明的游标将只是关闭，但是在以下条件下，STATIC 或 INSENSITIVE 游标不关闭：
    • CURSOR_CLOSE_ON_COMMIT 设置为OFF。

    • 静态游标要么是同步游标，要么是完全填充的异步游标。

当执行触发器时，触发器的操作总是好像有一个未完成的事务在起作用。如果激发触发器的语句是在隐性或显式事务中，则肯定会这样。在自动提交模式下，也是如此。当语句开始以自动提交模式执行时，如果遇到错误，则会有隐含的 BEGIN TRANSACTION 语句允许恢复该语句生成的所有修改。该隐含的事务对批处理中的其它语句没有影响，因为当语句完成时，该事务要么提交，要么回滚。但是，当调用触发器时，该隐含的事务将仍然有效。

这意味着，只要触发器中发出 BEGIN TRANSACTION 语句，则实际上就开始了一个嵌套事务。因为当回滚嵌套事务时，嵌套的 BEGIN TRANSACTION 语句将被忽略，触发器中发出的 ROLLBACK TRANSACTION 总是回滚过去该触发器本身发出的所有 BEGIN TRANSACTION 语句。ROLLBACK 回滚到最外部的 BEGIN TRANSACTION。

若要在触发器中进行部分回滚，则即使总是以自动提交模式进行调用，也必须使用 SAVE TRANSACTION 语句。以下的触发器阐明了这一点：

CREATE TRIGGER TestTrig ON TestTab FOR UPDATE AS
SAVE TRANSACTION MyName
INSERT INTO TestAudit
   SELECT * FROM inserted
IF (@@error <> 0)
BEGIN
  ROLLBACK TRANSACTION MyName
END

这也影响触发器中 BEGIN TRANSACTION 语句后面的COMMIT TRANSACTION 语句。因为 BEGIN TRANSACTION 启动一个嵌套事务，而随后的 COMMIT 语句只应用于该嵌套事务。如果在 COMMIT 之后执行 ROLLBACK TRANSACTION 语句，那么 ROLLBACK 将一直回滚到最外部的 BEGIN TRANSACTION。以下的触发器阐明了这一点：

CREATE TRIGGER TestTrig ON TestTab FOR UPDATE AS
BEGIN TRANSACTION
INSERT INTO TrigTarget
   SELECT * FROM inserted
COMMIT TRANSACTION
ROLLBACK TRANSACTION

此触发器绝对不会在 TrigTarget 表中插入。BEGIN TRANSACTION 总是启动一个嵌套事务。COMMIT TRANSACTION 只提交嵌套事务，而下面的 ROLLBACK TRANSACTION 则一直回滚到最外部的 BEGIN TRANSACTION。

请参见

@@TRANCOUNT

ROLLBACK WORK

ROLLBACK TRANSACTION

嵌套事务


事务中允许的 Transact-SQL 语句

在事务中除以下语句不可使用外，其它所有 Transact-SQL 语句均可使用：

ALTER DATABASE | LOAD DATABASE
BACKUP LOG | LOAD TRANSACTION
CREATE DATABASE | RECONFIGURE
DISK INIT | RESTORE DATABASE
DROP DATABASE | RESTORE LOG
DUMP TRANSACTION | UPDATE STATISTICS

另外，不能使用 sp_dboption 设置数据库选项，也不能使用修改用户定义的事务内部的 master 数据库的所有系统过程。


编写有效的事务

尽可能使事务保持简短很重要。启动事务后，DBMS 必须将很多资源控制到事务结束时，以保护事务的 ACID 属性。如果修改数据，则必须用排它锁保护修改过的行，以防止任何其它事务读取该行，并且必须将排它锁控制到提交或回滚事务时为止。根据事务隔离级别设置，SELECT 语句可以获取必须被控制到提交或回滚事务时为止的锁。特别是在有很多用户的系统中，必须尽可能使事务保持简短以减少并发连接间的资源锁定争夺。在有少量用户的系统中，运行时间长、效率低的事务可能不会成为问题，但是在有上千个用户的系统中，将不能忍受这样的事务。

编码指导方针

以下是编写有效事务的指导原则：

  • 不要在事务处理期间要求用户输入。

    在事务启动之前，获得所有需要的用户输入。如果在事务处理期间还需要其它的用户输入，则回滚当前的事务，并在提供了用户输入之后重新启动该事务。即使用户立即响应，作为人，其反应时间也要比计算机慢得多。事务占用的所有资源都要保持很长的时间，这就有可能造成阻塞问题。如果用户没有响应，该事务就会仍保持活动状态，并锁定关键资源，直到他们响应为止，但是用户可能会几分钟甚至几小时都不响应。

  • 在浏览数据时，尽量不要打开事务。

    在所有预备的数据分析完成之前，不应启动事务。

  • 保持事务尽可能地短。

    在知道了必须要进行的修改之后，启动事务，执行修改语句，然后立即提交或回滚。只有在需要时，才打开事务。

  • 灵活地使用更低的事务隔离级别。

    可以很容易地编写出许多使用授权读事务隔离级别的应用程序。并不是所有的事务都要求可串行事务隔离级别。

  • 灵活地使用更低的游标并发选项，如乐观并发选项。

    在很少有可能并发更新的系统中，处理某个偶然的"别人在您读取数据后更改了该数据"错误的开销，要比在读取数据时始终锁定行的开销小得多。

  • 在事务中尽量使访问的数据量最小。

    这样可以减少锁定的行数，从而减少事务之间的争夺。

避免并发问题

为了防止并发问题，应该小心地管理隐性事务。在使用隐性事务时，COMMIT 或 ROLLBACK 之后的下一个 Transact-SQL 语句会自动启动一个新事务。这可能在应用程序浏览数据时，甚至在要求用户输入时，打开新的事务。在完成保护数据修改所需要的最后一个事务之后和再次需要一个事务来保护数据修改之前，关闭隐性事务。该进程使 Microsoft® SQL Server™ 得以在应用程序正在浏览数据并获取来自用户的输入时使用自动提交模式。


锁定

Microsoft® SQL Server™ 2000 使用锁定确保事务完整性和数据库一致性。锁定可以防止用户读取正在由其他用户更改的数据，并可以防止多个用户同时更改相同数据。如果不使用锁定，则数据库中的数据可能在逻辑上不正确，并且对数据的查询可能会产生意想不到的结果。

虽然 SQL Server 自动强制锁定，但可以通过了解锁定并在应用程序中自定义锁定来设计更有效的应用程序。


并发问题

如果没有锁定且多个用户同时访问一个数据库，则当他们的事务同时使用相同的数据时可能会发生问题。并发问题包括：

  • 丢失或覆盖更新。

  • 未确认的相关性（脏读）。

  • 不一致的分析（非重复读）。

  • 幻像读。
丢失更新

当两个或多个事务选择同一行，然后基于最初选定的值更新该行时，会发生丢失更新问题。每个事务都不知道其它事务的存在。最后的更新将重写由其它事务所做的更新，这将导致数据丢失。

例如，两个编辑人员制作了同一文档的电子复本。每个编辑人员独立地更改其复本，然后保存更改后的复本，这样就覆盖了原始文档。最后保存其更改复本的编辑人员覆盖了第一个编辑人员所做的更改。如果在第一个编辑人员完成之后第二个编辑人员才能进行更改，则可以避免该问题。

未确认的相关性（脏读）

当第二个事务选择其它事务正在更新的行时，会发生未确认的相关性问题。第二个事务正在读取的数据还没有确认并且可能由更新此行的事务所更改。

例如，一个编辑人员正在更改电子文档。在更改过程中，另一个编辑人员复制了该文档（该复本包含到目前为止所做的全部更改）并将其分发给预期的用户。此后，第一个编辑人员认为目前所做的更改是错误的，于是删除了所做的编辑并保存了文档。分发给用户的文档包含不再存在的编辑内容，并且这些编辑内容应认为从未存在过。如果在第一个编辑人员确定最终更改前任何人都不能读取更改的文档，则可以避免该问题。

不一致的分析（非重复读）

当第二个事务多次访问同一行而且每次读取不同的数据时，会发生不一致的分析问题。不一致的分析与未确认的相关性类似，因为其它事务也是正在更改第二个事务正在读取的数据。然而，在不一致的分析中，第二个事务读取的数据是由已进行了更改的事务提交的。而且，不一致的分析涉及多次（两次或更多）读取同一行，而且每次信息都由其它事务更改；因而该行被非重复读取。

例如，一个编辑人员两次读取同一文档，但在两次读取之间，作者重写了该文档。当编辑人员第二次读取文档时，文档已更改。原始读取不可重复。如果只有在作者全部完成编写后编辑人员才可以读取文档，则可以避免该问题。

幻像读

当对某行执行插入或删除操作，而该行属于某个事务正在读取的行的范围时，会发生幻像读问题。事务第一次读的行范围显示出其中一行已不复存在于第二次读或后续读中，因为该行已被其它事务删除。同样，由于其它事务的插入操作，事务的第二次或后续读显示有一行已不存在于原始读中。

例如，一个编辑人员更改作者提交的文档，但当生产部门将其更改内容合并到该文档的主复本时，发现作者已将未编辑的新材料添加到该文档中。如果在编辑人员和生产部门完成对原始文档的处理之前，任何人都不能将新材料添加到文档中，则可以避免该问题。


乐观和悲观并发

Microsoft® SQL Server™ 2000 提供了乐观并发控制和悲观并发控制。乐观并发控制使用游标。SQL Server 默认使用悲观并发控制。

乐观并发

乐观并发控制假定不太可能（但不是不可能）在多个用户间发生资源冲突，允许不锁定任何资源而执行事务。只有试图更改数据时才检查资源以确定是否发生冲突。如果发生冲突，应用程序必须读取数据并再次尝试进行更改。

悲观并发

悲观并发控制根据需要在事务的持续时间内锁定资源。除非出现死锁，否则事务肯定会成功完成。

请参见

游标并发

死锁


隔离级别

当锁定用作并发控制机制时，它可以解决并发问题。这使所有事务得以在彼此完全隔离的环境中运行，但是任何时候都可以有多个正在运行的事务。

可串行性是通过运行一组并发事务达到的数据库状态，等同于这组事务按某种顺序连续执行时所达到的数据库状态。

SQL-92 隔离级别

尽管可串行性对于事务确保数据库中的数据在所有时间内的正确性相当重要，然而许多事务并不总是要求完全的隔离。例如，多个作者工作于同一本书的不同章节。新章节可以在任意时候提交到项目中。但是，对于已经编辑过的章节，没有编辑人员的批准，作者不能对此章节进行任何更改。这样，尽管有未编辑的新章节，但编辑人员仍可以确保在任意时间该书籍项目的正确性。编辑人员可以查看以前编辑的章节以及最近提交的章节。

事务准备接受不一致数据的级别称为隔离级别。隔离级别是一个事务必须与其它事务进行隔离的程度。较低的隔离级别可以增加并发，但代价是降低数据的正确性。相反，较高的隔离级别可以确保数据的正确性，但可能对并发产生负面影响。应用程序要求的隔离级别确定了 SQL Server 使用的锁定行为。

SQL-92 定义了下列四种隔离级别，SQL Server 支持所有这些隔离级别：

  • 未提交读（事务隔离的最低级别，仅可保证不读取物理损坏的数据）。

  • 提交读（SQL Server 默认级别）。

  • 可重复读。

  • 可串行读（事务隔离的最高级别，事务之间完全隔离）。

如果事务在可串行读隔离级别上运行，则可以保证任何并发重叠事务均是串行的。

下面四种隔离级别允许不同类型的行为。

隔离级别 | 脏读 | 不可重复读取 | 幻像
未提交读 | 是 | 是 | 是
提交读 | 否 | 是 | 是
可重复读 | 否 | 否 | 是
可串行读 | 否 | 否 | 否

事务必须运行于可重复读或更高的隔离级别以防止丢失更新。当两个事务检索相同的行，然后基于原检索的值对行进行更新时，会发生丢失更新。如果两个事务使用一个 UPDATE 语句更新行，并且不基于以前检索的值进行更新，则在默认的提交读隔离级别不会发生丢失更新。


SQL Server 中的锁定介绍

Microsoft® SQL Server™ 2000 具有多粒度锁定，允许一个事务锁定不同类型的资源。为了使锁定的成本减至最少，SQL Server 自动将资源锁定在适合任务的级别。锁定在较小的粒度（例如行）可以增加并发但需要较大的开销，因为如果锁定了许多行，则需要控制更多的锁。锁定在较大的粒度（例如表）就并发而言是相当昂贵的，因为锁定整个表限制了其它事务对表中任意部分进行访问，但要求的开销较低，因为需要维护的锁较少。

SQL Server 可以锁定以下资源（按粒度增加的顺序列出）。

资源 | 描述
RID | 行标识符。用于单独锁定表中的一行。
键 | 索引中的行锁。用于保护可串行事务中的键范围。
页 | 8 千字节 (KB) 的数据页或索引页。
扩展盘区 | 相邻的八个数据页或索引页构成的一组。
表 | 包括所有数据和索引在内的整个表。
DB | 数据库。

SQL Server 使用不同的锁模式锁定资源，这些锁模式确定了并发事务访问资源的方式。

SQL Server 使用以下资源锁模式。

锁模式 | 描述
共享 (S) | 用于不更改或不更新数据的操作（只读操作），如 SELECT 语句。
更新 (U) | 用于可更新的资源中。防止当多个会话在读取、锁定以及随后可能进行的资源更新时发生常见形式的死锁。
排它 (X) | 用于数据修改操作，例如 INSERT、UPDATE 或 DELETE。确保不会同时对同一资源进行多重更新。
意向 | 用于建立锁的层次结构。意向锁的类型为：意向共享 (IS)、意向排它 (IX) 以及与意向排它共享 (SIX)。
架构 | 在执行依赖于表架构的操作时使用。架构锁的类型为：架构修改 (Sch-M) 和架构稳定性 (Sch-S)。
大容量更新 (BU) | 向表中大容量复制数据并指定了 TABLOCK 提示时使用。

共享锁

共享 (S) 锁允许并发事务读取 (SELECT) 一个资源。资源上存在共享 (S) 锁时，任何其它事务都不能修改数据。一旦已经读取数据，便立即释放资源上的共享 (S) 锁，除非将事务隔离级别设置为可重复读或更高级别，或者在事务生存周期内用锁定提示保留共享 (S) 锁。

更新锁

更新 (U) 锁可以防止通常形式的死锁。一般更新模式由一个事务组成，此事务读取记录，获取资源（页或行）的共享 (S) 锁，然后修改行，此操作要求锁转换为排它 (X) 锁。如果两个事务获得了资源上的共享模式锁，然后试图同时更新数据，则一个事务尝试将锁转换为排它 (X) 锁。共享模式到排它锁的转换必须等待一段时间，因为一个事务的排它锁与其它事务的共享模式锁不兼容；发生锁等待。第二个事务试图获取排它 (X) 锁以进行更新。由于两个事务都要转换为排它 (X) 锁，并且每个事务都等待另一个事务释放共享模式锁，因此发生死锁。

若要避免这种潜在的死锁问题，请使用更新 (U) 锁。一次只有一个事务可以获得资源的更新 (U) 锁。如果事务修改资源，则更新 (U) 锁转换为排它 (X) 锁。否则，锁转换为共享锁。

排它锁

排它 (X) 锁可以防止并发事务对资源进行访问。其它事务不能读取或修改排它 (X) 锁锁定的数据。

意向锁

意向锁表示 SQL Server 需要在层次结构中的某些底层资源上获取共享 (S) 锁或排它 (X) 锁。例如，放置在表级的共享意向锁表示事务打算在表中的页或行上放置共享 (S) 锁。在表级设置意向锁可防止另一个事务随后在包含那一页的表上获取排它 (X) 锁。意向锁可以提高性能，因为 SQL Server 仅在表级检查意向锁来确定事务是否可以安全地获取该表上的锁。而无须检查表中的每行或每页上的锁以确定事务是否可以锁定整个表。

意向锁包括意向共享 (IS)、意向排它 (IX) 以及与意向排它共享 (SIX)。

锁模式 | 描述
意向共享 (IS) | 通过在各资源上放置 S 锁，表明事务的意向是读取层次结构中的部分（而不是全部）底层资源。
意向排它 (IX) | 通过在各资源上放置 X 锁，表明事务的意向是修改层次结构中的部分（而不是全部）底层资源。IX 是 IS 的超集。
与意向排它共享 (SIX) | 通过在各资源上放置 IX 锁，表明事务的意向是读取层次结构中的全部底层资源并修改部分（而不是全部）底层资源。允许顶层资源上的并发 IS 锁。例如，表的 SIX 锁在表上放置一个 SIX 锁（允许并发 IS 锁），在当前所修改页上放置 IX 锁（在已修改行上放置 X 锁）。虽然每个资源在一段时间内只能有一个 SIX 锁，以防止其它事务对资源进行更新，但是其它事务可以通过获取表级的 IS 锁来读取层次结构中的底层资源。

架构锁

执行表的数据定义语言 (DDL) 操作（例如添加列或除去表）时使用架构修改 (Sch-M) 锁。

当编译查询时，使用架构稳定性 (Sch-S) 锁。架构稳定性 (Sch-S) 锁不阻塞任何事务锁，包括排它 (X) 锁。因此在编译查询时，其它事务（包括在表上有排它 (X) 锁的事务）都能继续运行。但不能在表上执行 DDL 操作。

大容量更新锁

当将数据大容量复制到表，且指定了 TABLOCK 提示或者使用 sp_tableoption 设置了 table lock on bulk 表选项时，将使用大容量更新 (BU) 锁。大容量更新 (BU) 锁允许进程将数据并发地大容量复制到同一表，同时防止其它不进行大容量复制数据的进程访问该表。

请参见

死锁

游标锁定

锁定提示


锁兼容性

只有兼容的锁类型才可以放置在已锁定的资源上。例如，当控制排它 (X) 锁时，在第一个事务结束并释放排它 (X) 锁之前，其它事务不能在该资源上获取任何类型的（共享、更新或排它）锁。另一种情况下，如果共享 (S) 锁已应用到资源，其它事务还可以获取该项目的共享锁或更新 (U) 锁，即使第一个事务尚未完成。但是，在释放共享锁之前，其它事务不能获取排它锁。

资源锁模式有一个兼容性矩阵，显示了与在同一资源上可获取的其它锁相兼容的锁（按锁强度增长顺序列出）。

 | 现有的授权模式
请求模式 | IS | S | U | IX | SIX | X
意向共享 (IS) | 是 | 是 | 是 | 是 | 是 | 否
共享 (S) | 是 | 是 | 是 | 否 | 否 | 否
更新 (U) | 是 | 是 | 否 | 否 | 否 | 否
意向排它 (IX) | 是 | 否 | 否 | 是 | 否 | 否
与意向排它共享 (SIX) | 是 | 否 | 否 | 否 | 否 | 否
排它 (X) | 否 | 否 | 否 | 否 | 否 | 否

说明 意向排它 (IX) 锁与 IX 锁模式兼容，因为 IX 表示打算更新一些行而不是所有行。还允许其它事务读取或更新部分行，只要这些行不是其它事务当前所更新的行即可。

架构稳定性 (Sch-S) 锁与除了架构修改 (Sch-M) 锁模式之外的所有锁模式相兼容。

架构修改 (Sch-M) 锁与所有锁模式都不兼容。

大容量更新 (BU) 锁只与架构稳定性 (Sch-S) 锁及其它大容量更新 (BU) 锁相兼容。


键范围锁定

键范围锁定解决了幻像读并发问题，并支持可串行事务。键范围锁覆盖单个记录以及记录之间的范围，可以防止对事务访问的记录集进行幻像插入或删除。键范围锁仅用于代表在可串行隔离级别上操作的事务。

可串行性要求如果任意一个查询在一个事务中后面的某一时刻再次执行，其所获取的行集应与该查询在同一事务中以前执行时所获得的行集相同。如果本查询试图提取的行不存在，则在试图访问该行的事务完成之前，其它事务不能插入该行。如果允许另一个事务插入该行，则它将以幻像出现。

如果另一个事务试图插入驻留在锁定数据页上的行，页级锁定可以防止添加幻像行，并维护可串行性。但是，如果该行要添加到未被第一个事务锁定的数据页，应设定锁定机制防止添加该行。

键范围锁通过覆盖索引行和索引行之间的范围来工作（而不是锁定整个基础表的行）。因为第二个事务在该范围内进行任何行插入、更新或删除操作时均需要修改索引，而键范围锁覆盖了索引项，所以在第一个事务完成之前会阻塞第二个事务的进行。

键范围锁模式

键范围锁包括范围组件和行组件，按范围-行格式指定：

  • 范围表示保护两个连续索引项之间的范围的锁模式。

  • 行表示保护索引项的锁模式。

  • 模式表示使用的组合锁模式。键范围锁模式由两部分组成。第一部分表示用于锁定索引范围 (RangeT) 的锁类型，第二部分表示用于锁定特定键 (K) 的锁类型。这两部分用下划线 (_) 连接，如 RangeT_K。
范围 行 模式
  • 描述
RangeS | S | RangeS_S | 共享范围，共享资源锁；可串行范围扫描。
RangeS | U | RangeS_U | 共享范围，更新资源锁；可串行更新扫描。
RangeI | NULL | RangeI_N | 插入范围，空资源锁；用于在索引中插入新键之前测试范围。
RangeX | X | RangeX_X | 排它范围，排它资源锁；用于更新范围中的键。

说明 内部空锁模式与所有其它锁模式相兼容。

键范围锁模式有一个兼容性矩阵，表示哪些锁与在重叠键和范围上获取的其它锁兼容。

 | 现有的授权模式
请求模式 | S | U | X | RangeS_S | RangeS_U | RangeI_N | RangeX_X
共享 (S) | 是 | 是 | 否 | 是 | 是 | 是 | 否
更新 (U) | 是 | 否 | 否 | 是 | 否 | 是 | 否
排它 (X) | 否 | 否 | 否 | 否 | 否 | 是 | 否
RangeS_S | 是 | 是 | 否 | 是 | 是 | 否 | 否
RangeS_U | 是 | 否 | 否 | 是 | 否 | 否 | 否
RangeI_N | 是 | 是 | 是 | 否 | 否 | 是 | 否
RangeX_X | 否 | 否 | 否 | 否 | 否 | 否 | 否

会话锁

当键范围锁与其它锁重叠时，创建会话锁。

锁 1 | 锁 2 | 会话锁
S | RangeI_N | RangeI_S
U | RangeI_N | RangeI_U
X | RangeI_N | RangeI_X
RangeI_N | RangeS_S | RangeX_S
RangeI_N | RangeS_U | RangeX_U

在不同的复杂环境下（有时是在运行并发进程时），可以在一小段时间内观察到会话锁。

可串行范围扫描、单独提取、删除和插入

键范围锁定确保以下四种方案是可串行的：

  • 范围扫描查询

  • 单独提取不存在的行

  • 删除操作

  • 插入操作

然而，在可以发生键范围锁定之前，必须满足下列条件：

  • 事务隔离级别必须设置为 SERIALIZABLE。

  • 对数据执行的操作必须使用索引范围访问。只有当查询处理程序（如优化器）选择访问数据的索引路径时，才激活范围锁定。

下列每种方案的示例都基于下图中的表和索引。

范围扫描查询

为了确保范围扫描查询是可串行的，每次在同一事务中执行的相同查询应返回同样的结果。其它事务不能在范围扫描查询中插入新行；否则这些插入将成为幻像插入。例如，下面的查询使用前面插图中的表和索引：

SELECT name FROM mytable WHERE name BETWEEN 'A' AND 'C'

键范围锁放置在与数据行范围（名称在值 Adam 和 Dale 之间的行）对应的索引项上，以防止添加或删除满足上述查询条件的新行。虽然此范围中的第一个名称是 Adam，但是此索引项的 RangeS_S 模式键范围锁确保了以字母 A 开头的新名称（如 Abigail）不能添加在 Adam 之前。同样，Dale 索引项的 RangeS_S 键范围锁确保了以字母 C 开头的新名称（如 Clive）不能添加在 Carlos 之后。

说明 控制的 RangeS_S 锁数量为 n+1，此处 n 是满足查询条件的行数。

单独提取不存在数据

如果事务中的查询试图选择不存在的行，则以后在相同的事务中发出这一查询时，必须返回相同的结果。不允许其它事务插入不存在的行。例如，对于下面的查询：

SELECT name FROM mytable WHERE name = 'Bill'

键范围锁放置在对应于名称范围 Ben 到 Bing 之间的索引项上，因为名称 Bill 将插入到这两个相邻的索引项之间。RangeS_S 模式键范围锁放置在索引项 Bing 上。这样可以防止任何其它事务在索引项 Ben 和 Bing 之间插入插入值（如 Bill）。

删除操作

在事务中删除值时，在事务执行删除操作期间不必锁定值所属的范围。锁定删除的键值直至事务结束足以保持可串行性。例如，对于下面的 DELETE 语句：

DELETE mytable WHERE name = 'Bob'

排它 (X) 锁放置在对应于名称 Bob 的索引项上。其它事务可以在删除值 Bob 的前后插入或删除值。但是任何试图读取、插入或删除值 Bob 的事务将被阻塞，直到删除的事务提交或回滚为止。

可以使用下列三种基本锁模式执行范围删除：行锁、页锁或表锁。页、表或行锁定策略由查询优化器确定，或者可以由用户通过优化程序提示（如 ROWLOCK、PAGLOCK 或 TABLOCK）指定。在使用页锁或表锁的情况下，SQL Server 立即释放包含已删除行的索引页，并假定从页中删除了所有行。相反，使用行锁时，所有删除的行只是被标记为已删除；以后通过后台任务从索引页中删除它们。

插入操作

在事务中插入值时，在事务执行插入操作期间不必锁定值所属的范围。锁定插入的键值直至事务结束足以维护可串行性。例如，对于下面的 INSERT 语句：

INSERT mytable VALUES ('Dan')

RangeI_N 模式键范围锁放置在对应于名字 David 的索引项上以测试范围。如果已授权锁定，则插入 Dan，并且排它 (X) 锁放置在值 Dan 上。RangeI_N 模式键范围锁仅对测试范围必需，而不在执行插入操作的事务期间保留。其它事务可以在插入值 Dan 的前后插入或删除值。但是，任何试图读取、插入或删除值 Dan 的事务将被阻塞，直到插入的事务提交或回滚为止。


锁升级

锁升级是将众多细粒度锁转换为较少的粗粒度的锁的过程，以削减系统开销。当事务超过它的升级极限时，Microsoft® SQL Server™ 2000 自动将行锁和页锁升级为表锁。

例如，当事务从表中请求行时，SQL Server 自动获取受影响的行上的锁，并在包含这些行的页和表或者索引上放置更高级别的意向锁。当事务控制的锁数量超过了它的极限时，SQL Server 会试图将表上的意向锁更改为更强的锁（例如，将意向排它 (IX) 锁更改为排它 (X) 锁）。获取更强的锁后，表事务持有的所有页级锁和行级锁都被释放，从而削减锁的开销。

SQL Server 可以为同一查询选择行和页锁定，例如，在索引上放置页锁（如果在非聚集的索引节点中选定了足够的邻接键来满足查询）及在数据上放置行锁。以减少必须进行锁升级的可能性。

锁升级极限是由 SQL Server 动态确定的，无须进行配置。

请参见

locks 选项


动态锁定

虽然行级锁可以增加并发，但却增加了系统开销。表锁或页锁降低了开销，但代价是降低了并发性。

Microsoft® SQL Server™ 2000 使用动态锁定策略确定最经济的锁。执行查询时，SQL Server 基于架构和查询的特性自动确定最合适的锁。例如，为了缩减锁定的开销，优化程序可能在执行索引扫描时在索引中选择页级锁。

动态锁定有以下优点：

  • 简化数据库管理。数据库管理员不必再考虑调整锁升级极限。

  • 提高性能。SQL Server 通过使用适合任务的锁，使系统开销减至最小。

  • 应用程序开发人员可以集中精力进行开发。SQL Server 自动调整锁定。


显示锁定信息

执行 sp_lock 系统存储过程时，Microsoft® SQL Server™ 2000 提供活动锁的报表。

下面是结果集。

spid

Dbid

ObjId

IndId

Type

Resource

Mode

Status

1

1

0

0

DB

S

GRANT

6

1

0

0

DB

S

GRANT

7

1

0

0

DB

S

GRANT

8

1

0

0

DB

S

GRANT

8

1

1396200024

0

RID

1:1225:2

X

GRANT

8

1

1396200024

0

PAG

1:1225

IX

GRANT

8

1

1396200024

2

PAG

1:1240

IX

GRANT

8

1

21575115

0

TAB

IS

GRANT

8

1

1396200024

2

KEY

(03000100cb04)

X

GRANT

8

1

1396200024

0

TAB

IX

GRANT

类型列

"类型"列显示当前锁定的资源类型。

资源类型 | 描述
RID | 用于锁定表中的一行的行标识符。
KEY | 索引中的行锁。用于保护可串行事务中的键范围。
PAG | 数据或索引页。
EXT | 相邻的八个数据页或索引页构成的一组。
TAB | 包括所有数据和索引在内的整个表。
DB | 数据库。

资源列

"资源"列提供有关正被锁定资源的信息。

资源类型 | 描述
RID | 表内已锁定行的行标识符。行由 fileid:page:rid 组合进行标识，其中，rid 是页中的行标识符。
KEY | SQL Server 内部使用的十六进制数字。
PAG | 页码。页由 fileid:page 组合进行标识，其中，fileid 是 sysfiles 表中的 fileid，而 page 是该文件内的逻辑页码。
EXT | 正被锁定的扩展盘区中的第一个页码。页由 fileid:page 组合进行标识。
TAB | 由于 ObjId 列已包含表的对象 ID，所以没有提供任何信息。
DB | 由于 dbid 列已包含数据库的数据库 ID，所以没有提供任何信息。

在 sp_lock 的结果集内，锁定的 RID 资源类型中的资源描述为 1:1225:2。这表明在 fileid 1 内、页码为 1225 的页中、行标识符为 2 的行上应用了锁。有关更多信息，请参见有关死锁的疑难解答。

模式列

"模式"列描述正应用于资源的锁类型。锁类型包括任何多粒锁。

状态列

"状态"列显示锁是已经获取 (GRANT)、正在另一个进程中被阻塞 (WAIT)、还是正在转换为另一个锁 (CNVT)。正转换为另一种锁的锁会保持在一种模式中，但等待获取更强的锁模式（例如，从更新模式变为排它模式）。当遇到诊断阻塞问题时，会认为 CNVT 与 WAIT 类似。

监视锁定活动的其它工具

当持有和释放锁的速度比 sp_lock 显示的速度快时，使用 sp_lock 来显示锁定信息并不一定始终可行。在这种情况下，可以使用 SQL 事件探查器监视和记录锁定信息。此外，可以使用 Windows 性能监视器监视使用 SQL Server 锁对象计数器的锁活动。

查看当前的锁

Transact-SQL

企业管理器

企业管理器

SQL-DMO

请参见

锁事件分类

syslockinfo

SQL Server:Locks 对象

sp_lock


在 SQL Server 中自定义锁定

虽然 Microsoft® SQL Server™ 2000 自动执行锁定，但它仍可以通过以下方法自定义应用程序中的锁定：

  • 处理死锁和设置死锁优先级。

  • 处理超时和设置锁超时持续时间。

  • 设置事务隔离级别。

  • 对 SELECT、INSERT、UPDATE 和 DELETE 语句使用表级锁定提示。

  • 配置索引的锁定粒度。


死锁

当某组资源的两个或多个线程之间有循环相关性时，将发生死锁。

死锁是一种可能发生在任何多线程系统中的状态，而不仅仅发生在关系数据库管理系统中。多线程系统中的一个线程可能获取一个或多个资源（如锁）。如果正获取的资源当前为另一线程所拥有，则第一个线程可能必须等待拥有线程释放目标资源。这时就说等待线程在那个特定资源上与拥有线程有相关性。

如果拥有线程需要获取另外一个资源，而该资源当前为等待线程所拥有，则这种情形将成为死锁：在事务提交或回滚之前两个线程都不能释放资源，而且它们因为正等待对方拥有的资源而不能提交或回滚事务。例如，运行事务 1 的线程 T1 具有 Supplier 表上的排它锁。运行事务 2 的线程 T2 具有 Part 表上的排它锁，并且之后需要 Supplier 表上的锁。事务 2 无法获得这一锁，因为事务 1 已拥有它。事务 2 被阻塞，等待事务 1。然后，事务 1 需要 Part 表的锁，但无法获得锁，因为事务 2 将它锁定了。事务在提交或回滚之前不能释放持有的锁。因为事务需要对方控制的锁才能继续操作，所以它们不能提交或回滚。

说明 死锁经常与正常阻塞混淆。当一个事务锁定了另一个事务需要的资源，第二个事务等待锁被释放。默认情况下，SQL Server 事务不会超时（除非设置了 LOCK_TIMEOUT）。第二个事务被阻塞，而不是被死锁。有关更多信息，请参见自定义锁超时。

在该插图中，对于 Part 表锁资源，线程 T1 在线程 T2 上具有相关性。同样，对于 Supplier 表锁资源，线程 T2 在线程 T1 上具有相关性。因为这些相关性形成了一个循环，所以在线程 T1 和线程 T2 之间存在死锁。


检测和结束死锁

在 Microsoft® SQL Server™ 2000 中，单个用户会话可能有一个或多个代表它运行的线程。每个线程可能获取或等待获取各种资源，如：

  • 锁。

  • 与并行查询执行相关的资源（与交换端口相关联的处理协调器、发生器和使用者线程）。

  • 线程。

  • 内存。

上述这些资源除内存外都参与 SQL Server 死锁检测方案。对于内存，SQL Server 使用基于超时的机制，该机制由 sp_configure 中的 query wait 选项控制。

在 SQL Server 2000 中，死锁检测由一个称为锁监视器线程的单独的线程执行。在出现下列任一情况时，锁监视器线程对特定线程启动死锁搜索：

  • 线程已经为同一资源等待了一段指定的时间。锁监视器线程定期醒来并识别所有等待某个资源的线程。如果锁监视器再次醒来时这些线程仍在等待同一资源，则它将对等待线程启动锁搜索。

  • 线程等待资源并启动急切的死锁搜索。

SQL Server 通常只执行定期死锁检测，而不使用急切模式。因为系统中遇到的死锁数通常很少，定期死锁检测有助于减少系统中死锁检测的开销。

当锁监视器对特定线程启动死锁检测时，它识别线程正在等待的资源。然后，锁监视器查找特定资源的拥有者，并递归地继续执行对那些线程的死锁搜索，直到找到一个循环。用这种方式识别的循环形成一个死锁。

在识别死锁后，SQL Server 通过自动选择可以打破死锁的线程（死锁牺牲品）来结束死锁。SQL Server 回滚作为死锁牺牲品的事务，通知线程的应用程序（通过返回 1205 号错误信息），取消线程的当前请求，然后允许不间断线程的事务继续进行。

SQL Server 通常选择运行撤消时花费最少的事务的线程作为死锁牺牲品。另外，用户可以使用 SET 语句将会话的 DEADLOCK_PRIORITY 设置为 LOW。DEADLOCK_PRIORITY 选项控制在死锁情况下如何衡量会话的重要性。如果会话的设置为 LOW ，则当会话陷入死锁情况时将成为首选牺牲品。

识别死锁

识别死锁后，SQL Server 选择特定的线程作为死锁牺牲品，并返回一条列出死锁中涉及的资源的错误信息。该死锁信息采用下列形式：

Your transaction (process ID #52) was deadlocked on {lock | communication buffer | thread} resources with another process and has been chosen as the deadlock victim. Rerun your transaction.

死锁中涉及的线程和资源位于错误日志中。有关如何识别死锁中涉及的死锁线程和资源的更多信息，请参见有关死锁的疑难解答。

请参见

SET DEADLOCK_PRIORITY


处理死锁

当一个应用程序提交的事务被选作死锁牺牲品时，该事务将自动终止并回滚，然后系统将 1205 号错误信息返回给应用程序。因为任何提交 SQL 查询的应用程序都可以被选定作为死锁牺牲品，应用程序应该有错误处理程序来捕获错误信息 1205。如果应用程序没有捕获到错误，它可能继续处理而未意识到事务已经回滚，这样应用程序就会出错。

通过实现捕获 1205 号错误信息的错误处理程序，使应用程序得以处理该死锁情况并采取补救措施（例如，可以自动重新提交陷入死锁中的查询）。自动重新提交查询可能意味着用户不必知道发生了死锁。

在自动重新提交查询前，客户端程序应暂停，以便为控制所需锁的事务提供完成并释放这些锁的机会。这样，在事务试图获得那些锁时，可以最大限度地降低事务被死锁的可能性。

说明 死锁不总是取消返回错误的批处理操作。对于客户端程序来说进行错误检查是非常重要的，因为死锁并不总是返回失败的返回代码。在大多数情况下，如果发生死锁且批处理未自动取消，则应用程序应取消当前查询。如果没有进行此操作，则 SQL Server 在连接上可能仍有未决结果等待客户端处理。如果没有处理未决结果，则在该应用程序下一次试图向 SQL Server 发送命令时将发生错误。

请参见

处理错误和消息


将死锁减至最少

虽然不能完全避免死锁，但可以使死锁的数量减至最少。将死锁减至最少可以增加事务的吞吐量并减少系统开销，因为只有很少的事务：

  • 回滚，而回滚会取消事务执行的所有工作。

  • 由于死锁时回滚而由应用程序重新提交。

下列方法有助于最大限度地降低死锁：

  • 按同一顺序访问对象。

  • 避免事务中的用户交互。

  • 保持事务简短并在一个批处理中。

  • 使用低隔离级别。

  • 使用绑定连接。
按同一顺序访问对象

如果所有并发事务按同一顺序访问对象，则发生死锁的可能性会降低。例如，如果两个并发事务获得 Supplier 表上的锁，然后获得 Part 表上的锁，则在其中一个事务完成之前，另一个事务被阻塞在 Supplier 表上。第一个事务提交或回滚后，第二个事务继续进行。不发生死锁。将存储过程用于所有的数据修改可以标准化访问对象的顺序。

避免事务中的用户交互

避免编写包含用户交互的事务，因为运行没有用户交互的批处理的速度要远远快于用户手动响应查询的速度，例如答复应用程序请求参数的提示。例如，如果事务正在等待用户输入，而用户去吃午餐了或者甚至回家过周末了，则用户将此事务挂起使之不能完成。这样将降低系统的吞吐量，因为事务持有的任何锁只有在事务提交或回滚时才会释放。即使不出现死锁的情况，访问同一资源的其它事务也会被阻塞，等待该事务完成。

保持事务简短并在一个批处理中

在同一数据库中并发执行多个需要长时间运行的事务时通常发生死锁。事务运行时间越长，其持有排它锁或更新锁的时间也就越长，从而堵塞了其它活动并可能导致死锁。

保持事务在一个批处理中，可以最小化事务的网络通信往返量，减少完成事务可能的延迟并释放锁。

使用低隔离级别

确定事务是否能在更低的隔离级别上运行。执行提交读允许事务读取另一个事务已读取（未修改）的数据，而不必等待第一个事务完成。使用较低的隔离级别（例如提交读）而不使用较高的隔离级别（例如可串行读）可以缩短持有共享锁的时间，从而降低了锁定争夺。

使用绑定连接

使用绑定连接使同一应用程序所打开的两个或多个连接可以相互合作。次级连接所获得的任何锁可以象由主连接获得的锁那样持有，反之亦然，因此不会相互阻塞。

请参见

使用绑定连接


自定义锁超时

当由于另一个事务已拥有一个资源的冲突锁，而导致 Microsoft® SQL Server™ 2000 无法将锁授权给该资源的某个事务时，该事务被阻塞以等待该资源的操作完成。如果这导致了死锁，则 SQL Server 将终止其中参与的一个事务（不涉及超时）。如果没有出现死锁，则在其它事务释放锁之前，请求锁的事务被阻塞。默认情况下，没有强制的超时期限，并且除了试图访问数据外（有可能被无限期阻塞），没有其它方法可以测试某个资源是否在锁定之前已被锁定。

说明 sp_who 系统存储过程可用于确定进程是否正被阻塞以及被谁阻塞。

LOCK_TIMEOUT 设置允许应用程序设置语句等待阻塞资源的最长时间。当语句等待的时间大于 LOCK_TIMEOUT 设置时，系统将自动取消阻塞的语句，并给应用程序返回"已超过了锁请求超时时段"的 1222 号错误信息。

但是，SQL Server 不回滚或取消任何包含该语句的事务。因此，应用程序必须有捕获 1222 号错误信息的错误处理程序。如果应用程序没有捕获错误，则会继续运行，并未意识到事务中的个别语句已取消，从而当事务中的后续语句可能依赖于那条从未执行的语句时，导致应用程序出错。

执行捕获错误信息 1222 的错误处理程序使应用程序得以处理发生超时的情况，并采取补救操作，例如可以自动重新提交阻塞的语句或者回滚整个事务。

若要确定当前 LOCK_TIMEOUT 设置，请执行 @@LOCK_TIMEOUT 函数，例如：

DECLARE @Timeout int
SELECT @Timeout = @@lock_timeout
SELECT @Timeout
GO

请参见

@@LOCK_TIMEOUT

SET LOCK_TIMEOUT

sp_who

处理错误和消息


自定义事务隔离级别

默认情况下，Microsoft® SQL Server™ 2000 在 READ COMMITTED 的一个隔离级别上操作。但是，应用程序可能必须运行于不同的隔离级别。若要在应用程序中使用更严格或较宽松的隔离级别，可以通过使用 SET TRANSACTION ISOLATION LEVEL 语句设置会话的隔离级别，来自定义整个会话的锁定。

指定隔离级别后，SQL Server 会话中所有 SELECT 语句的锁定行为都运行于该隔离级别上，并一直保持有效直到会话终止或者将隔离级别设置为另一个级别。例如，若要设置事务隔离级别为可串行读，以确保并发事务不能在 authors 表中插入幻像行，请执行：

USE pubs
GO
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
GO
BEGIN TRANSACTION
SELECT au_lname FROM authors

说明 如果必要，可以通过指定表级锁定提示来替代单个 SELECT 语句的隔离级别。指定表级锁定提示不会影响会话中的其它语句。建议仅在绝对必要时才使用表级锁定提示更改默认的锁定行为。

若要确定当前设置的事务隔离级别，请使用 DBCC USEROPTIONS 语句，例如：

USE pubs
GO
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
GO
DBCC USEROPTIONS
GO

Set Option | Value
Textsize | 4096
Language | us_english
Dateformat | mdy
Datefirst | 7
isolation level | repeatable read

(5 rows affected)
DBCC execution completed. If DBCC printed error messages, see your System Administrator.

请参见

DBCC USEROPTIONS

SET TRANSACTION ISOLATION LEVEL

SELECT


锁定提示

可以使用 SELECT、INSERT、UPDATE 和 DELETE 语句指定表级锁定提示的范围，以引导 Microsoft® SQL Server™ 2000 使用所需的锁类型。当需要对对象所获得锁类型进行更精细控制时，可以使用表级锁定提示。这些锁定提示取代了会话的当前事务隔离级别。

说明 SQL Server 查询优化器自动作出正确的决定。建议仅在必要时才使用表级锁定提示更改默认的锁定行为。禁止锁定级别反过来会影响并发。

锁定提示 | 描述
HOLDLOCK | 将共享锁保留到事务完成，而不是在相应的表、行或数据页不再需要时就立即释放锁。HOLDLOCK 等同于 SERIALIZABLE。
NOLOCK | 不要发出共享锁，并且不要提供排它锁。当此选项生效时，可能会读取未提交的事务或一组在读取中间回滚的页面。有可能发生脏读。仅应用于 SELECT 语句。
PAGLOCK | 在通常使用单个表锁的地方采用页锁。
READCOMMITTED | 用与运行在提交读隔离级别的事务相同的锁语义执行扫描。默认情况下，SQL Server 2000 在此隔离级别上操作。
READPAST | 跳过锁定行。此选项导致事务跳过由其它事务锁定的行（这些行平常会显示在结果集内），而不是阻塞该事务，使其等待其它事务释放在这些行上的锁。READPAST 锁提示仅适用于运行在提交读隔离级别的事务，并且只在行级锁之后读取。仅适用于 SELECT 语句。
READUNCOMMITTED | 等同于 NOLOCK。
REPEATABLEREAD | 用与运行在可重复读隔离级别的事务相同的锁语义执行扫描。
ROWLOCK | 使用行级锁，而不使用粒度更粗的页级锁和表级锁。
SERIALIZABLE | 用与运行在可串行读隔离级别的事务相同的锁语义执行扫描。等同于 HOLDLOCK。
TABLOCK | 使用表锁代替粒度更细的行级锁或页级锁。在语句结束前，SQL Server 一直持有该锁。但是，如果同时指定 HOLDLOCK，那么在事务结束之前，锁将被一直持有。
TABLOCKX | 使用表的排它锁。该锁可以防止其它事务读取或更新表，并在语句或事务结束前一直持有。
UPDLOCK | 读取表时使用更新锁，而不使用共享锁，并将锁一直保留到语句或事务的结束。UPDLOCK 的优点是允许您读取数据（不阻塞其它事务）并在以后更新数据，同时确保自从上次读取数据后数据没有被更改。
XLOCK | 使用排它锁并一直保持到由语句处理的所有数据上的事务结束时。可以使用 PAGLOCK 或 TABLOCK 指定该锁，这种情况下排它锁适用于适当级别的粒度。

例如，如果将事务隔离级别设置为 SERIALIZABLE，并且在 SELECT 语句中使用表级锁定提示 NOLOCK，则键范围锁通常用于维护不采用可串行事务。

USE pubs
GO
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
GO
BEGIN TRANSACTION
SELECT au_lname FROM authors WITH (NOLOCK)
GO

生成的锁是：

EXEC sp_lock
GO

spid | dbid | ObjId | IndId | Type | Resource | Mode | Status
1 | 1 | 0 | 0 | DB |  | S | GRANT
6 | 1 | 0 | 0 | DB |  | S | GRANT
7 | 1 | 0 | 0 | DB |  | S | GRANT
8 | 4 | 0 | 0 | DB |  | S | GRANT
8 | 4 | 0 | 0 | DB |  | S | GRANT
8 | 4 | 117575457 | 0 | TAB |  | Sch-S | GRANT
9 | 4 | 0 | 0 | DB |  | S | GRANT
9 | 1 | 21575115 | 0 | TAB |  | IS | GRANT

SELECT object_name(117575457)
GO
-----------------------------
authors

引用 authors 唯一采用的锁是架构稳定性 (Sch-S) 锁。在这种情况下不能保证可串行性。

请参见

DELETE

INSERT

SELECT

SET TRANSACTION ISOLATION LEVEL

UPDATE


自定义索引的锁定

在大多数情况下，Microsoft® SQL Server™ 2000 动态锁定策略自动选择查询的最佳锁定粒度。在访问模式很好理解且一致的情况下，限制索引可用的锁定级别是很有益的。

例如，数据库应用程序使用的查找表在批处理进程中每周进行刷新。最有效的锁定策略是关闭页和行锁定，并允许所有并发读取器获得表上的共享 (S) 锁以减少开销。在每周的批处理更新时，更新进程可以使用排它 (X) 锁，然后更新整个表。

可以使用 sp_indexoption 系统存储过程来设置用于索引的锁定粒度。若要显示给定索引的当前锁定选项，请使用 INDEXPROPERTY 函数。可以禁止将页级锁、行级锁或二者的组合用于指定的索引。

禁止的锁 | 访问索引的锁
页级 | 行级锁和表级锁
行级 | 页级锁和表级锁
页级和行级 | 表级锁

例如，当已知表是争夺点时，禁止页级锁从而只允许行级锁会有好处。或者如果总是使用表扫描来访问索引或表，那么通过只允许表级锁而禁止页级锁和行级锁将会很有帮助。

重要 SQL Server 查询优化器自动作出正确的决定。建议您不要替代优化器作出的选择。禁止锁定级别反过来会影响表或索引的并发。例如，在由许多用户频繁访问的大型表上只指定表级锁会严重影响性能。用户在访问表之前必须等待释放表级锁。

请参见

INDEXPROPERTY

sp_indexoption


游标

关系数据库中的操作会对整个行集产生影响。由 SELECT 语句返回的行集包括所有满足该语句 WHERE 子句中条件的行。由语句所返回的这一完整的行集被称为结果集。应用程序，特别是交互式联机应用程序，并不总能将整个结果集作为一个单元来有效地处理。这些应用程序需要一种机制以便每次处理一行或一部分行。游标就是提供这种机制的结果集扩展。

游标通过以下方式扩展结果处理：

  • 允许定位在结果集的特定行。

  • 从结果集的当前位置检索一行或多行。

  • 支持对结果集中当前位置的行进行数据修改。

  • 为由其他用户对显示在结果集中的数据库数据所做的更改提供不同级别的可见性支持。

  • 提供脚本、存储过程和触发器中使用的访问结果集中的数据的 Transact-SQL 语句。
请求游标

Microsoft® SQL Server™ 2000 支持两种请求游标的方法：

  • Transact-SQL

    对根据 SQL-92 游标语法制定的游标，Transact-SQL 语言支持使用它们的语法。

  • 数据库应用程序编程接口（API）游标函数

    SQL Server 支持这些数据库 API 的游标功能：

    • ADO（Microsoft ActiveX® 数据对象)

    • OLE DB

    • ODBC（开放式数据库连接）

    • DB-Library

应用程序不能混合使用这两种请求游标的方法。已经使用 API 指定游标行为的应用程序不能再执行 Transact-SQL DECLARE CURSOR 语句请求一个 Transact-SQL 游标。只有在将所有 API 游标特性均设回默认值后，应用程序才可以执行 DECLARE CURSOR。

如果 Transact-SQL 和 API 游标均未被请求，则默认情况下 SQL Server 将一个完整的结果集返回给应用程序，这个结果集称为默认结果集。

游标进程

Transact-SQL 游标和 API 游标有不同的语法，但下列一般进程可用于所有 SQL Server 游标：

  1. 把游标与 Transact-SQL 语句的结果集相关联，并且定义游标的特征，如是否能够更新游标中的行。

  2. 执行 Transact-SQL 语句以填充游标。

  3. 检索想要查看的游标中的行。从游标中检索一行或多行的操作称为提取。执行一系列的提取操作以向前或向后检索行的操作称为滚动。

  4. 根据需要，对游标中当前位置的行执行修改操作（更新或删除）。

  5. 关闭游标。


默认结果集

Microsoft® SQL Server™ 2000 以下面的方式将结果集返回给客户端：

  1. SQL Server 从包含需执行的一个或一批 Transact-SQL 语句的客户端中接收网络数据包。

  2. SQL Server 编译并执行该语句或该批语句。

  3. SQL Server 首先把结果集（或者来自批处理或存储过程的多个结果集）中的行放在网络数据包中，然后把它们发送给客户端。SQL Server 在每个数据包中放置尽可能多的结果集的行。

  4. 包含结果集行的数据包被缓存在客户端的网络缓冲区中。当客户端提取这些行时，ODBC 驱动程序、OLE DB 提供程序或者 DB-Library 动态链接库（DLL）从网络缓冲区中取出这些行，并将数据传递给客户应用程序。然后客户端每次向前检索结果中的一行。

在应用程序处理完由 SQL Server 返回的所有行，或给 SQL Server 发送取消剩余结果的请求后，客户端才能在此连接上发送其它 Transact-SQL 语句。仅当所有结果均处理完毕后才能对连接进行更新；结果集为只读。

说明 流水游标是默认结果集已废弃的术语。

这是在没有请求游标时 SQL Server 所使用的结果集处理类型。满足下列条件时会出现这种情况：

  • 应用程序没有使用 DECLARE CURSOR 语句请求 Transact-SQL 服务器游标。而是直接执行了 Transact-SQL 语句，如 SELECT。

  • 如果应用程序使用 ADO、OLE DB 和 ODBC，则所有 API 游标特性均保持默认设置，没有 API 游标被请求。这一默认特性集的目的是请求行集大小为 1 的只进、只读游标。

  • 如果应用程序使用 DB-Library，则它将使用 DB-Library 核心函数来执行语句和处理结果集。

由于这种处理类型在所有游标特性均设置为其默认值时使用，且 SQL Server 或数据库 API 实际并未涉及游标处理的情况下使用，因此被称为默认结果集。

默认结果集不会以一整块的形式传送给应用程序，结果集缓存在客户端的网络缓冲区内。应用程序每次提取结果集的一行。在每一次提取中，OLE DB 提供程序、ODBC 驱动程序或者 DB-Library DLL 把数据从网络缓冲区的下一行转移到应用程序的变量内。OLE DB、ODBC 和 ADO 应用程序用于检索行的 API 函数与从游标中提取行所用的 API 函数相同。DB-Library 应用程序则使用核心函数 dbnextrow 来提取每一行。

默认结果集是把结果传递给客户端最有效的方法。从客户机传递给服务器的唯一数据包就是包含需执行语句的原始数据包。当结果被送回客户端时，SQL Server 把尽可能多的结果集行放置到每个数据包中，以减少发送给客户端的数据包数量。

在使用默认结果集的情况下，所有的 Transact-SQL 语句均受支持。使用默认结果集时还可以执行包含多个语句（这些语句返回结果集）的批处理或存储过程。

默认结果集只能用于向客户应用程序发送结果集。批处理、存储过程或触发器中的任何其它 Transact-SQL 语句或变量都无法使用默认结果集中的数据。例如，考虑存储过程或触发器中的这个语句：

SELECT ProductID FROM Northwind.dbo.Products

这个语句生成包含 Northwind 数据库中所有产品 ID 的默认结果集。存储过程或触发器中的所有其它 Transact-SQL 语句或变量都不能引用这一产品 ID 列表。对结果集所做的唯一处理是由 SQL Server 发送到客户端。该结果集中由其它 Transact-SQL 语句使用的数据必须在 Transact-SQL 服务器游标中：

DECLARE abc CURSOR FOR
SELECT ProductID FROM Northwind.dbo.Products

请参见

使用默认结果集

行集和 SQL Server 游标

使用默认结果集


游标实现

Microsoft® SQL Server™ 2000 支持三种游标实现：

Transact-SQL 游标

基于 DECLARE CURSOR 语法，主要用在 Transact-SQL 脚本、存储过程和触发器中。Transac-SQL 游标在服务器上实现并由从客户端发送到服务器的 Transact-SQL 语句管理。它们还包含在批处理、存储过程或触发器中。

应用编程接口（API）服务器游标

支持 OLE DB、ODBC 和 DB-Library 中的 API 游标函数。API 服务器游标在服务器上实现。每次客户应用程序调用 API 游标函数时，SQL Server OLE DB 提供程序、ODBC 驱动程序或 DB-Library 动态链接库（DLL）就把请求传送到服务器，以便对 API 服务器游标进行操作。

客户端游标

由 SQL Server ODBC 驱动程序、DB-Library DLL 和实现 ADO API 的 DLL 在内部实现。客户端游标通过在客户端高速缓存所有结果集行来实现。每次客户应用程序调用 API 游标函数时，SQL Server ODBC 驱动程序、DB-Library DLL 或 ADO DLL 就对高速缓存在客户端中的结果集行执行游标操作。

由于 Transact-SQL 游标和 API 服务器游标都在服务器端实现，它们一起被称为服务器游标。

不要混合使用这些不同类型的游标。如果执行一个来自应用程序的 DECLARE CURSOR 和 OPEN 语句，首先需要把 API 游标的特性设置为默认值。如果将 API 游标的特性设置非默认值的其它值，然后又执行 DECLARE CURSOR 和 OPEN 语句，您事实上是在要求 SQL Server 在 Transact-SQL 游标上映射 API 游标。例如，不要将 ODBC 特性设置为调用将键集驱动游标映射为结果集，然后又使用语句句柄执行 DECLARE CURSOR 和 OPEN 以调用 INSENSITIVE 游标。

服务器游标的一个潜在缺点是它们目前并不支持所有的 Transact-SQL 语句。服务器游标不支持生成多个结果集的 Transact-SQL 语句，因此，当应用程序执行包含多个 SELECT 语句的存储过程或批处理时，不能使用服务器游标。服务器游标也不支持包含 COMPUTE、COMPUTE BY、FOR BROWSE 或 INTO 关键字的 SQL 语句。

服务器游标与默认结果集的比较

使用游标不如使用默认结果集的效率高。在默认结果集中，从客户端发送到服务器的唯一一个数据包是包含需执行语句的数据包。而在使用服务器游标时，每一个 FETCH 语句都必须从客户端发送到服务器，然后在服务器中将它解析并编译为执行计划。

如果一个 Transact-SQL 语句将返回一个相对较小、可高速缓存于客户端应用程序可用内存中的结果集，且在执行语句前已经知道需要检索整个结果集，那么应使用默认结果集。只有在需要进行游标操作以支持应用程序的功能，或者可能只需检索结果集的一部分时，才会使用服务器游标。

服务器游标与客户端游标的比较

用服务器游标代替客户端游标有以下几个优点：

  • 性能

    在访问游标中的一部分数据时（典型如众多浏览应用程序），由于在网络上只发送提取的数据，所以使用服务器游标能够提供最佳的性能。客户端游标则把整个结果集高速缓存在客户端。

  • 附加游标类型

    如果SQL Server ODBC 驱动程序只使用客户端游标，则它只能够支持只进和静态游标。通过使用 API 服务器游标，驱动程序也能支持键集驱动和动态游标。同样，SQL Server 仅通过服务器游标支持所有的游标并发特性。客户端游标仅限于它们所支持的功能。

  • 更精确的定位更新

    服务器游标直接支持定位操作，例如 ODBC 的 SQLSetPos 函数，或带有 WHERE CURRENT OF 子句的 UPDATE 和 DELETE 语句。另一方面，通过生成 Transact-SQL UPDATE 搜索语句，客户端游标可以模拟定位游标更新；如果有多个行满足 UPDATE 语句中 WHERE 子句的条件，那么这将导致无意识的更新。

  • 内存使用

    在使用服务器游标时，客户端无需缓存大量数据或维护游标位置的信息，这些工作由服务器完成。

  • 多个活动语句

    使用服务器游标时，结果不会存留在游标操作之间的连接上。这就允许同时拥有多个活动的基于游标的语句。

除了静态和不感知游标外，所有服务器游标的操作都取决于基础表的架构。在声明游标后对这些表的架构所做的任何更改将导致随后对此游标进行操作时发生错误。


Specifying Cursors

 访问和更改关系数据

指定游标

在 OLE DB、ODBC 和 ADO 的规范中，游标在 Transact-SQL 语句返回的任何结果集上以隐性方式打开。在执行 Transact-SQL 语句之前，应设置特性或属性以定义游标的特征。然后可调用 API 函数以每次提取一行或多行。API 游标特性或属性的默认设置和 SQL Server 默认结果集具有相同的特征。当游标特性或属性设为默认值时， SQL Server ODBC 驱动程序和 Microsoft SQL Server的 OLE DB 提供程序均实现默认结果集。这种情况下不使用游标。仅当游标特性或属性的默认设置发生任何更改时，驱动程序和提供程序才实现游标。

OLE DB、ODBC、和 ADO 在引用这样的结果集和游标组合时使用不同的术语：

  • OLE DB 使用行集来表示结果集和与其相关的游标行为的组合。

  • ODBC 有时互换使用结果集和游标术语，这是因为它考虑到每一个结果集上都自动映射有一个游标。ODBC 中的行集特指一个提取所返回的行数。

  • ADO 使用记录集，其方式与 OLE DB 使用行集的方式相同。

DB-Library API 支持两种处理结果集的方式。DB-Library 核心函数仅支持将 Transact-SQL 语句的结果作为默认结果集进行处理。核心函数只允许以前向方式每次从结果集中检索一行，并且不支持通过结果集进行更新。如果希望在 DB-Library 应用程序中使用游标处理，那么必须使用 DB-Library 游标库中的特殊函数，而不使用核心函数。

Microsoft SQL Server OLE DB 提供程序、SQL Server ODBC 驱动程序和 DB-Library 默认使用一个 API 服务器游标来实现所请求的游标函数。通过装载 ODBC 游标库，ODBC 客户应用程序可以用客户端游标代替服务器游标。DB-Library 应用程序可以通过设置 DBCLIENTCURSOR 选项来使用客户端游标。OLE DB 不亲自实现客户端游标，但是 ADO API 可以实现。

下面的示例说明了根据当前的 API 游标设置和所执行的 Transact-SQL 语句可以实现的游标或结果集的类型。带有"不合并游标类型"的单元表明正在同时请求 Transact-SQL 服务器游标和 API 游标。这可能会导致未定义的行为，因此不应当这样做。


Transact-SQL 游标

Transact-SQL 游标主要用在存储过程、触发器和 Transact-SQL 脚本中，它们使结果集的内容对其它 Transact-SQL 语句同样可用。

在存储过程或触发器中使用 Transact-SQL 游标的典型进程为：

  1. 声明 Transact-SQL 变量包含游标返回的数据。为每一结果集列声明一个变量。声明足够大的变量以保存由列返回的值，并声明可从列数据类型以隐性方式转换得到的数据类型。

  2. 使用 DECLARE CURSOR 语句把 Transact-SQL 游标与一个 SELECT 语句相关联。DECLARE CURSOR 语句同时定义游标的特征，比如游标名称以及游标是否为只读或只进特性。

  3. 使用 OPEN 语句执行 SELECT 语句并生成游标。

  4. 使用 FETCH INTO 语句提取单个行，并把每列中的数据转移到指定的变量中。然后，其它 Transact-SQL 语句可以引用这些变量来访问已提取的数据值。Transact-SQL 不支持提取行块。

  5. 结束游标时，使用 CLOSE 语句。关闭游标可以释放某些资源，比如游标结果集和对当前行的锁定，但是如果重新发出一个 OPEN 语句，则该游标结构仍可用于处理。由于游标仍然存在，此时还不能重新使用游标的名称。DEALLOCATE 语句则完全释放分配给游标的资源，包括游标名称。在游标被释放后，必须使用 DECLARE 语句来重新生成游标。
监视 Transact-SQL 游标的活动

可以使用 sp_cursor_list 系统存储过程来获得对当前连接可见的游标列表，使用sp_describe_cursor、sp_describe_cursor_columns和 sp_describe_cursor_tables 来确定游标的特征。

在游标打开后，@@CURSOR_ROWS 函数或由 sp_cursor_list 或 sp_describe_cursor 返回的 cursor_rows 列会指出游标中的行数。

在每个 FETCH 语句执行之后，@@FETCH_STATUS 更新以反映最后一次提取的状态。也可以从由 sp_describe_cursor 返回的 fetch_status 列中获取该状态信息。@@FETCH_STATUS 报告游标中的状态，比如超出第一行和最后一行的提取。@@FETCH_STATUS 对于连接来说是全局性的，并在连接游标打开时由每次提取进行重置。如果必须在以后了解状态，就需要在连接中执行另外一个语句之前，把 @@FETCH_STATUS 保存在一个用户变量中。即使下一语句不是 FETCH，也可能是 INSERT、UPDATE 或 DELETE 这些语句，它们能够激发包含可重置 @@FETCH_STATUS 的 FETCH 语句的触发器。sp_describe_cursor 返回的 fetch_status 列对于指定的游标来说是确定的，不受那些引用其它游标的 FETCH 语句的影响，但 sp_describe_cursor 会受引用相同游标的 FETCH 语句的影响，因此使用时必须注意。

完成 FETCH 后，游标将定位在已提取的行上。被提取的行称为当前行。如果游标没有声明为只读游标，就可以执行带有 WHERE CURRENT OF cursor_name 子句的 UPDATE 或 DELETE 语句来修改当前行。

由 DECLARE CURSOR 语句赋给 Transact-SQL 游标的名称可以是全局或局部的。全局游标的名称可由任何位于同一连接上的批处理、存储过程或触发器引用。局部游标名称不能在声明游标的批处理、存储过程或触发器之外被引用。触发器和存储过程中的局部游标因而可以避免来自存储过程或触发器外部的无意引用。

使用游标变量

Microsoft® SQL Server™ 2000 也支持 cursor 数据类型的变量。游标可以有两种方法与一个 cursor 变量相关联：

/* Use DECLARE @local_variable, DECLARE CURSOR and SET. */
DECLARE @MyVariable CURSOR

DECLARE MyCursor CURSOR FOR
SELECT LastName FROM Northwind.dbo.Employees

SET @MyVariable = MyCursor

/* Use DECLARE @local_variable and SET */
DECLARE @MyVariable CURSOR

SET @MyVariable = CURSOR SCROLL KEYSET FOR
SELECT LastName FROM Northwind.dbo.Employees

当游标与 cursor 变量相关联之后，在 Transact-SQL 游标语句中就可以使用 cursor 变量代替游标名称。存储过程输出参数也可指派为 cursor 数据类型，并与一个游标相关联。这就允许存储过程有节制地展示其局部游标。

引用 Transact-SQL 游标

Transact-SQL 游标名称和变量只能由 Transact-SQL 语句引用，而不能由 OLE DB、ODBC、ADO 和 DB-Library 的 API 函数引用。例如，如果要使用 DECLARE CURSOR 和 OPEN 语句生成一个 Transact-SQL 游标，就无法使用 SQLFetch 或 SQLFetchScroll 函数来从 Transact-SQL 游标中提取行。需要游标处理而又使用这些 API 的应用程序应该使用数据库 API 中内建的游标支持代替 Transact-SQL 游标。

通过使用 FETCH 并绑定由 FETCH 返回给程序变量的每一列，可以在应用程序中使用 Transact-SQL 游标。Transact-SQL FETCH 不支持批处理，因此，这是将数据返回给应用程序的效率最低的方法。每提取一行均需往返服务器一次。使用内建在数据库 API 中的游标功能更为有效，可进行多行提取。

当包含在存储过程和触发器中时，Transact-SQL 游标极其有效。这是因为所有操作都编译到服务器上的一个执行计划内，不存在与行提取有关的网络流量。

请参见

@@FETCH_STATUS

FETCH

CLOSE

游标函数

OPEN

DEALLOCATE

WHERE

DECLARE CURSOR

Transact-SQL 游标名称的作用域


API 服务器游标

OLE DB、ODBC、ADO 和 DB-Library API 支持将游标映射至已执行 SQL 语句的结果集。Microsoft® SQL Server™ OLE DB 提供程序、SQL Server ODBC 驱动程序和 DB-Library 动态链接库 (DLL) 通过使用 API 服务器游标执行这些操作。API 服务器游标在服务器上实现，并由 API 游标函数进行管理。当应用程序调用 API 游标函数时，游标操作由 OLE DB 提供程序、ODBC 驱动程序或 DB-Library DLL 传递给服务器。

当在 OLE DB、ODBC 和 ADO 中使用 API 服务器游标时，使用 API 函数和方法实现如下功能：

  1. 打开一个连接

  2. 设置定义游标特征的特性或属性，API 自动将游标映射到每个结果集。

  3. 执行一条或多条 Transact-SQL 语句。

  4. 使用 API 函数和方法提取结果集中的行。

在 DB-Library 中，与 API 服务器游标一起使用特殊的 DB-Library 游标库函数。

当 API 游标特性或属性设为其默认值时，SQL Server OLE DB 提供程序和 SQL Server ODBC 驱动程序使用默认结果集。虽然从技术上说 API 要求游标，但默认游标特征与默认结果集的行为是匹配的。因此，OLE DB 提供程序和 ODBC 驱动程序利用默认结果集实现默认游标选项，这是从服务器中检索行最有效的方法。使用默认结果集时，应用程序可执行任何 Transact-SQL 语句和批处理，但是它在一个连接中只能有一个未完成的语句。这意味着在连接上执行下一个语句之前，应用程序必须处理或者取消由一个语句返回的所有结果集。

当 API 游标特性或属性没有按默认值进行设置时，SQL Server OLE DB 提供程序和 SQL Server ODBC 驱动程序将使用 API 服务器游标代替默认结果集。每次对提取行的 API 函数的调用都会产生到服务器的一次往返，以从 API 服务器游标中提取行。

DB-Library 应用程序使用 DB-Library 游标库函数请求游标。如果 DBCLIENTCURSOR 没有设置，那么 DB-Library 游标库函数将采用与 SQL Server OLE DB 提供程序和 SQL Server ODBC 驱动程序相同的方法来使用 API 服务器游标。

API 服务器游标约束

使用 API 服务器游标时，应用程序不能执行下列语句：

  • 服务器游标中SQL Server 不支持的 Transact-SQL 语句。

  • 返回多个结果集的批处理或存储过程。

  • 包含 COMPUTE、COMPUTE BY、FOR BROWSE 或 INTO 子句的 SELECT 语句。

  • 引用远程存储过程的 EXECUTE 语句。
API 服务器游标实现

SQL Server OLE DB 提供程序、SQL Server ODBC 驱动程序和 DB-Library DLL 使用这些特殊的系统存储过程向服务器示意游标操作。

  • sp_cursoropen 定义与游标和游标选项相关的 SQL 语句，然后生成游标。

  • sp_cursorfetch 从游标中提取一行或多行。

  • sp_cursorclose 关闭并释放游标。

  • sp_cursoroption 设置各种游标选项。

  • sp_cursor 用于请求定位更新。

  • sp_cursorprepare 把与游标有关的 Transact-SQL 语句或批处理编译成执行计划，但并不创建游标。

  • sp_cursorexecute 从由 sp_cursorprepare 创建的执行计划中创建并填充游标。

  • sp_cursorunprepare 废弃由 sp_cursorprepare 生成的执行计划。

这些系统存储过程将在使用 API 服务器游标的 ADO、OLE DB、ODBC 和 DB-Library 应用程序的 SQL Server 事件探查器跟踪中显示。这些记录仅供 SQL Server OLE DB 提供程序、SQL Server ODBC 驱动程序和 DB-Library DLL 内部使用。应用程序可通过数据库 API 的游标功能来使用这些过程的完整功能。在应用程序中直接指定过程的做法不受支持。

当 SQL Server 在某连接上执行语句时，只有在来自第一个语句的所有结果处理完毕或被取消时，才能在连接上执行其它语句。在使用 API 服务器游标时，这个规则仍然成立，但是从应用程序的角度来看，好象 SQL Server 在一个连接上已经开始支持多个活动语句。这是因为完整的结果集存储在服务器游标中，而仅有的传递给 SQL Server 的语句是对 sp_cursor 系统存储过程的执行。SQL Server 执行这些存储过程，且一旦客户端检索该结果集，它就可以开始执行其它语句。OLE DB 提供程序和 ODBC 驱动程序则在把控制返回给应用程序之前始终检索来自 sp_cursor 存储过程的所有结果集。这使应用程序可以插空在多级活动服务器游标中进行提取操作。

下表说明了应用程序如何利用两个语句句柄在一个连接上同时处理两个游标。

语句句柄 1 | 语句句柄 2
将游标特性设置可使用 API 服务器游标。 |
SQLExecDirect 一个 SQL 语句。ODBC 驱动程序调用 sp_cursoropen，然后检索该过程所返回的结果集。 |
 | 将游标特性设置可使用 API 服务器游标。
 | SQLExecDirect 一个 SQL 语句。ODBC 驱动程序调用 sp_cursoropen，然后检索该过程所返回的结果集。
SQLFetchScroll 检索第一个行块。驱动程序调用 sp_cursorfetch，然后检索该过程所返回的结果集。 |
 | SQLFetchScroll 检索第一个行块。驱动程序调用 sp_cursorfetch，然后检索该过程所返回的结果集。
SQLFetchScroll 以检索另一行块。驱动程序调用 sp_cursorfetch，然后检索该过程所返回的结果集。 |
 | SQLFetchScroll 以检索另一行块。驱动程序调用 sp_cursorfetch，然后检索该过程所返回的结果集。
调用 SQLFreeStmt 或 SQLCloseCursor。驱动程序调用 sp_cursorclose。 |
 | 调用 SQLFreeStmt 或 SQLCloseCursor。驱动程序调用 sp_cursorclose。

由于在所有 sp_cursor 存储过程的调用之后连接中不再留有未决结果，那么在假设全部使用 API 服务器游标执行的前提下，就可以在单个连接上并行执行多个 Transact-SQL 语句。

指定 API 服务器游标

下面是有关在 API 中如何使用 API 服务器游标的摘要：

  • OLE DB
    1. 打开会话对象，打开命令对象，并指定命令文本。

    2. 设置行集属性（如 DBPROP_OTHERINSERT、DBPROP_OTHERUPDATEDELETE、DBPROP_OWNINSERT、DBPROP_OWNUDPATEDELETE 等）以控制游标行为。

    3. 执行命令对象。

    4. 使用下列方法提取结果集中的行，如 IRowset::GetNextRows、IRowsetLocate::GetRowsAt、IRowsetLocate::GetRowsAtBookmark 和 IRowsetScroll::GetRowsAtRatio。
  • ODBC
    1. 打开一个连接并调用 SQLAllocHandle 分配语句句柄。

    2. 调用 SQLSetStmtAttr 以设置 SQL_ATTR_CURSOR_TYPE、SQL_ATTR_CONCURRENCY 和 SQL_ATTR_ROW_ARRAY_SIZE 的特性。作为选择，可通过设置 SQL_ATTR_CURSOR_SCROLLABLE 和 SQL_ATTR_CURSOR_SENSITIVITY 特性来指定游标的行为。

    3. 使用SQLExecDirect 或 SQLPrepare 和 SQLExecute 执行 Transact-SQL 语句。

    4. 使用 SQLFetch 或 SQLFetchScroll 提取行或行块。
  • ADO
    1. 定义连接对象和记录集对象，然后对连接对象执行 Open 方法。

    2. 指定 CursorType 和/或 LockType 参数以对记录集执行 Open 方法。

    3. 使用 Move、MoveFirst、MoveLast、MoveNext 和 MovePrevious 记录集方法提取行。
  • DB-Library
    1. DB-Library 核心函数总是使用默认结果集。

    2. 使用 DB-Library 游标库函数来使用 API 服务器游标，无须设置 DBCLIENTCURSOR。

请参见

通过 ADO 使用游标

行集和 SQL Server 游标

使用游标

游标函数


客户端游标

ODBC 和 DB-Library 都支持客户端游标，游标在客户端实现。在客户端游标中，默认结果集用于将整个结果集高速缓存在客户端上，所有的游标操作都在此客户端高速缓存中执行。不使用 Microsoft® SQL Server™ 2000 的任何一个服务器游标功能。客户端游标仅支持只进和静态游标，不支持键集驱动游标和动态游标。

最初实现 DB-Library 客户端游标时 SQL Server 尚不支持服务器游标。ODBC 实现使用 ODBC 游标库的客户端游标。这是为了用于仅支持游标特性默认设置的 ODBC 驱动程序。因为 DB-Library 和 SQL Server ODBC 驱动程序完全支持通过服务器游标的游标操作，所以应限制客户端游标的使用。

客户端游标应只用于减少由于服务器游标不支持所有的 Transact-SQL 语句或批处理所带来的限制。如果需要在不能用服务器游标执行的 Transact-SQL 语句或批处理上使用静态的滚动游标，则可以考虑使用客户端游标。

请参见

ODBC 游标库

游标函数


提取和滚动

从游标中检索行的操作称为提取。提取选项如下：

  • FETCH FIRST

    提取游标中的第一行。

  • FETCH NEXT

    提取上次提取行之后的行。

  • FETCH PRIOR

    提取上次提取行之前的行。

  • FETCH LAST

    提取游标中的最后一行。

  • FETCH ABSOLUTE n

    如果 n 为正整数，则提取游标中从第 1 行开始的第 n 行。如果 n 为负整数，则提取游标中的倒数第 n 行。如果 n 为 0，则没有行被提取。

  • FETCH RELATIVE n

    提取上次所提取行之后的第 n 行。如果 n 为正数，则提取所上次提取行之后的第 n 行。如果 n 为负数，则提取上次所提取行之前的第 n 行。如果 n 为 0，则同一行被再次提取。

打开游标时，游标中当前行的位置逻辑上应位于第一行之前。这使不同的提取选项具有下列行为，如果这是打开游标后的第一次提取操作：

  • FETCH FIRST

    提取游标中的第一行。

  • FETCH NEXT

    提取游标中的第一行。

  • FETCH PRIOR

    不提取行。

  • FETCH LAST

    提取游标中的最后一行。

  • FETCH ABSOLUTE n

    如果 n 为正整数，则提取游标中从第 1 行开始的第 n 行。如果 n 为负整数，则提取游标中的倒数的第 n 行（例如，n = -1 返回游标中的最后一行）。如果 n 为 0，则没有行被提取。

  • FETCH RELATIVE n

    如果 n 为正数，则提取游标中的第 n 行。如果 n 为负数或 0，则没有行被提取。

Transact-SQL 游标限于一次只能提取一行。API 服务器游标则支持每次提取时提取一批行。支持一次提取多行的游标称为块状游标。

游标分类

游标可以按照它所支持的提取选项进行分类：

  • 只进

    必须按照从第一行到最后一行的顺序提取行。FETCH NEXT 是唯一允许的提取操作。

  • 可滚动性

    可以在游标中任何地方随机提取任意行。允许所有的提取操作（但动态游标不支持绝对提取）。

可滚动游标对支持联机应用程序特别有用。可将游标映射为应用程序中的表格或列表框。随着用户向上、向下和在整个表格中滚动，应用程序使用滚动提取从游标中检索用户想要查看的行。

用 API 提取行

实际使用的语句、函数或方法的 API 都有不同的名称来提取行：

  • Transact-SQL 游标使用 FETCH FIRST、FETCH LAST、FETCH NEXT、FETCH PRIOR、FETCH ABSOLUTE(n) 和 FETCH RELATIVE(n) 语句。

  • OLE DB 使用如下方法：IRowset::GetNextRows、IRowsetLocate::GetRowsAt、IRowsetLocate::GetRowsAtBookmark 和 IRowsetScroll::GetRowsAtRatio。

  • ODBC 使用 SQLFetch 函数，它与用于一行的 FETCH NEXT 或 SQLFetchScroll 相同。SQLFetchScroll 支持块状游标和所有的提取选项（第一、最后、下一个、前一个、绝对、相对）。

  • ADO 使用 Move、MoveFirst、MoveLast、MoveNext 和 MovePrevious Recordset 方法获取游标的位置。然后使用 GetRows 记录集方法在那个位置检索一行或多行。也可直接调用 GetRows，将其中的 Start 参数设为想要提取的行数。

  • DB-Library 使用 dbcursorfetch 和 dbcursorfetchex 函数。

请参见

滚动和检索行

提取行

游标函数

FETCH

使用游标

游标函数


控制游标行为

有两种指定游标行为的模式：

游标类型

数据库 API 通常通过把游标分成四种类型来指定它们的行为：只进、静态（有时称为快照或不感知）、键集驱动和动态。

游标行为

SQL-92 标准定义了 DECLARE CURSOR 关键字 SCROLL 和 INSENSITIVE 指定游标行为。有些数据库 API 也支持通过可滚动性和感知性来定义游标的行为。

ADO 和 DB-Library 支持仅指定游标类型，而不是游标行为。

ODBC 支持使用游标类型或游标可滚动性和不感知性行为来指定游标的行为。

在 Microsoft® SQL Server™ 7.0 版之前，用于定义 Transact-SQL 游标的 DECLARE CURSOR 语句仅支持 SCROLL 和 INSENSITIVE 游标行为。在 SQL Server 7.0 中，DECLARE CURSOR 被扩展为支持游标类型关键字。

OLE DB 的游标行为模式与游标行为和游标类型都不同。

不要为游标同时指定游标类型和游标行为。应使用二者之一。因为 ODBC 和 Transact-SQL 游标同时支持游标行为和游标类型，定义游标时可使用 ODBC 或 Transact-SQL。ODBC 规范说明，同时指定游标行为和游标类型可能导致意想不到的结果。


游标类型

ODBC、ADO 和 DB-Library 定义了 Microsoft® SQL Server™ 2000 所支持的四种游标类型。已经扩展了 DECLARE CURSOR 语句，这样您就可以指定 Transact-SQL 游标的四种游标类型。这些游标检测结果集变化的能力和消耗资源（如在 tempdb 中所占的内存和空间）的情况各不相同。游标仅当尝试再次提取行时才会检测到行的更改。数据源没有办法通知游标当前提取行的更改。游标检测这些变化的能力也受事务隔离级别的影响。

SQL Server 支持的四种 API 服务器游标类型是：

  • 静态游标

  • 动态游标

  • 只进游标

  • 键集驱动游标

静态游标在滚动期间很少或更本检测不到变化，虽然它在 tempdb 中存储了整个游标，但消耗的资源很少。尽管动态游标使用 tempdb 的程度最低，在滚动期间它能够检测到所有变化，但消耗的资源也更多。键集驱动游标介于二者之间，它能检测到大部分的变化，但比动态游标消耗更少的资源。

尽管数据库 API 游标模式把只进游标看成一种独立的游标类型，但 SQL Server 却不这样。SQL Server 将只进和滚动都作为能应用到静态游标、键集驱动游标和动态游标的选项。

请参见

DECLARE CURSOR

通过 ADO 使用服务器游标

行集和 SQL Server 游标

游标的实现方式

dbcursoropen


只进游标

只进游标不支持滚动，它只支持游标从头到尾顺序提取。行只在从数据库中提取出来后才能检索。对所有由当前用户发出或由其它用户提交、并影响结果集中的行的 INSERT、UPDATE 和 DELETE 语句，其效果在这些行从游标中提取时是可见的。但是，因为游标不能向后滚动，所以在行提取后对行所做的更改对游标是不可见的，除非用于确定结果集中行位置的值被修改，比如更新一个由聚集索引覆盖的列。

尽管数据库 API 游标模式把只进游标看成一种独特的游标类型，但 Microsoft® SQL Server™ 2000 却不这样认为。SQL Server 将只进和滚动都作为能应用到静态游标、键集驱动游标和动态游标的选项。Transact-SQL 游标支持只进静态、键集驱动和动态游标。数据库 API 游标模型则假定静态、键集驱动和动态游标都是可滚动的。当一个数据库 API 游标的特性或属性被设为只进，则 SQL Server 将它作为只进动态游标来实现。


快速只进游标

Microsoft® SQL Server™ 2000 实现了一个称为快速只进游标的性能优化。快速只进游标有两种环境的支持：

  • Transact-SQL 游标可对 DECLARE CURSOR 指定 FAST_FORWARD 子句。这可以打开一个优化的只进、只读游标。

  • 使用用于 SQL Server 的 Microsoft OLE DB 提供程序，应用程序可以将行集属性 DBPROP_SERVERCURSOR、DBPROP_OTHERINSERT、DBPROP_OTHERUPDATEDELETE、DBPROP_OWNINSERT 和 DBPROP_OWNUPDATEDELETE 设置为 VARIANT_TRUE。

  • 使用 Microsoft SQL Server ODBC 驱动程序的应用程序可以把驱动程序特定的语句属性 SQL_SOPT_SS_CURSOR_OPTIONS 设为 SQL_CO_FFO 或 SQL_CO_FFO_AF。设置 SQL_CO_FFO 要求使用与 DECLARE CURSOR 中的FAST_FORWARD 子句相同的优化参数打开游标。SQL_CO_FFO_AF 要求启用自动提取选项。
使用自动提取选项

尽管通过在 DECLARE CURSOR 中指定 FAST_FORWARD，或在 ODBC 应用程序中指定 SQL_CO_FFO 都能取得一些性能改进，最重要的性能改进是通过在 ODBC 应用程序中指定 SQL_CO_FFO_AF 以启用自动提取选项而获得的。自动提取启用了两个优化，它们能够明显地减少网络流量：

  • 打开游标时，自动从游标中提取第一行或第一批行。这就省去了通过网络发送一个提取请求。

  • 当提取到达游标的末尾时，游标自动关闭。这就省去了通过网络发送一个单独的关闭请求。

最为显著的改进体现在对相对较小、能高速缓存在应用程序内存中的结果集的游标处理上。启用了自动提取的快速只进游标代表了将结果集放入 ODBC 应用程序的最有效方法。当包含 n 行的游标打开了自动提取选项时，ODBC 应用程序可以：

  • 指定行集大小为 n+1。

  • 分配 n+1 个变量的数组保存结果集列中的数据。

  • 将结果集列绑定到数组。

  • 执行生成游标的 SQL 语句。

SQL Server ODBC 驱动程序执行语句时要求打开游标。由于启用了自动提取，服务器将提取并返回 n 行。服务器将尽可能多的行填入返回给客户端的每一个网络数据包。当服务器试图提取第 n+1 行时，它将检测到游标结尾并自动关闭游标。当应用程序随后执行 SQLCloseCursor 或 SQLFreeStmt 时，ODBC 驱动程序不必给服务器发送任何关闭请求。整个操作过程中，只有一个数据包从客户端发送到服务器，且从服务器返回客户端的数据包数目为最少。

快速只进游标的隐性转换

快速只进游标在下列情况下隐性转换为其它游标类型：

  • 如果 SELECT 语句把一个或多个带触发器的表联接到不带触发器的表，则游标转换为静态游标。

  • 如果 SELECT 语句引用 text、ntext 或 image 列，那么如果使用了 SQL Server 的 OLE DB 提供程序或 SQL Server ODBC 驱动程序，则游标将转换为动态游标。

  • 如果快速只进游标不是只读的，则它将转换为动态游标。

  • 如果 SELECT 语句是一个引用链接服务器上一个或多个远程表的分布式查询，则游标将转换为键集驱动游标。

  • 如果 SELECT 语句是一个引用链接服务器上一个或多个远程表的分布式查询，则游标将转换为键集驱动游标。

请参见

行集和 SQL Server 游标

快速只进游标 (ODBC)


静态游标

静态游标的完整结果集在游标打开时建立在 tempdb 中。静态游标总是按照游标打开时的原样显示结果集。

游标并不反映在数据库中所做的任何影响结果集成员的更改，也不反映会使组成结果集的行的列值发生变化的更改。静态游标不会显示游标打开以后在数据库中新插入的行，即使它们符合游标 SELECT 语句的查询条件时也如此。如果组成结果集的行由其它用户更新，新的数据值不会显示在静态游标中。静态游标仍会显示在游标打开以后删除的行。在静态游标中不显示 UPDATE、INSERT 或者 DELETE 操作（除非关闭游标并重新打开），甚至不显示使用与打开游标相同的连接所做的修改。

Microsoft® SQL Server™ 静态游标始终是只读的。

因为静态游标的结果集存储在 tempdb 的工作表中，结果集中的行大小不能超过 SQL Server 表的最大行大小。

Transact-SQL 和 DB-Library 称静态游标为不感知游标。一些数据库 API 将这类游标识别为快照游标。


键集驱动游标

打开游标时， 键集驱动游标中的成员和行顺序是固定的。键集驱动游标由一套被称为键集的唯一标识符（键）控制。键由以唯一方式在结果集中标识行的列构成。键集是游标打开时来自所有适合 SELECT 语句的行中的一系列键值。键集驱动游标的键集在游标打开时建立在 tempdb 中。

对非键集列中的数据值所做的更改（由游标所有者更改或其它用户提交）在用户滚动游标时是可见的。在游标外对数据库所做的插入在游标内是不可见的，除非关闭并重新打开游标。使用 API 函数——如ODBC SQLSetPos 函数，通过游标所做的插入在游标的末尾可见。如果试图提取一个在打开游标后被删除的行，则 @@FETCH_STATUS 将返回一个"行缺少"状态。对键列的更新操作近似于删除旧键值并插入新键值。如果没有通过游标进行更新，则新键值不可见；如果是通过使用 API 函数（如 SQLSetPos）或 Transact-SQL WHERE CURRENT OF 子句和 SELECT 语句（FROM 子句中不包含 JOIN 条件）的游标进行的更新，则新键值在游标的尾部可见。如果插入操作在 FROM 子句中包含远程表，则新键值不可见；试图检索旧键值的操作将得到与检索删除行相同的行丢失提取状态。

说明 在远程表上声明键集驱动游标时不能使用计算列上的索引。可以另外创建一个索引来为该远程表提供唯一性键。


动态游标

动态游标与静态游标相对。当滚动游标时，动态游标反映结果集中所做的所有更改。结果集中的行数据值、顺序和成员在每次提取时都会改变。所有用户做的全部 UPDATE、INSERT 和 DELETE 语句均通过游标可见。如果使用 API 函数（如 SQLSetPos）或 Transact-SQL WHERE CURRENT OF 子句通过游标进行更新，则它们立即可见。在游标外部所做的更新直到提交时才可见，除非将游标的事务隔离级别设为未提交读取。


游标行为

ODBC 和 Transact-SQL 游标都支持使用 SQL-92 的游标可滚动性和敏感性行为来指定游标特性。

游标行为以下面的方式指定：

  • Transact-SQL 游标使用 SQL-92 中的有关语法，在 DECLARE 语句中 CURSOR 关键字之前指定 SCROLL 和 INSENSITIVE。

  • ODBC API 使用 SQL_ATTR_CURSOR_SCROLLABLE 和 SQL_ATTR_CURSOR_SENSITIVITY 语句特性。

请参见

DECLARE CURSOR

游标的实现方式


可滚动性

游标的可滚动行为定义了游标所支持的提取选项。

如果 DECLARE 语句指定了关键字 SCROLL，或如果 SQL_ATTR_CURSOR_SCROLLABLE 被赋给了 SQL_SCROLLABLE，则此游标支持所有的提取选项。

如果在 SQL-92 风格的 DECLARE 语句中省略了关键字 SCROLL，或如果 SQL_ATTR_CURSOR_SCROLLABLE 被赋给了 SQL_NONSCROLLABLE，则游标仅支持提取下一个操作。

请参见

DECLARE CURSOR

SQLSetStmtAttr


敏感性

游标的敏感性行为定义了对基行（用于建立游标）所做的更新是否对游标可见。敏感性也定义了能否通过游标进行更新。

如果在 Transact-SQL DECLARE 语句中指定了关键字 INSENSITIVE，或在 ODBC中将 SQL_ATTR_CURSOR_SENSITIVITY 赋给了 SQL_UNSPECIFIED 或 SQL_INSENSITIVE，则游标不反映数据的变化。游标为只读且不支持更新。

如果从 Transact-SQL DECLARE 语句中省略了关键字 INSENSITIVE，或在 ODBC 中将 SQL_ATTR_CURSOR_SENSITIVITY 赋给了 SQL_SENSITIVE，则游标可以反映由当前用户所做的或由其它用户提交的数据修改。除使用只读游标外，可以使用游标进行定位更新。

请参见

DECLARE CURSOR

SQLSetStmtAttr


游标锁定

在 Microsoft® SQL Server™ 中，游标定义中的 SELECT 语句遵守适用于任何其它 SELECT 语句的相同的事务锁定规则。但在游标中，可根据游标并发级别的规定获取一套额外的滚动锁。

通过任何 SELECT 语句（包括游标定义中的 SELECT 语句）获取的事务锁由下述各项进行控制：

  • 连接的事务隔离级别设置。

  • FROM 子句中指定的任何锁提示。

对于游标和独立的 SELECT 语句，这些锁都会保持直到当前事务结束。当 SQL Server 在自动提交模式下运行时，每个单独的 SQL 语句都是一个事务，当语句结束时，锁即被释放。如果 SQL Server 以显式或隐性事务模式运行，则这些锁将保持到事务提交或者回滚。

例如，下列两个 Transact-SQL 例子中所做的锁定基本相同：

/* Example 1 */
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
GO
BEGIN TRANSACTION
GO
SELECT * FROM authors
GO

/* Example 2 */
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
GO
BEGIN TRANSACTION
GO
DECLARE abc CURSOR STATIC FOR
SELECT * FROM authors
GO
OPEN abc
GO

将事务隔离级别设为可重复读取意味着：Example 1 中的独立 SELECT 语句和 Example 2 中的 DECLARE CURSOR 所包含的 SELECT 语句在所读取的每一行上都生成共享锁，并且这些共享锁将一直保持到事务提交或回滚。

获取锁

尽管游标和独立的 SELECT 语句在获取的事务锁类型方面遵守相同的规则，但它们获取锁的时间不同。由独立的 SELECT 语句或游标生成的锁总是在检索到一行时获取。对于独立的 SELECT 语句，执行语句时会检索所有行。但游标会根据游标的类型在不同时间检索行：

  • 静态游标在打开游标时检索整个结果集。这将在打开时锁定结果集中的每一行。

  • 键集驱动游标在打开游标时检索结果集中每行的关键字。这将在打开时锁定结果集中的每一行。

  • 动态游标（包括通常的只进游标）只有在提取行时才进行检索。仅当行已提取时才能获取锁。

  • 快速只进游标在获取锁的时间上会有所不同，这取决于查询优化器所选择的执行计划。如果选择动态计划，则在提取行之前不会得到任何锁。如果生成了工作表格，则行被读进工作表并在打开时锁定。

游标也支持自己的并发规定，其中某些会在每次提取时生成额外的锁。这些滚动锁会一直保持到下次提取操作或直到游标关闭，以其中较早者为准。如果将提交时保持打开游标的连接选项设为有效，则这些锁在提交和回滚操作过程中将一直保持。


游标和事务

Microsoft® SQL Server™ 2000 支持设置连接或数据库选项，以控制在提交和回滚时是将游标关闭还是保持打开状态。

若将此选项设为在提交或回滚时关闭游标，则当游标关闭时，所有滚动锁都自动释放。若将此选项设为在提交时游标仍保持打开，则所有活动的滚动锁将一直保持到下次提取或游标关闭。所有事务锁（包括游标中的行事务锁）在提交或回滚时都被释放，不论游标是否为打开状态。

请参见

SET CURSOR_CLOSE_ON_COMMIT

sp_dboption


游标并发

Microsoft® SQL Server™ 2000 支持服务器游标的四个并发选项：

  • READ_ONLY

  • OPTIMISTIC WITH VALUES

  • OPTIMISTIC WITH ROW VERSIONING

  • SCROLL LOCKS

READ_ONLY

不允许通过游标定位更新，且在组成结果集的行中没有锁。

OPTIMISTIC WITH VALUES

乐观并发控制是事务控制理论的一个标准部分。乐观并发控制用于这样的情形，即在打开游标及更新行的间隔中，只有很小的机会让第二个用户更新某一行。当某个游标以此选项打开时，没有锁控制其中的行，这将有助于最大化其处理能力。如果用户试图修改某一行，则此行的当前值会与最后一次提取此行时获取的值进行比较。如果任何值发生改变，则服务器就会知道其他人已更新了此行，并会返回一个错误。如果值是一样的，服务器就执行修改。

选择这个并发选项将迫使用户或程序员承担责任，处理那些表示其它用户已经对其进行了修改的错误。应用程序收到这种错误时采取的典型措施就是刷新游标，获得其新值，然后让用户决定是否对新值进行修改。在 SQL Server 6.5 版或早期版本中，text、ntext 和 image 列不用于并发比较。

OPTIMISTIC WITH ROW VERSIONING

此乐观并发控制选项基于行版本控制。使用行版本控制，其中的表必须具有某种版本标识符，服务器可用它来确定该行在读入游标后是否有所更改。在 SQL Server 中，这个性能由 timestamp 数据类型提供，它是一个二进制数字，表示数据库中更改的相对顺序。每个数据库都有一个全局当前时间戳值：@@DBTS。每次以任何方式更改带有 timestamp 列的行时，SQL Server 先在时间戳列中存储当前的 @@DBTS 值，然后增加 @@DBTS 的值。如果某个表具有 timestamp 列，则时间戳会被记到行级。服务器就可以比较某行的当前时间戳值和上次提取时所存储的时间戳值，从而确定该行是否已更新。服务器不必比较所有列的值，只需比较 timestamp 列即可。如果应用程序对没有 timestamp 列的表要求基于行版本控制的乐观并发，则游标默认为基于数值的乐观并发控制。

SCROLL LOCKS

这个选项实现悲观并发控制。在悲观并发控制中，在把数据库的行读入游标结果集时，应用程序将试图锁定数据库行。在使用服务器游标时，将行读入游标时会在其上放置一个更新锁。如果在事务内打开游标，则该事务更新锁将一直保持到事务被提交或回滚；当提取下一行时，将除去游标锁。如果在事务外打开游标，则提取下一行时，锁就被丢弃。因此，每当用户需要完全的悲观并发控制时，游标都应在事务内打开。更新锁将阻止任何其它任务获取更新锁或排它锁，从而阻止其它任务更新该行。然而，更新锁并不阻止共享锁，所以它不会阻止其它任务读取行，除非第二个任务也在要求带更新锁的读取。

滚动锁

根据在游标定义的 SELECT 语句中指定的锁提示，这些游标并发选项可以生成滚动锁。滚动锁在提取时在每行上获取，并保持到下次提取或者游标关闭，以先发生者为准。下次提取时，服务器为新提取中的行获取滚动锁，并释放上次提取中行的滚动锁。滚动锁独立于事务锁，并可以保持到一个提交或回滚操作之后。如果提交时关闭游标的选项为关，则 COMMIT 语句并不关闭任何打开的游标，而且滚动锁被保留到提交之后，以维护对所提取数据的隔离。

所获取滚动锁的类型取决于游标并发选项和游标 SELECT 语句中的锁提示。

锁提示 | 只读 | 乐观数值 | 乐观行版本控制 | 锁定
无提示 | 未锁定 | 未锁定 | 未锁定 | 更新
NOLOCK | 未锁定 | 未锁定 | 未锁定 | 未锁定
HOLDLOCK | 共享 | 共享 | 共享 | 更新
UPDLOCK | 错误 | 更新 | 更新 | 更新
TABLOCKX | 错误 | 未锁定 | 未锁定 | 更新
其它 | 未锁定 | 未锁定 | 未锁定 | 更新

*指定 NOLOCK 提示将使指定了该提示的表在游标内是只读的。

指定游标并发选项

并发选项在每个游标环境下的指定都不同：

  • Transact-SQL 游标

    在 DECLARE CURSOR 语句中指定 READ_ONLY、SCROLL_LOCK 和 OPTIMISTIC 关键字。OPTIMISTIC 关键字指定乐观行版本控制，Transact-SQL 游标不支持乐观数值并发选项。

  • ADO 应用程序

    指定 Recordset 对象 LockType 属性中的 adLockReadOnly、adLockPessimistic、adLockOptimistic 或 adLockBatchOptimistic。

  • ODBC 应用程序

    将语句特性 SQL_ATTR_CONCURRENCY 设置为 SQL_CONCUR_READ_ONLY、SQL_CONCUR_ROWVER、SQL_CONCUR_VALUES 或 SQL_CONCUR_LOCK。

  • DB-Library 应用程序

    将 dbcursoropen 参数 concuropt 设置为 CUR_READONLY、CUR_OPTCC（用于乐观行版本控制）、CUR_OPTCCVAL 或 CUR_LOCKCC。

请参见

DECLARE CURSOR

通过 ADO 使用服务器游标

dbcursoropen


游标事务隔离级别

通过结合游标并发设置的锁定状态、该游标 SELECT 中指定的任何提示，以及事务隔离级别选项，可以确定事务锁定的状态。

Microsoft® SQL Server™ 2000 支持下列游标事务隔离级别：

提交读

SQL Server 在将行读入游标期间，会获取一个共享锁，但在读完行之后会立即释放。因为排它锁会阻塞共享锁请求，所以游标无法读取其它任务已经更新但尚未提交的行。提交读是 SQL Server 和 ODBC的默认隔离级别设置。

未提交读

SQL Server 在将行读入游标期间不需要任何锁，并且不提供任何排它锁。游标可以使用已经更新但尚未提交的值来填充。在 SQL Server 中用户将绕过所有锁定事务控制机制。

可重复读或可串行读

如同 READ COMMITTED 中一样，在将行读入游标时，SQL Server 对每行都请求一个共享锁，但如果游标是在事务中打开的，则此共享锁将保持到事务结束，而不是在读取此行之后释放。这与在 SELECT 语句指定 HOLDLOCK 具有同样的效果。

请参见

自定义事务隔离级别

调整事务隔离级别

游标并发


使用定位操作更改行

可更新游标支持通过游标更新行的数据修改语句。当定位在可更新游标中的某行上时，您可以执行更新或删除操作，这些操作针对用于在游标中建立当前行的基表行。这些就称为定位更新。

定位更新在打开游标的同一个连接上执行。这就允许数据修改共享与游标相同的事务空间，并且使游标保持的锁不会阻止更新。

有两种方法在游标中执行定位更新：

  • UPDATE 或 DELETE 语句中的 Transact-SQL WHERE CURRENT OF 子句。

  • 数据库 API 定位更新函数或方法，如 ODBC SQLSetPos 函数。
使用 Transact-SQL 执行定位更新

Transact-SQL WHERE CURRENT OF 子句典型用于 Transact-SQL 存储过程、触发器以及脚本（当需要根据游标中特定行进行修改时）。存储过程、触发器、或脚本将：

  • DECLARE 和 OPEN 游标。

  • 用 FETCH 语句在游标中定位于一行。

  • 用 WHERE CURRENT OF 子句执行 UPDATE 或 DELETE 语句。用 DECLARE 语句中的 cursor_name 作为 WHERE CURRENT OF 子句中的 cursor_name。
使用 API 执行定位更新

因为通过 OLE DB、ADO 和 DB-Library API 函数和方法创建的游标没有名称，所以它们不能在 WHERE CURRENT OF 子句里使用。然而，ODBC 支持使用 SQLGetCursorName 函数为 API 服务器游标得到名称。在设置游标特性并通过执行 Transact-SQL 语句打开游标后，可使用 SQLGetCursorName 函数得到游标的名称。在游标中定位后，引用由 SQLGetCursorName 返回的名称执行带有 WHERE CURRENT OF 子句的 UPDATE 或 DELETE 语句。但不建议使用此方法。最好使用 ODBC API 中的定位更新函数。

数据库 API 支持两种不同的方法对API 服务器游标执行定位操作。ODBC 和 DB-Library 共享一个模型，OLE DB 和 ADO 共享另一个。

在 ODBC 和 DB-Library 中，将游标中的列与程序变量绑定，然后在游标中定位到特定行。如果执行定位更新，在程序变量中将数据值更改为新值。调用这些函数以执行定位操作：

  • ODBC：SQLSetPos 函数

  • DB-Library：dbcursor 函数

这些函数具有下列选项：

SQLSetPos(SQL_POSITION)

仅用于 ODBC，将 ODBC 游标定位到当前行集中的特定行。

SQLSetPos(SQL_REFRESH)、dbcursor(CRS_REFRESH)

使用游标当前所定位行的值刷新被绑定到结果集列上的程序变量。

SQLSetPos(SQL_UPDATE)、dbcursor(CRS_UPDATE)

使用绑定到结果集列上的程序变量中所存储的值来更新游标中的当前行。

SQLSetPos(SQL_DELETE)、dbcursor(CRS_DELETE)

删除游标中的当前行。

dbcursor(CRS_LOCKCC)

仅用于 DB-Library，锁定当前行。

OLE DB 和 ADO 使用不同模型支持定位更新。

在 OLE DB 中，当定位于行集里的某行时，调用 IRowsetChange::SetData 或 IRowsetChange::DeleteRows 方法进行定位更新。如果 OLE DB 提供程序支持 IRowsetUpdate::Update，则使用 IRowsetChange 方法所做的改变将被高速缓存至调用 IRowsetUpdate::Update。如果 OLE DB 提供程序不支持 IRowsetUpdate::Update，则使用 IRowsetChange 方法所做的更改会立即完成。

在 ADO 中，当定位于行集里的某行时，调用 Recordset 对象的 Update 或 Delete 方法来执行定位更新。如果 OLE DB 提供程序支持 IRowsetUpdate::Update，则使用 Recordset 对象的 Update 或 Delete方法所做的改变将被高速缓存至调用 Recordset 对象的 UpdateBatch 方法时。如果 OLE DB 提供程序不支持 IRowsetUpdate::Update，则使用 Recordset 对象的 Update 或 Delete 方法所做的更改会立即完成。

请参见

WHERE

定位更新 (ODBC)

dbcursor


游标编程详细信息

选择正确的游标选项是开发 Microsoft® SQL Server™ 2000 应用程序的重要部分。

使用块状游标可以减少在客户端和 SQL Server 之间的网络往返作业，从而提高性能。如果执行不受请求的游标类型支持的 Transact-SQL 语句，SQL Server 可以隐式转换游标类型。SQL Server 异步填充大型键集驱动游标的键集，这就缩短了游标打开和提取第一行之间的时间。


选择游标类型

按照不同的变量选择游标类型，包括：

  • 结果集的大小。

  • 可能需要的数据的百分比。

  • 打开的游标的性能。

  • 游标操作的需要，例如滚动或定位更新。

  • 由其它用户对数据所做修改的可见性级别。

如果没有做更新，则默认设置对于小结果集是适合的，但对于大结果集，使用动态游标更为适合，因为用户可能在检索许多行之前就可以找到答案。

选择游标类型的规则

选择游标类型时遵循的一些简单规则：

  • 对于单独选择（返回一行）或其它小结果集，使用默认设置。将小结果集高速缓存在客户端，并在此高速缓存中滚动而不是要求服务器实现一个游标，其效率更高。

  • 将整个结果集提取到客户端（如产生报表）时，使用默认设置。默认结果集是将数据传送到客户端的最快方式。

  • 如果应用程序使用定位更新，就不能使用默认结果集。

  • 如果应用程序使用多个活动语句，就不能使用默认结果集。如果仅使用游标支持多个活动语句，可选择快速只进游标。

  • 对于任何生成多结果集的 Transact-SQL 语句或 Transact-SQL 语句批都必须使用默认结果集。

  • 动态游标比静态或键集驱动游标的打开速度更快。当打开静态和键集驱动游标时，必须建立内部临时工作表，而动态游标则不需要。

  • 在联接中，键集驱动和静态游标可能比动态游标更快。

  • 如果要做绝对提取，必须使用键集驱动或静态游标。

  • 静态和键集驱动游标增加了对 tempdb 的使用。静态服务器游标在 tempdb 中建立整个游标，键集驱动游标则在 tempdb 中建立键集。

  • 如果游标在回滚操作期间必须保持打开状态，请使用同步静态游标，并将 CURSOR_CLOSE_ON_COMMIT 设为 OFF。

使用服务器游标时，对 API 提取函数或方法的每次调用都会产生到服务器的一个往返过程。应用程序应使用块状游标最小化这些往返过程，同时每次提取时返回适当大数目的行。

请参见

游标类型


块状游标

API 服务器游标并不限于每次只提取一行，它们可以一次检索到多行。当与客户端/服务器数据库（如 Microsoft® SQL Server™）一起使用时，可以更有效地一次提取多行。每次提取返回的行数称为行集大小。行集的大小大于 1 的游标称为块状游标。在所支持的 API 中，可以用下列方法使用块状游标：

  • ODBC
    1. 将 SQL_ATTR_ROWSET_SIZE 语句属性设为行集的大小。

    2. 使用列式或行式绑定将列绑定到变量数组，以保存返回行中的数据。每个数组的元素数目等于行集的大小。

    3. 对 SQLFetchScroll 的每次调用都将提取通过 SQL_ATTR_ROWSET_SIZE 设定的行数。
  • OLE DB
    1. 分配一个行句柄数组，并将每个句柄所代表的列绑定到变量数组以保存列数据。

    2. 调用 IRowset::GetNextRows，其中，将 cRows 参数设为行句柄数组中的句柄数和 prghRows 参数中的行句柄数组的地址。
  • ADO
    1. 定义一个变量，为要检索的行数保存数据。

    2. 根据需要，可使用记录集方法 Move、MoveFirst、MoveLast、MoveNext 和 MovePrevious 定位游标中的适当位置。

    3. 调用记录集方法 GetRows，其中，array 参数保存用于保存行的变量的地址，而 Crows 参数保存要返回的行数。根据需要，可使用 Start 参数表示在记录集中的提取位置。
  • DB-Library
    1. 分配用于保存每列数据的数组，并使用dbcursorbind 将每个数组绑定到它的目标列。

    2. 调用 dbcursorfetchex，其中将 rownum 参数设为要提取的行数。

请参见

行集和 SQL Server 游标

游标行集大小

dbcursorfetchex

dbcursorbind


隐性游标转换

应用程序可以请求一个游标类型，然后执行不受所请求类型的服务器游标支持的一个 Transact-SQL 语句。Microsoft® SQL Server™ 返回一个错误，指出游标类型已经改变。

以下因素可触发 SQL Server 将游标从一种类型隐性转换为另一种类型。

步骤 | 触发转换的操作 | 只进 | 快速只进 | 键集驱动 | Dynamic | 转至步骤
1 | 游标引用包含 TOP 子句的视图。 | 变为静态。 | 变为静态。 | 变为静态。 | 变为静态。 | 完成
2 | 查询 FROM 子句没有引用表。 | 变为静态。 |  | 变为静态。 | 变为静态。 | 完成
3 | 查询包含： 选择列表聚合 GROUP BY UNION DISTINCT HAVING | 变为静态。 |  | 变为静态。 | 变为静态。 | 完成
4 | 查询在触发器内引用 inserted 或 deleted 表。 | 变为静态。 |  | 变为静态。 | 变为静态。 | 完成
5 | 查询将触发器表联接到另一个表。 |  | 变为静态。 |  |  | 完成
6 | 没有指定 READ_ONLY。 |  | 变为静态。 |  |  | 8
7 | ODBC API 服务器游标引用 text、ntext 或 image 列。 |  | 变为动态。 |  |  | 8
8 | 查询生成内部工作表，例如 ORDER BY 的列没有被索引覆盖。 | 变为键集。 |  |  | 变为键集。 | 10
9 | 查询引用链接服务器中的远程表。 | 变为键集。 | 变为键集。 |  | 变为键集。 | 10
10 | 查询至少引用了一个没有唯一索引的表。 |  |  | 变为静态。 |  | 完成
11 | 游标引用 text、ntext 或 image 列；并且查询包含 TOP 子句。 |  |  | 变为键集。 |  | 完成

除了 SQL Server 7.0 中的限制外 SQL Server 6.5 版本还包含以下限制：

  • 如果请求动态游标，并且 Transact-SQL 语句包含与索引或子查询不匹配的 ORDER BY，则此游标会被转换成键集驱动或静态游标。如果所有的表都有唯一索引，但没有覆盖 ORDER BY 的索引，则游标转换为键集驱动游标。如果至少有一个表没有覆盖 ORDER BY 的索引，且至少有一个表没有唯一索引（不一定是同一个表），则游标转换成静态。

    如果索引列左边的索引没有被 ORDER BY引用，则不能使用此索引列覆盖 ORDER BY。例如，如果一个索引定义为使用 MyTable (LastName, FirstName)，则不能使用此索引覆盖使用 ORDER BY FirstName 的语句。

SQL Server 6.0 版关于动态游标的限制定义更为简单：

  • 如果请求一个动态游标，并且至少有一个表没有唯一索引，则此游标转换成静态游标。

如果正在使用 API 服务器游标并得到消息指出游标类型已被更改，可以调用以下函数来查看SQL ServerL 打开的游标类型：

  • ODBC：调用 SQLGetInfo 得到 SQL_CURSOR_TYPE 特性。

  • DB-Library：调用 dbcursorinfoex 并引用 dbcursorinfoex 返回的 DBCURSORINFO 结构中的 Type字段。

  • Transact-SQL：使用 sp_describe_cursor 并引用此过程返回的游标中的 model 和 scrollable 列。

请参见

隐性游标转换 (ODBC)

dbcursorinfoex


异步填充

Microsoft® SQL Server™ 2000 提供了异步填充大键集驱动游标或静态游标的性能优化。键集驱动和静态游标使用在 tempdb 中建立的工作表。键集驱动游标使用工作表存储它们的键集（标识游标中行的键的集合）。静态游标则使用工作表存储组成游标的行。如果 SQL Server 查询优化器估计键集驱动或静态游标中返回的行数将超过 sp_configure cursor threshold 参数的值，服务器就会启动另一个线程来填充工作表。控制权立即返回到应用程序，应用程序开始提取游标中前面的行，而无须等到整个工作表填充完以后才开始执行首次提取。

异步填充游标需要额外的开销。更有效的方法是不要异步填充小游标，从而使 sp_configure cursor threshold 值不至于设得太低。将异步填充保留给大型游标的使用。

@@CURSOR_ROWS函数报告游标中的行数。如果对具有正被填充的工作表的游标选择 @@CURSOR_ROWS，@@CURSOR_ROWS 将返回一个负数。返回数字的绝对值就是目前工作表中已填充的行数。例如，如果在填充键集驱动游标的键集时选择 @@CURSOR_ROWS，但 1,243 个键已包含在键集中，则 @@CURSOR_ROWS 返回的值为 -1243。

请参见

@@CURSOR_ROWS

sp_configure


Transact-SQL 游标名称的作用域

Microsoft® SQL Server™ 2000 支持 DECLARE CURSOR 语句上的 GLOBAL 和 LOCAL 关键字以定义游标名称的作用域。GLOBAL 指定游标名称对连接是全局性的。LOCAL 指定游标名称对含有 DECLARE CURSOR 语句的存储过程、触发器或批处理是局部性的。

在 Microsoft® SQL Server™ 7.0 版之前的版本中，Transact-SQL 游标的名称对连接而言是全局的。可以执行一个创建游标的存储过程，然后调用另一个存储过程从此游标中提取行：

USE pubs
GO
CREATE PROCEDURE OpenCrsr AS

DECLARE SampleCrsr CURSOR FOR
SELECT au_lname
FROM authors
WHERE au_lname LIKE 'S%'

OPEN SampleCrsr
GO

CREATE PROCEDURE ReadCrsr AS
FETCH NEXT FROM SampleCrsr
WHILE (@@FETCH_STATUS <> -1)
BEGIN
   FETCH NEXT FROM SampleCrsr
END
GO

EXEC OpenCrsr /* DECLARES and OPENS SampleCrsr. */
GO
EXEC ReadCrsr /* Fetches the rows from SampleCrsr. */
GO
CLOSE SampleCrsr
GO
DEALLOCATE SampleCrsr
GO

局部游标为存储过程和触发器中执行的游标提供了重要的保护作用。全局游标可以在声明它们的存储过程或触发器的外部被引用。因此，存储过程或触发器的外部语句可能会在无意中更改它们。因为不能在存储过程以外引用局部游标，因此局部游标比全局游标更安全，除非故意将局部游标作为游标输出参数返回调用方。

因为可以在存储过程或触发器的外部引用全局游标，所以全局游标可能会在无意中影响其它语句。例如，在存储过程中创建了一个名为 xyz 的全局游标，在存储过程结束时此游标是打开的。当此存储过程完成后若要使用 xyz 来声明另一个全局游标，则会因为使用重复的名称错误而导致失败。

全局游标和局部游标有各自的名称空间，因此可以同时有相同名称的全局游标和局部游标。接受游标命名参数的Transact-SQL 语句也支持 GLOBAL 关键字来标识名称的作用域。如果没有指定 GLOBAL，且在游标名称参数中同时有指定了此名称的全局游标和局部游标，则引用此局部游标。

如果 LOCAL和 GLOBAL 都没有指定，则数据库选项default to local cursor 控制由 DECLARE CURSOR 语句使用的默认值。如果 default to local cursor 为 true，Transact-SQL 游标默认为是局部的。如果此选项为 false，Transact-SQL 游标默认为是全局游标。在 SQL Server 2000版中，default to local cursors 选项默认为 FALSE，以便与 SQL Server 早期版本中的内容匹配。

使用 DECLARE 和 OPEN 局部游标的存储过程可以将游标传递出去以供要求调用的存储过程、触发器或批处理使用。这可以通过使用由新的 CURSOR VARYING 数据类型定义的 OUTPUT 参数来实现。游标变量只能用作 OUTPUT 参数。不能将它们用于输入参数。当存储过程完成时游标必须是打开的，以便将游标返回 OUTPUT 参数。也可以通过新的 CURSOR 数据类型来声明局部变量以包含对局部游标的引用。

USE pubs
GO
/* Create a procedure with a cursor output parameter. */
CREATE PROCEDURE OpenCrsr @OutCrsr CURSOR VARYING OUTPUT AS

SET @OutCrsr = CURSOR FOR
SELECT au_lname
FROM authors
WHERE au_lname LIKE 'S%'

OPEN @OutCrsr
GO

/* Allocate a cursor variable. */
DECLARE @CrsrVar CURSOR

/* Execute the procedure created earlier to fill
   the variable. */
EXEC OpenCrsr @OutCrsr = @CrsrVar OUTPUT

/* Use the variable to fetch the rows from the cursor. */
FETCH NEXT FROM @CrsrVar
WHILE (@@FETCH_STATUS <> -1)
BEGIN
   FETCH NEXT FROM @CrsrVar
END

CLOSE @CrsrVar

DEALLOCATE @CrsrVar
GO

数据库 API 不支持存储过程中的游标输出参数。不能直接从数据库 API 函数执行包含游标输出参数的存储过程。而只能从另一个存储过程、触发器、Transact-SQL 批处理或脚本执行这些存储过程。

只有在明确释放时或关闭连接时 GLOBAL 游标才可用。创建 LOCAL 游标的存储过程、触发器或批处理终止时将隐性释放 LOCAL 游标，除非游标已作为参数返回。当调用过程的代码中引用游标的参数或变量超出作用域时，将隐性地释放 LOCAL 游标。

请参见

DECLARE CURSOR

sp_dboption


Getting Server Cursor Metadata

访问和更改关系数据

获得服务器游标元数据

有两种方法可以获得描述服务器游标的元数据：

  • 通过数据库 API 使用 API 服务器游标的应用程序如 ADO、OLE DB、ODBC 或 DB-Library 通常使用 API 的功能来获得关于游标状态的信息。

  • Transact-SQL 脚本、存储过程和触发器可以使用在本主题中讨论的 Transact-SQL 函数和系统存储过程来获得关于 Transact-SQL 游标的信息。

有几个系统函数可以报告服务器游标或指派给游标变量的服务器游标的状态信息：

  • CURSOR_STATUS

    表示游标状态为打开或关闭，或者游标变量当前是否与游标相关联。

  • @@FETCH_STATUS

    表示为连接执行的最后一次提取操作的成功或失败。

  • @@CURSOR_ROWS

    报告为连接打开的上一个游标中填充的行数。

有几个系统存储过程报告服务器游标或指派给游标变量的服务器游标的特性：

  • sp_describe_cursor

    返回描述游标特性的游标，如游标的作用域、名称、类型、状态和行数。

  • sp_describe_cursor_columns

    返回描述游标中每一列特性的游标，如此列的名称、位置、大小和数据类型。

  • sp_describe_cursor_tables

    返回描述游标所引用的基本表的游标。

  • sp_cursor_list

    返回列出连接中当前所有可见游标的游标。sp_cursor_list 返回的游标格式与 sp_describe_cursor 返回的游标格式是相同的。

这些系统存储过程将它们的结果集作为输出游标变量返回。数据库 API 不支持游标变量，因此不能从应用程序调用这些过程，而只能在 Transact-SQL 脚本、存储过程和批处理中调用它们。应用程序应当使用数据库 API 的游标功能来获得 API 服务器游标的元数据。

必须注意这些函数和存储过程返回的状态信息，特别是 @@FETCH_STATUS。每次对为连接打开的任何游标执行 FETCH 语句时，@@FETCH_STATUS 返回的信息都会改变。在执行了几个其它语句后可能需要引用状态信息的存储过程或触发器应当立即将 @@FETCH_STATUS 保存在 FETCH 语句后的整型变量中。即使在 FETCH 和测试状态的语句之间的批处理中没有 FETCH 语句，也可以重置 @@FETCH_STATUS。如果插入的 INSERT、UPDATE 或 DELETE 语句触发了触发器，则此触发器可以打开并从游标中提取。@@FETCH_STATUS 因此将将包含触发器中的上一个 FETCH 语句的状态。

存储过程报告它们用于特定游标的状态信息，因此它们的状态信息不受其它游标操作的影响。它们的状态信息仍然受相同游标上操作的影响，因此在使用存储过程返回的状态信息时必须加以注意。

请参见

@@CURSOR_ROWS

@@FETCH_STATUS

CURSOR_STATUS

sp_cursor_list

sp_describe_cursor

sp_describe_cursor_columns

sp_describe_cursor_tables


Using Cursors with Distributed Queries

访问和更改关系数据

通过分布式查询使用游标

通过分布式查询使用游标时，若提供程序支持必要的 OLE DB 功能，则不感知（静态）和键集驱动游标类型均受支持。通过分布式查询请求的动态或只进游标会被隐性地转换为键集驱动游标。

如果分布式查询中的所有本地和远程表都有唯一的键，则该查询支持键集驱动游标。有关分布式查询支持键集游标的条件，请参见 OLE DB 提供程序的键集驱动游标要求。

如果分布式查询允许键集驱动游标，则在满足提供程序可更新性要求的情况下，键集驱动游标也可用于更新任何基础远程表。有关远程表上进行更新的要求，请参见 OLE DB 提供程序的 UPDATE 和 DELETE 语句要求。

请参见

DECLARE CURSOR

使用游标

通过 ADO 使用服务器游标


分布式查询

分布式查询可以访问来自多种异类数据源的数据，而这些数据可存储在相同或不同的计算机上。Microsoft® SQL Server™ 2000 通过使用 OLE DB（Microsoft 通用数据访问应用程序接口 (API) 规范）支持分布式查询。

此"分布式查询"部分讨论一般的分布式查询概念，并描述如何在分布式查询中使用 Transact-SQL 语句访问单独的 SQL Server 实例上的数据或非 SQL Server 数据源。SQL Server 联机丛书的其它部分包含附加的相关信息。

有关信息 | 请参见
配置 OLE DB 提供程序和链接服务器 | 配置链接服务器 为分布式查询配置 OLE DB 提供程序
分布式事务中的分布式查询 | 分布式查询和分布式事务
链接服务器 | 访问外部数据
ODBC 和分布式查询 | 支持分布式查询的架构函数
有关特定 OLE DB 提供程序的信息 | 用 SQL Server 测试过的 OLE DB 提供程序
性能问题 | 优化分布式查询
OLE DB 提供程序开发人员参考 | 用于分布式查询的 OLE DB 提供程序参考
分布式查询中的 Transact-SQL 语言 | 外部数据和 Transact-SQL 系统存储过程
解决分布式查询问题 | 分布式查询错误信息

分布式查询支持 SQL Server 用户访问：

  1. 存储在多个 SQL Server 实例中的分布式数据。

  2. 存储在各种可使用 OLE DB 提供程序访问的关系和非关系数据源中的异类数据。

OLE DB 提供程序将数据表现在称为行集的表格格式对象中。SQL Server 2000 允许在 Transact-SQL 语句中象引用 SQL Server 表一样引用 OLE DB行集。

可以在 SELECT、INSERT、UPDATE 和 DELETE Transact-SQL 语句中直接引用外部数据源中的表和视图。因为分布式查询使用 OLE DB 作为基础接口，所以它们不仅可以访问使用 SQL 查询处理器的传统关系数据库管理系统 (DBMS)，而且还能访问由不同功能和完善度的数据源所管理的数据。只要拥有数据的软件通过 OLE DB 提供程序在表格格式行集中表现其数据，就可在分布式查询中使用该数据。

说明 在 SQL Server 中使用分布式查询与 Microsoft Access 先前支持的通过 ODBC 使用链接表功能类似。这种功能现在已内置到以 OLE DB 为外部数据接口的 SQL Server 中。

示例

您是一家大型保险公司的地区销售经理，该公司在很多国家/地区都有分支机构。每个地区的分公司都选用某个产品存储其销售数据。英国分公司用 Oracle 存储其数据，澳大利亚分公司用 Access，西班牙分公司用 Microsoft Excel，而美国分公司则用 SQL Server。您需要过去三年的季度报表，其中要列出保险策略、分支机构和季度业绩最好的销售代表。以上三个查询都可以通过在 SQL Server 上运行单个分布式查询实现。

请参见

配置链接服务器

分布式查询和分布式事务

安全帐户委派


访问外部数据

若要访问来自 OLE DB 数据源的数据，需为 Microsoft® SQL Server™ 2000 提供下列信息： <0L type=disc>

  • 表现数据源的 OLE DB 提供程序的名称。

  • OLE DB 提供程序定位数据源所需的任何信息。

  • OLE DB 数据源能够以行集表现的对象名称，或可发送给 OLE DB 提供程序、使之表现一个行集的查询。能够表现为行集的对象称作远程表。能够生成行集的查询称作直接传递查询。

  • 或者，可以向 SQL Server 提供 OLE DB 数据源的有效登录 ID。

    • SQL Server 2000 支持在 Transact-SQL 语句中引用异类 OLE DB 数据源的这些方法。链接服务器名称和特殊计算机名称。

    链接服务器名称

    链接服务器是已定义到 Microsoft® SQL Server™ 2000 的虚拟服务器，其中包含了访问 OLE DB 数据源所需的全部信息。链接服务器名称使用系统存储过程 sp_addlinkedserver 定义。链接服务器定义包含定位 OLE DB 数据源所需的全部信息。然后使用 sp_addlinkedsrvlogin 将本地 SQL Server 登录映射为链接服务器中的登录。于是可以通过下列方法引用远程表：

    • 在 Transact-SQL 语句中用作表或视图引用的四部分名称中，将链接服务器名称作为服务器名称。该名称的其它三部分引用表现为行集的链接服务器中的对象。

    • 将链接服务器名称作为 OPENQUERY 函数的输入参数。OPENQUERY 函数向 OLE DB 提供程序发送需执行的命令。返回的行集可在 Transact-SQL 语句中用于表或视图的引用。
    特殊名称

    对没有定义为链接服务器名称的 OLE DB 数据源执行不常用查询时，使用特殊名称。在 SQL Server 2000 中，OPENROWSET 和 OPENDATASOURCE 函数提供了连接信息，借以从 OLE DB 数据源访问数据。

    OPENROWSET 和 OPENDATASOURCE 只应在引用不常访问的 OLE DB 数据源时使用。对于需要经常访问的数据源，应定义链接服务器。无论 OPENDATASOURCE 还是 OPENROWSET 都不能提供链接服务器定义的全部功能，包括安全管理和查询目录信息的能力。每次调用这些函数时，都必须提供所有的连接信息（包括密码）。

    OPENROWSET 和 OPENDATASOURCE 看起来象函数，但其实是宏并且不支持将 Transact-SQL 变量提供为参数。

    可以在任何返回行集的 OLE DB 提供程序中使用 OPENROWSET 函数，并且可以在 Transact-SQL 语句中使用表或视图引用的位置使用该函数。OPENROWSET 函数指定如下内容：

    • 连接 OLE DB 数据源所需的全部信息。

    • 生成行集的对象名称或查询。

    OPENDATASOURCE 函数将连接信息作为由四部分组成的对象名称的一部分加以提供。该函数只支持使用 catalog.schema.object 注释表现多个行集的 OLE DB 提供程序。OPENDATASOURCE 函数可在 Transact-SQL 语法的链接服务器名称处使用。OPENDATASOURCE 指定如下内容：

    • 用于访问数据源的 OLE DB 提供程序中登记为 PROGID 的名称。

    • 连接字符串，用以指定将各种连接属性传递到 OLE DB 提供程序。连接字符串语法是关键字-值对的序列。其基本语法在 Microsoft® 数据访问软件开发工具包中定义，且每个提供程序都记录了所支持的特定关键字/值对。有关连接字符串的更多信息，请参见 OPENDATASOURCE。
    访问链接服务器

    使用 sp_addlinkedserver 创建链接服务器后，可以使用下列方法访问它：

    • 分布式查询。通过 SELECT、INSERT、UPDATE 和 DELETE 语句并使用基于链接服务器的名称访问此链接服务器中的表。

    • 远程存储过程。可以使用四部分名称对链接服务器执行存储过程。

    使用 sp_addserver 可将运行 SQL Server 实例的服务器定义为远程服务器。然后就可以在远程存储过程调用中引用该远程服务器。远程服务器组件作为现有应用程序的兼容功能而被保留。

    应用程序在移植到 SQL Server 2000 时，在一段时间内可能需要运行一些新代码（针对链接服务器定义使用分布式查询）和一些旧代码（使用远程服务器定义）。链接服务器和远程服务器都使用相同的名称空间，因此链接服务器或远程服务器定义的名称必须与被远程访问的服务器的网络名称不同。使用不同的服务器名称定义一个条目，然后使用 sp_setnetname 将其与远程服务器的网络名称关联起来。

    说明 因为系统存储过程可以简明地显示出参数的使用，所以这部分的示例使用系统存储过程配置链接服务器。但是，SQL Server 企业管理器也支持配置链接服务器。有关更多信息，请参见配置链接服务器。


使用链接服务器名称标识数据源

在定义了链接服务器以后，就可以在 Transact-SQL 语句中使用形如 linked_server_name.catalog.schema.object_name 的四部分名称以在链接服务器中引用数据对象。下表对这四部分名称进行了分别描述：

部分名称 | 描述
linked_server_name | 引用 OLE DB 数据源的链接服务器
catalog | OLE DB 数据源中包含该对象的目录
schema | 目录中包含该对象的架构
object_name | 架构中的数据对象

Microsoft® SQL Server™ 使用链接服务器名称标识 OLE DB 提供程序和数据源。Catalog、schema 和 object_name 参数传递给 OLE DB 提供程序以标识特定的数据对象。当链接服务器指代 SQL Server 实例时，catalog 指代数据库，schema 指代所有者 ID。

下图说明一个四部分的 SQL Server 名称如何解析为 OLE DB 提供程序中的对象。

在使用链接服务器中的对象工作时应始终使用完全合法名称。不支持隐性解析链接服务器中表的 dbo 所有者名称。因此，即使链接服务器是另一个 SQL Server 实例，没有架构名称的查询也会生成 7314 号错误。SQL Server 不支持链接服务器上的全文检索。

请参见

OPENQUERY


使用特殊名称标识数据源

当引用 OLE DB 数据源的频率不够高，尚不足以保证配置链接服务器时，可使用特殊名称作为表引用。在 Microsoft® SQL Server™ 2000 中，可以使用 OPENROWSET 和 OPENDATASOURCE 函数提供特殊名称。

OPENROWSET 和 OPENDATASOURCE 函数都提供特殊连接信息。可以使用这些函数指定访问 OLE DB 数据源所需的全部信息。但是，不可以将 OPENROWSET 和 OPENDATASOURCE 函数互换。

当 OLE DB 提供程序通过指定表（或视图）名称或通过指定返回行集的查询返回行集时，可以使用 OPENROWSET 函数。而 OPENROWSET 函数则可以在 Transact-SQL 语句中代替表或视图名称。

本示例使用特殊名称从 Microsoft Access 版本的 Northwind 示例数据库中检索数据。
SELECT *
FROM OPENROWSET('Microsoft.Jet.OLEDB.4.0',
   'c:\MSOffice\Access\Samples\northwind.mdb';'admin';'',
   Customers)

仅当提供程序表现行集并 使用 catalog.schema.object 注释时，才可以使用 OPENDATASOURCE。该函数可以在能够使用链接服务器名的 Transact-SQL 语法位置中使用。即，在 catalog.schema.object 注释中，OPENDATASOURCE 可以用作表或视图名称（四部分）的第一部分。

-- SELECT from a table on another instance of SQL Server.
SELECT *
FROM OPENDATASOURCE(
          'SQLOLEDB',
          'Data Source=ServerName;User ID=MyUID;Password=MyPass'
                   ).Northwind.dbo.Categories

只有在特殊情况下（当不可能配置永久链接服务器时）访问外部数据时，才应使用 OPENROWSET 和 OPENDATASOURCE。这两个函数不提供链接服务器中的所有可用功能，如管理登录映射、查询链接服务器元数据的能力和配置各种连接设置（如超时值）的能力。

OPENROWSET 和 OPENDATASOURCE 的参数不支持变量。必须将它们指定为字符串字面值。如果需要将变量作为参数传递到这两个函数中，则可以使用 EXEC 语句动态地构造并执行包含这些变量的查询字符串。

请参见

OPENDATASOURCE

OPENROWSET


将直接传递查询作为表使用

Microsoft® SQL Server™ 2000 将直接传递查询作为未解释的查询字符串发送到 OLE DB 数据源。查询必须使用 OLE DB 数据源可以接受的语法。Transact-SQL 语句像使用常规表引用一样使用直接传递查询结果。

本示例使用直接传递查询从 Microsoft Access 版本的 Northwind 示例数据库中检索结果集。

SELECT *
FROM OpenRowset('Microsoft.Jet.OLEDB.4.0',
   'c:\northwind.mdb';'admin'; '',
   'SELECT CustomerID, CompanyName
         FROM Customers
         WHERE Region = ''WA'' ')

有两种方法可以从 OLE DB 提供程序中生成行集：

  • 引用提供程序可以表现为表格格式行集的数据源中的对象。所有提供程序都支持这一功能。

  • 向提供程序发出一条命令，该命令应可以由提供程序处理且处理结果可以表现为行集。此功能要求提供程序支持 OLE DB Command 对象和它的所有强制接口。

当提供程序支持 Command 对象时，这两个 Transact-SQL 函数可以用来发送命令（称为直接传递查询）：

  • OPENQUERY 使用链接服务器名称向 OLE DB 数据源发送命令字符串。

  • OPENROWSET 和 OPEBDATASOURCE 支持向 OLE DB 数据源发送命令字符串。可以使用特殊名称引用所得到的行集。

OLE DB 规范并未定义一种可供所有 OLE DB 提供程序使用的命令语言。OLE DB 提供程序可以支持与所表现数据相关的任意命令语言。表现关系数据库数据的 OLE DB 提供程序通常支持 SQL 语言。其它类型的提供程序（例如表现电子邮件文件或网络目录中的数据的提供程序）通常支持另一种语言。

请参见

FROM

OPENDATASOURCE

OPENQUERY

OPENROWSET


外部数据和 Transact-SQL

只要提供程序支持所需的 OLE DB 接口，则后文中所提到的每一类 Transact-SQL 语句均可以使用。以下是允许在通过基于链接服务器名称或特殊名称来访问的远程表中使用的 Transact-SQL 语言子集。

  • 所有具有标准格式 SELECT select_list FROM 子句 WHERE 子句的查询均允许使用。当 new_table_name 引用远程表时，不允许使用 SELECT 语句的 INTO new_table_name 子句。

  • 在 SELECT、INSERT、UPDATE 和 DELETE 语句中，不能用单部分或四部分表名限制远程表中的列。应在 FROM 子句中指定远程表的别名，并且别名应用于限定列名。

  • 当指定一个来自远程表的大型对象 (LOB) 列作为 SELECT 语句的 select_list 中的项时，SELECT 语句不能包含 ORDER BY 子句。

  • IS NULL 和 IS NOT NULL 谓词不能引用远程表中的 LOB 列。

  • 当查询中还包含 WHERE 子句时，在分布式查询中不能使用 GROUP BY ALL。支持不指定 ALL 的 GROUP BY 子句。

  • 只要提供程序符合 OLE DB 对 INSERT 语句的要求，就允许对远程表使用 INSERT 语句。有关更多信息，请参见 OLE DB 提供程序的 INSERT 要求。

  • 如果提供程序符合特定表中的 OLE DB 接口要求，则允许对远程表使用 UPDATE 和 DELETE 语句。有关更多信息，请参见 OLE DB 提供程序的 UPDATE 和 DELETE 语句要求。

  • 如果提供程序符合对某个远程表进行更新的条件，那么当在 UPDATE 或者 DELETE 语句中（UPDATE 或 DELETE remote_table WHERE CURRENT OF cursor_name）指定了远程表时，即可通过在分布式查询中定义的游标对该远程表进行更新或删除。有关更多信息，请参见通过分布式查询使用游标。

  • 不支持对远程表使用 READTEXT、WRITETEXT 和 UPDATETEXT语句。

  • 如果在 Microsoft® SQL Server™ 2000 进程的外部实例化提供程序（提供程序选项 AllowInProcess 为 0），则在更新或插入操作中不能引用具有大对象数据类型（如 text、ntext 或 image）的列。有关更多信息，请参见为分布式查询配置 OLE DB 提供程序。

  • 不允许对链接服务器使用数据定义语言语句（如 CREATE、ALTER 或 DROP 语句）。

  • 不允许对链接服务器执行其它数据库级的操作或语句。

  • STATIC 或 INSENSITIVE 游标可以引用远程表。如果 OLE DB 提供程序满足某些要求，则 KEYSET 游标可以引用远程表。有关这些要求的更多信息，请参见 OLE DB 提供程序的键集驱动游标要求。其它类型的游标不可以引用远程表。

  • 只对 SQL Server 数据源支持存储过程。


设置用于分布式查询的 SQL-92 选项

必须先将连接的 ANSI_NULLS 和 ANSI_WARNINGS 选项打开，然后才能执行分布式查询。

请参见

SET ANSI_DEFAULTS


在分布式查询中使用事务

Microsoft® SQL Server™ 2000 支持使用 ITransactionLocal（本地事务）和 ITransactionJoin（分布式事务）OLE DB 接口对外部数据进行基于事务的访问。使用分布式事务，SQL Server 可以确保涉及多个节点的事务在所有节点中均已提交或返回。如果提供程序不支持参与分布式事务（不支持 ITransactionJoin），则在事务内时仅允许对该提供程序执行只读操作。

如果遇到不允许的语句，该语句将返回错误信息，而用户事务（如果有）将中止执行。

请参见

事务


数据类型映射

OLE DB 提供程序根据 OLE DB 类型标识符 DBTYPE 来表现其数据的数据类型。通过映射数据在 OLE DB 数据类型和本机 Microsoft® SQL Server™ 数据类型之间转换数据类型：

  • 从 OLE DB 数据类型到 SQL Server 本机数据类型。这种转换在 SQL Server 从 OLE DB 数据源读取数据时发生（在 SELECT 语句中或 UPDATE、INSERT 和 DELETE 语句的读取过程中）。

  • 从 SQL Server 本机数据类型到 OLE DB 数据类型。这种转换在 SQL Server 将数据写入 OLE DB 数据源（其中被修改的表是远程表）时发生，主要是在 INSERT或 UPDATE 语句中。
从 OLE DB 提供程序到 SQL Server 的数据类型映射

从 OLE DB 提供程序映射到 SQL Server 的数据类型定义所允许的比较和表达式，以及涉及远程数据的有效显式转换。remote_column 数据类型对应被映射的本地数据类型，如下面的"数据类型映射"表所示。

表达式中远程表列的类型规则如下所述：在相同的上下文中，如果下表中对应的被映射 SQL Server 数据类型合法，则 Transact-SQL 表达式中给定的远程列值也合法。例如，考虑以下表达式：

local_column operator remote_column

local_column 是本地表列，remote_column 是远程表列。如果 operator 对于本地列的数据类型和 remote_column 的 DBTYPE 映射数据类型都是有效的运算符，那么该表达式也是有效的。

同样，如果 remote_column 的 DBTYPE 映射为 SQL Server 的原始 data_type_2，并且允许从 data_type_2 到 data_type_1 的显式转换，则允许使用 CAST (remote_column AS data_type_1)。例如，提供程序端的 DBTYPE_DATE 数据类型列可以转换成 SQL Server 中的 datetime 列。但是，DBTYPE_DATE 数据不能直接转换成 varchar。

到 SQL Server 类型的映射由 DBTYPE 和描述该列的 DBCOLUMNFLAGS 值来确定。该信息通过 COLUMNS 架构行集或者 IColumnsInfo 接口从提供程序获得。如果是 COLUMNS 架构行集，则 DATA_TYPE 和 COLUMN_FLAGS 列表示这些值。如果是 IColumnsInfo::GetColumnInfo 接口，则 DBCOLUMNINFO 结构的成员 wType 和 dwFlags 表示这些信息。

下面的"数据类型映射"表显示从 OLE DB 提供程序到 SQL Server 的数据类型映射。对于给定的列，如果给定其 DBTYPE 和 DBCOLUMNFLAG 值，就可以找到对应的 SQL Server 数据类型。

DBTYPE | DBCOLUMNFLAGS | SQL Server 数据类型
DBTYPE_I1 |  | numeric(3, 0)1
DBTYPE_I2 |  | smallint
DBTYPE_I4 |  | Int
DBTYPE_I8 |  | bigint
DBTYPE_UI1 |  | tinyint
DBTYPE_UI2 |  | numeric(5,0)
DBTYPE_UI4 |  | numeric(10,0)
DBTYPE_UI8 |  | numeric(20,0)
DBTYPE_R4 |  | Float
DBTYPE_R8 |  | Real
DBTYPE_NUMERIC |  | numeric
DBTYPE_DECIMAL |  | decimal
DBTYPE_CY |  | money
DBTYPE_BSTR | DBCOLUMNFLAGS_ISLONG = true | ntext
DBTYPE_BSTR | DBCOLUMNFLAGS_ISFIXEDLENGTH = true | nchar
DBTYPE_BSTR | DBCOLUMNFLAGS_ISFIXEDLENGTH = false | nvarchar
DBTYPE_IDISPATCH |  | 错误
DBTYPE_ERROR |  | 错误
DBTYPE_BOOL |  | Bit
DBTYPE_VARIANT |  | nvarchar(4000)
DBTYPE_IUNKNOWN |  | 错误
DBTYPE_GUID |  | uniqueidentifier
DBTYPE_BYTES | DBCOLUMNFLAGS_ISLONG = true，或最大的列大小大于 8,000 字节 | image
DBTYPE_BYTES | DBCOLUMNFLAGS_ISROWVER = true, DBCOLUMNFLAGS_ISFIXEDLENGTH = true, 列大小为 8 | timestamp
DBTYPE_BYTES | DBCOLUMNFLAGS_ISFIXEDLENGTH = true | binary
DBTYPE_BYTES | DBCOLUMNFLAGS_ISFIXEDLENGTH = false | varbinary
DBTYPE_STR | DBCOLUMNFLAGS_ISFIXEDLENGTH = true | char
DBTYPE_ STR | DBCOLUMNFLAGS_ISFIXEDLENGTH = false | varchar
DBTYPE_STR | DBCOLUMNFLAGS_ISLONG = true，或最大的列大小大于 4,000 个字符 | text
DBTYPE_WSTR | DBCOLUMNFLAGS_ISFIXED | nchar
DBTYPE_WSTR | DBCOLUMNFLAGS_ISFIXEDLENGTH = false | nvarchar
DBTYPE_WSTR | DBCOLUMNFLAGS_ISLONG = true，或最大的列大小大于 4,000 个字符 | ntext
DBTYPE_UDT |  | 错误
DBTYPE_DATE |  | datetime
DBTYPE_DBDATE |  | 错误
DBTYPE_DBTIME |  | 错误
DBTYPE_DBTIMESTAMP |  | datetime
DBTYPE_ARRAY |  | 错误
DBTYPE_BYREF |  | 忽略
DBTYPE_VECTOR |  | 错误
DBTYPE_RESERVED |  | 错误

1 numeric(p,s) 表示精度为 p、小数位数为 s 的 SQL Server 数据类型 numeric。

说明 如果数据必须转换成不同于默认显示的本机数据类型，则必须进行显式转换（使用 CAST 或 CONVERT 函数）。有关更多信息，请参见 CAST 和 CONVERT。

从 SQL Server 到 OLE DB 提供程序的数据类型映射

本机 SQL Server 数据类型使用相同的表名映射到 OLE DB 类型。如果两种条件存在任意一种，则允许从 SQL Server 类型 S1 映射到给定的 OLE DB 类型 T。

  • 相应的映射可以在表 1 中找到。

  • 表 1 中定义了允许的从数据类型 S1 到另一 SQL Server 数据类型 S2 的隐式转换以及从 S2 到 T 的映射。


分布式查询中的排序规则

在 Microsoft® SQL Server™ 7.0 版中，本地 SQL Server 始终只有一个服务器范围的排序规则（代码页和排序次序）。来自远程数据源的所有字符数据都通过本地服务器范围的排序规则解释。但是，Microsoft SQL Server 2000 支持多种排序规则（每个列的排序规则都可以不同）；每个字符值都有一个关联的排序规则属性。SQL Server 2000 解释来自远程数据源的字符数据的排序规则属性并相应地处理它。

SQL Server 2000 将远程数据的排序规则用于字符数据（Unicode 和非 Unicode）的比较和排列操作。根据数据源是否对应于 SQL Server，以不同的方式确定远程字符数据的排序规则信息：

  • SQL Server 数据 OLE DB 提供程序自动报告它所返回的每列的排序规则。

  • 对于不在 SQL Server 中，但是已知其排序规则与 SQL Server 所支持的排序规则之一相同的远程表，管理员可以指定 OLE DB 数据源的默认排序规则作为链接服务器定义的一部分。然后，SQL Server 可以将该默认排序规则作为从链接服务器返回的所有列的排序规则。

SQL Server 确定远程字符列的排序规则后，在对远程表列进行转换、比较和操作时遵循与本地列相同的规则。有关 SQL Server 应用于排序规则及其支持的排序规则名称的规则的更多信息，请参见 SQL Server 排序规则基础知识。

通过使用 sp_serveroption 定义的两个链接服务器选项控制 SQL Server 是否及如何使用链接服务器的排序规则：

  • UseRemoteCollation 指定是使用远程列的排序规则还是本地服务器的排序规则。如果为 TRUE，SQL Server 数据源使用远程列的排序规则，非 SQL Server 数据源使用 CollationName 中指定的排序规则。如果为 FALSE，则分布式查询总是使用本地服务器实例的默认排序规则，而且 CollationName 和远程列的排序规则将被忽略。

  • 如果 UseRemoteCollation 是 TRUE，而数据源不是 SQL Server 数据源，则 CollationName 指定远程数据源使用的排序规则名称。此名称必须是 SQL Server 支持的排序规则之一。如果访问的是 SQL Server 以外的 OLE DB 数据源，但该数据源的排序规则与 SQL Server 的某个排序规则匹配，则使用该选项。SQL Server 数据源报告其列的排序规则，而对引用 SQL Server 数据源的链接服务器忽略 CollationName。

    说明 启用远程排序规则的唯一方法是通过链接服务器选项，因此，使用特殊名称（如 OPENROWSET 和 OPENDATASOURCE）所构造的查询无法使用远程字符数据的排序规则信息。另外，所有升级到 SQL Server 2000 的 SQL Server 7.0 中的链接服务器都设置为 UseRemoteCollation = False。

下表汇总了 SQL Server 确定每列的排序规则的方式。

 | Use Remote Collation = ON |  | Use Remote Collation = OFF
链接服务器类型 | 未设置排序规则名称 | 排序规则名称 设置（为 CollX） | 未设置排序规则名称 | 排序规则名称 设置（为 CollX）
SQL Server | 远程列的实际排序规则 | 远程列的实际排序规则 | 本地 SQL Server 实例的默认排序规则 | 本地 SQL Server 实例的排序规则
其它 | 本地 SQL Server 实例的默认排序规则 | CollX | 本地 SQL Server 实例的默认排序规则 | 本地 SQL Server 实例的默认排序规则


从链接服务器中获得元数据

OLE DB 提供程序通过 IDBSchemaRowset 接口表现有关其数据的元数据，而该接口可用于检索 OLE DB 行集对象格式的信息。Microsoft® SQL Server™ 2000 使用此接口获得有关远程表的元数据。元数据通过下列接口表现给用户：

  • SQL Server ODBC 驱动程序特定的函数。有关更多信息，请参见 SQLLinkedServers 和 SQLLinkedCatalogs。

  • SQL Server OLE DB 提供程序特定的行集。有关更多信息，请参见架构行集。

  • 多种 ODBC 目录函数接受引用链接服务器上对象的两部分名称。

  • 系统存储过程与现有的返回链接服务器上的元数据的目录存储过程类似。

下列系统存储过程可用于从链接服务器中检索元数据。

sp_linkedservers | sp_primarykeys
sp_catalogs | sp_indexes
sp_column_privileges | sp_table_privileges
sp_columns_ex | sp_tables_ex
sp_foreignkeys |

说明 SQL Server 是在分布式查询中支持存储过程的唯一数据源。

SQL Server 使用 OLE DB 提供程序的 IDBSchemaRowset 接口实现这些元数据扩展。因为这个接口是可选接口，所以提供程序可能不会实现该接口。在用这样的提供程序定义的链接服务器上不能使用元数据。

请参见

系统存储过程

升级目录存储过程


用 SQL Server 测试过的 OLE DB 提供程序

已用某些 OLE DB 提供程序测试过 Microsoft® SQL Server™ 2000 分布式查询。与 SQL Server 2000 一起安装的某些测试过的 OLE DB 提供程序包括：

  1. 用于 SQL Server 的 Microsoft OLE DB 提供程序

  2. 用于 ODBC 的 Microsoft OLE DB 提供程序

  3. 用于 Jet 的 Microsoft OLE DB 提供程序

  4. 用于 DTS 包的 Microsoft OLE DB 提供程序

  5. 用于 Oracle 的 Microsoft OLE DB 提供程序

其它测试过的提供程序可通过 Microsoft Windows® 2000（用于 Microsoft 目录服务的 Microsoft OLE DB 提供程序和用于 Microsoft 索引服务的 Microsoft OLE DB 提供程序）和 Microsoft Host Integration Server（用于 DB2 的 Microsoft OLE DB 提供程序）获得。

下表列出了已使用 SQL Server 分布式查询测试过的 OLE DB 提供程序。通过在 OPENQUERY 和 OPENROWSET 函数中指定一个直接传递查询，即可在 SELECT 语句中引用所有这些提供程序支持。

数据源 | 提供程序名称 | 在四部分的名称中使用 | 在直接传递查询中使用 | 在 INSERT、UPDATE 或 DELETE 中使用 | 在分布式事务中使用
SQL Server 6.5 或更高版本 | 用于 SQL Server 的 Microsoft OLE DB 提供程序 | 是 | 是 | 是 | 是
ODBC 数据源 | 用于 ODBC 的 Microsoft OLE DB 提供程序 | 是* | 是 | 是* | 是*
Microsoft Access (Jet) 数据库 | 用于 Jet 4,00 版的 Microsoft OLE DB 提供程序 | 是 | 是 | 是（如果数据库是用 Microsoft Jet 4.0 或其早期版本创建的，则为否） | 否
Microsoft Excel 电子表格 | 用于 Jet 4,00 版的 Microsoft OLE DB 提供程序 | 是 | 是 | 是 | 否
数据转换服务包数据源对象 | 用于 DTS 包的 Microsoft OLE DB 提供程序 | 是 | 是 | 否 | 否
Oracle 数据库 | 用于 Oracle 2.6 版的 Microsoft OLE DB 提供程序 | 是 | 是 | 是 | 是
Microsoft Windows® 2000 目录 | 用于 Microsoft 目录服务的 Microsoft OLE DB 提供程序 | 否 | 是 | 否 | 否
本地文件系统（通过索引服务） | 用于 Microsoft 索引服务的 Microsoft OLE DB 提供程序（需要 Microsoft Windows NT® 4.0 Service Pack 4 或更高版本） | 否 | 是 | 否 | 否
IBM DB2 数据库 | 用于 DB2 的Microsoft OLE DB 提供程序 | 是 | 是 | 是 | 否

* 用于 ODBC 的 Microsoft OLE DB 提供程序的能力取决于所使用的 ODBC 驱动程序。提供程序可能并不支持某些 ODBC 驱动程序的全部功能。

尽管 Microsoft 仅支持引用由 Microsoft 测试的提供程序的分布式查询，但分布式查询应当可以使用任何的 OLE DB 提供程序，只要这些提供程序满足"用于分布式查询的 OLE DB 提供程序参考"中所列出的要求。

如果提供程序不支持在四部分名称中使用，则可以通过使用直接传递查询在 OPENQUERY 或 OPENROWSET 函数中引用它。


用于 SQL Server 的 Microsoft OLE DB 提供程序

用于 SQL Server 的 Microsoft OLE DB 提供程序为 Microsoft® SQL Server™ 2000 数据库提供了 OLE DB 接口。通过用于 SQL Server 的 OLE DB 提供程序，SQL Server 分布式查询可以查询 SQL Server 远程实例中的数据。

创建链接服务器以访问 SQL Server 数据库

  1. 执行 sp_addlinkedserver 创建链接服务器，指定 SQLOLEDB 作为 provider_name，指定运行 SQL Server 远程实例的服务器的网络名称作为 data_source。

    例如，若要创建一个名为 LinkSQLSrvr 的链接服务器，以便对运行于网络名称为 NetSQLSrvr 的服务器上的 SQL Server 实例进行操作，请执行：

    sp_addlinkedserver N'LinkSQLSrvr', ' ', N'SQLOLEDB', N'NetSQLSrvr'
  2. 将每个需要访问链接服务器的本地 SQL Server 登录映射为链接服务器上的 SQL Server 授权登录。

    下例将本地登录 Joe 的访问权限映射到名为 LinkedSQLSrvr 的链接服务器上的 SQL Server 授权登录 Visitor。

    sp_addlinkedsrvlogin N'LinkSQLSrvr', false, N'Joe', N'Visitor', N'VisitorPwd'

对运行 SQL Server 7.0 版或更早版本的服务器执行分布式查询时，必须升级早期版本上的目录存储过程，以确保分布式查询可以正常运行。例如，如果服务器运行的是 SQL Server 7.0 版实例，则在可以从运行 SQL Server 2000 实例的服务器中的分布式查询内引用目录存储过程之前，必须将它升级到 SQL Server 2000。有关更多信息，请参见升级目录存储过程 (OLE DB)。

更新远程 SQL Server 表时，本地服务器或客户端不接收为该更新而激发的触发器所产生的任何结果集或消息。

当使用四部分的名称时，一定要指定架构名称。没有在分布式查询中指定架构名称将会禁止 OLE DB 查找表。当引用本地表时，如果未指定所有者姓名，SQL Server 将使用默认值。下列 SELECT 语句将产生 7314 号错误，即使链接服务器登录映射为链接服务器上 Northwind 数据库中的用户 dbo。

sp_addlinkedserver @server = N'LinkServer',
    @srvproduct = N' ',
    @provider = N'SQLOLEDB',
    @datasrc = N'ServerNetName',
    @catalog = N'Northwind'
GO
SELECT *
FROM LinkServer.Northwind..Shippers

下面的示例定义了链接服务器和远程服务器，它们都访问网络名称为 othersite 的同一台计算机。链接服务器定义使用与远程服务器的网络名称相同的名称；而远程服务器定义则使用其它名称。

/* Create a linked server definition to othersite. */
EXEC sp_addlinkedserver 'othersite', N'SQL Server'

/* Create a remote server definition using a
   fictitious name. */
EXEC sp_addserver 'RPCothersite'

/* Set the fictitious nameto the network name faraway. */
EXEC sp_setnetname 'RPCothersite', 'othersite'

然后，就可以在分布式查询或者远程过程调用中引用这两个名称。

/* A distributed query referencing othersite. */
SELECT *
FROM othersite.Northwind.dbo.Employees
/* A remote procedure call to the same server. */
EXEC RPCothersite.master.dbo.sp_who
/* Distributed queries can be used to execute
   stored procedures on the other server. */
EXEC othersite.master.dbo.sp_who

通过链接服务器执行的存储过程和通过远程服务器执行的存储过程之间的登录映射机制不同。

链接 SQL Server 中的事务考虑事项

用于 SQL Server 的 Microsoft OLE DB 提供程序不支持嵌套事务。因此，对于隐性或显式事务的内部数据修改操作和分布式分区视图上的数据修改操作，应将 XACT_ABORT 设置为 ON。

在隐性或显式事务或分布式分区视图内部，不支持对同一 SQL Server 实例的环回连接。

请参见

分布式查询和分布式事务


聚集 SQL Server 中的链接服务器考虑事项

当在 SQL Server 群集中通过没有随 Microsoft® SQL Server™ 2000 提供的 OLE DB 提供程序配置链接服务器时，一定要确保在群集的所有节点上都安装这些 OLE DB 提供程序。此外，定义链接服务器的任何属性都应是位置透明的，不应包含假定 SQL Server 总是运行在给定的聚集节点上的信息。

下面的示例根据运行 SQL Server 的服务器定义链接服务器，并在 SELECT 语句中使用一个四部分名称引用其中一个远程表。

sp_addlinkedserver @server = N'LinkServer',
    @srvproduct = N' ',
    @provider = N'SQLOLEDB',
    @datasrc = N'ServerNetName',
    @catalog = N'Northwind'
GO
SELECT *
FROM LinkServer.Northwind.dbo.Shippers
GO
环回链接服务器

可以定义链接服务器指回（环回）到在其上定义它们的服务器。当在单服务器网络中测试使用分布式查询的应用程序时，环回服务器是很有用的。

例如，在名为 MyServer 的服务器上执行下面的 sp_addlinkedserver 存储过程将定义一个环回链接服务器：

sp_addlinkedserver @server = N'MyLink',
    @srvproduct = N' ',
    @provider = N'SQLOLEDB',
    @datasrc = N'MyServer',
    @catalog = N'Northwind'
GO

使用 MyLink 作为服务器名称的 Transact-SQL 语句通过 SQLOLEDB 提供程序循环并环回到本地服务器。

在分布式事务中不能使用环回链接服务器。从分布式事务内部对环回链接服务器使用分布式查询命令将导致下面的错误：

Msg: 3910 Level: 16 State: 1
[Microsoft][ODBC SQL Server Driver][SQL Server]Transaction context in use by another session.


多个 SQL Server 实例上的分布式查询

在运行多个 SQL Server 实例的服务器上指定 Microsoft® SQL Server™ 2000 实例时，不需要更改分布式查询中使用的 Transact-SQL 元素的语法。可以使用以下两种方法在分布式查询中指定实例：

  • 使用 sp_addlinkedserver 的 @datasrc 参数中的 'server_name/instance_name' 语法指定服务器名称。

  • 在连接字符串中指定 Server=server_name; INSTANCENAME=instance_name。

如果没有指定实例，分布式查询将连接到指定服务器上 SQL Server 2000 的默认实例。

下面的示例在名为 London 的服务器上指定一个名为 Payroll 的特定实例：

-- Define a linked server on an instance of SQL Server using @datasrc.
sp_addlinkedserver
    @server = 'LondonPayroll1',
    @provider = 'SQLOLEDB',
    @datasource = 'London/Payroll'

-- Define a linked server on an instance of SQL Server using
-- INSTANCENAME in a provider string.
sp_addlinkedserver
    @server = 'LondonPayroll2',
    @provider = 'SQLOLEDB',
    @provstr = 'Server=London;INSTANCENAME=Payroll'

-- Specify an instance of SQL Server in OPENDATASOURCE
-- using Data Source.
SELECT *
FROM OPENDATASOURCE(
          'SQLOLEDB',
          'Data Source=London/Payroll;User ID=MyUID;Password=MyPass'
                   ).Northwind.dbo.Categories

-- Specify an instance of SQL Server in OPENROWSET
-- using a provider string.
SELECT a.*
FROM OPENROWSET(
          'SQLOLEDB',
          'Data Source=London;INSTANCENAME=Payroll;
           User ID=MyUID;Password=MyPass',
           Northwind.dbo.Categories
                ) AS a

-- Specify an instance of SQL Server in OPENROWSET
-- using a the datasource parameter.
SELECT a.*
FROM OPENROWSET(
          'SQLOLEDB','London/Payroll','MyUID','MyPass',
          'SELECT * FROM Northwind.dbo.Categories'
                ) AS a


用于 ODBC 的 Microsoft OLE DB 提供程序

用于 ODBC 的 OLE DB 提供程序为 ODBC 数据源提供 OLE DB 接口。通过使用用于 ODBC 的 OLE DB 提供程序，Microsoft® SQL Server™ 分布式查询可以访问所有 ODBC 数据。

说明 对于 SQL Server 2000 数据源，使用用于 SQL Server 的 OLE DB 提供程序。不使用用于 ODBC 的 OLE DB 提供程序。

创建链接服务器以在使用 ODBC 数据源时访问 ODBC 数据库

  • 在安装 SQL Server 的计算机上创建系统数据源。

  • 执行 sp_addlinkedserver 创建链接服务器，指定 MSDASQL 或 NULL 为 provider_name，指定 ODBC 系统数据源的名称为 data_source。

    SQL Server 在 Microsoft Windows NT® 上作为服务运行，而服务并非总能访问用户数据源，因此ODBC 用户数据源无法用于分布式查询。例如，名为 SystemDSN 的系统数据源引用运行 SQL Server 的服务器，且该服务器以 pubs 作为默认数据库：

    sp_addlinkedserver 'SQLPubs', ' ', 'MSDASQL', 'SystemDSN'

链接服务器可以使用用于 ODBC 的 OLE DB 提供程序，而不必使用 ODBC 数据源。链接服务器只需一步即可定义。用于 ODBC 的 OLE DB 提供程序用来定位 ODBC 驱动程序以及连接 ODBC 数据源所需的全部信息都必须在 provider_string 中进行定义。

下例通过指定 provider_string，在名为 SalesSvr 的 SQL Server 的 pubs 数据库上，通过 ODBC 创建名为 SQLPubs 的链接服务器。

sp_addlinkedserver 'SQLPubs', ' ', 'MSDASQL', NULL, NULL,
'Driver={SQL Server};Database=pubs;Server=SalesSvr;UID=sa;PWD=;'

如果通过用于 ODBC 的 OLE DB 提供程序定义链接服务器，且该链接服务器访问 SQL Server 数据库，则存在以下限制：

  • 不能引用包含一个或多个 timestamp 列的表。

  • 如果表中有可为空的 char、varchar、nchar、nvarchar、binary 或 varbinary 列，并且创建表时 ANSI_PADDING 选项设为 OFF，则不能引用这些表。


用于 Jet 的 Microsoft OLE DB 提供程序

用于 Jet 的 Microsoft® OLE DB 提供程序为 Microsoft Access 数据库提供了 OLE DB 接口，并且使 Microsoft SQL Server™ 2000 分布式查询得以查询 Access 数据库。

创建链接服务器以访问 Access 数据库

  1. 执行 sp_addlinkedserver 来创建链接服务器，指定 Microsoft.Jet.OLEDB.4.0 作为 provider_name，指定 Access .mdb 数据库文件的完整路径名作为 data_source。.mdb 数据库文件必须驻留在服务器上，data_source 在服务器上而不是在客户机上进行评估，路径必须是服务器上的有效路径。

    例如，若要创建一个名为 Nwind 的链接服务器，对 C:\Mydata directory 目录中名为 Nwind.mdb 的 Access 数据库进行操作，请执行：

    sp_addlinkedserver 'Nwind', 'Access 97', 'Microsoft.Jet.OLEDB.4.0',
    'c:\mydata\Nwind.mdb'
  2. 为访问非保密的 Access 数据库，试图访问 Access 数据库的 SQL Server 登录应该有一个为用户名 Admin 定义的没有密码的登录映射。

    下例使得本地用户 Joe 可以访问名为 Nwind 的链接服务器。

    sp_addlinkedsrvlogin 'Nwind', false, 'Joe', 'Admin', NULL

    要访问保密的 Access 数据库，需使用注册表编辑器配置注册表，以便使用 Access 所用的正确的工作组信息文件。使用注册表编辑器向该注册表项中添加 Access 使用的工作组信息文件的完整路径名称：

    HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Jet\4.0\Engines\SystemDB

    配置了注册表项后，请使用 sp_addlinkedsrvlogin 创建从本地登录到 Access 登录的登录映射：

    sp_addlinkedsrvlogin 'Nwind', false, 'Joe',
                        'AccessUser', 'AccessPwd'

Access 数据库没有目录和架构名称。因此，基于 Access 的链接服务器中的表可以在分布式查询中使用表单 linked_server...table_name 的四部分名称进行引用。

下例从名为 Nwind 的链接服务器中检索 Employees 表的所有行。

SELECT *
FROM Nwind...Employees

创建链接服务器以访问 Excel 电子表格

用于 Jet 4.0 的 Microsoft OLE DB 提供程序可用于访问 Microsoft Excel 电子表格。

  • 若要创建访问 Excel 电子表格的链接服务器，请使用下例中的格式。
    sp_addlinkedserver N'Excel', N'Jet 4.0',
                   N'Microsoft.Jet.OLEDB.4.0',
                   N'c:\data\MySheet.xls', NULL, N'Excel 5.0'
GO
sp_addlinkedsrvlogin N'Excel', false, sa, N'ADMIN', NULL
GO
  • 若要访问 Excel 电子表格中的数据，请将单元范围与名称相关联。可以通过将范围名称用作表名来访问命名范围。通过使用在上例中设置的链接服务器，下面的查询可用于访问称为 SalesData 的命名范围。
    SELECT *
FROM EXCEL...SalesData
GO

    将某行插入命名单元范围时，该行将添加到命名单元范围的最后一行之后。因此，如果想在列标题后插入行 rA，则将列标题单元与某个名称关联并使用该名称作为表名。当插入行时，单元范围将自动增长。

设置链接服务器以访问格式化文本文件

用于 Jet 的 Microsoft OLE DB 提供程序可用于访问并查询文本文件。

  • 若要直接创建访问文本文件的链接服务器而不将文件链接为 Access .mdb 文件中的表，请执行 sp_addlinkedserver，如下例所示。

    提供程序是 Microsoft.Jet.OLEDB.4.0，提供程序字符串为"Text"。数据源是包含文本文件的目录的完整路径名称。schema.ini 文件（描述文本文件的结构）必须与此文本文件存在于相同的目录中。有关创建 schema.ini 文件的更多信息，请参见 Jet 数据库引擎文档。

    --Create a linked server.
EXEC sp_addlinkedserver txtsrv, 'Jet 4.0',
    'Microsoft.Jet.OLEDB.4.0',
    'c:\data\distqry',
    NULL,
    'Text'
GO

--Set up login mappings.
EXEC sp_addlinkedsrvlogin txtsrv, FALSE, NULL, Admin, NULL
GO

--List the tables in the linked server.
EXEC sp_tables_ex txtsrv
GO

--Query one of the tables: file1#txt
--using a 4-part name.
SELECT *
FROM txtsrv...[file1#txt]


用于 DTS 包的 Microsoft OLE DB 提供程序

用于 DTS 包的 Microsoft OLE DB 提供程序是一个只读的提供程序，用来表现"数据转换服务包数据源对象"。通过在 Transact-SQL 语句中使用 OPENROWSET 函数或使用四部分名称引用包，提供程序可用来从表现包中的行集。通过使用其命令语言，OPENQUERY 还可用于向提供程序发送命令。有关使用用于 DTS 包的 Microsoft OLE DB 提供程序的更多信息，请参见异类数据类型的 DTS 驱动程序支持。

在 DTS 中定义了数据源对象后，定义链接服务器以访问 DTS 包数据源对象

  • 执行 sp_addlinkedserver 以创建链接服务器，指定 DTSPackageDSO 为 provider_name，包名称为 product_name，dtsrun 命令提示实用工具的开关为 data_source。

    例如，若要创建一个名为 MyDTSPackage 的链接服务器，以访问保存到 C:\Dts\DTSFilePackage.dts 文件的 DTS 包，请执行：

    sp_addlinkedserver MyDTSPackage, 'PackageName', 'DTSPackageDSO', '/FC:\Dts\DTSFilePackage.dts'

    然后，通过使用 DTS 包的名称或者 DTS 包中某个步骤的名称，可以引用由该 DTS 包表现的行集：

    SELECT * FROM MyDTSPackage...DTSStep1

    包名称或步骤名称在 DTS 中定义。

另外，使用 OPENROWSET 函数可以引用 DTS 包。有关更多信息，请参见从外部源查询 DTS 包。


用于 Oracle 的 Microsoft OLE DB 提供程序

用于 Oracle 的 Microsoft OLE DB 提供程序使分布式查询得以查询 Oracle 数据库中的数据。

说明 从安装在 Microsoft Windows® 98 上的 SQL Server 实例执行分布式事务时，Oracle 客户端软件不支持使用用于 Oracle 的 Microsoft OLE DB 提供程序进行分布式查询。

创建链接服务器以访问 Oracle 数据库实例

  1. 确保运行 SQL Server 的服务器上的 Oracle 客户端软件已达到提供程序所要求的级别。用于 Oracle 的 Microsoft OLE DB 提供程序要求 Oracle 客户端软件支持文件的版本为 7.3.3.4.0 或更高版本，并且 SQL*Net 的版本为 2.3.3.0.4。

  2. 在运行 SQL Server 的服务器上创建指向 Oracle 数据库实例的 SQL*Net 别名。有关更多信息，请参见 Oracle 文档。

  3. 执行 sp_addlinkedserver 创建链接服务器，指定 MSDAORA 为 provider_name，指定用于 Oracle 数据库实例的 SQL*Net 别名为 data_ source。

    以下示例假设已将一个 SQL*Net 别名定义为 OracleDB。

    sp_addlinkedserver 'OrclDB', 'Oracle', 'MSDAORA', 'OracleDB'
  4. 使用 sp_addlinkedsrvlogin 创建从 SQL Server 登录到 Oracle 登录的登录映射。

    以下示例通过 Oracle 登录名 OrclUsr 和密码 OrclPwd 将 SQL Server 登录 Joe 映射到步骤 3 中定义的链接服务器：

    sp_addlinkedsrvlogin 'OrclDB', false, 'Joe', 'OrclUsr', 'OrclPwd'

每个 Oracle 数据库实例仅有一个名称为空的目录。Oracle 链接服务器中的表必须使用四部分名称格式 OracleLinkedServerName..OwnerUserName.TableName 进行引用。例如，以下 SELECT 语句引用 Oracle 用户 MARY 在 OrclDB 链接服务器映射的服务器上所拥有的表 SALES。

SELECT *
FROM OrclDB..MARY.SALES

在 Oracle 链接服务器中引用表时，请使用以下规则：

  • 如果在 Oracle 中创建的表名和列名没有引用的标识符，请全部使用大写字母名称。

  • 如果在 Oracle 中创建的表名和列名有引用的标识符，请全部使用与 Oracle 中创建名称时相同的字母大小写格式。

  • INSERT 语句应为表中的所有列提供值，即使表中的某些列为 NULL 或有默认值。
注册表项

若要启用用于 Oracle 的 OLE DB 提供程序以与 Oracle 客户端软件一起使用，必须从命令行运行注册表文件以修改客户端的注册表。不应同时运行客户端软件的多个实例。这些文件都列在下表中并位于同一目录结构中，而该目录结构包含通常在 C:\Program Files\Common Files\System Files\OLE DB 中的 Microsoft 数据访问组件 (MDAC) 安装。

Oracle 客户端 | Windows NT 或 9x | Windows 2000
7.x | mtxoci7x_winnt.reg | mtxoci7x_win2k.reg
8.0 | mtxoci80x_winnt.reg | mtxoci80x_win2k.reg
8.1 | mtxoci81x_winnt.reg | mtxoci81x_win2k.reg


用于 Microsoft 目录服务的 OLE DB 提供程序

用于 Microsoft 目录服务的 Microsoft OLE DB 提供程序支持对 Microsoft® Windows® 2000 目录服务信息的访问。此 OLE DB 提供程序支持两种命令方言（LDAP 和 SQL）访问目录服务，并以可使用 SQL Server 分布式查询进行查询的表格格式返回结果。

创建链接服务器以访问 Windows 2000 目录服务

  • 将 ADSDSOObject 用作 provider_name 并将 adsdatasource 用作 sp_addlinkedserver 系统存储过程的 data_source 参数，由此创建链接服务器。
    EXEC sp_addlinkedserver 'ADSI', 'Active Directory Services 2.5',
'ADSDSOObject', 'adsdatasource'
GO

对于 Windows 授权登录，只需自映射就足以通过使用 SQL Server 安全委托来访问目录。因为默认情况下为通过 sp_addlinkedserver 创建的链接服务器创建自映射，所以不需要其它登录映射。

对于 SQL Server 授权登录，可以使用 sp_addlinkedsrvlogin 系统存储过程配置用于连接到目录服务的适当的登录/密码。

查询目录服务

用于 Microsoft 目录服务的 Microsoft OLE DB 提供程序支持两种命令方言（LDAP 和 SQL）查询目录服务。可以使用 OPENQUERY 函数将命令发送到目录服务并在 SELECT 语句中使用它的结果。

下例显示创建这样的视图，它在域地址为 sales.northwind.com 的服务器 ADSISrv 上使用 OPENQUERY 函数返回目录中的信息。OPENQUERY 函数内部的命令是一个 SQL 查询，它在目录上执行并返回对象的 Name、SN 和 ST 特性，而这些对象都属于目录中指定的层次结构位置 (OU=Sales) 上的 Contact 类。然后，可以在任何 SQL Server 查询中使用该视图。

CREATE VIEW viewADContacts
AS
SELECT [Name], SN [Last Name], ST State
FROM OPENQUERY( ADSI,
   'SELECT Name, SN, ST
   FROM ''LDAP://ADSISrv/ OU=Sales,DC=sales,DC=northwind,DC=com''
   WHERE objectCategory = ''Person'' AND
      objectClass = ''contact''')
GO
SELECT * FROM viewADContacts

有关 LDAP 和 SQL 方言的更多信息，请参见 Microsoft Active Directory 服务文档。


用于 Microsoft 索引服务的 OLE DB 提供程序

Microsoft® Windows 2000 包括 Microsoft Internet Information 服务 (IIS) 和索引服务 3.0 版。这些服务可以根据文件属性筛选文件，并执行全文索引和文件数据检索。（对于 Microsoft Windows NT® 4.0 系统，Windows NT 4.0 Option Pack 包括 IIS 4.0 和 Microsoft 索引服务 2.0。）

索引服务还包括用于 Microsoft 索引服务的 Microsoft OLE DB 提供程序。此提供程序可用来对非数据库文件进行全文索引或属性值搜索。可使用 sp_addlinkedserver 完成链接服务器定义，并使用 SQL Server 企业管理器进行安全设置。然后，分布式查询可引用提供程序以检索索引信息。

创建链接服务器以访问索引服务全文索引

  1. 使用索引服务创建全文索引。默认情况下，索引服务安装一个名为 default 的目录。有关更多信息，请参见索引服务文档。

  2. 执行 sp_addlinkedserver 创建链接服务器，指定 MSIDXS 为 provider_name，指定全文索引的名称为 data_source。

    例如，若要创建名为 FTIndexWeb 的链接服务器服务访问名为 Web 的全文索引，请执行：

    sp_addlinkedserver FTIndexWeb, 'Index Server', 'MSIDXS', 'Web'
  3. 索引服务客户端的安全授权的根据是使用用于 Microsoft 索引服务的 OLE DB 提供程序的进程的 Windows 帐户。分布式查询运行于 Microsoft SQL Server™ 2000 进程的上下文中。SQL Server 通常在拥有高级授权的帐户下运行，因此某些 SQL Server 用户可以使用索引服务链接服务器访问未经授权访问的信息。sysadmin 固定服务器角色成员通过严格控制可以使用索引服务链接服务器执行分布式查询的 SQL Server 登录来解决这一问题，

    管理员首先使用 sp_droplinkedsrvlogin 删除映射到索引服务链接服务器的所有登录，例如：

    sp_droplinkedsrvlogin FTIndexWeb, NULL

    然后管理员使用 sp_addlinkedsrvlogin 授予单个登录以访问链接服务器的权限，例如：

    sp_addlinkedsrvlogin FTIndexWeb, true, 'SomeLogin'

    若要执行这些操作，请在 SQL Server 企业管理器中右键单击链接服务器，单击"属性"命令，然后选择"安全"选项卡。

    此外，通过设置提供程序选项 DisallowAdhocAccess，可禁用通过使用 OPENQUERY 对用于 Microsoft 索引服务的 OLE DB 提供程序进行特殊访问。有关更多信息，请参见为分布式查询配置 OLE DB 提供程序。

Transact-SQL 语句可以使用 OPENQUERY 函数向索引服务发出命令，使用的 SQL 语法与 SQL Server 支持的全文查询语法一致，后者是对数据库所存储数据进行全文检索的语法。索引服务支持的 SQL 全文语法在 Index Server Programmer's Guide 中定义，该指南可在 Microsoft Development Network 或 Windows NT 4.0 Option Pack 的索引服务文档中找到。有关在 Transact-SQL 语句中使用用于 Microsoft 索引服务的 OLE DB 提供程序的更多信息，请参见文件数据的全文查询。


用于 DB2 的 OLE DB 提供程序

用于 DB2 的 Microsoft OLE DB 提供程序是随 Microsoft® Host Integration Server 2000 一起分发的，它使 Microsoft SQL Server™ 2000 分布式查询得以查询 DB2 数据库中的数据。

创建链接服务器以访问 DB2 数据库

  1. 在运行 SQL Server 实例的计算机上安装用于 Host Integration Server 2000 的 Windows NT 或 Windows 9x 客户端软件。选择各选项以安装用于 DB2 的 OLE DB 提供程序，以及与 SNA 网络中运行的 IBM 计算机进行通讯所需的网络组件。

  2. 确定用于 DB2 的 OLE DB 提供程序访问要查询的 DB2 数据源时所需的连接字符串。确定连接字符串的最好方法是使用 Host Integration Server 新 OLE DB 数据源应用程序生成数据链接文件。有关更多信息，请参见 Microsoft Host Integration Server 2000 文档。

  3. 执行 sp_addlinkedserver 以创建链接服务器，指定 DB2OLEDB 为 provider_name，指定包含要访问的数据的 DB2 目录名为 catalog，并指定步骤 2 中的连接字符串为 provider_string。

    以下示例表示如何使用 sp_addlinkedserver 创建访问 DB2 数据库的链接服务器定义：

    EXEC sp_addlinkedserver @server = 'DB2SRV',
   @srvproduct = 'Microsoft OLE DB Provider for DB2',
   @catalog = 'SEATTLE',
   @provider = 'DB2OLEDB',
   @provstr =
     'NetLib=SNA;NetAddr=;NetPort=;RemoteLU=SEATTLE;LocalLU=LOCAL;
      ModeName=QPCSUPP;InitCat=SEATTLE;
      Default Schema=WNW3XX;PkgCol=WNW3XX;TPName=;Commit=YES;
      IsoLvl=NC;AccMode=;CCSID=37;PCCodePage=1252;BinAsChar=NO;
      Data Source=Seattle_WNW3XX'
  4. 执行 sp_addlinkedsrvlogin 以创建从 SQL Server 2000 登录到 DB2 登录的登录映射。

    以下示例将 SQL Server 2000 登录名 SQLJoe 映射为 DB2 登录名 DB2Joe：

    EXEC sp_addlinkedsrvlogin 'DB2SRV', false, 'SQLJoe', 'DB2Joe', 'JoePwd'

完成这些步骤之后，可以将链接服务器的名称 DB2SRV 用作四部分名称中的服务器名称，并在 OPENQUERY 函数中将其用作 linked_server。例如：

SELECT *
FROM DB2SRV.SEATTLE.WNW3XX.DEPARTMENT

或

SELECT *
FROM OPENQUERY(DB2SRV, 'SELECT * FROM SEATTLE.WNW3XX.EMP_ACT')

当在 DB2 数据源上进行的分布式查询涉及 NULL 值比较时，请使用 IS NULL 或 IS NOT NULL，而不是比较运算符，如 =、< 或 >。另外，INSERT 语句应为表中的所有列提供值，即使表中的某些列为 NULL 或有默认值。


用于 Exchange 的 OLE DB 提供程序

用于 Exchange 的 Microsoft® OLE DB 提供程序以表格形式表现存储在 Microsoft Exchange 2000 Web Store 中的数据。使用 SQL 样式的语言就可以查询这些数据，SQL 样式的语言与用于 Microsoft 索引服务的 OLE DB 提供程序所支持的 SQL 子集非常相似。

Microsoft SQL Server™ 2000 分布式查询可用于通过该 OLE DB 提供程序从 Exchange Web Store 中查询数据，并且可与 SQL Server 中的表联接。Exchange Web Store 应与 SQL Server 位于同一台计算机中。位于其它计算机上的 Web Store 不能通过用于 Exchange 的 OLE DB 提供程序进行访问。

用于 Exchange 的 OLE DB 提供程序可作为 Microsoft Exchange 2000 的一部分。

在 Exchange Web Store 上创建链接服务器

  • 将 'exoledb.datasource.1' 作为 provider_name 参数，并将对应于 Web Store 根文件夹的 URL 作为 sp_addlinkedserver 系统存储过程的 data_source 参数。
    EXEC sp_addlinkedserver 'exchange',
'Exchange OLE DB provider',
'exoledb.DataSource.1',
'file:\\.\backofficestorage\localhost\public folders'
  • 对于 Windows NT 授权登录，不需要登录映射。SQL Server 在连接到用于 Exchange 的 OLE DB 提供程序时模拟 Windows NT 授权登录。对于 SQL 授权登录，如果需要，通过提供用户名和密码来设置登录映射。

以下限制适用于从用于 Exchange 的 OLE DB 提供程序中查询数据：

  • 只支持直接传递式查询。四部分构成的名称不能用于 Exchange OLE DB 提供程序。

  • 用于 Exchange 的 OLE DB 提供程序中的所有字符列都以 ntext 列的形式显示给 SQL Server。为了在这些列上进行比较，必须使用 CONVERT 函数将它们显式转换为 nvarchar。

  • SQL Server 分布式查询不支持 Exchange 提供程序中带 OLE DB DBTYPE DBTYPE_VECTOR 的多值列。

若要从 SQL Server 访问 Exchange Web Store 中的数据，请通过如上建立的链接服务器

  • 创建视图以便从感兴趣的 Web Store 文件夹中检索列形式的所需属性。视图定义将字符串列转换成 nvarchar 以便可以在 WHERE 子句中通过条件对其进行过滤。

    例如，假设 Web Store 包含一个名为 Contacts 的文件夹，其中包含一个联系人列表。下列脚本创建了一个针对 Contacts 文件夹的视图，同时检索联系人的名、姓、公司名称和出生日期。

    CREATE VIEW Contact
AS
SELECT convert(nvarchar(30),"urn:schemas:contacts:sn") LastName,
Convert(nvarchar(30),"urn:schemas:contacts:givenname")
FirstName,
    Convert(nvarchar(30), "urn:schemas:contacts:o") Company,
    Convert(nvarchar(50), "urn:schemas:contacts:email1") Email,
    "urn:schemas:contacts:bday" BirthDay
FROM OpenQuery(Exchange,
  'SELECT "urn:schemas:contact:sn",
           "urn:schema:contacts:givenname",
          "urn:schemas:contacts:o",
        "urn:schemas:contacts:email1"
        "urn:schemas:contacts:bday"
   FROM SCOPE(''.\contacts'')'

    现在可以查询视图并像常规表一样将其与本地 SQL Server 表联接。例如，Contacts 视图可以与本地 Supplier 表联接以确定各供应商的联系人信息。

    SELECT FirstName, LastName, Email, Company
FROM    Suppliers S, Contacts C
WHERE S.Company = C.CompanyName

有关 Exchange OLE DB 提供程序支持的 SQL 语言的信息，请参见 Microsoft Exchange 2000 文档。


用于分布式查询的 OLE DB 提供程序参考

分布式查询对远程表进行的操作取决于用来访问表的 OLE DB 提供程序的功能。OLE DB 规范为 OLE DB 提供程序定义了一组对象。每个对象都有一组接口。其中许多对象和接口都是可选的，可能并不受提供程序的支持。如果 OLE DB 提供程序不支持其中的某些对象和接口，则依赖于这些组件的分布式查询功能将不能与通过该提供程序访问的远程表一起使用。


分布式查询使用的 OLE DB 对象

下表中显示了 Transact-SQL 分布式查询所使用的 OLE DB 对象和接口。在 OLE DB 提供程序可用于任意 Transact-SQL 分布式查询之前，它至少必须支持最低限度的一组对象和接口。这些对象和接口在表的"必需"列中标记为"是"。在"必需"列中为"否"的对象和接口仅在支持高级分布式查询功能时才需要。如果提供程序不支持接口，那么也不支持与之相关的 Transact-SQL 功能。例如，如果不支持 IRowsetLocate 和 IRowsetChange 接口，则不能在远程中表使用 UPDATE 和 DELETE 语句。

对象 | 接口 | 必需 | 描述
数据源 | IDBInitialize | 是 | 初始化和设置数据和安全上下文。
 | IDBCreateSession | 是 | 创建 DB 会话对象。
 | IDBProperties | 是 | 获取有关提供程序性能的信息并设置初始化属性。
 | IDBInfo | 否 | 获取有关提供程序所支持的 SQL 语法的信息。
DB 会话 | IDBSchemaRowset | 否 | 获取表和/或列元数据。所需的行集是 TABLES 和 COLUMNS。使用的其它行集（如果可用）是 TABLES_INFO、CATALOGS、INDEXES、STATISTICS、TABLE_STATISTICS、VIEWS、PRIMARY_KEYS、TABLE_PRIVILEGES 和 COLUMN_PRIVILEGES。
 | IopenRowset | 是 | 打开表、索引或柱状图上的行集。
 | IgetDataSource | 是 | 从 DB 会话对象返回到数据源对象。
 | IDBCreateCommand | 否 | 创建一个命令对象（查询）。
 | ItransactionLocal | 否 | 用于启动提供程序上的事务。
 | ItransactionJoin | 否 | 用于分布式事务支持。如果不支持该接口，则不允许在用户事务中更新远程提供程序。
行集（在表上） | Irowset | 是 | 扫描行。
 | Iaccessor | 是 | 绑定到行集中的各列。
 | IcolumnsInfo | 是 | 获取有关行集中各列的信息。
 | IrowsetInfo | 是 | 获取有关行集属性的信息。
 | IrowsetLocate | 否 | 为 UPDATE 或 DELETE 操作和基于索引的查找所必需。
 | IrowsetChange | 否 | 为在表中执行的 INSERT、UPDATE 或 DELETE 操作所必需。基表的行集应该支持此接口，以便支持 INSERT、UPDATE 或 DELETE 语句。
 | IconvertType | 是 | 验证行集是否支持其列中的特定数据类型转换。
行集（在索引上） | Irowset | 是 | 扫描行。
 | Iaccessor | 是 | 绑定到行集中的各列。
 | IcolumnsInfo | 是 | 获取有关行集中各列的信息。
 | IrowsetInfo | 是 | 获取有关行集属性的信息。
 | IrowsetIndex | 是 | 为索引上的行集所必需；用于索引功能（设置范围、查找）。
 | IconvertType | 是 | 验证行集是否支持其列中的特定数据类型转换。
命令（可选） | Icommand | 是 | 用于执行查询。
 | IcommandText | 是 | 用于定义查询文本。
 | IcommandProperties | 是 | 指定由命令返回的行集中所需的属性。
 | IcommandWith Parameters | 否 | 用于执行参数化查询。
 | IcommandPrepare | 否 | 用于为获取元数据准备命令。
错误（可选） | IerrorRecords | 是 | 获取指向错误记录的 IErrorInfo 接口的指针。
 | IerrorInfo | 是 | 获取错误记录的文本描述。
任何对象（可选） | IsupportErrorInfo | 否 | 确定给定接口是否支持错误对象。


OLE DB 提供程序的四部分名称要求

只有 OLE DB 提供程序支持以下各项时，分布式查询才可以使用由四部分组成的名称：

  • IDBSchemaRowset 接口。

  • 在 IDBSchemaRowset 接口中，它支持的名称所有组成部分的限制。限制是 OLE DB 中定义的一种机制，用于通过使用 OLE DB 架构行集指定元数据查询的搜索条件。

  • IDBInfo 接口中的字面值 DBLITERAL_CATALOG_SEPARATOR、DBLITERAL_SCHEMA_SEPARATOR 和 DBLITERAL_QUOTE。Microsoft® SQL Server™ 2000 使用的默认值可能无法与不支持这些字面值的提供程序一起使用。

如果 OLE DB 提供程序不符合这些要求，则只能在 OPENDATASOURCE 或 OPENROWSET 函数中使用直接传递查询引用该提供程序。


OLE DB 提供程序的 UPDATE 和 DELETE 语句要求

只有当用于访问远程表的 OLE DB 提供程序满足下列条件时，Transact-SQL UPDATE 和 DELETE 语句才能引用远程表：

  1. 提供程序必须支持在要更新或删除的表中通过 IOpenRowset 打开的行集中的书签。

  2. 提供程序必需支持在要更新或删除的表中通过 IOpenRowset 打开的行集中的 IRowsetLocate 和 IRowsetChange 接口。

  3. IRowsetChange 接口必须支持更新 (SetData) 和删除 (DeleteRows) 方法。

用于 SQL Server 的 Microsoft OLE DB 提供程序仅在具有唯一索引的表中支持这些接口。因此，仅当其它 Microsoft® SQL Server™ 实例中的远程表具有唯一索引时，才允许在该表上使用 UPDATE 或 DELETE 语句。


OLE DB 提供程序的 INSERT 语句要求

只有用于访问远程表的 OLE DB 提供程序满足如下条件，Transact-SQL INSERT 语句才能引用远程表：

  • 提供程序必须支持在插入数据的表中通过 IopenRowset 打开的行集上的 IrowsetChange 接口。

  • 插入数据的基表上的 IrowsetChange 接口必须支持插入 (InsertRow) 方法。


OLE DB 提供程序的键集驱动游标要求

仅当满足如下要求时，Transact-SQL 键集驱动游标才可以引用远程表：

  • 分布式查询必须满足在声明键集驱动游标的 DECLARE CURSOR 语句中所使用的SELECT 语句的要求。有关用于键集驱动游标支持的 Transact-SQL 条件的更多信息，请参见 DECLARE CURSOR。

  • 查询中的所有本地表必须有唯一索引。远程表的索引应通过 IDBSchemaRowset 接口的 INDEXES 行集来表现。
OLE DB 提供程序的索引要求

SQL Server 可以在来自 OLE DB 提供程序的表上使用索引以评估某些查询。因此，提供程序应表现允许扫描索引行集的 OLE DB 接口，并使用从索引行集获得的书签查找基表行集。

仅当索引和表行集位于作为 Microsoft® SQL Server™ 实例的同一台计算机上时，使用 OLE DB 提供程序的索引才有性能方面的好处。因此，仅当数据源位于作为 SQL Server 的同一台计算机上时才应设置"类似访问路径的索引"选项。

仅当满足下列条件时 SQL Server 才使用 OLE DB 提供程序的索引：

  • 提供程序必须支持带有 TABLES、COLUMNS 和 INDEXES 架构行集的 IDBSchemaRowset 接口。

  • 提供程序必须支持通过指定索引名和相应的基表名、使用 IOpenRowset 在索引上打开行集。

  • 索引对象必须支持其所有强制接口：IRowset、IRowsetIndex、IAccessor、IColumnsInfo、IRowsetInfo 和 IConvertTypes。

  • 为了基于书签在行上定位，在索引基表（使用 IOpenRowset）上打开的行集必须支持 IRowsetLocate 接口。

如果 OLE DB 提供程序满足上述这些要求，则 SQL Server 管理员可以设置"类似访问路径的索引"提供程序选项，以使 SQL Server 能够使用提供程序的索引评估查询。默认情况下，除非设置此选项，否则 SQL Server 不尝试使用提供程序的索引。

可更新键集游标要求

通过在分布式查询上定义的键集游标可以更新或删除远程表。例如：

UPDATE | DELETE remote_table WHERE CURRENT OF cursor_name.

在下列条件下允许在分布式查询上使用可更新游标：

  • 提供程序应满足远程表上的更新和删除条件。有关更多信息，请参见 OLE DB 提供程序的 UPDATE 和 DELETE 语句要求。

  • 所有的游标操作都必须在具有可重复读隔离级别或可串行隔离级别的显式用户事务（或多语句事务）中进行。

提供程序必须支持具有 ITransactionJoin 接口的分布式事务。


OLE DB 提供程序的分布统计要求

Microsoft® SQL Server™ 2000 定义了 OLE DB 规范的扩展，以允许 OLE DB 提供程序报告有关所提供数据的行数和键值范围的统计。SQL Server 可以使用这些信息来提高分布式查询的性能。

SQL 为非过程化语言。SQL 语句没有指定取得所需结果需要完成的步骤。这些语句定义结果集的格式和基表中的行必须满足的条件，以便在生成该结果集时使用。数据库引擎必须分析每个 SQL 语句并确定访问基表效率最高的方法。执行此任务的数据库引擎部分叫作优化程序。

如果优化程序可以使用 WHERE 子句谓词所引用的基表列中有关值分布情况的统计，将会改善优化过程的结果。优化程序使用的分布统计包括：

  • 表中的行数，也称为表的基数。

  • 列中存储的非重复数值的个数，也称为列的基数。

  • 有关列中的非重复数值如何在表的各行中分布的信息。

为进一步优化分布式查询，SQL Server 定义了 OLE DB 规范的扩展，使得 OLE DB 提供程序可以报告它们所表现的行集或表的分布统计。尽管这些扩展是在 SQL Server 文档中定义的，但如果要使 SQL Server 可以使用这些信息，各个 OLE DB 提供程序开发者必须在他们的提供程序中编写支持扩展的代码。如果提供程序有支持扩展的代码，SQL Server 就可以使用这些扩展来优化分布式查询的性能。如果提供程序不支持扩展，SQL Server 将使用分布统计的简单估计值。

说明 用于 SQL Server 的 Microsoft OLE DB 提供程序和用于 Oracle 的 Microsoft OLE DB 提供程序都支持分布统计。

分布统计扩展围绕称为"统计"的单元来生成。每个表都可以有零个或多个统计，每个统计报告一个或多个列的数据。一个统计记录下列内容：

  • 此统计所涉及的每个单独的列中值的基数（即唯一值的个数）。

  • 统计所涉及的所有列的串联值的基数。

  • 或者，有关统计所涉及的第一列中键值的不同范围的柱状图报表信息。报告的值可以包括每一键值范围内的行数、唯一值的数量，或键值小于或等于该范围内最高键值的表中的行数。

下面是示例表。

ColumnA | ColumnB
'abc' | 'xyz'
'abc' | 'xyz'
'def' | 'xyz'
'mno' | 'xyz'
'mno' | 'mmm'
'tuv' | 'xyz'

如果统计涉及 ColumnA 和 ColumnB，因为最前面两行的 ColumnA 和 ColumnB 组合值相同 ('abc' + 'xyz')，所以两列组合值的基数是 5。ColumnA 的基数为 4，ColumnB 的基数为 2。一个关于 ColumnA 的简单的四级柱状图报表如下：

值范围 | 该范围内表行数所占的百分比
'aaa' 到 'hzz' | 50%
'iaa' 到 'nzz' | 33%
'oaa' 到 'rzz' | 00%
'taa' 到 'zzz' | 17%

不同的 OLE DB 数据源记录不同列组合的分布统计，OLE DB 提供程序报告的一组统计是可以通过定义来实现的。例如，SQL Server 6.5 版或更早版本仅为索引所涉及的列生成分布统计，并且表中所定义的每个索引只有一个统计。SQL Server 7.0 版或更高版本生成以下统计：

  • 表中所定义的每个索引有一个统计。

  • 每个 CREATE STATISTIC 语句有一个统计。

  • 每个自动生成的统计有一个统计。有关更多信息，请参见统计信息。

如果一个列可能为谓词参数中指定的给定值返回少量的行数，则该列具有很高的选择性。分布统计可以用来估计选择性的程度：

  • 与基数小的列比较起来，基数大的列拥有更多的数据值，每个数据值可能匹配较少的行数。

  • 如果 OLE DB 提供程序使用柱状图报告列中的数值分布情况，则 SQL Server 优化程序还可以估计谓词参数中的特定值是位于选择性较好的范围内，还是位于选择性较差的范围内。

链接服务器有一个良好的分布统计还有助于优化程序为分布式查询的本地部分生成高效的执行计划。

SQL Server 优化程序使用分布统计试图减少必须在 OLE DB 提供程序与 SQL Server 之间通讯的数据量。例如，在本地服务器上的 TableA 和链接服务器的 TableB 之间执行分布式联接时，SQL Server 可以使用分布统计确定以下两个进程中的哪一个效率更高：

  • 将行从匹配非联接谓词的 TableA 发送到链接服务器，并让链接服务器执行联接。

  • 将行从匹配非联接谓词的 TableB 检索到本地服务器，并在本地服务器上执行联接。

如果 OLE DB 提供程序没有报告列的基数信息，SQL Server 优化程序将取一个较小的基数。如果提供程序没有报告统计的分布柱状图，优化程序在进行操作时将把数值视为均匀分布在表的各行中。

SQL Server 使用下列来自 OLE DB 提供程序的扩展报告分布统计：

  • DBPROP_TABLESTATISTICS 是一个新的数据源属性，表示提供程序是否报告分布统计。

  • TABLE_STATISTICS 是一种新的 IDBSchemaRowset，列出可用于给定基表的统计，包括列的基数和行的基数。

  • IopenRowset::OpenRowset 得到增强，可以接受标识统计的新参数。如果指定了一个统计，OpenRowset 返回柱状图行集，显示 StatisticID 中指定的统计所涉及的第一列的数值分布情况。

这些 OLE DB 的扩展都包括在 OLE DB 2.6 版中。有关与分布统计有关的这些扩展的信息，请参见 OLE DB 2.6 规范。

OLE DB 提供程序可以选择实现下列增强性能的方式：即只采样基表中的部分行来确定分布统计和柱状图。这些提供程序应该在 TABLE_STATISTICS 和柱状图行集中报告之前计算它们的基数和柱状图数据，以反映表的总值。

在实现时定义 OLE DB 提供程序是否将 TABLE_STATISTICS 和柱状图行集中的数据与当前的基表内容保持一致。


OLE DB 提供程序的 SQL 方言要求

OLE DB 提供程序对 SQL 的支持程度决定了 Microsoft® SQL Server™ 2000 如何有效地将分布式查询操作委派给 OLE DB 提供程序。如果提供程序不支持 SQL，而仅仅打开行集，则 SQL Server 必须检索全部行集并执行全部逻辑操作，即使分布式查询只需要源行集中的行子集时。如果 OLE DB 提供程序支持很多 SQL 语法元素，则 SQL Server 可以生成更完善的查询，使得源提供程序在向 SQL Server 返回行集之前能够筛选出无用的行。

OLE DB 规范定义一个 DBPROP_SQLSUPPORT 属性，通过它，提供程序可以报告其所支持的 SQL 语法等级。不同版本的 SQL Server 在分布式查询中所需的最低 SQL 支持等级为：

  • SQL Server 2000：DBPROPVAL_SQL_SUBMINIMUM

  • SQL Server 7,0：DBPROPVAL_SQL_ANSI92_ENTRY 或 DBPROPVAL_SQL_ODBC_CORE

除了支持来自基础 OLE DB 提供程序的较低级 SQL 语法以外，SQL Server 2000 还定义了新的 SQLPROPSET_OPTHINTS 属性集，提供程序可以用它来指定支持在 DBPROPVAL_SQL_SUBMINIMUM 定义之外的单独的 SQL 语法元素。如果提供程序支持用于优化分布式查询的一项或两项功能，但不支持完整的 DBPROPVAL_SQL_ANSI92_ENTRY 或 DBPROPVAL_SQL_ODBC_CORE 语法，提供程序可以使用 SQLPROPSET_OPTHINTS 属性向 SQL Server通知它所支持的优化功能。


DBPROPVAL_SQL_SUBMINIMUM 语法

DBPROPVAL_SQL_SUBMINIMUM 要求提供程序支持 DBPROPVAL_SQL_ODBC_MINIMUM 功能，但是区别如下：

  • DBPROPVAL_SQL_ODBC_MINIMUM 中有，但DBPROPVAL_SQL_SUBMINIMUM 中没有的功能：

    DDL 语句。
    INSERT、UPDATE 和 DELETE 语句。
    动态参数标记。
    在 FROM 子句中的多表。

  • DBPROPVAL_SQL_SUBMINIMUM 中有，但 DBPROPVAL_SQL_ODBC_MINIMUM 中没有的功能：

    选择列表中的列别名。
    整数和精确数字常量
    IS [NOT] NULL 谓词。

尽管 DBPROPVAL_SQL_SUBMINIMUM 语法的定义与 DBPROPVAL_SQL_ODBC_MINIMUM 相关，但是 SQL Server 分布式查询优化器从不测试 DBPROPVAL_SQL_ODBC_MINIMUM。

DBPROPVAL_SQL_SUBMINIMUM 语法使用如下约定：

规范 | 用于
大写 | SQL 关键字。
斜体 | SQL 语法中用户提供的参数。
| （竖线） | 分隔括号或大括号内的语法项目。只能选择一个项目。
[ ] （方括弧） | 可选语法项目。不必键入方括号。
{}（花括号） | 必选语法项。不要键入花括号。
[ ,...n ] | 表示前面的项可重复 n 次。用逗号分隔每一项。
[ ...n ] | 表示前面的项可重复 n 次。每一项由空格分隔。
<标签> ::= | 语法块的名称。此规则用于对可在语句中的多个位置使用的过长语法或语法单元部分进行分组和标记。适合使用语法块的每个位置由括在尖括号内的标签表示：<标签>。

这是用于 DBPROPVAL_SQL_SUBMINIMUM 的语法（与 SQL Server 预期的一样）：

<select_statement>

SELECT [ALL | DISTINCT] <select_list>

FROM <table_reference_list>

[ WHERE < search_condition > ]

[<order_by_clause>]

SELECT 子句

< select_list > ::= * | <select_sublist>[,...n]

<select_sublist> ::=

expression [<alias>]

alias <user_defined_name>

FROM 子句

<table_reference_list> ::=

<table_reference>

<table_reference> ::=

table_name

table_name

<table_identifier>

<table_identifier> ::=

<user_defined_name>

WHERE 子句

< search_condition > ::=

<boolean_term> [OR <search_condition>]

<boolean_term> ::=

<boolean_factor> [AND <boolean_term>]

<boolean_factor> ::=

[NOT] <boolean_primary>

<boolean_primary> ::=

<comparison_predicate> | <search_condition>

<comparison_predicate> ::=

<expression> <comparison_operator> <expression>

| <expression> IS [NOT] NULL

<comparison_operator> ::=

< | > | <= | >= | = | <>

ORDER BY 子句

<order_by_clause> ::=

ORDER BY <sort_specification>[,...n]

<sort_specification> ::=

{ | <column_name> } [ASC | DESC]

常用语法元素

<expression>

<term> | <expression> {+|_} <term>

<term> ::= <factor> | <term> {*|/} <factor>

<factor>::= [+|-] <primary>

<primary> ::= <column_name> | <literal> | (<expression>)

<column_name>

[<table_name>].<column_identifier>

<literal> ::= <character_string_literal>

| <integer_literal>

| <exact_numeric_literal>

<character_string_literal> ::=

'{character}[...n]'

字符是提供程序或数据源的字符集中的任何字符。使用两个单引号 ('') 表示文字字符串中的单引号（省略号）。

<integer_literal> ::=

[+|-] <unsigned_integer>

<exact_numeric_literal>::=

[+|-] <unsigned_integer> [<period><unsigned_integer]

| <period><unsigned_integer>

<column_identifier> ::=

<user_defined_name>

<user_defined_name>

<letter>[<digit>|<letter>|_][...n]

<unsigned_integer> ::=

{<digit>}[...n]

<digit> ::= 0|1|2|3|4|5|6|7|8|9

<letter> ::= <lower_case_letter>|<upper_case_letter>

<lower_case_letter> ::=

a|b|c|d|e|f|g|h|I|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|z

<upper_case_letter> ::=

A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|X|Y|Z

<period> ::= .


SQLPROPSET_OPTHINTS 属性集程序设计

单个的 OLE DB 提供程序可以支持 DBPROPVAL_SQL_SUBMINIMUM 定义之外的一些 SQL 功能（但并非在 DBPROPVAL_SQL_ODBC_CORE 或 DBPROPVAL_SQL_ANSI92_ENTRY 中的全部功能）。Microsoft® SQL Server™ 2000 查询优化器可以使用这些驱动程序所支持的一些功能来提高分布式查询的性能。这些提供程序可以使用 SQLPROPSET_OPTHINTS 属性集向 SQL Server 报告它们所支持的可以提高分布式查询速度的功能。

尽管已经在 SQL Server 文档中定义了 SQLPROPSET_OPTHINTS 属性集，但是个别 OLE DB 提供程序的开发人员必须在其提供程序中编写支持该属性集的代码。将对该属性集的支持编入提供程序中后，SQL Server 便可以用它来优化分布式查询的性能。

支持 DBPROPVAL_SQL_ANSI92_ENTRY 或 DBPROPVAL_SQL_ODBC_CORE 的 OLE DB 提供程序除需要 SQLPROP_DATELITERALS 外，不需要其它任何 SQLPROPSET_OPTHINTS 属性。这些提供程序必须支持 SQLPROPSET_OPTHINTS 所涉及的除 SQLPROP_DATELITERALS 以外的全部属性集，以便有资格支持 DBPROPVAL_SQL_ANSI92_ENTRY 或 DBPROPVAL_SQL_ODBC_CORE。

这些是通过 SQLPROPSET_OPTHINTS 报告的属性。

属性 | 描述
SQLPROP_ANSILIKE | 指定 LIKE 子句如在 SQL-92 Entry Level 中所定义的那样受到支持，并使用 % 和 _ 通配符。
SQLPROP_DATELITERALS | 指定提供程序支持 datetime 字面值或依照 Transact-SQL 语法的常量。
SQLPROP_DYNAMICSQL | 指定提供程序支持ODBC 参数标记语法使用问号：?。
SQLPROP_INNERJOIN | 指定提供程序支持在 WHERE 子句中引用多个表，条件是这些引用不是外部联接引用。
SQLPROP_GROUPBY | 指定提供程序在 SELECT 语句中支持 GROUP BY 和 HAVING 子句。属性同时指定提供程序支持 AVG、COUNT、MIN、MAX 和 SUM 聚合函数，条件是 DISTINCT 未指定为聚合参数。
SQLPROP_NESTEDQUERIES | 指定提供程序支持 FROM 子句中的 SELECT 嵌套语句。
SQLPROP_SUBQUERIES | 指定提供程序如在 SQL-92 Entry Level中所定义的一样支持子查询。

以下是在 OLE DB 提供程序代码中用于定义 SQLPROPSET_OPTHINTS 属性集的常量：

Extern const GUID SQLPROPSET_OPTHINTS =

{ 0x2344480c, 0x33a7, 0x11d1,

{ 0x9b, 0x1a, 0x0, 0x60, 0x8, 0x26, 0x8b, 0x9e }

};

enum SQLPROPERTIES

{

SQLPROP_NESTEDQUERIES = 0x4,

SQLPROP_DYNAMICSQL = 0x5,

SQLPROP_GROUPBY = 0x6,

SQLPROP_DATELITERALS = 0x7,

SQLPROP_ANSILIKE = 0x8,

SQLPROP_INNERJOIN = 0x9,

SQLPROP_SUBQUERIES = 0x10,

}


全文检索

一直以来，从数据库列或文件系统中检索特定文本数据曾经是麻烦且花销很大的过程，且经常需要借助第三方工具。

Microsoft® SQL Server™ 提供多文本数据检索系统。SQL Server 2000 提供了一种增强的全文检索服务，使您得以：

  • 在后台更新索引。

    填充或更新索引不必影响其它任务。可以在后台使用全文索引向导、SQL Server 企业管理器或 SQL Server 代理程序作业调度程序来调度全文索引更新。

  • 在全文索引的三种维护方法中选择一种。

    根据数据和资源情况，可在完全重建、基于时间戳的增量重建和更改跟踪这三种方法中选择一种来维护全文索引。完全重建方法涉及重新扫描所有行。基于时间戳的增量重建方法仅重新扫描自上次重建（完全重建或增量重建）索引后更改的行。通过使用更改跟踪方法，SQL Server 维护对索引数据的所有更改列表，可使用该列表更新全文索引。有关更多信息，请参见维护全文索引。

  • 对 image 列中存储的某些类型的数据进行索引和检索。

    使用全文检索，可以对 image 列中存储的某些类型的数据进行索引和查询。全文检索使用几种支持的筛选之一来解释这些数据并析取文本数据，以便进行索引和查询。SQL Server 提供用于 .doc、.xls、.ppt、.txt 和 .htm 文件扩展名的筛选。有关更多信息，请参见筛选支持的文件类型。

    一旦对 image 列进行了索引，就可以使用检索谓词 CONTAINS 和 FREETEXT 检索该列。有关更多信息，请参见使用全文谓词查询 Image 列。

  • 限制返回匹配的数量。

    当在查询中使用 CONTAINSTABLE 或 FREETEXTTABLE 行集函数的可选参数 top_n_by_rank 时，SQL Server 将只返回排在前面的若干匹配项，数量由上面的数字 n 所指定。有关更多信息，请参见使用 CONTAINSTABLE 和 FREETEXTTABLE 赋值行集函数中的"限制结果集"。

请参见

CONTAINS

CONTAINSTABLE

FREETEXT

FREETEXTTABLE

sp_fulltext_column


对 SQL Server 数据进行全文查询

数字信息有存储在数据库列中的，也有作为非结构数据（主要是文本）存储在文件系统中的。还有一些文本数据存储在数据库的字符类型列中。例如，在 Microsoft® SQL Server™ 中，这样的数据可以存储在数据类型为 char、varchar、text、ntext、nchar 或 nvarchar 的数据库列中。

因此，就需要一种方法从数据库中检索这种文本数据。传统的关系数据库管理系统在文本数据的查找模式方面能力有限。例如，一个系统也许可以根据模式匹配来检索文本，但是不能处理查找邻近的单词和短语这方面的检索。

以前，为了检索或者查询存储在这些基于字符的数据库列中的数据，关系数据库管理系统的企业用户不得不购买昂贵的第三方工具。这些解决方案一般包括两个步骤：

  1. 通过一个通道将数据从数据库拉出。

  2. 将该数据存为基于字符的操作系统文件，以便能够应用全文索引。

使用这个分两步的过程意味着，无法将全文查询与常规的结构化关系查询完美地结合起来。SQL Server 通过允许对 SQL Server 表中的基于纯字符的数据（包括单词和短语，或者一个单词或短语的多种形式）进行全文查询，解决了这个问题。

启用数据库以便进行全文索引

企业管理器

企业管理器

Transact-SQL

启用表以便进行全文索引

企业管理器

企业管理器

Transact-SQL

启用列以便进行全文索引

企业管理器

企业管理器

Transact-SQL

编辑表中的全文索引

企业管理器

企业管理器

Transact-SQL

从表中删除全文索引

企业管理器

企业管理器

Transact-SQL {

创建全文目录

企业管理器

企业管理器

Transact-SQL

重建全文目录

企业管理器

企业管理器

Transact-SQL

在数据库中重建所有全文目录

企业管理器

企业管理器

Transact-SQL

启动和停止对全文索引的完全填充或增量填充

企业管理器

企业管理器

Transact-SQL

检查全文目录的状态、表和调度

企业管理器

企业管理器

更改或创建全文目录的新调度

企业管理器

企业管理器

从数据库中删除全文目录

企业管理器

企业管理器

Transact-SQL

删除数据库中的所有全文目录

企业管理器

企业管理器

Transact-SQL

清理服务器上的全文目录

企业管理器

企业管理器

Transact-SQL

为数据库重新填充所有全文目录

企业管理器

企业管理器

Transact-SQL


全文索引和查询概念

全文索引、查询和同步化最主要的设计要求是，在注册进行全文检索的所有表上都有一个唯一的全文键列（或者单列主键）。全文索引对使用的重要字及其所在位置进行跟踪。

例如，假定有一个对 DevTools 表的全文索引。全文索引可能指出在 Abstract 列的第 423 个和第 982 个单词处找到了单词 Microsoft，所在的行与 ProductID 6 关联。该索引结构支持对所有包含被索引单词的项进行有效检索，以及高级检索操作，如短语检索和邻近检索。

为防止全文索引因包含很多对检索没有帮助的词而变得臃肿，a、and、is 或 the 这类额外的词都忽略不计。例如，指定"the products ordered during these summer months"与指定"products ordered during summer months"是一样的。有这两个字符串的行都会被返回。

目录 \Mssql\Ftdata\Sqlserver\Config 下提供了多种语言的干扰词列表。在安装带有全文检索支持的 Microsoft® SQL Server™ 时会创建这个目录，并同时安装干扰词文件。干扰词文件可以编辑。例如，高技术公司的系统管理员可以把单词 computer 添加到他们的干扰词表中去。（如果编辑干扰词文件，则必须在更改生效之前重新填充全文目录。）下表显示了干扰词文件及其相应的语言。

干扰词文件 | 语言
Noise.chs | 简体中文
Noise.cht | 繁体中文
Noise.dat | 语言中性
Noise.deu | 德语
Noise.eng | 英语（英国）
Noise.enu | 英语（美国）
Noise.esn | 西班牙语
Noise.fra | 法语
Noise.ita | 意大利语
Noise.jpn | 日语
Noise.kor | 韩文
Noise.nld | 荷兰语
Noise.sve | 瑞典语

在处理全文查询时，检索引擎将满足检条件的行的键值返回给 Microsoft SQL Server。比如有一个 SciFi 表，其中 Book_No 列是主键列。

Book_No | Writer | Title
A025 | Asimov | Foundation's Edge
A027 | Asimov | Foundation and Empire
C011 | Clarke | Childhood's End
V109 | Verne | Mysterious Island

假定想使用一个全文检索查询来查找包含单词 Foundation 的书名。在本例中，将从全文索引获得值 A025 和 A027。然后 SQL Server 用这些键值和其它栏的信息响应该查询。

下表显示了存储全文索引数据所使用的语言。这些语言基于 SQL Server 安装期间选择的 Unicode 排序规则区域设置标识符。

Unicode 排序规则区域设置标识符 | 全文数据存储所用的语言
中文注音符号（台湾） | 繁体中文
汉语拼音 | 简体中文
中文笔画 | 简体中文
中文笔画（台湾） | 繁体中文
荷兰语 | 荷兰语
英语（英国） | 英语（英国）
法语 | 法语
通用 Unicode | 英语（美国）
德语 | 德语
德文电话簿 | 德语
意大利语 | 意大利语
日语 | 日语
日语 Unicode | 日语
韩文 | 韩文
韩文 Unicode | 韩文
现代西班牙语 | 西班牙语
瑞典/芬兰语 | 瑞典语

此列表中没有的其它所有 Unicode 排序规则区域设置标识符值都映射到使用空格分隔单词的中性语言单词的断字符和词干分隔符。

说明 Unicode 排序规则区域设置标识符设置用于所有可进行全文索引的数据类型（如 char、nchar 等）。如果为 char、varchar 或 text 类型列的排序次序设置的语言类型，不是 Unicode 排序规则区域设置标识符语言，那么在对 char、varchar 和 text 类型的列进行全文索引和查询时，仍然使用 Unicode 排序规则区域设置标识符值。


执行全文检索

使用全文查询，可以对启用了全文检索的表中的字符数据执行语言检索。语言检索对单词和短语进行操作，不像 LIKE 谓词是用于检索字型。而且，全文检索功能可以对查询条件进行衡量，报告与原始条件的匹配程度。

在给定数据库中执行全文检索涉及到以下这些任务：

  1. 标识要注册进行全文检索的表和列。

  2. 索引注册了的列的数据，并用非无关单词填充全文索引。

  3. 对注册了的列查询填充的全文索引。

  4. 应确保后来对注册列中数据的更改能够传播到索引，以此保持全文索引与数据的同步。

任务 1、2 和 4 是使用图形工具和向导（可以通过 SQL Server 企业管理器或内建的过程来获取）完成的。有关管理全文索引的更多信息，请参见维护全文索引。

说明 SQL Server 不支持链接服务器上的全文检索。

有关对注册列查询填充的全文索引（任务 3）的信息是"访问和更改数据"中全文主题的主要内容。

与标准关系数据库索引不同，当全文注册列中的值更新时，当在全文注册表中添加行时，或者当从全文注册表中删除行时，全文索引不会立即修改。相反，全文索引被异步重新填充，因为：

  • 一般更新全文索引比更新标准索引所需要的时间要多很多。

  • 全文检索通常没有标准检索精确，因为检索结果是一个行集，其中包含了被检索的单词或者短语（无论它们出现在字符流的什么位置）。例如，使用标准索引时，检索返回一个与原始查询完全匹配的精确字符模式或数字。而使用全文检索，可以搜索与数据很相近的形式，如名词的复数形式、动词可能使用的各种形式，或者原始搜索条件的大写或小写形式。

    全文索引的使用目的与常规索引不同，常规索引在其相关表中的数据更改时必须立即更新。而全文索引可以与其表中的数据同步。虽然全文索引填充会花些时间，但这些更新不需要中断。可以在后台使用 SQL Server 代理程序作业调度程序、sp_add_job 存储过程或全文索引向导来调度这些更新。

在数据改变之后，重新填充一个索引时，唯一的键列值被传递给检索引擎，以标识那些需要重新索引的项。


Transact-SQL 全文查询组件

Microsoft® SQL Server™ 提供下列用于全文查询的 Transact-SQL 组件。

Transact-SQL 谓词

这些谓词可以用在 SELECT 语句的任意搜索条件（包括 WHERE 子句）中。

  • CONTAINS

  • FREETEXT
Transact-SQL 行集赋值函数

这些函数可以用在 SELECT 语句的 FROM 子句中。

  • CONTAINSTABLE

  • FREETEXTTABLE
Transact-SQL 全文属性

下列 Transact-SQL 函数返回有关数据库对象全文属性的信息。

SERVERPROPERTY

  • IsFullTextInstalled 表示全文组件与 SQL Server 的当前实例一起安装。该属性是同名的 FULLTEXTSERVICEPROPERTY 函数属性的对等物。

DATABASEPROPERTYEX 和 DATABASEPROPERTY

  • IsFullTextEnabled 表示是否已为全文索引启用数据库。

COLUMNPROPERTY

  • IsFullTextIndexed 表示是否已为全文索引启用列。

OBJECTPROPERTY

  • TableFullTextBackgroundUpdateIndexOn 表示表是否有全文后台更新索引。

  • TableFullTextCatalogId 提供表的全文索引数据所驻留的全文目录 ID。

  • TableFullTextChangeTrackingOn 表示全文更改跟踪是否已在表上启用。

  • TableFullTextKeyColumn 提供全文唯一键列的列 ID。

  • TableFullTextPopulateStatus 表示全文表的填充状态。

  • TableHasActiveFulltextIndex 表示表是否含有活动全文索引。

INDEXPROPERTY

  • IsFulltextKey 表示索引是否为表的全文键。

Transact-SQL 包含专门返回全文属性的函数。

FULLTEXTCATALOGPROPERTY 返回有关全文目录属性的信息：PopulateStatus、ItemCount、IndexSize、UniqueKeyCount、LogSize 和 PopulateCompletionAge。有关更多信息，请参见 FULLTEXTCATALOGPROPERTY。

FULLTEXTSERVICEPROPERTY 返回有关全文服务等级属性的信息：ResourceUsage、ConnectTimeout 和 IsFulltextInstalled。IsFulltextInstalled 返回与同名的 SERVERPROPERTY 属性相同的信息。有关更多信息，请参见 FULLTEXTSERVICEPROPERTY。

Transact-SQL 全文系统存储过程

这些存储过程可以用来与编写查询相结合。例如，在指定一个查询之前，可以用它们查找被全文索引的表列的名称和全文唯一键列的列 ID。

  • sp_fulltext_database 是从当前数据库启用或删除全文索引的存储过程。

  • sp_fulltext_catalog、sp_fulltext_table 和 sp_fulltext_column 都是用于定义全文索引和初始化全文索引填充的存储过程。

  • sp_help_fulltext_catalogs、sp_help_fulltext_tables、sp_help_fulltext_columns 以及这些存储过程的变体，都用来查询由此前标识的系统存储过程所定义的全文索引元数据。

说明 全文系统存储过程不能用于事务处理。


使用 CONTAINS 谓词

可以使用 CONTAINS 谓词在数据库中检索特定的短语。当然，这样的查询可以使用 LIKE 谓词编写。但是，CONTAINS 的许多形式所提供的文本查询能力比 LIKE 要强。此外，与使用 LIKE 谓词不同的是，CONTAINS 检索总是区分大小写。

说明 对于那些区分大小写的语言（多数都基于拉丁语），全文检索查询以区分大小写的方式执行。但是，在日语中，有许多表示语音的拼字法，其中拼字规范化这一概念与不区分大小写类似，如 kana = 不区分。这种拼字规范化不被支持。

假定想在 Northwind 数据库内进行搜索，以查找短语"bean curd"。如果使用 CONTAINS 谓词，这就是一个很简单的查询。

USE Northwind
GO
SELECT Description
FROM Categories
WHERE Description LIKE '%bean curd%'
GO

或者，使用 CONTAINS：

USE Northwind
GO
SELECT Description
FROM Categories
WHERE CONTAINS(Description, ' "bean curd" ')
GO

CONTAINS 谓词使用的函数表示法中，第一个参数是要检索的列的名称，第二个参数是全文检索条件。检索条件（此例中是"bean curd"）可以很复杂，由一个或多个条件组成，这将在后面介绍。

CONTAINS 谓词支持使用复杂的语法在基于字符的列中检索：

  • 一个或多个特定的单词和/或短语（简单条件）。

    一个单词是一个或多个字符，中间没有空格或标点。有效的短语可以由带空格的多个单词组成，单词之间可以有标点也可以没有标点。例如，croissant 是一个单词，而 café au lait 是一个短语。这样的单词和短语称为简单条件。

  • 特定单词的变形（派生条件）。

    例如，检索单词 drive 的变形。如果表中的不同行包含单词 drive、drives、drove、driving 和 driven，那么所有这些都将包含在结果集中，因为其中每一个都可以从单词 drive 变形而来。

  • 以指定文本（前缀条件）打头的单词或短语。

    如果是短语，则该短语内的每个单词都被看作是一个前缀。例如，条件 auto tran* 与 automatic transmission 和 automobile transducer 匹配。

  • 使用加权值的单词或短语（加权条件）。

    例如，查找一个赋权值比另一个单词高的单词。这将返回排序的查询结果。

  • 与另一个单词或短语相近的单词或短语（邻近条件）。

    例如，查找一些行，这些行中单词 ice 与 hockey 相邻，或者短语 ice skating 与 ice hockey 相邻。

CONTAINS 谓词可以用 AND 和 OR 将这些条件中的几个结合起来，例如，要查找同一个启用全文的数据库列中所有有 latte 和 New York-style bagel 的行。不仅如此，还可以使用 AND NOT 对条件取反，例如 bagel and not cream cheese。

在使用 CONTAINS 时，记住 SQL Server 将从检索条件中去掉干扰词。干扰词是象 a、and、is 或 the 这样的词，这些词可能经常出现，但是在检索特定文本时没有真正的帮助。


检索特定单词或短语（简单条件）

在基于拉丁语和其它单字节的语言中，如果一组字符由空格或标点在两头隔开，则它通常解释为一个单词，而如果它本身包含多个单词并有空格，而且单词之间可以有（或没有）标点，则解释为短语。例如，在英语中，像 clock 或 calendar 这样的单词包含一个或多个字符，没有空格或标点。在大多数语言中，一个短语包含多个单词并有空格，而且单词之间可以有（或没有）标点，如"cheese, crackers, and apple juice"。

亚洲语言则不同，因为亚洲语言的一个字符也可以是一个字，再由字组成词，词中间不必空格或加标点。

下面的查询在 titles 表的 notes 列检索单词 business。

USE pubs
GO
SELECT title_id, title, notes
FROM titles
WHERE CONTAINS(notes, 'business')
GO

此查询在 titles 表的 notes 列检索短语"common business applications"。

USE pubs
GO
SELECT title_id, title, notes
FROM titles
WHERE CONTAINS(notes, ' "common business applications" ')
GO

CONTAINS 谓词只能用于其中的列启用了全文查询的表。有关使一列或多列启用全文查询的更多信息，请参见 sp_fulltext_table 和 sp_fulltext_column，以及全文索引。

可以使一个或多个表列启用全文查询。对该表给定的全文谓词可以访问表中一个或所有启用全文查询的列。假定 pubs 数据库的 title 表中的 title 和 notes 列都启用了全文方式，那么只要一个行的任何一个这样的列中有短语"French gourmet"，下面的查询就会返回该行中的书名 ID、书名和价格。

USE pubs
GO
SELECT title_id, title, price
FROM titles
WHERE CONTAINS( *, ' "French gourmet" ' )
GO

星号 (*) 用来代替一列的名称，表示该表中所有启用了全文查询的列。


使用 AND、OR 和 AND NOT 组合全文检索运算符

可以在 CONTAINS 谓词的搜索条件之间使用圆括号和布尔运算符（AND、AND NOT 和 OR）。假定 titles 表中的一行或多行包含关于喜爱的配方（favorite recipes）和美食家的配方（gourmet recipes）的信息。要检索包含关于任何一种配方类型的文本的行，请在"favorite recipes"和"gourmet recipes"短语之间使用一个 OR。

USE pubs
GO
SELECT title, notes
FROM titles
WHERE CONTAINS( notes, ' "favorite recipes" OR "gourmet recipes" ' )
GO

此例检索 titles 表中 titles 包含 cooking，但是不包含 computers 和 computer 的所有行。

USE pubs
GO
SELECT title_id, title, ytd_sales
FROM titles
WHERE CONTAINS( title, ' cooking AND NOT ("computer*" )' )
GO

此例获得一个产品目录说明表，其中的说明提到了单词 beers 和 ales。

USE Northwind
GO
SELECT CategoryName, Description
FROM Categories
WHERE CONTAINS( Description, ' beers AND ales ' )
GO

可以将短语和谓词结合起来检索单词和短语的组合。例如，可以检索所有包含滑冰（ice skating）或冰球（hockey）但是不包含奥运会（Olympics）的行。使用 CONTAINS 谓词的 WHERE 子句的形式如下：

WHERE CONTAINS (*, '("ice skating" or hockey) AND NOT olympics')


检索单词或短语的多种形式（前缀条件）

可以在列中检索以指定单词或短语打头的文本。用于检索的指定文本称为前缀条件。

当前缀条件是单词时，将返回列中所有以该单词打头的项目。例如，若要检索包含单词 ice 的所有行，如在 ice、ice cream 或 ice-shaved drinks 中，查询形式如下：

USE Northwind
GO
SELECT Description, CategoryName
FROM Categories
WHERE CONTAINS (Description, ' "ice*" ' )
GO

将会返回所有与星号 (*) 之前指定的文本相匹配的文本。如果像在 CONTAINS (DESCRIPTION, 'ice*') 中一样，文本和星号没有由双引号分隔，则全文检索会将星号看作是字符并搜索 ice* 的精确匹配项。

当前缀条件是短语时，组成短语的每个单词均被看作是单独的前缀条件。将返回包含以该前缀条件打头的单词的所有行。例如，前缀条件"light bread*"将查找含有"light breaded"、"lightly breaded"或"light bread"的所有行。

USE Northwind
GO
SELECT Description, CategoryName
FROM Categories
WHERE CONTAINS (Description, ' "light bread*" ' )
GO


检索指定单词的任意形式（通用条件）

可以检索一个动词的所有各种时态，或者名词的单数和复数形式。例如，下面的查询检索 Categories 表的 Description 列中 dry 的各种形式（dry、dried、drying 等等）。

USE Northwind
GO
SELECT Description, CategoryName
FROM Categories
WHERE CONTAINS (Description, 'FORMSOF(INFLECTIONAL, "dry")')
GO

注意，一个条件不能用来同时匹配同一个查询中的名词和动词。


使用加权值检索单词或短语（加权条件）

可以检索单词或短语，并指定加权值。权值是介于 0.0 和 1.0 之间的数字，表示一组单词和短语中每个单词和短语的重要程度。权值为 0.0 时，是可用的最低值，1.0 是可用的最高值。例如，下面的查询使用权值检索所有符合以下条件的顾客地址：地址中所有以字符串 des 打头的文本都与 Rue 或 Bouchers 相邻。Microsoft® SQL Server™ 将对那些包含指定单词较多的行赋予较高的等级值。因此，SQL Server 对包含 des Rue Bouchers 的行赋予的等级值比对包含 des Rue 的行赋予的等级值要高。

USE Northwind
GO
SELECT CompanyName, ContactName, Address
FROM Customers
WHERE CONTAINS(Address, 'ISABOUT ("*des*",
         Rue WEIGHT(0.5),
         Bouchers WEIGHT(0.9)
         ) ' )
GO

加权条件可以与其它四种类型的条件结合起来使用。


检索与另一个单词或短语近似的单词或短语（邻近条件）

可以检索与另一个单词或短语邻近的单词或短语。此外，还可以以任意顺序指定两个单词或短语，其结果是一样的。下例检索与单词 computers 接近的单词 user。

USE pubs
GO
SELECT title, notes
FROM titles
WHERE CONTAINS (notes, 'user NEAR computers')
GO

但也可以交换 WHERE 子句中两个单词的位置，获得的结果是一样的：

WHERE CONTAINS (notes, 'computers NEAR user')

可以指定用"~"符号代替前面查询中的 NEAR 关键字，获得的结果是一样的：

WHERE CONTAINS (notes, 'computers ~ user')

可以在检索条件中指定两个以上的单词或短语。例如，可以这样：

WHERE CONTAINS (notes, ' hardware ~ softward ~ computer ')

这表示 hardware 应该与 software 接近，software 应该与 computer 接近。

此外，匹配检索词的前缀这种方法可以与检索与另一个单词或短语接近的单词或短语的方法相结合。此例检索符合这些条件的所有描述：其中的 sauces 接近 mix 的任意形式，如 mixing 或 mixed。

WHERE CONTAINS(Description, ' sauces ~ "mix*" ')

若要查找 wheat bread mix 以及 wheatberry bread mix，可以使用这样的检索：

WHERE CONTAINS(Description, ' "wheat*" ~ "bread mix" ')

请参见

CONTAINS

WHERE


使用 FREETEXT 谓词

使用 FREETEXT 谓词可以输入单词或短语的任意集合，甚至一个完整的句子。全文查询引擎将检查该文本，标识出所有重要的单词和名词短语，并用这些条件在内部构造一个查询。此例使用 FREETEXT 谓词查询名为 description 的一列。

FREETEXT (description, ' "The Fulton County Grand Jury said Friday an investigation of Atlanta's recent primary election produced no evidence that any irregularities took place." ')

检索引擎将标识出如下的单词和名词短语：

单词

Fulton, county, grand, jury, Friday, investigation, Atlanta, recent, primary, election, produce, evidence, irregularities

短语

Fulton county grand jury, primary election, grand jury, Atlanta's recent primary election

FREETEXT 字符串中的这些单词和短语（及其变形）在内部结合到一个查询中，并被赋予适当的加权等级值，然后才执行实际的检索。

请参见

FREETEXT


使用全文谓词查询 image 列

CONTAINS 和 FREETEXT 谓词可以用来检索索引的 image 列。

许多文档类型可以存储在单个 image 列中。Microsoft® SQL Server™ 支持某些文档类型并为这些类型提供筛选。此次发布的版本提供用于 Office 文档、文本文件和 HTML 文件的筛选。

当 image 列参与到全文索引中时，全文服务会查看 image 列中文档的扩展名，并应用相应的筛选解释二进制数据，然后析取索引和查询所需的文本信息。

这样，当对表中的 image 列建立全文索引时，必须另外创建一列来保存该文档的信息。这一类型列可以是任何基于字符的数据类型，并包含文档的文件扩展名，如用于 Microsoft Word 文档的 doc。如果类型列为 NULL，全文服务将假定该文档为文本文件。

  • 在全文索引向导中，如果选择了一个 image 列用于索引，还必须同时指定一个 Binding 列以存放文档类型。

  • sp_fulltext_column 存储过程也接受列的参数以容纳文档类型。

  • sp_help_fulltext_columns 存储过程还返回文档类型列的列名和列标识。

有关设置全文索引和在 image 列上检索的更多信息，请参见筛选支持的文件类型。

image 列一旦经过索引，就可以象对待表中的任何其它列一样，使用 CONTAINS 和 FREETEXT 谓词对其进行查询。


将全文谓词与其它 Transact-SQL 谓词结合起来

CONTAINS 和 FREETEXT 谓词可以与其它任意 Transact-SQL 谓词结合起来，如 LIKE 和 BETWEEN；它们还可用于子查询中。此例检索符合下列条件的说明：其中的目录不是 Seafood，而且其中的说明包含单词 sauces 和 seasonings。

USE Northwind
GO
SELECT Description
FROM Categories
WHERE CategoryName <> 'Seafood' AND
   CONTAINS(Description, ' sauces AND seasonings ')
GO

下面的查询在子查询内使用了 CONTAINS。使用 pubs 数据库，该查询获得了 titles 表中所有符合以下条件的书的书名值：其出版商所在地接近安大略省 (Ontario) 的飞碟之都 Moonbeam。（关于出版商的此信息在 pub_info 表的 pr_info column 中，此处只有一个这样的出版商。）

USE pubs
GO
-- Add some interesting rows to some tables.
INSERT INTO publishers
  VALUES ('9970',
          'Penumbra Press',
          'Moonbeam',
          'ON',
          'Canada')
INSERT INTO pub_info (pub_id, pr_info)
  VALUES ('9970',
          'Penumbra press is located in the small village of Moonbeam.  Moonbeam is well known as the flying saucer capital of Ontario.  You will often find one or more flying saucers docked close to the tourist information centre on the north side of highway 11.')
INSERT INTO titles
  VALUES ('FP0001',
          'Games of the World',
          'crafts',
          '9970',
          9.85,
          0.00,
          20,
          213,
          'A crafts book!  A sports book!  A history book!  The fun and excitement of a world at play - beautifully described and lavishly illustrated',
          '1977/09/15')
GO
-- Given the full-text catalog for these tables is pubs_ft_ctlg,
-- repopulate it so new rows are included in the full-text indexes.
sp_fulltext_catalog 'pubs_ft_ctlg', 'start_full'
WAITFOR DELAY '00:00:30'   -- Wait 30 seconds for population.
GO
-- Issue the query.
SELECT T.title, P.pub_name
FROM publishers P,
     titles T
WHERE P.pub_id = T.pub_id
  AND P.pub_id = (SELECT pub_id
                  FROM pub_info
                  WHERE CONTAINS (pr_info,
                                 ' moonbeam AND
                                   ontario AND
                                   "flying saucer" '))
GO

请参见

CONTAINS

FREETEXT

WHERE


使用 CONTAINSTABLE 和 FREETEXTTABLE 赋值行集函数

CONTAINSTABLE 和 FREETEXTTABLE 函数用来指定返回每行的相对排名的全文查询。这两个函数与全文谓词 CONTAINS 和 FREETEXT 很相似，但是用法不同。

区分全文谓词与函数

虽然全文谓词和全文赋值行集函数都用于全文查询，而且二者用来指定全文检索条件的 Transact-SQL 语句是一样的，但是它们在使用方法上仍有主要差别：

  • CONTAINS 和 FREETEXT 都返回 TRUE 或 FALSE 值，因此它们一般在 SELECT 语句的 WHERE 子句中指定。

    CONTAINSTABLE 和 FREETEXTTABLE 都返回一个有 0、1 或多行的表，因此它们必须总是在 FROM 子句中指定。

  • CONTAINS 和 FREETEXT 只能用于指定选择条件，Microsoft® SQL Server™ 使用该条件确定结果集的成员资格。

    CONTAINSTABLE 和 FREETEXTTABLE 也用来指定选择标准。返回的表中有一个名为 KEY的列，其中包含全文键值。每个全文注册表均含有这样的列，该列中的值一定是唯一的。在 CONTAINSTABLE 或 FREETEXTTABLE 返回的全文注册表中，KEY 列中的值是匹配全文检索条件中所指定的选择条件的行的唯一值。

    不仅如此，CONTAINSTABLE 和 FREETEXTTABLE 产生的表中还有名为 RANK 的一列，其中包含从 0 到 1000 的值。这些值用来根据返回的行满足选择标准的程度对它们进行排序。

使用 CONTAINSTABLE 和 FREETEXTTABLE 函数的查询比那些使用 CONTAINS 和 FREETEXT 谓词的查询更复杂，因为函数返回的符合要求的行必须显式地与原始 SQL Server 表中的行联接起来。

对于包含位于词"sauces"或"candies"附近的"sweet and savory"的 Description 列，下面的示例返回其所有食品分类的描述和分类名称。所有具有"Seafood"分类名称的行都将被忽视。仅返回等级值为 2 或等级值更高的行。

USE Northwind
GO
SELECT FT_TBL.Description,
   FT_TBL.CategoryName,
   KEY_TBL.RANK
FROM Categories AS FT_TBL INNER JOIN
   CONTAINSTABLE (Categories, Description,
      '("sweet and savory" NEAR sauces) OR
      ("sweet and savory" NEAR candies)'
   ) AS KEY_TBL
   ON FT_TBL.CategoryID = KEY_TBL.[KEY]
WHERE KEY_TBL.RANK > 2
   AND FT_TBL.CategoryName <> 'Seafood'
ORDER BY KEY_TBL.RANK DESC

对于包含位于词"sauces"或"candies"附近的"sweet and savory"的 Description 列，下面的示例返回其前 10 种食品的描述和分类名称。

SELECT FT_TBL.Description,
   FT_TBL.CategoryName,
   KEY_TBL.RANK
FROM Categories AS FT_TBL INNER JOIN
   CONTAINSTABLE (Categories, Description,
      '("sweet and savory" NEAR sauces) OR
      ("sweet and savory" NEAR candies)'
      , 10
   ) AS KEY_TBL
   ON FT_TBL.CategoryID = KEY_TBL.[KEY]
CONTAINSTABLE 和 CONTAINS 之间的比较

CONTAINSTABLE 函数和 CONTAINS 谓词使用相似的检索条件。

但是，在 CONTAINSTABLE 中指定将进行全文检索的表、要在表中检索的某列（或所有列）以及检索条件。还有一个参数是可选参数，使用户可以指出只返回最大指定数目的匹配项。有关更多信息，请参见"限制结果集"部分。

CONTAINSTABLE 返回包含一个名为 RANK 的列的表。这个 RANK 列包含表示行与选择条件匹配程度的每行的值。

此查询使用 CONTAINSTABLE 指定返回每行的排名值。

USE Northwind
GO
SELECT K.RANK, CompanyName, ContactName, Address
FROM Customers AS C
     INNER JOIN
     CONTAINSTABLE(Customers,Address, 'ISABOUT ("des*",
                                                Rue WEIGHT(0.5),
                                                Bouchers WEIGHT(0.9)
                                               )
                                      '
                  ) AS K
     ON C.CustomerID = K.[KEY]

下面是结果集：

RANK CompanyName          ContactName       address
---- ------------         -----------       -------
123  Bon app'             Laurence Lebihan  12, rue des Bouchers
65   Du monde entier      Janine Labrune    67, rue des Cinquante Otages
15   France restauration  Carine Schmitt    54, rue Royale
15   La maison d'Asie     Annette Roulet    1 rue Alsace-Lorraine
15   Maison Dewey         Catherine Dewey   Rue Joseph-Bens 532
15   Mère Paillarde       Jean Fresnière    43 rue St. Laurent
15   Spécialités du monde Dominique Perrier 25, rue Lauriston
15   Vins et alcools      Paul Henriot      59 rue de l'Abbaye
     Chevalier
15   Victuailles en stock Mary Saveley      2, rue du Commerce
FREETEXTTABLE 和 FREETEXT 之间的比较

下面的查询将 FREETEXTTABLE 查询扩展为先返回排名最高的行，并将每行的等级加到选择列表中。要指定该查询，必须知道 CategoryID 是 Categories 表的唯一键列。

USE Northwind
GO
SELECT KEY_TBL.RANK, FT_TBL.Description
FROM Categories AS FT_TBL
     INNER JOIN
     FREETEXTTABLE(Categories, Description,
                    'How can I make my own beers and ales?') AS KEY_TBL
     ON FT_TBL.CategoryID = KEY_TBL.[KEY]
ORDER BY KEY_TBL.RANK DESC
GO

FREETEXTTABLE 与 FREETEXT 在语法上的唯一区别是插入表名作为第一个参数。

下面是同一个查询的扩充，此查询只返回排名值为 10 或更大的行：

USE Northwind
GO
SELECT KEY_TBL.RANK, FT_TBL.Description
FROM Categories FT_TBL
     INNER JOIN
     FREETEXTTABLE (Categories, Description,
                    'How can I make my own beers and ales?') AS KEY_TBL
     ON FT_TBL.CategoryID = KEY_TBL.[KEY]
WHERE KEY_TBL.RANK >= 10
ORDER BY KEY_TBL.RANK DESC
GO
标识唯一键列名

使用赋值行集函数的查询很复杂，因为必须知道唯一键列的名称。每个启用全文查询的表都有 TableFulltextKeyColumn 属性，其中包含为在该表实施唯一行而选择的列的 ID 号。此例显示了如何以编程方式获得和使用键列名。

USE Northwind
GO
DECLARE @key_column sysname
SET @key_column = Col_Name(Object_Id('Categories'),
                           ObjectProperty(Object_id('Categories'),
                                          'TableFulltextKeyColumn')
                          )
print @key_column
EXECUTE ('SELECT Description, KEY_TBL.RANK
          FROM Categories FT_TBL
               INNER JOIN
               FreetextTable (Categories, Description,
                    ''How can I make my own beers and ales?'') AS KEY_TBL
               ON FT_TBL.'
         +
                         @key_column
         +
         '                            = KEY_TBL.[KEY]
           WHERE KEY_TBL.RANK >= 10
           ORDER BY KEY_TBL.RANK DESC
         ')
GO

可以编写接受关于查询的一些事实的存储过程，以避免使用 CONTAINSTABLE 和 FREETEXTTABLE 的复杂性，然后可以创建并执行相应的查询。下面是一个简化的过程，它提交一个 FREETEXTTABLE 查询。下表显示了过程参数（所有输入）。

参数 | 必需 | 描述
@additional_predicates | 可选 | 如果有的话，用 AND 加在 FREETEXT 谓词后面。KEY_TBL.RANK 可以用于表达式内部。
@freetext_column | 是 |
@freetext_search | 是 | 检索条件。
@from_table | 是 |
@order_by_list | 可选 | KEY_TBL.RANK 可以是指定的列之一。
< select_list > | 是 | KEY_TBL.RANK 可以是指定的列之一。

该过程的代码如下：

CREATE PROCEDURE freetext_rank_proc
       @select_list             nvarchar(1000),
       @from_table              nvarchar(517),
       @freetext_column         sysname,
       @freetext_search         nvarchar(1000),
       @additional_predicates  nvarchar(500)      = '',
       @order_by_list           nvarchar(500)      = ''
AS
BEGIN
   DECLARE @table_id              integer,
           @unique_key_col_name   sysname,
           @add_pred_var          nvarchar(510),
           @order_by_var          nvarchar(510)

   -- Get the name of the unique key column for this table.
   SET @table_id = Object_Id(@from_table)
   SET @unique_key_col_name =
   Col_Name( @table_id,
   ObjectProperty(@table_id, 'TableFullTextKeyColumn') )

   -- If there is an additional_predicate, put AND() around it.
   IF @additional_predicates <> ''
      SET @add_pred_var = 'AND (' + @additional_predicates + ')'
   ELSE
      SET @add_pred_var = ''

   -- Insert ORDER BY, if needed.
   IF @order_by_list <> ''
      SET @order_by_var = 'ORDER BY ' + @order_by_var
   ELSE
      SET @order_by_var = ''

   -- Execute the SELECT statement.
   EXECUTE (   'SELECT '
             + @select_list
             + ' FROM '
             + @from_table
             + ' AS FT_TBL, FreetextTable('
             + @from_table
             + ','
             + @freetext_column
             + ','''
             + @freetext_search
             + ''') AS KEY_TBL '
             + 'WHERE FT_TBL.'
             + @unique_key_col_name
             + ' = KEY_TBL.[KEY] '
             + @add_pred_var
             + ' '
             + @order_by_var
           )
END

此过程可以用来提交该查询：

USE Northwind
GO
EXECUTE freetext_rank_proc
     'Description, KEY_TBL.RANK',                -- Select list
     'Categories',                               -- From
     'Description',                              -- Column
     'How can I make my own beers and ales?',    -- Freetext search
     'KEY_TBL.RANK >= 10',                       -- Additional predicate
     'KEY_TBL.RANK DESC'                         -- Order by
GO
限制结果集

在许多全文查询中，符合检索条件的项有很多。为了防止查询返回太多的匹配值，可以在 CONTAINSTABLE 和 FREETEXTTABLE 中使用可选参数 top_n_by_rank，根据想返回的排名指定匹配数量。

有了这些信息，Microsoft® SQL Server™ 将按等级值对匹配项进行排序，然后只返回达到指定数目的匹配项。这种选择可以大幅度提高性能。例如，对于正常情况下会从一个一百万行的表中返回 100,000 行的查询而言，如果只要求返回前 100 行，则查询处理速度将会更快。

如果在前面使用 CONTAINSTABLE 的示例中，只想返回前 3 个匹配项，则查询形式如下：

USE   Northwind
GO
SELECT   K.RANK, CompanyName, ContactName, Address
FROM      Customers AS C
         INNER JOIN
         CONTAINSTABLE(Customers,Address, 'ISABOUT ("des*",
            Rue WEIGHT(0.5),
            Bouchers WEIGHT(0.9))', 3) AS K
         ON C.CustomerID = K.[KEY]

下面是结果集：

RANK CompanyName          ContactName       address
---- ------------         -----------       -------
123  Bon app'             Laurence Lebihan  12, rue des Bouchers
65   Du monde entier      Janine Labrune    67, rue des Cinquante Otages
15   France restauration  Carine Schmitt    54, rue Royale

请参见

CONTAINSTABLE

FREETEXTTABLE


使用 Transact-SQL 函数获取全文属性值

有几个 Transact-SQL 函数可以用来获取全文属性值。例如，可以使用 TableFulltextKeyColumn 属性通过编程方式获得一个表的唯一键列标识。而且，IsFullTextEnabled 属性可以用来检查一个数据库是否启用了全文查询。此例检查 Northwind 数据库是否启用了全文查询。

USE Northwind
GO
SELECT DATABASEPROPERTY('Northwind', 'IsFullTextEnabled')

如果返回的值为 1，则 Northwind 数据库启用了全文查询。值为 0 则表示 Northwind 数据库没有启用全文查询。

下表完整地列出了所有的属性。应该注意的是这些属性中有许多只能用于全文管理。

函数 | 属性
COLUMNPROPERTY | IsFulltextIndexed
DATABASEPROPERTY | IsFulltextEnabled
DATABASEPROPERTYEX | IsFulltextEnabled
INDEXPROPERTY | IsFulltextKey
OBJECTPROPERTY | TableFulltextBackgroundUpdateIndexOn
 | TableFulltextCatalogId
 | TableFulltextChangeTrackingOn
 | TableFulltextKeyColumn
 | TableFulltextPopulateStatus
 | TableHasActiveFulltextIndex
FULLTEXTCATALOGPROPERTY | PopulateStatus
 | ItemCount
 | IndexSize
 | UniqueKeyCount
 | LogSize
 | PopulateCompletionAge
FULLTEXTSERVICEPROPERTY | ResourceUsage
 | ConnectTimeout
 | IsFullTextInstalled

请参见

COLUMNPROPERTY

DATABASEPROPERTY

DATABASEPROPERTYEX

FULLTEXTCATALOGPROPERTY

FULLTEXTSERVICEPROPERTY

INDEXPROPERTY

OBJECTPROPERTY


全文管理与全文查询结合的示例

全文索引可以使用 SQL Server 企业管理器或存储过程进行管理。有时候，将全文管理存储过程结合在进行查询的同一个脚本中是很方便的。下面的脚本范例就结合了这两个任务：

  • 创建并填充一个表。

  • 将 pubs 数据库启用全文检索。

  • 创建全文目录。

  • 注册新表及其中的某些列，以进行全文检索。

  • 用新表中的全文索引信息填写新的全文目录。

  • 对新表执行全文查询。
USE pubs
-- Create and populate a table.
IF EXISTS (SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES
      WHERE TABLE_NAME = 'FulltextTest')
   DROP TABLE FulltextTest
GO
CREATE TABLE FulltextTest
             ( article_id int IDENTITY(100,1)
                             CONSTRAINT PK_title_id PRIMARY KEY,
               article_title nvarchar(200)
             )
GO
INSERT FulltextTest (article_title) VALUES (N'Steven Buchanan has always enjoyed ice skating.')
INSERT FulltextTest (article_title) VALUES (N'Elvis Stoiko: The best male figure skater')
INSERT FulltextTest (article_title) VALUES (N'Steven Buchanan On Ice: Skating Reaches Tops in Public Opinion Poll')
INSERT FulltextTest (article_title) VALUES (N'Last night, Steven Buchanan skated on the ice!! Skating fans cheer!')
INSERT FulltextTest (article_title) VALUES (N'Ice-skating brings out the best in Steven. Buchanan exults in first victory...')
GO

-- Enable full-text searching in the database.
EXEC sp_fulltext_database 'enable'
GO

-- Create a new full-text catalog.
EXEC sp_fulltext_catalog 'StevenBCatalog',
                         'create'
GO

-- Register the new table and column within it for full-text querying,
-- then activate the table.
EXEC sp_fulltext_table 'FulltextTest',
                       'create',
                       'StevenBCatalog',
                       'PK_title_id'
EXEC sp_fulltext_column 'FulltextTest',
                        'article_title',
                        'add'
EXEC sp_fulltext_table 'FulltextTest',
                       'activate'
GO

-- Start full population of the full-text catalog. Note that it is
-- asynchronous, so delay must be built in if populating a
-- large index.

EXEC sp_fulltext_catalog 'StevenBCatalog',
                         'start_full'
WHILE (SELECT fulltextcatalogproperty('StevenBCatalog',
'populatestatus')) <> 0
   BEGIN
      WAITFOR DELAY '00:00:02'     -- Check
      every 2 seconds to see if full-text index population is complete.
   CONTINUE
END

GO

-- Execute a full-text query against the new table.
SELECT article_title
FROM FulltextTest
WHERE CONTAINS(article_title, ' "Steven Buchanan" AND "ice skating" ')

下面是结果集：

article_title
------------------------------------------------------------------------
Steven Buchanan has always enjoyed ice skating.
Last night, Steven Buchanan skated on the ice!! Skating fans cheer!
Steven Buchanan On Ice: Skating Reaches Tops in Public Opinion Poll
Ice-skating brings out the best in Steven. Buchanan exults in first victory...
(4 row(s) affected)


文件数据的全文查询

Microsoft® SQL Server™ 不仅支持对 SQL Server 数据进行文本化查询，还支持对驻留在文件系统中的数据进行文本化查询。支持此功能的产品和特性包括 SQL Server 分布式查询、Microsoft Internet 信息服务 4.0 和 Microsoft 索引服务 2.0。

数字化存储的信息有很大一部分仍然是以非结构化数据的形式（主要是文本）存储在文件系统中。这些信息经常与数据库中的数据相关，这就要求对这两个数据源都进行查询。但是，从文件系统导入这些数据常常是不可行的。结合了 SQL 语言扩充的分布式查询使得编写这种没有数据的查询成为可能。这称为文件内容检索。

有两种主要的文本查询：

属性检索

对文档应用筛选来析取属性，如作者、主题、类型、字数、打印的页数以及最后编写的时间，然后对这些属性进行查询。

全文检索

创建文档中所有非干扰词的索引，然后使用这些索引支持语言检索和邻近检索。

例如，下面的查询在 D 驱动器上选择包含与文本邻近的"SQL Server"短语的所有 Microsoft Word 文件的名称、大小和作者。然后，它将此与作者表联接起来，以获得作者的国籍。

SELECT Q.FileName, Q.Size, Q.DocAuthor, W.Citizens7hip
FROM OpenQuery(MyLinkedServer,
               'SELECT FileName, Size, DocAuthor
                FROM SCOPE('' "D:\" '')
                WHERE CONTAINS(''"SQL Server"
                                 NEAR() text'')
                AND FileName LIKE ''%.doc%'' '
              ) > 0 AS Q,
     writers AS W
WHERE Q.DocAuthor = W.writer_name

文件内容检索依赖于用于 Microsoft 索引服务 2.0 的 Microsoft OLE DB 提供程序。它还依赖于索引服务支持基础筛选和全文索引。

用于 Microsoft 索引服务 2.0 的 Microsoft OLE DB 提供程序具有对独立于 SQL Server 的文件系统中的数据进行 SQL 查询的支持能力。为支持这种查询而对 SQL 语言进行的核心扩充在两个产品中是一样的。但是，有一些扩充与索引服务 2.0 无关。有关对 SQL Server 中的数据进行全文查询的语法的更多信息，请参见对 SQL Server 数据进行全文查询。

请参见

CONTAINS

WHERE

FREETEXT


使用 Microsoft Internet 信息服务和索引服务进行文件内容检索

Microsoft® Internet Information 服务 (IIS) 4.0 和索引服务 2.0 版（都包含在 Microsoft® Windows NT® 4.0 Option Pack 中）相结合，提供对文件数据的属性筛选和检索以及全文索引和检索。支持对文件数据进行文本查询比支持对数据库数据进行文本查询更有优势，这是因为在 Microsoft SQL Server™ 中，对后者的查询被限制为在基于字符的列上进行查询。这些文件内容检索能力独立于 SQL Server，而且支持 ADO（ActiveX® 数据对象）内基于 SQL 的查询。ADO 查询中使用的 SQL 与此处解释的 SQL 扩充一致。

格式筛选

索引服务提供用于几种流行文件格式（如 Microsoft Word、Microsoft PowerPoint®、Microsoft Excel 和 HTML）的筛选。筛选还可用于纯文本。用户和第三方供应商也可以编写用于其它格式的筛选。筛选的一个目的是对非纯文本文档提供支持。另一个目的是从文件内容获取属性值，以及获取关于文件的属性值。假定每个文件都是一个文档，属性包括每个文档的标题，每个 PowerPoint 文档中有注释的页数，每个文档的段落数，每个文档最后被访问的日期和时间，以及每个文件的物理路径。有关属性列表，请参见使用文件属性进行文件内容检索。有关属性的完整列表，请参见索引服务文档。

短语和邻近检索

文件系统检索的全文索引是通过扫描文件的内容创建的。该过程包括跟踪所使用的重要单词及其所在的位置。例如，全文索引可以指示在给定文件的第 227、473、1017 个单词处找到了单词 Canada。此索引结构支持对包含索引单词的所有项进行高效检索，以及进行高级检索操作，如短语检索和邻近检索。短语检索的一个例子是查找"white elephant"，其中的 white 在 elephant 前面。邻近检索的一个例子是查找其中的 big 出现在 house 附近的短语。为了防止全文索引因包含很多对检索没有帮助的词而变得臃肿，干扰词（如 a、and 和 the）都被忽略不计。

干扰词

已经提供了许多语言的干扰词列表，而且所支持的语言集也在增长。在筛选过程中，选择哪个干扰词列表取决于资料的语言，而该资料依赖于文件格式：有些文件按部分（例如，按段落）进行语言设置，而有些作为文档的属性指定语言设置。对于大多数标准操作，这些干扰词列表应该足够了，但是也可以用文本编辑器进行修改，以用于特定的环境。有关更多信息，请参见 Windows NT 4.0 Option Pack 中的索引服务 2.0 文档。

文本检索目录

索引服务在文本检索目录中存储索引和属性值。默认情况下，在安装索引服务时会创建名称为 Web 的文本检索目录。一个给定的文本检索目录引用一个或多个 IIS 虚拟目录（也称为虚拟根目录）。一个虚拟目录引用一个或多个物理目录，以及（可选的）其它虚拟目录。在真正的文件通过虚拟目录链接到文本目录之后，将会通知索引服务必须对哪些新文件进行索引，索引服务于是开始对与这些文件相关的属性和内容进行筛选和索引。以后如对这些文件进行任何更改，都会通知索引服务，索引服务会对更新的文件重新筛选并重新建立索引。


使用虚拟表进行文件内容查询

每个 SQL 查询必须至少指定一个已定义的表，也就是说列的数目和类型已预先知道，或者已作为查询的一部分指定。关系数据库通常包含许多预先定义的表，关于这些表中的列的元数据则存储在一个架构中。

但是，文件系统中文件的集合通常没有这种预定义结构。文件属性可能与列相似，但是文件没有确定的属性集。文件本身或许与行类似，但是文件通常不分成与表中的行类似的同种集合。因此，表的概念是不清楚的，SELECT * 是无意义的，而且行和列都是没有边界的。看待这个问题的另一个方法是，文件系统实际上都有一个广义的架构，其中包括每种可能的文件属性（已知的和未知的）。

Microsoft® 索引服务通过提供 SCOPE 函数来解决这个问题，该函数定义组成虚拟表的行集，并提供替代列的文件属性。


使用 SCOPE 函数进行文件系统查询

SCOPE 是在索引服务的 FROM 子句中定义的，它被用来指定组成虚拟表的文件集。下面是语法。

下表显示了语法元素及其描述。

语法元素 | 描述
() | 虚拟表包含在文本目录中注册的所有文件，和 OPENQUERY() 函数中指定的链接服务器的数据源。
DEEP TRAVERSAL OF | 虚拟表包括在指定路径下目录中的所有文件，或者虚拟目录以及所有子目录（到任意级）中的所有文件都被认为是虚拟表的一部分。如果 DEEP 和 SHALLOW 均未指定，则默认设置为 DEEP。
SHALLOW TRAVERSAL OF | 虚拟表只包括指定路径下最上层目录中的文件，或者虚拟目录被认为是虚拟表的一部分。
physical_path | 这是到真实目录的路径。如果指定了真实目录，则筛选和索引都作为查询过程的一部分来执行。这可能要花很长时间。
virtual_directory | 这是指派给虚拟目录的别名（或别名链），该虚拟目录已经在 OPENQUERY() 函数中指定的链接服务器的文本目录和数据源中注册。在这种情况下，筛选和索引可能已经做了，因此查询比在指定 physical_path 时要快得多。


使用文件属性进行文件内容检索

Microsoft® 索引服务筛选和维护超过 50 个文件属性。所有这些都可以在文件检索查询中指定。从编写一个 SELECT 语句来看，有 3 种属性：

  • 只能在 WHERE 子句中指定的属性。

  • 可以在 WHERE 子句中指定、而且还能够在 ORDER BY 子句中指定的属性。

  • 能够在 WHERE 子句中指定、而且还能够在选择列表中指定的属性。

下表显示了属性列表。

属性名称 | 数据类型 | 描述 | 在 ORDER BY 中使用 | 在选择列表中使用
Access | datetime | 访问文件的最新日期和时间。 | 是 | 是
Characterization | nvarchar 或 ntext | 文件内容摘要。注意，在这个版本的索引服务中，这通常是文档的第一段或者第一节。在以后的版本中，它将是一个实际的摘要。 |  | 是
Contents | nvarchar 或 ntext | 文件的主要内容。 |  |
Create | datetime | 文件创建的日期和时间。 | 是 | 是
Directory | nvarchar | 到该文件的物理路径，不包括文件名。 | 是 | 是
DocAuthor | nvarchar | 文档作者。 | 是 | 是
DocComments | nvarchar | 关于文档的注释。 | 是 | 是
DocLastAuthor | nvarchar | 上次编辑文档的用户。 | 是 | 是
DocLastPrinted | datetime | 文档上次打印的日期和时间。 | 是 |
DocPageCount | integer | 文档的页数 | 是 |
DocPartTitles | varchar 数组 | 文档各部分的名称： Microsoft PowerPoint® 中的幻灯片标题 Microsoft Excel 中的电子表格 Microsoft Word 中的文档。 |  |
DocSubject | nvarchar | 文档的主题。 | 是 | 是
DocTitle | nvarchar | 文档的标题。 | 是 | 是
DocWordCount | integer | 文档的字数。 | 是 | -
FileIndex | decimal(19,0) | 文件的唯一标识符。 | 是 | 是
FileName | nvarchar | 文件名。 | 是 | 是
HitCount | integer | 与查询匹配的字数。 | 是 | 是
Path | nvarchar | 到该文件的完整物理路径，包括文件名。 | 是 | 是
Rank | integer | 从 0 到 1000 的值，表示该行与选择标准接近的程度。 | 是 | 是
Size | decimal(19,0) | 文件大小，以字节计。 | 是 | 是

用户和第三方供应商可以编写筛选并将其添加到该属性集中。例如，他们还可以通过给 HTML 文档添加自己的标记来添加属性。此外，为了允许查询和检索这种用户定义的属性，对索引服务的 Transact-SQL 扩充包括对 SET 语句的支持，该语句允许指定新属性的名称及其相关类型。


使用文件内容和数据库数据进行全文查询示例

第一个查询返回符合条件的书的书名和出版年份，这些书用别名为 /pubs 的虚拟目录中的文件表示。符合查询要求的书的价格必须低于 $20.00，而且 Characterization 属性中的文本必须表示该书是关于 ice hockey 的。已经知道 Create 属性中的年份部分总是书的出版年份。用户已经定义了 BookCost 属性（为 money 类型），这筛选出每本书的价格。

SELECT Q.DocTitle, DATEPART(YEAR, Q.Create)
FROM OPENQUERY(FileSystem,
               'SELECT DocTitle, Create
                FROM SCOPE('' "/pubs" '')
                WHERE BookCost <= 20.00
                  AND CONTAINS( Characterization, '' "ice hockey" '' )
               ') AS Q

已经给 OPENQUERY 函数返回的表指派了别名值 Q。然后这个别名被用来限定外部选择列表中的项。其中，SQL Server DATEPART() 函数只是用来传递 create datetime 值的年份部分。

第二个查询返回的信息与上一个相同。不同之处在于，书的价格是从数据库的 BookCost 表的 document_cost 列中获得的，而不是从文件系统中的属性获得的。BookCost 表的主键是 document_author 和 document_title 的结合。

SELECT Q.DocTitle, DATEPART(YEAR, Q.Create)
FROM OPENQUERY(FileSystem,
               'SELECT DocTitle, Create, DocAuthor, DocTitle
                FROM SCOPE('' "/pubs" '')
                  AND CONTAINS( Characterization, '' "ice hockey" '' )
               ') AS Q,
     BookCost as B
WHERE Q.DocAuthor = B.document_author
  AND Q.DocTitle = B.document_title
  AND B.document_cost <= 20.00

OPENQUERY 函数返回的表被联接到数据库中真正的 BookCost 表，然后价格合适的行被筛选出来，包括到外部的 SELECT 中。

这最后一个查询也将文件系统中的数据和数据库联接起来，这一次，来自文件系统和数据库的数据都出现在外部选择列表中。而且，Rank 属性（表示所选的行符合选择条件的程度）出现在选择列表中，并用于确保证在外部 SELECT 中等级高的行出现在等级低的行的前面。此例在文件中记录了冰球明星纪念馆中的奖章上的文字。每个奖章都有一个文件，奖章号可以用 DocSubject 属性获得。HockeyHall 表包含 PlaqueNo、PlayerName、StartYear和 LastYear 列，主键是 PlaqueNo。该查询从表中返回 PlayerName 和 PlaqueNo 属性，从文件返回 Rank 和 DocComments 属性。只返回在 20 世纪初为加拿大队或美国队服务过的队员。

SELECT HH.PlayerName, HH.PlaqueNo, Q.Rank, Q.DocComments
FROM OPENQUERY(FileSystem,
               'SELECT DocSubject, DocComments, Rank
                FROM SCOPE('' "/hall_of_fame" '')
                WHERE CONTAINS( Contents, '' Canada OR "United States" '' )
               ') AS Q,
     HockeyHall as HH
WHERE Q.DocSubject = HH.PlaqueNo
  AND HH.StartYear < 1915 AND HH.EndYear < 1899
ORDER BY Q.Rank DESC


 访问和更改关系数据

使用虚拟表的查询示例

可以用与产生选择列表的文件系统中的表等价的项和 FROM 子句指定查询。对于 SELECT 语句的其它部分，这些属性可以用来替代 WHERE 子句和 ORDER BY 子句中的列。但是不支持 GROUP BY 和 HAVING 子句。下面的示例显示了所有支持的子句的用法。

此查询选择了在 SQL 标准虚拟目录及其所有子目录中，包含短语"overloaded function"的所有文件的完整物理路径和文件创建时间戳。

SELECT *
FROM OPENQUERY(FileSystem,
               'SELECT Path, Create
                FROM SCOPE('' "/SQL-standards" '')
                WHERE CONTAINS(Contents, '' "overloaded function" '') '
              )

此查询与上一个相似，只是它仅考虑直接在 SQL 标准虚拟目录中的文件。

SELECT *
FROM OPENQUERY(FileSystem,
               'SELECT Path, Create
                FROM SCOPE('' SHALLOW TRAVERSAL OF "/SQL-standards" '')
                WHERE CONTAINS(Contents, '' "overloaded function" '')   ' )

在接下来的这个查询中，只考虑直接在 SQL3 虚拟子目录中的文件。

SELECT *
FROM OPENQUERY(FileSystem,
               'SELECT Path, Create
                FROM SCOPE('' "/SQL-standards/SQL3" '')
                WHERE CONTAINS(Contents, '' "overloaded function" '')  ' )

这个查询在 /corpus 虚拟目录及其子目录或者 C 驱动器上 \temp 目录下的所有文件中选择文档的作者、标题、主题和文件名，其中的文档的字数至少是 5,000 字，作者是 Wendy Vasse 或者 Anas Abbar。代表页数最多的文档的行排在最上面：

SELECT *
FROM OPENQUERY(FileSystem,
               'SELECT DocAuthor, DocTitle, DocSubject, FileName
                FROM SCOPE('' "/corpus" '',
                           '' "C:\temp" '' )
                WHERE DocWordCount >= 5000 AND
                      ( DocAuthor = ''Wendy Vasse'' OR
                        DocAuthor = ''Anas Abar'' )
                ORDER BY DocPageCount DESC                    ' )