天天看點

第十七章 使用觸發器

文章目錄

  • 第十七章 使用觸發器
  • 定義觸發器
  • 觸發器的類型
    • AFTER Triggers
    • 遞歸觸發器
  • Trigger Code
    • %ok, %msg, and %oper 系統變量
    • {fieldname}文法
    • 觸發器代碼中的宏
    • `{name*O}`, `{name*N}`和`{name*C}`觸發代碼文法
    • 附加觸發器代碼文法
  • Pulling Triggers
  • 觸發器和對象通路
    • 在對象通路期間沒有拔出觸發器
  • 觸發器與事務
  • 列出觸發器

本章介紹如何在Intersystems SQL中定義觸發器。觸發器是響應某些SQL事件執行的代碼行。本章包括以下主題:

有幾種方法可以為特定表定義觸發器:

  • 在将投影到SQL表的持久性類定義中包含觸發定義。例如,MyApp.person類的此定義包括Loggevent觸發器的定義,在每個成功的資料插入到MyApp.person表之後,将在每個成功的資料插入後調用:
Class MyApp.Person Extends %Persistent [DdlAllowed]
{
    // ... Class Property Definitions

    Trigger LogEvent [ Event = INSERT, Time = AFTER ]
    {
        // Trigger code to log an event 
    }
 }
      
  • 使用SQL建立觸發指令建立觸發器。這在相應的持久性類中生成觸發對象定義。 SQL觸發器名稱按照辨別符命名約定進行操作。 IntersystemsIris®資料平台使用SQL觸發名稱生成相應的觸發類實體名稱。

必須擁有%create_trigger管理級别權限來建立觸發器。必須具有删除觸發器的%drop_trigger管理級别權限。

類的最大使用者定義觸發器數為200。

注意:Intersystems Iris不支援收集投影的表上的觸發。使用者無法定義這樣的觸發器,并且作為子表的集合的投影不認為涉及該基本集合的觸發。

Intersystems Iris不支援修改Security.Roles和Security.Users表的觸發器。

觸發器由以下内容定義:

  • 導緻它執行的事件類型。觸發器可以是單個事件觸發器或多事件觸發。定義單個事件觸發器以在指定表上發生插入,更新或删除事件時執行。定義多事件觸發器以執行當在指定的表中發生多個指定的事件中的任何一個時執行。可以使用類定義或建立觸發指令定義插入/更新,更新/删除或插入/更新/删除多事件觸發器。事件類型在Class定義中指定了所需的事件觸發器關鍵字。
  • 觸發器執行的時間:在事件發生之前或之後。

    這是由可選的Time trigger關鍵字在類定義中指定的。

    預設為Before。

  • 可以将多個觸發器與同一事件和時間相關聯;在這種情況下,可以使用order trigger關鍵字來控制觸發多個觸發器的順序。先觸發順序較低的觸發器。

    如果多個觸發器具有相同的Order值,則不指定它們的觸發順序。

  • 可選的Foreach trigger關鍵字提供了額外的粒度。

    該關鍵字控制觸發器是每一行觸發一次(Foreach = row),還是每一行或對象通路觸發一次(Foreach = row/object),還是每語句觸發一次(Foreach = statement)。

    沒有Foreach trigger關鍵字定義的觸發器每一行觸發一次。

    如果觸發器是用Foreach = row/object定義的,那麼觸發器也會在對象通路期間的特定點被調用,如本章後面所述。

    可以使用INFORMATION.SCHEMA.TRIGGERS的ACTIONORIENTATION屬性列出每個觸發器的Foreach值

下面是可用的觸發器及其等價的回調方法:

  • BEFORE INSERT (等價于 %OnBeforeSave())
  • AFTER INSERT (等價于 %OnAfterSave())
  • BEFORE UPDATE (等價于 %OnBeforeSave())
  • AFTER UPDATE (等價于 %OnAfterSave())
  • BEFORE UPDATE OF specified column(s)
  • AFTER UPDATE OF specified column(s)
  • BEFORE DELETE (等價于 %OnDelete())
  • AFTER DELETE (等價于 %OnAfterDelete())

注意:當觸發器執行時,它不能直接修改正在處理的表中的屬性值。

這是因為InterSystems IRIS在字段(屬性)值驗證代碼之後執行觸發代碼。

例如,觸發器不能将LastModified字段設定為正在處理的行中的目前時間戳。

但是,觸發器代碼可以對表中的字段值發出更新。

更新執行自己的字段值驗證。

在INSERT、UPDATE或DELETE事件發生後執行AFTER觸發器:

  • 如果SQLCODE=0(事件成功完成),InterSystems IRIS将執行AFTER觸發器。
  • 如果SQLCODE是負數(事件失敗),系統間IRIS就不會執行AFTER觸發器。
  • 如果SQLCODE=100(沒有發現要插入、更新或删除的行),則系統間IRIS執行AFTER觸發器。

