【问题标题】:Refresh data retrieved by a custom function in Google Sheet刷新由 Google 表格中的自定义函数检索到的数据
【发布时间】:2013-06-24 20:25:43
【问题描述】:

我编写了一个自定义 Google Apps 脚本,它将接收 id 并从网络服务获取信息(价格)。

我在电子表格中使用这个脚本,它工作得很好。我的问题是这些价格发生了变化,而我的电子表格没有更新。

如何强制它重新运行脚本并更新单元格(无需手动检查每个单元格)?

【问题讨论】:

标签: google-apps-script google-sheets custom-function


【解决方案1】:

好的,看来我的问题是谷歌的行为方式很奇怪——只要脚本参数相似,它就不会重新运行脚本,它使用之前运行的缓存结果。因此它不会重新连接到 API,也不会重新获取价格,它只是返回之前缓存的脚本结果。

在此处查看更多信息(如果您受到影响,请为这些问题添加星标):

Henrique G. Abreu's answer

我的解决方案是在我的脚本中添加另一个参数,我什至不使用它。现在,当您使用与之前调用不同的参数调用函数时,它将不得不重新运行脚本,因为这些参数的结果不会在缓存中。

因此,每当我调用该函数时,对于额外参数,我都会传递“$A$1”。 我还创建了一个名为 refresh 的菜单项,当我运行它时,它将当前日期和时间放在 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() 脚​​本后需要重新加载电子表格。

【讨论】:

  • 为什么不使用 now() 作为附加参数?
  • 你看,就像我的函数不会被重新评估,因为它的参数没有改变(即单元格的值),now() 也不会被重新评估,因为它已经没有参数,因此它的返回值不会改变,所以我的函数的参数也不会改变。此外,使用 now() 会导致我的函数一直重新评估,考虑到它会生成多个 HTTP 调用,这有点繁重......
  • 很好的发现。我在使用命名范围作为输入时遇到了这个问题。使用您的答案,我发现它通常足以在输入范围内传递一个虚拟总和,如“sum(B:D)”,其中 B-D 行在自定义函数查找的范围内。更改任何单元格将触发总和更改和自定义函数刷新。顺便说一句,自定义函数似乎甚至不必声明被忽略的参数。
  • 遗憾的是,这仍然是我找到的解决此问题的最佳解决方案。而不是谷歌只允许我们禁用特定函数调用的缓存,我们任意增加存储在缓存中的内容只是为了确保我们没有得到缓存的值......不过,感谢您的帖子,
  • 尝试为您的自定义函数使用参数like this example
【解决方案2】:

您缺少了严格的缓存 bug 功能。它是这样工作的:

Google 认为您的所有自定义函数直接依赖于其参数值来返回其结果(您可以选择依赖其他静态数据)。

鉴于此先决条件,他们只能在参数更改时评估您的函数。例如

假设我们在单元格 B1 中有文本“10”,然后在其他单元格中输入 =myFunction(B1)

myFunction 将被评估并检索其结果。然后,如果您将单元格 B1 的值更改为“35”,自定义将按预期重新评估,并正常检索新结果。 现在,如果您再次将单元格 B1 更改为原始的“10”,则不会重新计算,会立即从缓存中检索原始结果。

因此,当您使用工作表名称作为参数来动态获取它并返回结果时,您就违反了缓存规则。

不幸的是,如果没有这个惊人的功能,您将无法拥有自定义功能。因此,您必须更改它以直接接收值,而不是工作表名称,或者不使用自定义函数。例如,您可以在脚本中设置一个参数来告诉摘要应该去哪里,并让onEdit 在总数发生变化时更新它们。

【讨论】:

  • 嗯,这很烦人,但谢谢你的回答!通过向函数添加一个虚拟参数,我得到了一个讨厌的解决方法;如果我然后传递一个单元格引用,我可以通过在该单元格中增加一个数字来强制刷新数据。不要以为我可以创建一个刷新按钮,点击一下就可以了,是吗?没关系,它不需要真的是自动的,简单明了。
  • 好吧,你可以创建一个按钮,实际上是一个绘图,并为其分配一个脚本函数,这将增加你的虚拟参数单元,这将强制更新。
【解决方案3】:

我所做的与 tbkn23 类似。除了进行更改之外,此方法不需要任何用户操作。

