AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • 主页
  • 系统&网络
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • 主页
  • 系统&网络
    • 最新
    • 热门
    • 标签
  • Ubuntu
    • 最新
    • 热门
    • 标签
  • Unix
    • 最新
    • 标签
  • DBA
    • 最新
    • 标签
  • Computer
    • 最新
    • 标签
  • Coding
    • 最新
    • 标签
主页 / coding / 问题 / 79540747
Accepted
rg_
rg_
Asked: 2025-03-28 15:07:48 +0800 CST2025-03-28 15:07:48 +0800 CST 2025-03-28 15:07:48 +0800 CST

如何提高 Google Apps Script 的效率(向 Salesforce 获取请求,将数据写入 Google 表格)

  • 772

我有一个 AppSheet 应用,它使用 Google Sheet 作为其表格的数据源,尽管 Google Sheet 的底层数据源是 Salesforce。Salesforce sObject 非常庞大(660 多列,数十万条记录),因此我无法使用 AppSheet/Salesforce 集成,因为当我只需要每个用户一小部分数据时,同步所有数据效率非常低。

我正在使用 Google Apps 脚本向 Salesforce 发送 GET 请求,该请求返回“Workers”的有效负载,通常少于一百行,每行 10 列。GET 请求选择应用程序登录用户的“地盘”中的 Workers。

Workers 表保存所有用户的草皮,因为在任何给定时间可能有多个用户登录,但应用程序仅在 UI 中呈现当前登录用户的草皮。

应用程序中没有可供用户修改工人记录的用户界面,只需要读取和选择工人记录以更新相关表(该表会写回 Salesforce)即可。因此,Salesforce 始终拥有工人的最新更新数据,我不需要与 Google 表格进行双向同步。

脚本可以运行,但速度非常慢。对于一组约 100 条记录(每条记录 10 列)的记录,整个 loadTurf 过程大约需要 30 秒,我正在尝试找出如何改进脚本以使其更快。我无法控制 Salesforce 身份验证/JWT 交换/GET 请求部分的速度,但该部分仅需 3-5 秒,其余部分是 API 有效负载返回后发生的处理:检查重复项、删除需要用新数据覆盖的行以及将新数据写入工作表。

我知道一定有更快的方法来做到这一点。检查重复项和删除部分似乎特别低效,每次用 迭代一行forEach,但我无法找到批量处理删除的方法,因为草皮理论上可能处于非连续行中。有没有办法一次性删除整个过滤范围,而无需一次迭代一行?根据 Salesforce 有效负载检查每行是否有更新的列似乎比直接删除所有行并粘贴 Salesforce 返回的新行更慢。

无论如何,这是我的代码,最慢的部分似乎是检查重复和删除操作,对于一批 100 条记录,每个操作大约需要 10-11 秒。有人能建议一些改进方法吗?

const fieldsArray = [ ... list of fields ... ];
const ss = SpreadsheetApp.openByUrl( ... url ...);
const workers = ss.getSheetByName( ... sheetName ...); 
const contactIds = workers.getRange("A2:A").getValues().flat().filter(Boolean);

async function loadUserTurf(employer) {
  let records;
  if (employer) {
    try {
      const qp = new QueryParameters();
      qp.setSelect(fieldsArray.toString());
      qp.setFrom("Contact");
      qp.setWhere(`Employer_Name_Text__c = \'${employer}\' AND Active_Worker__c = TRUE`);

      records = await get(qp);
      setUserTurf(employer, records);
    } catch (err) {
      logErrorFunctions('loadUserTurf', employer, records, err);
    }

  } else {
    console.log(`loadUserTurf > 24: no employer provided`);
  }
  
}

const confirmUniqueContactId = (id) => !contactIds.includes(id);

function appendNewRows(data, sheet) { // data = array of objects
    try {
      data.forEach(obj => {
        if (confirmUniqueContactId(obj.Id)) {
          // flatten object to array
          const row = Object.values(obj).slice(1);
          sheet.appendRow(row);
        }        
      })
    } catch (err) {
      logErrorFunctions('appendNewRows', [data, sheet], '', err);
    }
}

