UPDATE (Transact-SQL)

更改 SQL Server 2008 表或视图中一列或多列的现有数据。

主题链接图标Transact-SQL 语法约定

语法

[ WITH common_table_expression [...n] ]
UPDATE 
    [ TOP (expression) [ PERCENT ] ] 
    { { table_alias | <object> | rowset_function_limited 
        [ WITH (table_hint_limited [ ...n ] ) ]
      }
      | @table_variable
    }
    SET
        { column_name= { expression | DEFAULT | NULL }
          | { udt_column_name.{ { property_name=expression
                                | field_name=expression }
                                | method_name(argument [ ,...n ] )
                              }
          }
          | column_name { .WRITE (expression,@Offset,@Length) }
          | @variable=expression
          | @variable=column=expression
          | column_name { += | -= | *= | /= | %= | &= | ^= | |= } expression
          | @variable { += | -= | *= | /= | %= | &= | ^= | |= } expression
          | @variable=column_name { += | -= | *= | /= | %= | &= | ^= | |= } expression
        } [ ,...n ] 

    [ OUTPUT clause ]
    [ FROM { table_source } [ ,...n ] ] 
    [ WHERE { search_condition 
            | { [ CURRENT OF 
                  { { [ GLOBAL ] cursor_name } 
                      | cursor_variable_name 
                  } 
                ]
              }
            } 
    ] 
    [ OPTION (query_hint [ ,...n ] ) ]
[ ; ]

<object> ::=
{ 
    [ server_name .database_name.schema_name. 
    | database_name .[ schema_name ] . 
    | schema_name.
    ]
    table_or_view_name
}

参数

  • WITH common_table_expression
    指定在 UPDATE 语句作用域内定义的临时命名结果集或视图,也称为公用表表达式 (CTE)。 结果集派生自 SELECT 语句并由 UPDATE 语句引用。 有关详细信息,请参阅 WITH common_table_expression (Transact-SQL)

  • TOP ( expression) [ PERCENT ]
    指定将要更新的行数或行百分比。expression 可以为行数或行百分比。 在 INSERT、UPDATE、MERGE 或 DELETE 语句的 TOP 表达式中引用的行不按任何顺序排列。

    在 INSERT、UPDATE、MERGE 和 DELETE 语句中,需要使用括号分隔 TOP 中的 expression。 有关详细信息,请参阅 TOP (Transact-SQL)

  • table_alias
    在表示要从中更新行的表或视图的 FROM 子句中指定的别名。

  • server_name
    表或视图所在的链接服务器的名称。server_name 可以指定为链接服务器名称,或者通过使用 OPENDATASOURCE 函数指定。

    在 server_name 指定为某一链接服务器时,需要 database_name 和 schema_name。 如果使用 OPENDATASOURCE 指定 server_name,则 database_name 和 schema_name 可能不适用于所有数据源,并且受到访问远程对象的 OLE DB 访问接口的性能的限制。 有关详细信息,请参阅分布式查询

  • database_name
    数据库的名称。

  • schema_name
    该表或视图所属架构的名称。

  • table_or view_name
    要更新行的表或视图的名称。

    table_or_view_name 引用的视图必须可更新,并且只在视图定义的 FROM 子句中引用一个基表。 有关可更新视图的详细信息,请参阅 CREATE VIEW (Transact-SQL)

  • rowset_function_limited
    OPENQUERYOPENROWSET 函数。 使用这些函数受到访问远程对象的 OLE DB 访问接口的性能的限制。 有关详细信息,请参阅分布式查询

  • WITH (table_hint_limited)
    指定目标表所允许的一个或多个表提示。 表提示在 UPDATE 语句执行期间覆盖查询优化器的默认行为。 不允许使用 NOLOCK 和 READUNCOMMITTED。 有关表提示的信息,请参阅表提示 (Transact-SQL)

  • @table\_variable
    变量指定为表源。

  • SET
    指定要更新的列或变量名称的列表。

  • column_name
    包含要更改的数据的列。column_name 必须已存在于 table_or view_name 中。 不能更新标识列。

  • expression
    返回单个值的变量、文字值、表达式或嵌套 select 语句(加括号)。 expression 返回的值替换 column_name 或 @variable 中的现有值。

    注意注意

    当引用 Unicode 字符数据类型 nchar、nvarchar 和 ntext 时,“expression”应采用大写字母“N”作为前缀。 如果未指定“N”,则 SQL Server 会将字符串转换为与数据库或列的默认排序规则相对应的代码页。 此代码页中没有的字符都将丢失。 有关详细信息,请参阅使用 Unicode 数据

  • DEFAULT
    指定用为列定义的默认值替换列中的现有值。 如果该列没有默认值并且定义为允许 Null 值,则该参数也可用于将列更改为 NULL。

  • { += | -= | *= | /= | %= | &= | ^= | |= }
    用于执行某个运算并将原始值设置为运算结果的复合运算符。

    +=    相加并赋值

    -=    相减并赋值

    *=    相乘并赋值

    /=    相除并赋值

    %=    取模并赋值

    &=    “位与”并赋值

    ^=    “位异或”并赋值

    |=    “位或”并赋值

    有关详细信息,请参阅复合运算符 (Transact-SQL)

  • udt_column_name
    用户定义类型列。

  • property_name | field_name
    用户定义类型的公共属性或公共数据成员。 提供值的表达式必须可隐式转换为属性的类型。

    若要修改同一用户定义类型列的不同属性,请发出多个 UPDATE 语句,或者调用该类型的赋值函数方法。

  • method_name( argument [ ,... n] )
    带一个或多个参数的 udt_column_name 的非静态公共赋值函数方法。如果对 Transact-SQL Null 值调用赋值函数方法,或者赋值函数方法产生的新值为 Null,则 SQL Server 将返回错误。

  • .WRITE (expression,@Offset, @Length)
    指定修改 column_name 值的一部分。expression 替换 @Length 单位(从 column_name 的 @Offset 开始)。 只有 varchar(max)、nvarchar(max) 或 varbinary(max) 列才能使用此子句来指定。column_name 不能为 NULL,也不能由表名或表别名限定。

    expression 是复制到 column_name 的值。expression 必须运算或隐式转换为 column_name 类型。 如果将 expression 设置为 NULL,则忽略 @Length,并将 column_name 中的值按指定的 @Offset 截断。

    @Offset 是 column_name 值中的起点,从该点开始编写 expression。@Offset 是基于零的序号位置,数据类型为 bigint,不能为负数。 如果 @Offset 为 NULL,则更新操作将在现有 column_name 值的结尾追加 expression,并忽略 @Length。 如果 @Offset 大于 column_name 值的长度,则数据库引擎将返回错误。 如果 @Offset 加上 @Length 超出了列中基础值的限度,则将删除到值的最后一个字符。 如果 @Offset 加上 LEN(expression) 大于声明的基础大小,则将出现错误。

    @Length 是指列中某个部分的长度,从 @Offset 开始,该长度由 expression 替换。@Length 的数据类型为 bigint,不能为负数。 如果 @Length 为 NULL,则更新操作将删除从 @Offset 到 column_name 值的结尾的所有数据。

    有关详细信息,请参阅“更新大值数据类型”。

  • @variable
    已声明的变量,该变量将设置为 expression 所返回的值。

    SET @variable = column = expression 将变量设置为与列相同的值。 这与 SET @variable = column, column = expression 不同,后者将变量设置为列更新前的值。

  • OUTPUT_clause
    在 UPDATE 操作中,返回更新后的数据或基于更新后的数据的表达式。 在针对本地分区视图、分布式分区视图、远程表或远程视图的任何 DML 语句中都不支持 OUTPUT 子句。 有关详细信息,请参阅 OUTPUT 子句 (Transact-SQL)

  • FROM table_source
    指定将表、视图或派生表源用于为更新操作提供条件。 有关详细信息,请参阅 FROM (Transact-SQL)

    如果所更新对象与 FROM 子句中的对象相同,并且在 FROM 子句中对该对象只有一个引用,则指定或不指定对象别名均可。 如果更新的对象在 FROM 子句中出现了不止一次,则对该对象的一个(并且只有一个)引用不能指定表别名。 FROM 子句中对该对象的所有其他引用都必须包含对象别名。

  • WHERE
    指定条件来限定所更新的行。 根据所使用的 WHERE 子句的形式,有两种更新形式:

    • 搜索更新指定搜索条件来限定要删除的行。

    • 定位更新使用 CURRENT OF 子句指定游标。 更新操作发生在游标的当前位置。 使用 WHERE CURRENT OF 子句的定位更新将在游标的当前位置更新单行。 这比使用 WHERE <search_condition> 子句限定所更新行的搜索更新更精确。 当搜索条件不唯一标识一行时,搜索更新将修改多行。

  • search_condition
    为要更新的行指定需满足的条件。 搜索条件也可以是联接所基于的条件。 对搜索条件中可以包含的谓词数量没有限制。 有关谓词和搜索条件的详细信息,请参阅搜索条件 (Transact-SQL)

  • CURRENT OF
    指定更新在指定游标的当前位置进行。

  • GLOBAL
    指定 cursor_name 涉及到全局游标。

  • cursor_name
    要从中进行提取的打开的游标的名称。 如果同时存在名为 cursor_name 的全局游标和局部游标,那么,在指定了 GLOBAL 时,该参数是指全局游标;否则是指局部游标。 游标必须允许更新。

  • cursor_variable_name
    游标变量的名称。cursor_variable_name 必须引用允许更新的游标。

  • OPTION (query_hint [ ,... n ] )
    指定优化器提示用于自定义数据库引擎处理语句的方式。 有关详细信息,请参阅查询提示 (Transact-SQL)

