How to filter data in columns using google app script editor - google-apps-script

I'm facing some issues related to filter data in the columns using google app script editor.
I'm able to set a filter in columns using google app script as you can see in the above screenshot. but problem is when I'm trying to get the filtered data. it returns some number series instead of actual data as you can see below :
[20-03-09 18:19:48:395 IST] [1,2,4,5,6,8,9,10,11,12,13,14,15,19,20,21,22,23,24,26,27,28,29,30]
To set a filter :
function setFilter() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var filterSettings = {};
// The range of data on which you want to apply the filter.
// optional arguments: startRowIndex, startColumnIndex, endRowIndex, endColumnIndex
filterSettings.range = {
sheetId: ss.getActiveSheet().getSheetId()
};
// Criteria for showing/hiding rows in a filter
// https://developers.google.com/sheets/api/reference/rest/v4/FilterCriteria
filterSettings.criteria = {};
var columnIndex = 2;
filterSettings['criteria'][columnIndex] = {
'hiddenValues': ["England", "France"]
};
var request = {
"setBasicFilter": {
"filter": filterSettings
}
};
Sheets.Spreadsheets.batchUpdate({'requests': [request]}, ss.getId());
}
To get the filtered data:
function getFilteredRows() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var ssId = ss.getId();
var sheetId = ss.getActiveSheet().getSheetId();
let data = getIndexesOfFilteredRows(ssId,sheetId);
Logger.log(JSON.stringify(data));
}
function getIndexesOfFilteredRows(ssId, sheetId) {
var hiddenRows = [];
// limit what's returned from the API
var fields = "sheets(data(rowMetadata(hiddenByFilter)),properties/sheetId)";
var sheets = Sheets.Spreadsheets.get(ssId, {fields: fields}).sheets;
for (var i = 0; i < sheets.length; i++) {
if (sheets[i].properties.sheetId == sheetId) {
var data = sheets[i].data;
var rows = data[0].rowMetadata;
for (var j = 0; j < rows.length; j++) {
if (rows[j].hiddenByFilter) hiddenRows.push(j);
}
}
}
return hiddenRows;
}
How to set a filter in columns and get the filtered data using google app script.
Please help me with this.

In your case, the script for filtering has already worked. You want the script for retrieving the values from the filtered sheet in the Spreadsheet.
You want to achieve this using Sheets API with Google Apps Script.
If my understanding is correct, how about this modification? Please think of this as just one of several possible answers.
In your case, the function of getIndexesOfFilteredRows is modified. Using hiddenByFilter, the hidden rows and shown rows are retrieved as an object.
Modified script:
function getIndexesOfFilteredRows(ssId, sheetId) {
var object = {hiddenRows: [], hiddenRowValues: [], shownRows: [], shownRowValues: []};
// limit what's returned from the API
var fields = "sheets(data,properties/sheetId)";
var sheets = Sheets.Spreadsheets.get(ssId, {fields: fields}).sheets;
for (var i = 0; i < sheets.length; i++) {
if (sheets[i].properties.sheetId == sheetId) {
var data = sheets[i].data;
var rows = data[0].rowMetadata;
for (var j = 0; j < rows.length; j++) {
var r = [];
if (data[0].rowData[j] && Array.isArray(data[0].rowData[j].values)) {
r = data[0].rowData[j].values.map(function(e) {
var temp = "";
if (e.hasOwnProperty("userEnteredValue")) {
if (e.userEnteredValue.hasOwnProperty("numberValue")) {
temp = e.userEnteredValue.numberValue;
} else if (e.userEnteredValue.hasOwnProperty("stringValue")) {
temp = e.userEnteredValue.stringValue;
}
}
return temp;
});
}
if (r.length > 0) {
if (rows[j].hiddenByFilter) {
object.hiddenRows.push(j);
object.hiddenRowValues.push(r);
} else {
object.shownRows.push(j);
object.shownRowValues.push(r);
}
}
}
}
}
return object;
}
Result:
When above script is run for the filtered sheet, the following object which has the hidden row numbers, hidden row values, shown row numbers and shown row values is returned.
{
"hiddenRows":[0,1],
"hiddenRowValues":[["a1","b1","c1"],["a2","b2","c2"]],
"shownRows":[2,3],
"shownRowValues":[["a3","b3","c3"],["a4","b4","c4"]]
}
Reference:
DimensionProperties
If I misunderstood your question and this was not the direction you want, I apologize.
Added:
How about this sample script? In this sample script, the values filtered with filterValues can be retrieved as an object. In this case, the result is the same with your setFilter() and the modified getIndexesOfFilteredRows(). But the basic filter is not used. So please be careful this.
function myFunction() {
var filterValues = ["England", "France"]; // Please set the filter values.
var column = 3; // In this case, it's the column "C". Please set the column number.
var sheetName = "Sheet1"; // Please set the sheet name.
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName(sheetName);
var values = sheet.getDataRange().getValues();
var object = values.reduce(function(o, e, i) {
if (filterValues.indexOf(e[column - 1]) > -1) {
o.hiddenRows.push(i + 1);
o.hiddenRowValues.push(e);
} else {
o.shownRows.push(i + 1);
o.shownRowValues.push(e);
}
return o;
}, {hiddenRows: [], hiddenRowValues: [], shownRows: [], shownRowValues: []});
Logger.log(object)
}
If you want to retrieve only the filtered values, this script might be suitable.
In this case, the script can run with and without V8. But when V8 is enabled, the loop speed will be fast. Ref

