Обновить данные, полученные с помощью пользовательской функции в Google Sheet

92

Я написал собственный скрипт Google Apps, который будет получать idи извлекать информацию из веб-службы (цена).

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

Как я могу заставить его повторно запустить скрипт и обновить ячейки (без ручного обхода каждой ячейки)?

tbkn23
источник
1
Да, вот объяснение, которое я сделал по этому поводу: stackoverflow.com/questions/9022984/…
Henrique G. Abreu
Я прочитал ваше объяснение в рамках своего исследования. Очень полезно, спасибо. Я добавил ссылку на свой ответ.
tbkn23
Тем, кто сталкивается с подобным (определенным и логичным, но иногда неудачным) поведением, может быть полезно проголосовать за этот запрос функции в Google Issue Tracker: issueetracker.google.com/issues/36763858 .
Тимоти Джонс
Вот простой ответ, который я сделал.
Антенны
Реальные обходные пути: stackoverflow.com/a/56802017 и stackoverflow.com/a/61946592
TheMaster

Ответы:

92

Хорошо, похоже, моя проблема заключалась в том, что Google ведет себя странным образом - он не перезапускает скрипт, пока параметры скрипта похожи, он использует кешированные результаты из предыдущих запусков. Следовательно, он не повторно подключается к API и не получает повторно цену, он просто возвращает предыдущий результат скрипта, который был кэширован.

См. Дополнительную информацию здесь: https://code.google.com/p/google-apps-script-issues/issues/detail?id=888

и здесь: Скрипт для обобщения данных, не обновляющихся

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

Поэтому всякий раз, когда я вызываю функцию, в качестве дополнительного параметра я передаю «$ A $ 1». Я также создал пункт меню под названием «Обновить», и когда я его запускаю, он помещает текущую дату и время в A1, поэтому все вызовы скрипта с $ A $ 1 в качестве второго параметра должны быть пересчитаны. Вот код из моего скрипта:

function onOpen() {
  var sheet = SpreadsheetApp.getActiveSpreadsheet();
  var entries = [{
    name : "Refresh",
    functionName : "refreshLastUpdate"
  }];
  sheet.addMenu("Refresh", entries);
};

function refreshLastUpdate() {
  SpreadsheetApp.getActiveSpreadsheet().getRange('A1').setValue(new Date().toTimeString());
}

function getPrice(itemId, datetime) {
  var headers =
      {
        "method" : "get",
        "contentType" : "application/json",
        headers : {'Cache-Control' : 'max-age=0'}
      };

  var jsonResponse = UrlFetchApp.fetch("http://someURL?item_id=" + itemId, headers);
  var jsonObj = eval( '(' + jsonResponse + ')' );
  return jsonObj.Price;
  SpreadsheetApp.flush();
}   

И когда я хочу указать цену товара с ID 5 в ячейке, я использую следующую формулу:

=getPrice(5, $A$1)

Когда я хочу обновить цены, я просто нажимаю пункт меню «Обновить» -> «Обновить». Помните, что вам необходимо перезагрузить электронную таблицу после изменения onOpen()сценария.

tbkn23
источник
4
почему бы не использовать now () в качестве дополнительного параметра?
Advanced
5
Видите ли, точно так же, как моя функция не будет повторно оцениваться, потому что ее параметры не изменились (т.е. значения ячеек), now () не будет повторно оцениваться, так как у нее нет параметров, поэтому возвращаемое значение будет не изменится, поэтому параметры моей функции не изменятся. Кроме того, использование now () приведет к тому, что моя функция все время будет переоценивать, что немного тяжело, учитывая, что она генерирует несколько HTTP-вызовов ...
tbkn23
2
Хорошая находка. У меня возникла эта проблема при использовании именованных диапазонов в качестве входных данных. Используя ваш ответ, я понял, что часто бывает достаточно передать фиктивную сумму по диапазону входных данных, как в «sum (B: D)», где строки BD находятся в диапазоне, который ищет пользовательская функция. Изменение любой ячейки приведет к изменению суммы и обновлению пользовательской функции. Кстати, похоже, что пользовательская функция даже не обязана объявлять игнорируемый параметр.
Шон Гувер
1
Жаль, что это по-прежнему лучшее решение, которое я нашел для этой проблемы. Вместо того, чтобы Google просто позволял нам отключать кеш для определенных вызовов функций, мы произвольно увеличиваем то, что хранится в кеше, чтобы убедиться, что мы не получаем кешированное значение ... Тем не менее, спасибо за сообщение,
GrayedFox
Попробуйте использовать параметр для своей пользовательской функции, как в этом примере
Aerials,
33

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

То, что я сделал, было похоже на tbkn23.

