【问题标题】:Update named range automatically自动更新命名范围
【发布时间】:2018-12-05 09:56:57
【问题描述】:

我一直很难弄清楚这一点。我意识到对于那些遵循 GAS 标签的人来说,这可能比平时更基本,但是非常感谢任何帮助。

如果我将更大的任务分解成组件,我现在的目标是自动更新几个命名范围。

电子表格上有一个名为“DataImport”的选项卡。 DataImport 有 10 列,所有 1000 行长。每列都有一个命名范围,例如卷心菜 (A2:A1000)、狗 (B2:B1000) 等。

There is a script attached to a new menu item "Update Data" that when selected imports a csv file into DataImport tab meaning that the length of the data set will grow.

如何告诉工作表将每个命名范围更新为数据长度?因此,如果原始命名范围“卷心菜”是 A2:A1000,并且在更新之后,数据现在实际上与 A2:A1500 一样长,我将如何告诉工作表更新范围卷心菜?

我在网上找到了一段sn-p的代码,开始摆弄:

function testNamedRange() {
   var ss = SpreadsheetApp.getActiveSpreadsheet();
   var range = ss.getRange('DataImport!A:A');
   var data_len = range.length;
   SpreadsheetApp.getUi().alert(data_len); // "alert just gives "undefined"
   ss.setNamedRange('TestRange', range);
   var rangeCheck = ss.getRangeByName('TestRange');
   var rangeCheckName = rangeCheck.getA1Notation();
}

我的想法是,如果我可以使用自定义菜单功能获取更新后的数据长度,然后我可以使用 setNamedRange() 来更新卷心菜范围。

我真的迷路了,我想这比我想象的要简单。

如何将命名范围的卷心菜更新为 UpdateData 列 A 中的数据长度?

【问题讨论】:

  • 您的意思是在使用新数据更新工作表之后(将新数据附加到现有数据)?然后在运行更新脚本后,您可以简单地使用函数:getRangeByName(cabbages) 来检索新的值范围。希望有帮助!
  • 道格,当你说“我想这比我想象的要简单”时,你错了。您不能修改命名范围。您必须先删除它们,然后重新添加新定义。要获得一个想法,请参阅:script.google.com/macros/d/MpU2iJHZ63Z00Oijw_8tYTxcaARnGcqu_/…
  • 没错。根据新高度重新创建范围。
  • 为什么不只根据列来定义范围?例如。而不是 Cabbages A2:A1000 将其定义为 A2:A。通常,如果您将行插入命名范围,范围定义会自动更改。

标签: google-apps-script google-sheets


【解决方案1】:

编辑:重要

在公式中使用 INDIRECT("rangeName") 而不仅仅是 rangeName。 以编程方式扩展范围的唯一方法是删除它,然后使用新定义将其添加回来。此过程会破坏公式并返回 #ref 而不是范围名称。这应该是一个不必要的工作。如果您同意,请在https://code.google.com/p/google-apps-script-issues/issues/detail?id=5048

上加注星标和问题跟踪器
=sum(indirect("test1"),indirect("test3"))

通过检查命名范围中的最后一行是否与工作表中的最后一行相同来模拟开放式命名范围。如果不是,则调整命名范围,使命名范围中的最后一行与工作表中的最后一行相同。

应该与 on open 和 on change 事件一起使用。

function updateOpenEndedNamedRanges() {
  var ss = SpreadsheetApp.getActiveSpreadsheet();

  // names of open-ended ranges
  var openEndedRangeNames = ["test1", "test2", "test3", "s2test1" ];

  for(i in openEndedRangeNames) {
    var rName = openEndedRangeNames[i];
    try{
      var r = ss.getRangeByName(rName);
      }
    catch(err) {
      GmailApp.sendEmail("me@gmail.com",
                         rName + " -- Cannot find",
         "Trying to update open-ended ranges after rows added. \n"
         + "Unable to find range name-- "+ rName 
         + " -- in ss ( " + ss.getName() + " ) "
         + "\n If it is not needed please remove it " 
         + "from array \n openEndedRangeNames[] \n in the function \n"
         + "updateOpenEndedNamedRanges()");
      continue;
      }
    var rlr = r.getLastRow();
    var s = r.getSheet();
    var slr = s.getMaxRows();
    if(rlr==slr ) continue;
    var rfr = r.getRow();
    var rfc = r.getColumn();
    var rnc = r.getNumColumns();
    var rnr = slr - rfr + 1;
    ss.removeNamedRange(rName);
    ss.setNamedRange( rName, s.getRange(rfr, rfc, rnr, rnc ));
    }
}


function ssChangeEvent(change) {
 // changeType (EDIT, INSERT_ROW, INSERT_COLUMN, REMOVE_ROW, 
 //      REMOVE_COLUMN, INSERT_GRID, REMOVE_GRID, or OTHER)
  switch(change.changeType) {
    case "INSERT_ROW":
      updateOpenEndedNamedRanges();
      break;
    default:
      Logger.log(change.changeType + " detected. No action taken ");
      }
  }

设置 ssChangeEvent(change) 在添加行时运行

资源>此项目触发