Related

google sheets, apps script parsehtml error

this is the complete code,
function extractData() {
var url = "https://www.theopenalliance.com/teams/2023/";
var html = UrlFetchApp.fetch(url).getContentText();
var data = parseHtml(html);
var sheet = SpreadsheetApp.getActiveSheet();
sheet.clearContents();
if (data.length > 0) {
sheet.getRange(1, 1, data.length, data[0].length).setValues(data);
for (var i = 0; i < data.length; i++) {
for (var j = 0; j < data[i].length; j++) {
if (data[i][j].indexOf("http") === 0) {
var button = sheet.getRange(i + 1, j + 1).attachButton({
text: "Link",
url: data[i][j]
});
}
}
}
}
}
function parseHtml(html) {
var startIndex = html.indexOf("<tbody>");
var endIndex = html.indexOf("</tbody>");
var table = html.substring(startIndex, endIndex);
var rows = table.split("<tr>");
var data = [];
for (var i = 1; i < rows.length; i++) {
var cells = rows[i].split("<td");
var row = [];
for (var j = 1; j < cells.length; j++) {
var cell = cells[j];
var linkStartIndex = cell.indexOf("href=");
if (linkStartIndex !== -1) {
var linkEndIndex = cell.indexOf("class");
var link = cell.substring(linkStartIndex + 6, linkEndIndex - 2);
row.push(link);
} else {
row.push(cell.substring(cell.indexOf(">") + 1, cell.indexOf("</td>")));
}
}
data.push(row);
}
return data;
}
however function parseHtml(html) gives an error with this line
var startIndex = html.indexOf("<tbody>");
Anyone has any suggestions? i'm trying to copy and paste tables from the link to a google sheets.
i expected to see every teams numbers and other values (Public links, location etc) in google sheets but nothing shows up. Also i was expecting to see buttons that had links attached to them if the buttons exists, such as github, photos etc. Please check the link and im sure you will have a better idea of im trying to tell. Also please help me fix the code, if possible, copy and edit the code than repost it, i would greatly appreciate it
In your situation, how about using Sheets API? Because I thought that the HTML parser of Sheets API is useful for your situation. When Sheets API is used for your URL, how about the following sample script?
Sample script:
Before you use this script, please enable Sheets API at Advanced Google services.
function myFunction() {
const url = "https://www.theopenalliance.com/teams/2023/"; // This is from your script.
const html = UrlFetchApp.fetch(url).getContentText();
const table = html.match(/<table[\s\S\w]+?<\/table>/);
if (!table) {
throw new Error("Table was not found.");
}
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss.getActiveSheet().clearContents();
SpreadsheetApp.flush();
const requests = { requests: [{ pasteData: { html: true, data: table[0], coordinate: { sheetId: sheet.getSheetId() } } }] };
Sheets.Spreadsheets.batchUpdate(requests, ss.getId());
}
When this script is run, a table is retrieved from the URL and put it into the active sheet.
References:
Method: spreadsheets.batchUpdate
PasteDataRequest

How can Google Sheets Form Update Records from Results Using Google App script?