Функция, которую я хочу переоценить, имеет лишний неиспользуемый параметр $ A $ 1. Итак, вызов функции

=myFunction(firstParam, $A$1)

Но в коде подпись функции

function myFunction(firstParam)

Вместо функции обновления я использовал функцию onEdit (e), подобную этой

function onEdit(e)
{
   SpreadsheetApp.getActiveSheet().getRange('A1').setValue(Math.random());
}

Эта функция запускается всякий раз, когда редактируется какая-либо ячейка в электронной таблице. Итак, теперь вы редактируете ячейку, случайное число помещается в A1, это обновляет список параметров, как было предложено tbkn23, что приводит к повторной оценке настраиваемой функции.

Лекси Кисть
источник
5
Работает отлично. Обратной стороной является перепутанная история отмены (ctrl + z).
Джон
1
Хороший трюк с раздражающим недостатком, как указал Джон ... Я надеюсь, что кто-то исправит его :-)
Эниссай
Небольшая и педантичная оговорка с этим ответом; в невероятно маловероятном случае Math.random возвращает то же число, в этом случае он не будет обновляться, поскольку это конкретное число уже было кэшировано.
Вуди Пэйн
12

Это очень поздно, и я не знаю, что это было бы полезно, но на самом деле здесь есть настройки, вы можете NOW()автоматически обновлять

введите описание изображения здесь

Thaina
источник
1
NOW()это встроенная функция, а не пользовательская функция.
Рубен
@ Rubén Дело в том, что вы можете включить функцию NOW () в любую настраиваемую функцию, которую хотите обновить,
Thaina
13
В настоящее время аргументы пользовательских функций должны быть детерминированными, другими словами, они не разрешают NOW () в качестве аргумента. См developers.google.com/apps-script/guides/sheets/functions
Ruben
3
Выдает ошибку, если вы пытаетесь использовать NOW () внутри пользовательской функции
Стефано Джаконе,
6

Если ваша настраиваемая функция находится внутри определенного столбца, просто упорядочивайте электронную таблицу по этому столбцу.

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

грипп
источник
1

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

Работая с скриптом Lexi как есть, он, похоже, больше не работал с текущими таблицами, но если я добавлю фиктивную переменную в свою функцию в качестве параметра (нет необходимости фактически использовать ее внутри функции), она действительно будет заставить листы Google снова обновить страницу.

Итак, объявление типа: function myFunction (firstParam, dummy) с последующим его вызовом будет таким, как было предложено. Это сработало для меня.

Кроме того, если случайная переменная появляется на всех ваших листах, которые вы редактируете, неудобно, простое решение ограничиться одним листом:

function onEdit(e)
{
  e.source.getSheetByName('THESHEETNAME').getRange('J1').setValue(Math.random());
}
AvengingAB
источник
1
@ Rubén это очень похоже, но с хорошей неприятностью; вместо использования активного листа он использует определенное имя листа, улучшая вашу историю
Джонас Д.
1

Логика скрипта:

  • Пользовательские функции не обновляются, пока не изменятся аргументы.
  • Создайте триггер onChange для изменения всех аргументов всех пользовательских функций в электронной таблице с помощью textFinder

Фрагмент:

/*@customfunction*/
function sheetNames(e) {
  return SpreadsheetApp.getActive()
    .getSheets()
    .map(function(sheet) {
      return sheet.getName();
    });
}

/*Create a installable trigger to listen to grid changes on the sheet*/
function onChange(e) {
  if (!/GRID/.test(e.changeType)) return; //Listen only to grid change
  SpreadsheetApp.getActive()
    .createTextFinder('=SHEETNAMES\\([^)]*\\)')
    .matchFormulaText(true)
    .matchCase(false)
    .useRegularExpression(true)
    .replaceAllWith(
      '=SHEETNAMES(' + (Math.floor(Math.random() * 500) + 1) + ')'
    );
}

Читать:

Хозяин
источник
1

Как отмечалось ранее:

Пользовательские функции не обновляются, пока не изменятся аргументы.

Возможное решение - создать флажок в одной ячейке и использовать эту ячейку в качестве аргумента для пользовательской функции:

  1. Установите флажок: выберите свободную ячейку, например [A1], перейдите в [Вставить]> [Флажок]
  2. Сделайте эту ячейку аргументом: =myFunction(A1)
  3. Установите флажок, чтобы обновить формулу
Макс Махров
источник
-3

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

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

Шрик
источник
10
Это было бы просто здорово, но это не работает ... Перезагрузка страницы не обновляет значения. Более того, удаление ячейки и повторный вызов того же вызова функции по-прежнему сохраняет старое значение. Если я вызову ту же функцию точно из другой ячейки, она покажет новое значение, но не в старой ячейке.
tbkn23