最佳实践

可以在 UPDATE 语句中使用变量名称来显示受影响的旧值和新值,但仅当 UPDATE 语句影响单个记录时才应使用变量名称。 如果 UPDATE 语句影响多个记录,则使用 OUTPUT 子句可以返回每个记录的旧值和新值。

指定 FROM 子句为更新操作提供条件时务须小心。 如果 UPDATE 语句包含的 FROM 子句在指定后并未生成对每个更新的列只有一个值可用的结果(换句话说,如果 UPDATE 语句是不确定性的),则其结果将不明确。 这可能导致意外结果。 例如,对于下面脚本中的 UPDATE 语句,Table1 中的全部两行都满足 UPDATE 语句中 FROM 子句的限定条件;但是,将使用 Table1 的哪一行来更新 Table2 中的行是不明确的。

USE AdventureWorks;
GO
IF OBJECT_ID ('dbo.Table1', 'U') IS NOT NULL
    DROP TABLE dbo.Table1;
GO
IF OBJECT_ID ('dbo.Table2', 'U') IS NOT NULL
    DROP TABLE dbo.Table2;
GO
CREATE TABLE dbo.Table1 
    (ColA int PRIMARY KEY NOT NULL, ColB decimal(10,3) NOT NULL);
GO
CREATE TABLE dbo.Table2 
    (ColA int PRIMARY KEY NOT NULL, ColB decimal(10,3) NOT NULL);
GO
INSERT INTO dbo.Table1 VALUES (1, 10.0), (1, 20.0);
INSERT INTO dbo.Table2 VALUES (1, 0.0);
;
GO

UPDATE dbo.Table2 
SET dbo.Table2.ColB = dbo.Table2.ColB + dbo.Table1.ColB
FROM dbo.Table2 
    INNER JOIN dbo.Table1 
    ON (dbo.Table2.ColA = dbo.Table1.ColA);
