[Google Apps Script] 複数条件でのテーブルデータの検索(VLOOKUP)を多次元テーブルに対して行う
こんにちは、CX 事業本部製造ビジネステクノロジー部の若槻です。
Google スプレッドシートでテーブルから条件に合致するレコードの検索を行いたい場合は、VLOOKUP がよく使われると思います。
https://support.google.com/docs/answer/3093318 より引用
しかしこの VLOOKUP を複数のカラムの値を条件にして行いたい場合は、複数カラムの値を結合した文字列を取得するなど関数の記述が複雑になってしまいます。
そこで今回は、複数条件での vlookup を多次元テーブルに対して行う処理を Google Apps Script(GAS) で実装してみました。
対象のスプレッドシート
Source シート
検索対象のレコードが格納されている Source
シートです。
Target シート
検索条件となる複数のカラムが格納されており、また検索結果の入力先となる Target
シート(多次元テーブル)です。
GAS コード
VLOOKUP を行う GAS コードを作成するにあたり、いくつか改善を重ねていったのでそれぞれの段階でのコードを紹介します。
基本的な処理の実装
まずはじめに次のような基本的な処理を行うコードを作成しました。
- Source シートからテーブルデータを取得
- Target シートから検索条件を取得
- Target シートのセルごとに検索条件に合致するレコードを検索
- 検索結果を Target シートにセット
const SOURCE_START_CELL = 'B3'; const SOURCE_END_CELL = 'F52'; const TARGET_START_ROW_NUM = 3; const TARGET_END_ROW_NUM = 29; const TARGET_COLUMN = 'E'; function myFunction() { // スプレッドシートを取得 const ss = SpreadsheetApp.getActiveSpreadsheet(); // Source シートの取得 const sourceSheet = ss.getSheetByName('Source'); // Source データのセル範囲の取得 const sourceRange = sourceSheet.getRange(SOURCE_START_CELL + ':' + SOURCE_END_CELL); // Source データの二次元の値データの取得 const sourceValues = sourceRange.getValues(); // Source データの Json 形式のリストの取得 const sourceDataList = sourceValues.map(value => { return { name: value[0], columnA: value[1], columnB: value[2], columnC: parseInt(value[3]), columnD: value[4] }; }); // Target シートの取得 const targetSheet = ss.getSheetByName('Target'); // 検索条件となる ColumnD の値の取得 const conditionColumnD = targetSheet.getRange(TARGET_COLUMN + '2').getValue(); // Target に指定した範囲をセルごとに処理 for (let i = TARGET_START_ROW_NUM; i <= TARGET_END_ROW_NUM; i++) { // 検索条件のカラムの値を取得 const conditionColumnA = targetSheet.getRange('B' + i).getValue(); const conditionColumnB = targetSheet.getRange('C' + i).getValue(); const conditionColumnC = parseInt(targetSheet.getRange('D' + i).getValue()); // 検索結果 const results = sourceDataList.filter(value => value.columnA === conditionColumnA && value.columnB === conditionColumnB && value.columnC === conditionColumnC && value.columnD === conditionColumnD ); if (results.length > 0) { // 条件に合う結果がある場合は、先頭のデータの name をセット targetSheet.getRange(TARGET_COLUMN + i).setValue(results[0].name); } else { // 条件に合う結果がない場合は '-' をセット targetSheet.getRange(TARGET_COLUMN + i).setValue('-'); } } }
コードを実行すると、コード冒頭で指定した E3
から E29
の範囲のセルに値がセットされました。
setValues で複数のセルに一括で値をセットする
前述のコードの問題点として、setValue
でセルごとに値をセットしているため処理が遅いという点があります。そこで次のコードでは setValues で複数のセルに一括で値をセットするようにしました。
const SOURCE_START_CELL = 'B3'; const SOURCE_END_CELL = 'F52'; const TARGET_START_ROW_NUM = 3; const TARGET_END_ROW_NUM = 29; const TARGET_COLUMN = 'E'; function myFunction() { // スプレッドシートを取得 const ss = SpreadsheetApp.getActiveSpreadsheet(); // Source シートの取得 const sourceSheet = ss.getSheetByName('Source'); // Source データのセル範囲の取得 const sourceRange = sourceSheet.getRange(SOURCE_START_CELL + ':' + SOURCE_END_CELL); // Source データの二次元の値データの取得 const sourceValues = sourceRange.getValues(); // Source データの Json 形式のリストの取得 const sourceDataList = sourceValues.map(value => { return { name: value[0], columnA: value[1], columnB: value[2], columnC: parseInt(value[3]), columnD: value[4] }; }); // Target シートの取得 const targetSheet = ss.getSheetByName('Target'); // 検索条件となる ColumnD の値の取得 const conditionColumnD = targetSheet.getRange(TARGET_COLUMN + '2').getValue(); // 処理結果を格納するための配列 let resultsArray = []; // Target に指定した範囲をセルごとに処理 for (let i = TARGET_START_ROW_NUM; i <= TARGET_END_ROW_NUM; i++) { // 検索条件のカラムの値を取得 const conditionColumnA = targetSheet.getRange('B' + i).getValue(); const conditionColumnB = targetSheet.getRange('C' + i).getValue(); const conditionColumnC = parseInt(targetSheet.getRange('D' + i).getValue()); // 検索結果 const results = sourceDataList.filter(value => value.columnA === conditionColumnA && value.columnB === conditionColumnB && value.columnC === conditionColumnC && value.columnD === conditionColumnD ); if (results.length > 0) { // 条件に合う結果がある場合は、先頭のデータの name を配列に追加 resultsArray.push([results[0].name]); } else { // 条件に合う結果がない場合は '-' を配列に追加 resultsArray.push(['-']); } } // 複数のセルに一括で値をセット targetSheet.getRange(TARGET_COLUMN + TARGET_START_ROW_NUM + ':' + TARGET_COLUMN + TARGET_END_ROW_NUM).setValues(resultsArray); }
複数の列に一括でセットする
前述までのコードでは一度の実行で処理できる列数が 1 列のみでしが、複数の列を一括で処理できるようにしました。行と列それぞれに対して For ループを行うことで複数列に対する処理を実現しています。
const SOURCE_START_CELL = 'B3'; const SOURCE_END_CELL = 'F52'; const TARGET_START_ROW_NUM = 3; const TARGET_END_ROW_NUM = 29; const TARGET_START_COLUMN = 'E'; const TARGET_END_COLUMN = 'N'; function myFunction() { // スプレッドシートを取得 const ss = SpreadsheetApp.getActiveSpreadsheet(); // Source シートの取得 const sourceSheet = ss.getSheetByName('Source'); // Source データのセル範囲の取得 const sourceRange = sourceSheet.getRange(SOURCE_START_CELL + ':' + SOURCE_END_CELL); // Source データの二次元の値データの取得 const sourceValues = sourceRange.getValues(); // Source データの Json 形式のリストの取得 const sourceDataList = sourceValues.map(value => { return { name: value[0], columnA: value[1], columnB: value[2], columnC: parseInt(value[3]), columnD: value[4] }; }); // Target シートの取得 const targetSheet = ss.getSheetByName('Target'); // 処理結果を格納するための配列 let resultsArray = []; // Target に指定した範囲をセルごとに処理 for (let i = TARGET_START_ROW_NUM; i <= TARGET_END_ROW_NUM; i++) { let rowResults = []; for (let j = TARGET_START_COLUMN.charCodeAt(0); j <= TARGET_END_COLUMN.charCodeAt(0); j++) { // 検索条件のカラムの値を取得 const conditionColumnA = targetSheet.getRange('B' + i).getValue(); const conditionColumnB = targetSheet.getRange('C' + i).getValue(); const conditionColumnC = parseInt(targetSheet.getRange('D' + i).getValue()); const conditionColumnD = targetSheet.getRange(String.fromCharCode(j) + '2').getValue(); // 検索結果 const results = sourceDataList.filter(value => value.columnA === conditionColumnA && value.columnB === conditionColumnB && value.columnC === conditionColumnC && value.columnD === conditionColumnD ); if (results.length > 0) { // 条件に合う結果がある場合は、先頭のデータの name を配列に追加 rowResults.push([results[0].name]); } else { // 条件に合う結果がない場合は '-' を配列に追加 rowResults.push(['-']); } } resultsArray.push(rowResults); } // 複数のセルに一括で値をセット targetSheet.getRange(TARGET_START_COLUMN + TARGET_START_ROW_NUM + ':' + TARGET_END_COLUMN + TARGET_END_ROW_NUM).setValues(resultsArray); }
コードを実行すると、コード冒頭で指定した E3
から E29
の範囲のセルに値がセットされました。
AA 列以降も対応可能とする
前述までのコードには、AA 列以降は処理できないという問題点があります。
例えば、次のように AA 列以降の列を処理対象に指定してみます。
const SOURCE_START_CELL = 'B3'; const SOURCE_END_CELL = 'F52'; const TARGET_START_ROW_NUM = 3; const TARGET_END_ROW_NUM = 29; const TARGET_START_COLUMN = 'Y'; const TARGET_END_COLUMN = 'AB';
すると次のようなエラーが発生します。
Exception: The number of columns in the data does not match the number of columns in the range. The data has 0 but the range has 4.
myFunction @ Code.gs:68
これは AA
列以降は 2 文字の組み合わせとなるため、 charCodeAt
および fromCharCode
で想定通りの値が取得できず、setValues
で設定したいデータのレンジと設定対象のレンジが一致しないためです。
そこで次のようにカラムを番号から文字列へ変換する columnToLetter
および文字列から番号へ変換する letterToColumn
を実装します。
const SOURCE_START_CELL = 'B3'; const SOURCE_END_CELL = 'F52'; const TARGET_START_ROW_NUM = 3; const TARGET_END_ROW_NUM = 29; const TARGET_START_COLUMN = 'Y'; const TARGET_END_COLUMN = 'AB'; // カラムを番号から文字列へ変換 function columnToLetter(column) { let temp, letter = ''; while (column > 0) { temp = (column - 1) % 26; letter = String.fromCharCode(temp + 65) + letter; column = (column - temp - 1) / 26; } return letter; } // カラムを文字列から番号へ変換 function letterToColumn(letter) { let column = 0, length = letter.length; for (var i = 0; i < length; i++) { column += (letter.charCodeAt(i) - 64) * Math.pow(26, length - i - 1); } return column; } function myFunction() { // スプレッドシートを取得 const ss = SpreadsheetApp.getActiveSpreadsheet(); // Source シートの取得 const sourceSheet = ss.getSheetByName('Source'); // Source データのセル範囲の取得 const sourceRange = sourceSheet.getRange(SOURCE_START_CELL + ':' + SOURCE_END_CELL); // Source データの二次元の値データの取得 const sourceValues = sourceRange.getValues(); // Source データの Json 形式のリストの取得 const sourceDataList = sourceValues.map(value => { return { name: value[0], columnA: value[1], columnB: value[2], columnC: parseInt(value[3]), columnD: value[4] }; }); // Target シートの取得 const targetSheet = ss.getSheetByName('Target'); // 処理結果を格納するための配列 let resultsArray = []; // Target に指定した範囲をセルごとに処理 for (let i = TARGET_START_ROW_NUM; i <= TARGET_END_ROW_NUM; i++) { let rowResults = []; for (let j = letterToColumn(TARGET_START_COLUMN); j <= letterToColumn(TARGET_END_COLUMN); j++) { // 検索条件のカラムの値を取得 const conditionColumnA = targetSheet.getRange('B' + i).getValue(); const conditionColumnB = targetSheet.getRange('C' + i).getValue(); const conditionColumnC = parseInt(targetSheet.getRange('D' + i).getValue()); const conditionColumnD = targetSheet.getRange(columnToLetter(j) + '2').getValue(); // 検索結果 const results = sourceDataList.filter(value => value.columnA === conditionColumnA && value.columnB === conditionColumnB && value.columnC === conditionColumnC && value.columnD === conditionColumnD ); if (results.length > 0) { // 条件に合う結果がある場合は、先頭のデータの name を配列に追加 rowResults.push([results[0].name]); } else { // 条件に合う結果がない場合は '-' を配列に追加 rowResults.push(['-']); } } resultsArray.push(rowResults); } // 複数のセルに一括で値をセット targetSheet.getRange(TARGET_START_COLUMN + TARGET_START_ROW_NUM + ':' + TARGET_END_COLUMN + TARGET_END_ROW_NUM).setValues(resultsArray); }
すると次のように Y3
から AB29
の範囲のセルに値がセットされました。
おわりに
Google Apps Script で複数条件でのテーブルデータの検索(VLOOKUP)を多次元テーブルに対して行う方法をご紹介しました。
Google スプレッドシートのデータでよく行うパターンの処理だと思うので、ぜひ参考にしてみてください。
参考
- スプレッドシートの複数条件VLOOKUP関数のGAS化 – TD SYNNEX Blog
- GASでのスプレッドシートの範囲選択について – TD SYNNEX Blog
- 【GAS】スプレッドシートで列指定で同じ行の更新日時を取得する方法 | Log Stocker
- GAS Rangeを制するものはスプシを #JavaScript - Qiita
以上