觸發器執行可以是遞歸的。

例如,如果表T1有一個對表T2執行插入操作的觸發器,表T2也有一個對表T1執行插入操作的觸發器。

當表T1有一個調用例程/過程的觸發器,并且該例程/過程執行對T1的插入操作時,也可以發生遞歸。

觸發器遞歸的處理取決于觸發器的類型:

  • 行和行/對象觸發器:InterSystems IRIS不阻止行觸發器和行/對象觸發器遞歸地執行。

    處理觸發器遞歸是程式員的責任。

    如果觸發代碼不處理遞歸執行,則可能發生runtime <FRAMESTACK>錯誤。

  • 語句觸發器:InterSystems IRIS阻止AFTER語句觸發器遞歸執行。

    如果InterSystems IRIS檢測到該觸發器在執行堆棧中已經被調用,它将不會發出AFTER觸發器。

    沒有錯誤發出;

    觸發器不會被第二次執行。

InterSystems IRIS不會阻止BEFORE語句觸發器遞歸地執行。

在觸發遞歸之前處理是程式員的責任。

如果BEFORE觸發器代碼不處理遞歸執行,可能會發生runtime <FRAMESTACK>錯誤。

每個觸發器包含執行觸發操作的一行或多行代碼。

每當與觸發器關聯的事件發生時,SQL引擎就會調用這段代碼。

如果觸發器是使用CREATE觸發器定義的,則可以用ObjectScript或SQL編寫此操作代碼。

(InterSystems IRIS将SQL編寫的代碼轉換為類定義中的ObjectScript。)

如果觸發器是使用Studio定義的,那麼這個操作代碼必須用ObjectScript編寫。

因為觸發器的代碼不是作為過程生成的,是以觸發器中的所有局部變量都是公共變量。

這意味着觸發器中的所有變量都應該用一個新語句顯式聲明;

這可以防止它們與調用觸發器的代碼中的變量發生沖突。

  • %ok:僅在觸發器代碼中使用的變量。

    如果觸發代碼成功,它設定%ok=1。

    如果觸發代碼失敗,它設定%ok=0。

    如果在觸發器執行期間發出SQLCODE錯誤,InterSystems IRIS将設定%ok=0。

    當%ok=0時,觸發器代碼中止,觸發器操作和調用觸發器的操作被復原。

    如果插入或更新觸發器代碼失敗,并且表中定義了一個外鍵限制,InterSystems IRIS将釋放外鍵表中相應行上的鎖。

觸發代碼可以顯式設定%ok=0。

這會建立一個運作時錯誤,中止觸發器的執行并復原操作。

通常,在設定%ok=0之前,觸發器代碼顯式地将%msg變量設定為使用者指定的字元串,用于描述這個使用者定義的觸發器代碼錯誤。

%ok變量是一個必須顯式更新的公共變量。

在完成非觸發代碼SELECT、INSERT、UPDATE或DELETE語句後,%ok的值與之前的值沒有變化。

%ok僅在執行觸發器代碼時定義。

%msg:觸發代碼可以顯式地将%msg變量設定為描述運作時錯誤原因的字元串。

設定變量%msg。

%oper:僅在觸發器代碼中使用的變量。

觸發器代碼可以引用變量%oper,該變量包含觸發觸發器的事件(插入、更新或删除)的名稱。

在觸發器代碼中,可以使用特殊的{fieldname}文法引用字段值(對于屬于觸發器關聯的表的字段)。

例如,下面是MyApp中LogEvent觸發器的定義。

Person類包含一個對ID字段的引用,如{ID}:

Class MyApp.Person Extends %Persistent [DdlAllowed]
{
    // ... Definitions of other class members

    /// This trigger updates the LogTable after every insert
    Trigger LogEvent [ Event = INSERT, Time = AFTER ]
    {
        // get row id of inserted row
        NEW id,SQLCODE,%msg,%ok,%oper
        SET id = {ID}

        // INSERT value into Log table
        &sql(INSERT INTO LogTable 
            (TableName, IDValue) 
            VALUES ('MyApp.Person', :id))
        IF SQLCODE<0 {SET baderr="SQLCODE ERROR:"_SQLCODE_" "_%msg
                      SET %ok=0
                    RETURN baderr }

      }
   // ... Definitions of other class members

}
      

這個{fieldname}文法支援統一字段。

它不支援%SerialObject集合屬性。

例如,如果表引用了嵌入的串行對象類Address(其中包含屬性City),那麼觸發器文法{Address_City}就是對字段的有效引用。

觸發器文法{Address}是對集合屬性的引用,不能使用。

觸發器代碼可以包含一個引用字段名的宏定義(使用{fieldname}文法)。

但是,如果你的觸發代碼包含一個#Include預處理器指令,用于一個引用字段名的宏(使用{fieldname}文法),那麼這個字段名就不能被通路。

這是因為InterSystems IRIS在代碼被傳遞給宏預處理器之前,翻譯觸發器代碼中的{fieldname}引用。

如果一個{fieldname}引用在#Include檔案中,它不會在觸發器代碼中“看到”,是以不會被轉換。

這種情況的解決方法是定義一個帶參數的宏,然後将{fieldname}傳遞給觸發器中的宏。

例如,#Include檔案可以包含如下一行:

#Define dtThrowTrigger(%val) SET x=$GET(%val,"?")
      

然後在觸發器中調用提供{fieldname}文法作為參數的宏:

$$$dtThrowTrigger({%%ID})   
      

{name*O}, {name*N}和{name*C}觸發代碼文法

在更新觸發器代碼中有三種文法快捷方式可用。

可以使用下面的文法引用舊的(預更新的)值:

{fieldname*O}
      

其中fieldname是字段的名稱,星号後面的字元是字母“O”(表示舊)。

對于插入觸發器,{fieldname*O}總是空字元串("")。

你可以使用下面的文法來引用新的(更新後的)值:

{fieldname*N}
      

其中fieldname是字段的名稱,星号後面的字元是字母“N”(表示新字段)。

{fieldname*N}文法隻能用于引用要存儲的值;

它不能用來更改值。

不能在觸發器代碼中設定{fieldname*N}。

在插入或更新時計算字段的值應該通過其他方法實作,比如SqlComputeOnChange。

可以使用以下文法測試字段值是否被更改(更新):

{fieldname*C}
      

其中,fieldname是字段的名稱,星号後面的字元是字母“C”(表示已更改)。

{fieldname*C}的計算結果是1,如果字段已經被修改,0,如果它沒有被修改。

對于插入觸發器,InterSystems IRIS将{fieldname*C}設定為1。

對于具有流屬性的類,如果SQL語句(INSERT或UPDATE)沒有插入/更新流屬性本身,則對流屬性{stream *N}和{stream *O}的SQL觸發器引用将傳回流的OID。

然而,如果SQL語句确實插入/更新了stream屬性,{stream *O}仍然是OID,但{stream *N}的值被設定為以下之一:

  • 在觸發器之前,将流字段的值以傳遞給更新或插入的任何格式傳回。

    這可以是輸入到stream屬性中的文字資料值,也可以是臨時stream對象的OREF或OID。

  • AFTER trigger将流的Id作為{stream *N}的值傳回。

    這是InterSystems IRIS的Id值,存儲在流字段名為global的^classnameD中。

    該值根據流屬性的CLASSNAME類型參數使用适當的Id格式。

如果一個流屬性使用InterSystems IRIS對象更新,{stream *N}的值總是一個OID。

注意:對于由串行對象的數組集合建立的子表觸發器,觸發器邏輯與對象通路/儲存一起工作,但與SQL通路(插入或更新)不工作。

在ObjectScript中編寫的觸發器代碼可以包含僞域引用變量{%%CLASSNAME}、{%%CLASSNAMEQ}、{%%OPERATION}、{%%TABLENAME}和{%%ID}。

這些僞字段在類編譯時被轉換成特定的值。

可以從觸發器代碼、SQL計算代碼和SQL映射定義中使用類方法,因為類方法不依賴于擁有開放對象。

必須使用##class(classname). methodname()文法從觸發器代碼中調用方法。

你不能使用..Methodname()文法,因為這個文法需要一個目前打開的對象。

可以将目前行字段的值作為類方法的參數傳遞,但是類方法本身不能使用字段文法。

如果調用對應于該表的DML指令,則“拉出”(執行)已定義的觸發器。

對于DML指令成功插入、更新或删除的每一行,都會拉取一行或行/對象觸發器。

對于每個成功執行的INSERT、UPDATE或DELETE語句,都會拉出一次語句觸發器,而不管該語句是否實際更改了表資料中的任何行。

  • INSERT語句拉動相應的插入觸發器。

    插入可以通過指定%NOTRIGGER關鍵字來阻止觸發相應的觸發器。

    指定%NOJOURN關鍵字的插入不會記錄該插入或相應的插入觸發器。

    這意味着插入事件或觸發事件都不可能復原。