GO
SELECT ColA, ColB 
FROM dbo.Table2;

当结合使用 FROM 和 WHERE CURRENT OF 子句时,可能发生同样的问题。 在以下示例中,Table2 中的全部两行都满足 UPDATE 语句中 FROM 子句的限定条件。 将使用 Table2 的哪一行来更新 Table1 中的行是不明确的。

USE AdventureWorks;
GO
IF OBJECT_ID ('dbo.Table1', 'U') IS NOT NULL
    DROP TABLE dbo.Table1;
GO
IF OBJECT_ID ('dbo.Table2', 'U') IS NOT NULL
    DROP TABLE dbo.Table2;
GO
CREATE TABLE dbo.Table1
    (c1 int PRIMARY KEY NOT NULL, c2 int NOT NULL);
GO
CREATE TABLE dbo.Table2
    (d1 int PRIMARY KEY NOT NULL, d2 int NOT NULL);
GO
INSERT INTO dbo.Table1 VALUES (1, 10);
INSERT INTO dbo.Table2 VALUES (1, 20), (2, 30);
GO

DECLARE abc CURSOR LOCAL FOR
    SELECT c1, c2 
    FROM dbo.Table1;
OPEN abc;
FETCH abc;
UPDATE dbo.Table1 
SET c2 = c2 + d2 
FROM dbo.Table2 
WHERE CURRENT OF abc;
GO
SELECT c1, c2 FROM dbo.Table1;
GO

数据类型

所有的 char 和 nchar 列向右填充至定义长度。

如果 ANSI_PADDING 设置为 OFF,则会从插入 varchar 和 nvarchar 列的数据中删除所有尾随空格,但只包含空格的字符串除外。 这些字符串被截断为空字符串。 如果 ANSI_PADDING 设置为 ON,则插入尾随空格。 Microsoft SQL Server ODBC 驱动程序和用于 SQL Server 的 OLE DB 访问接口自动对每个连接设置 ANSI_PADDING ON。 这可在 ODBC 数据源中进行配置,也可通过设置连接特性或属性进行配置。 有关详细信息,请参阅 SET ANSI_PADDING (Transact-SQL)

更新大值数据类型

使用 .WRITE (expression, @Offset,@Length) 子句执行对 varchar(max)、nvarchar(max) 和 varbinary(max) 等数据类型的部分或完整更新。 例如,对 varchar(max) 列的部分更新可能只删除或修改该列的前 200 个字符,而完整更新则删除或修改该列中的所有数据。

为了获得最佳性能,建议按照块区大小为 8040 字节倍数的方式插入或更新数据。

当 UPDATE 语句导致下列任一操作时,数据库引擎便会将部分更新转换为完整更新:

  • 更改分区视图或表的键列。

  • 修改多行并且还将非唯一的聚集索引的键更新为非常量值。

不能使用 .WRITE 子句更新 NULL 列或将 column_name 的值设置为 NULL。

对于 varbinary 和 varchar 数据类型,以字节为单位指定 @Offset@Length;对于 nvarchar 数据类型,则以字符为单位进行指定。 已针对双字节字符集 (DBCS) 排序规则计算了适当的偏移量。

如果在 OUTPUT 子句中引用了由 .WRITE 子句修改的列,则该列的完整值(deleted.column_name 中的前像或 inserted.column_name 中的后像)都返回到表变量中的指定列。

若要针对其他字符或二进制数据类型获得相同的 .WRITE 功能,请使用 STUFF (Transact-SQL)

更新 FILESTREAM 数据

您可以使用 UPDATE 语句将 FILESTREAM 字段更新为 null 值、空值或相对较小的内联数据量。 但是,使用 Win32 接口可以更有效地将大量数据以流的方式导入到文件中。 更新 FILESTREAM 字段时,即会修改文件系统中的基础 BLOB 数据。 将 FILESTREAM 字段设置为 NULL 即会删除与该字段相关联的 BLOB 数据。 您不能使用 .WRITE() 执行对 FILESTREAM 数据的部分更新。 有关详细信息,请参阅 FILESTREAM 概述

更新 text、ntext 和 image 列

使用 UPDATE 修改 text、ntext 或 image 列时将对列进行初始化,向其列分配有效的文本指针,并且分配至少一个数据页(除非使用 NULL 更新该列)。 如果 UPDATE 语句在更新聚集键以及一个或者多个 text、 ntext 或 image 列时可以更改多个行,则对这些列的部分更新将作为替换所有值来执行。

若要替换或修改大型 text、ntext 或 image 数据块,请使用 WRITETEXTUPDATETEXT,而不使用 UPDATE 语句。

重要说明重要提示

在 MicrosoftSQL Server 以后的版本中,将删除 ntext、text 和 image 数据类型。 请避免在新开发工作中使用这些数据类型,并考虑修改当前使用这些数据类型的应用程序。 请改用 nvarchar(max)varchar(max)varbinary(max)。 有关详细信息,请参阅使用大值数据类型

错误处理

您可以通过在 TRY…CATCH 构造函数中指定 UPDATE 语句,实现对该语句的错误处理。 有关详细信息,请参阅在 Transact-SQL 中使用 TRY...CATCH

如果对行的更新违反了某个约束或规则,或违反了对列的 NULL 设置,或者新值是不兼容的数据类型,则取消该语句、返回错误并且不更新任何记录。

当 UPDATE 语句在表达式求值过程中遇到算术错误(溢出、被零除或域错误)时,则不进行更新。 批处理的剩余部分不再执行,并且返回错误消息。

如果对参与聚集索引的一列或多列的更新导致聚集索引和行的大小超过 8,060 字节,则更新失败并且返回错误消息。

互操作性

仅当所修改的表是表变量时,才允许在用户定义函数的主体中使用 UPDATE 语句。

