Более быстрый способ найти первую пустую строку в столбце Google Sheet

Я сделал сценарий, который каждые несколько часов добавляет новую строку в электронную таблицу 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 строк он становится очень медленным, даже через десять секунд. Меня беспокоит, что при достижении тысяч строк он будет слишком медленным, может быть, по таймауту или хуже. Есть ли способ лучше?


person Omiod    schedule 30.07.2011    source источник


Ответы (16)


На этот вопрос было просмотрено более 12 тыс. Просмотров, так что пришло время обновить его, поскольку характеристики производительности новых листов отличаются от тех, когда Серж провел свои начальные тесты.

Хорошие новости: производительность во всех отношениях намного лучше!

Самый быстрый:

Как и в первом тесте, чтение данных из таблицы только один раз, а затем работа с массивом дало огромный выигрыш в производительности. Интересно, что исходная функция Дона работала намного лучше, чем модифицированная версия, которую тестировал Серж. (Похоже, что 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);
}

Результаты теста:

Вот результаты, суммированные за 50 итераций в электронной таблице со 100 строками x 3 столбцами (заполненными тестовой функцией Сержа).

Имена функций соответствуют коду в скрипте ниже.

скриншот

«Первая пустая строка»

Первоначальный запрос заключался в том, чтобы найти первую пустую строку. Ни один из предыдущих сценариев на самом деле не обеспечивает этого. Многие проверяют только один столбец, а это значит, что они могут дать ложноположительные результаты. Другие находят только первую строку, которая следует за всеми данными, что означает, что пустые строки в несмежных данных пропускаются.

Вот функция, которая соответствует спецификации. Он был включен в тесты, и, хотя он был медленнее, чем молниеносная проверка с одним столбцом, он показал респектабельные 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://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;
}
person Mogsdad    schedule 27.11.2014
comment
Небольшой комментарий, чтобы упомянуть, что мой исходный код действительно возвращал правильное значение, потому что я использовал следующий код, когда условие было истинным: {n ++; break}. Таким образом, добавление 1 к n (в вашей модифицированной версии) возвращает n + 2 ... что слишком много ;-). Я признаю, что это был, вероятно, не самый очевидный способ продолжения. - person Serge insas; 21.04.2015
comment
Версия @Serge insas работает медленнее, потому что вы получаете весь диапазон, а не только один столбец, как в решении Дона. После исправления они работают одинаково быстро. - person Antony Hatchkins; 14.11.2016
comment
Также ваша версия решения Сержа и его собственное решение неверны, но по-разному: его решение не обрабатывает n = 1000 в вашем примере настройки, а ваше решение не обрабатывает n = 10 (попробуйте удалить A10). - person Antony Hatchkins; 14.11.2016

В блоге Google Apps Script есть сообщение об оптимизации операций с таблицами, которое говорил о пакетном чтении и записи, которое действительно может ускорить процесс. Я попробовал ваш код в электронной таблице со 100 строками, и это заняло около семи секунд. При использовании 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 строк вместо того, чтобы захватывать весь столбец.

person Don Kirkby    schedule 01.02.2012
comment
Это работает, только если вы можете гарантировать, что строка с пустой ячейкой в ​​столбце A пуста. У него также нет условия остановки для цикла while, поэтому он может (и делает) исключение, если каждая ячейка в столбце A заполнена. Адаптация Сержа справляется с условием остановки. - person Mogsdad; 27.11.2014
comment
Кроме того, результат отличается на 1. Если первая пустая строка равна 10 (расчет электронной таблицы), это дает 9 (расчет массива). - person Mogsdad; 28.11.2014
comment
См. ответ Могсдада для более полной проверки пустых строк, а также некоторых сравнений производительности. Спасибо, @Mogsdad. - person Don Kirkby; 28.11.2014

Он уже присутствует на листе в виде метода getLastRow.

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

Ссылка https://developers.google.com/apps-script/class_sheet#getLastRow