我要重新评估的函数有一个额外的未使用参数 $A$1。所以函数调用是

=myFunction(firstParam, $A$1)

但在代码中函数签名是

function myFunction(firstParam)

我没有使用 Refresh 函数,而是使用了这样的 onEdit(e) 函数

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

每当编辑电子表格中的任何单元格时都会触发此功能。所以现在你编辑一个单元格,在 A1 中放置一个随机数,这会按照 tbkn23 的建议刷新参数列表,导致重新评估自定义函数。

【讨论】:

  • 效果很好。缺点是,撤消 (ctrl+z) 历史记录被搞砸了。
  • Jon 指出的一个令人讨厌的缺点的好技巧......我希望有人会改进它:-)
  • 对这个答案的小而迂腐的警告;在极不可能发生的情况下 Math.random 返回相同的数字,在这种情况下它不会更新,因为该特定数字已经被缓存。
【解决方案4】:

有一些设置可以让NOW()自动更新:

【讨论】:

  • NOW() 是内置函数,不是自定义函数。
  • @Rubén 关键是你可以在任何你想要更新的自定义函数中包含 NOW() 函数
  • 此时自定义函数的参数应该是确定性的,换句话说,它们不会将 NOW() 作为参数。见developers.google.com/apps-script/guides/sheets/functions
  • 如果您尝试在自定义函数中使用 NOW() 会引发错误
【解决方案5】:

如果您的自定义函数位于特定列中,只需按该列对电子表格进行排序。

排序操作会强制刷新数据,这会立即为该列的所有行调用您的自定义函数。

【讨论】:

  • 仅在工作表尚未订购时才有效
【解决方案6】:

脚本逻辑:

  • 除非参数发生变化,否则自定义函数不会更新。

  • 创建一个onChange 触发器以使用TextFinder 更改电子表格中所有自定义函数的所有参数

  • @tbkn23 的The idea to add a extra dummy parameter 和@Lexi 的use of the triggers 在此处实现了一个随机数作为参数。这个答案的不同主要是因为使用了 TextFinder 类(Apps 脚本中一个相对较新的补充),这更好,因为

    • 不需要额外的单元格。

    • 不需要菜单 > 不需要额外的点击。如果您需要自定义复习,a checkbox is a better implementation

    • 您也可以更改formula itself instead of changing the parameters

    • 可以将更改/触发器配置为仅过滤掉某些更改。例如,以下示例脚本触发器过滤掉除INSERT_GRID/REMOVE_GRID(Grid=Sheet) 之外的所有更改。这适用于提供sheetnames 的自定义函数。任何地方的编辑都不会更改工作表/工作表名称的列表,但插入或删除工作表会。

示例脚本(获取工作表列表):

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

/**
 * @listens to changes in a Google sheet
 * @see https://developers.google.com/apps-script/guides/triggers/installable#managing_triggers_manually
 */
function onChange(e) {
  /* Following types of change are available:
   * EDIT
   * INSERT_ROW
   * INSERT_COLUMN
   * REMOVE_ROW
   * REMOVE_COLUMN
   * INSERT_GRID
   * REMOVE_GRID
   * FORMAT
   * OTHER - This usually refers to changes made by the script itself or sheets api
   */
  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) + ')'
    );
}

阅读:

【讨论】:

  • 好的。所以要使用它,我会像你一样创建一个onChange() 函数,用我需要重新运行的自定义函数的名称替换SHEETNAMES,并用我希望触发器的更改类型替换GRID继续运行(例如EDIT)。 ...但是.replaceAllWith() 调用正确吗?我认为您需要在 '=SHEETNAMES(\1' + (Math.floor.....) 等原始查询中反向引用该组。并且您需要注册onChange(e) 函数,还是谷歌会因为函数名称/签名而自动注册?
  • @bobpaul 您需要手动注册触发器。请参阅代码中的 @see 链接。 sheetnames 自定义函数不接受任何改变输出的参数(e 未使用)。所以我没有包含反向引用。但如果您有任何非可选参数,则需要根据需要更改正则表达式。
【解决方案7】:

如前所述:

除非参数发生变化,否则自定义函数不会更新。

可能的解决方案是在单个单元格中创建一个复选框,并将此单元格用作自定义函数的参数:

  1. 创建一个复选框:选择空闲单元格,例如[A1],转到[插入] > [复选框]
  2. 将此单元格作为参数:=myFunction(A1)
  3. 点击复选框刷新公式

