salesforce : C# Api compare to ApexDataloader - salesforce

I have performance issue on salesforce
I am trying to load table from salesforce into excel cvs, for that used, I tested the ApexDataLoader and found out that to load whole lead table for my organization take around 4~5 min, and have around 60,000 records.
Now I want to do the same with a C# code, for that I write this code:
var user = "xxx";
var password = "xxx";
var token = "xxx";
var sforceService = new SforceService();
var login = sforceService.login(user, String.Concat(password, token));
sforceService.Url = login.serverUrl;
sforceService.SessionHeaderValue = new SessionHeader { sessionId = login.sessionId };
var query = "The full query that I took from ApexDataLoder";
var startTime = DateTime.Now.TimeOfDay;
var firstTime = DateTime.Now.TimeOfDay;
var result = sforceService.query(query);
int i = 0;
while (!result.done)
{
var endTime = DateTime.Now.TimeOfDay;
Debug.Print(endTime.Subtract(startTime) + " " + i * result.records.Count() + " - " + (i + 1) * result.records.Count() +
" Time from start: " + endTime.Subtract(firstTime));
startTime = DateTime.Now.TimeOfDay;
result = sforceService.queryMore(result.queryLocator);
i++;
}
After few lines I saw that I read only 2000 lines in total (out of 60,000) in 2 miniuts.
That's mean that to get the whole table I will need 60 min.
Why there is so big difference between that ApexDataLoader (5 min) to my code? what I am doing wrong?
Thanks for the help!

I found 2 things that really improve performance,
one, set up the EnableDecompression to true
sforceService.EnableDecompression = true;
And the second is based on this thread, it is better to do a query "select Id from Lead" and collect all the id's and then do a multi thread processing to get the data with the retrieve function.
Hope that will help people and if someone have any other improvement tricks, please let me know

Related

Google Apps Script For Loop Through Array Speed

I have a simple example of a function I am working on. I am trying to loop through a column of unique item IDs. If the item ID is found in the item ID column of another sheet, it pulls adjacent attributes from the data table, and assigns them in the same row. I have a function and it works, however, this is a base example. In reality I need to do this for 1000+ rows, and much larger data sets. It is currently taking 30-60 mins to run. I believe there is a much faster way to do this with arrays and using foreach and getvalues I'm just not sure how to get started. Any help would be greatly appreciated.
function example() {
var list = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("List");
var data = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Data")
var listendRow = list.getLastRow();
var dataendRow = data.getLastRow();
var dataid = data.getDataRange().getValue();
for (var i = 2; i <= listendRow; i++) {
for (var j = 2; j <= dataendRow; j++){
var idnum = [list.getRange(i, 2,listendRow).getValue()];
var id = data.getRange(j, 3).getValue();
var name = data.getRange(j, 4).getValue();
var weight = data.getRange(j, 5).getValue();
if (idnum == id){
list.getRange(i, 3).setValue(name);
list.getRange(i, 4).setValue(weight);
}
}
}
}
Here is the link to the sheet:
https://docs.google.com/spreadsheets/d/1PPZKRXhiAAfFG1d-CU02MV_CSrqbdsCsyo_QADz5yiA/edit?usp=sharing
I believe your goal is as follows.
Your script works fine. Under this condition, you want to reduce the process cost of your script.
Modification points:
When I saw your sample Spreadsheet, V8 runtime is not used. Please enable V8 runtime. When V8 runtime is used, the process cost of the script can be reduced.
In your script, getValue and setValue are used in a loop. In this case, the process cost becomes high. Ref
SpreadsheetApp.getActiveSpreadsheet() can be declared one time.
In order to reduce the process cost of your script, how about the following modification?
Modified script:
Before you run this script, please enable V8 runtime.
function example2() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var list = ss.getSheetByName("List");
var data = ss.getSheetByName("Data");
var obj = data.getRange("C2:E" + data.getLastRow()).getValues().reduce((o, [a, ...b]) => (o[a] = b, o), {});
var range = list.getRange("B2:B" + list.getLastRow());
var values = range.getValues().map(([b]) => obj[b] || [null, null]);
range.offset(0, 1, values.length, 2).setValues(values);
}
When this script is run, the values are retrieved from "Data" sheet and create an object for searching the ID. And, the values are retrieved from "List" sheet and an array for putting to the sheet is created. And also, the array is put to "List" sheet.
Note:
When you try to use this script without enabling V8 runtime, an error like Syntax error occurs. Please be careful about this.
This modified script is for your sample Spreadsheet. If your actual Spreadsheet is differnt structure from your provided sample one, this modified script might not be able to be used. Please be careful about this.
If you cannot use V8 runtime, please test the following modified script.
function example2b() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var list = ss.getSheetByName("List");
var data = ss.getSheetByName("Data");
var obj = data.getRange("C2:E" + data.getLastRow()).getValues()
.reduce(function (o, [a, b, c]) {
o[a] = [b, c];
return o
}, {});
var range = list.getRange("B2:B" + list.getLastRow());
var values = range.getValues().map(function ([b]) { return obj[b] || [null, null] });
range.offset(0, 1, values.length, 2).setValues(values);
}
References:
getValues()
setValues(values)
reduce()
map()

Optimizing Google Scripts calls - Timeout issues

I have a Google Apps Script that extracts data from our SQL database to display in our Google Sheets report, but I am running into an issue where the script now has timeout issues because it takes so long to run. I know there's an inefficiency with the while loop populating the new rows and column values, but how would you optimize this to run faster? I'm sure it is because I'm making Google function calls thousands of times (our database is probably thousands of rows long).
I'm a pretty amateur programmer here, I've kind of frankensteined this from other Stack Overflow responses. Judgment is expected, but any help would be appreciated! Thank you!
function getPostedF5() {
var conn = Jdbc.getConnection('jdbc:#################;databaseName=****;user=***;password=***************');
//var conn = Jdbc.getConnection(dbUrl, user, userPwd);
var start = new Date();
var stmt = conn.createStatement();
// Read up to 25000 rows of data from the table and log them.
stmt.setMaxRows(25000);
var results = stmt.executeQuery('SELECT RTRIM(IV30400.ITEMNMBR), LEFT(IV30400.ITEMNMBR, 5), RTRIM(IV30400.DOCNUMBR), IV30400.SERLTNUM, IV30400.SERLTQTY, RTRIM(IV30400.FROMBIN), IV30200.DOCDATE as Date_Created, IV30200.DEX_ROW_TS as Date_Posted FROM IV30400 LEFT JOIN IV30200 ON IV30400.DOCNUMBR = IV30200.DOCNUMBR WHERE IV30400.DOCNUMBR LIKE \'%F%\' AND IV30400.FROMBIN LIKE \'%FOR %\' AND IV30200.DOCDATE >= \'2021-01-01 00:00:00.000\' ORDER BY IV30200.DOCDATE ASC');
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Posted F5 in GP");
sheet.getRange('B1:I25000').clear();
var cell = sheet.getRange('B1'); //Begin depositing data starting in specified cell
var numCols = results.getMetaData().getColumnCount();
var row =0;
while (results.next()) {
var rowString = '';
for (var col = 0; col < numCols; col++) {
rowString += results.getString(col + 1) + '\t';
cell.offset(row, col).setValue(results.getString(col +1 ));
}
row++
Logger.log(rowString)
}
results.close();
stmt.close();
conn.close();
var end = new Date();
Logger.log('Time elapsed: %sms', end - start);
}
Try something like this instead:
let a=[];
while (results.next()) {
let r=[];
for (var col = 0; col < numCols; col++) {
r.push(results.getString(col+1));
}
a.push(r);
}
sheet.getRange(1,1,a.length,a[0].length).setValues(a);

indexOf() doesn't find the value in array