person Peter H    schedule 12.05.2012
comment
Проблема заключается в том, что если столбец A имеет 10 строк, а столбец B - 100, это вернет 100. Чтобы получить последнюю строку столбца, вам нужно перебрать содержимое (насколько мне известно) - person Jeremy; 06.06.2012
comment
Хм, тогда почему бы не SpreadsheetApp.getActiveSpreadsheet().getRange('A:A').getLastRow() + 1? - person avalancha; 09.06.2016
comment
Argh nvm Я только что заметил, что это дает вам действительно последнюю ячейку диапазона - person avalancha; 09.06.2016
comment
OP на самом деле никогда не запрашивал конкретную проверку столбца, так что это определенно самый эффективный способ добиться того, чего он хотел. Жаль, что я этого раньше не видел! (Хотя я многому научился из других сообщений.) - person rryanp; 22.03.2017
comment
Это самый быстрый - person Ling Loeng; 16.05.2018
comment
Есть сообщения, что getLastRow иногда работает медленно. Ref. stackoverflow.com/q/50301012/1595451 и stackoverflow.com/q/40127929/1595451 - person Rubén; 04.08.2018

Увидев этот старый пост с 5 тыс. Просмотров, я сначала проверил "лучший ответ" и был весьма удивлен его содержанием ... это был действительно очень медленный процесс! Затем я почувствовал себя лучше, когда увидел ответ Дона Киркби: массивный подход действительно намного эффективнее!

Но насколько эффективнее?

Итак, я написал этот небольшой тестовый код в электронной таблице с 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']);
  }
}

И тестовую таблицу, чтобы попробовать сами :-)


РЕДАКТИРОВАТЬ :

Следуя комментарию Могсдада, я должен упомянуть, что эти имена функций действительно плохой выбор ... Это должно было быть что-то вроде getLastNonEmptyCellInColumnAWithPlentyOfSpaceBelow(), что не очень элегантно (не так ли?), Но более точное и связное с тем, что оно фактически возвращает.

Комментарий:

Как бы то ни было, я хотел показать скорость выполнения обоих подходов, и он, очевидно, сделал это (не так ли? ;-)

person Serge insas    schedule 19.08.2013
comment
Спасибо за тест. Я только что переместил лучший ответ Дона Киркби. - person Omiod; 19.08.2013
comment
Замечательная идея! Спасибо - person Serge insas; 19.08.2013
comment
Обе функции getFirstEmpty дают неверные результаты. Поскольку они считают в обратном порядке, они идентифицируют последнюю строку с данными в столбце A. На листе с непрерывными данными они будут отличаться от первой пустой строки. Если раньше в электронной таблице есть пустая строка, никто ее не найдет. - person Mogsdad; 28.11.2014
comment
Привет, Могсдад, ты, конечно, не ошибся ... вместо этого его следует называть getLastRow ... Думаю, я использовал это имя из-за других предыдущих ответов ... Что касается ограничения столбца A, оно было в OP сам ... Промежуточные пустые ячейки? ну как с этим справиться? Честно говоря, я не знаю лучшего выбора, это в основном зависит от того, что вы ищете ... первый пробел в списке или последняя строка, откуда вы могли бы продолжить? В любом случае, спасибо, что указали на это, я добавлю небольшую правку в имена функций ;-) - person Serge insas; 28.11.2014

Я знаю, что это старая ветка, и здесь было несколько очень умных подходов.

Я использую сценарий

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

если мне нужна первая полностью пустая строка.

Если мне нужна первая пустая ячейка в столбце, я делаю следующее.

  • Моя первая строка обычно является строкой заголовка.
  • Моя вторая строка - это скрытая строка, и каждая ячейка имеет формулу

    =COUNTA(A3:A)
    

    Где A заменяется буквой столбца.

  • Мой сценарий просто считывает это значение. Это обновляется довольно быстро по сравнению со сценарием.

Один раз это не сработает, и это когда я разрешаю пустым ячейкам разбивать столбец. Мне пока не нужно исправление для этого, я подозреваю, что оно может быть получено из COUNTIF, или комбинированной функции, или одной из многих других встроенных функций.

РЕДАКТИРОВАТЬ: COUNTA справляется с пустыми ячейками в пределах диапазона, так что беспокойство по поводу «один раз это не сработает» на самом деле не вызывает беспокойства. (Это может быть новое поведение с «новыми таблицами».)

