58

数時間ごとにGoogleAppsスプレッドシートに新しい行を追加するスクリプトを作成しました。

これは、最初の空の行を見つけるために作成した関数です。

function getFirstEmptyRow() {
  var spr = SpreadsheetApp.getActiveSpreadsheet();
  var cell = spr.getRange('a1');
  var ct = 0;
  while ( cell.offset(ct, 0).getValue() != "" ) {
    ct++;
  }
  return (ct);
}

正常に動作しますが、約100行に達すると、10秒でも非常に遅くなります。数千行に達すると、速度が遅くなり、タイムアウトになるか、さらに悪化する可能性があるのではないかと心配しています。もっと良い方法はありますか?

4

16 に答える 16

65

この質問の表示回数は12,000回を超えています。新しいシートのパフォーマンス特性は、セルジュが最初のテストを実行したときとは異なるため、更新する必要があります。

良いニュース:パフォーマンスは全体的にはるかに優れています!

最速:

最初のテストと同様に、シートのデータを1回だけ読み取ってからアレイを操作すると、パフォーマンスが大幅に向上しました。興味深いことに、Donの元の関数は、Sergeがテストした修正バージョンよりもはるかに優れたパフォーマンスを示しました。(論理的ではない、whileよりも高速であるように見えます。)for

サンプルデータの平均実行時間はわずか38ミリ秒で、以前の168ミリ秒から短縮されています。

// Don's array approach - checks first column only
// With added stopping condition & correct result.
// From answer https://stackoverflow.com/a/9102463/1677912
function getFirstEmptyRowByColumnArray() {
  var spr = SpreadsheetApp.getActiveSpreadsheet();
  var column = spr.getRange('A:A');
  var values = column.getValues(); // get all data in one call
  var ct = 0;
  while ( values[ct] && values[ct][0] != "" ) {
    ct++;
  }
  return (ct+1);
}

試験結果:

これは、100行x 3列のスプレッドシート(​​Sergeのテスト関数で埋められた)に50回以上の反復を要約した結果です。

関数名は、以下のスクリプトのコードと一致します。

スクリーンショット

「最初の空の行」

最初の質問は、最初の空の行を見つけることでした。以前のスクリプトはどれも実際にはそれを実現していません。多くの場合、1つの列だけをチェックします。これは、誤検知の結果をもたらす可能性があることを意味します。他の人は、すべてのデータに続く最初の行のみを見つけます。つまり、連続していないデータの空の行が失われます。

これが仕様を満たしている関数です。これはテストに含まれており、超高速の単一列チェッカーよりも低速でしたが、正解の場合は50%のプレミアムである68msで到達しました。

/**
 * Mogsdad's "whole row" checker.
 */
function getFirstEmptyRowWholeRow() {
  var sheet = SpreadsheetApp.getActiveSheet();
  var range = sheet.getDataRange();
  var values = range.getValues();
  var row = 0;
  for (var row=0; row<values.length; row++) {
    if (!values[row].join("")) break;
  }
  return (row+1);
}

完全なスクリプト:

テストを繰り返したい場合、または比較として独自の関数をミックスに追加したい場合は、スクリプト全体を取得してスプレッドシートで使用してください。

/**
 * Set up a menu option for ease of use.
 */
function onOpen() {
  var menuEntries = [ {name: "Fill sheet", functionName: "fillSheet"},
                      {name: "test getFirstEmptyRow", functionName: "testTime"}
                     ];
  var sh = SpreadsheetApp.getActiveSpreadsheet();
  sh.addMenu("run tests",menuEntries);
}

/**
 * Test an array of functions, timing execution of each over multiple iterations.
 * Produce stats from the collected data, and present in a "Results" sheet.
 */