【讨论】:

    【解决方案8】:

    使用谷歌财务函数作为参数。喜欢 =GOOGLEFINANCE("CURRENCY:CADARS")

    这些函数每 x 分钟强制重新加载一次

    【讨论】:

    • x = 20+ // 分钟
    【解决方案9】:

    我在函数中使用了一个虚拟变量,这个变量指的是电子表格中的一个单元格。然后我在脚本中有一个Myfunction() 在该单元格中写入一个Math.Random 数字。

    MyFunction 在触发服务(编辑/当前项目触发器)下,您可以选择不同的事件触发器,例如 On-Open 或时间驱动,您可以选择一个时间段,例如从 1 分钟到一个月。

    【讨论】:

      【解决方案10】:

      按原样使用 Lexi 的脚本,它似乎不再适用于当前的表格,但如果我将虚拟变量作为参数添加到我的函数中(无需在函数中实际使用它),它确实会强制谷歌表格再次刷新页面。

      因此,声明如下:function myFunction(firstParam,dummy) 然后调用它就像建议的那样。这对我有用。

      此外,如果随机变量出现在您编辑的所有工作表上是一件令人讨厌的事情,那么限制为一个工作表的简单补救措施如下:

      function onEdit(e)
      {
        e.source.getSheetByName('THESHEETNAME').getRange('J1').setValue(Math.random());
      }
      

      【讨论】:

      • @Rubén 它非常相似,但很麻烦;它使用定义的工作表名称而不是使用活动工作表,从而改善您的历史记录
      【解决方案11】:

      缓存问题的另一种解决方案。

      在您的方法中有一个虚拟变量。 通过

      Filter(<the cell or cell range>,1=1)
      

      作为该参数的值。

      例如

      =getValueScript("B1","B4:Z10", filter(B4:Z10,1=1))
      

      过滤器的输出没有被使用。但是它向电子表格表明此公式对 B4:Z10 范围敏感。

      【讨论】:

      • 它不再起作用了。如果您尝试以这种方式执行此操作,您将看到错误“此函数不允许使用 NOW()、RAND() 或 RANDBETWEEN() 引用单元格”
      【解决方案12】:

      我在为工作创建仪表板时遇到了类似的问题。尽管 Arsen 最近发表了评论,Chamil 的上述解决方案(即使用 Sheet 的 Filter 函数作为值传递给函数中的虚拟变量)工作得很好。在我的例子中,我使用一个函数来监视一个范围,并且不能在同一范围上使用过滤器,因为它创建了一个循环引用。所以我只有一个单元格(在我的例子中是下面代码中的 E45),我可以随时在其中更改数字,我希望我的函数可以更新:

      =myFunction("E3:E43","D44",filter(E45,1=1))
      

      正如 Chamil 指出的那样,脚本中没有使用过滤器:

      function myFunction(range, colorRef, dummy) {
        variable 'dummy' not used in code here
      }
      

      【讨论】:

        【解决方案13】:

        由于 google app 脚本是 JS 的扩展,因此函数应该能够处理比函数签名中定义的参数更多或更少的参数。因此,如果您有一些功能,例如

        function ADD(a, b) {
          return CONSTANTS!$A$1 + a + b
        }
        

        然后你会这样调用这个函数

        =ADD(A1, B1, $A$2)
        

        其中 $A$2 是某个复选框(插入 -> 复选框),您可以在需要更改工作表和单元格 CONSTANTS$A$1 中的值后单击以“刷新”

        【讨论】:

          【解决方案14】:

          您可以做的是在电子表格的某处设置另一个单元格,每次添加新工作表时都会更新该单元格。确保它不会因每次更改而更新,而仅在您想要进行计算时更新(在您添加工作表时)。然后,将此单元格的引用传递给您的自定义函数。如前所述,自定义函数可以忽略此参数。

          【讨论】:

            【解决方案15】:

            鉴于 Henrique Abreu 解释的该功能,您可以尝试开箱即用的电子表格功能 QUERY,我在工作中经常使用 SQL 喜欢的查询 在原始数据上,并将数据作为摘要获取到不同的选项卡,结果数据会随着原始数据的变化而实时更新。

            我的建议是基于您的脚本没有 URL 获取等高级工作,只是数据工作,因为没有实际读取数据,我无法使用 QUERY 给出精确的解决方案。

            关于 Henrique Abreu 提到的 缓存功能(我没有足够的声誉直接在他的回答下发表评论),我做了测试并发现:

            1. 看起来没有缓存工作,测试函数的脚本如下所示:

              函数加法器(基础){ 实用程序.sleep(5000); 返回基数 + 10; }

            通过调用一个单元格在工作表中应用该自定义函数 adder(),然后来回更改该单元格值,每次我看到加载消息和总时间超过 5 秒。 可能与GAS issue:中提到的更新有关

            此问题现已修复。新表格中的自定义函数现在可以感知上下文,并且不会像以前那样过度缓存值。

            1. 本主题中提到的问题仍然存在,我的测试表明,Google sheet 每次仅在何时重新计算自定义函数

              • 函数直接调用的值已更改。

              函数 getCellValue(sheetName,row,col) { var ss=SpreadsheetApp.getActiveSpreadsheet(); var sh = ss.getSheetByName(sheetName); 返回 sh.getRange(row, col).getValue(); }


              黄色单元格中任何值的更改将导致重新计算自定义函数;函数会忽略真实数据源值的变化。

              • 包含单元格位置的函数在工作表中已更改。前任。在上方或左侧插入/删除行/列。

            【讨论】:

              【解决方案16】:

              我不想有一个虚拟参数。 YMMV 关于这个。

              1 一个“项目列表”单元格,一个是“刷新”

              2 带有“onEdit”的脚本,如果单元格是“刷新”:

              a)清空文档缓存

              b)用外部数据填充文档缓存(在我的例子中是一个表)

              c)对于所有带有我的 'getStockoData(...' 自定义函数的单元格

              • 得到公式

              • 设置'=0'

              • 设置公式

              d)将 (1) 中的单元格设置为“Ready”

              这确实会刷新您想要的位,但速度不快。

              【讨论】:

                【解决方案17】:

                我从1:44 关注了这个video,这对我有用。

                您应该使用特定的更新函数,初始化“当前时间”变量并将这个永久更新的变量传递给您的自定义函数。然后转到触发器并为更新功能设置“每分钟”时间驱动的触发器(或选择其他时间间隔进行更新)。

                代码:

                function update() {
                  var dt = new Date();
                  var ts = dt.toLocaleTimeString();
                  var cellVal = '=CustomFunction("'+ ts + '")';
                  SpreadsheetApp.getActiveSheet().getRange('A1').setValue(cellVal);
                }
                

                【讨论】:

                  【解决方案18】:

                  今天我解决了这个问题

                  1. 向我的函数添加另一个参数:
                  function MY_FUNC(a, b, additional_param) { /* ... */ }
                  
                  1. 也为此参数添加一个值,引用一个单元格:
                  =MY_FUNC("a", "b", A1)
                  
                  1. 并在引用的单元格 (A1) 中放置一个复选框。

                  现在,只需单击一下(在复选框上)即可强制重新计算我的函数。

                  【讨论】:

                    【解决方案19】:

                    正如@Brionius 所说,在函数上添加一个额外的动态参数。如果你使用 now() 你可能会遇到超时问题,让更新慢一点...

                    cell A1 = int(now()*1000)
                    cell A2 = function(args..., A1)
                    

                    【讨论】:

                    • 它不再起作用了。如果您尝试以这种方式执行此操作,您将看到错误“此函数不允许使用 NOW()、RAND() 或 RANDBETWEEN() 引用单元格”
                    【解决方案20】:

                    如果您编写了自定义函数并在电子表格中将其用作公式,则每次打开电子表格或修改任何引用单元格时,都会重新计算公式。

                    如果您只想继续盯着电子表格并希望其值发生变化,请考虑添加一个定时触发器来更新单元格。阅读有关触发器的更多信息here

                    【讨论】:

                    • 那就太好了,只是它不起作用...重新加载页面不会刷新值。此外,删除单元格并重新进入相​​同的函数调用,仍然保留旧值。如果我从另一个单元格中完全调用相同的函数,它将显示新值,但不在旧单元格中。
                    猜你喜欢
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 2023-02-25
                    • 2020-07-13
                    相关资源
                    最近更新 更多