function setUserTurf(employerName, payload) {
  // Check for matching rows -- has this turf been pulled before?
  // create an array of all matching row indices so we can delete them
  // and replace them with fresh data from Salesforce

  const allData = workers.getDataRange().getValues();
  const turfIndices = allData.map((row, index) => {
    if (row[3] === employerName) {
      return index + 1;
    }
  }).filter(n => n); // remove null values
  
  // If no matching rows found (user's first login)
  // create new rows to append the payload from Salesforce
  if (!turfIndices.length) {
    // append new rows with data from payload from loadTurf function
    try {
      appendNewRows(payload, workers);    
    } catch (err) {
      console.log(err);
      logErrorFunctions('setUserTurf', turfIndices, '', err);
    }
  } else {
    // otherwise, delete all existing rows in that turf and replace them with fresh data from Salesforce
    // because checking for differences at the individual cell level seems even more inefficient?
    try {
      turfIndices.forEach(index => workers.deleteRow(index));
      // append new rows with data from payload from loadTurf function
      appendNewRows(payload, workers); 
    } catch (err) {
      logErrorFunctions('setUserTurf', turfIndices, '', err);
    }
  }
}
google-apps-script
  • 1 1 个回答
  • 54 Views

1 个回答

  • Voted
  1. Best Answer
    Tanaike
    2025-03-28T15:57:33+08:002025-03-28T15:57:33+08:00

    修改点:

    我认为脚本中的以下修改可能更接近您的目标。

    • 使用setValues而不是appendRow将值放入电子表格中。
    • 为了以较低的处理成本删除多行,如何使用 Sheets API?
    • 另外,在删除行的情况下,我认为在您的脚本中,turfIndices需要反转数组。

    当这些点体现在你的脚本里的时候,它就变成下面这样了。

    修改后的脚本:

    首先,请在高级 Google 服务中启用 Sheets API。

    并且,请修改您的功能appendNewRows和setUserTurf如下内容。

    function appendNewRows(data, sheet) {
      try {
        const values = data.reduce((ar, obj) => {
          if (confirmUniqueContactId(obj.Id)) {
            const row = Object.values(obj).slice(1);
            ar.push(row);
          }
          return ar;
        }, []);
        sheet.getRange(sheet.getLastRow() + 1, 1, values.length, values[0].length).setValues(values);
      } catch (err) {
        logErrorFunctions('appendNewRows', [data, sheet], '', err);
      }
    }
    
    function setUserTurf(employerName, payload) {
      const allData = workers.getDataRange().getValues();
      const turfIndices = allData.map((row, index) => {
        if (row[3] === employerName) {
          return index + 1;
        }
      }).filter(n => n);
      if (!turfIndices.length) {
        try {
          appendNewRows(payload, workers);
        } catch (err) {
          console.log(err);
          logErrorFunctions('setUserTurf', turfIndices, '', err);
        }
      } else {
        try {
          const sheetId = workers.getSheetId();
          const requests = turfIndices.reverse().map(e => ({ deleteDimension: { range: { sheetId, startIndex: e - 1, endIndex: e, dimension: "ROWS" } } }));
          Sheets.Spreadsheets.batchUpdate({ requests }, ss.getId());
          SpreadsheetApp.flush();
          appendNewRows(payload, workers);
        } catch (err) {
          logErrorFunctions('setUserTurf', turfIndices, '', err);
        }
      }
    }
    

    笔记:

    如果您无法使用Sheets API,请按如下方式修改上述脚本。

    从

    const sheetId = workers.getSheetId();
    const requests = turfIndices.reverse().map(e => ({ deleteDimension: { range: { sheetId, startIndex: e - 1, endIndex: e, dimension: "ROWS" } } }));
    Sheets.Spreadsheets.batchUpdate({ requests }, ss.getId());
    SpreadsheetApp.flush();
    

    到

    turfIndices.reverse().forEach(index => workers.deleteRow(index));
    

    附加信息:

    在您的脚本中,函数appendNewRows和setUserTurf可能能够按如下方式合并。

    function setUserTurf(employerName , payload) {
      const allData = workers.getDataRange().getValues();
      const turfIndices = allData.map((row, index) => {
        if (row[3] === employerName) {
          return index + 1;
        }
      }).filter(n => n);
      if (turfIndices.length) {
        const sheetId = workers.getSheetId();
        const requests = turfIndices.reverse().map(e => ({ deleteDimension: { range: { sheetId, startIndex: e - 1, endIndex: e, dimension: "ROWS" } } }));
        Sheets.Spreadsheets.batchUpdate({ requests }, ss.getId());
        SpreadsheetApp.flush();
      }
      const values = payload.reduce((ar, obj) => {
        if (!contactIds.includes(obj.Id)) {
          const row = Object.values(obj).slice(1);
          ar.push(row);
        }
        return ar;
      }, []);
      if (values.length == 0) return;
      workers.getRange(workers.getLastRow() + 1, 1, values.length, values[0].length).setValues(values);
    }
    

    参考:

    • 撤销()
    • 方法:spreadsheets.batchUpdate
    • 删除维度请求
    • 2