当对表的 UPDATE 操作定义 INSTEAD OF 触发器时,将运行触发器而不运行 UPDATE 语句。 早期版本的 SQL Server 只支持对 UPDATE 和其他数据修改语句定义 AFTER 触发器。

限制和局限

不能在直接或间接引用定义有 INSTEAD OF 触发器的视图的 UPDATE 语句中指定 FROM 子句。 有关 INSTEAD OF 触发器的详细信息,请参阅 CREATE TRIGGER (Transact-SQL)

对于用于远程表以及本地和远程分区视图的 UPDATE 语句,忽略 SET ROWCOUNT 选项的设置。 在 SQL Server 的下一个版本中,使用 SET ROWCOUNT 将不会影响 UPDATE 语句。 请不要在新的开发工作中将 SET ROWCOUNT 与 UPDATE 语句一起使用,并应修改当前使用它的应用程序以使用 TOP 语法。

在公用表表达式 (CTE) 是 UPDATE 语句的目标时,在该语句中对 CTE 的所有引用都必须匹配。 例如,如果在 FROM 子句中向 CTE 分配了一个别名,则该别名必须用于对 CTE 的所有其他引用。 需要明确的 CTE 引用,因为 CTE 没有对象 ID,而 SQL Server 使用对象 ID 来识别对象与其别名之间的隐式关系。 如果没有这一关系,查询计划可能会产生意外的联接行为和意外的查询结果。 以下示例演示在 CTE 是更新操作的目标对象时指定 CTE 的正确和不正确的方法。

USE tempdb;
GO
-- UPDATE statement with CTE references that are correctly matched.
DECLARE @x TABLE (ID int, Value int);
DECLARE @y TABLE (ID int, Value int);
INSERT @x VALUES (1, 10), (2, 20);
INSERT @y VALUES (1, 100),(2, 200);

WITH cte AS (SELECT * FROM @x)
UPDATE x -- cte is referenced by the alias.
SET Value = y.Value
FROM cte AS x  -- cte is assigned an alias.
INNER JOIN @y AS y ON y.ID = x.ID;
SELECT * FROM @x;
GO

下面是结果集。

ID     Value

------ -----

1      100

2      200

(2 row(s) affected)

-- UPDATE statement with CTE references that are incorrectly matched.
USE tempdb;
GO
DECLARE @x TABLE (ID int, Value int);
DECLARE @y TABLE (ID int, Value int);
INSERT @x VALUES (1, 10), (2, 20);
INSERT @y VALUES (1, 100),(2, 200);

WITH cte AS (SELECT * FROM @x)
UPDATE cte   -- cte is not referenced by the alias.
SET Value = y.Value
FROM cte AS x  -- cte is assigned an alias.
INNER JOIN @y AS y ON y.ID = x.ID; 
SELECT * FROM @x; 
GO

下面是结果集。

ID     Value

------ -----

1      100

2      100

(2 row(s) affected)

日志记录行为

UPDATE 语句将完全记入日志;但是,如果将数据库恢复模式设置为大容量日志恢复模式或简单模式,则对插入或追加新数据的 .WRITE 更新进行最小日志记录。 在使用 .WRITE 更新现有值时,不使用最小日志记录。 有关详细信息,请参阅可以尽量减少日志量的操作

安全性

权限

要求对目标表具有 UPDATE 权限。 如果 UPDATE 语句包含 WHERE 子句,或 SET 子句中的 expression 使用了表中的某个列,则还要求要更新的表具有 SELECT 权限。

UPDATE 权限默认授予 sysadmin 固定服务器角色、db_owner 和 db_datawriter 固定数据库角色以及表所有者的成员。 sysadmin、db_owner 和 db_securityadmin 角色成员以及表所有者可以将权限转让给其他用户。

示例

类别

作为特征的语法元素

基本语法

UPDATE

限制更新的行

WHERE • TOP • WITH 公用表表达式 • WHERE CURRENT OF

设置列值

计算值 • 复合运算符 • 默认值 • 子查询

指定目标表,而非标准表

视图 • 表变量 • 表别名

基于来自其他表的数据更新数据

FROM

更新远程表中的行

链接服务器 • OPENQUERY • OPENDATASOURCE

更新大型对象数据类型

.WRITE • OPENROWSET

更新用户定义类型

用户定义类型

通过使用提示覆盖查询优化器的默认行为

表提示 • 查询提示

捕获 UPDATE 语句的结果

OUTPUT 子句

在其他语句中使用 UPDATE

存储过程 • TRY…CATCH

基本语法

本节中的示例说明了使用最低要求的语法的 UPDATE 语句的基本功能。

A. 使用简单 UPDATE 语句

以下示例将为 Person.Address 表中的所有行更新单个列。

USE AdventureWorks;
GO
UPDATE Person.Address
SET ModifiedDate = GETDATE();

B. 更新多列

以下示例针对 SalesPerson 表中的所有行更新 Bonus、 CommissionPct 和 SalesQuota 列中的值。

USE AdventureWorks;
GO
UPDATE Sales.SalesPerson
SET Bonus = 6000, CommissionPct = .10, SalesQuota = NULL;
GO

限制更新的行

本节中的示例说明您可以用于限制 UPDATE 语句影响的行数的方法。

A. 使用 WHERE 子句

以下示例使用 WHERE 子句指定要更新的行。 该语句为在 Color 列中具有现有值“Red”并在 Name 列中具有以“Road-250”开头的值的所有行更新 Production.Product 表中 Color 列的值。

USE AdventureWorks;
GO
UPDATE Production.Product
SET Color = N'Metallic Red'
WHERE Name LIKE N'Road-250%' AND Color = N'Red';
GO

B. 使用 TOP 子句

下面的示例使用 TOP 子句来限制 UPDATE 语句中修改的行数。 当 TOP (n) 子句与 UPDATE 一起使用时,将针对随机选择的 n 行执行更新操作。 以下示例按照 Employee 表中 10 个随机行的 25% 更新 VacationHours 列。

USE AdventureWorks;
GO
UPDATE TOP (10) HumanResources.Employee
SET VacationHours = VacationHours * 1.25 ;
GO

如果必须使用 TOP 来应用按有意义的时间顺序排列的更新,您必须同时使用 TOP 和 ORDER BY 子句。 下列示例更新了雇佣最早的 10 名雇员的假期小时数。

UPDATE HumanResources.Employee
SET VacationHours = VacationHours + 8
FROM (SELECT TOP 10 EmployeeID FROM HumanResources.Employee
     ORDER BY HireDate ASC) AS th
WHERE HumanResources.Employee.EmployeeID = th.EmployeeID;
GO

C. 使用 WITH common_table_expression 子句

以下示例按照直接或间接向 ManagerID12 报告的所有雇员的 25% 更新 VacationHours 值。 公用表表达式将返回直接向 ManagerID12 报告的雇员以及直接向这些雇员报告的雇员等的层次列表。 只修改公用表表达式所返回的行。 有关递归公用表表达式的详细信息,请参阅使用公用表表达式的递归查询

USE AdventureWorks;
GO
WITH DirectReports(EmployeeID, NewVacationHours, EmployeeLevel)
AS
(SELECT e.EmployeeID, e.VacationHours, 1
  FROM HumanResources.Employee AS e
  WHERE e.ManagerID = 12
  UNION ALL
  SELECT e.EmployeeID, e.VacationHours, EmployeeLevel + 1
  FROM HumanResources.Employee as e
  JOIN DirectReports AS d ON e.ManagerID = d.EmployeeID
)
UPDATE HumanResources.Employee
SET VacationHours = VacationHours * 1.25
FROM HumanResources.Employee AS e
JOIN DirectReports AS d ON e.EmployeeID = d.EmployeeID;
GO

D. 使用 WHERE CURRENT OF 子句

下面的示例使用 WHERE CURRENT OF 子句只更新游标所在的行。 如果游标基于某个联接,则只修改 UPDATE 语句中指定的 table_name。 而不影响其他参与该游标的表。

USE AdventureWorks;
GO
DECLARE complex_cursor CURSOR FOR
    SELECT a.EmployeeID
    FROM HumanResources.EmployeePayHistory AS a
    WHERE RateChangeDate <> 
         (SELECT MAX(RateChangeDate)
          FROM HumanResources.EmployeePayHistory AS b
          WHERE a.EmployeeID = b.EmployeeID) ;
OPEN complex_cursor;
FETCH FROM complex_cursor;
UPDATE HumanResources.EmployeePayHistory
SET PayFrequency = 2 
WHERE CURRENT OF complex_cursor;
CLOSE complex_cursor;
DEALLOCATE complex_cursor;
GO

设置列值

本节中的示例说明如何通过使用计算值、子查询和 DEFAULT 值更新列。

A. 指定计算值

下面的示例在 UPDATE 语句中使用计算值。 该示例将 Product 表中所有行的 ListPrice 列的值加倍。

USE AdventureWorks ;
GO
UPDATE Production.Product
SET ListPrice = ListPrice * 2;
GO

B. 指定复合运算符

下面的示例使用变量 @NewPrice 通过在当前价格基础上加 10 来提高所有红色自行车的价格。

USE AdventureWorks;
GO
DECLARE @NewPrice int = 10;
UPDATE Production.Product
SET ListPrice += @NewPrice
WHERE Color = N'Red';
GO

下面的示例使用复合运算符 += 将数据 ' - tool malfunction' 追加到其 ScrapReasonID 介于 10 和 12 之间的行的 Name 列中的现有值。

USE AdventureWorks;
GO
UPDATE Production.ScrapReason 
SET Name += ' - tool malfunction'
WHERE ScrapReasonID BETWEEN 10 and 12;

C. 在 SET 子句中指定子查询

下面的示例在 SET 子句中使用子查询来确定用于更新列的值。 该子查询必须仅返回标量值(也就是说,每行单个值)。 该示例修改 SalesPerson 表中的 SalesYTD 列,以反映 SalesOrderHeader 表中记录的最近销售情况。 该子查询在 UPDATE 语句中聚合了每个销售人员的销售信息。

USE AdventureWorks;
GO
UPDATE Sales.SalesPerson
SET SalesYTD = SalesYTD + 
    (SELECT SUM(so.SubTotal) 
     FROM Sales.SalesOrderHeader AS so
     WHERE so.OrderDate = (SELECT MAX(OrderDate)
                           FROM Sales.SalesOrderHeader AS so2
                           WHERE so2.SalesPersonID = 
                                 so.SalesPersonID)
     AND Sales.SalesPerson.SalesPersonID = so.SalesPersonID
     GROUP BY so.SalesPersonID);
GO

D. 使用 DEFAULT 值更新行

下面的示例为其 CostRate 值大于 20.00 的所有行将 CostRate 列设置为其默认值 (0.00)。

USE AdventureWorks;
GO
UPDATE Production.Location
SET CostRate = DEFAULT
WHERE CostRate > 20.00;

指定目标表,而非标准表

本节中的示例说明如何通过指定视图、表别名或表变量来更新行。

A. 将视图指定为目标对象

下面的示例通过将视图指定为目标对象更新表中的行。 视图定义引用多个表,但是,UPDATE 语句会成功,因为它引用仅来自基础表之一的列。 如果指定来自两个表的列,UPDATE 语句将失败。 有关详细信息,请参阅通过视图修改数据

USE AdventureWorks;
GO
UPDATE Person.vStateProvinceCountryRegion
SET CountryRegionName = 'United States of America'
WHERE CountryRegionName = 'United States';

B. 将表别名指定为目标对象