person Niccolo    schedule 09.08.2014
comment
Разгрузка идентификации последней строки в формулах электронной таблицы - это умно! - person Mogsdad; 12.07.2015
comment
Спасибо и спасибо за редактирование. Я полагаю, что с тех пор, как я это написал, поведение изменилось. - person Niccolo; 24.09.2015

И почему бы не использовать appendRow?

var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
spreadsheet.appendRow(['this is in column A', 'column B']);
person Sonny    schedule 03.04.2013
comment
Почему нет? Потому что он не отвечает на вопрос - первая пустая строка не идентифицирована. Вместо этого в нижнюю часть электронной таблицы была добавлена ​​новая строка, даже если над ней есть пустые строки. - person Mogsdad; 28.11.2014
comment
Это лучший ответ, и @Mogsdad ошибается: sheet.appendRow () НЕ добавляет строку внизу листа и НЕ оставляет пустые строки над ней. Вместо этого он делает именно то, что просил OP: он добавляет строку после последней строки листа, содержащего контент. Кроме того, он является атомарным, поэтому два метода appendRow (), запущенные одновременно, не будут пытаться писать в один и тот же последний столбец. Итог: это правильный ответ! (По крайней мере, до тех пор, пока вам не нужен индекс последней строки после записи в нее, чего OP не запрашивал.) - person Jpsy; 04.03.2016
comment
@Jpsy Вы могли бы извлечь выгоду из тренировки вежливости. Не стесняйтесь сомневаться в достоверности старого ответа или вопроса - в конце концов, это ответы из предложения Старых листов, все могло измениться. Но они этого не сделали - и у вас нет никаких доказательств, подтверждающих ваше подстрекательское заявление. Этот клип ясно демонстрирует проблему с appendRow (): он не находит ПЕРВАЯ пустая строка. (Первая строка после последней строки с содержимым, да, но опять же, это не то, о чем просили.) - person Mogsdad; 04.03.2016
comment
@Mogsdad Хммм, похоже, нам обоим понравится такая тренировка. Снова читая OP, я думаю, что вижу источник недоразумения: он / она пишет о поиске первой пустой строки, но четко описывает вариант использования, в котором не будет прерывистых пустых строк, а первая пустая строка всегда будет ниже всего остального содержимого . Так что в каком-то смысле мы оба правы. Но для варианта использования OP это все еще правильный ответ: он хочет добавить в конец содержимого. - person Jpsy; 04.03.2016
comment
@Jpsy Я не согласен - они предоставили рабочий фрагмент кода как часть своего вопроса. Код OP останавливался бы на прерывистой пустой ячейке в столбце A (который они считают пустой строкой), и они не просили это изменить. Если они ищут более быстрый способ предоставить те же функциональные возможности, которые у них уже есть, appendRow () потерпит неудачу. Учитывая, что они продемонстрировали усилия по написанию кода, предположение, что они были невнимательны к поиску в документации для поиска альтернативных методов, не кажется оправданным. Если бы это был новый вопрос, правильнее было бы попросить разъяснений. - person Mogsdad; 04.03.2016
comment
Вопрос @Mogsdad OP описывает процесс ведения журнала с записями, добавляемыми в постоянно растущий список. Читая его описание, его первая пустая строка - это не промежуточная строка, а строка после последней строки содержимого. Тот факт, что его код также находит промежуточные пустые строки, является побочным эффектом, который, очевидно, не был предназначен, но не мешал его потребностям. При этом я согласен с тем, что результаты приведенных здесь ответов очень ценны, и я поддержал несколько из них, включая ваш, прежде чем комментировать здесь. Но все же это правильный ответ, по крайней мере, по состоянию на февраль 2016 года. - person Jpsy; 07.03.2016

Я изменил код, предоставленный ghoti, так, чтобы он искал пустую ячейку. Сравнение значений не работало в столбце с текстом (или я не мог понять, как это сделать), вместо этого я использовал isBlank (). Обратите внимание, что значение инвертируется с помощью! (перед переменной r), если смотреть вперед, так как вы хотите, чтобы i увеличивался до тех пор, пока не будет найден пробел. Увеличивая лист на десять, вы хотите перестать уменьшать 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;
person Richard Rasch    schedule 27.01.2016