I have a program that filters and updates data from an existing sheet.
The program works as follows:
1. Find and filter out the required value
2. Enter data in [Adjustment] column then update to database in Record sheet.
I tried to try but my program doesn't seem to work.
I tried to edit the program code but when run it will affect the other columns and the [adjustment] column value is entered wrong.
This is my link program
function Searchold(){
var ss = SpreadsheetApp.getActiveSpreadsheet ();
var shtRecords = ss. getSheetByName ("RECORD");
var shtForm = ss. getSheetByName ("TEST") ;
var records = shtRecords. getDataRange () . getValues ();
var sField = shtForm. getRange ("A3").getValue ();
var sValue = shtForm.getRange ("A6").getValue();
var sCol = records [0].lastIndexOf(sField);
var results = records.filter(function(e){return sValue == e[sCol] });
if(results.length==0){SpreadsheetApp.getUi().alert("not found values");}
else{
shtForm.getRange(9,1,results.length,results[0].length).setValues(results);
}
}
function Updatenew(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var shtRecords = ss.getSheetByName("RECORD");
var shtForm = ss.getSheetByName("TEST");
var LastRow = shtForm.getRange("A8").getNextDataCell(SpreadsheetApp.Direction.DOWN).getLastRow();
var newData = shtForm.getRange(9,1,LastRow -1,7).getValues();
for(var i =0; i<newData.length;i++){
var oldData= shtRecords.getDataRange().getValues();
for(var j= 0;j<oldData.length;j++){
if(newData[i][0] ==oldData[j][0]){
var newData2 = [newData[i]];
shtRecords.getRange(j + 1,1,1,newData2[0].length).setValues(newData2);
}
}
}
}
Can you help me with the update program? Sincerely thank you
Modification points:
When I saw your showing script of Updatenew, I think that each row of var oldData = shtRecords.getDataRange().getValues() is used in each loop of for (var i = 0; i < newData.length; i++) {}. By this, each row is overwritten by each row of newData. By this, all searched rows in "RECORD" sheet are the same value. I thought that this might be the reason for your issue.
var oldData = shtRecords.getDataRange().getValues(); can be used one call.
In order to avoid this issue by modifying your script, as one of several methods, how about the following modification?
From:
for (var i = 0; i < newData.length; i++) {
var oldData = shtRecords.getDataRange().getValues();
for (var j = 0; j < oldData.length; j++) {
if (newData[i][0] == oldData[j][0]) {
var newData2 = [newData[i]];
shtRecords.getRange(j + 1, 1, 1, newData2[0].length).setValues(newData2);
}
}
}
To:
var oldData = shtRecords.getDataRange().getValues();
for (var j = 0; j < oldData.length; j++) {
for (var i = 0; i < newData.length; i++) {
if (newData[0][0] == oldData[j][0]) {
var newData2 = newData.splice(0, 1);
shtRecords.getRange(j + 1, 1, 1, newData2[0].length).setValues(newData2);
break;
}
}
}
Note:
At the above modification, setValues is used in a loop. In this case, the process cost becomes high. If you want to reduce the process cost of the script, how about using Sheets API? When Sheets API is used, how about the following modification? Please enable Sheets API at Advanced Google services.
To
var temp = newData.slice();
var data = shtRecords.getDataRange().getValues().reduce((ar, r, i) => {
if (temp[0][0] == r[0]) {
var t = temp.splice(0, 1);
t[0][2] = Utilities.formatDate(t[0][2], Session.getScriptTimeZone(), "dd/MM/yyyy");
t[0][4] = Utilities.formatDate(t[0][4], Session.getScriptTimeZone(), "dd/MM/yyyy");
ar.push({ range: `'RECORD'!A${i + 1}`, values: t });
}
return ar;
}, []);
Sheets.Spreadsheets.Values.batchUpdate({ data, valueInputOption: "USER_ENTERED" }, ss.getId());

Import Google Sheet Data all at once to an existing sheet with data

