I've created a new project that should compare a name from Sheet1 with a list of names in Sheet2 and check if the name is already in that list. For that I chose a for-loop to get through the list in Sheet2 and compare every list entry with the name from Sheet1. Only if the name already exists in the list stuff should happen.
function myFunction() {
var tabSheet1 = 'Sheet1';
var tabSheet2 = 'Sheet2';
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet1 = ss.getSheetByName(tabSheet1);
var sheet2 = ss.getSheetByName(tabSheet2);
var lastRow1 = sheet2.getLastRow() + 1;
var playerNameSheet1 = sheet1.getRange(1, 1).getValue();
for (var j = 1; j < lastRow1; j++) {
var playerNameSheet2 = sheet2.getRange(j, 1).getValue();
if (playerNameSheet2 == playerNameSheet1) {
...stuff...
}
}
}
Now my problem is that it seems like the script isn't able to identify that a name already exists in the list. Both values (playerNameSheet1 and playerNameSheet2) are completely identical (no space or other hidden obstacles), however the script would never continue with stuff in the if-statement. My example name to test my script was "Oliver Baumann".
I'm a bit confused about it - even more, because another comparison a bit later in the script code works just fine.
I've already tried to change the operator into === but that wouldn't work either.
if (playerNameSheet2 === playerNameSheet1) {
...stuff...
}
I've also observed that if I put a dot behind both variables I'm only able to choose further functions with playerNameSheet2, but not with playerNameSheet1. Maybe I did a typing error and am just too blind to see it? I don't know. Anyone an idea how to resolve the issue?
The complete project can be found here. However, a lot of stuff is in german and very rudimental. I just started it and haven't got time to clean it up. Just so you don't wonder.
You will likely benefit from a change to your inspection routine - currently what you have is not scalable due to the slow, repeated calls to the Spreadsheet Service. Use a batch method - getValues() - to return a Javascript Array that contains all the content you could want from your 'master list' of names:
// Create an N x 1 array of arrays, e.g. [ [r1c1], [r2c1], [r3c1], ... [rNc1] ],
// of data in column A in sheet2. There will be blanks at the end if other columns have more data.
var allNames = sheet2.getRange(1, 1, sheet2.getLastRow(), 1).getValues();
To check if the name from the first sheet is present, we can replace this code:
for (var j = 1; j < lastRow1; j++) {
var playerNameSheet2 = sheet2.getRange(j, 1).getValue();
if (playerNameSheet2 == playerNameSheet1) {
/* do stuff */
with this code (note j now starts at 0):
for (var j = 0; j < allNames.length; ++j) {
if (playerNameSheet1 === allNames[j][0]) {
/* do stuff */
If you only need to do stuff on a name once in the function call (e.g. you don't need to execute the loop body twenty times when the sheet 1 name is "Bob" and there are twenty instances of "Bob" on sheet 2), you can simplify checking allNames for a value with the Array#indexOf method. First, one must collapse the "2D" array of arrays of values into an array of values. We want to apply a function to every element of the outer array and construct an array of its outputs, so we choose to call Array#map on it:
var db = allNames.map(function (row) { return row[0]; });
The function we use simply returns the first element of the passed element - i.e. the value in the first column, resulting in an output like [ r1c1, r2c1, r3c1, ... rNc1 ].
The replacement code is then:
if (db.indexOf(playerNameSheet1) === -1) {
console.log({
message: "Did not find '" + playerNameSheet1 + "' in database.",
database: db, original: allNames, searched: playerNameSheet1
});
return;
}
/* do stuff */
Which says "if the name is not on sheet 2, log the failed lookup and then quit running the function." To promote actual logging, the log is sent to Stackdriver, which will keep it for much longer than the native Logger class would.
If your do stuff bits use the j index, you can still obtain that index and use the associated row in sheet 2:
var index = db.indexOf(playerNameSheet1);
if (index === -1) {
console.log({
message: "Did not find '" + playerNameSheet1 + "' in database.",
database: db, original: allNames, searched: playerNameSheet1
});
return;
}
/* do stuff with the user's existing row of data, e.g.
var userDataRow = sheet2.getRange(index + 1, 1, 1, sheet2.getLastColumn()).getValues();
var userData = userDataRow[0];
...
*/
A possible improvement to the indexOf modification, which I leave for you to investigate and/or implement, would be to use an Object to hold the names as "keys" (object properties) and the index of the associated sheet data (or even the data directly) as the associated value of the key-value pair.
you can try to convert data in array and compare in for-loop:
var dataRangeSpieler = sheetSpieler.getDataRange().getValues();
var dataRangeDBSpiele = sheetDBSpieler.getDataRange().getValues();
for (i in dataRangeSpieler ) {
for (j in dataRangeDBSpiele) {
if (dataRangeSpieler[i][1] == dataRangeDBSpiele[j][0]) {
Logger.log(dataRangeSpieler[i][1]); //Oliver Baumann
}
}
}
Related
So currently, I am trying to make a google script that exports separate google sheets for each unique username -- i.e., to try to make a customizable report for each client. Basically, I have a list of unique usernames -- a list called uniqueUserName -- and I want to set the name of the new sheet to the "Name" which corresponds to the username. For example, suppose Sally1 is in the following table. The code would search through the usernames (with a for loop) and, once the for loop hits Sally1, the code would return Sally Wall -- i.e., the name corresponding to her username. Sally Wall would then be the new name of the document.
Username
Name
Timmy
Tim Jones
Sally1
Sally Wall
catsforlife
John Mueller
ready2learn
Cindy Rodney
I have tried the following code:
newSheet.setName(function(uniqueName, values){
for (var i=0; i < values.length; i++) {
if (values[i][0].Username === uniqueName) {
return values[i][1].Name;
break;
}
}
});
(I include the break function, because, if someone's name shows up twice, I don't want to copy their name twice. )
How would I have to adjust this code to serve these ends? Is this on the right track?
Honestly, I've been at this code for a while, looking up as much as I can, on stack overflow, YouTube, so your help is much appreciated!
One problem is that you're passing a function to the setName() method, but it expects a string. One straightforward way to solve this would be to immediately call the function after it's declared, passing the arguments as well.
Another thing is that you're trying to access the properties Name and Username of elements in values, but, assuming you received values from getValues(), those properties don't exist. You can get the values you want by just using the indices, as you're already doing: values[i][0] and values[i][1].
Also, you don't need to use break because the loop will be interrupted by the return statement anyway.
Considering the above and assuming there's a sheet called Names (that has the names you posted) and another called Report Template, you can change your code to something like:
function newNamedSheetTest() {
const uniqueName = 'Sally1';
const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
const values = spreadsheet.getSheetByName('Names').getDataRange().getValues();
const newSheet = spreadsheet.getSheetByName('Report Template').copyTo(spreadsheet);
newSheet.setName(function (uniqueName, values) {
for (let i = 0; i < values.length; i++) {
if (values[i][0] === uniqueName) {
return values[i][1];
}
}
}(uniqueName, values));
}
Another option would be to use filter and map to get the name of the sheet:
function newNamedSheetTestShort() {
const uniqueName = 'Sally1';
const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
const values = spreadsheet.getSheetByName('Names').getDataRange().getValues();
const newSheetName = values.filter(row => row[0] === uniqueName).map(row => row[1]);
const newSheet = spreadsheet.getSheetByName('Report Template').copyTo(spreadsheet);
newSheet.setName(newSheetName);
}
My goal
I'm trying to link rows in two different sheets in the same spreadsheet according to a value in a cell. The basic idea is that if a value in column B in Sheet1 has a matched value in column B in Sheet2, an hyperlink should be added to the cell with the matched value in Sheet1 linking to the whole row of the matched value in the Sheet2.
What I did
As you can see from the code below, it looks for the matches, if it's found, it edits the matched values to add the hyperlinks. I don't only want to push the "linked" matched values into the new array, I also want the non-matched values without the link. The idea is that the link will be added if a match is found, otherwise the value will still be added, just without the link.
function linkToContacts(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var Sheet1 = ss.getSheetByName("Sheet1");
var Sheet2 = ss.getSheetByName("Sheet2");
var Sheet2ID = Sheet2.getSheetId();
var arrSheet1 = Sheet1.getRange(4,2,Sheet1.getLastRow()-3).getValues(); // -3 because I have 3 row as headers before the data, which start in row 4 col B
var arrSheet2 = Sheet2.getRange(4,2,Sheet2.getLastRow()-3).getValues(); // -3 because I have 3 row as headers before the data, which start in row 4 col B
var arrOutput = [];
for(var i = 0; i<arrSheet1.length;i++){
for(var j = 0; j<arrSheet2.length;j++) {
if(arrSheet1[i][0] === arrSheet2[j][0]){
arrOutput.push(['=HYPERLINK("#gid=' + Sheet2ID + '&range=' + Sheet2.getRange(j+4,2,1,Sheet2.getLastColumn()-1).getA1Notation() + '";"' + arrSheet1[i][0] + '")']);
} else {
arrOutput.push([arrSheet1[i][0]]);
}
}
}
Sheet1.getRange(4,2,Sheet1.getLastRow()).clearContent();
Sheet1.getRange(4,2,arrOutput.length).setValues(arrOutput);
}
The problem
The two arrays contain only unique values respectively. The problem is that, because of the double loop, each item is checked by the length of arrSheet2. So for instance let's imagine this scenario:
var arrSheet1 = [apple,avocado,banana];
var arrSheet2 = [apple,banana,mango,];
the arrOutput will result in:
arrOutput = [
apple(link),apple,apple,
avocado,avocado,avocado,
banana(link),banana,banana
];
In a quick, probably unelegant, way to solve the issue I've tried to delete the duplicates frm arrOutput but obviously the value with the link and the values without are different so the best it can get with this solution is this:
arrOutput = [
apple(link),apple,
avocado,
banana(link),banana
];
Question
Is there a smarter/more efficient way to get to
arrOutput = [apple(link),avocado,banana(link)];
or in case what I'm doing actually makes sense, what I should do to get to the result above?
You could use Array.prototype.filter to create a new array that contains only matching elements. If no elements are found this will return an empty array.
var arrSheet1 = ['apple','avocado','banana'];
var arrSheet2 = ['apple','banana','mango'];
var intersect = arrSheet1.filter(function (element) {
return arrSheet2.includes(element);
});
// ['apple','banana']
Or without an Array.prototype.includes polyfill:
var intersect = arrSheet1.filter(function (element) {
return arrSheet2.indexOf(element) !== -1);
})
Issue:
Double looping and pushing array elements multiple times
Solution:
Use break and conditional if statements to control logic
Flow:
If hyperlink is pushed, break the second sheet loop
Only push Sheet1 element if there's no hyperlinks in Sheet2(i.e., Wait till the last element of Sheet2 is iterated)
Snippet:
for(var i = 0; i<arrSheet1.length;i++){
for(var j = 0, k = arrSheet2.length-1; j<=k; j++) {// modified
if(arrSheet1[i][0] === arrSheet2[j][0]){
arrOutput.push(['=HYPERLINK("#gid=' + Sheet2ID + '&range=' + Sheet2.getRange(j+4,2,1,Sheet2.getLastColumn()-1).getA1Notation() + '";"' + arrSheet1[i][0] + '")']);
break; //break j loop to continue next i loop
} else if(j === k){//push sheet1 only on the last element
arrOutput.push([arrSheet1[i][0]]);
}
}
}
Note:
Use of objects {} might be better. Convert sheet2 array to object {val1:hash,val2:hash,...}. Then you can easily check if sheet1 elements are present in sheet2 using in
This was difficult to title without examples and context. Here goes...
I have a Google app script which searches through a column of student ids (column A on the compiledDATA sheet) and then sets a value (an award) in column B of the same row. This works fine for a single student id, but I need the script to loop and set the same award value for all of the students in the GroupAwardIDs range which is located on a separate sheet called Group Awards.
Here's a link to my sample spreadsheet.
The values to be set are nonconsecutive, and in actual use there may be over a thousand to be set at a time.
How can I achieve this in a quick and efficient way without running into quota issues?
Here's the script (please excuse all the comments - it helps me keep track):
function AwardGroup() {
var sheet = SpreadsheetApp.getActiveSpreadsheet();
var StarLog = sheet.getSheetByName("compiledDATA");
var GroupAward = sheet.getRangeByName("GroupAward").getValue();
var GroupAwardIDs = sheet.getRangeByName("GroupAwardIDs").getValue(); // THESE ARE THE IDS OF STUDENTS WHO WILL RECEIVE THE AWARD. HOW DO SET VALUES FOR ALL AND ONLY THESE IDS?
var IDs = sheet.getRangeByName("StudentIDs").getValues(); // all of the student IDs
for (var i = 0; i < IDs.length; i++) {
if (IDs[i] == "123461") { // THIS WORKS IF HARDCODE A SINGLE ID
var rowNumber = i+3; // find row and add 3 to compensate for GroupAward range staring at row 3
var StarLogCurrent = StarLog.getRange("B"+rowNumber).getValue(); // locates students award log cell using A1 notation
var appendAward = GroupAward.concat(StarLogCurrent); // prepends new award to previous awards
StarLog.getRange("B"+rowNumber).setValue(appendAward); //write new star log
}
}
}
You want to put GroupAward ("'Group Awards'!B3") to the column "B" of "compiledDATA" with the same row, when StudentIDs ("compiledDATA!A3:A1000") and GroupAwardIDs ("'Group Awards'!B7:B1002") are the same. If my understanding is correct, how about this modification? I think that there are several solutions for your situation. So please think of this as one of them.
Modification points :
Retrieve all GroupAwardIDs.
Remove empty elements in GroupAwardIDs.
Search IDs using GroupAwardIDs and put GroupAward when the IDs is the same with GroupAwardIDs.
Put the values with GroupAward.
Modified script :
Please modify as follows.
From :
var GroupAwardIDs = sheet.getRangeByName("GroupAwardIDs").getValue(); // THESE ARE THE IDS OF STUDENTS WHO WILL RECEIVE THE AWARD. HOW DO SET VALUES FOR ALL AND ONLY THESE IDS?
var IDs = sheet.getRangeByName("StudentIDs").getValues(); // all of the student IDs
for (var i = 0; i < IDs.length; i++) {
if (IDs[i] == "123461") { // THIS WORKS IF HARDCODE A SINGLE ID
var rowNumber = i+3; // find row and add 3 to compensate for GroupAward range staring at row 3
var StarLogCurrent = StarLog.getRange("B"+rowNumber).getValue(); // locates students award log cell using A1 notation
var appendAward = GroupAward.concat(StarLogCurrent); // prepends new award to previous awards
StarLog.getRange("B"+rowNumber).setValue(appendAward); //write new star log
}
}
To :
var GroupAwardIDs = sheet.getRangeByName("GroupAwardIDs").getValues(); // Modified
var IDs = sheet.getRangeByName("StudentIDs").getValues();
// I modified below script.
GroupAwardIDs = GroupAwardIDs.filter(String);
var res = IDs.map(function(e){
return GroupAwardIDs.filter(function(f){
return f[0] == e[0]
}).length > 0 ? [GroupAward] : [""];
});
sheet.getRange("compiledDATA!B3:B1000").setValues(res);
If I misunderstand your question, please tell me. I would like to modify it.
Edit :
You want to add GroupAward to the original values at column B. I understood what you want to do like this. If my understanding is correct, please modify to as follows. In this sample, I used ", " as the delimiter.
var GroupAwardIDs = sheet.getRangeByName("GroupAwardIDs").getValues(); // Modified
var IDs = sheet.getRangeByName("StudentIDs").getValues();
// I modified below script.
var columnB = sheet.getRange("compiledDATA!B3:B1000");
var valColB = columnB.getValues();
GroupAwardIDs = GroupAwardIDs.filter(String);
var res = IDs.map(function(e, i){
return GroupAwardIDs.filter(function(f){
return f[0] == e[0]
}).length > 0 ? [valColB[i][0] ? GroupAward + ", " + valColB[i][0] : GroupAward] : [valColB[i][0]]; // Modified
});
columnB.setValues(res);
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.