I've written a script to read data from a website using an API and i'd like to write the output in a google sheet. There are 4 data items per ID of a json object and i'd like to write them to 4 columns, C - F, starting in row 2 until row 32.
I believe your goal as follows.
You want to put the values of [current_price,forecast,demand,available_shares] from the cell "C2" on the active sheet.
For this, how about this answer?
Modification points:
In your script, the value is put to the Spreadsheet with for (i = 2; i < 33; i++) {} every each stockId. By this, the old value is overwritten by the next value. I think that this is the reason of your issue.
I think that in your case, the following flow can be used.
An array is prepared before the for loop.
Put the value of [current_price,forecast,demand,available_shares] to the array.
When the for loop is finished, the array is put to the Spreadsheet.
By this flow, the value of each stockId is put to the array, and then, the array is put to the Spreadsheet. When above points are reflected to your script, it becomes as follows.
Modified script:
function myFunction() {
let values = []; // Added
for (let stockId = 1; stockId < 32; stockId++) {
if (stockId == 24) continue;
var response = UrlFetchApp.fetch("https://api.torn.com/torn/" + stockId + "?selections=stocks&key=" + API);
var content = response.getContentText();
var json = JSON.parse(content);
var current_price = json["stocks"][stockId]["current_price"];
var forecast = json["stocks"][stockId]["forecast"];
var demand = json["stocks"][stockId]["demand"];
var available_shares = json["stocks"][stockId]["available_shares"];
values.push([current_price,forecast,demand,available_shares]); // Added
}
var ss = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet(); // Added
ss.getRange("C2:F" + (values.length + 1)).setValues(values); // Added
}
Reference:
setValues(values)
Related
function dataManp() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var copySheet = ss.getSheetByName("My-Sheet-1");
var pasteSheet = ss.getSheetByName("My-Sheet-2");
var clearContentRange = pasteSheet.getRange("A1:Z100");
clearContentRange.clearContent();
var source = copySheet.getRange("a1:f100");
var destination = pasteSheet.getRange("a1:f100");
source.copyTo(destination, {formatOnly:true , contentsOnly:true});
source.copyTo(destination,SpreadsheetApp.CopyPasteType.PASTE_COLUMN_WIDTHS,false);
var rows = pasteSheet.getDataRange();
var numRows = rows.getNumRows();
var values = rows.getValues();
var rowsDeleted = 0;
for (var i = 0; i <= numRows - 1; i++) {
var row = values[i];
if (row[3] == '') {
var deleteRowNum = (parseInt(i)+1) - rowsDeleted
pasteSheet.deleteRow(deleteRowNum);
// temp_array[i] = i
rowsDeleted++;
}
}
pasteSheet.deleteColumn(2)
}
Hi,
I have written the following script to perform the following operations,
Copy data from My-Sheet-1 and paste to My-Sheet-2
Deletion of rows that corresponds to empty cells in column 3.
After that deletion of column 2
Rigthnow, the My-Sheet-1 contains only 60 rows and 20, the script is taking approximately 7 secs to complete. However in future the number of rows may extend to some 1000. Is there a way to optimize the above code, so that it takes less time to complete.
My observation is that, copy and pasting the data takes just milli secs. The major time consuming part are the operations, I am performing in the pasteSheet after pasting it. It may be helpful, if we can copy My-Sheet-1 to a temporary variable (copy everything including the formulas, format specifications, values, text etc..) and perform all operations in the temporary variable and then paste everything in the temporary variable to the desired target sheet. But, I don't know, how to copy everything in a sheet to a temporary variable, also, I am not sure, whether this will reduce the time or not. I would be glad, if I can get some help on this as well (i.e. copying everything in a sheet to a temporary variable, perfrom operations on the variables and then paste data in the variable to a new sheet)
Thank you
Edit - 1
Would like to add that, My-Sheet-1 contains mixed data (i.e. numerics, color formatted text, formulas in some cells etc)
Explanation:
deleteRow() takes some time per execution, so it's not recommended to use on hundreds of rows in a loop.
Simple answer would be:
Make a 2D array for Sheet1 using getValues().
Delete / filter out array elements depending if row2 is blank.
Use setValues() to write the filtered array into Sheet2.
Sample Code:
function dataManp() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var copySheet = ss.getSheetByName("My-Sheet-1");
var pasteSheet = ss.getSheetByName("My-Sheet-2");
var lr = copySheet.getLastRow();
var clearContentRange = pasteSheet.getRange(1,1,lr,26);
clearContentRange.clearContent();
var source = copySheet.getRange(1,1,lr,6);
var destination = pasteSheet.getRange(1,1,lr,6);
source.copyTo(destination, {formatOnly:true , contentsOnly:true});
source.copyTo(destination,SpreadsheetApp.CopyPasteType.PASTE_COLUMN_WIDTHS,false);
destination.clearContent();
var values = source.getValues();
var temp_array = [];
for (var i = 0; i < lr; i++) {
var rowValue = values[i];
if (rowValue[2] != '') {
temp_array.push(rowValue);
}
}
var newDest = pasteSheet.getRange(1,1,temp_array.length,6)
newDest.setValues(temp_array);
pasteSheet.deleteColumn(2);
}
One caveat is that you need to have the same format for all rows in a column.
Sample Input:
Sample Output:
I am using google sheets quite a lot, but now I am trying to use google apps script to get and update dynamic data retrieved from formulas into a static table.
So, I have a sheet called 'dynamique', with formulas retrieving, filtering and sorting data from other spreadsheets.
I want to be able to work on this data, so I am trying to create a button which would copy all the values from the 'dynamique' sheet into another sheet called 'statique'. That is, I want a formula which would check if the values from the column C of the 'dynamique' sheet are in the column C of the 'statique' sheet. And if the values aren't there, I want the script to copy them. (columns A and B are empty)
I've managed to get my script to work for one column, but now, I want to copy the whole line.
For example, if the value in dynamique!C10 can't be found in statique!C:C, my script writes the value of dynamique!C10 in the first empty cell of the column statique!C:C. But I want it to write dynamique!C10:J10 into my destination sheet (say it's going to be maybe statique!C8:J8).
Here is my code, working for only one cell.
function dynamicToStatic() {
var dynSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("dynamique");
var staSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("statique");
var dynLength = dynSheet.getRange("C1:C").getValues().filter(String).length;//.getLastRow();
var staLength = staSheet.getRange("C1:C").getValues().filter(String).length;
var staRange = staSheet.getRange(6,3,staLength-1);
var staValues = staRange.getValues();
var rangeToCheck = dynSheet.getRange(6,3,dynLength-1,8);
var valuesToCheck = rangeToCheck.getValues();
var numRows = rangeToCheck.getNumRows();
var staNumRows = staRange.getNumRows();
for (i = 0; i<= numRows; i++) {
var row = valuesToCheck[i];
var index = ArrayLib.indexOf(staValues , -1 , row);
if (index == -1) {
//if (staValues.indexOf(row) != -1) {
staSheet.getRange(i+6,3,1,8).setValues(row);
}
}
var timestamp = new Date();
staSheet.getRange(4,3).setValue('List updated on the: '+timestamp);
}
Now I can't manage to retrieve the whole line of the array, so as to be able to copy it using range.setValues(). I always get error messages.
Any help would be more than appreciated...
function gettingFullRows() {
const ss=SpreadsheetApp.getActive();
const sh=ss.getSheetByName('Sheet1');
const shsr=2;//data startrow
const vA=sh.getRange(shsr,1,sh.getLastRow()-shsr+1,sh.getLastColumn()).getValues();
let html='';
vA.forEach((r,i)=>{
html+=Utilities.formatString('<br />Row:%s is %s',i+shsr,r.join(','));
});
SpreadsheetApp.getUi().showModelessDialog(HtmlService.createHtmlOutput(html), "Row");
}
So i did some re-writing to your code and made some comments in there. I hope this will make some things clear.
Array's are 0 indexed. So if the value is NOT found in the .indexOf then it would return -1. Also (for speed) i first push all the result to a array and then set the array in one "action" this saves a lot of time. The calls to and from a sheet takes the most time.
For the conversion to a 1d array i used spread operator
See this link for difference in const / var / let
The timestamp string i updated with the use of Template literals
If you have some questions, shoot! (also i did not test this ofcourse)
function dynamicToStatic() {
const dynSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("dynamique");
const staSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("statique");
const dynValues = dynSheet.getRange(1,3,dynSheet.getLastRow(),8).getValues();
//This is a 2d array
const staRaw = staSheet.getRange(6, 3, staSheet.getLastRow()).getValues();
//Convert to 1d array, for the indexoff later on, this way it is easier.
const staValues = [].concat(...staRaw);
//to store the output, as a 2d array, inside the if you see i push it in as array so you have the 2d array for the setValues.
const output = [];
for (let i = 0; i < dynValues.length; i++){
//i = the index of the array (row) inside the array of rows, the 0 would be the values of column C.
if (staValues.indexOf(dynValues[i][0]) >= 0){
output.push([dynValues[i]]);
}
}
//Start by the lastrow + 1, column C(3), ouput is a array of arrays(rows), then get the [0].lengt for the columns inside the row array.
staSheet.getRange(staSheet.getLastRow()+1, 3, output.length, output[0].lenght).setValues(output);
const timestamp = new Date();
staSheet.getRange(4,3).setValue(`List updated on the: ${timestamp}`);
}
EDIT:
Since my problem is rather difficult to describe I added an example of my data which hopefully shows what I'd like to achieve:
https://docs.google.com/spreadsheets/d/1Wa_z2e2br53usul3uMy8nczXushvhPFRiIlZ5M2ueYU/edit?usp=sharing
I hope it's okay to do so.
I could need some help with the following problem: I'm trying to create a summary sheet for a variety of data sheets. Each data sheet, i.e., "Sheet1", "Sheet2", "Sheet3", e.g., has an ID variable that I use for splitting my data within each data sheet.
What I'm trying to do is to loop through all my data sheets, grab the data in each sheet, split the data for each sheet by ID, so that I have all rows with As, Bs and Cs for each sheet, and then put all these pieces together BUT SEPARATED BY A COUPLE OF EMPTY ROWS in my summary sheet.
What I have done thus far is this:
function main() {
// SETUP.
var app = SpreadsheetApp;
var workbook = app.getActiveSpreadsheet();
var activeSheet = workbook.getActiveSheet();
// CREATE NEW SUMMARY SHEET.
var targetSheet = workbook.getSheetByName("Summary");
if (!targetSheet) {
workbook.insertSheet("Summary",1);
}
// ARRAY OF SHEETS USED IN MY LOOP.
var tabs = ["Sheet 1",
"Sheet 2",
"Sheet 3"];
// LOOP FOR ALL SHEETS.
for (var i = 0; i < tabs.length; i++) {
var sheet = workbook.getSheetByName(tabs[i]);
// GRAB THE ORIGINAL DATA.
var originalData = sheet.getRange(5, 1, sheet.getLastRow()-5, sheet.getLastColumn()).getValues();
// SELECT ID AND SORT BY UNIQUE IDs.
var range = sheet.getRange(5,2,sheet.getLastRow()-5,1).getValues();
var range = [].concat.apply([], range);
let uniqueValues = range.filter((v, i, a) => a.indexOf(v) === i);
// GRAB THE UNIQUE DATA PIECES IN EACH SHEET.
for (var t = 0; t < uniqueValues.length; t++) {
var filterText = uniqueValues[t];
var newData = originalData.filter(function(item) {
return item[1] === filterText;
});
// DO SOMETHING I HAVE YET TO DEFINE
// e.g., exclude rows that fall beneath a certain threshold.
// WRITE DATA PIECES BACK TO SUMMARY SHEET.
workbook.getSheetByName("Summary").getRange(???).setValues(newData);
}
}
}
The code above works fine and does slice my data on different data sheets correctly. However, setting the pieces back together is an issue. Right now the data pieces of each iteration is overwritten by the next one.
What I need to do is to figure out a way how to grab these different slices of data based on my ID and then put them together in a way shown in my example data (linke above).
I think I'm lost somewhere between different loops and the data stored temporarily within the loops.
Answer:
After gathering the data you need to:
Run through the rows
Check the product name
Push rows with the same product name to separate arrays
Use setValues() to set the data of each product
Add 4 blank rows
Code Modifications:
Before your LOOP THROUGH ALL SHEETS for loop, add an array declaration:
var dataSets = [];
Then, replace the following line:
workbook.getSheetByName("Summary").getRange(???).setValues(newData);
With:
dataSets.push(newData);
And after the for loop, do the data processing set out in the first section of this answer:
// Create a 2D array to push the data to:
var allData = [[],[],[]];
// loop through the Data sets and push them to the correct element of allData:
dataSets.forEach(function(dataSet) {
dataSet.forEach(function(row) {
if (row[1] == "Product A") {
allData[0].push(row)
}
else if (row[1] == "Product B") {
allData[1].push(row)
}
else if (row[1] == "Product C") {
allData[2].push(row)
}
})
})
// Define the next row to add data to in the Summary sheet:
var nextRow = 1;
// Set the data to the Summary sheet:
allData.forEach(function(product) {
var noOfColumns = product[0].length;
var noOfRows = product.length;
workbook.getSheetByName("Summary").getRange(nextRow, 1, noOfRows, noOfColumns).setValues(product)
nextRow += noOfRows + 4;
});
I'm having an issue pulling the correct values out of a for loop in Google Sheets.
Here's my code:
Note: this is a snippet from a larger function
function sendEmails() {
var trackOriginSheet = SpreadsheetApp.getActiveSpreadsheet().getName();
var getMirSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Miranda");
//Set a new object to hold conditional data
var holdingData = new Object();
//Create function to get values from origin sheet
var returnedValues = function (trackOriginSheet) {
//Load dynamic variables into an object via returnedValues()
if (trackOriginSheet === getMirSheet) {
var startMirRow = 2; // First row of data to process
var numRowsMir = 506; // Number of rows to process
// Fetch the range of cells A2:Z506
var dataRangeMir = getMirSheet.getRange(startMirRow, 1, numRowsMir, 26);
// Fetch values for each cell in the Range.
var dataMir = dataRangeMir.getValues();
for (var k in dataMir) {
var secondRowMir = dataMir[k];
var intRefDescMir = secondRowMir[3];
var intAdminActionsMir = secondRowMir[4];
//Push returned data to holdingData Object
holdingData.selectedData = secondRowMir;
holdingData.refDesc = intRefDescMir;
holdingData.adminActions = intAdminActionsMir;
}
}
}
Here's a copy of the sheet I'm working on
What I need to have happened here first, is track the origin sheet, then create an object to hold data returned from the returnedValues() function. Later, I'll call the properties of this object into a send email function.
The problem is that I need to be able to pull data from the selected sheet dynamically (the "Miranda" sheet in this case.) In other words, when a user selects the "Yes" option in column I of the Miranda sheet, the first thing this script needs to do is pull the values of the variables at the top of the for loop within the same row that the user selected "Yes." Then, I'm pushing that data to a custom object to be called later.
It's apparent to me, that I'm doing it wrong. There's, at least, something wrong with my loop. What have I done? :)
EDIT:
After reviewing the suggestion by VyTautas, here's my attempt at a working loop:
for (var k = 0; k < dataMir.length; k++) {
var mirColI = dataMir[k][8];
var mirRefDesc = dataMir[k][2];
var mirAdminActions = dataMir[k][3];
var mirDates = dataMir[k][4];
if (mirColI === "Yes") {
var activeRowMir = mirColI.getActiveSelection.getRowIndex();
//Pull selected values from the active row when Yes is selected
var mirRefDescRange = getMirSheet.getRange(activeRowMir, mirRefDesc);
var mirRefDescValues = mirRefDescRange.getValues();
var mirAdminActionsRange = getMirSheet.getRange(activeRowMir, mirAdminActions);
var mirAdminActionsValues = mirAdminActionsRange.getValues();
var mirDatesRange = getMirSheet.getRange(activeRowMir, mirDates);
var mirDatesValues = mirAdminActionsRange.getValues();
var mirHoldingArray = [mirRefDescValues, mirAdminActionsValues, mirDatesValues];
//Push mirHoldingArray values to holdingData
holdingData.refDesc = mirHoldingArray[0];
holdingData.adminActions = mirHoldingArray[1];
holdingData.dates = mirHoldingArray[2];
}
}
Where did all that whitespace go in the actual script editor? :D
You already correctly use .getValues() to pull the entire table into an array. What you need to do now is have a for loop go through dataMir[k][8] and simply fetch the data if dataMir[k][8] === 'Yes'. I also feel that it's not quite necessary to use for (var k in dataMir) as for (var k = 0; k < dataMir.length; k++) is a lot cleaner and you have a for loop that guarantees control (though that's probably more a preference thing).
You can also reduce the number of variables you use by having
holdingData.selectedData = mirData[k]
holdingData.refDesc = mirData[k][2] //I assume you want the 3rd column for this variable, not the 4th
holdingData.adminActions = mirData[k][3] //same as above
remember, that the array starts with 0, so if you mirData[k][0] is column A, mirData[k][1] is column B and so on.
EDIT: what you wrote in your edits seems like doubling down on the code. You already have the data, but you are trying to pull it again and some variables you use should give you an error. I will cut the code from the if, although I don't really see why you need to both get the active sheet and sheet by name. If you know the name will be constant, then just always get the correct sheet by name (or index) thus eliminating the possibility of working with the wrong sheet.
var titleMirRows = 1; // First row of data to process
var numRowsMir = getMirSheet.getLastRow(); // Number of rows to process
// Fetch the range of cells A2:Z506
var dataRangeMir = getMirSheet.getRange(titleMirRows + 1, 1, numRowsMir - titleMirRows, 26); // might need adjusting but now it will only get as many rows as there is data, you can do the same for columns too
// Fetch values for each cell in the Range.
var dataMir = dataRangeMir.getValues();
for (var k = 0; k < dataMir.length; k++) {
if (dataMir[k][7] === 'Yes') { //I assume you meant column i
holdingData.refDesc = dataMir[k] //this will store the entire row
holdingData.adminActions = dataMir[k][3] //this stores column D
holdingData.dates = dataMir[k][4] //stores column E
}
}
Double check if the columns I have added to those variables are what you want. As I understood the object stores the entire row array, the value in column called Administrative Actions and the value in column Dates/Periods if Applicable. If not please adjust accordingly, but as you can see, we minimize the work we do with the sheet itself by simply manipulating the entire data array. Always make as few calls to Google Services as possible.
I'm writing the following code (a test as of now) using Google Scripts to pass data from one spreadsheet to another. The passing of the code is working just fine, however my second For loop – which I intend to use to detect duplicate values and avoid passing those rows over – is not working.
Checking the logs I see that even though the "i" and "j" values are correctly being passed inside the If block, the "if(sheetsIDHome[i] == sheetsIDTarget[j])" statement is never triggering, even when I confirm that both values are the same.
Any help would be greatly appreciated, thank you in advance!
function move(){
var homeBook = SpreadsheetApp.getActiveSpreadsheet();
var sheet = homeBook.getSheets()[0];//Sheet where my Home data is stored
var limit = sheet.getLastRow(); //number of rows with content in them
var evento = sheet.getRange(2, 1, limit-1).getValues(); //Even titles array
var descript = sheet.getRange(2,2,limit-1).getValues(); //Event Descriptions array
var tags = sheet.getRange(2,3,limit-1).getValues(); //Tags array
var sheetsIDHome = sheet.getRange(2,4,limit-1).getValues(); //ID's array
var targetBook = SpreadsheetApp.openById("1t3qMTu2opYffLmFfTuIbV6BrwsDe9iLHZJ_ZT89kHr8"); // Traget Workbook
var target = targetBook.getSheets()[0]; //Sheet1, this is my Target sheet
if (target.getLastRow() > 1){
var sheetsIDTarget = target.getRange(2, 4,target.getLastRow()-1).getValues();}
else{
var sheetsIDTarget = target.getRange(2, 4, 1).getValues();}
var targetRow = target.getLastRow()+1; //Target row to start pasting content
for (var i = 0; i < evento.length; i++) { //Loops throught every value from my Home sheet in order to pass it to my Target Sheet
var isKlar = 1; //This works as a switch, data passing will not activate if isKlar set to 0
Logger.log("Switch is: "+isKlar);
for(var j = 0; j < sheetsIDTarget.length; j++){ //While having a certain "i" value in place, will loop though all my values in my target array using the counter "j"
if(sheetsIDHome[i] == sheetsIDTarget[j]){ //If the ID of my curent row from Home matches any of the values in my target sheet, my "isKlar" switch should turn off and the break loop will be exited.
Logger.log("If Activated");
isKlar = 0;
break;}
else{Logger.log("ID's: "+sheetsIDHome[i] + " vs " + sheetsIDTarget[j]);}
}
if(isKlar === 1){ //data passing will not activate if isKlar set to 0
//pass data to the Target sheet
target.getRange(targetRow,1).setValue(evento[i]);
target.getRange(targetRow,2).setValue(descript[i]);
target.getRange(targetRow,3).setValue(tags[i]);
target.getRange(targetRow,4).setValue(sheetsIDHome[i]);
targetRow++; //select the next available row in ny Target sheet
}
}
}
Edit. - Right now I'm testing both ID arrays with the same numbers (e.g. 1, 2, 3, 4). The log inside my else statement does show the correct values being read for both arrays... I thought it was a scope issue, but now I'm not sure where the problem is.
the issue is a sheet range.getValues() returns an array of arrays, not an array of values.
values[0] is the first row, and values[0][0] is the first value in that first row. rework your code knowing this.