Currently I have been using this script which imports data from a spreadsheet located in my google drive. The function works but imports the data one line at a time. Some times these sheets are 400+ rows and that takes a long time. I am looking for it to grab all data and import it into an existing spreadsheet and the end of the last value.
function getData() {
get_files = ['July1-2022'];
var ssa = SpreadsheetApp.getActiveSpreadsheet();
var copySheet = ssa.getSheetByName('CancelRawData');
for(z = 0; z < get_files.length; z++)
{
var files = DriveApp.getFilesByName(get_files[z]);
while (files.hasNext())
{
var file = files.next();
break;
}
var ss = SpreadsheetApp.open(file);
SpreadsheetApp.setActiveSpreadsheet(ss);
var sheets = SpreadsheetApp.getActiveSpreadsheet().getSheets();
for(var i = 0; i < sheets.length; i++)
{
var nameSheet = ss.getSheetByName(sheets[i].getName());
var nameRange = nameSheet.getDataRange();
var nameValues = nameRange.getValues();
for(var y = 1; y < nameValues.length; y++)
{
copySheet.appendRow(nameValues[y]);
}
}
}
SpreadsheetApp.getUi().alert("🎉 Congratulations, your data has been all imported", SpreadsheetApp.getUi().ButtonSet.OK);
}
I believe your goal is as follows.
You want to reduce the process cost of your script.
Modification points:
appendRow is used in a loop. The process cost will be high.
In your script, sheets can be written by var sheets = SpreadsheetApp.open(file).getSheets();.
When these points are reflected in your script, it becomes as follows.
Modified script 1:
This script uses the Spreadsheet service (SpreadsheetApp).
function getData() {
// Retrieve values.
get_files = ['July1-2022'];
var values = [];
for (z = 0; z < get_files.length; z++) {
var files = DriveApp.getFilesByName(get_files[z]);
while (files.hasNext()) {
var file = files.next();
break;
}
var sheets = SpreadsheetApp.open(file).getSheets();
for (var i = 0; i < sheets.length; i++) {
values = [...values, ...sheets[i].getDataRange().getValues()];
}
}
if (values.length == 0) return;
// Put values.
const maxLen = Math.max(...values.map(r => r.length));
values = values.map(r => [...r, ...Array(maxLen - r.length).fill(null)]);
var copySheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('CancelRawData');
copySheet.getRange(copySheet.getLastRow() + 1, 1, values.length, values[0].length).setValues(values);
SpreadsheetApp.getUi().alert("🎉 Congratulations, your data has been all imported", SpreadsheetApp.getUi().ButtonSet.OK);
}
Modified script 2:
This script uses Sheets API. Before you use this script, please enable Sheets API at Advanced Google services.
function getData() {
// Retrieve values.
get_files = ['July1-2022'];
var values = [];
for (z = 0; z < get_files.length; z++) {
var files = DriveApp.getFilesByName(get_files[z]);
while (files.hasNext()) {
var file = files.next();
break;
}
var spreadsheetId = file.getId();
var ranges = SpreadsheetApp.open(file).getSheets().map(s => `'${s.getSheetName()}'!${s.getDataRange().getA1Notation()}`);
values = [...values, ...Sheets.Spreadsheets.Values.batchGet(spreadsheetId, { ranges }).valueRanges.flatMap(({ values }) => values)];
}
if (values.length == 0) return;
// Put values.
var dstSS = SpreadsheetApp.getActiveSpreadsheet();
Sheets.Spreadsheets.Values.append({ values }, dstSS.getId(), 'CancelRawData', { valueInputOption: "USER_ENTERED" });
SpreadsheetApp.getUi().alert("🎉 Congratulations, your data has been all imported", SpreadsheetApp.getUi().ButtonSet.OK);
}
Note:
When these scripts are run, all files of get_files are retrieved and all values are retrieved from all sheets in all Spreadsheets, and the retrieved values are put to the destination sheet of CancelRawData.
References:
getValues()
setValues(values)
Method: spreadsheets.values.batchGet
Method: spreadsheets.values.append

How do I use Sheets.Spreadsheets.getByDataFilter from app script for a filter view that is created in spreadsheet?