function testTime() {
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  ss.getSheets()[0].activate();
  var iterations = parseInt(Browser.inputBox("Enter # of iterations, min 2:")) || 2;

  var functions = ["getFirstEmptyRowByOffset", "getFirstEmptyRowByColumnArray", "getFirstEmptyRowByCell","getFirstEmptyRowUsingArray", "getFirstEmptyRowWholeRow"]

  var results = [["Iteration"].concat(functions)];
  for (var i=1; i<=iterations; i++) {
    var row = [i];
    for (var fn=0; fn<functions.length; fn++) {
      var starttime = new Date().getTime();
      eval(functions[fn]+"()");
      var endtime = new Date().getTime();
      row.push(endtime-starttime);
    }
    results.push(row);
  }

  Browser.msgBox('Test complete - see Results sheet');
  var resultSheet = SpreadsheetApp.getActive().getSheetByName("Results");
  if (!resultSheet) {
    resultSheet = SpreadsheetApp.getActive().insertSheet("Results");
  }
  else {
    resultSheet.activate();
    resultSheet.clearContents();
  }
  resultSheet.getRange(1, 1, results.length, results[0].length).setValues(results);

  // Add statistical calculations
  var row = results.length+1;
  var rangeA1 = "B2:B"+results.length;
  resultSheet.getRange(row, 1, 3, 1).setValues([["Avg"],["Stddev"],["Trimmed\nMean"]]);
  var formulas = resultSheet.getRange(row, 2, 3, 1);
  formulas.setFormulas(
    [[ "=AVERAGE("+rangeA1+")" ],
     [ "=STDEV("+rangeA1+")" ],
     [ "=AVERAGEIFS("+rangeA1+","+rangeA1+',"<"&B$'+row+"+3*B$"+(row+1)+","+rangeA1+',">"&B$'+row+"-3*B$"+(row+1)+")" ]]);
  formulas.setNumberFormat("##########.");

  for (var col=3; col<=results[0].length;col++) {
    formulas.copyTo(resultSheet.getRange(row, col))
  }

  // Format for readability
  for (var col=1;col<=results[0].length;col++) {
    resultSheet.autoResizeColumn(col)
  }
}

// Omiod's original function.  Checks first column only
// Modified to give correct result.
// question https://stackoverflow.com/questions/6882104
function getFirstEmptyRowByOffset() {
  var spr = SpreadsheetApp.getActiveSpreadsheet();
  var cell = spr.getRange('a1');
  var ct = 0;
  while ( cell.offset(ct, 0).getValue() != "" ) {
    ct++;
  }
  return (ct+1);
}

// Don's array approach - checks first column only.
// With added stopping condition & correct result.
// From answer https://stackoverflow.com/a/9102463/1677912
function getFirstEmptyRowByColumnArray() {
  var spr = SpreadsheetApp.getActiveSpreadsheet();
  var column = spr.getRange('A:A');
  var values = column.getValues(); // get all data in one call
  var ct = 0;
  while ( values[ct] && values[ct][0] != "" ) {
    ct++;
  }
  return (ct+1);
}

// Serge's getFirstEmptyRow, adapted from Omiod's, but
// using getCell instead of offset. Checks first column only.
// Modified to give correct result.
// From answer https://stackoverflow.com/a/18319032/1677912
function getFirstEmptyRowByCell() {
  var spr = SpreadsheetApp.getActiveSpreadsheet();
  var ran = spr.getRange('A:A');
  var arr = []; 
  for (var i=1; i<=ran.getLastRow(); i++){
    if(!ran.getCell(i,1).getValue()){
      break;
    }
  }
  return i;
}

// Serges's adaptation of Don's array answer.  Checks first column only.
// Modified to give correct result.
// From answer https://stackoverflow.com/a/18319032/1677912
function getFirstEmptyRowUsingArray() {
  var sh = SpreadsheetApp.getActiveSpreadsheet();
  var ss = sh.getActiveSheet();
  var data = ss.getDataRange().getValues();
  for(var n=0; n<data.length ;  n++){
    if(data[n][0]==''){n++;break}
  }
  return n+1;
}

/**
 * Mogsdad's "whole row" checker.
 */
function getFirstEmptyRowWholeRow() {
  var sheet = SpreadsheetApp.getActiveSheet();
  var range = sheet.getDataRange();
  var values = range.getValues();
  var row = 0;
  for (var row=0; row<values.length; row++) {
    if (!values[row].join("")) break;
  }
  return (row+1);
}

function fillSheet(){
  var sh = SpreadsheetApp.getActiveSpreadsheet();
  var ss = sh.getActiveSheet();
  for(var r=1;r<1000;++r){
    ss.appendRow(['filling values',r,'not important']);
  }
}