Based on this post, I'm working on a proof of concept to capture an item request via Google form, email it for approval and the approval result posted back to the corresponding row in Google sheet. The row is searched from an array, using the timestamp as key.
I faced 2 challenges: First, the timestamp in the array is in a different format and sometimes differed by 1 second. I tweaked this by adjusting the format to match how the array values look like and run a 2nd search if the first timestamp search fails. I hope this is foolproof but let me know if there is a better way. I couldn't figure out why there is a 1-second difference sometimes.
I got stuck on the second challenge: I'm unable to search successfully the timestamp in the array at all. indexOf() always returns a value of -1.
Will appreciate any help.
Please be detailed if needed, I'm a newbie.
Here's my code:
function sendEmail(e) {
// Response columns: Timestamp Requester Email Item Cost
var email = e.namedValues["Requester Email"];
var item = e.namedValues["Item"];
var cost = e.namedValues["Cost"];
var timestamp = e.namedValues["Timestamp"];
var url = ScriptApp.getService().getUrl();
// Enhancement: include timestamp to coordinate response
var options = '?approval=%APPROVE%&timestamp=%TIMESTAMP%&reply=%EMAIL%'
.replace("%TIMESTAMP%",encodeURIComponent(e.namedValues["Timestamp"]))
.replace("%EMAIL%",e.namedValues["Requester Email"])
var approve = url+options.replace("%APPROVE%","Approved");
var reject = url+options.replace("%APPROVE%","Rejected");
var html = "<body>"+
"<h2>Please review</h2><br />"+
"Request from: " + email + "<br />"+
"For: "+item +", at a cost of: $" + cost + "<br /><br />"+
"Approve<br />"+
"Reject<br />"+
"</body>";
MailApp.sendEmail(Session.getEffectiveUser().getEmail(),
"Approval Request",
"Requires html",
{htmlBody: html});
}
function doGet(e) {
var answer = (e.parameter.approval === 'Approved') ? 'Buy it!' : 'Not this time, Keep saving';
var timestamp = e.parameter.timestamp;
var newtimestamp = Utilities.formatDate(new Date(timestamp), "GMT+8", "EEE MMM dd yyyy HH:mm:ss 'GMT+0800 (SGT)'"); //reformat timestamp to match the ones in the data array
var approvalCol = 5;
MailApp.sendEmail(e.parameter.reply, "Purchase Request",
"Your manager said: "+ answer);
// Update approval status back to the sheet
var wsID = "<myworksheetID>";
var sheet = SpreadsheetApp.openById(wsID).getSheetByName("Requests");
var data = sheet.getDataRange().getValues();
var oneColArray = new Array();
for(i=0;i<data.length;++i){
oneColArray.push(data[i][0]); // taking index 0 means I'll get column A of each row and put it in the new array
}
var row = oneColArray.indexOf(newtimestamp);
Logger.log("\ntimestamp: " + timestamp + "\n\n" + "newtimestamp: " + newtimestamp + "\n\n" + "oneColArray: \n" + oneColArray);
if (row < 0) { //not found
//Lower timestamp by 1 second. Sometimes there is a 1-second difference than the one in the array. I don't know why
var dateString = Utilities.formatDate(new Date(timestamp), "GMT+8",'EEE, d MMM yyyy HH:mm:ss');
var date = new Date(dateString);
var revisedtimestamp = new Date((date.getTime() - (1/(24*60*60)*1000)));
// now search again using the adjusted timestamp
var row = oneColArray.indexOf(revisedtimestamp);
if (row < 0) {
Logger.log("\ntimestamp: " + timestamp + "\n\n" + "newtimestamp: " + newtimestamp + "\n\n" + "oneColArray: \n" + oneColArray + "\n\n" + "revisedtimestamp: " + revisedtimestamp);
throw new Error ("Request not found in list.");
} else {
sheet.getRange(row + 1, approvalCol).setValue(e.parameter.approval);
}
} else {
sheet.getRange(row + 1, approvalCol).setValue(e.parameter.approval);
}
}
I found a solution based on this post.
I changed this line:
var row = oneColArray.indexOf(newtimestamp);
to this to convert the array into string, and indexOf() returns the starting point where it finds the first occurrence of the timestamp. From the return value I just calculated what row it is in the worksheet:
var row = oneColArray.join().indexOf(newtimestamp);
Another variation which I preferred more is looping through the data array's timestamp column, in each loop the value is converted into string before doing indexOf(). I just need to add 1 to [i] to get the actual row in the worksheet.
for (var i = 0; i < data.length; ++i) {
var row = data[i][0].toString().indexOf(newtimestamp);
if (row > -1) {
var a = i;
break;
}
}

Google Apps Script: How to copy and automatically edit the data from a spreadsheet to others?

