I'm trying to build a Macro to erase all the rows that have empty values on column D. Originally, I was using this code that I found:
function deleteRows() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = ss.getSheetByName('Datos Competidor 2 - EV');
var r = s.getRange('D:D');
var v = r.getValues();
for(var i=v.length-1;i>=0;i--)
if(v[0,i]=='')
s.deleteRow(i+1);
};
However the excessive number of calls to the API made this really slow and some times even fail due to a timeout.
I decided to just add all the rows that met the condition to a list and then just pass that to the deleteRow() in order to only call the API once using this code:
function deleteBlankRows() {
emptyRange=[]
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = ss.getSheetByName('Datos Competidor 2 - EV');
var r = s.getRange('D:D');
var v = r.getValues();
for(var i=v.length-1;i>=0;i--)
if(v[0,i]=='')
emptyRange.push((i)+":"+(i));
ss.getRangeList(emptyRange).activate();
ss.getActiveSheet().deleteRows(ss.getActiveRange().getRow(), ss.getActiveRange().getNumRows());
};
The execution seems to work just fine, completing in 1 to 2 seconds, however, rows aren't erased as much as selected by the end of the execution.
This is what I see:
Final result
Any ideas why this is happening?
Thanks!
I believe your goal is as follows.
You want to reduce the process cost of your script.
In this case, how about using Sheets API? When Sheets API is used, I thought that the process cost for deleting the rows can be reduced a little. When the Sheets API is reflected in your script, it becomes as follows.
Modified script:
Before you use this script, please enable Sheets API at Advanced Google services.
function deleteRows() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = ss.getSheetByName('Datos Competidor 2 - EV');
var values = s.getRange('D1:D' + s.getLastRow()).getDisplayValues();
var sheetId = s.getSheetId();
var requests = values.reduce((ar, [d], i) => {
if (!d) ar.push({ deleteDimension: { range: { sheetId, startIndex: i, endIndex: i + 1, dimension: "ROWS" } } });
return ar;
}, []).reverse();
Sheets.Spreadsheets.batchUpdate({ requests }, ss.getId());
}
References:
Method: spreadsheets.batchUpdate
DeleteDimensionRequest
Delete Rows with empties on column D
function deleteBlankRows() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const s = ss.getSheetByName('Datos Competidor 2 - EV');
const r = s.getRange('D1:D' + s.getLastRow());
const v = r.getValues().flat();
let d = 0;
v.forEach((e, i) => {
if (!e) {
s.deleteRow(i + 1 - d++)
}
})
}
Related
Is there a app script I can run that will update the ranges of all the filter views on a sheet at once?
I have hundreds of filter views, and it would be laborious to do it manually.
The filter views are all on a sheet called "Data'. I need to change the range from A1:AB3116 TO A1:AB9011
Thanks for any help.
I believe your goal is as follows.
You want to change the range of filter views.
You want to change from "A1:AB3116" to "A1:AB9011" of all filter views in "data" sheet.
In this case, how about the following sample script?
Sample script:
Please copy and paste the following script to the script editor of Spreadsheet and enable Sheets API at Advanced Google services, and save the script.
Please confirm sheetName and obj. In this sample, your provided information is used.
function myFunction() {
var sheetName = "data"; // This is from your question.
var obj = [{ before: "A1:AB3116", after: "A1:AB9011" }]; // This is from your question.
// Retrieve spreadsheet and sheet.
var ss = SpreadsheetApp.getActiveSpreadsheet();
var spreadsheetId = ss.getId();
var sheet = ss.getSheetByName(sheetName);
var sheetId = sheet.getSheetId();
// Convert a1Notation to gridRange.
var o = obj.map(({ before, after }) =>
[before, after].map(r => {
var rng = sheet.getRange(r);
var rowStart = rng.getRow() - 1;
var rowEnd = rowStart + rng.getNumRows();
var colStart = rng.getColumn() - 1;
var colEnd = colStart + rng.getNumColumns();
return { sheetId, startRowIndex: rowStart, endRowIndex: rowEnd, startColumnIndex: colStart, endColumnIndex: colEnd };
})
);
// Create request body for using the batchUpdate of Sheets API.
var filterViews = Sheets.Spreadsheets.get(spreadsheetId, { ranges: [sheetName], fields: "sheets(filterViews)" }).sheets[0].filterViews;
var requests = filterViews.reduce((ar, { range, ...e }) => {
var check = o.find(([{ startRowIndex, endRowIndex, startColumnIndex, endColumnIndex }]) => range.startRowIndex == startRowIndex && range.endRowIndex == endRowIndex && range.startColumnIndex == startColumnIndex && range.endColumnIndex == endColumnIndex);
if (check) {
ar.push({ updateFilterView: { filter: { filterViewId: e.filterViewId, range: check[1] }, fields: "*" } });
}
return ar;
}, []);
// Reuest Sheets API using the created request body.
if (requests.length == 0) return;
Sheets.Spreadsheets.batchUpdate({ requests }, spreadsheetId);
}
When this script is run, the filter views are retrieved from "data" sheet. And, the range of A1:AB3116 is searched from the retrieved filter views, and when it is found, the range is changed to A1:AB9011 and update the filter views.
In this sample, when you change multiple changes of ranges, you can use them in obj.
References:
Method: spreadsheets.get
Method: spreadsheets.batchUpdate
Related thread
Script to Update Multiple Google Sheet Filter View Ranges
I have the below code that checks through a sheet for the words "CASH". Once it's found, it will copy the row to a new sheet then delete the row.
Prior, my range for the values was set up as so:
var range = ss1.getRange(2, 1, lr, lc);
But now that my sheet has 6000+ rows, this is highly inefficient. I've changed it so that it only looks through a range that is lr-100 so that it doesn't have to dig so deep.
Since making that change, my old code to delete rows ss1.deleteRow(i+2) is no longer valid, because i references the row only within that particular range (i.e., if it's the 90th row out of that 100, i = 90, and that would end up deleting the 90th row in my sheet when it should have been, for example, 6500.
question How do I find the correct row# in this new way my script is setup?
var etfar = ["BOTZ"]
function doCash() {
for (var i = 0; i < etfar.length; i++) {
cashMoney(etfar[i])
}
};
function cashMoney(etf) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var ss1 = ss.getSheetByName(etf);
var ss2 = ss.getSheetByName("Cash");
var lr = ss1.getLastRow();
var lc = ss1.getLastColumn();
var range = ss1.getRange(lr-100, 1, lr, lc);
var data = range.getValues();
for (var i = data.length - 1; i >= 0; i--)
{
var check = data[i][4] // ith row, 3rd column
if (check.includes("CASH")) {
var rowUsd = data[i];
// something has to happen here to find the exact row
ss2.appendRow(rowUsd);
//ss1.deleteRow(i+2); <-- this is old code. Prior, in my 'range' I used to start my rows from "2", but that was highly inefficient and I've changed it to "lr-100" so that it begins checking from the last 100 rows instead
}
};
};
From now that my sheet has 6000+ rows, this is highly inefficient., in your situation, in order to reduce the process cost of the script, how about using Sheets API? When Sheets API is used for your situation, all rows might be able to be processed. When Sheets API is used for your script, it becomes as follows.
Modified script:
This script uses Sheets API. So, please enable Sheets API at Advanced Google services. And, please set etfar.
function sample() {
var etfar = ["Sheet1", "Sheet2",,,]; // Please set your sheet names.
var ss = SpreadsheetApp.getActiveSpreadsheet();
var dstSheet = ss.getSheetByName("Cash");
var dstSheetId = dstSheet.getSheetId();
var lastRow = dstSheet.getLastRow();
var { copyRows, deleteRows } = etfar.reduce((o, e) => {
var srcSheet = ss.getSheetByName(e);
var srcSheetId = srcSheet.getSheetId();
srcSheet.getRange("E2:E" + srcSheet.getLastRow()).getValues().forEach(([v], i) => {
if (v == "CASH") {
o.copyRows.push({ copyPaste: { source: { sheetId: srcSheetId, startRowIndex: i + 1, endRowIndex: i + 2, startColumnIndex: 0 }, destination: { sheetId: dstSheetId, startRowIndex: lastRow, endRowIndex: lastRow + 1, startColumnIndex: 0 } } });
o.deleteRows.push({ deleteDimension: { range: { sheetId: srcSheetId, dimension: "ROWS", startIndex: i + 1, endIndex: i + 2 } } });
lastRow++;
}
});
return o;
}, { copyRows: [], deleteRows: [] });
var requests = [...copyRows, ...deleteRows.reverse()];
if (requests.length == 0) return;
Sheets.Spreadsheets.batchUpdate({ requests }, ss.getId());
}
When this script is run, the values searched by the column "E" are retrieved from the sheets of etfar, and those values are appended to the destination sheet "Cash". And also, the copied rows of each sheet are removed.
In this script, these processes can be run by one API call.
Note:
When I saw your script, from var check = data[i][4] // ith row, 3rd column, I thought that data[i][4] is the coumn "E". But you say 3rd column. It's the column "C". If you want to search the value of "CASH" from the column "C", please modify getRange("E2:E" + srcSheet.getLastRow()) to getRange("C2:C" + srcSheet.getLastRow()).
References:
Method: spreadsheets.batchUpdate
CopyPasteRequest
DeleteDimensionRequest
I think this is what you want
function cashMoney(etf) {
var ss = SpreadsheetApp.getActive();
var sh1 = ss.getSheetByName(etf);
var sh2 = ss.getSheetByName("Cash");
var lr = sh1.getLastRow();
var lc = sh1.getLastColumn();
var range = sh1.getRange(lr - 100, 1, 101, lc);
var data = range.getValues();
var d = 0;
for (var i = data.length - 1; i >= 0; i--) {
var check = data[i][4] // ith row, 3rd column
if (check.includes("CASH")) {
var rowUsd = data[i];
sh2.appendRow(rowUsd);
sh.deleteRow(lr - 100 + i - d++);The row is data start row + i
}
};
}
The d keeps track of the rows that have already been deleted from the spreadsheet which is necessary because they have not been deleted from the data set.
I have a sheet with more than 3000 rows and I want to group those rows by a parameter in ColA. So all rows having '1' in colA should be grouped under the row above with '0' in colA. Once groups are created I want them to collapse.
Since the script will be triggered daily upon data update, I also need the before-created groups to be removed so the new ones can be properly created.
I have several scrips doing what I need but it takes forever for them to go through all the rows. Is it possible to optimize them in some way or perhaps a different approach can be used for my needs? Thanks in advance for your help!
function removeAllGroups1() {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName("Pipeline");
const rg = sh.getDataRange();
const vs = rg.getValues();
vs.forEach((r, i) => {
let d = sh.getRowGroupDepth(i + 1);
if (d >= 1) {
sh.getRowGroup(i + 1, d).remove()
}
});
}
function groupRows1() {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName('Pipeline');
const levels = sh.getRange(2, 1, sh.getLastRow() - 1).getValues().flat();
levels.forEach((e, i) => sh.getRange(i + 2, 1).shiftRowGroupDepth(e));
}
function collapse() {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName('Pipeline');
let lastRow = sh.getDataRange().getLastRow();
for (let row = 1; row < lastRow; row++) {
let depth = sh.getRowGroupDepth(row);
if (depth < 1) continue;
sh.getRowGroup(row, depth).collapse();
}
}
Data Sample: https://docs.google.com/spreadsheets/d/10BNrnAyQw89gy-Sj3CLiz4AgVtFI0AjXTvc0REGGTfY/edit#gid=113574154
I believe your goal is as follows.
You want to group the rows with high process speed.
You want to delete all groups with high process speed.
You want to collapse all groups with high process speed.
From the above goal, I thought that when Sheets API is used, the process cost can be reduced. And, Sheets API can group rows, delete all groups and collapse all groups.
Sample script:
Before you run this script, please enable Sheets API at Advanced Google services.
function removeAllGroups2() {
const sheetName = "sample";
const ss = SpreadsheetApp.getActive();
const ssId = ss.getId();
const sheetId = ss.getSheetByName(sheetName).getSheetId();
const n = Sheets.Spreadsheets.get(ssId, { ranges: [sheetName] }).sheets[0].rowGroups.reduce((n, { depth }) => n < depth ? depth : n, 0);
const requests = Array(n).fill("").map(_ => ({ deleteDimensionGroup: { range: { sheetId, dimension: "ROWS" } } }));
Sheets.Spreadsheets.batchUpdate({ requests }, ssId);
}
function groupRows2() {
const sheetName = "sample";
const ss = SpreadsheetApp.getActive();
const sheet = ss.getSheetByName(sheetName);
const levels = sheet.getRange("A2:A" + sheet.getLastRow()).getValues();
const sheetId = sheet.getSheetId();
const requests = levels.flatMap(([a], i) => Array(a).fill("").map(_ => ({ addDimensionGroup: { range: { sheetId, startIndex: i + 1, endIndex: i + 2, dimension: "ROWS" } } })));
Sheets.Spreadsheets.batchUpdate({ requests }, ss.getId());
}
function collapse2() {
const ss = SpreadsheetApp.getActive();
const requests = Sheets.Spreadsheets.get(spreadsheetId, {ranges: ["sample"]}).sheets[0].rowGroups.map(r => {
r.collapsed = true;
return { updateDimensionGroup: { fields: "*", dimensionGroup: r }};
});
Sheets.Spreadsheets.batchUpdate({ requests }, ss.getId());
}
Note:
I tested these sample scripts using your sample Spreadsheet. So when the structure of the Spreadsheet is different from your sample Spreadsheet, these scripts might not be able to be used. Please be careful about this. At first, please test them using your sample script.
References:
Method: spreadsheets.get
Method: spreadsheets.batchUpdate
DeleteDimensionGroupRequest
AddDimensionGroupRequest
UpdateDimensionGroupRequest
I would like to find if a certain value is in a range using app scripts for google sheets.
var sheet = SpreadsheetApp.getActiveSheet();
var rangeBikeNumbers = sheet.getDataRange("A5:A5000");
var values = rangeBikeNumbers.getValues();
If I have my range rangeBikeNumbers, how can I check if the number "42" for example is in that range. I have searched for hours now and have beeb unable to find any answer to this. indexOf only seems to return -1, regardless of whether or not the value is in the range.
var indexDataNumber = values.indexOf(42); for example always ends up being -1
I believe your goal as follows.
You want to check whether the value of 42 is existing in the range of A5:A5000.
In this case, I would like to propose to use TextFinder. Because when TexiFinder is used, the process cost is low. Ref By the way, getDataRange has not arguments. From your script, I thought that you might want var rangeBikeNumbers = sheet.getRange("A5:A5000");.
When this is reflected to your script, it becomes as follows.
Modified script:
function myFunction() {
var sheet = SpreadsheetApp.getActiveSheet();
var rangeBikeNumbers = sheet.getRange("A5:A5000");
var find = rangeBikeNumbers.createTextFinder("42").matchEntireCell(true).findNext();
if (find) {
// In this case, the value of 42 is existing in the range.
} else {
// In this case, the value of 42 is NOT existing in the range.
}
}
Note:
About var indexDataNumber = values.indexOf(42); for example always ends up being -1, I think that the reason of this issue is due to that values is 2 dimensional array. If you want to use this, you can also use the following script.
function myFunction() {
var sheet = SpreadsheetApp.getActiveSheet();
var rangeBikeNumbers = sheet.getRange("A5:A5000");
var values = rangeBikeNumbers.getValues();
var find = values.map(([e]) => e).indexOf(42); // of values.flat().indexOf(42);
if (find > -1) {
// In this case, the value of 42 is existing in the range.
} else {
// In this case, the value of 42 is NOT existing in the range.
}
}
References:
Benchmark: Process Costs for Searching Values in Spreadsheet using Google Apps Script
getDataRange()
getRange(a1Notation)
createTextFinder(findText)
Select any active range that you wish to search and it will search for the seed in that at range. The seed is currently defaulted to 42 but you can change it.
function findSeedInRange(seed = 42) {
const ui = SpreadsheetApp.getUi();
const ss = SpreadsheetApp.getActive();
const sh = ss.getActiveSheet();
const rg = sh.getActiveRange();
const row = rg.getRow();
const col = rg.getColumn();
var found = false;
rg.getValues().forEach((r, i) => {
r.forEach((c, j) => {
if (c == seed) {
let r = sh.getRange(i + row, j + col).getA1Notation();
ui.alert(`Found ${seed} in ${r}`);
found = true;
}
})
})
if(!found) {
ui.alert(`Did not find ${seed}`);
} else {
ui.alert('That is all.')
}
}
Here's another approach:
function findSeedInRange() {
const ui = SpreadsheetApp.getUi();
const ss = SpreadsheetApp.getActive();
const sh = ss.getActiveSheet();
const rg = sh.getActiveRange();
const resp = ui.prompt('Enter Seed', 'Enter Seed', ui.ButtonSet.OK_CANCEL)
if (resp.getSelectedButton() == ui.Button.OK) {
var seed = parseInt(resp.getResponseText());
const row = rg.getRow();
const col = rg.getColumn();
var found = false;
rg.getValues().forEach((r, i) => {
r.forEach((c, j) => {
if (c == seed) {
let r = sh.getRange(i + row, j + col).getA1Notation();
ui.alert(`Found ${seed} in ${r}`);
found = true;
}
});
});
if (!found) {
ui.alert(`Did not find ${seed}`);
} else {
ui.alert('That is all.')
}
} else {
ui.alert('Operation cancelled.')
}
}
I have the following problem with this function. It only deletes blank rows below but I want to delete the rows above.
function removeEmptyRows(){
var sh = SpreadsheetApp.getActive();
var sh1=sh.getSheetByName('name');
var range=sh1.getRange('A:A');
var maxRows = sh1.getMaxRows();
var lastRow = sh1.getLastRow();
sh1.deleteRows(lastRow+1, maxRows-lastRow);
}
I tried with the following function
function removeemptyrows(){
var ss=SpreadsheetApp.getActive();
var sh1=ss.getSheetByName('name');
var range=sh1.getRange('A:A');
var values = range.getValues();
for( var i = values.length-1; i >=0; i-- ) {
for( var j = 0; j < values[i].length; j++ )
if( values[i][j] === "" )
sh1.deleteRow(i+1)
}
}
but it deletes rows too slowly - one by one.
You want to delete the rows that the cell of column "A" is empty in the range from 10 row to bottom of sheet.
You want to achieve this using Google Apps Script.
You want to reduce the process cost.
If my understanding is correct, how about this answer? Please think of this as just one of several possible answers.
Modification point:
In this case, I would like to propose to use Sheets API. When Sheets API is used, the rows can be deleted by one API call, even when the rows are discreted.
Sample script:
When you use this script, please enable Sheets API at Advanced Google services.
function removeemptyrows() {
var sheetName = "name"; // Please set the sheet name.
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName(sheetName);
var sheetId = sheet.getSheetId();
var values = sheet.getRange('A10:A').getValues();
var requests = values.reduce(function(ar, [e], i) {
if (!e) ar.push({deleteDimension:{range:{sheetId:sheetId,dimension:"ROWS",startIndex:(i + 9),endIndex:(i + 10)}}});
return ar;
}, []).reverse();
if (requests.length > 0) Sheets.Spreadsheets.batchUpdate({requests: requests}, ss.getId());
}
In this case, the sample script is almost the same with the below script of https://stackoverflow.com/a/60613983/7108653 . As 2 modification parts, in your case, you want to delete the rows of empty cell at the column "A". So 'C6:C' + sheet.getLastRow() and if (e) were modified to 'A10:A' and if (!e), respectively.
References:
Method: spreadsheets.batchUpdate
Advanced Google services
If I misunderstood your question and this was not the direction you want, I apologize.