在 Google 表格列中找到第一个空行的更快方法

Posted

技术标签:

【中文标题】在 Google 表格列中找到第一个空行的更快方法【英文标题】:Faster way to find the first empty row in a Google Sheet column 【发布时间】:2011-10-16 11:05:38 【问题描述】:

我编写了一个脚本,每隔几个小时就会在 Google Apps 电子表格中添加一个新行。

这是我用来查找第一个空行的函数:

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

它工作正常,但是当达到大约 100 行时,它变得非常慢,甚至十秒。 我担心当达到数千行时,它会太慢,可能会超时或更糟。 有没有更好的办法?

【问题讨论】:

【参考方案1】:

这个问题的浏览量现已超过 12K - 所以是时候更新了,因为新表格的性能特征与 Serge ran his initial tests 时不同。

好消息:性能全面提升!

最快:

与第一个测试一样,只读取工作表的数据一次,然后对数组进行操作,可以带来巨大的性能优势。有趣的是,Don 的原始功能比 Serge 测试的修改版本的性能要好得多。 (看来whilefor 快​​,这不合逻辑。)

样本数据的平均执行时间仅为 38ms,低于之前的 168ms

// Don's array approach - checks first column only
// With added stopping condition & correct result.
// From answer https://***.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 列的电子表格中汇总了 50 次迭代(用 Serge 的测试函数填充)。

函数名称与下面脚本中的代码匹配。

"第一个空行"

最初的要求是找到第一个空行。以前的脚本都没有真正实现这一点。许多人只检查一列,这意味着他们可能会给出假阳性结果。其他人只找到所有数据之后的第一行,这意味着不连续数据中的空行会丢失。

这是一个符合规范的函数。它被包含在测试中,虽然比闪电般快速的单列检查器慢,但它以 68 毫秒的速度进入,比正确答案高出 50%!