相关问题

  • 连接到引用变量?

  • jQuery 和冒泡加载页面导入内容为空 Google Sheet Importxml - 什么 google apps 脚本解决方法?

  • 如何使用 Gmail 插件中的卡服务实现文件上传功能

  • 脚本未按预期将单元格值复制到 K5

  • 谷歌应用程序脚本水平复选框,如单选按钮(选中/取消选中)

Sidebar

Stats

  • 问题 205573
  • 回答 270741
  • 最佳答案 135370
  • 用户 68524
  • 热门
  • 回答
  • Marko Smith

    重新格式化数字,在固定位置插入分隔符

    • 6 个回答
  • Marko Smith

    为什么 C++20 概念会导致循环约束错误,而老式的 SFINAE 不会?

    • 2 个回答
  • Marko Smith

    VScode 自动卸载扩展的问题(Material 主题)

    • 2 个回答
  • Marko Smith

    Vue 3:创建时出错“预期标识符但发现‘导入’”[重复]

    • 1 个回答
  • Marko Smith

    具有指定基础类型但没有枚举器的“枚举类”的用途是什么?

    • 1 个回答
  • Marko Smith

    如何修复未手动导入的模块的 MODULE_NOT_FOUND 错误?

    • 6 个回答
  • Marko Smith

    `(表达式,左值) = 右值` 在 C 或 C++ 中是有效的赋值吗?为什么有些编译器会接受/拒绝它?

    • 3 个回答
  • Marko Smith

    在 C++ 中,一个不执行任何操作的空程序需要 204KB 的堆,但在 C 中则不需要

    • 1 个回答
  • Marko Smith

    PowerBI 目前与 BigQuery 不兼容:Simba 驱动程序与 Windows 更新有关

    • 2 个回答
  • Marko Smith

    AdMob:MobileAds.initialize() - 对于某些设备,“java.lang.Integer 无法转换为 java.lang.String”

    • 1 个回答
  • Martin Hope
    Fantastic Mr Fox msvc std::vector 实现中仅不接受可复制类型 2025-04-23 06:40:49 +0800 CST
  • Martin Hope
    Howard Hinnant 使用 chrono 查找下一个工作日 2025-04-21 08:30:25 +0800 CST
  • Martin Hope
    Fedor 构造函数的成员初始化程序可以包含另一个成员的初始化吗? 2025-04-15 01:01:44 +0800 CST
  • Martin Hope
    Petr Filipský 为什么 C++20 概念会导致循环约束错误,而老式的 SFINAE 不会? 2025-03-23 21:39:40 +0800 CST
  • Martin Hope
    Catskul C++20 是否进行了更改,允许从已知绑定数组“type(&)[N]”转换为未知绑定数组“type(&)[]”? 2025-03-04 06:57:53 +0800 CST
  • Martin Hope
    Stefan Pochmann 为什么 {2,3,10} 和 {x,3,10} (x=2) 的顺序不同? 2025-01-13 23:24:07 +0800 CST
  • Martin Hope
    Chad Feller 在 5.2 版中,bash 条件语句中的 [[ .. ]] 中的分号现在是可选的吗? 2024-10-21 05:50:33 +0800 CST
  • Martin Hope
    Wrench 为什么双破折号 (--) 会导致此 MariaDB 子句评估为 true? 2024-05-05 13:37:20 +0800 CST
  • Martin Hope
    Waket Zheng 为什么 `dict(id=1, **{'id': 2})` 有时会引发 `KeyError: 'id'` 而不是 TypeError? 2024-05-04 14:19:19 +0800 CST
  • Martin Hope
    user924 AdMob:MobileAds.initialize() - 对于某些设备,“java.lang.Integer 无法转换为 java.lang.String” 2024-03-20 03:12:31 +0800 CST

热门标签

python javascript c++ c# java typescript sql reactjs html

Explore

  • 主页
  • 问题
    • 最新
    • 热门
  • 标签
  • 帮助

Footer

AskOverflow.Dev

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve