INSTEAD OF 触发器中的表达式和计算列

视图的选择列表可以包含除简单表达式(仅由一个列名构成)以外的其他表达式。这些视图上的 INSTEAD OF 触发器必须具有相应的逻辑,能够正确地确定 INSERT 和 UPDATE 中指定的值中哪些值必须设置到基表中的列。此类表达式的示例包括以下几种:

  • 不映射到任何表中任何列的视图表达式,例如常量或某些类型的函数。

  • 映射到多列的视图表达式,例如串联两列或更多列的字符串组成的复杂表达式。

  • 转换单个基表列的值的视图表达式,例如在函数中引用列。

这些情况也适用于作为简单表达式的视图列,这些视图列引用基表中的计算列。定义计算列的表达式可与视图选择列表中的较复杂表达式具有相同的形式。

视图可在其不映射到任何基表列的选择列表中包含表达式,例如:

CREATE VIEW dbo.ExpressionView
AS
SELECT BusinessEntityID, JobTitle, GETDATE() AS TodaysDate
FROM AdventureWorks2008R2.HumanResources.Employee;

虽然 TodaysDate 列并不映射到任何表列,但是 SQL Server 必须在插入的表中创建 TodaysDate 列,该列将传递到为 ExpressionView 定义的 INSTEAD OF 触发器。但 inserted.TodaysDate 列可为空,因此,引用 ExpressionView 的 INSERT 语句不必为该列提供值。因为表达式不映射到表中的列,所以触发器会忽略 INSERT 在该列中提供的任何值。

应该对简单视图表达式(这些表达式引用基表中的计算列,而基表也生成与其他列无关的结果)应用这一相同的方法,例如:

CREATE TABLE dbo.ComputedExample
   (
    PrimaryKey    int PRIMARY KEY,
    ComputedCol   AS SUSER_NAME()
   );

一些复杂表达式映射到多列,例如:

CREATE TABLE dbo.SampleTable
     (
      PriKey    int,
      FirstName nvarchar(20),
      LastName  nvarchar(30)
     );
GO
CREATE VIEW dbo.ConcatView
AS
SELECT PriKey, FirstName + ' ' + LastName AS CombinedName
FROM SampleTable;

ConcatView 中的表达式 CombinedName 的值为 FirstName 值和 LastName 值的串联值。如果对 ConcatView 定义 INSTEAD OF INSERT 触发器,则必须对 INSERT 语句如何为 CombinedName 列提供值有约定,让触发器确定字符串的哪部分应放在 FirstName 列中,哪部分应放在 LastName 列中。如果选择的约定是让 INSERT 语句通过使用 'first_name;last_name' 约定指定 CombinedName 的值,则以下触发器可成功地处理 INSERT:

CREATE TRIGGER InsteadSample on dbo.ConcatView
INSTEAD OF INSERT
AS
BEGIN

   INSERT INTO dbo.SampleTable
      SELECT PriKey,
         -- Pull out the first name string.
         SUBSTRING(
            CombinedName,
            1,
            (CHARINDEX(';', CombinedName) - 1)
            ),
         -- Pull out the last name string.
         SUBSTRING(
            CombinedName,
            (CHARINDEX(';', CombinedName) + 1),
            DATALENGTH(CombinedName) - (CHARINDEX(';', CombinedName) + 1)
            )
      FROM inserted
END;

需要类似的逻辑来处理本身是简单表达式但引用包含复杂表达式的计算列的视图列。

一些视图表达式可以转换基表列的值,例如通过执行数学运算或使用列作为函数的参数。在这种情况下,INSTEAD OF INSERT 触发器中的逻辑可采用两种方法:

  • 约定可以是:所有 INSERT 语句都提供要放在基表中的原始值,触发器逻辑将插入的表中的值移至基表。

  • 约定可以是:所有 INSERT 语句提供预期得到的由视图上的 SELECT 返回的值,在这种情况下触发器中的逻辑必须反转此操作。例如:

    CREATE TABLE dbo.BaseTable
      (
       PrimaryKey   int PRIMARY KEY,
       ColumnB      int,
       ColumnC      decimal(19,3)
      );
    
    CREATE VIEW dbo.SquareView AS
    SELECT PrimaryKey, ColumnB,
           -- Square the value of ColumnC
           SQUARE(ColumnC) AS SquareC
    FROM BaseTable;
    
    CREATE TRIGGER SquareTrigger ON dbo.SquareView
    INSTEAD OF INSERT
    AS
    BEGIN
      INSERT INTO dbo.BaseTable
         SELECT PrimaryKey, ColumnB,
                 -- Perform logical inverse of function in view.
                 SQRT(SquareC)
         FROM inserted
    END;
    

对于某些表达式(例如使用加法和减法数学运算的复杂表达式),用户也许不能提供触发器用来明确为目标基表列生成值的值。例如,如果视图选择列表包含表达式 IntColA + IntColB AS AddedColumns,那么 inserted.AddedColumns 中的值 10 表示什么?10 是 3 + 7、2 + 8 还是 5 + 5 的结果?仅从 inserted.AddedColumns 值无法得知应将什么值放在 IntColA 和 IntColB 中。

在这些情况下,可对触发器进行编码以使用备用信息源确定基表列中要设置的值。对于具有 INSTEAD OF 触发器的视图,视图选择列表必须包含足够的信息,以为由触发器修改的基表中所有非空列生成值。并不是所有的数据都必须直接来自 inserted 表。在有些情况下,插入的表中的值可以是触发器用来检索其他基表中相关数据的键值。

请参阅

概念