Get Google sheets column by name in Google Scripts - javascript

I have a script that allows me to get the contents of a column from my Google Sheet and display it in my HTML form while removing any duplicates of the same name.
Example: red, red, yellow, yellow, blue, green would show in the dropdown menu as red, yellow, blue, green.
The thing is, I would like to get the column contents by name and not by number i.e 1.
Here is my script:
function getColors() {
var sheet = SpreadsheetApp.openById("1czFXXQAIbW9IlAPwHQ0D5S_a-Ew82p-obBEalJFNJTI").getSheetByName("Vinyl Costs");
var getLastRow = sheet.getLastRow();
var return_array = [];
for(var i = 2; i <= getLastRow; i++)
{
if(return_array.indexOf(sheet.getRange(i, 1).getValue()) === -1) {
return_array.push(sheet.getRange(i, 1).getValue());
}
}
return return_array;
}
I've found a similar question and the accepted answer was this:
function getByName(colName, row) {
var sheet = SpreadsheetApp.getActiveSheet();
var data = sheet.getDataRange().getValues();
var col = data[0].indexOf(colName);
if (col != -1) {
return data[row-1][col];
}
}
But I can't seem to make that work with mine? This is my first ever Google Script so I don't really understand it 100% yet.

I changed the functions a bit.
For one thing, getByName now gets not all values of the sheet, but only the first row.
function getColors() {
const sheet = SpreadsheetApp.openById("1czFXXQAIbW9IlAPwHQ0D5S_a-Ew82p-obBEalJFNJTI").getSheetByName("Vinyl Costs");
const colName = 'your_column_name';
const colNum = getColNumberByName(colName);
if (colNum === null) {
Logger.log('Column ' + colName + ' was not found!');
return [];
}
const firstRow = 2;
const lastRow = sheet.getLastRow();
// get all values from column
const columnData = sheet.getRange(firstRow, colNum, lastRow).getValues().flat();
// filter values on duplicates
return columnData.filter((el, i) => i === columnData.indexOf(el) && el !== '');
}
function getColNumByName(colName, row = 1) {
const sheet = SpreadsheetApp.openById("1czFXXQAIbW9IlAPwHQ0D5S_a-Ew82p-obBEalJFNJTI").getSheetByName("Vinyl Costs");
const [data] = sheet.getRange(row, 1, row, sheet.getLastColumn()).getValues();
const col = data.indexOf(colName);
// adding 1 because column nums starting from 1
return col === -1 ? null : col + 1;
}

Related

Google Apps Script Filtering

I have some data in google sheet which I want to filter based on a certain criteria and return a corresponding value from another column. Lastly, count the number of elements in the returned column. Here is a sample data:
Sample data
A
B
1
Initials
Application Reference
2
MWB.KBB
1001
3
JET,JJB
1002
4
KBB
100,310,041,005
5
MKGC
1006
6
KBB
1007
Let's say I want to filter the data by searching for "KBB". I want to get all cells that contain the word "KBB" which should be three (3) cells. However, I am only getting two in return. The 1st row that contain two elements in a single cell is not included but it should be included. Lastly, count the elements in the returned column based on the criteria.
Here's the code I have tried:
function filter(){
//opened ss via url
const ws = ss.getSheetByName("Sample");
const range = ws.getRange(2,1,ws.getLastRow() - 1,2).getValues();
const initial = range.map(function(n){return n[0];});
const filtered = initial.filter(filterLogic);
Logger.log(initial); // [MWP, KBB, JET, JJB, KBB, MKGC, KBB]
Logger.log(filtered); // [KBB, KBB]
}
function filterLogic(name){
if(name == "KBB"){
return true;
} else {
return false;
}
}
The above code is only for the criteria. Not included is the counting of elements for the returned value from another column after the filter is applied.
What should I do so I can include the first row that contains the text "KBB" as well in my filtered data. Is there any other way around this?
Code:
function searchForKBB(n = "KBB") {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName("Sheet0");
const osh = ss.getSheetByName("Sheet1");
let o = sh.getRange(1,1,sh.getLastRow(),sh.getLastColumn()).createTextFinder(n).matchEntireCell(false).findAll().map(rg => [rg.getA1Notation()]);
o.unshift(["Ranges"]);
osh.getRange(1,1,o.length,o[0].length).setValues(o)
}
Data:
A
B
1
Initials
Application Reference
2
MWB.KBB
1001
3
JET,JJB
1002
4
KBB
100,310,041,005
5
MKGC
1006
6
KBB
1007
Results:
Ranges
A2
A4
A6
Maybe you can do this:
```
function getAllKBBs(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var ss1 = ss.getSheetByName("YOUR_SHEET_NAME");
var range = ss1.getRange(1,1,ss1.getLastRow(),4).getValues();
output = whenTextContains("KBB", range, 1, 1);
Logger.log(output.length);
} ```
where whenTextContains() function is in this repository
https://github.com/NikolaPlusEqual/GoogleAppsScriptFilters/blob/main/Functions
Or, you can copy this into you code and call above function:
function letterToColumn(letter){
var column = 0, length = letter.length;
for (var i = 0; i < length; i++)
{
column += (letter.charCodeAt(i) - 64) * Math.pow(26, length - i - 1);
}
return column;
}
////// source for letterToColumn() function :
////// https://stackoverflow.com/questions/21229180/convert-column-index-into-corresponding-column-letter
var setData = {}
function whenTextContains(txt, rng, col, targetCol = 0){
if (typeof col == "number" && typeof txt == "string"){
setData.col = col;
setData.txt = txt;
}
else{
return;
}
var output = rng.filter(wtc);
if(targetCol == 0){
return output;
}
else if(typeof targetCol == "number"){
var result = output.map(function (item) {
return item[targetCol-1];
});
return result;
}
else if(typeof targetCol == "string"){
var targetnum = letterToColumn(targetCol);
var result = output.map(function (item) {
return item[targetnum-1];
});
return result;
}
else{
return;
}
}
function wtc(ar){
var txt = setData.txt;
var col = setData.col - 1;
var str = ar[col].toString();
return str.includes(txt);
}

