web-dev-qa-db-ja.com

1つのテーブルのみのトリガーを無効にする

トリガーを一時的に無効にすることは可能ですが、1つのテーブルに対してのみ可能です。

たとえば、テーブル、TableAにon insert、update、deleteトリガーがあります。同じトリガーを持つテーブルBもありますが、それらはテーブルAの特定の列にのみ影響します。

現在、両方のテーブルを使用する更新クエリがあります。テーブルAの更新はトリガーを起動する必要があることはわかっていますが、テーブルBの更新はトリガーを起動する必要はありません。更新が完了するまで、これらのトリガーを無効にしたいと思います。

これは可能ですか? MySQL 5.1を使用しています

[補遺]

これは本質的にトリガーテーブルAです

BEGIN
    IF (OLD.status != 1 AND NEW.status = 2) THEN
        IF (OLD.geo_lat IS NOT NULL AND OLD.geo_long IS NOT NULL) THEN
            DELETE FROM geo WHERE datatype IN (3,4) AND foreignid = NEW.id;
        END IF;
    ELSEIF (OLD.Status = 1 AND NEW.Status != 2) THEN
        IF (NEW.geo_lat IS NOT NULL AND NEW.geo_long IS NOT NULL) THEN
            INSERT INTO geo (datatype, foreignid, long, lat, hostid, morton, status) VALUES  (IF(NEW.groupType=1,3,4), NEW.id, NEW.geo_long, NEW.geo_lat, NEW.hostid, 0, NEW.Status);
        END IF;
    ELSEIF (NEW.status != 3) THEN  
        IF (OLD.geo_lat IS NOT NULL AND OLD.geo_long IS NOT NULL AND (NEW.geo_lat IS NULL OR NEW.geo_long IS NULL)) THEN
            DELETE FROM geo WHERE datatype IN (3,4) AND foreignid = NEW.id;
        ElSEIF ((OLD.geo_lat IS NULL OR OLD.geo_long IS NULL) AND NEW.geo_lat IS NOT NULL AND NEW.geo_long IS NOT NULL) THEN
            INSERT INTO geo (datatype, foreignid, longitude, latitude, hostid, morton, status) VALUES  (IF(NEW.groupType=1,3,4), NEW.id, NEW.geo_long, NEW.geo_lat, NEW.hostid, 0, NEW.Status);
        ELSEIF (OLD.geo_lat!=NEW.geo_lat OR OLD.geo_long != NEW.geo_long OR OLD.status != NEW.status) THEN
            UPDATE geo SET lat = NEW.geo_lat, long = NEW.geo_long, status = NEW.status WHERE datatype IN (3,4) AND foreignid = NEW.id;
        END IF;
    END IF;
END

基本的にここにテーブルBのトリガーがあります

CREATE TRIGGER `usergroups_comments_insert` AFTER INSERT ON `usergroups_comment`
    FOR EACH ROW
    BEGIN
       CALL sp-set-comment_count(NEW.`gid`);
    END;

これが、テーブルBから呼び出されるストアドプロシージャです。

DELIMITER $$

CREATE PROCEDURE `sp_set-comment_count` (IN _id INT)
BEGIN
   -- AC   - All Count
   -- OLDAC- Old All Count
   DECLARE AC, OLDAC INT DEFAULT 0;

   SELECT COUNT(*) AS ac
     INTO AC
     FROM usergroups AS ug
LEFT JOIN usergroup_comments AS ugm ON ugm.`gid` = ug.`id`
LEFT JOIN mediagallery AS dm ON ugm.mid = dm.`id`
    WHERE dm.`status` NOT IN (200, 201, 202, 203, 204, 205)
      AND ug.`id` = _id;

   SELECT allCount
     INTO OLDAC
     FROM usergroups
    WHERE ug.`id` = _id;

IF (OLDAC <> AC) THEN 

   UPDATE usergroups
      SET allCount = AC,
    WHERE usergroups.`id` = _id;

END IF;

END $$
6
Mark D

実際、すべてのトリガーにif thenブロックを配置すると、すべてのトリガーを効果的にシャットダウンできます。ここにそのようなコードブロックがあります

IF @TRIGGER_DISABLED = 0 THEN
    ...trigger body
END IF;

Mysql環境では、次のことができます。

  • 実行SET @TRIGGER_DISABLED = 1;
  • データのメンテナンスを行う
  • 実行SET @TRIGGER_DISABLED = 0;

したがって、テーブルAのトリガーは次のようになります。

