數據庫筆記(九)——對違反約束的處理 2017.10.15

寫在前面:本篇博客大部分內容參考數據庫系統概念(本科教學版)第四章的一些尾巴,然后開始講第六章的關系代數
筆者接下來的代碼示例會主要在SQL Server數據庫中測試


在開始今天的摸魚大業之前,讓我們構造一些簡單表

-- 執行下面的語句構造表
CREATE TABLE country(
  country_id INTEGER PRIMARY KEY ,
  country_name VARCHAR(20)
);

CREATE TABLE person(
  person_id INTEGER PRIMARY KEY ,
  name VARCHAR(20),
  country_id INTEGER FOREIGN KEY REFERENCES country(country_id)
    ON DELETE CASCADE
    ON UPDATE CASCADE
);

INSERT INTO country (country_id, country_name) VALUES (
    1, 'China'
);
INSERT INTO country (country_id, country_name) VALUES (
    2, 'English'
);
INSERT INTO country (country_id, country_name) VALUES (
    3, 'America'
);

INSERT INTO person (person_id, name, country_id) VALUES (
    1, 'Sunny', 1
);

INSERT INTO person (person_id, name, country_id) VALUES (
    2, 'Robbin', 2
);

INSERT INTO person (person_id, name, country_id) VALUES (
    3, 'Jane', 3
);

級聯操作

在指定外鍵以后,由于存在完整性約束,所以在執行刪除或更新的時候由于語句可能會破壞完整性約束而執行失敗。因此可以在定義外鍵的時候聲明為級聯刪除和級聯更新(是一種對違反參照完整性約束時的處理方式)

  • 使用方式

    CREATE table 表名(
      ...
      FOREGIN KEY (字段序列) REFERENCES 表名(字段序列)
          ON DELETE CASCADE
          ON UPDATE CASCADE,
      ...
    )
    
  • 級聯刪除(ON DELETE CASCADE)

    • 級聯刪除是在定義外鍵時指定的,但是卻會在執行刪除語句時產生影響
    • 舉個栗子
      • 我們先不指定級聯
        DROP TABLE person;
        DROP TABLE country;
        CREATE TABLE country(
          country_id INTEGER PRIMARY KEY ,
          country_name VARCHAR(20)
        );
        
        CREATE TABLE person(
          person_id INTEGER PRIMARY KEY ,
          name VARCHAR(20),
          country_id INTEGER FOREIGN KEY REFERENCES country(country_id)
        );
        
        INSERT INTO country (country_id, country_name) VALUES (
            1, 'China'
        );
        INSERT INTO country (country_id, country_name) VALUES (
            2, 'English'
        );
        INSERT INTO country (country_id, country_name) VALUES (
            3, 'America'
        );
        
        INSERT INTO person (person_id, name, country_id) VALUES (
            1, 'Sunny', 1
        );
        
        INSERT INTO person (person_id, name, country_id) VALUES (
            2, 'Robbin', 2
        );
        
        INSERT INTO person (person_id, name, country_id) VALUES (
            3, 'Jane', 3
        );
        
        
      • 然后執行下面的刪除操作
        -- 下面我們試圖刪除中國的信息,但是在person表里有一條數據引用了中國,所以因為參照完整性約束的存在,所以這條語句會執行失敗
        DELETE country
        WHERE country_id = 1
        
      • 接下來我們重新構造一遍(當然直接用DDL語句更新也是可以的)--并在構造person表時指定了級聯刪除
        DROP TABLE person;
        DROP TABLE country;
        CREATE TABLE country(
          country_id INTEGER PRIMARY KEY ,
          country_name VARCHAR(20)
        );
        
        CREATE TABLE person(
          person_id INTEGER PRIMARY KEY ,
          name VARCHAR(20),
          country_id INTEGER FOREIGN KEY REFERENCES country(country_id)
              ON DELETE CASCADE
        );
        
        INSERT INTO country (country_id, country_name) VALUES (
            1, 'China'
        );
        INSERT INTO country (country_id, country_name) VALUES (
            2, 'English'
        );
        INSERT INTO country (country_id, country_name) VALUES (
            3, 'America'
        );
        
        INSERT INTO person (person_id, name, country_id) VALUES (
            1, 'Sunny', 1
        );
        
        INSERT INTO person (person_id, name, country_id) VALUES (
            2, 'Robbin', 2
        );
        
        INSERT INTO person (person_id, name, country_id) VALUES (
            3, 'Jane', 3
        );
        
        
      • 然后再次執行下面的刪除操作
        -- 此時執行會發現語句成功執行了,不但刪除了中國的信息,連帶person表中引用了中國信息的所有數據都被刪除了
        DELETE country
        WHERE country_id = 1
        
    • 上面的例子便很好的說明了級聯刪除的作用。如果我們視圖刪除外鍵參照鍵所在表(此處為country表)的某條數據A(此處是中國的信息),而這條數據又被外鍵所在表的一條或多條數據B所關聯(此處person表中Sunny的country_id關聯了country表中中國的id)。在指定了級聯刪除的情況下,刪除A會連帶著刪除所有滿足條件的B
    • 當然在實際使用的時候用的還是比較少的,因為參照完整性約束在一定程度上可以防止數據的誤刪除,對數據庫的完整性起了一定的保護作用,如果指定了級聯刪除,這層保護就失效了。所以還是視情況而用
  • 級聯更新(ON UPDATE CASCADE)

    • 類似的,級聯更新和級聯刪除一樣,如果我們更新時違反了完整性約束,同樣更新操作不被拒絕,而是級聯更新
    • 舉個栗子(我們在上面操作的基礎上執行,上面構造時指定了級聯刪除,但是沒指定級聯更新)
      -- 我們試圖執行下面的更新操作,我們把修改English的country_id, 但是由于person表中還有數據的country_id=2,如果下面的更新成功執行,則會導致person表中存在country_id=2的數據,而country中卻沒有對應數了,違反參照完整性約束,故下面的語句執行失敗
      UPDATE country
      SET country_id = 4
      WHERE country_id = 2
      
    • 同樣的,我們重新構造一下,此時指定級聯更新
      DROP TABLE person;
      DROP TABLE country;
      CREATE TABLE country(
        country_id INTEGER PRIMARY KEY ,
        country_name VARCHAR(20)
      );
      
      CREATE TABLE person(
        person_id INTEGER PRIMARY KEY ,
        name VARCHAR(20),
        country_id INTEGER FOREIGN KEY REFERENCES country(country_id)
          ON DELETE CASCADE
          ON UPDATE CASCADE
      );
      
      INSERT INTO country (country_id, country_name) VALUES (
          1, 'China'
      );
      INSERT INTO country (country_id, country_name) VALUES (
          2, 'English'
      );
      INSERT INTO country (country_id, country_name) VALUES (
          3, 'America'
      );
      
      INSERT INTO person (person_id, name, country_id) VALUES (
          1, 'Sunny', 1
      );
      
      INSERT INTO person (person_id, name, country_id) VALUES (
          2, 'Robbin', 2
      );
      
      INSERT INTO person (person_id, name, country_id) VALUES (
          3, 'Jane', 3
      );
      
    • 此時再執行一下上面的更新語句
      -- 由于指定了級聯更新,所以會發現下面的語句執行成功了,不但更改了country表中的數據,連帶著person表中的數據也一并更新了
      UPDATE country
      SET country_id = 4
      WHERE country_id = 2
      
    • 上面就是級聯更新的效果
  • 另一類對違反完整性約束的處理

    • SET DEFAULT
      • 一旦違反完整性約束,就將參照域(此處為country_id)設置為默認值
    • SET NULL
      • 一旦違反完整性約束,就將參照域(此處為country_id)設置為NULL
    • 舉個栗子
      • 執行下面的構造
        DROP TABLE person;
        DROP TABLE country;
        CREATE TABLE country(
          country_id INTEGER PRIMARY KEY ,
          country_name VARCHAR(20)
        );
        
        CREATE TABLE person(
          person_id INTEGER PRIMARY KEY ,
          name VARCHAR(20),
          country_id INTEGER FOREIGN KEY REFERENCES country(country_id)
            ON DELETE SET NULL
        );
        
        INSERT INTO country (country_id, country_name) VALUES (
            1, 'China'
        );
        INSERT INTO country (country_id, country_name) VALUES (
            2, 'English'
        );
        INSERT INTO country (country_id, country_name) VALUES (
            3, 'America'
        );
        
        INSERT INTO person (person_id, name, country_id) VALUES (
            1, 'Sunny', 1
        );
        
        INSERT INTO person (person_id, name, country_id) VALUES (
            2, 'Robbin', 2
        );
        
        INSERT INTO person (person_id, name, country_id) VALUES (
            3, 'Jane', 3
        );
        
      • 然后執行下面的刪除操作
        -- 下面的刪除操作成功執行,但不是級聯刪除,而是把person表中原來country_id=1的數據的country_id都設成了NULL
        DELETE country
        WHERE country_id = 1
        