How to delete rows fast if they have empty values at specific columns in Google App Script

below is a code that checks if the cells at columns [G, H, J] are empty and delete the row where the the condition is true.
Nevertheless, the runtime of the code below is extremely slow, needs approx 15 minutes per 1500 table entries.
is there any way to optimise it ?
can I hash the rows that meet the condition below and then delete them all at once?
P.S: the original code can be found here https://gist.github.com/dDondero/285f8fd557c07e07af0e which I adapted it to my use case.
function deleteRows() {
var sheet = SpreadsheetApp.getActiveSheet();
var rows = sheet.getDataRange();
var lastRow =sheet.getRange(1,1).getDataRegion(SpreadsheetApp.Dimension.ROWS).getLastRow() + 1;
var values = rows.getValues();
var row;
var rowsDeleted = 0;
for (var i = 0; i < lastRow; i++) {
row = values[i];
if (row[9] == '' && row[7] == '' && row[6] == ''
) {
sheet.deleteRow((parseInt(i)+1) - rowsDeleted);
rowsDeleted++;
}
}
}
Try:
function DeleteEmptyRows() {
const sheet = SpreadsheetApp.getActiveSheet()
const values = sheet.getDataRange()
.getValues()
.filter(row => row[9] !== '' && row[7] !== '' && row[6] !== '')
sheet.getDataRange().clearContent()
return sheet.getRange(1, 1, values.length, values[0].length)
.setValues(values)
}
If you have any background colors attached to rows, let me know and I can make an adjustment for you.
This code will filter out all rows with the empty cells specified, clear the sheet, and then set the values.
Delete rows
function deleteRows() {
const ss = SpreadsheetApp.getActive();
const sh = ss.getActiveSheet();
const rg = sh.getDataRange();
const vs = rg.getValues().filter(r => !(!r[6] || !r[7] || !r[9]));
rg.clearContent();
sh.getRange(1, 1, vs.length, vs[0].length).setValues(vs);
}

Improving performance of Trace Dependents script in Google Apps Script

