見出し画像

Google Apps Scriptのイベントトリガー「変更」で値が更新された時に自動実行

以前に編集イベントで、値が更新された時に作業を自動実行する方法について書きました。

今回は変更イベントで同様のことを実現する方法についてです。値の更新に対する自動処理は編集イベントの方が向くので、この記事は変更イベントを使わざるを得ない状況下の話です。また、関連してロックについても触れます。

「編集?変更?🤔」と思われた方は、まずは以下の記事をご覧ください。


▍ケース説明

編集イベントの際と同様のケースですが、再掲します。

以下のような構成の、"main" という名前のシートがあるとします。

シート例

C列(処理状況)は入力規則によって、「未処理」「処理」「処理済」を選ぶようになっています。ここで「処理」を選ぶと、所定の処理を自動実行するとします。

イベントトリガーの設定は以下です。changeHandler 関数の内容は後述します。イベントの種類は「変更時」です。

イベントトリガーの設定

▍コード例(簡易版)

コードの例は以下です。なお、ここでは具体的な自動処理の内容は割愛しています。

function changeHandler(e) {
  /**
    * (1) 変更の種類が"EDIT"かを判定
    */
  // 編集以外の変更の場合は関数を終了
  if (e.changeType !== "EDIT") {
    return;
  }

  /**
    * (2) 変更されたシートが"main"かを判定
    */
  // 変更されたシートを取得
  const sheet = e.source.getActiveSheet();

  // シート名が"main"でなければ処理を終了
  if (sheet.getSheetName() !== "main") {
    return;
  }

  /**
    * (3) 3列目(処理状況)の中で値が"処理"かを判定
    */
  // 3列目のデータを取得
  const triggerData = sheet.getRange(1, 3, sheet.getLastRow()).getValues().flat();

  // 値が"処理"の行番号を配列に追加
  const rowIndexes = [];
  for (let [index, value] of triggerData.entries()) {
    if (value === "処理") {
      rowIndexes.push(index + 1); // スプレッドシートが1開始なので+1
    }
  }

  /**
    * (4) 値が"処理"の行番号をもとに自動処理を実行
    */
  // 処理対象の行番号分を繰り返し
  for (let rowIndex of rowIndexes) {
    // 自動処理を実行


    // 処理状況を"処理済"に変更
    sheet.getRange(rowIndex, 3).setValue("処理済");
  }
}

コメントのブロックの順に解説します。

(1) 変更イベントでは様々な操作でイベントトリガーが発動するため、対象の操作を編集(EDIT)のみに絞っています。なお、changeType は変更イベントに特有の情報です。

(2) シートを "main" に絞っています。編集イベントの際と同じです。

(3) 変更イベントにおいて厄介な箇所です。編集イベントと異なり、編集箇所を得られません。そこで、3列目("処理状況" 列)の値を全て取得し、その中で値が "処理" の箇所に絞り、該当の行番号を取得します。

(4) 値が "処理" の行の数分だけ、自動処理を実行して処理状況を "処理済" に変更します。

▍同時実行の課題とロック

先ほどの方法では、同時にシートを編集すると、自動処理が複数回実行されてしまう可能性があります。以下に例で解説します。

2行目3列目(処理状況)の値を "処理" に更新したとします。イベントトリガーが発動し、判定を通って自動処理が行われます。このとき、スクリプト側で "処理" を "処理済" に変更するまでにタイムラグがあります。

この間に "main" シートの別のセルの値が更新されると、イベントトリガーが発動します。2行目3列目の値は "処理" のままなので、判定を通って自動処理が行われます。

結果、2回自動処理が行われることになります。

このような事象を避けるためには、ロックを活用できます。ロックは同時実行を防ぐ仕組みです。大まかには、ロックを取得しているものだけが処理を実行でき、他はロックの解放を待つことになります。

ロックについては詳しく説明されている記事があるので、詳細はそちらに譲らせていただきます。

公式リファレンスも貼っておきます。

▍コード例(ロック実装版)

ロックを実装したコードは以下です。

function changeHandler(e) {
  // 編集以外の変更の場合は関数を終了
  if (e.changeType !== "EDIT") {
    return;
  }

  // 変更されたシートを取得
  const sheet = e.source.getActiveSheet();

  // シート名が"main"でなければ処理を終了
  if (sheet.getSheetName() !== "main") {
    return;
  }

  // ロックの準備
  const lock = LockService.getDocumentLock();

  // ロックを取得(最大20秒待機)
  try {
    lock.waitLock(20000);
  } catch (error) {
    console.log("ロックを取得できませんでした。", error.message);
    return;
  }

  // ロック取得中の処理
  try {
    // 3列目のデータを取得
    const triggerData = sheet.getRange(1, 3, sheet.getLastRow()).getValues().flat();

    // 値が"処理"の行番号を配列に追加
    const rowIndexes = [];
    for (let [index, value] of triggerData.entries()) {
      if (value === "処理") {
        rowIndexes.push(index + 1); // スプレッドシートが1開始なので+1
      }
    }

    // 処理対象の行番号分を繰り返し
    for (let rowIndex of rowIndexes) {
      // 自動処理を実行


      // 処理状況を"処理済"に変更
      sheet.getRange(rowIndex, 3).setValue("処理済");
    }
    
  } finally {
    // ロックを解放
    lock.releaseLock();
  }
}

ポイントは以下です。

  • ロック取得には、waitLock か tryLock を使えます。ここでは、ロック取得に失敗した際のエラーメッセージを出力するために waitLock を使っています。

  • ロックを取得できた場合、終わったら必ず releaseLock でロックを解放する必要があります。ここでは、ロック取得後にエラーが発生してもロックを解放するよう、try…finallyを記述しています。

▍おわりに

この記事では、変更イベントを使用して値が更新された際に自動処理を行う方法を紹介しました。編集イベントと異なり更新箇所を直接得られないため、一手間加える必要があります。併せて、ロックの実装も求められます。

基本は編集イベントの方がオススメですが、変更イベントを使わざるを得ない状況もあります。そのような場合の自動処理の実現方法として、この記事の内容をお役立ていただけると嬉しいです。


私たちのデジタル技術活用のメモが次のマガジンにあります。よろしければ覗いてみてください!


この記事が気に入ったらサポートをしてみませんか?