快速插入不能用于具有插入觸發器的表。

  • UPDATE語句拉動相應的更新觸發器。

    更新可以通過指定%NOTRIGGER關鍵字來阻止觸發相應的觸發器。

    指定%NOJOURN關鍵字的更新不會記錄該更新或相應的更新觸發器。

    這意味着更新事件或觸發事件都不可能復原。

  • 根據執行的DDL操作的類型,INSERT或UPDATE語句拉動相應的INSERT觸發器或UPDATE觸發器。

    要防止觸發任何類型的觸發器,請指定%NOTRIGGER關鍵字。

  • DELETE語句拉動相應的DELETE觸發器。

    DELETE可以通過指定%NOTRIGGER關鍵字來阻止觸發相應的觸發器。

    指定%NOJOURN關鍵字的删除不會記錄删除或相應的删除觸發器。

    這意味着删除事件或觸發事件都不可能復原。

  • TRUNCATE TABLE語句不會觸發删除觸發器。

預設情況下,DDL語句和相應的觸發操作被記錄在日志中。

%NOJOURN關鍵字阻止DDL指令和觸發動作的日志記錄。

如果觸發器是用Foreach = row/object定義的,那麼觸發器也會在對象通路期間的特定點被調用,這取決于觸發器定義的Event和Time關鍵字,如下所示:

Event Time 此時也調用Trigger
INSERT BEFORE 在新對象的%Save()之前
AFTER 在新對象的%Save()後
UPDATE 在已存在對象的%Save()之前
在已存在對象的%Save()後
DELETE 在現有對象的%DeleteId()之前
在現有對象的%DeleteId()後

是以,也沒有必要為了保持SQL和對象行為同步而實作回調方法,

預設情況下,SQL對象使用%Storage.Persistent存儲。

InterSystems IRIS也支援 %Storage.SQL storage。

SQL存儲。

在使用 %Storage.SQL storage的類中儲存或删除對象時。

SQL存儲,所有語句(Foreach = statement)、行(Foreach = row)和行/對象(Foreach = row/object)觸發器被拉出。

沒有定義Foreach trigger關鍵字的觸發器是行觸發器。

提取所有觸發器是預設行為。

但是,在使用%Storage.SQL storage儲存或删除類中的對象時。

SQL,可以指定隻有定義為Foreach = row/object的觸發器應該被拉出。

定義為Foreach = statement或Foreach = row的觸發器不會被拉取。

這是通過指定類參數OBJECTSPULLTRIGGERS = 0來實作的。

預設值是OBJECTSPULLTRIGGERS = 1。

此參數僅應用于使用%Storage.SQL定義的類。

觸發器在事務中執行觸發器碼。它設定事務級别,然後執行觸發器代碼。成功完成觸發器代碼後,觸發器送出事務。

注意:使用事務的觸發器的結果是,如果觸發器調用送出事務的代碼,則觸發器的完成失敗,因為事務級别已經遞減為0.調用生産的業務服務時可能發生這種情況。

使用INSERT語句級别對象觸發器後,如果觸發器集%OK = 0,則使用SQLCODE -131錯誤失敗行的插入失敗。如下所示,可能會發生交易復原:

  • 如果auto_commit = on,則插入的事務将被復原。
  • 如果auto_commit =off,則應用于復原或送出輸入的事務。
  • 如果使用no_auto_commit模式,則不啟動事務,是以插入件不能復原。

Auto_Commit模式是使用 SET TRANSACTION %COMMITMODE option或 SetOption()方法建立的,如下所示 SET status=$SYSTEM.SQL.Util.SetOption("AutoCommit",intval,.oldval). 可用方法INTVAL值為0(無),1(隐式)和2(顯式)。

觸發器可以在觸發器中的%MSG變量中設定錯誤消息。此消息将傳回給呼叫者,給出觸發器失敗的資訊。

在管理門戶SQL接口目錄詳細資訊中列出了為指定表定義的觸發器。這列出了每個觸發器的基本資訊。

Information.schema.triggers類列出了目前命名空間中的定義觸發器。對于每個觸發資訊.Schema.triggers列出了各種屬性,包括觸發器的名稱,關聯的架構和表名稱,EventManipulation屬性(插入,更新,删除,插入/更新,ActionTiming屬性(之前,之後),建立的屬性(觸發建立時間戳)和ActionStatement屬性,它是生成的SQL觸發器代碼。

建立的屬性從上次修改課程定義時派生觸發建立時間戳。是以,随後使用此類(例如,定義其他觸發器)可能導緻建立屬性值的意外更新。

SELECT TABLE_NAME,TRIGGER_NAME,CREATED,EVENT_MANIPULATION,ACTION_TIMING,ACTION_ORIENTATION,ACTION_STATEMENT 
FROM INFORMATION_SCHEMA.TRIGGERS WHERE TABLE_SCHEMA='SQLUser'