Google Apps Script多表多列行遍历效率过低优化方案咨询
优化核心思路
你当前脚本性能差的核心原因不是循环次数多,而是循环内反复调用SpreadsheetApp的读写API,这类跨服务调用的耗时是内存运算的上千倍,每次getRange、getValue、setValues都会产生额外开销,哪怕只读写1个单元格。
具体优化措施
- 每个人员工作表一次性读取所有需要的列数据到内存:统一读B列(日期)+ 所有项目对应的列,不用按项目循环读列
- 所有逻辑判断、数据格式化都在内存的数组里完成,全程不调用单元格读写API
- 把所有要写入的行先攒到同一个二维数组里,最后一次性写入目标表,避免逐行写入
- 提前计算好所有项目对应的列索引,避免循环内反复处理列标转序号的逻辑
优化后代码
const members = {'Person1':'P1', 'Person2':'P2'}; const projects = {'Project1':'L','Project2':'R'}; // 提前把列字母转成列序号,方便后续取数 const projectColIndexes = Object.fromEntries( Object.entries(projects).map(([name, colLetter]) => [name, columnLetterToIndex(colLetter)]) ); // B列是日期列,转成索引(数组从0开始,B列是第2列,索引为1) const DATE_COL_INDEX = 1; function saveRowValue() { const sourceSpreadsheet = SpreadsheetApp.getActiveSpreadsheet(); const targetSpreadsheet = SpreadsheetApp.openById('-SPREADSHEET-'); const targetSheet = targetSpreadsheet.getSheetByName('Tracking time'); // 初始化输出数组,先加表头 const rowsToWrite = [['Project', 'Initials', 'Date', 'Tracking time']]; const projectNames = Object.keys(projects); for (const [personName, initials] of Object.entries(members)) { const sourceSheet = sourceSpreadsheet.getSheetByName(personName); const maxRow = sourceSheet.getLastRow(); if (maxRow < 1) continue; // 空表直接跳过 // 一次性读取所有需要的列范围 const allCols = [DATE_COL_INDEX + 1, ...Object.values(projectColIndexes).map(i => i+1)]; const minCol = Math.min(...allCols); const maxCol = Math.max(...allCols); // 单次读取整段数据到内存 const sheetValues = sourceSheet.getRange(1, minCol, maxRow, maxCol - minCol + 1).getValues(); const offset = minCol - 1; const dateIdx = DATE_COL_INDEX - offset; // 内存内遍历处理所有行 for (let rowIdx = 0; rowIdx < sheetValues.length; rowIdx++) { const rowData = sheetValues[rowIdx]; const date = rowData[dateIdx]; if (!(date instanceof Date)) continue; // 日期不合法直接跳过整行 // 遍历所有项目取对应值 for (const projectName of projectNames) { const projColIdx = projectColIndexes[projectName] - offset; const rawVal = rowData[projColIdx]; if ([null, 0, ''].includes(rawVal)) continue; // 过滤无效值,和原逻辑一致 const formattedVal = Utilities.formatDate(rawVal, "GMT", sourceSheet.getRange(rowIdx+1, projectColIndexes[projectName]+1).getNumberFormat()); rowsToWrite.push([projectName, initials, date, formattedVal]); } } } // 单次写入所有数据到目标表 if (rowsToWrite.length > 1) { targetSheet.clearContents(); // 不需要清空旧数据可以删掉这行 targetSheet.getRange(1, 1, rowsToWrite.length, rowsToWrite[0].length).setValues(rowsToWrite); } } // 辅助函数:列字母转0起始的索引 function columnLetterToIndex(letter) { let index = 0; for (let i = 0; i < letter.length; i++) { index = index * 26 + (letter.charCodeAt(i) - 'A'.charCodeAt(0) + 1); } return index - 1; }
优化效果说明
优化后读写API调用次数从原来的O(mpr)级降到了O(m + 1)级:m个人员工作表各读1次,最后只写1次目标表,15人6项目600行的场景耗时基本可以压到1秒以内,后续数据量上涨也不会出现指数级变慢的问题。如果需要进一步提速,可以提前把所有项目列的数字格式也一次性读入内存,不需要循环调用getNumberFormat。
内容的提问来源于stack exchange,提问作者Killanight
相关产品推荐
相关产品推荐

