見出し画像

#37 ないものを探す

今回のプログラムは、そのまま使えるものではありません。このプログラムを参考に、自身でカスタマイズしてプログラムを作成するためのアイディア・テクニックとして紹介します。

  • 今回のサンプルプログラムでは、個人情報を含んだデータを扱うと公開するのに面倒なので「市町村名」を対象にしていますが、実際のケースでは「メールアドレス」を対象にするケースが考えられます。

  • 冒頭にも書いたように、プログラムのカスタマイズが必要なためコピペですぐ使えるものではなく、上記のように取り扱うデータも変更になっていることをご理解ください。

プログラムの目的

目的は、あるシートにまとめられた名簿データを参照して、回答データに回答として登録されていないものを抽出することです。

具体的には、以下のように用意された名簿データを基にして、回答のないデータ(市町村名)を抽出します。

名簿データ(サンプル) - シート名「名簿」
回答データ(サンプル) - シート名「回答」

富山県内には 15 の市町村があります。その一覧が「名簿データ」に保存されていますが、「回答データ」にはいくつかのデータが抜けていて 11件のデータしかありません。このような状況でどの市町村が抜けているのかを抽出するプログラムです。

  • 今回の「回答データ」は「名簿データ」の市町村名の列をコピペしたものを、いくつかランダムに削除したもので、並び替えは行ってありませんが、プログラムとしては「回答データ」がどんな順であっても構いません。

  • 今回のサンプルデータは、富山県のホームページに掲載されていた富山県内の市町村の一覧を利用させていただきました。 ※サンプルデータとして用いた、回答の寄せられていない市町村について、何の意図もありません。

作成したプログラム

実際に作成したプログラムは以下の通りです。

作成したプログラム

プログラムの説明

const nameList = "名簿";            // 名簿が保存されているシートの名前
const nameCol = 2;                  // 名簿のチェック対象となる列(1~) 
const answerList = "回答";          // 検索対象の回答が保存されているシートの名前
const answerCol = 1;                // 回答のチェック対象となる列(1~) 

冒頭で const によって定義しているのは、チェックの対象となる「名簿データ」「回答データ」の情報です。それぞれ、シート名と列番号を指定しています。
今回は市町村名が、「名簿データ」は列 B に保存されているので 2、「回答データ」は列 A に保存されているので 1、を設定しています。
この部分は、チェックの対象に応じて変更しなければなりません。

/****************************************************************************
 * 指定された名簿の中から、回答していないものを探す
 */
function searchUnrespondents() {

この部分は、関数を説明するコメントと、関数の宣言となります。

  // 名簿データを 2次元配列として読み込む
  const nameSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(nameList);
  const nameData = nameSheet.getDataRange().getValues();
  console.log(nameData);

GAS のプログラムには実行時間に制限が課せられているので、セルの値を一つずつ読み込むのは好ましくないため、この部分では、「名簿データ」の内容を 2次元配列としてすべて読み込んでいます。

  // 回答データのチェック対象の列だけを 1次元配列として読み込む
  const answerSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(answerList);
  const answerData = answerSheet.getRange(2, answerCol, answerSheet.getLastRow() - 1).getValues().flat();
  console.log(answerData);

同様に、「回答データ」の内容も配列に読み込んでいます。ただし、「名簿データ」とは異なり、チェックの対象となる列だけを、1行目の見出し行を除き、実データだけを1次元配列として読み込んでいます。

1次元配列として読み込まれた「回答データ」

.getRange() で列方向に範囲を指定して、 .getValues() によって配列として取得し、.flat() で 1次元配列に変換しています。

  // 名簿データの行数分、回答の有無をチェックする
  for (let i = 1; i < nameData.length; i++) {
    searchStr = nameData[i][nameCol - 1];
    if (!answerData.includes(searchStr) {
      // 回答がない
      console.log(i + " : " + searchStr + " is " + answerData.includes(searchStr));
    }
  }
}

2つの配列(nameDataanswerData)を用意したら、nameData の内容を順番にチェックしていきます。
「回答データ」を 1次元配列にしたことで、.includes() によって配列内に目的のデータが存在しているかを簡単にチェックできます。
.includes() は、指定されたデータが配列内に存在していなければ false を戻すので、23行目の if{ } の中が実行されるのは、「名簿データ」に存在しているのに、「回答データ」には存在していないものだけとなります。

プログラムの実行結果

.includes() を使うことで、二重ループにすることなく、簡単にデータが存在しているかをチェックできます。

この .includes() を使うテクニックは、以下のページを参考にしたものです。

まとめ

今回のプログラムでは、「市町村名」が存在するかどうかをチェックするものになっていますが、チェックの対象をメールアドレスとし、「回答データ」は Google フォームの回答とすれば、回答していない人に対して催促メールを送信できるようになります。

その催促メールの送信を、時間起動するトリガーにしておけば、毎日決められた時間までに Google フォームで回答を送信していない人に対してメールを送信するようにもできます。

「時間主導型」のトリガー

ただし、「時間主導型」のトリガーは実行されるタイミングにズレが生じる場合があるので、より厳密な時間に実行されるようにしたいのであれば、毎回、日時を指定したトリガーを設定し直さなければなりません。

この辺りは、必要に応じてさらにカスタマイズしてみてください。

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