I'm trying to solve this question but despite the effort I have not advanced!
Every day I get a spreadsheet with two tabs of data. Each one has about 9 thousand rows. I need to capture the data from this worksheet and generate two new spreadsheets with one for "daily reporting" (with only the data of this day) and another for create a "database" (with all data previous results). How can I do this automatically with GAS?
I working in this code below so far, but when I will set the values in the new spreadsheet, I got error about range. :( I don't know how to declare that I want copy and set all the rows between 10 and the last with data from
function getData() {
var day = SpreadsheetApp.openById('XXXX');
var tab1 = day.getSheets()[0];
var tab2 = day.getSheets()[1];
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sss = ss.getSheets()[0];
var weeks = tab1.getRange("G8").getValues();
var dates = tab1.getRange("G9").getValues();
var regions = tab1.getRange('A10:A').getValues();
var locnames = tab1.getRange('B10:B').getValues();
var locnums = tab1.getRange('C10:C').getValues();
var divisions = tab1.getRange('D10:D').getValues();
var depnums = tab1.getRange('E10:E').getValues();
var depnames = tab1.getRange('F10:F').getValues();
var saless = tab1.getRange('G10:G').getValues();
var qtys = tab2.getRange('G10:G').getValues();
var datestart = sss.getRange('A2').setValues(dates);
var dateend = sss.getRange('B2').setValues(dates);
var week = sss.getRange('C2').setValues(weeks);
var region = sss.getRange('D2:D').setValues(regions);
var locname = sss.getRange('E2:E').setValues(locnames);
var locnum = sss.getRange('F2:F').setValues(locnums);
var division = sss.getRange('G2:G').setValues(divisions);
var depnum = sss.getRange('H10:H').setValues(depnums);
var depname = sss.getRange('I2:I').setValues(depnames);
var sales = sss.getRange('J2:J').setValues(saless);
var qty = sss.getRange('K2:K').setValues(qtys);
}
If anyone can help me, thank you immensely.
I put a spreadsheet in the link to illustrate the format of sheets and what I need to do. The first two tabs are the same as the ones I get. And in the next two tabs(daily_report, database) are the worksheets that I want to get separately with the code.
https://docs.google.com/spreadsheets/d/1_iz0oansAfINosV2qNoDG0QMNpGS1bp1AL5kJQztRtc/edit?usp=sharing
The target range must be matched to the source range, or there will be an error. The length and width of the target range must be the same as the source data. You can use the data to set the length (number of rows) and width (number of columns)
Parameters:
range(Start Row, Start Column, Number of Rows, Number of Columns)
Code:
var regions = tab1.getRange('A10:A').getValues();
var region = sss.getRange(2,4,regions.length,regions[0].length).setValues(regions);

Audit of what records a given user can see in SalesForce.com

I am trying to determine a way to audit which records a given user can see by;
Object Type
Record Type
Count of records
Ideally would also be able to see which fields for each object/record type the user can see.
We will need to repeat this often and for different users and in different orgs, so would like to avoid manually determining this.
My first thought was to create an app using the partner WSDL, but would like to ask if there are any easier approaches or perhaps existing solutions.
Thanks all
I think that you can follow the documentation to solve it, using a query similar to this one:
SELECT RecordId
FROM UserRecordAccess
WHERE UserId = [single ID]
AND RecordId = [single ID] //or Record IN [list of IDs]
AND HasReadAccess = true
The following query returns the records for which a queried user has
read access to.
In addition, you should add limit 1 and get from record metadata the object type,record type, and so on.
I ended up using the below (C# using the Partner WSDL) to get an idea of what kinds of objects the user had visibility into.
Just a quick'n'dirty utility for my own use (read - not prod code);
var service = new SforceService();
var result = service.login("UserName", "Password");
service.Url = result.serverUrl;
service.SessionHeaderValue = new SessionHeader { sessionId = result.sessionId };
var queryResult = service.describeGlobal();
int total = queryResult.sobjects.Count();
int batcheSize = 100;
var batches = Math.Ceiling(total / (double)batcheSize);
using (var output = new StreamWriter(#"C:\test\sfdcAccess.txt", false))
{
for (int batch = 0; batch < batches; batch++)
{
var toQuery =
queryResult.sobjects.Skip(batch * batcheSize).Take(batcheSize).Select(x => x.name).ToArray();
var batchResult = service.describeSObjects(toQuery);
foreach (var x in batchResult)
{
if (!x.queryable)
{
Console.WriteLine("{0} is not queryable", x.name);
continue;
}
var test = service.query(string.Format("SELECT Id FROM {0} limit 100", x.name));
if(test == null || test.records == null)
{
Console.WriteLine("{0}:null records", x.name);
continue;
}
foreach (var record in test.records)
{
output.WriteLine("{0}\t{1}",x.name, record.Id);
}
Console.WriteLine("{0}:\t{1} records(0)", x.name, test.size);
}
}
output.Flush();
}

Resources