Google Script - 超出最大执行时间

ton*_*ike 2 javascript google-apps-script

我已经构建了一个从多个电子表格中提取并计算总值的脚本。基本上发生的情况是多个员工将为特定客户输入在特定任务上执行的时间。然后我想计算在主电子表格上为特定客户完成了多少工作并提取一些分析。

因此,在主电子表格上,每个客户都有一个选项卡。此函数从主电子表格中获取工作表名称并创建所有客户名称的数组。我这样做的原因是,如果创建了一个新选项卡,该客户名称将自动包含在客户数组中。

function getCurrentCustomers() {
  var sheets = servicesSpreadsheet.getSheets();
  for (var i=0 ; i < sheets.length ; i++) {
    if (sheets[i].getName() != "Services" && sheets[i].getName() != "Employee Files") {
      currentCustomers.push(sheets[i].getName());
    };
  };
};
Run Code Online (Sandbox Code Playgroud)

下一个函数查看特定 Google Drive 文件夹中的所有文件并返回数组中的 ID。这允许我创建存储在此特定文件夹中的员工电子表格的副本,并且该电子表格的值将在更改时自动计算。

function listFilesInFolder() {
  var folder = DriveApp.getFolderById("0B7zrWIHovJrKVXlsaGx0d2NFT2c");
  var contents = folder.getFiles();

  var cnt = 0;
  var file;

  while (contents.hasNext()) {
    //Finds the file in the specified folder
    var file = contents.next();
    //Increases the count
    cnt++;
    //Gets the Id of the file
    data = [
      file.getName(),
      file.getId()
      ];
    //Appends it to the employeeId list
    employeeIds.push(data);
  };
  return employeeIds;
};
Run Code Online (Sandbox Code Playgroud)

最后一个函数会大大减慢它的速度。

首先,我为所有可能的服务创建一个数组。不幸的是,有137 个单独的服务。

然后我遍历所有客户。对于每个客户,我遍历每项服务以查看它是否出现在任何员工电子表格中。我认为有一种更有效的方法可以做到这一点。谷歌脚本在我什至没有通过一个完整的客户之前就超时了。此外,我什至还没有包括所有员工的电子表格。我只是使用虚拟数据进行测试。

function calculateNumbers(){
  var allServices = servicesSpreadsheet.getSheetByName("Services").getRange("Z2:Z137").getValues();
  Logger.log(allServices);
  Logger.log(allServices[0][0]);
  employeeList = listFilesInFolder();

  //Gets services spreadsheet range


  /*Loops through all of the current customers (currentCustomers comes from function getCurrentCustomers)*/
  for (var c = 0; c < currentCustomers.length; c++) {

    var currentCustomer = currentCustomers[c];
    var lastColumn = servicesSpreadsheet.getSheetByName(currentCustomer).getLastColumn();
    var servicesRange = SpreadsheetApp.openById("1X3RRR3UVeot-DYCyXOsfVo0DoKjHezltwBPwUm8ZYig").getSheetByName(currentCustomer).getRange("A4:BC227").getValues();  

    //Loops through all of the services
    var serviceTotal = 0;    
    for (var service = 0; service < allServices.length; service++){

      //Loops through employee spreadsheet Ids
      for (var i = 0; i < employeeList.length; i++) {
        //Get employee spreadsheet ID
        var spreadsheetId = employeeList[i][1];

        //Open the employee spreadsheet by ID
        var employeeSpreadsheet = SpreadsheetApp.openById(spreadsheetId);

        //Get the sheets from the particular employee spreadsheet
        var sheets = employeeSpreadsheet.getSheets();

        //Gets the name of each sheet in the employee spreadsheet
        var sheetsName = [];
        for (var j = 0; j < sheets.length; j++) {
          sheetsName.push(sheets[j].getName());
        };

        //Loops through all of the sheets in an employee spreadsheet ignoring the Services spreadsheet
        for (var q = 0; q < sheetsName.length; q++) {
          if (sheetsName[q] != "Services") {

            //Gets the range of the spreadsheet
            var range = employeeSpreadsheet.getSheetByName(sheetsName[q]).getRange("A5:E1000").getValues();

            //Loops through the range to see if range matches service and customer
            for (var r = 0; r < range.length; r++) {
              if (range[r][3] == allServices[service][0] && range[r][1] == currentCustomer) {
                serviceTotal += range[r][4];
              }; 
            };
          };
        };         
      };

      //Adds the service total to the correct customer's spreadsheet
      for (var serviceServices = 4; serviceServices <= servicesRange.length; serviceServices++){
        var end = 0;
        if (end > 0) {break}
        else if (allServices[service][0] == servicesSpreadsheet.getSheetByName(currentCustomer).getRange(serviceServices,1).getValues()) {          
          servicesSpreadsheet.getSheetByName(currentCustomer).getRange(serviceServices,6).setValue(serviceTotal);
          end += 1;
        };
      };
    };

  }; 
};
Run Code Online (Sandbox Code Playgroud)

