【问题标题】:How can I improve the performance of this Google Sheets custom function?如何提高此 Google 表格自定义功能的性能?
【发布时间】:2022-10-05 17:33:08
【问题描述】:

我有一个 Google Sheets 项目使用这个自定义函数 50-100 次,所以我试图让这个函数尽可能高效。该函数过滤写入 INPUT 工作表的数据(通过 Google API),然后为数据的子集绘制表格。

我在这里提供了一个工作示例电子表格:https://docs.google.com/spreadsheets/d/1KVjDl0Ix2bnlPqPEnsY4wh34MIPeayDPk3cq7DdU3g4/edit?usp=sharing

\'META\' 工作表只是通过更改单元格 A1 中的值来触发自定义函数运行(即模拟通过 Google API 填充的 INPUT 表),这是自定义函数调用的参数。

\'INPUT\' 表包含示例输入数据。该函数不使用灰色列(组名称、字段名称、类型、连接 ID、VLookup 值):

Instance Id Group name Group Id Field name Field Id Type Value File Id Role Concatenated Id VLookup value
Instance1A Widgets 91c7db0a-c52a-407d-869a-af8ba8bf8ba7 Field 1 09456c1a-abb4-4e81-94bd-7ce4c88afffc CURRENCY 100 Pilot_File Pilot 09456c1a-abb4-4e81-94bd-7ce4c88afffcInstance1A 100
Instance1A Widgets 91c7db0a-c52a-407d-869a-af8ba8bf8ba7 Field 2 474f6395-83a7-4c2b-aa5a-ceb00e200f8e CURRENCY 200 Pilot_File Pilot 474f6395-83a7-4c2b-aa5a-ceb00e200f8eInstance1A 200
Instance1A Widgets 91c7db0a-c52a-407d-869a-af8ba8bf8ba7 Field 3 ac64e001-fe85-400a-92e4-69cebf1c260d CURRENCY 300 Pilot_File Pilot ac64e001-fe85-400a-92e4-69cebf1c260dInstance1A 300
Instance1B Widgets 91c7db0a-c52a-407d-869a-af8ba8bf8ba7 Field 1 09456c1a-abb4-4e81-94bd-7ce4c88afffc CURRENCY 110 Pilot_File Pilot 09456c1a-abb4-4e81-94bd-7ce4c88afffcInstance1B 110
Instance1B Widgets 91c7db0a-c52a-407d-869a-af8ba8bf8ba7 Field 2 474f6395-83a7-4c2b-aa5a-ceb00e200f8e CURRENCY 220 Pilot_File Pilot 474f6395-83a7-4c2b-aa5a-ceb00e200f8eInstance1B 220
Instance1B Widgets 91c7db0a-c52a-407d-869a-af8ba8bf8ba7 Field 3 ac64e001-fe85-400a-92e4-69cebf1c260d CURRENCY 330 Pilot_File Pilot ac64e001-fe85-400a-92e4-69cebf1c260dInstance1B 330
Instance2A Widgets 91c7db0a-c52a-407d-869a-af8ba8bf8ba7 Field 1 09456c1a-abb4-4e81-94bd-7ce4c88afffc CURRENCY 1000 Co-PIlot_File Co-Pilot 09456c1a-abb4-4e81-94bd-7ce4c88afffcInstance2A 1000
Instance2A Widgets 91c7db0a-c52a-407d-869a-af8ba8bf8ba7 Field 2 474f6395-83a7-4c2b-aa5a-ceb00e200f8e CURRENCY 2000 Co-PIlot_File Co-Pilot 474f6395-83a7-4c2b-aa5a-ceb00e200f8eInstance2A 2000
Instance2A Widgets 91c7db0a-c52a-407d-869a-af8ba8bf8ba7 Field 3 ac64e001-fe85-400a-92e4-69cebf1c260d CURRENCY 3000 Co-PIlot_File Co-Pilot ac64e001-fe85-400a-92e4-69cebf1c260dInstance2A 3000

\'TABLE_CONFIG\' 表包含结果表的配置属性。该函数未使用灰色列(描述):

Field Id Description Desired table field column Group Id
09456c1a-abb4-4e81-94bd-7ce4c88afffc Field 1 1 91c7db0a-c52a-407d-869a-af8ba8bf8ba7
474f6395-83a7-4c2b-aa5a-ceb00e200f8e Field 2 2 91c7db0a-c52a-407d-869a-af8ba8bf8ba7
ac64e001-fe85-400a-92e4-69cebf1c260d Field 3 3 91c7db0a-c52a-407d-869a-af8ba8bf8ba7