下面的示例更新表 Production.ScrapReason 中的行。 在 FROM 子句中分配给 ScrapReason 的表别名指定为 UPDATE 子句中的目标对象。

USE AdventureWorks;
GO
UPDATE sr
SET sr.Name += ' - tool malfunction'
FROM Production.ScrapReason AS sr
JOIN Production.WorkOrder AS wo 
     ON sr.ScrapReasonID = wo.ScrapReasonID
     AND wo.ScrappedQty > 300;

C. 将表变量指定为目标对象

下面的示例更新表变量中的行。

USE AdventureWorks;
GO
-- Create the table variable.
DECLARE @MyTableVar table(
    EmpID int NOT NULL,
    NewVacationHours int,
    ModifiedDate datetime);

-- Populate the table variable with employee ID values from HumanResources.Employee.
INSERT INTO @MyTableVar (EmpID)
    SELECT EmployeeID FROM HumanResources.Employee;

-- Update columns in the table variable.
UPDATE @MyTableVar
SET NewVacationHours = e.VacationHours + 20,
    ModifiedDate = GETDATE()
FROM HumanResources.Employee AS e 
WHERE e.EmployeeID = EmpID;

-- Display the results of the UPDATE statement.
SELECT EmpID, NewVacationHours, ModifiedDate FROM @MyTableVar
ORDER BY EmpID;
GO

基于来自其他表的数据更新数据

本节中的示例说明基于其他表中的信息更新某一表中的行的方法。

A. 将 UPDATE 语句用于来自其他表的信息

下面的示例修改 SalesPerson 表中的 SalesYTD 列,以反映 SalesOrderHeader 表中记录的最近销售情况。

USE AdventureWorks;
GO
UPDATE Sales.SalesPerson
SET SalesYTD = SalesYTD + SubTotal
FROM Sales.SalesPerson AS sp
JOIN Sales.SalesOrderHeader AS so
    ON sp.SalesPersonID = so.SalesPersonID
    AND so.OrderDate = (SELECT MAX(OrderDate)
                        FROM Sales.SalesOrderHeader 
                        WHERE SalesPersonID = 
                              sp.SalesPersonID);
GO

上一个示例假定在特定日期只记录指定销售人员的一笔销售业务,并假定更新信息是最新的。 如果在同一天中可以记录指定销售人员的多笔销售业务,所示的示例将不能正常运行。 该示例运行时没有错误,但是每个 SalesYTD 值只能用一笔销售数据更新,而不管那一天实际发生了多少笔销售业务。 这是因为一条 UPDATE 语句永远不会对同一行更新两次。

对于特定销售人员在同一天可销售不止一批的情况,每个销售人员的所有销售量必须在 UPDATE 语句中合计在一起,如下例所示:

USE AdventureWorks;
GO
UPDATE Sales.SalesPerson
SET SalesYTD = SalesYTD + 
    (SELECT SUM(so.SubTotal) 
     FROM Sales.SalesOrderHeader AS so
     WHERE so.OrderDate = (SELECT MAX(OrderDate)
                           FROM Sales.SalesOrderHeader AS so2
                           WHERE so2.SalesPersonID = 
                                 so.SalesPersonID)
     AND Sales.SalesPerson.SalesPersonID = so.SalesPersonID
     GROUP BY so.SalesPersonID);
GO

更新远程表中的行

本节中的示例说明如何通过使用链接服务器行集函数引用一个远程目标表,更新该表中的行。

A. 通过使用链接服务器更新远程表中的数据

下面的示例更新远程服务器上的表。 该示例从使用 sp_addlinkedserver 创建指向远程数据源的链接开始。 然后,将链接服务器名称 MyLinkServer 指定为 server.catalog.schema.object 形式的由四个部分组成的对象名称的一部分。 请注意,您必须为 @datasrc 指定一个有效的服务器名称。

USE master;
GO
-- Create a link to the remote data source. 
-- Specify a valid server name for @datasrc as 'server_name' or 'server_name\instance_name'.

EXEC sp_addlinkedserver @server = N'MyLinkServer',
    @srvproduct = N' ',
    @provider = N'SQLNCLI10', 
    @datasrc = N'<server name>',
    @catalog = N'AdventureWorks';
GO
USE AdventureWorks;
GO
-- Specify the remote data source using a four-part name 
-- in the form linked_server.catalog.schema.object.

UPDATE MyLinkServer.AdventureWorks.HumanResources.Department
SET GroupName = N'Public Relations'
WHERE DepartmentID = 4;

B. 通过使用 OPENQUERY 函数更新远程表中的数据

下面的示例通过指定 OPENQUERY 行集函数更新远程表中的行。 在之前例子中创建的链接服务器名称用于此示例。

UPDATE OPENQUERY (MyLinkServer, 'SELECT GroupName FROM HumanResources.Department WHERE DepartmentID = 4') 
SET GroupName = 'Sales and Marketing';

C. 通过使用 OPENDATASOURCE 函数更新远程表中的数据

下面的示例通过指定 OPENDATASOURCE 行集函数向远程表插入一行。 通过使用 server_name 或 server_name\instance_name 格式,为该数据源指定一个有效的服务器名称。 您可能需要为即席分布式查询配置 SQL Server 的实例。 有关详细信息,请参阅即席分布式查询选项

UPDATE OPENQUERY (MyLinkServer, 'SELECT GroupName FROM HumanResources.Department WHERE DepartmentID = 4') 
SET GroupName = 'Sales and Marketing';

更新大型对象数据类型

本节中的示例说明更新使用大型对象 (LOB) 数据类型定义的列中的值的方法。

A. 使用包含 .WRITE 的 UPDATE 来修改 nvarchar(max) 列中的数据