这就是员工电子表格的样子

这是主电子表格上客户工作表的一部分

第一张图片显示了员工电子表格的样子。第二个显示单个客户表的外观。主电子表格上有许多客户工作表。

您会注意到的一件事是,每个服务都有一个类别。我在想也许检查类别,然后检查特定的服务。有23个类别。

我还希望有一种方法可以只查看实际已经完成工作的服务。如果没有人做过广告系列设置,也许可以忽略。

任何帮助将不胜感激。我为冗长的帖子道歉!

Dou*_*ell 5

您在循环内多次调用服务。这些可能每次最多需要几秒钟,并且通常被认为非常慢。这违反了Apps Script 最佳实践,因为它会大大增加您的执行时间。

TL;DR:您可能会调用 Apps 脚本服务数千次。每个调用最多可能需要几秒钟才能执行。您需要在循环之外缓存您的服务调用,否则您的性能将受到严重影响。

例子

1:

    if (sheets[i].getName() != "Services" && sheets[i].getName() != "Employee Files")
Run Code Online (Sandbox Code Playgroud)

使用工作表名称创建并设置一次变量,并检查它而不是调用该getName()方法两次。这不是什么大问题,但会增加执行时间。

2:

这是一个大问题,因为它在您的循环中深入一层 calculateNumbers

var lastColumn = servicesSpreadsheet.getSheetByName(currentCustomer).getLastColumn();
var servicesRange = SpreadsheetApp.openById("1X3RRR3UVeot-DYCyXOsfVo0DoKjHezltwBPwUm8ZYig").getSheetByName(currentCustomer).getRange("A4:BC227").getValues();
Run Code Online (Sandbox Code Playgroud)

2a: 您正在打开一个新的电子表格,打开一个新的工作表,然后为同一张工作表获取一个范围,并为您的servicesRange. 这些服务调用会很快堆积起来,并增加您的执行时间。

2b: 我看到你得到了lastColumn,但我没有看到它在任何地方使用?也许我错过了一些东西,但是在为每个循环进行服务调用时它没有被使用会增加你的执行时间。

3:

这是巨大的,您可能会在此处调用 Apps Script 服务数千或数万次。这个片段已经有两个循环级别深了。

//Loops through all of the sheets in an employee spreadsheet ignoring the Services spreadsheet
        for (var q = 0; q < sheetsName.length; q++) {
          if (sheetsName[q] != "Services") {

            //Gets the range of the spreadsheet
            var range = employeeSpreadsheet.getSheetByName(sheetsName[q]).getRange("A5:E1000").getValues();

            //Loops through the range to see if range matches service and customer
            for (var r = 0; r < range.length; r++) {
              if (range[r][3] == allServices[service][0] && range[r][1] == currentCustomer) {
                serviceTotal += range[r][4];
              }; 
            };
          };
        };         
      };

      //Adds the service total to the correct customer's spreadsheet
      for (var serviceServices = 4; serviceServices <= servicesRange.length; serviceServices++){
        var end = 0;
        if (end > 0) {break}
        else if (allServices[service][0] == servicesSpreadsheet.getSheetByName(currentCustomer).getRange(serviceServices,1).getValues()) {          
          servicesSpreadsheet.getSheetByName(currentCustomer).getRange(serviceServices,6).setValue(serviceTotal);
          end += 1;
        };
      };
    };
Run Code Online (Sandbox Code Playgroud)

您在调用相同服务的嵌套循环中有多层嵌套循环。如果您有一个三次嵌套循环,每个级别迭代 10 次,并且底层每个循环调用一次服务。您将调用 Apps 脚本服务 1,000 次。保守地说,假设每次服务调用 0.5 秒,您已经有 8.3 分钟的执行时间。

缓存您的服务调用,并执行批量操作。