\'RESULTS_Pilot\' 和 \'RESULTS_Co-Pilot\' 表是如何从整个电子表格的不同位置调用自定义函数的示例。标题是静态的。该函数是这样调用的:

=getTable(\"91c7db0a-c52a-407d-869a-af8ba8bf8ba7\", \"TABLE_CONFIG\", \"Pilot\", META!A1)

结果表必须始终以示例中显示的格式返回以显示:

Instance Id Field 1 Field 2 Field 3 File Id
Instance1A 100 200 300 Pilot_File
Instance1B 110 220 330 Pilot_File

我对编码很陌生,对 Google Apps 脚本也很陌生。自定义函数正在工作,但我希望能提供加速它的提示,或者我的代码效率低下或冗余的指针。

谢谢你。

// Filters INPUT by Group Id and Role. Called from getTable().
function filterInput(group, role) {
  var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(\"INPUT\");
  var range = sheet.getDataRange();
  var values = range.getValues();
  var results = [];
  values.forEach(function (row) {
    if (row[2] === group && row[8] === role) {
      results.push(row);
    }
  });
  return results;
}

// Builds matrix. Called from getTable().
let generateMatrix = function (m, n, value) {
  let matrix = [];
  for (let i = 0; i < m; i++) {
    let row = [];
    for (let j = 0; j < n; j++) {
      row.push(value);
    }
    matrix.push(row);
  }
  return matrix;
};

// Main function called from RESULTS_Pilot and RESULTS_Co-Pilot worksheets
function getTable(groupUUID, configSheetName, role) {
  // Filter INPUT tab to get only rows for group and role
  values = filterInput(groupUUID, role);
  // If filtered INPUT contains 0 rows, return empty string
  if (values.length === 0) {
    Logger.log(\"There are no matching rows in INPUT.\");
    return (\"\");
  }
  else {
    Logger.log(\"There are \" + values.length + \" filtered rows for the specified group and role.\");
    var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(configSheetName);
    var range = sheet.getDataRange();
    var configValues = range.getValues();
    var configFields = 0;
    var rowNum = 0;
    var uniqueInstanceRows = 0; // Need to find the number of unique instance rows (i.e. unique Instance Ids) to build table
    var colValues = []; // Create array with only first column values (Instance Ids) from filtered input rows
    for (i = 0; i < values.length; i++) {
      colValues.push(values[i][0]);
    }

    const unique = (value, index, self) => { return self.indexOf(value) === index; } // Get only unique Instance Ids from array
    var Unique_List = colValues.filter(unique);
    uniqueInstanceRows = Unique_List.length; // Will be used to build empty table using generateMatrix()
    Logger.log(\"# of unique Instance Id rows: \" + uniqueInstanceRows);

    configValues.forEach(function (configRow) { // Count number of config fields for the group in TABLE_CONFIG worksheet
      if (configRow[3] === groupUUID && configRow[2] !== \"\") {
        configFields = configFields + 1;
      }
    })
    Logger.log(\"There are \" + configFields + \" config fields.\");

    // Generate table structure
    table = generateMatrix(uniqueInstanceRows + 1, configFields, \"\");

    // Fill first column in results table with unique Instance Ids
    for (i = 0; i < Unique_List.length; i++) {
      table[i][0] = Unique_List[i];
    }

    // Generate a table to store Instance Id / File Id pairs 
    pairsTable = generateMatrix(Unique_List.length, 2, \"\");
    values.forEach(function (row) {
      for (i = 0; i < Unique_List.length; i++) {
        if (row[0] === Unique_List[i]) {
          pairsTable[i][0] = Unique_List[i];
          pairsTable[i][1] = row[7];
        }
      }
    })

    // Fill last column in results table with corresponding File Ids
    table.forEach(function (row, index) {
      for (i = 0; i < pairsTable.length; i++) {
        if (row[0] === pairsTable[i][0]) {
          table[index][configFields + 1] = pairsTable[i][1];
        }
      }
    })

    // Populate results table with remaining group field values
    values.forEach(function (row) {
      configValues.forEach(function (configRow) {
        if (row[4] === configRow[0] && configRow[2] !== \"\") {
          for (i = 0; i <= Unique_List.length; i++) {
            if (row[0] === Unique_List[i]) {
              rowNum = i;
            }
          }
          let val = row[6];
          table[rowNum][configRow[2]] = val;
        }
      })
    })
    // Return results table
    return table;
  }
}
  • 如果您将输入表和预期输出表添加到问题中,您的问题可以大大改善。 Tables 是比电子表格更好的选择来显示您的数据结构。如果您共享电子表格,请确保还添加工作表的图像以避免问题结束,因为此处的问题必须是 self containedYour email address can also be accessed by the public,当您共享 Google 文件时。
  • 该功能实际上很慢吗?你似乎在使用数组,这意味着你已经越过了最大的乌龟陷阱。之后就是基本的js优化。从表面上看,我仍然不清楚每个函数的输入和预期输出是什么。您的问题可能更适合Code Review。我会注意到unique 可以用Set 重写,它对唯一数据有更好的支持。如果有的话,我想将电子表格/数据流更改为不调用自定义函数 100 次。我还会使用Cache 服务来避免重新计算相同的输入。
  • Edit 显示您如何调用每个函数并裁剪屏幕截图以仅显示数据(或在您已经显示表格时将其完全删除)。看来您正在为每一行调用=func(A1)。如果是这种情况,请考虑重写它以接受如下数组:=func(A1:A100)
  • @TheMaster 每个 \'RESULTS_\' 工作表只调用一次 getTable() 函数。然而,电子表格包含 50-100 个这些工作表,目前无法更改这种使用模式,因为对结果表数据执行了许多后续计算。
  • onEdit trigger 之类的东西不是更好吗?

标签: google-apps-script google-sheets


【解决方案1】:

使用这个漂亮的项目AlaSQLGS~

我们可以使用AlaSQL 来简化许多(imo)在数据库中比javascript/appscript 更好的数据操作功能。

这是一个您可以使用您的数据进行修改的示例:

const alasql = AlaSQLGS.load()

function Update_Results() {
  const groupUUID = '91c7db0a-c52a-407d-869a-af8ba8bf8ba7';
  const role = 'Pilot';
  const inputSheetName = 'INPUT';
  const outputSheetName = 'RESULTS_Pilot';

  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const inputSheet = ss.getSheetByName(inputSheetName);
  const outputSheet = ss.getSheetByName(outputSheetName)
  
  let data = inputSheet.getDataRange().getValues();
  data.shift() // Remove column names~
  
  // Create a Table and insert values into it.
  // I use \` so often, because you have Spaces and illegal sql column names.
  // Note that these are backticks, not single quotes.
  alasql(`
    CREATE TABLE data (
      \`Instance Id\` STRING,
      \`Group Id\` STRING,
      \`Field name\` STRING,
      \`Field Id\` STRING,
      \`Value\` INT,
      \`File Id\` STRING,
      \`Role\` STRING
    );
    SELECT [0] AS \`Instance Id\`
         , [2] AS \`Group Id\`
         , [3] AS \`Field name\`
         , [4] AS \`Field Id\`
         , [6] AS \`Value\`
         , [7] AS \`File Id\`
         , [8] AS \`Role\`
    INTO data
    FROM ?;
  `, [data])

  // Query the Table
  // MATRIX gives us an Array of Arrays instead of an Array of Objects.
  let output = alasql(`
    SELECT MATRIX
           \`Instance Id\`
         , \`Field name\` 
         , \`File Id\`
         , [Value]
      FROM data
     PIVOT (MAX([Value]) FOR \`Field name\`)
     WHERE \`Group Id\` = ?
       AND \`Role\` = ?
  `, [groupUUID, role]);
  
  // Add Column Names
  output.unshift(['Instance Id', 'File Id', ...output[0].slice(2).map((x,i) => `Field ${i+1}`)])
  
  // Insert the result into the sheet
  outputSheet.clearContents()
  outputSheet.getRange(1, 1, output.length, output[0].length).setValues(output);

  Logger.log(output)
}

记录器输出:

[[Instance Id, File Id, Field 1, Field 2, Field 3],
 [Instance1A, Pilot_File, 100.0, 200.0, 300.0],
 [Instance1B, Pilot_File, 110.0, 220.0, 330.0]]

GSheet 输出:

Instance Id File Id Field 1 Field 2 Field 3
Instance1A Pilot_File 100 200 300
Instance1B Pilot_File 110 220 330

【讨论】:

    【解决方案2】:

    当一个普通的电子表格公式似乎就足够了时,为什么你需要一个自定义函数呢?目前还不清楚。尝试使用 query()pivot 子句,如下所示:

    =query(INPUT!A1:K, "select A, max(G), H where I = 'Pilot' group by A, H pivot D", 1)

    =query(INPUT!A1:K, "select A, max(G), H where I = 'Co-Pilot' group by A, H pivot D", 1)

    这些公式与您当前的自定义函数结果完全匹配,它们应该运行得更快。

    【讨论】:

      猜你喜欢
      • 2017-01-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-07-16
      • 2017-01-14
      相关资源
      最近更新 更多