以下示例使用 .WRITE 子句更新 DocumentSummary(Production.Document 表内的 nvarchar(max) 列)中的部分值。 通过指定替换单词、现有数据中要替换的单词的开始位置(偏移量)以及要替换的字符数(长度),将单词 components 替换为单词 features。 此示例还使用 OUTPUT 子句将 DocumentSummary 列的前像和后像返回到 @MyTableVar 表变量。

USE AdventureWorks;
GO
DECLARE @MyTableVar table (
    DocumentID int NOT NULL,
    SummaryBefore nvarchar(max),
    SummaryAfter nvarchar(max));
UPDATE Production.Document
SET DocumentSummary .WRITE (N'features',28,10)
OUTPUT inserted.DocumentID,
       deleted.DocumentSummary, 
       inserted.DocumentSummary 
    INTO @MyTableVar
WHERE DocumentID = 3 ;
SELECT DocumentID, SummaryBefore, SummaryAfter 
FROM @MyTableVar;
GO

B. 使用包含 .WRITE 的 UPDATE 在 nvarchar(max) 列中添加和删除数据

以下示例从当前值设置为 NULL 的 nvarchar(max) 列中添加和删除列。 由于不能使用 .WRITE 子句修改 NULL 列,因此先使用临时数据填充该列。 然后,使用 .WRITE 子句将该数据替换为正确的数据。 其他示例将数据追加到列值的结尾,从列中删除(截断)数据,最后从列中删除部分数据。 SELECT 语句显示由每个 UPDATE 语句生成的数据修改。

USE AdventureWorks;
GO
-- Replacing NULL value with temporary data.
UPDATE Production.Document
SET DocumentSummary = N'Replacing NULL value'
WHERE DocumentID = 1;
GO

SELECT DocumentSummary 
FROM Production.Document
WHERE DocumentID = 1;
GO
-- Replacing temporary data with the correct data. Setting @Length to NULL 
-- truncates all existing data from the @Offset position.
UPDATE Production.Document
SET DocumentSummary .WRITE(N'Carefully inspect and maintain the tires and crank arms.',0,NULL)
WHERE DocumentID = 1;
GO

SELECT DocumentSummary 
FROM Production.Document
WHERE DocumentID = 1;
GO

-- Appending additional data to the end of the column by setting 
-- @Offset to NULL.
UPDATE Production.Document
SET DocumentSummary .WRITE (N' Appending data to the end of the column.', NULL, 0)
WHERE DocumentID = 1;
GO

SELECT DocumentSummary 
FROM Production.Document
WHERE DocumentID = 1;
GO

-- Removing all data from @Offset to the end of the existing value by 
-- setting expression to NULL. 
UPDATE Production.Document
SET DocumentSummary .WRITE (NULL, 56, 0)
WHERE DocumentID = 1;
GO

SELECT DocumentSummary 
FROM Production.Document
WHERE DocumentID = 1;
GO

-- Removing partial data beginning at position 9 and ending at 
-- position 21.
UPDATE Production.Document
SET DocumentSummary .WRITE ('',9, 12)
WHERE DocumentID = 1;
GO

SELECT DocumentSummary 
FROM Production.Document
WHERE DocumentID = 1;
GO

C. 使用包含 OPENROWSET 的 UPDATE 以修改 varbinary(max) 列

以下示例将 varbinary(max) 列中存储的现有图像替换为新图像。 同时使用 OPENROWSET 函数和 BULK 选项将图像加载到列中。 此示例假定指定的文件路径中存在名为 Tires.jpg 的文件。

USE AdventureWorks;
GO
UPDATE Production.ProductPhoto
SET ThumbNailPhoto = (
    SELECT *
    FROM OPENROWSET(BULK 'c:\Tires.jpg', SINGLE_BLOB) AS x )
WHERE ProductPhotoID = 1;
GO

D. 使用 UPDATE 修改 FILESTREAM 数据

下面的示例使用 UPDATE 语句修改文件系统文件中的数据。 建议您不要使用此方法将大量数据传送到文件。 使用适当的 Win32 接口。 下面的示例将文件记录中的所有文本替换为文本 Xray 1。 有关详细信息,请参阅FILESTREAM 概述

UPDATE Archive.dbo.Records
SET [Chart] = CAST('Xray 1' as varbinary(max))
WHERE [SerialNumber] = 2;

更新用户定义类型

下面的示例修改 CLR 用户定义类型 (UDT) 列中的值。 说明了三种方法。 有关用户定义列的详细信息,请参阅 CLR 用户定义类型

A. 使用系统数据类型

您可以通过提供 SQL Server 系统数据类型的值更新 UDT,条件是该用户定义类型支持该类型的隐式转换或显式转换。 以下示例显示如何通过从字符串显式转换来更新用户定义类型 Point 的列中的值。

UPDATE dbo.Cities
SET Location = CONVERT(Point, '12.3:46.2')
WHERE Name = 'Anchorage';

B. 调用某一方法

您可以通过调用标记为“赋值函数”的用户定义类型的方法执行更新,对 UDT 进行更新。 以下示例调用类型 Point 的名为 SetXY 的赋值函数方法。 这将更新该类型的实例状态。

UPDATE dbo.Cities
SET Location.SetXY(23.5, 23.5)
WHERE Name = 'Anchorage';

C. 修改属性或数据成员的值

您可以通过修改用户定义类型的已注册属性或公共数据成员的值,对 UDT 进行更新。 提供值的表达式必须可隐式转换为属性的类型。 以下示例修改用户定义类型 Point 的属性 X 的值。

UPDATE dbo.Cities
SET Location.X = 23.5
WHERE Name = 'Anchorage';

通过使用提示覆盖查询优化器的默认行为

本节中的示例说明如何使用表提示和查询提示在处理 UPDATE 语句时暂时覆盖查询优化器的默认行为。

注意事项注意

由于 SQL Server 查询优化器通常会为查询选择最佳执行计划,因此我们建议仅在最后迫不得已的情况下才可由资深的开发人员和数据库管理员使用提示。

A. 指定表提示

下面的示例指定表提示 TABLOCK。 该表提示指定对 Production.Product 表采用共享锁,并保持到 UPDATE 语句结束。

USE AdventureWorks;
GO
UPDATE Production.Product
WITH (TABLOCK)
SET ListPrice = ListPrice * 1.10
WHERE ProductNumber LIKE 'BK-%';
GO

B. 指定查询提示

下面的示例在 UPDATE 语句中指定查询提示OPTIMIZE FOR (@variable)。 此提示在编译和优化查询时提示查询优化器对局部变量使用特定值。 仅在查询优化期间使用该值,在查询执行期间不使用该值。

USE AdventureWorks;
GO
CREATE PROCEDURE Production.uspProductUpdate
@Product nvarchar(25)
AS
SET NOCOUNT ON;
UPDATE Production.Product
SET ListPrice = ListPrice * 1.10
WHERE ProductNumber LIKE @Product
OPTION (OPTIMIZE FOR (@Product = 'BK-%') );
GO
-- Execute the stored procedure 
EXEC Production.uspProductUpdate 'BK-%';

捕获 UPDATE 语句的结果

本节中的示例说明如何使用 OUTPUT 子句从 UPDATE 语句影响的每一行返回信息(或基于的表达式)。 这些结果可以返回到处理应用程序,以供在确认消息、存档以及其他此类应用程序要求中使用。

A. 使用包含 OUTPUT 子句的 UPDATE

下面的示例将 Employee 表中 VacationHours 列的前 10 行更新 25%,并且还将 ModifiedDate 列中的值设置为当前日期。 OUTPUT 子句将返回 VacationHours 的值,该值在将 deleted.VacationHours 列中的 UPDATE 语句和 inserted.VacationHours 列中的已更新值应用于 @MyTableVar 表变量之前存在。

在它后面的两个 SELECT 语句返回 @MyTableVar 中的值以及 Employee 表中更新操作的结果。 有关使用 OUTPUT 子句的更多示例,请参阅 OUTPUT 子句 (Transact-SQL)

USE AdventureWorks;
GO
DECLARE @MyTableVar table(
    EmpID int NOT NULL,
    OldVacationHours int,
    NewVacationHours int,
    ModifiedDate datetime);
UPDATE TOP (10) HumanResources.Employee
SET VacationHours = VacationHours * 1.25,
    ModifiedDate = GETDATE() 
OUTPUT inserted.EmployeeID,
       deleted.VacationHours,
       inserted.VacationHours,
       inserted.ModifiedDate
INTO @MyTableVar;

--Display the result set of the table variable.
SELECT EmpID, OldVacationHours, NewVacationHours, ModifiedDate
FROM @MyTableVar;
GO

--Display the result set of the table.
SELECT TOP (10) EmployeeID, VacationHours, ModifiedDate
FROM HumanResources.Employee;
GO

在其他语句中使用 UPDATE

本节的示例说明如何在其他语句中使用 UPDATE。

A. 在一个存储过程中使用 UPDATE

下面的示例在一个存储过程中使用了 UPDATE 语句。 该存储过程采用一个输入参数 @NewHours 和一个输出参数 @RowCount。 @NewHours 参数值用于 UPDATE 语句中,以便更新 HumanResources.Employee 表中的 VacationHours 列。 @RowCount 输出参数用于返回对局部变量有影响的行数。 在 SET 子句中使用 CASE 表达式,以便按条件确定为 VacationHours 设置的值。 在按每小时向员工付薪时 (SalariedFlag = 0),VacationHours 设置为当前小时数加上 @NewHours 中指定的值;否则,VacationHours 设置为在 @NewHours 中指定的值。

USE AdventureWorks;
GO
CREATE PROCEDURE HumanResources.Update_VacationHours
@NewHours smallint,
@RowCount int OUTPUT
AS 
SET NOCOUNT ON;
UPDATE HumanResources.Employee
SET VacationHours = 
    ( CASE
         WHEN SalariedFlag = 0 THEN VacationHours + @NewHours
         ELSE @NewHours
       END
    )
WHERE CurrentFlag = 1;
SET @RowCount = @@ROWCOUNT;
GO

-- Execute the stored procedure and return the number of rows updated to the variable @RowCount
DECLARE @RowCount int;
EXECUTE HumanResources.Update_VacationHours 40, @RowCount OUTPUT;
SELECT @RowCount AS RowCount;

B. 在 TRY…CATCH 块中使用 UPDATE

下面的示例在 TRY…CATCH 块中使用 UPDATE 语句处理在更新操作期间可能发生的执行错误。 有关详细信息,请参阅在 Transact-SQL 中使用 TRY...CATCH

USE AdventureWorks;
GO
BEGIN TRANSACTION;

BEGIN TRY
    -- Intentionally generate a constraint violation error.
    UPDATE HumanResources.Department
    SET Name = N'MyNewName'
    WHERE DepartmentID BETWEEN 1 AND 2;
END TRY
BEGIN CATCH
    SELECT 
        ERROR_NUMBER() AS ErrorNumber
        ,ERROR_SEVERITY() AS ErrorSeverity
        ,ERROR_STATE() AS ErrorState
        ,ERROR_PROCEDURE() AS ErrorProcedure
        ,ERROR_LINE() AS ErrorLine
        ,ERROR_MESSAGE() AS ErrorMessage;

    IF @@TRANCOUNT > 0
        ROLLBACK TRANSACTION;
END CATCH;

IF @@TRANCOUNT > 0
    COMMIT TRANSACTION;
GO

更改历史记录

更新的内容

向语法中添加了 table_alias,作为指定要更新的目标表的方法。

在“限制和局限”一节添加了与使用公用表表达式作为 UPDATE 语句的目标有关的信息。