BEGIN 
    IF @TRIGGER_DISABLED = 0 THEN
    IF (OLD.status != 1 AND NEW.status = 2) THEN 
        IF (OLD.geo_lat IS NOT NULL AND OLD.geo_long IS NOT NULL) THEN 
            DELETE FROM geo WHERE datatype IN (3,4) AND foreignid = NEW.id; 
        END IF; 
    ELSEIF (OLD.Status = 1 AND NEW.Status != 2) THEN 
        IF (NEW.geo_lat IS NOT NULL AND NEW.geo_long IS NOT NULL) THEN 
            INSERT INTO geo (datatype, foreignid, long, lat, hostid, morton, status) VALUES  (IF(NEW.groupType=1,3,4), NEW.id, NEW.geo_long, NEW.geo_lat, NEW.hostid, 0, NEW.Status); 
        END IF; 
    ELSEIF (NEW.status != 3) THEN   
        IF (OLD.geo_lat IS NOT NULL AND OLD.geo_long IS NOT NULL AND (NEW.geo_lat IS NULL OR NEW.geo_long IS NULL)) THEN 
            DELETE FROM geo WHERE datatype IN (3,4) AND foreignid = NEW.id; 
        ElSEIF ((OLD.geo_lat IS NULL OR OLD.geo_long IS NULL) AND NEW.geo_lat IS NOT NULL AND NEW.geo_long IS NOT NULL) THEN 
            INSERT INTO geo (datatype, foreignid, longitude, latitude, hostid, morton, status) VALUES  (IF(NEW.groupType=1,3,4), NEW.id, NEW.geo_long, NEW.geo_lat, NEW.hostid, 0, NEW.Status); 
        ELSEIF (OLD.geo_lat!=NEW.geo_lat OR OLD.geo_long != NEW.geo_long OR OLD.status != NEW.status) THEN 
            UPDATE geo SET lat = NEW.geo_lat, long = NEW.geo_long, status = NEW.status WHERE datatype IN (3,4) AND foreignid = NEW.id; 
        END IF; 
    END IF; 
    END IF; 
END 

したがって、テーブルBのトリガーは次のようになります。

CREATE TRIGGER `usergroups_comments_insert` AFTER INSERT ON `usergroups_comment`     
    FOR EACH ROW     
    BEGIN     
        IF @TRIGGER_DISABLED = 0 THEN
            CALL sp-set-comment_count(NEW.`gid`);     
        END IF;
    END;     

テーブルAのトリガーを起動し、テーブルBは起動しない場合は、コードブロックをテーブルBのトリガーにのみ追加します。

6
RolandoMySQLDBA

このアプローチを簡略化できます。

  1. 最初の「BEGIN」ブロックにラベルを追加するだけです。
  2. 制御変数がNOT NULLかどうかをテストします。
  3. はいの場合、トリガーを残します。

したがって、元のトリガーコードを「IF」ステートメントにラップすることを回避できます。明確に定義された方法で変更する必要があるのは、トリガーヘッドだけです。

例:

CREATE TRIGGER [YOUR_TRIGGER_SPEC]
Trigger: BEGIN

      IF @TRIGGER_DISABLED NOT NULL THEN
         LEAVE Trigger;
      END IF;

      [YOUR CODE]
END;

楽しんで :-)

RolandoMySQLDBA 2012-05-01 18:38 EDTによる編集

実際にその数か月前に試しましたが、不安定です。方法は次のとおりです。

サンプルテーブル

mysql> show create table mytext\G
*************************** 1. row ***************************
       Table: mytext
Create Table: CREATE TABLE `mytext` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `txt` text NOT NULL,
  `txtmd5` char(32) NOT NULL DEFAULT '',
  PRIMARY KEY (`id`),
  UNIQUE KEY `txtmd5` (`txtmd5`)
) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=latin1
1 row in set (0.00 sec)

mysql> select * from mytext;
+----+------------------------+----------------------------------+
| id | txt                    | txtmd5                           |
+----+------------------------+----------------------------------+
|  1 | Rolando Edwards        | ab14209a029a8f6d42d7c5a5d7a77623 |
|  2 | Pamela Edwards         | 5dcbd06ea48c690032b1b29a514eb0e2 |
|  3 | Dominique Edwards      | 7487431d74ac2d17a9d63123672a4bdf |
|  4 | Diamond Edwards        | bc8d80541a000ed506048134058e2878 |
|  5 | The Quick Brown Fox    | be94284cfa534c1837e744c061f71e17 |
| 11 | Quick Brown Fox Jumped | 495a52136057b242a80e514d7cbe77c7 |
+----+------------------------+----------------------------------+
6 rows in set (0.00 sec)

mysql>

これがトリガーです:

DROP TRIGGER IF EXISTS mytext_bi;
DELIMITER $$
CREATE TRIGGER mytext_bi BEFORE INSERT ON mytext FOR EACH ROW
TheTrigger:BEGIN
    DECLARE found_count INT;
    SELECT COUNT(1) INTO found_count
    FROM mytext WHERE txtmd5 = MD5(LEFT(new.txt,10));
    IF found_count = 1 THEN
        LEAVE TheTrigger;
    END IF;
    SET new.txtmd5 = MD5(LEFT(new.txt,10));
END $$
DELIMITER ;

「ドミニク・エドワーズ」を挿入しようとするとどうなるか見てください

mysql> insert into mytext (txt) values ('Dominique Edwards');
Query OK, 1 row affected (0.06 sec)

mysql> select * from mytext;
+----+------------------------+----------------------------------+
| id | txt                    | txtmd5                           |
+----+------------------------+----------------------------------+
|  1 | Rolando Edwards        | ab14209a029a8f6d42d7c5a5d7a77623 |
|  2 | Pamela Edwards         | 5dcbd06ea48c690032b1b29a514eb0e2 |
|  3 | Dominique Edwards      | 7487431d74ac2d17a9d63123672a4bdf |
|  4 | Diamond Edwards        | bc8d80541a000ed506048134058e2878 |
|  5 | The Quick Brown Fox    | be94284cfa534c1837e744c061f71e17 |
| 11 | Quick Brown Fox Jumped | 495a52136057b242a80e514d7cbe77c7 |
| 14 | Dominique Edwards      |                                  |
+----+------------------------+----------------------------------+
7 rows in set (0.00 sec)

mysql>

ああ、それはとにかく滑りました!!!

あなたの努力だけでなく、私がまったく同じことを試みた(BEGINの近くにラベルを貼った)ため、あなたの答えを+1しますが、それでもうまくいきませんでした。どのように学ぶ方法。

MySQLストアドプロシージャ言語を完全に信頼するべきではなく(信号処理は適切に実装されていません)、「楽しんでください」と言ったときにそれを強調しました。 時々、楽しむことはそれを試してみることを意味します!!!。それが本当の学びの精神です。

私はそれをこのように回避しました:

DROP TRIGGER IF EXISTS mytext_bi;
DELIMITER $$
CREATE TRIGGER mytext_bi BEFORE INSERT ON mytext FOR EACH ROW
BEGIN
    DECLARE found_count INT;
    SELECT COUNT(1) INTO found_count
    FROM mytext WHERE txtmd5 = MD5(LEFT(new.txt,10));
    IF found_count = 0 THEN
        SELECT COUNT(1) INTO found_count FROM table_that_does_not_exist;
    END IF;
    SET new.txtmd5 = MD5(LEFT(new.txt,10));
END $$
DELIMITER ;

ストアドプロシージャの性質は、私が回答で行ったようにIF-THENブロックを介してナビゲートする完全なコードを実行するか、信号処理を使用することです(これも、完全に動作するわけではありません)。

DBA StackExchangeへようこそ!!!ここでも、約束どおりに+1します。

長く編集して申し訳ありませんが、サンプルコードをコメントに入れることができませんでした。

4
Frank

まず第一に、「+ 1」をありがとう:-)

そして-あなたは正しい:トリガーを無効にすることはアクションの彼自身を妨げません。現在、MySQLによって実装されているこの種の「中止」シグナルを実行する「正しい」方法はありません。

一般的な「回避策」は、回答で説明したように、SQL例外を発生させることです。そのため、アクション全体が副作用(例外)によって防止されます。

しかし:注意してください。この「解決策」は、時としてより多くの問題を引き起こします-トランザクションのトリガーと必要な-そしてうまくいけば制御された-ロールバックについて考えてください。うううう! (つまり、このアプローチに引用符を使用した理由)。

さらに、そのような種類の実装をデバッグすることは本当に難しいことです。この動作により、開発者、管理者、または保守担当者は、「適切な」SQL例外と実際に発生した問題を区別できる必要があります。恐ろしい。

そして最後に重要なことですが、この種の制限はMySQLの各ストアドルーチン(手順、関数、トリガー)に当てはまります。

実際、この制御の欠如は、RDBMSの各プロバイダーのせいです。そしてうん-これは私が「楽しい時を過す」という意味です:-)

さて、ルーツに戻りましょう-質問は:「トリガーを無効にする」(アクションを妨げないこと)特にこの特別な要件に対しては、アプローチは安全で信頼できます。

MySQLは '@TRIGGER_DISABLED'などのセッションベースの変数をNULLで初期化するため、提供されたアプローチは安全に機能します。これも副作用です。

そして、覚えておいてください:この種の変数は現在のセッションでのみ表示されます!そのため、現在のセッションのトリガーを無効にできます。この種の実装には、「サーバー全体」の影響はありません。

この種のもの(サーバー全体のトリガーを無効にする)が必要な場合は、サーバー変数(uuups-予測できない副作用を伴う別の種類の「回避策」)または-より明確な方法-テーブルなどのサーバー全体の可視でアクセス可能なオブジェクトを使用する必要があります。

だから、また私の長い応答のために「ごめんなさい」そしてもう一度:楽しんでください:-)

2
Frank