脚本需要 11 - 20 秒来查找 18,000 行数据集中的项目

Script is taking 11 - 20 seconds to lookup up an item in an 18,000 row data set

我有两本 Google sheets 练习册。

一个是 "master" 查找数据源,其关键字基于制造商商品编号,可以是从 1234 到 A-01/234-Name_1 的任何值。此 sheet,通过 SpreadsheetApp.openByUrl 引用,有 18,000 行和 13 列。关键列已转换为纯文本,sheet 按此列排序。

第二个是 "template",人们在这里输入他们需要对照大师查找的项目编号,通常一次输入 20 - 1500 个项目。

脚本在模板中。它非常慢,通常会在 30 分钟后超时。它是由其他人编写的,我是 App Script 的新手,但我想我已经设法理解脚本在做什么以及瓶颈在哪里发生。

它做了很多事情,但这是查找的主要内容:

var numrows = master.getDataRange().getNumRows();
var masterdata = master.getDataRange().getValues();
var itemnumberlist = template.getDataRange().getValues();
var retreiveddata = [];     
// iterate through the manf item number list to find all matches in the   
// master and return those matches to another sheet
 for (i = 1; i < template.getDataRange().getValues().length; i++) {
   for (j = 0; j < numrows; j++) {
     if (masterdata[j][1].toString() === itemnumberlist[i][1].toString()) { 
      retreiveddata.push(data[j]);
       anothersheet.appendRow(data[j]); 
     } 
   }
 } 

我使用 Logger.log() 确定每次通过 i 循环需要 11 - 19 秒,这看起来很疯狂。

我一直在进行一些 google 搜索,并且尝试了一些不同的方法...

首先,我尝试将找到的数据的写入移出 for 循环,这样脚本将首先完成所有读取,然后写入一大块,但我无法完全正确。我的两次尝试如下。

var mycounter = 0;
for (i = 0; i < template.getDataRange().getValues().length; i++) {
   for (j = 0; j < numrows; j++) {
     if (masterdata[j][0].toString() === itemnumberlist[i][0].toString()) { 
      retreiveddata.push(masterdata[j]);
      mycounter = mycounter + 1;
     } 
   }
 }

// Attempt 1 
// var myrange = retreiveddata.length;   
//  for(k = 0; k < myrange; k++) {
//    anothersheet.appendRow(retreiveddata.pop([k]);  
//    }

//Attempt 2
 var myotherrange = anothersheet.getRange(2,1,myothercounter, 13)  
 myotherrange.setValues(retreiveddata);  

我记不太清了,因为那是星期五,但我认为两次尝试都导致脚本试图将整个主文件写入 "anothersheet"。

所以我暂时搁置了这个,决定尝试别的东西。我试图在几个示例 spreadsheet 中重现该问题,但我无法这样做。相同的脚本在每次查找不到 1 秒的时间内通过我的 15,000 行样本 "master" 文件。我唯一能想到的是我使用了一个随机数作为我的密钥,而不是一个奇怪的文本字符串。

这让我想到也许我可以对主数据和要查找的值都使用哈希算法,但这提出了一系列其他问题。

我从另一个论坛借用了这些功能post:

function GetMD5Hash(value) {
  var rawHash = Utilities.computeDigest(Utilities.DigestAlgorithm.MD5, 
value);
  var txtHash = '';
    for (j = 0; j <rawHash.length; j++) {
   var hashVal = rawHash[j];
    if (hashVal < 0)
      hashVal += 256; 
    if (hashVal.toString(16).length == 1)
      txtHash += "0";
    txtHash += hashVal.toString(16);
     Utilities.sleep(100);
  }
    return txtHash;
}

function RangeGetMD5Hash(input) {
  if (input.map) {            // Test whether input is an array.
    return input.map(GetMD5Hash); // Recurse over array if so.
    Utilities.sleep(100);
  } else {
    return GetMD5Hash(input)
  }
}

我真的花了一整天的时间来获取我主传播中所有 18,000 个项目#s 的哈希值sheet。 GetMD5Hash 和 RangeGetMD5Hash 都不会 return 始终如一的值。我一次只能做几行。有时我会无限期地得到 "Loading..."。有时我会收到“#Name”,其中包含有关未定义 GetMD5Hash 的消息(尽管它在前一行上有效)。有时我会收到“#Error”消息,其中包含有关内部错误的消息。

此方法实际上将每个项目的查找时间减少到 2 - 3 秒(好多了,但不是很好)。但是,我无法让哈希函数始终如一地处理输入数据。

在这一点上,我非常沮丧并且落后于我的其他工作,所以我想我应该联系这些论坛上的聪明人并希望得到某种奇迹般的回应。

总而言之,我正在寻找关于这三项的建议:

  1. 我在尝试将写入移出 for 循环时做错了什么?
  2. 有没有办法更快地获取哈希值或使用不同的方法来实现相同的目标?
  3. 我还能尝试什么来帮助加快脚本速度?

如有任何建议,我们将不胜感激!

-曼迪

听起来您尝试将 appendRow() 调用移出循环似乎找到了正确的方法。任何时候你正在读取或写入一个 spreadsheet ,你可以预期单个调用需要 1 到 2 秒,所以当你得到匹配时,这会占用很多时间。将匹配项存储在数组中并一次写入它们是可行的方法。

我注意到的另一件事是您的脚本在实际的 for 循环条件语句中调用了 getValues()。条件语句在循环的每次迭代中每次都会执行,因此即使您没有匹配项,这也可能会浪费大量时间。

最后的调整可能会有所帮助,具体取决于您想要的行为。您可以在找到第一个匹配项后停止内部 for 循环,如果您只关心第一个匹配项或知道只会有一个匹配项,这将为您节省很多迭代。为此,请将“break”紧跟在 retreiveddata.push(masterdata[j]); 行之后。

要修复 getValues 问题,更改:

for (i = 1; i < template.getDataRange().getValues().length; i++) {

收件人:

for (i = 1; i < itemnumberlist.length; i++) {

并且修复了 appendRow 问题,包括 break 调用:

for (i = 1; i < itemnumberlist.length; i++) {
   for (j = 0; j < numrows; j++) {
     if (masterdata[j][0].toString() === itemnumberlist[i][0].toString()) { 
      retreiveddata.push(masterdata[j]);
      break; //stop searching after first match, move on to next item
     } 
   }
 }

 //make sure you have data to write before trying to write it.
 if(retreiveddata.length > 0){  
    var myotherrange = anothersheet.getRange(2,1,retreiveddata.length, retreiveddata[0].length);
    myotherrange.setValues(retreiveddata);  
 }

如果您在每次执行时为 "anothersheet" 重新使用相同的 sheet,您可能还需要调用 anothersheet.clear() 以在写入新结果之前擦除任何现有数据.

我会完全传递散列方法,比较字符串就是比较字符串,所以无论它们是散列还是实际部件号,我都不希望有显着差异。