I have a Google Apps Script that will replicate Excel's 'Trace Dependents' function by finding all the dependents of a cell from the entire worksheet, taking into account named ranges. The script works perfectly for small worksheets, unfortunately when working with largish worksheets the script will time out before it manages to complete. I have worksheets with around 1m+ cells and the script sometimes manages to run fully but even then it takes around 5 minutes which is quite long.
Essentially the script works by looping through every formula in the worksheet and performing regex tests on them to see if the formulas include the audited cells reference or name.
I was wondering if there are any quick wins in my script that could help speed up the performance, or if anyone has any suggestions on how to go about improving somehow?
Apologies if this isn't the right place to ask this sort of question, if there is somewhere else I should ask this please let me know.
const getNamedRange = function (actSheet, cell) {
//loop through the sheets named ranges and if the nr's range is the cell, then that name is the name of the current cell
let matchedName;
actSheet.getNamedRanges().forEach((name) => {
if (name.getRange().getA1Notation() === cell) matchedName = name.getName();
});
return matchedName;
};
const isInRange = function (ss, currentCell, stringRange, j, k) {
//extract the sheet name from the range if it has one
const sheetName = stringRange[0].toString().match(/^(.*)!/)
? stringRange[0].toString().match(/^(.*)!/)[1]
: null;
//if there is a sheet name, get the range from that sheet, otherwise just get the range from the active sheet as it will be on the same sheet as audited cell
const range = sheetName
? ss.getSheetByName(sheetName).getRange(stringRange)
: ss.getActiveSheet().getRange(stringRange);
const startRow = range.getRow();
const endRow = startRow + range.getNumRows() - 1;
const startCol = range.getColumn();
const endCol = startCol + range.getNumColumns() - 1;
const cellRow = currentCell.getRow();
const cellCol = currentCell.getColumn();
const deps = [];
if (cellRow >= startRow && cellRow <= endRow && cellCol >= startCol && endCol <= endCol)
deps.push([j, k]);
return deps
};
function traceDependents() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const currentCell = ss.getCurrentCell();
const curCellRef = currentCell.getA1Notation();
const dependentRefs = [];
const sheets = ss.getSheets();
const actSheet = ss.getActiveSheet();
const actSheetName = actSheet.getName();
const actIndex = actSheet.getIndex();
//get the name of the cell
const namedRange = getNamedRange(actSheet, curCellRef);
//get the row and column text from the current cell
const rowText = currentCell.getRow().toString();
const columnText = curCellRef.substring(0, curCellRef.length - rowText.length);
//If the sheet name has a space, then need to add the quote marks and ! as per Google Sheets standard
const formattedActSheetName = actSheetName.includes(" ")
? `'${actSheetName}'!`
: `${actSheetName}!`;
for (let i = 0; i < sheets.length; i++) {
const range = sheets[i].getDataRange();
const formulas = range.getFormulas();
const dependents = [];
//If the sheet is the current sheet, then all references will not have the sheet ref, so it should be blank
const curSheetRef = i === actIndex - 1 ? "" : formattedActSheetName;
//create the tests to see if the formulas include the current cell
const crRegex = new RegExp(
`(?<!!|:)${curSheetRef}${curCellRef}(?![0-9])|` +
`(?<!!|:)${curSheetRef}\\$${curCellRef}(?![0-9])|` +
`(?<!!|:)${curSheetRef}[^$]${columnText}\\$${rowText}(?![0-9])|` +
`(?<!!|:)${curSheetRef}\\$${columnText}\\$${rowText}(?![0-9])`
);
const nrRegex = new RegExp(`(?<!_)${namedRange}(?!_)`);
//run through all of the cells in the sheet and test their formulas with the above to see if they are dependents
for (let j = 0; j < formulas.length; j++) {
const row = formulas[j];
for (let k = 0; k < row.length; k++) {
const cellFormula = row[k];
if (crRegex.test(cellFormula) || nrRegex.test(cellFormula))
dependents.push([j, k]);
//check if the current cell formula includes a range in it, e.g. A1:A20, if it does, create a unique array with all the large ranges
const largeRegex = new RegExp(
`(?<!!|:|\\$)${curSheetRef}\\$?[A-Z]{1,3}\\$?([0-9]{1,7})?:\\$?[A-Z]{1,3}\\$?([0-9]{1,7})?`,
"g"
);
const largeRange = [...new Set(cellFormula.match(largeRegex))];
//if there are any large ranges, check if the range includes the audited cell. If it does, add the cell to the dependents
if (largeRange) {
largeRange.forEach((range) => {
range.replaceAll("$", "");
isInRange(ss, currentCell, range, j, k).forEach((dep) =>
dependents.push(dep)
);
});
}
}
}
//Format the dependent's cell references with their sheet name to allow navigation to them
for (let l = 0; l < dependents.length; l++) {
const cell = range.getCell(dependents[l][0] + 1, dependents[l][1] + 1);
dependentRefs.push(`${sheets[i].getName()}!${cell.getA1Notation()}`);
}
}
//Add the current cell as the first element of the array
dependentRefs.unshift(`${actSheetName}!${curCellRef}`);
return [...new Set(dependentRefs)];
}

How to optimize Google Spreadsheet function execution time?

function findAgentRow(){
let row = 2;
let emailCell;
let maxRow = sheet.getLastRow();
while(--maxRow){
emailCell = sheet.getRange(row, findColumn(sheet, "Agent"));
if(emailCell.getValue() == agentEmail){
return row
}else if(row == sheet.getLastRow()){
return 0;
}else{
row = row + 1;
}
}
}
I have written a google spreadsheets function that locates a row containing information about a person. It checks whether the row contains the person's email. The issue is that when the spreadsheet contains more than 700 rows the execution time is more than 2 minutes.
Is there a way to optimize it?
Try not to use findColumn in each loop.
You should also aim at using only one getValues.
function findAgentRow(agentEmail) {
// const sheet = SpreadsheetApp.getActiveSheet();
const row = 2;
const col = findColumn(sheet, 'Agent');
const values = sheet.getRange(row, col, sheet.getLastRow() - 1, 1).getValues().flat();
return values.indexOf(agentEmail) + row;
}
function findColumn(sheet, value) {
const row = 1;
const values = sheet.getRange(row, 1, 1, sheet.getLastColumn()).getValues()[0];
return values.indexOf(value) + 1;
}