// Function to test the value returned by each contender.
// Use fillSheet() first, then blank out random rows and
// compare results in debugger.
function compareResults() {
  var a = getFirstEmptyRowByOffset(),
      b = getFirstEmptyRowByColumnArray(),
      c = getFirstEmptyRowByCell(),
      d = getFirstEmptyRowUsingArray(),
      e = getFirstEmptyRowWholeRow(),
      f = getFirstEmptyRowWholeRow2();
  debugger;
}
于 2014-11-27T22:46:43.760 に答える
55

Google Apps Scriptブログには、スプレッドシート操作の最適化に関する投稿があり、読み取りと書き込みのバッチ処理について説明しました。これにより、処理速度が大幅に向上します。100行のスプレッドシートでコードを試しましたが、約7秒かかりました。を使用するRange.getValues()と、バッチバージョンに1秒かかります。

function getFirstEmptyRow() {
  var spr = SpreadsheetApp.getActiveSpreadsheet();
  var column = spr.getRange('A:A');
  var values = column.getValues(); // get all data in one call
  var ct = 0;
  while ( values[ct][0] != "" ) {
    ct++;
  }
  return (ct);
}

スプレッドシートが十分に大きくなった場合、列全体を取得するのではなく、100行または1000行のチャンクでデータを取得する必要がある場合があります。

于 2012-02-01T20:04:44.000 に答える
36

シートのgetLastRowメソッドとしてすでに存在しています。

var firstEmptyRow = SpreadsheetApp.getActiveSpreadsheet().getLastRow() + 1;

参照https://developers.google.com/apps-script/class_sheet#getLastRow

于 2012-05-12T08:29:53.977 に答える
8

5kビューのこの古い投稿を見て、私は最初に「ベストアンサー」をチェックし、その内容に非常に驚いていました...これは確かに非常に遅いプロセスでした!それから、ドン・カークビーの答えを見たとき、私は気分が良くなりました。配列アプローチは確かにはるかに効率的です!

しかし、どれだけ効率的ですか?

だから私は1000行のスプレッドシートにこの小さなテストコードを書きました、そしてここに結果があります:(悪くはありません!...どれがどれであるかを言う必要はありません...)

ここに画像の説明を入力してください ここに画像の説明を入力してください

これが私が使ったコードです:

function onOpen() {
  var menuEntries = [ {name: "test method 1", functionName: "getFirstEmptyRow"},
                      {name: "test method 2 (array)", functionName: "getFirstEmptyRowUsingArray"}
                     ];
  var sh = SpreadsheetApp.getActiveSpreadsheet();
  sh.addMenu("run tests",menuEntries);
}

function getFirstEmptyRow() {
  var time = new Date().getTime();
  var spr = SpreadsheetApp.getActiveSpreadsheet();
  var ran = spr.getRange('A:A');
  for (var i= ran.getLastRow(); i>0; i--){
    if(ran.getCell(i,1).getValue()){
      break;
    }
  }
  Browser.msgBox('lastRow = '+Number(i+1)+'  duration = '+Number(new Date().getTime()-time)+' mS');
}

function getFirstEmptyRowUsingArray() {
  var time = new Date().getTime();
  var sh = SpreadsheetApp.getActiveSpreadsheet();
  var ss = sh.getActiveSheet();
  var data = ss.getDataRange().getValues();
  for(var n =data.length ; n<0 ;  n--){
    if(data[n][0]!=''){n++;break}
  }
  Browser.msgBox('lastRow = '+n+'  duration = '+Number(new Date().getTime()-time)+' mS');
}

function fillSheet(){
  var sh = SpreadsheetApp.getActiveSpreadsheet();
  var ss = sh.getActiveSheet();
  for(var r=1;r<1000;++r){
    ss.appendRow(['filling values',r,'not important']);
  }
}

そして、自分で試してみるためのテストスプレッドシート:-)


編集 :

Mogsdadのコメントに続いて、これらの関数名は確かに悪い選択であることに言及する必要があります...それはgetLastNonEmptyCellInColumnAWithPlentyOfSpaceBelow()あまりエレガントではありませんが(そうですか?)、実際に返されるものとより正確で一貫性のあるものでなければなりません。

コメント :

とにかく、私のポイントは両方のアプローチの実行速度を示すことでした、そしてそれは明らかにそれをしました(そうではありませんでしたか?;-)

于 2013-08-19T16:51:30.800 に答える
8

