简体   繁体   English

Google脚本-超出了最大执行时间

[英]Google Script - exceeded maximum execution time

I have built a script that pulls from multiple spreadsheets and calculates total values. 我建立了一个脚本,该脚本从多个电子表格中提取并计算总值。 Basically what happens is multiple employees will input hours performed on a particular task for a particular customer. 基本上,发生的事情是多个员工将输入为特定客户执行特定任务的时间。 I then want to calculate how much work was done for a particular customer on a master spreadsheet and pull some analytics. 然后,我想在主电子表格上计算为特定客户完成了多少工作,并进行了一些分析。

So on the master spreadsheet, there is a tab for each customer. 因此,在主电子表格上,每个客户都有一个选项卡。 This function takes the sheet names from the master spreadsheet and creates an array of all of the customer names. 此功能从主电子表格中获取工作表名称,并创建一个包含所有客户名称的数组。 The reason why I do this is so that if a new tab is created, that customer name will automatically be included in the customer array. 我这样做的原因是,如果创建了新选项卡,则该客户名称将自动包含在客户数组中。

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());
    };
  };
};

The next function takes a look at all of the files in a particular Google Drive folder and returns the IDs in an array. 下一个功能将查看特定Google Drive文件夹中的所有文件,并以数组形式返回ID。 This allows me to create a copy of an employee spreadsheet stored in this particular folder, and that spreadsheet's values will automatically be calculated as they change. 这使我可以创建存储在此特定文件夹中的员工电子表格的副本,并且该电子表格的值将在更改时自动进行计算。

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;
};

The last function is the one that is slowing it down a great deal. 最后一个功能是大大降低了它的速度。

First I create an array for all of the possible services. 首先,我为所有可能的服务创建一个数组。 Unfortunately there are 137 individual services. 不幸的是,这里有137个独立服务。

Then I loop through all of the customers. 然后我遍历所有客户。 For each customer, I loop through every service to see if it appears on any of the employees spreadsheets. 对于每位客户,我都会遍历每项服务,以查看其是否出现在任何员工电子表格中。 I am thinking there is a more efficient way to do this. 我认为有一种更有效的方法可以做到这一点。 The Google Script times out before I even get through one full customer. Google脚本在我获得一位完整客户之前就超时了。 Also, I haven't even included the spreadsheets for all of the employees yet. 另外,我什至没有包括所有员工的电子表格。 I am just testing using dummy data. 我只是在测试使用伪数据。

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;
        };
      };
    };

  }; 
};

这是员工电子表格的外观

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

The first image shows what an employee spreadsheet looks like. 第一张图片显示了员工电子表格的外观。 The second shows what an individual customer sheet looks like. 第二个显示单个客户表的外观。 There are many customer sheets on the master spreadsheet. 主电子表格上有许多客户表。

One of the things you will notice, is that each service has a category. 您会注意到的一件事是,每个服务都有一个类别。 I was thinking maybe to check the category and then check the particular service. 我当时想也许要检查类别,然后再检查特定的服务。 There are 23 categories. 有23个类别。

I was also hoping there was a way to only look at services that have actually had work done on them. 我还希望有一种方法可以只查看实际上已经对它们完成工作的服务。 If no one has ever done a Campaign Setup, maybe that could be ignored. 如果没有人进行过“广告系列设置”,则可能会被忽略。

Any help would be greatly appreciated. 任何帮助将不胜感激。 I apologize for the lengthy post! 我为冗长的帖子表示歉意!

You are calling services inside of loops multiple times. 您正在循环内多次调用服务。 These can take up to a couple seconds each, and are generally considered to be very slow. 这些每次最多可能需要几秒钟,通常被认为非常慢。 This is against Apps Script best practice as it greatly increases your execution time. 这违反了Apps Script的最佳做法,因为它大大增加了您的执行时间。

TL;DR: You are calling Apps Script services potentially thousands of times. TL; DR:您可能要调用数千次Apps Script服务。 Each call can take up to a couple seconds to execute. 每次通话最多可能需要几秒钟才能执行。 You need to cache your service calls outside of your loops, otherwise your performance is going to suffer horribly. 您需要将服务调用缓存在循环之外,否则您的性能将受到严重影响。

Examples : 例子

1: 1:

  if (sheets[i].getName() != "Services" && sheets[i].getName() != "Employee Files") 

Create and set a variable with the sheet name once, and check that instead of calling the getName() method twice. 创建并设置一次具有工作表名称的变量,然后检查该变量,而不是两次调用getName()方法。 This isn't a huge deal, but will increase execution time. 这不是什么大问题,但是会增加执行时间。

2: 2:

This is a biggie, as it's one level deep in your loop in calculateNumbers 这是一个大问题,因为它在您的循环中calculateNumbers

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

2a: You are opening a new spreadsheet, opening a new worksheet, and then getting a range for the same sheet, and getting the values of that range once per loop for your servicesRange . 2A:您正在打开一个新的电子表格,打开一个新的工作表,然后得到一个范围在同一张纸上,每环获得该范围内的值,当您的servicesRange These service calls will stack up quick, and bloat your execution time. 这些服务调用将迅速堆叠,并会使您的执行时间膨胀。

2b: I see you are getting the lastColumn , but I don't see it used anywhere? 2b:我看到您正在获取lastColumn ,但是我在任何地方都看不到它吗? Maybe I missed something, but it going unused while making a service call for each loop will add even more to your execution time. 也许我错过了一些东西,但是在为每个循环进行服务调用时未使用它会增加您的执行时间。

3: 3:

This is massive, you are potentially calling Apps Script services thousands or tens of thousands of times here. 这是巨大的,您可能在这里成千上万次调用Apps Script服务。 This snippet is already two loop levels deep. 此代码段已经深了两个循环级别。

//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;
        };
      };
    };

You have multi-level nested loops in nested loops calling the same services. 您在调用相同服务的嵌套循环中具有多级嵌套循环。 If you had a thrice nested loop with each level being iterated 10 times, and the bottom level calling a service once per loop. 如果您有一个三次嵌套的循环,每个循环被迭代10次,而底层则每个循环调用一次服务。 You would be calling an Apps Script service 1,000 times. 您将调用一次Apps Script服务1,000次。 Being conservative, at let's say 0.5 seconds per service call, you would already have 8.3 minutes of execution time. 保守地说,假设每个服务调用为0.5秒, 那么您已经有8.3分钟的执行时间。

Cache your service calls, and perform bulk operations. 缓存您的服务呼叫,并执行批量操作。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM