确定单列中的最后一行

106

我的表格中有数据分布在从 A 列到 H 列。

我需要确定在列 A 中包含数据的最后一行(数据是连续的 - 没有数据/行之间的空隙)。

其他列中也有数据,这些数据比列 A 的行数要多,所以我需要仅隔离列 A 中的数据。(和/或只是列 A 中的某个范围)。

我可以在电子表格级别上使用

=COUNTA(A2:A100)
然而,在我为Google应用脚本解决方案进行的所有研究中,我似乎只能找到执行多个函数并涵盖数十行代码的要求 - 包括大量的“i ++”内容...我可以通过直接从“A1”进行偏移来更简单地完成此操作。
可能有一种特定于列的修改此方法的方式吗?
var aLast = ss.getDataRange().getNumRows();

如果需要复杂的过程,那就用复杂的过程。但我很难想象(更难找到!)一个更简单的解决方案。

有没有人愿意启发我一下(或者打破我的幻想)?

30个回答

220

要不尝试使用JavaScript技巧?

var Avals = ss.getRange("A1:A").getValues();
var Alast = Avals.filter(String).length;

我从这个答案中借鉴了这个想法。 Array.filter()方法是在Avals数组上操作的,该数组包含列A中的所有单元格。通过使用本地函数的构造函数进行过滤,我们仅返回非空元素。

这仅适用于单个列; 如果范围包含多个列,则filter()的结果将包括来自所有列的单元格,因此超出范围的填充维度。


2
需要指出的是,对于更有经验的程序员来说,几乎显而易见的一点是,范围不能跨越多个列。这是我没有完全理解的一点,但Mogsdad在评论中指出(已被删除)它不能跨越多个列。再次强调,我只是想为那些和我一样经验不足,仍然难以掌握某些概念的人提供帮助。谢谢,Mogsdad。 - Soundfx4
8
@5th4x4 从一个 JavaScript 新手的角度来看,我有一个澄清问题:这不会返回一行中非空单元格的数量,而是如果上面的单元格中间有空隙,它不一定会返回最后一个有内容的单元格的行号吗? 因此,可以使用类似于这个while 循环 来修剪数组末尾的 null 元素。 - Scotosaurus
1
@Scotosaurus 没错。但是,问题特别提到数据没有任何空隙:“所有数据/行都是连续的 - 没有间隙”,因此答案是正确的,但对于存在间隙的数据将无法运行。 - dnlmzw
2
我无法理解,var Alast = Avals.filter(String).length; 变量中的 String 是什么意思? - Avazbek Ismanov
1
这个不起作用。我通过艰难的方式发现它返回单元格的数量,无论它们是否有内容。下面@Mrityunjay pandey提供的解决方案是有效的。 - maxhugen
显示剩余3条评论

17

假设按列A排序,这将获取工作表中的最后一行。

function getLastDataRow(sheet) {
  var lastRow = sheet.getLastRow();
  var range = sheet.getRange("A" + lastRow);
  if (range.getValue() !== "") {
    return lastRow;
  } else {
    return range.getNextDataCell(SpreadsheetApp.Direction.UP).getRow();
  }              
}

这修正了@mrityunjay-pandey部分正确的答案。

要获取最后一行和最后一列,我们可以使用:

function columnToLetter(column) {
  var 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) {
  var 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 getLastDataColumn(sheet) {
  var lastCol = sheet.getLastColumn();
  var range = sheet.getRange(columnToLetter(lastCol) + "1");
  if (range.getValue() !== "") {
    return lastCol;
  } else {
    return range.getNextDataCell(SpreadsheetApp.Direction.PREVIOUS).getColumn();
  }              
}

function getLastDataRow(sheet) {
  var lastRow = sheet.getLastRow();
  var range = sheet.getRange("A" + lastRow);
  if (range.getValue() !== "") {
    return lastRow;
  } else {
    return range.getNextDataCell(SpreadsheetApp.Direction.UP).getRow();
  }              
}

function run() {
  var sheet = SpreadsheetApp.getActiveSheet();
  var [startRow, lastRow] = [2, getLastDataRow(sheet)];
  var [startCol, lastCol] = [1, getLastDataColumn(sheet)];
}

1
根据您的看法,为什么@mrityunjay-pandey的答案只是部分正确? - TheMaster
我之前一直在使用这个解决方案,直到我开始进行筛选,结果导致应用程序出现了故障。因此,如果你有筛选器或用户可以隐藏行的需求,我不建议使用这个解决方案。请参考https://stackoverflow.com/a/64441930/452587。 - undefined

15

更新2021 - 还考虑了空单元格

接受的答案以及大多数答案(如果不是全部)都有一个共同的限制,这可能不适用于问题的所有者(他们具有连续数据),但对于未来的读者而言,这是一个限制。

  • 也就是说,如果所选列中间包含空单元格,则接受的答案将给出错误的结果。

例如,考虑这个非常简单的场景:

enter image description here

接受的解决方案将给出4,而正确答案是6

解决方案:

使用reverse方法从数组末尾开始查找第一个非空值的index

const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName('Sheet1')
const lrow = sh.getLastRow();
const Avals = sh.getRange("A1:A"+lrow).getValues();
const Alast  = lrow - Avals.reverse().findIndex(c=>c[0]!='');

喜欢这个产品的简洁和稳定性。 - undefined

13

虽然没有简单的公式,但我可以想到的是,并不需要几十行代码就可以找到A列中的最后一行。尝试使用这个简单的函数,在单元格中像使用其他函数一样使用它。=CountColA()

function CountColA(){
  var sheet = SpreadsheetApp.getActiveSheet();
  var data = sheet.getDataRange().getValues();
  for(var i = data.length-1 ; i >=0 ; i--){
    if (data[i][0] != null && data[i][0] != ''){
      return i+1 ;
    }
  }
}

以上代码返回整个工作表的最后一行,而不是A列的最后一行。我只需要以变量形式获取A列的最后一行,以在脚本内部使用。由于没有什么神奇的方法可以实现这一点,我想我只能从A1开始循环,直到找到(空)宝藏为止。无论如何,还是谢谢。 - 5th4x4
1
抱歉,这是我的小错误。请查看已更正的脚本。 - Srik
我就是理解不了这行代码 "for(var i = data.length-1 ; i >=0 ; i--){ "。我试过将 i>=2 改成 i>=5,即使我的数据列范围只有6个项目,但我仍然从函数中得到返回值6。在 return 语句之前,我使用了 Logger.log() 来记录返回值。为什么结果还是一样的呢? - k.b
这意味着从数据集的末尾开始向下工作,直到i小于零。因此,如果您的最后一行在第6行,您将始终获得相同的结果——除非您将其更改为“<= 7”。 - Nate-Wilkins

11
var Direction=SpreadsheetApp.Direction;
var aLast =ss.getRange("A"+(ss.getLastRow()+1)).getNextDataCell(Direction.UP).getRow();

正如lopezvit所提到的那样, 根据文档,getNextDataCell类似于进入给定范围并按下Ctrl(Command)+箭头键。因此,这个函数之所以有效,是因为它到达了可能的最后一行,再加上一(向下箭头),然后按下ctrl + UP,这样就一定能够获取到具有某些内容的最后一行。唯一可以改进的就是检查最后一个单元格+1是否大于最大单元格,并对该情况进行特殊处理。


1
根据文档,getNextDataCell类似于进入给定范围并按下ctrl(command)+箭头键。因此,它之所以有效是因为它进入到最后一行,再加上一(向下箭头),然后按下ctrl + UP,这样就一定可以获取到具有某些内容的最后一行。唯一需要改进的是检查最后一个单元格+1是否大于最大单元格,并针对该情况制定特定的逻辑。 - lopezvit
2
如果getLastRow() == getMaxRows(),则会失败。 - maxhugen

9

虽然我不知道这是不是一个好的方法,但介绍一下这种方法吧。这个方法没有使用循环。请将其视为样本之一。

  1. 获取您想要知道最后一行号的列数据。
  2. 将该列数据导入到新的电子表格中作为临时表(在这种情况下,您也可以向您当前正在使用的电子表格添加新表,它可用作临时表)。
  3. 使用 getLastRow() 获取最后一行号。
  4. 删除临时电子表格。

示例脚本:

var col = ##; // Here, please input a column number.
var ss = SpreadsheetApp.getActiveSheet();
var coldata = ss.getRange(1, col, ss.getLastRow(), 1).getValues();
var tempss = SpreadsheetApp.create("temporary_sheet");
tempss.getActiveSheet().getRange(1,1,coldata.length, coldata[0].length).setValues(coldata);
var last_row = tempss.getLastRow(); // The number of last row
Drive.Files.remove(tempss.getId()); // In this case, the file is removed using Drive API.

注意:

在上述情况下,即使列中有空单元格,也可以检索到最后一行的编号。如果该列没有空单元格,则可以通过以下脚本检索特定列的最后一行编号。这不会创建临时表。

var last_row = ss.getRange(1, col, ss.getLastRow(), 1).getValues().filter(String).length;

更新于2021年5月19日:

在这种情况下,我想采用以下两种模式。

  1. 通过从表格顶部搜索来检索特定列的第一个空单元格

  2. 通过从表格底部搜索来检索特定列的第一个非空单元格

为了实现上述目标,我认为可以使用以下两种模式。

  1. 检索该列的值,并使用循环搜索结果。

  2. 直接使用Google Apps Script的内置方法检索结果。

我测量了它们的处理成本。结果发现,以下两个脚本是所有方法中最低的。

1. 通过从表格顶部搜索来检索特定列的第一个空单元格

Object.prototype.get1stEmptyRowFromTop = function (columnNumber, offsetRow = 1) {
  const range = this.getRange(offsetRow, columnNumber, 2);
  const values = range.getDisplayValues();
  if (values[0][0] && values[1][0]) {
    return range.getNextDataCell(SpreadsheetApp.Direction.DOWN).getRow() + 1;
  } else if (values[0][0] && !values[1][0]) {
    return offsetRow + 1;
  }
  return offsetRow;
};

// Please run this function.
function main() {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet1");
  const res = sheet.get1stEmptyRowFromTop(3);
  console.log(res); // Retrieve the 1st empty row of column "C" by searching from TOP of sheet.
}
  • 请提供表格对象和列号。
  • 在此脚本中,当使用第二个参数时,可以设置偏移行。例如,当第1行和第2行是标题行时,可以使用此脚本const res = sheet.get1stEmptyRowFromTop(3, 2);

2.通过从表格底部向上搜索检索特定列的第一个非空单元格

对于这个问题,我认为这个模式可能比较适合。

Object.prototype.get1stNonEmptyRowFromBottom = function (columnNumber, offsetRow = 1) {
  const search = this.getRange(offsetRow, columnNumber, this.getMaxRows()).createTextFinder(".").useRegularExpression(true).findPrevious();
  return search ? search.getRow() : offsetRow;
};

// Please run this function.
function main() {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet1");
  const res = sheet.get1stNonEmptyRowFromBottom(3);
  console.log(res); // Retrieve the 1st non empty row of column "C" by searching from BOTTOM of sheet.
}
  • 请提供表格对象和列号。
  • 在此脚本中,当使用第二个参数时,可以设置偏移行。例如,当第一行和第二行是标题行时,可以使用此脚本:const res = sheet.get1stNonEmptyRowFromBottom(3, 2);

结果:

使用上述脚本时,将获得以下结果。

enter image description here

  • 当使用“从表格顶部开始搜索特定列的第一个空单元格”的脚本用于列“C”时,将获得第6行。

  • 当使用“从表格底部开始搜索特定列的第一个非空单元格”的脚本用于列“C”时,将获得第9行。

参考资料:


7

来发一份替代性答案也不算晚,希望对您有所帮助。这是我查找最后一个单元格的代码段。我主要关注速度。在我的数据库中,约有150,000行数据,在此数据上运行该函数(平均)只需0.087秒,而@Mogsdad优雅的JS解决方案则需要(平均)0.53秒。在函数调用之前,两个数组都已经预加载过了。它利用递归进行二分搜索。对于10万行以上的数据,它返回结果不需超过15至20次跳转。

我保留了Log调用,以便您可以先在控制台中测试它并查看其工作原理。

/* @OnlyCurrentDoc */

function myLastRow() {

var ss=SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var colArray = ss.getRange('A1:A').getDisplayValues();  // Change to relevant column label and put in Cache
var TestRow=ss.getLastRow();
var MaxRow=ss.getMaxRows(); 

Logger.log ('TestRow = %s',TestRow);
Logger.log ('MaxRow = %s',MaxRow);

var FoundRow=FindLastRow(TestRow,MaxRow);

Logger.log ('FoundRow = %s',FoundRow);    

function FindLastRow(v_TestRow,v_MaxRow) {

/* Some housekeeping/error trapping first
* 1) Check that LastRow doesn't = Max Rows. If so then suggest to add a few lines as this
* indicates the LastRow was the end of the sheet.
* 2) Check it's not a new sheet with no data ie, LastRow = 0 and/or cell A1 is empty.
* 3) A return result of 0 = an error otherwise any positive value is a valid result.
*/

 return   !(colArray[0][0]) ? 1                   // if first row is empty then presume it's a new empty sheet
        :!!(colArray[v_TestRow][0]) ? v_TestRow   // if the last row is not empty then column A was the longest
        : v_MaxRow==v_TestRow ? v_TestRow         // if Last=Max then consider adding a line here to extend row count, else
        : searchPair(0,v_TestRow);                // get on an find the last row
}

function searchPair(LowRow,HighRow){

var BinRow = ((LowRow+HighRow)/2)|0;   // force an INT to avoid row ambiguity

Logger.log ('LowRow/HighRow/BinRow = %s/%s/%s',LowRow, HighRow, BinRow);

/*   Check your log. You shoud find that the last row is always found in under 20 hops.
 *   This will be true whether your data rows are 100 or 100,000 long.
 *   The longest element of this script is loading the Cache (ColArray)
 */

 return   (!(colArray[BinRow-1][0]))^(!(colArray[BinRow][0])) ? BinRow
        : (!(colArray[BinRow-1][0]))&(!(colArray[BinRow][0])) ? searchPair(LowRow,BinRow-1)
        : (!!(colArray[BinRow-1][0]))|(!!(colArray[BinRow][0])) ? searchPair(BinRow+1,HighRow)
        : false;   // Error
 }
}

/* The premise for the above logic is that the binary search is looking for a specific pairing, <Text/No text>
 * on adjacent rows.  You said there are no gaps so the pairing <No Text/Text> is not tested as it's irrelevant.
 * If the logic finds <No Text/No Text> then it looks back up the sheet, if it finds <Text/Text> it looks further
 * down the sheet.  I think you'll find this is quite fast, especially on datasets > 100,000 rows.
 */

2
仍然比这个答案慢。但是这个答案很好。 - TheMaster

5
你可以通过反向操作来实现这一点。 从电子表格中的最后一行开始,向上移动直到获取某个值。即使在中间有空行的情况下,此方法也适用于所有情况。 代码如下所示:
var iLastRowWithData = lastValue('A');
function lastValue(column) {
  var iLastRow = SpreadsheetApp.getActiveSheet().getMaxRows();
  var aValues = SpreadsheetApp.getActiveSheet().getRange(column + "2:" + column + lastRow).getValues();

  for (; aValues[iLastRow - 1] == "" && iLastRow > 0; iLastRow--) {}
  return iLastRow;
}

5

虽然这是一个旧帖子,但我发现了一种似乎有效的简单方法

ws.getRange("A2").getNextDataCell(SpreadsheetApp.Direction.DOWN).getLastRow()

5

I've used getDataRegion

sheet.getRange(1, 1).getDataRegion(SpreadsheetApp.Dimension.ROWS).getLastRow()

请注意,这取决于数据是连续的(根据原帖的要求)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接