これは古いスレッドであり、ここには非常に巧妙なアプローチがいくつかあります。

スクリプトを使用します

var firstEmptyRow = SpreadsheetApp.getActiveSpreadsheet().getLastRow() + 1;

最初の完全に空の行が必要な場合。

列の最初の空のセルが必要な場合は、次のようにします。

  • 私の最初の行は通常、タイトル行です。
  • 2行目は非表示の行で、各セルには数式があります

    =COUNTA(A3:A)
    

    ここAで、は列文字に置き換えられます。

  • 私のスクリプトはこの値を読み取るだけです。これは、スクリプトアプローチと比較して非常に迅速に更新されます。

これが機能しない場合があります。それは、空のセルで列を分割できるようにする場合です。私はまだこれを修正する必要はありません。1つは、から派生しCOUNTIFたものか、組み合わせた関数か、他の多くの組み込み関数の1つである可能性があります。

編集: COUNTA範囲内の空白セルを処理するので、「一度これが機能しない」という懸念は実際には問題ではありません。(これは、「新しいシート」の新しい動作である可能性があります。)

于 2014-08-09T17:02:03.863 に答える
5

そして、なぜappendRowを使用しないのですか?

var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
spreadsheet.appendRow(['this is in column A', 'column B']);
于 2013-04-03T18:07:40.493 に答える
2

空のセルを検索するように、提供されたコードghotiを微調整しました。値の比較は、テキストのある列では機能しませんでした(または、その方法がわかりませんでした)。代わりに、isBlank()を使用しました。値が!で否定されていることに注意してください。(変数rの前)空白が見つかるまでiを増やしたいので、楽しみにしています。シートを10ずつ処理すると、空白ではない(!削除された)セルが見つかったときにiの減少を停止する必要があります。次に、シートを1つずつ最初の空白に戻します。

