How do I search Google Spreadsheets? - search
I am doing a few exhaustive searches and need to determine if a new domain (URL) is already in a Spreadsheet. However, none of the Spreadsheet objects have search functions, namely findText() found in most Document objects. I feel like I am missing something significant.
What am I missing?
findText function: https://developers.google.com/apps-script/class_table#findText
SearchResult object: https://developers.google.com/apps-script/class_searchresult
Spreadsheet object: https://developers.google.com/apps-script/class_sheet
My best guess is to try and convert specific Spreadsheet ranges in Document tables, then perform the search. Mendokusai
Unfortunately there is no searching functionality in the Spreadsheet services. You can get the data for the range you are searching on, and then iterate over it looking for a match. Here's a simple function that does that:
/**
* Finds a value within a given range.
* #param value The value to find.
* #param range The range to search in.
* #return A range pointing to the first cell containing the value,
* or null if not found.
*/
function find(value, range) {
var data = range.getValues();
for (var i = 0; i < data.length; i++) {
for (var j = 0; j < data[i].length; j++) {
if (data[i][j] == value) {
return range.getCell(i + 1, j + 1);
}
}
}
return null;
}
I wrote a search tool with a graphical user interface that performs a global search in 3 columns of a single sheet. It could be easily modified to suit your needs. I guess it would be a good idea to add an anchor in the UI to let you open the url you just found.
Here is the code, hoping it will help you to design your own version.
EDIT : I added the anchor widget in the code below (getting its ref in column E)
// G. Variables
var sh = SpreadsheetApp.getActiveSheet();
var ss = SpreadsheetApp.getActiveSpreadsheet();
var lastrow = ss.getLastRow();
//
function onOpen() {
var menuEntries = [ {name: "Search GUI", functionName: "searchUI"},
];
ss.addMenu("Search Utilities",menuEntries);// custom menu
}
// Build a simple UI to enter search item and show results + activate result's row
function searchUI() {
var app = UiApp.createApplication().setHeight(130).setWidth(400);
app.setTitle("Search by name / lastname / adress");
var panel = app.createVerticalPanel();
var txtBox = app.createTextBox().setFocus(true);
var label=app.createLabel(" Item to search for :")
panel.add(label);
txtBox.setId("item").setName("item");
var label0=app.createLabel("Row").setWidth("40");
var label1=app.createLabel("Name").setWidth("120");
var label2=app.createLabel("Lastname").setWidth("120");
var label3=app.createLabel("Street").setWidth("120");
var hpanel = app.createHorizontalPanel();
hpanel.add(label0).add(label1).add(label2).add(label3)
//
var txt0=app.createTextBox().setId("lab0").setName("0").setWidth("40");
var txt1=app.createTextBox().setId("lab1").setName("txt1").setWidth("120");
var txt2=app.createTextBox().setId("lab2").setName("txt2").setWidth("120");
var txt3=app.createTextBox().setId("lab3").setName("txt3").setWidth("120");
var hpanel2 = app.createHorizontalPanel();
hpanel2.add(txt0).add(txt1).add(txt2).add(txt3)
var hidden = app.createHidden().setName("hidden").setId("hidden");
var subbtn = app.createButton("next ?").setId("next").setWidth("250");
var link = app.createAnchor('', '').setId('link');
panel.add(txtBox);
panel.add(subbtn);
panel.add(hidden);
panel.add(hpanel);
panel.add(hpanel2);
panel.add(link);
var keyHandler = app.createServerHandler("click");
txtBox.addKeyUpHandler(keyHandler)
keyHandler.addCallbackElement(panel);
//
var submitHandler = app.createServerHandler("next");
subbtn.addClickHandler(submitHandler);
submitHandler.addCallbackElement(panel);
//
app.add(panel);
ss.show(app);
}
//
function click(e){
var row=ss.getActiveRange().getRowIndex();
var app = UiApp.getActiveApplication();
var txtBox = app.getElementById("item");
var subbtn = app.getElementById("next").setText("next ?")
var txt0=app.getElementById("lab0").setText('--');
var txt1=app.getElementById("lab1").setText('no match').setStyleAttribute("background", "white");// default value to start with
var txt2=app.getElementById("lab2").setText('');
var txt3=app.getElementById("lab3").setText('');
var link=app.getElementById('link').setText('').setHref('')
var item=e.parameter.item.toLowerCase(); // item to search for
var hidden=app.getElementById("hidden")
var data = sh.getRange(2,2,lastrow,4).getValues();// get the 4 columns of data
for(nn=0;nn<data.length;++nn){ ;// iterate trough
if(data[nn].toString().toLowerCase().match(item.toString())==item.toString()&&item!=''){;// if a match is found in one of the 4 fields, break the loop and show results
txt0.setText(nn+2);
txt1.setText(data[nn][0]).setStyleAttribute("background", "cyan");
txt2.setText(data[nn][1]);
txt3.setText(data[nn][2]);
link.setText(data[nn][3]).setHref(data[nn][3]);
sh.getRange(nn+2,2).activate();
subbtn.setText("found '"+item+"' in row "+Number(nn+2)+", next ?");
hidden.setValue(nn.toString())
break
}
}
return app ;// update UI
}
function next(e){
var row=ss.getActiveRange().getRowIndex();
var app = UiApp.getActiveApplication();
var txtBox = app.getElementById("item");
var subbtn = app.getElementById("next").setText("no other match")
var hidden=app.getElementById("hidden");
var start=Number(e.parameter.hidden)+1;//returns the last search index stored in the UI
var item=e.parameter.item.toLowerCase(); // item to search for
var txt0=app.getElementById("lab0");
var txt1=app.getElementById("lab1").setStyleAttribute("background", "yellow");
var txt2=app.getElementById("lab2");
var txt3=app.getElementById("lab3");
var link=app.getElementById('link').setText('').setHref('')
var data = sh.getRange(2,2,lastrow,4).getValues();// get the 4 columns of data
for(nn=start;nn<data.length;++nn){ ;// iterate trough
if(data[nn].toString().toLowerCase().match(item.toString())==item.toString()&&item!=''){;// if a match is found in one of the 4 fields, break the loop and show results
txt0.setText(nn+2);
txt1.setText(data[nn][0]).setStyleAttribute("background", "cyan");
txt2.setText(data[nn][1]);
txt3.setText(data[nn][2]);
link.setText(data[nn][3]).setHref(data[nn][3])
sh.getRange(nn+2,2).activate();
subbtn.setText("found '"+item+"' in row "+Number(nn+2)+", next ?");
hidden.setValue(nn.toString())
break
}
}
return app ;// update UI
}// eof 05-12 Serge insas
I ended up using spreadsheet formulas to solve my problem instead. Specifically, I used the MATCH() function, which can look up a string in an array (in this case a column in another sheet in the same document).
This is significantly simpler than looping through an array, though less efficient and does not allow for full automation. In fact, when the column reached 2,000 entries, Google Drive froze so often, I had to start using Excel instead. Nevertheless, the Match() solution was more appropriate for what I was looking for.
Appreciate all the other responses though.
You can "search" using the SpreadsheetAPI List Feed query parameter. This will return any row that matches using full word matching. Throw some asterisks around your parameter (URL encoded of course) and it becomes wildcard.
I have not tried it, but it appears that the Google Visualization API Query Language will allow you to execute SQL queries against Google sheets.
Related
Copy Excel cell value and add rows to another table
In a table (in excel) in a column I have some number(A). I want the flow to take that number (A) and to create number of rows equels to Number (A) For example if number(A) is 4, then in another table to be added 4 rows
I've made an assumption on the source and destination tables. This concept can be adjusted and applied to suit your own scenario. I'd be using Office Scripts to do this. If you've never used it then feel free to consult the Microsoft documentation to get you going ... https://learn.microsoft.com/en-us/office/dev/scripts/tutorials/excel-tutorial This is the script you need to create (change the name of your tables accordingly) ... function main(workbook: ExcelScript.Workbook) { var addRowsTable = workbook.getTable('TableRowsToAdd'); var addRowsToTable = workbook.getTable('TableAddRowsToTable'); var addRowsTableDataRange = addRowsTable.getRangeBetweenHeaderAndTotal(); var addRowsTableDataRangeValues = addRowsTableDataRange.getValues(); // Sum the values so we can determine how many more rows need to be added // to the destination table. var sumOfAllRowsToBeInExistence = 0; for (var i = 0; i < addRowsTableDataRangeValues.length; i++) { if (!isNaN(addRowsTableDataRangeValues[i][0])) { sumOfAllRowsToBeInExistence += Number(addRowsTableDataRangeValues[i][0]); } } var currentRowCount = addRowsToTable.getRangeBetweenHeaderAndTotal().getRowCount(); var rowsToAdd = sumOfAllRowsToBeInExistence - currentRowCount; console.log(`Current row count = ${currentRowCount}`); console.log(`Rows to add = ${rowsToAdd}`); if (rowsToAdd > 0) { /* The approach below is contentious given the performance impact but this approach ... for (var i = 1; i <= rowsToAdd; i++) { ... didn't always yield the correct result. May be a bug but needs investigation. Ultimately, there are a few ways to achieve the same result, like using the resize method. This was the easiest option for a StackOverflow answer. */ while (addRowsToTable.getRangeBetweenHeaderAndTotal().getRowCount() < sumOfAllRowsToBeInExistence) { addRowsToTable.addRows(); } } } You can then call that from PowerAutomate using the Run script action under Excel Online (Business) ... You can use that approach or all of the actions that are available in PowerAutomate which will achieve the same sort of thing. IMO, Using Office Scripts is much easier. Creating a large flow can be a real pain in the backside to deal with given there'll be a whole heap of actions that you'll need to throw in to reach the same outcome.
I would pass the number of rows to add in an office scripts script as a parameter. Once you have the value, create a JSON string of a 2d array. You want to create a loop using the number of rows to add. In the loop you continue to concatenate the 2d array. Once you've exited the loop, parse the JSON string and add the 2d array to the table. You can see how you code might look below: function main(workbook: ExcelScript.Workbook, rowsToAdd: number) { //set table name let tbl = workbook.getTable("table2") //initialize json string with open bracket let jsonArrString = "[" //set the temp json string with a 2d array let tempJsonArr = '["",""],' //concatenate json string equal to the number of rows to add for (let i = 0; i < rowsToAdd; i++){ jsonArrString += tempJsonArr } //remove extra comma from JSON string jsonArrString = jsonArrString.slice(0, jsonArrString.length-1) //add closing bracket to JSON string jsonArrString += "]" //parse json string into array let jsonArr: string[][] = JSON.parse(jsonArrString) //add array to table to add the number of rows tbl.addRows(null,jsonArr) }
Update a row in google sheets based on duplicate
I'm designing a script that takes an object (jsonData[data]) and inputs its values into a different sheet based on which product it is. Currently the script inputs all the data into a new row each time the form reaches a new stage, however the form goes through 4 stages of approval and so I'm finding each submission being entered into 4 different rows. Each submission has an "Id" value within the object which remains the same (but each submission could also be on any row in the sheet as it's used a lot). I'm checking whether the ID exists in the sheet and using iteration to find the row number: function updatePlatformBulkInfo(jsonData) { var sheetUrl = "https://docs.google.com/spreadsheets/d/13U9r9Lu2Fq1WTT8pQ128heCm6_gMmH1R4O6u8e7kvBo/edit#gid=0"; var sheetName = "PlatformBulkSetup"; var doc = SpreadsheetApp.openByUrl(sheetUrl); var sheet = doc.getSheetByName(sheetName); var rowList = []; var formId = jsonData["Id"]; var allSheetData = sheet.getDataRange().getValues(); setLog("AllSheetData = " + allSheetData[1][11]) //Logs to ensure data is collected correctly var rowEdited = false; var rowNumber = 0; //Check whether ID exists in the sheet for (var i = 0; i < allSheetData.length; i++) { if(allSheetData[i][11] == formId) { rowEdited = true; } else { rowNumber += 1; } } My issue is with the next part: //Append row if ID isn't duplicate or update row if duplicate found if (rowEdited == false) { for (var data in jsonData) { rowList.push(jsonData[data]) } setLog("***Row List = " + rowList + " ***"); setLog("***Current Row Number = " + rowNumber + " ***"); sheet.appendRow(rowList); } else if(rowEdited == true){ var newRowValue = jsonData[data]; sheet.getRange(rowNumber, 1).setValues(newRowValue); } Everything works fine if the duplicate isn't found (the objects values are appended to the sheet). But if a duplicate is found I'm getting the error: Cannot find method setValues(string) This looks to me like i'm passing a string instead of an object, but as far as I'm aware I've already converted the JSON string into an object: var jsonString = e.postData.getDataAsString(); var jsonData = JSON.parse(jsonString); How can I modify my script to write the updated data to the matched row?
It's unclear based on your code whether or not you will actually write to the correct cell in the case of a duplicate. As presented, it looks as though you loop over the sheet data, incrementing a row number if the duplicate is not found. Then, after completing the loop, you write to the sheet, in the row described by rowNumber, even though your code as written changes rowNumber after finding a duplicate. To address this, your loop needs to exit upon finding a duplicate: var duplicateRow = null, checkedCol = /* your column to check */; for(var r = 0, rows = allSheetData.length; r < rows; ++r) { if(allSheetData[r][checkedCol] === formId) { // Convert from 0-base Javascript index to 1-base Range index. duplicateRow = ++r; // Stop iterating through allSheetData, since we found the row. break; } } In both cases (append vs modify), you seem to want the same output. Rather than write the code to build the output twice, do it outside the loop. Note that the order of enumeration specified by the for ... in ... pattern is not dependable, so if you need the elements to appear in a certain order in the output, you should explicitly place them in their desired order. If a duplicate ID situation is supposed to write different data in different cells, then the following two snippets will need to be adapted to suit. The general idea and instructions still apply. var dataToWrite = []; /* add items to `dataToWrite`, making an Object[] */ Then, to determine whether to append or modify, test if duplicateRow is null: if(dataToWrite.length) { if(duplicateRow === null) { sheet.appendRow(dataToWrite); } else { // Overwriting a row. Select as many columns as we have data to write. var toEdit = sheet.getRange(duplicateRow, 1, 1, dataToWrite.length); // Because setValues requires an Object[][], wrap `dataToWrite` in an array. // This creates a 1 row x N column array. If the range to overwrite was not a // single row, a different approach would be needed. toEdit.setValues( [dataToWrite] ); } }
Below is the most basic solution. At the end of this post, I'll expand on how this can be improved. I don't know how your data is organized, how exactly you generate new unique ids for your records, etc., but let's assume it looks something like this. Suppose we need to update the existing record with new data. I assume your JSON contains key-value pairs for each field: var chris = { id:2, name: "Chris", age: 29, city: "Amsterdam" }; Updating a record breaks down into several steps: 1) Creating a row array from your object. Note that the setValues() method accepts a 2D array as an argument, while the appendRow() method of the Sheet class accepts a single-dimension array. 2) Finding the matching id in your table if it exists. The 'for' loop is not very well-suited for this idea unless you put 'break' after the matching id value is found. Otherwise, it will loop over the entire array of values, which is redundant. Similarly, there's no need to retrieve the entire data range as the only thing you need is the "id" column. IMPORTANT: to get the row number, you must increment the array index of the matching value by 1 as array indices start from 0. Also, if your spreadsheet contains 1 or more header rows (mine does), you must also factor in the offset and increment the value by the number of headers. 3) Based on the matching row number, build the range object for that row and update values. If no matching row is found, call appendRow() method of the Sheet class. function updateRecord(query) { rowData = []; var keys = Object.keys(query); keys.forEach(function(key){ rowData.push(query[key]); }) var ss = SpreadsheetApp.getActive(); var sheet = ss.getSheets()[0]; var headers = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0]; var idColumn = 1; var ids = sheet.getRange(2, idColumn, sheet.getLastRow() - 1, 1).getValues(); var i = 0; var matchedRow; do { if (ids[i] == query.id) { matchedRow = i + 2; } i++; } while (!matchedRow && i < ids.length); if (matchedRow) { var row = sheet.getRange(matchedRow, idColumn, 1, rowData.length); row.setValues([rowData]); } else { sheet.appendRow(rowData); } } NOTE: if your query contains only some fields that need to be updated (say, the 'id' and the 'name' field), the corresponding columns for these fields will be headers.indexOf(query[key]) + 1; Possible improvements If the goal is to use the spreadsheet as a database and define all CRUD (Create, Read, Write, Delete) operations. While the exact steps are beyond the scope of the answer, here's the gist of it. 1) Deploy and publish the spreadsheet-bound script as a web app, with the access set to "anyone, even anonymous". function doGet(e) { handleResponse(e); } function doPost(e) { handleRespone(e); } function handleResponse(e) { if (e.contentLength == -1) { //handle GET request } else { //handle POST request } } 2) Define the structure of your queries. For example, getting the list of values and finding a value by id can be done via GET requests and passing parameters in the url. Queries that add, remove, or modify data can be sent as payload via POST request. GAS doesn't support other methods besides GET and POST, but you can simulate this by including relevant methods in the body of your query and then selecting corresponding actions inside handleResponse() function. 3) Make requests to the spreadsheet URL via UrlFetchApp. More details on web apps https://developers.google.com/apps-script/guides/web
How to count an amount of comments or specific words written by Accounts in Google sheet
I am trying to count comments or specific words written by users in Google Sheet table. for example 5 accounts have permission on sheet and i want to count how many comments or yes/no is written in table by each user. is there any solution for this task?
This will get the yes/no count case insensitive and it also has two arrays (yays and nays) which contain the row and column of each yes/no response. Also gets the Users email address. function getYesAndNo() { var ss=SpreadsheetApp.getActiveSpreadsheet(); var sht=ss.getActiveSheet(); var rng=sht.getDataRange(); var rngA=rng.getValues(); var yays=[]; var nays=[]; var yay=/yes/i; var nay=/no/i; var user = Session.getActiveUser().getEmail(); for(var i=0;i<rngA.length;i++) { for(var j=0;j<rngA[i].length;j++) { if(String(rngA[i][j]).match(yay)) { yays.push([Number(i+1),Number(j+1)]); //rows and columns not indexes } if(String(rngA[i][j]).match(nay)) { nays.push([Number(i+1),Number(j+1)]); //rows and columns not indexes } } } Logger.log(yays); Logger.log(nays); SpreadsheetApp.getUi().alert('User Email = ' + user + ' Yes Count = ' + yays.length + ' No Count = ' + nays.length); }
Get entries count for a multiple category view (two categories) using a key (Xpages)
I am trying to retrieve the entries count for a multiple category view (two categories) using a key. var db:NotesDatabase = session.getDatabase(sessionScope.serverPath,sessionScope.dbName); var luview = db.getView(sessionScope.viewName); var vec:NotesViewEntryCollection = null; if (key != null) { vec = luview.getAllEntriesByKey(key); } count = vec.getCount().toFixed() The count being returned is incorrect. I have over 500 documents in the view. It seems to be returning just the document count (20) of the first sub-category. I've found mention of this as a bug in the forums. I'm running this on a 9.0 server. Any pointers would be much appreciated. What I would like is the total count - categories (25) + documents (500), that I can use in the repeat control limit. Thanks, Dan
I was able to resolve this by using the NotesViewNavigator. var nav:NotesViewNavigator = v.createViewNavFromCategory(key); var entry:NotesViewEntry = nav.getFirst(); while (entry != null) { count = count + 1; var tmpentry:NotesViewEntry = nav.getNext(entry); entry.recycle(); entry = tmpentry; }
Dan - If you can get the entry count into the view column with an #AllChildren... or #AllDecendents or something like that then using the view navigator you should be able to read that value in and not have to actually loop through all the documents. Another way is to create a different view, could be hidden, and not categorize that second column. Then you're original solution should work I'd think.
How do I search for a string and return the string's neighbor word from a text in google apps script?
I'm getting some spesific emails from gmail to a spreadsheet. But before I send them to spreadsheet. I want to get some spesific data from email plain text. For example, there is a word "arrival" in the text and the date next to it. I want to get this date. This is what I have so far: function abepostalar() { var threads = GmailApp.search('label:AOOA'); Logger.log(threads); Logger.log(threads[0].getMessages()); Logger.log(threads[0].getMessages().length); var messages;// = threads[0].getMessages(); var ContentEmail;// = messages[0].getBody(); for (var i = 0; i < threads.length; i++) { messages = threads[i].getMessages() ContentEmail = messages[0].getPlainBody(); // These are what I tried to find the word. not working. //var bilgi = ContentEmail.findText("Uygar"); //var yazi = ContentEmail.asText().getText(); //Logger.log(bilgi); //MesajlariDosyayaYaz(date); // a function to send date to the spreadsheet } } I tried some other functions here and here couldn't nail it.
The value you get from messages[0].getPlainBody() is a string and it appears that you are trying document service methods which of course won't work. Try using javascript strings methods instead.