Всего два цента, но я делаю это все время. Я просто записываю данные в ВЕРХ листа. Дата перевернута (последнее сверху), но я все еще могу заставить его делать то, что хочу. В приведенном ниже коде хранятся данные, которые он собирает с сайта риэлтора в течение последних трех лет.

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

Этот фрагмент функции скребка вставляет строку выше # 2 и записывает туда данные. Первая строка - это заголовок, поэтому я не касаюсь этого. Я не рассчитал время, но проблема возникает только тогда, когда сайт меняется.

person HardScale    schedule 15.07.2017

Действительно, getValues ​​- хороший вариант, но вы можете использовать функцию .length для получения последней строки.

 function getFirstEmptyRow() {
  var spr = SpreadsheetApp.getActiveSpreadsheet();
  var array = spr.getDataRange().getValues();
  ct = array.length + 1
  return (ct);
}
person Thomas    schedule 11.05.2012
comment
Не дает правильного результата, если в данных есть пробелы на всю строку. - person Mogsdad; 28.11.2014

У меня похожая проблема. Сейчас это таблица со многими сотнями строк, и я ожидаю, что она вырастет до многих тысяч. (Я не видел, сможет ли электронная таблица Google обрабатывать десятки тысяч строк, но в конце концов доберусь до нее.)

Вот что я делаю.

  1. Сделайте шаг вперед по столбцу на сотни, остановитесь, когда я окажусь на пустом ряду.
  2. Сделайте шаг назад по столбцу на десятки, ища первую непустую строку.
  3. Пройдите вперед по столбцу, ища первую пустую строку.
  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;
}

Это намного быстрее, чем проверять каждую ячейку сверху. И если у вас есть другие столбцы, которые расширяют ваш рабочий лист, это может быть быстрее, чем проверять каждую ячейку снизу.

person ghoti    schedule 19.08.2013

Использование indexOf - один из способов добиться этого:

function firstEmptyRow() {
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var sh = ss.getActiveSheet();
  var rangevalues = sh.getRange(1,1,sh.getLastRow(),1).getValues(); // Column A:A is taken
  var dat = rangevalues.reduce(function (a,b){ return a.concat(b)},[]); // 
 2D array is reduced to 1D//
  // Array.prototype.push.apply might be faster, but unable to get it to work//
  var fner = 1+dat.indexOf('');//Get indexOf First empty row
  return(fner);
  }
person TheMaster    schedule 09.02.2018

В моих таблицах я храню дополнительный лист «технического обслуживания», в котором храню такие данные.

Чтобы получить следующую свободную строку диапазона, я просто просматриваю соответствующую ячейку. Я могу получить значение мгновенно, потому что поиск значения происходит при изменении данных.

Формула в ячейке обычно выглядит примерно так:

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

Значение в A9 может периодически устанавливаться для некоторой строки, которая близка к «достаточно» до конца.

Предупреждение: я никогда не проверял, применимо ли это для огромных наборов данных.

person Martin Bramwell    schedule 28.09.2012

Наконец-то я получил для него однострочное решение.

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

Он отлично работает для меня.

person Hari Das    schedule 13.09.2013
comment
Это не удастся, если перед последней непустой ячейкой есть пустая ячейка. - person Amit; 22.11.2017

Вот список того, что должен делать код:

  • Дайте правильный ответ, если нет пустых ячеек
  • Быть быстрым
  • Вернуть правильный номер строки, а не порядковый номер массива
  • Получите правильный номер строки пустой ячейки, даже если в других столбцах на вкладке листа больше строк с данными
  • Иметь хорошие имена переменных
  • Ответьте на исходный вопрос
  • Избегайте ненужной обработки данных
  • Предоставьте комментарии, поясняющие, что делает код
  • Будьте достаточно универсальными, чтобы адаптироваться к условиям читателей

В этом решении используется метод массива some, который прекращает повторение цикла, когда условие истинно. Это позволяет избежать траты времени на просмотр каждого элемента массива и при этом использовать метод массива, а не цикл for или while.

Метод some возвращает только true или false, но есть способ получить номер индекса, потому что метод some останавливает цикл, когда условие истинно.

Номер индекса присваивается переменной в области вне функции массива. Это не замедляет обработку.

Код:

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"})

}
person Alan Wells    schedule 29.05.2020

Я прошел через слишком много реализаций последней строки для определенного столбца. Многие решения работают, но работают медленно для больших или нескольких наборов данных. Один из моих вариантов использования требует, чтобы я проверял последнюю строку в определенных столбцах в нескольких таблицах. Я обнаружил, что использование всего столбца как диапазона с последующим повторением по нему является слишком медленным, а добавление нескольких из них вместе делает сценарий вялым.

Мой хакер был такой:

=ROW(index(sheet!A2:A,max(row(sheet!A2:A)*(sheet!A2:A<>""))))-1
  • Пример: добавьте это в ячейку A1, чтобы найти последнюю строку в столбце A. Можно добавить где угодно, просто убедитесь, что вы управляете -1 в конце в зависимости от того, в какой строке помещена формула. Вы также можете указать, что это другой столбец, а не тот, который вы пытаетесь сосчитать, и вам не нужно управлять -1. Вы также можете рассчитывать ОТ начальной строки, например C16: C - будет подсчитывать значения C16 и далее.

  • Эта формула надежно дает мне последнюю строку, включая пробелы в середине набора данных.

  • Чтобы использовать это значение в моем коде GS, я просто читаю значение ячейки из A1. Я понимаю, что Google ясно, что функции электронных таблиц, такие как чтение / запись, тяжелы (отнимают много времени), но, по моему опыту, это намного быстрее, чем методы последней строки подсчета столбцов (для больших наборов данных)

  • Чтобы сделать это эффективным, я получаю последнюю строку в столбце один раз, затем сохраняю ее как глобальную переменную и увеличиваю в своем коде, чтобы отслеживать, какие строки мне следует обновить. Чтение ячейки каждый раз, когда вашему циклу необходимо выполнить обновление, будет слишком неэффективным. Прочтите один раз, повторите значение, и формула ячейки A1 (см. Выше) сохраняет обновленное значение для следующего запуска вашей функции.

  • Это также работает, если для данных включены фильтры. Фактическая последняя строка сохраняется

Пожалуйста, дайте мне знать, было ли это полезно для вас! Если у меня возникнут проблемы, я прокомментирую этот ответ.

person Tanveer Chandok    schedule 29.09.2020
comment
Здорово, что вы хотите поделиться своим ответом. Но не копируйте один и тот же ответ на разные вопросы. Сделайте свой ответ конкретным для каждого вопроса или отметьте вопросы как повторяющиеся. См. meta.stackoverflow.com/questions/270841/ - person TheMaster; 29.09.2020
comment
Ах хорошо! Извините - не знал, что это запрещено. Просто хотел помочь, разместив это в темах, где это актуально. Буду следовать вашим предложениям. - person Tanveer Chandok; 29.09.2020

Это мой самый первый пост на stackOverflow, я надеюсь удовлетворить все ваши потребности в сетевом этикете, поэтому, пожалуйста, будьте любезны со мной.

соображения

Я думаю, что самый быстрый способ найти первую пустую ячейку в столбце (в любом случае я не мог выполнить проверку производительности) - это позволить движку Google выполнять последовательные задачи самостоятельно; это просто намного эффективнее. С точки зрения программиста, это означает: НЕ использовать какие-либо итерации / циклы, например FOR, WHILE и т. Д. (Кстати, это тот же подход к программированию для движков баз данных - любая активность НЕ следует использовать циклы для поиска информации.)

идея

  1. Пройдите ВНИЗ и найдите ячейку в последней строке листа (учитывая все столбцы),
  2. оттуда перейдите ВВЕРХ, найдите первую ячейку, содержащую данные в указанном столбце (выбрав столбец),
  3. сдвиньте одну ячейку вниз, чтобы найти свободное место.

Следующая функция делает это всего за одну команду (игнорируя объявления 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
}
person JackBlackSante    schedule 16.06.2021