Trying to paste Values from formula Google App Script

This is just a snippet of my code from Google App Script which iterates through each row in columns 1, 2, 3. If an edit is made in column 3, an incremental ID will be generated and a concatenation of the same row and different columns will also be generated - in this case Column D, E, and F. I am struggling with figuring out a way to change the formulas into values. What am I missing here?
// Location format = [sheet, ID Column, ID Column Row Start, Edit Column]
var locations = [
["Consolidated Media Plan",1,9,3]
];
function onEdit(e){
// Set a comment on the edited cell to indicate when it was changed.
//Entry data
var range = e.range;
var col = range.getColumn();
var row = range.getRow();
// Location Data
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
function getNewID(){
function IDrange(){
var dataRange = sheet.getDataRange();
var lastRow = dataRange.getLastRow();
return sheet.getRange(IDrowStart,IDcol,lastRow-IDrowStart).getValues();
};
//Get largest Value in range
function getLastID(range){
var sorted = range.sort();
var lastIDval = sorted[sorted.length-1][0];
return lastIDval;
};
//Stores leading letters and zeroes and trailing letters
function getLettersNzeroes(id){
//Get any letters or zeroes.
var re = new RegExp("^([a-zA-Z0])$");
var letterZero = [];
for(char = 0; char < id.length; char++){
if(re.test(id[char])){
letterZero.push([char,id[char]]);// [[position, letter or zero]]
};
};
// Categorize letters and zeroes into start and end blocks
var startLetterZero = "",
endLetter = "",
len = letterZero.length - 1;
for(j = 0; j < letterZero.length; j++){
if(letterZero[j][0] === j){
startLetterZero += letterZero[j][1];
}else if(letterZero[j][1] !== "0" && letterZero[len][0] - (len - j) == letterZero[j][0]){
endLetter += letterZero[j][1];
};
};
var startNend = {"start":startLetterZero,"end":endLetter};
return startNend;
};
//Gets last id number. Adds 1 an checks to set if its new length is greater than the lastNumber.
function getNewNumber(id){
var removeZero = false;
var lastNum = parseInt(id.replace(/\D/g,''),10);//Remove letters
var newNum = (lastNum+1).toString();
if(lastNum.toString().length !== newNum.length){
var removeZero = true;
};
var newNumSet = {"num":newNum, "removeZero": removeZero};
return newNumSet
};
var lastID = getLastID(IDrange());
var lettersNzeroes = getLettersNzeroes(lastID);
var newNumber = getNewNumber(lastID);
//If the number is 9,99,999,9999 etc we need to remove a zero if it exists.
if(newNumber.removeZero === true && lettersNzeroes.start.indexOf("0") !== -1.0){
lettersNzeroes.start = lettersNzeroes.start.slice(0,-1);
};
//Rejoin everything together
var newID = lettersNzeroes.start +
newNumber.num +
lettersNzeroes.end;
return newID;
};
for(i = 0; i < locations.length; i++){
var sheetID = locations[i][0],
IDcol = locations[i][1],
IDrowStart = locations[i][2],
EditCol = locations[i][3];
var offset = IDcol - EditCol;
var cell = sheet.getActiveCell();
if(sheetID === sheet.getName()){
if(EditCol === col){
//ID Already Exists the editing cell isn't blank.
if(cell.offset(0,offset).isBlank() && cell.isBlank() === false){
var newID = getNewID();
cell.offset(0,offset).setValue(newID);
cell.offset(0,-1).setFormulaR1C1('=concatenate(R[0]C[-1],"_",INDEX(Glossary!K:K,MATCH(R[0]C[2],Glossary!J:J,0)))');
};
};
};
};
};
EDIT:
This is my full code, I have been unsuccessful with trying to retrieve just the values of the formula within the same (i.e, If C9 gets edited, a formula with the values specific to the 9th row should be populated)
Also, I've tried to add an index/match formula to the concatenation formula at the bottom of the code - it works as expected on the google sheets, but when I run it with the script it pastes the correct formula but it returns a #NAME? error message. However, when I copy and paste the exact same formula in the cell, it works perfectly, any idea what could be causing this error?
This works for me. I know it's not exactly the same thing but I didn't have access to getNewId()
function onEdit(e) {
var sh=e.range.getSheet();
if(sh.getName()!='Sheet1')return;
//e.source.toast('flag1');
if(e.range.columnStart==3 && e.range.offset(0,1).isBlank() && e.value) {
//e.source.toast('flag2');
e.range.offset(0,1).setValue(e.value);
e.range.offset(0,2).setFormulaR1C1('=concatenate(R[0]C[-1],"_",R[0]C[-2],"_",R[0]C[-3],"_",R[0]C[-4])');
}
}

Categories

Resources