function findRow_() {
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  ss.setActiveSheet(ss.getSheetByName("DAT Tracking"));
  var r = ss.getRange('C:C');
  // Step forwards by hundreds
  for (var i = 2; !r.getCell(i,1).isBlank(); i += 100) { }
  // Step backwards by tens
  for ( ; r.getCell(i,1).isBlank(); i -= 10) { }
  // Step forwards by ones
  for ( ; !r.getCell(i,1).isBlank(); i++) { }
  return i;
于 2016-01-27T20:30:03.787 に答える
2

ちょうど私の2セントですが、私はいつもこれをします。シートの一番上にデータを書き込むだけです。日付が逆になっています(一番上が遅い)が、それでも自分のやりたいことを実行させることができます。以下のコードは、過去3年間、不動産業者のサイトから取得したデータを保存しています。

var theSheet = SpreadsheetApp.openById(zSheetId).getSheetByName('Sheet1');
theSheet.insertRowBefore(1).getRange("A2:L2").setValues( [ zPriceData ] );

スクレーパー関数のこのチャンクは、#2の上に行を挿入し、そこにデータを書き込みます。最初の行はヘッダーなので、触れません。時間を計っていませんが、問題が発生するのはサイトが変更されたときだけです。

于 2017-07-15T02:56:33.323 に答える
1

確かにgetValuesは良いオプションですが、.length関数を使用して最後の行を取得できます。

 function getFirstEmptyRow() {
  var spr = SpreadsheetApp.getActiveSpreadsheet();
  var array = spr.getDataRange().getValues();
  ct = array.length + 1
  return (ct);
}
于 2012-05-11T20:07:01.393 に答える
1

同様の問題があります。現在、これは数百行のテーブルであり、数千行に成長すると予想しています。(Googleスプレッドシートが数万行を処理するかどうかはわかりませんが、最終的にはそこに到達します。)

これが私がしていることです。

  1. 列を何百も前に進み、空の行にいるときに停止します。
  2. 最初の空でない行を探して、列を数十ずつ後方に移動します。
  3. 最初の空の行を探して、列を1つずつ進めます。
  4. 結果を返します。

もちろん、これは連続したコンテンツがあるかどうかに依存します。そこにランダムな空白行を含めることはできません。または、少なくとも、そうすると、結果は最適ではなくなります。また、重要だと思われる場合は、増分を調整できます。これらは私にとってはうまくいき、50のステップと100のステップの間の持続時間の違いはごくわずかであることがわかりました。

function lastValueRow() {
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var r = ss.getRange('A1:A');
  // Step forwards by hundreds
  for (var i = 0; r.getCell(i,1).getValue() > 1; i += 100) { }
  // Step backwards by tens
  for ( ; r.getCell(i,1).getValue() > 1; i -= 10) { }
  // Step forwards by ones
  for ( ; r.getCell(i,1).getValue() == 0; i--) { }
  return i;
}

これは、すべてのセルを上から検査するよりもはるかに高速です。また、ワークシートを拡張する他の列がある場合は、すべてのセルを下から検査するよりも高速な場合があります。

于 2013-08-19T14:44:01.970 に答える
1

indexOfの使用は、これを実現する方法の1つです。

関数firstEmptyRow(){
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var sh = ss.getActiveSheet();
  var rangevalues = sh.getRange(1,1、sh.getLastRow()、1).getValues(); //列A:Aが取得されます
  var dat = rangevalues.reduce(function(a、b){return a.concat(b)}、[]); //
 2D配列は1D//に縮小されます
  // Array.prototype.push.applyは高速かもしれませんが、動作させることができません//
  var fner = 1 + dat.indexOf('');//indexOfを取得最初の空の行
  return(fner);
  }
于 2018-02-09T18:07:33.567 に答える
0

スプレッドシートには、そのようなデータを保持する追加の「メンテナンス」シートを保持しています。

範囲の次の空き行を取得するには、関連するセルを調べます。データが変更されたときに値を見つける作業が行われるため、値を即座に取得できます。

セル内の数式は通常、次のようなものです。

=QUERY(someSheet!A10:H5010, 
    "select min(A) where A > " & A9 & " and B is null and D is null and H < 1")

A9の値は、最後まで「十分」に近い行に定期的に設定できます。

警告:これが巨大なデータセットに対して実行可能かどうかを確認したことはありません。

于 2012-09-28T11:54:38.343 に答える
0

ついに私はそれのための単一行の解決策を得ました。

var sheet = SpreadsheetApp.getActiveSpreadsheet();
var lastEmptyOnColumnB = sheet.getRange("B1:B"+sheet.getLastRow()).getValues().join(",").replace(/,,/g, '').split(",").length;

それは私にとってはうまくいきます。

于 2013-09-13T18:08:42.047 に答える
0

コードが実行する必要があることのリストは次のとおりです。

  • 空のセルがない場合は正解してください
  • 早くして
  • 配列のインデックス番号ではなく、正しい行番号を返します
  • シートタブの他の列にデータを含む行が多い場合でも、空のセルの正しい行番号を取得します
  • 良い変数名を持っている
  • 元の質問に答える
  • 不要なデータ処理を避ける
  • コードの機能についてコメントの説明を提供する
  • 読者の条件に適応するのに十分なジェネリックである

someこのソリューションは、条件が真の場合にループの反復を停止する配列メソッドを使用します。これにより、配列のすべての要素をループするために費やされる時間を無駄にすることなく、forまたはwhileループではなく配列メソッドを使用します。

このsomeメソッドはtrueまたはfalseのみを返しますが、条件がtrueの場合に一部のメソッドがループを停止するため、インデックス番号を取得する方法があります。

インデックス番号は、配列関数の外部のスコープ内の変数に割り当てられます。これにより、処理が遅くなることはありません。

コード:

function getFirstEmptyCellIn_A_Column(po) {
  var foundEmptyCell,rng,sh,ss,values,x;

  /*
    po.sheetTabName - The name of the sheet tab to get
    po.ssID - the file ID of the spreadsheet
    po.getActive - boolean - true - get the active spreadsheet - 
  */

  /*  Ive tested the code for speed using many different ways to do this and using array.some
    is the fastest way - when array.some finds the first true statement it stops iterating -
  */

  if (po.getActive || ! po.ssID) {
    ss =  SpreadsheetApp.getActiveSpreadsheet();
  } else {
    ss = SpreadsheetApp.openById(po.ssID);
  }

  sh = ss.getSheetByName(po.sheetTabName);
  rng = sh.getRange('A:A');//This is the fastest - Its faster than getting the last row and getting a
  //specific range that goes only to the last row

  values = rng.getValues(); // get all the data in the column - This is a 2D array

  x = 0;//Set counter to zero - this is outside of the scope of the array function but still accessible to it

  foundEmptyCell = values.some(function(e,i){
    //Logger.log(i)
    //Logger.log(e[0])
    //Logger.log(e[0] == "")

    x = i;//Set the value every time - its faster than first testing for a reason to set the value
    return e[0] == "";//The first time that this is true it stops looping
  });

  //Logger.log('x + 1: ' + (x + 1))//x is the index of the value in the array - which is one less than the row number
  //Logger.log('foundEmptyCell: ' + foundEmptyCell)

  return foundEmptyCell ? x + 1 : false;
}

function testMycode() {

  getFirstEmptyCellIn_A_Column({"sheetTabName":"Put Sheet tab name here","ssID":"Put your ss file ID here"})

}
于 2020-05-29T01:41:49.550 に答える
0

私は、特定の列の最後の行のこれらの実装の多くを経験しました。多くのソリューションは機能しますが、大規模または複数のデータセットでは低速です。私のユースケースの1つでは、複数のスプレッドシートの特定の列の最後の行を確認する必要があります。私が見つけたのは、列全体を範囲として取得し、それを反復処理するのは遅すぎることです。これらのいくつかを一緒に追加すると、スクリプトが遅くなります。

私の「ハック」は次の式です。

=ROW(index(sheet!A2:A,max(row(sheet!A2:A)*(sheet!A2:A<>""))))-1
  • 例:これをセルA1に追加して、列Aの最後の行を検索します。どこにでも追加できます。数式を配置する行に応じて、最後に「-1」を付けてください。カウントしようとしている列ではなく、これを別の列に配置することもできます。-1を管理する必要はありません。「C16:C」のように、開始行からカウントすることもできます-値C16以降をカウントします

  • この数式により、データセットの中央にある空白を含め、最後の行が確実に得られます。

  • GSコードでこの値を使用するには、A1からセル値を読み取るだけです。Googleは、読み取り/書き込みなどのスプレッドシート関数が重い(時間がかかる)ことを明確に理解していますが、これは私の経験では列数の最後の行のメソッドよりもはるかに高速です(大規模なデータセットの場合)

  • これを効率的にするために、列の最後の行を1回取得し、それをグローバル変数として保存し、コードをインクリメントして、更新する必要のある行を追跡します。ループが更新を行う必要があるたびにセルを読み取るのは非効率的です。一度読み取り、値を繰り返すと、A1セルの数式(上記)は、関数が次に実行されるときに更新された値を「保存」します。

  • これは、データでフィルターがオンになっている場合にも機能します。実際の最後の行は維持されます

これがお役に立てば幸いです。問題が発生した場合は、この回答についてコメントします。

于 2020-09-29T20:22:02.207 に答える
0

これはstackOverflowに関する私の最初の投稿です。ネチケットのニーズをすべて満たしたいと思っていますので、よろしくお願いします。

考慮事項

列の最初の空白セルを見つける最も速い方法(とにかくパフォーマンスチェックを実行できませんでした)は、Googleエンジン自体に順次タスクを実行させることだと思います。それは単にはるかに効率的です。プログラマーの観点からは、これは、FOR、WHILEなどのあらゆる種類の反復/ループを使用しないことを意味します(ちなみに、これはデータベースエンジンでの同じプログラミングアプローチです-どのアクティビティもループを使用して情報を見つけるべきではありません。)

アイデア

  1. 一番下に移動して、シートの最後の行(すべての列を考慮)のセルを見つけます。
  2. そこから、UPに移動して、指定した列のデータを含む最初のセルを見つけます(列を選択します)。
  3. 1つのセルを下にシフトして、空き領域を見つけます。

次の関数は、これを1つのコマンドで実行します(var宣言を無視します。ここでは読みやすさを向上させるためです)。

コード

function lastCell() {    
  var workSheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
  var lastRow = workSheet.getLastRow();
  var columnToSearch = 1; //index of the column to search. 1 is 'A'.

  workSheet.getRange(lastRow, columnToSearch).activateAsCurrentCell().
    getNextDataCell(SpreadsheetApp.Direction.UP).activate();
  workSheet.getCurrentCell().offset(1, 0).activate(); // shift one cell down to find a free cell
}
于 2021-06-16T21:23:19.937 に答える