延遲檢查

這是由于數據庫默認是在執行每一條SQL語句的時候都進行完整性約束的檢查,導致有些操作無法進行。延遲操作就將完整性約束的檢查延遲到了事務結束的時候檢查(大多數數據庫不支持,比如SQL Server, 但Oracle數據支持)

  • 由于不常用,SQL Server也不支持,這里就講一下概念,不舉實際的栗子了。

  • 假設上面的例子表中沒有指定延遲檢查

    • 執行下面的語句
    -- 執行下面兩條語句是會出錯的,因為插入第一條數據的時候,由于完整性約束的存在,要求country表中要有country_id=4的數據,但是這個數據目前還不存在(所以只要先執行第二條語句,這兩個語句才能成功執行)
    INSERT INTO person (person_id, name, country_id) VALUES (
        4, 'Jerry', 4
    );
    INSERT INTO country (country_id, country_name) VALUES (
        4, 'France'
    );
    
  • 而如果指定了延遲檢查呢

    • 執行下面語句(下面兩個語句處于同一個事務中)
    -- 由于是延遲檢查,所以兩條數據都插入完,執行commit,事務結束時才進行完整性約束的檢查,此時就不會出錯,可以正常插入
    INSERT INTO person (person_id, name, country_id) VALUES (
        4, 'Jerry', 4
    );
    INSERT INTO country (country_id, country_name) VALUES (
        4, 'France'
    );
    COMMIT
    
  • 雖然SQL標準中有這個概念,但是大多數數據庫沒有提供支持,并且不常用

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容