【讨论】:

  • 编辑后的版本增加了一些错误检查功能,并将 upssChangeEvent(change) 设置为触发器将使其在添加行时自动运行。
  • 这太棒了!它正在工作。非常感谢您,我非常感谢您的回答。鉴于我即将完成的任务,我希望我会大量使用这段代码,所以它非常有帮助。
  • 唉!有一个我没有准备好的副作用。所有引用命名范围的公式现在都显示#REF,它们以前引用了范围的名称。你以前遇到过这种情况吗?有解决办法吗?我可以作为一个单独的问题发布
  • 我没有直接的答案。需要稍微思考一下。一个单独的问题可能是一个好主意,以便其他人可以尝试一下,并希望尽快解决它。根据您拥有的公式数量,间接公式可能是防止重置引用的选项。
  • 现在正在做一些研究。也感谢您的回答,这让我离目标更近了一步
【解决方案2】:

提供我写的这个函数来处理命名范围的动态调整:

function resizeNamedRange(rangeName, addRows, addCols) {
/* expands (or decreases) a range of a named range. 
rows and columns to add can be negative (to decrease range of name). Params:
rangeName - name of range to resize.
addRows - number of rows to add (subtract) from current range.
addCols - number of columns to add (subtract) from current range.
Call example: resizeNamedRange("Products",1,0);
   */

  var sh = SpreadsheetApp.getActiveSpreadsheet();

  try {
    var oldRange = sh.getRangeByName(rangeName);
    var numRows = oldRange.getNumRows() + addRows;
    var numCols = oldRange.getNumColumns() + addCols;
    if (numRows < 1 || numCols <1) {
      Logger.log("Can't resize a named range: minimum range size is 1x1.");
      return;
    }
    sh.setNamedRange(rangeName, oldRange.offset(0,0,numRows, numCols));

  } catch (e) {
    Logger.log ("Failed resizing named range: %s. Make sure range name exists.", rangeName); 
  }
}

【讨论】:

    【解决方案3】:

    也许我遗漏了一些东西,但下面的函数需要一个范围名和它应该包含的范围。如果范围名称已经存在,它将范围更新为传递的值。如果范围名称不存在,则使用传递的范围值创建它。

    同样关于“#REF!”表中的问题。您可以进行查找和替换并勾选“在公式中查找”框。输入“#REF!”在查找和替换框中的命名范围名称。这假设只有一个命名范围被删除并且没有其他不相关的#REF!错误。这种方法帮助我在短短几分钟内修复了一个错误分布在 8 个不同工作表和 20 多个公式上的电子表格。

    /**
     * Corrects a named range to reflect the passed range or creates it if it doesn't exist.
     *
     * @param {string} String name of the Named Range
     * @param {range} Range (not string, but range type from Sheet class)
     * @return {void || range} returns void if Named Range had to be created, returns NamedRange class if just updated. This needs improvement.
     * @customfunction
     */
    function fixNamedRange (name, range) {
      var i = 0;
      var ss = SpreadsheetApp
               .getActiveSpreadsheet();
      var ssNamedRanges = ss.getNamedRanges();
    
      for (i = 0; i<ssNamedRanges.length && ssNamedRanges[i].getName() != name; i++) {};
    
      if (i == ssNamedRanges.length) {
        return (ss.setNamedRange(name, range));
      } else {
        return (ssNamedRanges[i].setRange(range));
      }
    }
    

    【讨论】:

      【解决方案4】:

      我找到了解决办法!

      我有一个带有下拉列表的单元格,其中包含公司在系统上注册的所有客户,如果我们输入的名称没有出现在列表中,则执行 newClient 函数。基本上我们使用 SpreadsheetApp.getUi() 来保存新信息。一旦我们介绍了客户数据,函数就会在客户的工作表上创建一个新行,并在最后一行的提示中输入信息。完成后,自动更新下拉列表。

      真正的函数在一个大函数内部,如果需要,它会调用 newClient,所以真正的函数是 newClient(client, clients),在示例中我放置了变量以使其更容易。

      我希望它有效!

      function newClient() {
        var ss = SpreadsheetApp.getActive().getSheetByName('Clients'); // Sheet with all the client information, name, city, country...
        var client = 'New company';
        var clients = ss.getRange('A2:A').getValues();
        var ui = SpreadsheetApp.getUi();
        ui.alert('Client '+client+' does not exist, enter the next information.');
        var city = ui.prompt('Enter city').getResponseText();
        var country = ui.prompt('Enter country').getResponseText();
        client = client.toUpperCase();
        city = city.toUpperCase();
        country = country.toUpperCase();
        ui.alert('Here is the information you entered about '+client+':'+'\n\n'+'City: '+city+'\n\n'+'Country: '+country)
        ss.insertRowAfter(ss.getLastRow()); // Insert a row after the last client
        ss.getRange('A'+(clients.length+2)).setValue(client); // Let's suppose we have 150 clients, on the first row we have the titles Client, City, Country, then we have the 150 clients so the last client is on row 151, that's why we enter the new one on the 152
        ss.getRange('B'+(clients.length+2)).setValue(city);
        ss.getRange('C'+(clients.length+2)).setValue(country);
        var namedRanges = SpreadsheetApp.getActive().getNamedRanges(); // We get all the named ranges in an array
        for (var i = 0; i < namedRanges.length; i++) {
          var name = namedRanges[0].getName();
          if (name == 'Clients') { // All the clients are stored on 'Clients' named range
            var range = ss.getRange('A2:A'); // Update the range of the 'Clients' named range
            namedRanges[i].setRange(range);
          }
        }
        ui.alert('Client created, you can find it on the drop down list.');
      }
      

      【讨论】:

        猜你喜欢
        • 2010-11-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多