I am seeking help with the following case
I have a spreadsheet, and it contains few filter views - f1, f2, ...
I have an app script associated with the spreadsheet. I have enabled Resources > Advanced Google Services to access the Sheets API v4.
Currently, I access that data as
var fruits = Sheets.Spreadsheets.Values.get("1YBPXShvssFpTI-5dPSsy_N_iEVaeHezdxymsdxpTy6w", "Fruits!A:B").values;
And I get the corresponding data back.
I would now, like to only get the data that is used by the filter view, so that I do not bring the entire data which is not necessary and slows down the processing.
I saw that there is something called Sheets.Spreadsheets.getByDataFilter(resource, spreadsheetId), but I am not sure how to create the resource object.
Given my filters, and knowing the spreadsheet Id, how do I only fetch the data based on the filter names that I know?
UPDATE
My latest attempt looks like
var ss = SpreadsheetApp.getActiveSpreadsheet();
function getUnpostedItems() {
Logger.log("This function will prioritize the new items that are added into the inventory");
var sheet = ss.getSheetByName("Items");
var filterSettings = {};
filterSettings.criteria = {};
var condition = {
"condition": {
"type": "LESS_THAN",
"values": [
{ "userEnteredValue": "=NOW()-30" }
]
}
}
filterSettings['criteria'][1] = {
'condition': condition
};
var filterSettings = {
range: {
sheetId: sheet.getSheetId(),
},
}
var req = {
"setBasicFilter": {
"filter": filterSettings
}
}
// var items = Sheets.Spreadsheets.batchUpdate({'requests': [req]}, ss.getId());
var items = ss.getRange("Items!A:B").getValues()
// var items1 = Sheets.Spreadsheets.Values.get("1YBPXShvssFpTI-5dPSsy_N_iEVaeHezdxymsdxpTy6c", "Items!A:B").values
Logger.log("Found items:" + items.length);
return [];
}
But no luck so far!
As per #tanaike's help, I was able to get the following working
function getUnpostedItems() {
Logger.log("This function will prioritize the new items that are added into the inventory");
// var ss = SpreadsheetApp.getActiveSpreadsheet(); // Added
var sheet = ss.getSheetByName("Items"); // Modified
var values = sheet.getDataRange().getValues();
Logger.log("VALUES "+values.length);
//var newCriteria = SpreadsheetApp.newFilterCriteria().whenDateBefore(new Date()).build();
var newCriteria = SpreadsheetApp.newFilterCriteria().whenDateBefore(subDaysFromDate(new Date(), 30)).build();
var range = sheet.getFilter().setColumnFilterCriteria(1, newCriteria).getRange(); //The 1-indexed position of the column.
// values = range.getValues();
// I added below script.
var res = Sheets.Spreadsheets.get(ss.getId(), {
ranges: ["Items"], // <--- Please set the sheet name.
fields: "sheets/data"
});
var values = res.sheets[0].data[0].rowMetadata.reduce(function(ar, e, i) {
if (!e.hiddenByFilter && res.sheets[0].data[0].rowData[i]) {
ar.push(
res.sheets[0].data[0].rowData[i].values.map(function(col) {
return col.userEnteredValue[Object.keys(col.userEnteredValue)[0]];
})
);
}
return ar;
}, []);
Logger.log("VALUES "+values.length);
Logger.log("VALUES "+values);
//Logger.log("Found Items:" + items.length);
return [];
}

Appending rows in a Loop - Google Spreadsheet

I have an app script which uses the YouTube API to get videos from YouTube for a channel. YouTube only returns 50 results at a time.
How do I append rows in the spreadsheet each time I get the next set of results
The essence of my question is, how does one update rows on the spreadsheet in a For Loop.
I tried. sheet.appendRows(object []) and that does not work.
Any ideas ?
My Script
function searchbySafety(safety) {
var first_response = YouTube.Search.list('id,snippet',{
channelId: 'UCpZG4Vl2tqg5cIfGMocI2Ag' ,
maxResults: 50,
safeSearch: safety
});
Create_headers();
for (var i = 0; i < first_response.items.length; i++) {
var item = first_response.items[i];
Logger.log('[%s] Title: %s', item.id.videoId, item.snippet.title);
gen_results(first_response, safety);
write_range(data);
}
var nextPageToken = first_response.nextPageToken;
while(nextPageToken != null) {
var videoresponse = YouTube.Search.list('id,snippet', {
channelId: 'UCpZG4Vl2tqg5cIfGMocI2Ag' ,
maxResults:50,
pageToken: nextPageToken,
safeSearch: safety
});
for (var i = 0; i < videoresponse.items.length; i++) {
var item = videoresponse.items[i];
Logger.log('[%s] Title: %s', item.id.videoId, item.snippet.title);
//gen_results(videoresponse, safety);
//write_rest_data(data);
}
nextPageToken = videoresponse.nextPageToken;
}
}
function gen_results(response, safety){
for (var i = 0; i < response.items.length; i ++)
{
var item = response.items[i];
row = [item.snippet.title, "https://www.youtube.com/watch?v=" + item.id.videoId, safety];
data[i] = row;
}
return data;
}
function write_range(data){
var range = report_sheet.getRange(2,1,50,3);
range.setValues(data);
}
function Create_headers(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheets = ss.getSheets();
var sheet = sheets[3];
Logger.log(sheet.getName());
var headers = [
["Title","URL","Safety"]
];
var range = sheet.getRange("A1:C1");
range.setValues(headers);
report_sheet = sheet;
}
Since you are using a separate function to write data array to the sheet this is very simple and straightforward :
Instead of using a hard coded start row value use the result of the getLastRow() method, see documentation here.
If we use getLastRow()+1 the next block of data will be written right below the last existing row.
Btw, I changed also the height and width definitions in your function to make it more "universal" : it takes the dimensions from the array dimensions so that the match is always perfect even if, for some reason, you change the array size.
code :
function write_range(data){
var lastRow = report_sheet.getLastRow()+1;
var range = report_sheet.getRange(lastRow,1,data.length,data[0].length);
range.setValues(data);
}