/**
 * 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://***.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://***.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://***.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://***.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;

【讨论】:

只是一个小评论,提到我的原始代码实际上返回了正确的值,因为我在条件为真时使用了以下代码:n++;break。因此,将 1 添加到 n (在您的修改版本中)使其返回 n+2 ...这有点太多了 ;-)。我承认这可能不是最明显的方法。 @Serge insas 版本运行速度较慢,因为您获取整个范围,而不是像 Don 的解决方案中仅获取一列。修复后,它们的运行速度同样快。 此外,您的 Serge 解决方案版本和他自己的解决方案都是错误的 - 但方式不同:他的解决方案在您的示例设置中不处理 n=1000,而您的解决方案不处理 n=10 (尝试删除 A10)。【参考方案2】:

Google Apps 脚本博客在 optimizing spreadsheet operations 上发表了一篇文章,谈到了可以真正加快速度的批处理读取和写入。我在一个有 100 行的电子表格上尝试了你的代码,它花了大约 7 秒。通过使用Range.getValues(),批处理版本需要一秒钟。

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 行为单位抓取数据,而不是抓取整列。

【讨论】:

只有当您可以保证 A 列中包含空白单元格的行是“空的”时,这才有效。 while 循环也没有停止条件,因此如果 A 列中的每个单元格都已满,它可以(并且确实)抛出异常。 Serge 的适应处理了停止条件。 另外,结果差 1。如果第一个“空”行为 10(电子表格计算),则为 9(数组计算)。 请参阅Mogsdad's answer 以获得更完整的空行检查以及一些性能比较。谢谢,@Mogsdad。【参考方案3】:

它已经作为工作表上的 getLastRow 方法存在。

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

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

【讨论】:

这个问题是,如果 A 列有 10 行,B 列有 100,这将返回 100。要获取列的最后一行,您必须遍历内容(至我知道) 嗯,那为什么不SpreadsheetApp.getActiveSpreadsheet().getRange('A:A').getLastRow() + 1 Argh nvm 我刚刚注意到这给了你真的范围的最后一个单元格 OP 实际上从未要求检查特定的列,因此这绝对是实现他想要的最有效的方法。很遗憾我没有早点看到这个! (虽然我确实从其他帖子中学到了很多东西。) 这是最快的【参考方案4】:

看到这篇具有 5k 浏览量的旧帖子,我首先查看了“最佳答案”,并对其内容感到非常惊讶……这确实是一个非常缓慢的过程!然后看到Don Kirkby的回答我感觉好多了,数组的方法确实效率更高!

但是效率高多少呢?

所以我在一个有 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']);
  

还有the test spreadsheet 自己尝试一下:-)


编辑:

在 Mogsdad 的评论之后,我应该提一下,这些函数名称确实是一个糟糕的选择......它应该像 getLastNonEmptyCellInColumnAWithPlentyOfSpaceBelow() 这样不是很优雅(是吗?)但更准确和与实际内容一致返回。

评论:

无论如何,我的意思是展示这两种方法的执行速度,而且它显然做到了(不是吗?;-)

【讨论】:

感谢您的测试。我刚刚将最佳答案移至 Don Kirkby's。 好主意!谢谢 两个“getFirstEmpty”函数都给出了错误的结果。因为它们向后计数,所以它们用 A 列中的数据标识最后一行。在具有连续数据的工作表中,这将与第一个空行相差一个。如果电子表格前面有空行,则两者都不会找到。 嘿 Mogsdad,你当然没有错......它应该被称为“getLastRow”......我想我使用这个名字是因为其他以前的答案......至于对 A 列的限制,它在 OP 本身中......中间空白单元格?那么,如何处理呢?老实说,我不知道最好的选择,这主要取决于你在寻找什么......列表中的第一个空白或你可以继续的最后一行?无论如何,感谢您的指出,我将添加一个关于函数名称的小编辑;-)【参考方案5】:

我知道这是一个旧线程,这里有一些非常聪明的方法。

我使用脚本

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

如果我需要第一个完全空的行。

如果我需要列中的第一个空单元格,请执行以下操作。

我的第一行通常是标题行。

我的第二行是隐藏行,每个单元格都有公式

=COUNTA(A3:A)

其中A 替换为列字母。

我的脚本只是读取这个值。与脚本方法相比,这种更新速度非常快。

有一次这不起作用,那就是我允许空单元格拆分列。我还不需要解决这个问题,我怀疑一个可能来自COUNTIF,或者一个组合函数或许多其他内置函数之一。

编辑: COUNTA 确实可以处理某个范围内的空白单元格,因此对“一次不起作用”的担忧并不是真正的问题。 (这可能是“新表格”的一种新行为。)

【讨论】:

将最后一行的标识卸载到电子表格公式很聪明! 感谢您的编辑。我认为自从我写了之后,行为已经改变了。【参考方案6】:

为什么不使用appendRow?

var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
spreadsheet.appendRow(['this is in column A', 'column B']);

【讨论】:

为什么不呢?因为它没有回答问题——“第一个空行”尚未确定。而是在电子表格的底部添加了一个新行,即使它上面有“空”行。 这是最好的答案,@Mogsdad 是错误的: sheet.appendRow() 不会在工作表底部追加一行,也不会在其上方留下空行。相反,它完全符合 OP 的要求:它在包含内容的工作表的最后一行之后附加一行。此外,它是原子的,因此同时启动的两个 appendRow() 不会尝试写入相同的“最后”列。底线:这是正确的答案! (至少只要您在写入后不需要最后一行的索引,OP 没有要求。) @Jpsy 你可以从礼貌训练中受益。随意质疑旧答案或问题的有效性 - 毕竟,这些答案来自旧表格产品,事情可能已经改变。但他们没有——而且你没有任何证据支持你的煽动性声明。 This clip clearly demonstrates the problem with appendRow():它没有找到 FIRST 空行。 (最后一行内容之后的第一行,是的 - 但同样,这不是所要求的。) @Mogsdad 嗯,听起来我们都可以享受这样的培训。再次阅读 OP,我想我看到了误解的根源:他/她写了关于搜索“第一个空行”的文章,但清楚地描述了一个用例,其中不存在间歇性空行并且第一个空行将始终低于所有其他内容。所以在某种程度上我们都是正确的。但是对于 OP 的用例,这仍然是正确的答案:他想在内容的末尾添加。 @Jpsy 我不同意 - 他们提供了一个工作代码 sn-p 作为他们问题的一部分。 OP 的代码将停在 A 列中的间歇性空单元格(他们认为这是一个空行),并且他们没有要求更改。如果他们正在寻找一种更快的方法来提供他们已经拥有的相同功能,那么 appendRow() 将会失败。鉴于他们展示了编写代码的努力,假设他们不注意搜索文档以查找替代方法似乎没有必要。如果这是一个新问题,正确的做法是要求澄清。【参考方案7】:

我调整了 ghoti 提供的代码,以便它搜索一个空单元格。比较值不适用于带有文本的列(或者我无法弄清楚如何),而是使用 isBlank()。请注意,该值被 ! (在变量 r 前面)向前看时,因为您希望 i 增加直到找到空白。当您发现一个非空白单元格(!已删除)时,将工作表增加 10 您希望停止减少 i 。然后,将工作表向后退到第一个空白处。

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;

【讨论】:

【参考方案8】:

只有我的两分钱,但我一直这样做。我只是将数据写入工作表的顶部。它的日期颠倒了(最新的在上面),但我仍然可以让它做我想做的事。下面的代码在过去三年中一直在存储从房地产经纪人网站上抓取的数据。

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

刮板函数的这一部分在#2 上方插入一行并将数据写入那里。第一行是标题,所以我不碰它。我没有计时,但我唯一遇到的问题是网站更改时。

【讨论】:

【参考方案9】:

确实,getValues 是一个不错的选择,但您可以使用 .length 函数来获取最后一行。

 function getFirstEmptyRow() 
  var spr = SpreadsheetApp.getActiveSpreadsheet();
  var array = spr.getDataRange().getValues();
  ct = array.length + 1
  return (ct);

【讨论】:

如果数据存在行宽间隙,则不会给出正确的结果。【参考方案10】:

我也有类似的问题。现在它是一个有数百行的表,我预计它会增长到数千行。 (我还没有看到 Google 电子表格是否可以处理数万行,但我最终会做到的。)

这就是我正在做的事情。

    在列中向前走数百,当我在空行时停下来。 在列中后退十位,寻找第一个非空行。 逐列前进,寻找第一个空行。 返回结果。

这当然取决于是否有连续的内容。那里不能有任何随机的空行。或者至少,如果你这样做,结果将是次优的。如果您认为这很重要,您可以调整增量。这些对我有用,我发现 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;

这比从顶部检查每个单元要快得多。如果您碰巧有一些其他列可以扩展您的工作表,那么它也可能比从底部检查每个单元格更快。

【讨论】:

【参考方案11】:

使用 indexOf 是实现此目的的方法之一:

函数 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),[]); // 二维数组缩减为一维// // Array.prototype.push.apply 可能更快,但无法让它工作// var fner = 1+dat.indexOf('');//获取indexOf第一个空行 返回(fner);

【讨论】:

【参考方案12】:

我在我的电子表格中保留了一张额外的“维护”表,用于保存此类数据。

要获得范围的下一个空闲行,我只需检查相关单元格。我可以立即获取值,因为查找值的工作发生在数据更改时。

单元格中的公式通常类似于:

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

A9 中的值可以定期设置为接近“足够”到末尾的某行。

警告:我从未检查过这是否适用于庞大的数据集。

【讨论】:

【参考方案13】:

最后我得到了一个单行解决方案。

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

对我来说很好用。

【讨论】:

如果在最后一个非空单元格之前的任何地方有一个空单元格,这将失败【参考方案14】:

下面是代码应该做什么的列表:

如果没有空单元格,请给出正确答案 要快 返回正确的行号——不是数组的索引号 即使工作表选项卡中的其他列有更多包含数据的行,也可以获取空单元格的正确行号 有好的变量名 回答原问题 避免不必要的数据处理 为代码的作用提供注释说明 足够通用以适应读者的条件

此解决方案使用数组方法some,当条件为真时将停止迭代循环。这样可以避免浪费时间循环遍历数组的每个元素,并且使用数组方法而不是 forwhile 循环。

some 方法只返回 true 或 false,但是有一种方法可以捕获索引号,因为 some 方法会在条件为 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")


【讨论】:

【参考方案15】:

对于特定列,我已经经历了太多最后一行的实现。许多解决方案都有效,但对于大型或多个数据集来说速度很慢。我的一个用例要求我检查多个电子表格中特定列的最后一行。我发现将整个列作为一个范围然后迭代它太慢了,将其中的一些加在一起会使脚本变得迟缓。

我的“hack”是这个公式:

=ROW(index(sheet!A2:A,max(row(sheet!A2:A)*(sheet!A2:A<>""))))-1

示例:将此添加到单元格 A1,以查找 A 列中的最后一行。可以添加到任何地方,只需确保根据放置公式的行来管理末尾的“-1”。您也可以将其放置为另一个列,而不是您要计数的列,并且您不需要管理 -1。您还可以从起始行开始计数,例如“C16:C” - 将从 C16 开始计数值

这个公式可靠地给了我最后一行,包括数据集中间的空白

要在我的 GS 代码中使用这个值,我只是从 A1 中读取单元格值。我知道 Google 很清楚读/写等电子表格功能很繁重(耗时),但根据我的经验(对于大型数据集),这比列计数最后一行方法快得多

为了提高效率,我在 col 中获取最后一行,然后将其保存为全局变量并在我的代码中递增以跟踪我应该更新哪些行。每次循环需要进行更新时读取单元格的效率太低了。读取一次,迭代值,A1 单元格公式(上图)将“存储”更新后的值以供下次函数运行时使用

如果数据已打开过滤器,这也有效。实际最后一行保持不变

如果这对您有帮助,请告诉我!如果我遇到任何问题,我会对此答案发表评论。

【讨论】:

很高兴您想分享您的答案。但不要为不同的问题复制粘贴相同的答案。为每个问题提供具体的答案或将问题标记为重复问题参见meta.***.com/questions/270841/… 啊好吧!抱歉 - 不知道这是不允许的。只是想通过将其发布在相关的线程中来提供帮助。会听从您的建议。【参考方案16】:

这是我在***上的第一篇文章,我希望能满足你所​​有的网络礼仪需求,所以请对我好一点。

注意事项

我认为在列中找到第一个空白单元格的最快方法(无论如何我无法运行性能检查)是让 Google 引擎自己执行顺序任务;它的效率要高得多。从程序员的角度来看,这意味着不使用任何类型的迭代/循环,即 FOR、WHILE 等。(顺便说一下,这与数据库引擎上的编程方法相同 - 任何活动不应使用循环来查找信息。)

想法

    一路DOWN,找到工作表最后一行的单元格(考虑所有列), 从那里,UP找到指定列中包含数据的第一个单元格(选择列), 向下移动一个单元格以找到空闲位置。

以下函数仅在一个命令中执行此操作(忽略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

【讨论】:

以上是关于在 Google 表格列中找到第一个空行的更快方法的主要内容,如果未能解决你的问题,请参考以下文章

获取 Google 表格列中的最后一个非空单元格

在 Google Docs 电子表格的列中计算具有任何值(字符串或数字)的单元格数

VBA从第一个空行到数据结束复制公式

EXCEL表格中用宏命令隐藏有条件的空行?

将外部数据作为静态数据拉到下一个空行,然后重复下一个空行? (将 Google Sheet 数据备份到 Excel)

用于多个选项卡的 Google 表格自动时间戳脚本