2016-07-29 2 views
2

Я построил скрипт, который извлекает из нескольких электронных таблиц и вычисляет общие значения. В основном, что происходит, несколько сотрудников будут вводить часы, выполненные по конкретной задаче для конкретного клиента. Затем я хочу рассчитать, сколько работы было сделано для конкретного клиента на главной электронной таблице и вывести некоторые аналитики.Google 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()); 
    }; 
    }; 
}; 

Следующая функция принимает взглянуть на все файлы в определенной папке Google Drive и возвращает идентификаторы в массиве. Это позволяет мне создать копию таблицы сотрудников, хранящейся в этой конкретной папке, и что значения электронной таблицы будут автоматически вычисляться по мере их изменения.

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

Последняя функция - это то, что замедляет ее.

Сначала я создаю массив для всех возможных сервисов. К сожалению, есть отдельные услуги.

Затем я просматриваю всех клиентов. Для каждого клиента я просматриваю каждую службу, чтобы увидеть, отображается ли она на любой из таблиц сотрудников. Я думаю, что есть более эффективный способ сделать это. Сценарий Google истекает, прежде чем я даже пройду через одного полного клиента. Кроме того, я еще не включил электронные таблицы для всех сотрудников. Я просто тестирую фиктивные данные.

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

    }; 
}; 

This is what an Employee Spreadsheet Looks Like

This is part of a customer's sheet on the master spreadsheet

Первое изображение показывает, что работник таблицы выглядит. Второй показывает, как выглядит отдельный лист клиента. В основной таблице есть много листов клиента.

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

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

Любая помощь была бы принята с благодарностью. Прошу прощения за длинный пост!

ответ

4

Вы вызываете услуги внутри циклов несколько раз. Они могут занимать до нескольких секунд каждый, и обычно считаются очень медленными. Это против Apps Script best practice, так как это значительно увеличивает время выполнения.

TL; DR: Вы вызываете приложения Script Script потенциально тысячи раз. Для выполнения каждого вызова может потребоваться до двух секунд. Вам нужно кэшировать свои вызовы за пределами ваших циклов, иначе ваша производительность будет ужасно страдать.

Примеры:

1:

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

Создание и установить переменную с именем листа один раз, и проверить, что вместо вызова метода getName() дважды. Это не огромная сделка, но увеличит время выполнения.

2:

Это важная персона, так как это один уровень глубоко в цикле в calculateNumbers

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

2a: Вы открываете новую таблицу, открывая новый рабочий лист, а затем получая диапазон для одного листа и получая значения этого диапазона один раз за цикл для вашего servicesRange. Эти служебные вызовы быстро складываются и раздувают ваше время выполнения.

2b: Я вижу, вы получаете lastColumn, но я не вижу его использовать в любом месте? Возможно, я что-то пропустил, но он не используется, а вызов службы для каждого цикла добавит еще больше к вашему времени выполнения.

3:

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

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

У вас есть многоуровневые вложенные циклы в вложенных циклах, вызывающих одни и те же сервисы. Если у вас трижды вложенный цикл, каждый уровень которого повторяется 10 раз, а нижний уровень вызывает услугу один раз за цикл. Вы будете называть приложение Script Script 1000 раз. Будучи консервативным, скажем, 0,5 секунды за звонок службы, , у вас уже будет 8,3 минуты времени выполнения.

Закрепите свои служебные вызовы и выполните массовые операции.

+0

Wow! Это потрясающе. Я не могу поблагодарить вас за то, что вы нашли время ответить на все эти вопросы. Я сделаю изменения и посмотрю, что такое время выполнения. (Очевидно) Я очень новичок в кодировании. Это самая сложная вещь, которую я создал, и она получилась невероятно неэффективно, но вы помогли мне понять, почему. Если бы я мог купить вам выпить, я бы через секунду. Спасибо @DouglasGaskell! Честно говоря, это было так долго, я больше не ожидал, что никто не ответит, но подумал, что я сделаю это. – tonestrike

Смежные вопросы