Why is my Sorting script works inconsistently? - javascript

Hi helpful contributors,
I have a response google sheet that has a set of datarange with different type of data values (screenshot) as below. Col2 Timestamp values are auto-generated upon user input in gform. I moved it as col2 (default is in col1) because I want to put the Quote ID as primary key col. Quote ID values are auto-generated based on google script that is triggered upon submission of gform:-
gresponse sheet with inconsistent sort
I supposed we would have the data range autosort by timestamp (and I need it significantly to sort by timestamp) but this is not happening consistently with mine. I already tried the below codes but still not working consistently too. But when I sort it manually in the sheet using the feature Filter to choose Timestamp column and then sort A->Z, it works fine. But that won't be effective for my requirements
Method 1 :
// this resulted as in the screenshot.
SortRange = "A2:AC";
SortOrder = [{column:2, ascending: true}];
function multiSortColumns(){
var range = msheet.getRange(SortRange);
range.sort(SortOrder);
}
Method 2 :
// some sorted, some not but still wrong and not ascending order.
SortRange = 2,1,sheet.getLastRow() - 1, sheet.getLastColumn();
SortOrder = [{column:2, ascending: true}];
function multiSortColumns(){
var range = sheet.getRange(SortRange);
range.sort(SortOrder);
}
Method 3
var sheet=SpreadsheetApp.getActiveSheet().sort(2);
// this doesn't sort by specific column we want and it includes sorting the header.
Please help to correct my code. Tqvm in advance.

Potential Issues:
The first two methods in your question don't seem to define the
sheet object.
Also I am not even sure if you execute
multiSortColumns at all.
The third method will also sort the header as well.
Another potential issue would be when using:
const sheet = ss.getActiveSheet();
The latter considers the active sheet which can be any of the selected sheets in the spreadsheet file.
Instead I would advice you to use:
const sheet = ss.getSheetByName("Form Responses 1");
assuming the name of the sheet you want to sort is Form Responses 1
Solution:
Here is a modified version of your second method which makes sure that everything is executed when you execute myFunction():
function myFunction() {
const ss = SpreadsheetApp.getActive();
// const sheet = ss.getActiveSheet(); // not recommended
const sheet = ss.getSheetByName("Form Responses 1"); // recommended
sheet.getRange(2, 1, sheet.getLastRow() -1, sheet.getLastColumn()).sort({column: 2, ascending: true});
}

Related

Google Sheets Apps Script Copy Row of Data If date same as tab name

I found some code that almost does what i need and have tried playing around with it to get it to work, but no luck. I get an export with data with dates in the last column on every row.
I simply want to copy the last column rows of dates to the tabs with the same name.
function MoveDate_FourthDEC() {
var ss=SpreadsheetApp.getActive();
var sh1=ss.getSheetByName("Import");
var sh2=ss.getSheetByName("4/12/2020");
var rg1=sh1.getRange(2,1,sh1.getLastRow(),32);//starting at column2
var data=rg1.getValues();
for(var i=0;i<data.length;i++) {
// 13 = collected should be in this column which is column N
if(data[i][31]=="4/12/2020") {
sh2.appendRow(data[i]);
}}}
Explanation:
Your goal is to copy all the rows for which column AF matches the names of the sheets.
To begin with, you can use forEach() to iterate over every sheet. For each sheet, you want to check whether the sheet name matches a date in column AF. If it does, then you need to filter only the rows that contain this date in column AF and store them into an temporary array:
let temp_data = data.filter(r=>r[31]==sh.getName());
Then you can efficiently copy and paste all the relevant rows to the matching sheet:
sh.getRange(sh.getLastRow()+1,1,temp_data.length,temp_data[0].length).setValues(temp_data);
Side notes:
When dealing with date objects you need to consider the display values in the sheet. This is why I am using getDisplayValues instead of getValues.
Since data starts from the second row, you need to deduct one row from the last row with content, to get the correct range:
getRange(2,1,sh1.getLastRow()-1,32)
I am using includes to check if the sheet name matches the last column. In order to use includes you need to flatten the 2D array that is returned by the getDisplayValues function.
Solution:
function MoveDate_FourthDEC() {
const ss = SpreadsheetApp.getActive();
const sh1 = ss.getSheetByName("Import");
const shs = ss.getSheets();
const dts = sh1.getRange('AF2:AF'+sh1.getLastRow()).getDisplayValues().flat();
const data=sh1.getRange(2,1,sh1.getLastRow()-1,32).getDisplayValues();
shs.forEach(sh=>{
if (dts.includes(sh.getName())){
let temp_data = data.filter(r=>r[31]==sh.getName());
sh.getRange(sh.getLastRow()+1,1,temp_data.length,temp_data[0].length).setValues(temp_data);
}});
}

Auto-expanding formula and then copy paste values

I have a function that adds an auto-expanding formula to some Header row cell
In the next line of code, I get the Display Values and then post them back to the sheet
I am concerned that I will be getting the values in the range of the auto-expanding formulas before they have finished expanding
Will this r.getDisplayValues(); get the values before the auto-expansion has finished? thereby getting values with blank data that should have data
I have tested various scenarios, but this is not definitive
Also, I have not been able to find anything in searching on this
Thank you
function setFormulasAE_n() {
var ss =SpreadsheetApp.getActive();
var sheet =ss.getSheetByName('Elements');
var LC = sheet.getLastColumn();
var LR = sheet.getLastRow();
//Auto-expanding Formulas to be added
//Two dim array with 1 row
var formulas = [[
"=ArrayFormula({\"ig_TagsHistorical\";iferror(vlookup(INDIRECT(\"Elements!A2:A\"&counta(Elements!$AJ$1:$AJ)), \'HelperElements_(ignore)\'!$A$2:$G, {5}, 0))})",
"=ArrayFormula({\"Additional Networks\";iferror(vlookup(INDIRECT(\"Elements!A2:A\"&counta(Elements!$AJ$1:$AJ)), \'Helper_(ignore)\'!$A$2:$D, {4}, 0))})",
]];
//Add auto-expanding formulas to Cell(s)
var cell = sheet.getRange(1,LC+1,1,formulas[0].length);
cell.setFormulas(formulas);
//Get range and post back Display Values
var r = sheet.getRange(1,LC+1,LR,formulas[0].length);
var v = r.getDisplayValues();
r.setValues(v)
}
The setFormulas() function you have used in the script is synchronous which essentially means that the code following the instruction won't be executed until the functions finishes the execution.
Therefore, r.getDisplayValues() will always get the values after the auto-expansion.
What you could do to make sure that the values you get are the expected ones is to use the flush() function after the cell.setFormulas(formulas) line of code. What flush() does is basically applying all the pending Spreadsheet changes - more specifically the formulas you need to set.
Furthermore, you can check these links since they might be of help to you:
SpreadsheetApp Class - flush();
Range Class - setFormulas().

How to use .map() method to place values from one page to anther using data as coordinates for the next

I am trying to take a data range of X and Y values and place a third column's value into another spreadsheet using the X and Y values as a cell range. How can use .map() method to utilize the array for a series of tasks? Is my only real option to use a "for loop" which can be very slow to go through many rows of data?
I have tried and successfully utilized the "for loop" function to go one by one through one sheet find the values in each column and plot the third column of values in another sheet. However, the larger that list gets the slower the process
function testMap() {
//Open Active Spreadsheet App
var ss = SpreadsheetApp.getActive();
//Capture sheets by name
var sheetCoord = ss.getSheetByName('Coordinates');
var sheetPlots = ss.getSheetByName('Plots');
//Create last row and column variables
var lR = sheetCoord.getLastRow();
var lC = sheetCoord.getLastColumn();
lR = lR-1;
//Create array of all data
var data = sheetCoord.getRange(2, 1, lR, lC).getValues();
//Create variables for placement into proper sheet
var x = data.map(function(f){ return f[0] });
var y = data.map(function(f){ return f[1] });
var v = data.map(function(f){ return f[2] });
sheetPlots.getRange(y, x, lR, lC).setValues(v);
}
I expect this to map the values in the range than incrementally take each value of each range and plot the "v" cell value in the coordinates on the Plots sheet. It just says
"Cannot convert 6,2,1 to (class). (line 23, file "Code")".
You want to put values to the cells using the coordinates retrieved from the Spreadsheet.
Column "A", "B" and "C" of the sheet of Coordinates are the column number, the row number and the value, respectively.
If my understanding is correct, how about this answer?
Issues:
The issue of your script is to use row and column of getRange(row, column, numRows, numColumns) as the array. The official document of getRange(row, column, numRows, numColumns) says as follows.
row: Integer
The starting row index of the range; row indexing starts with 1.
column: Integer
The starting column index of the range; column indexing starts with 1.
numRows: Integer
The number of rows to return.
numColumns: Integer
The number of columns to return.
In your case, it seems that the values are required to be put to the cell of each coordinate. By this, when the data is large, the process cost will become high.
Unfortunately, when setValues of Spreadsheet service is used, values are put to the continuous coordinates. This cannot be used for the situation that the values are put to the discrete coordinates.
For example, if your goal can use the situation that the cells are overwritten by the values including the empty values, setValues() can be used.
Solution:
In order to resolve above issues, I would like to propose to use the method of batchUpdate of Sheets API. When Sheets API is used, the values can be put to the cells of the discrete coordinates by one API call. And from my experiment, for putting values, when the data is large, Sheets API is faster than Spreadsheet service. From this situation, I proposed to use Sheets API.
Modified script:
Before you use this script, please enable Sheets API at Advanced Google Services.
function testMap() {
var ss = SpreadsheetApp.getActive();
var sheetCoord = ss.getSheetByName('Coordinates');
var sheetPlots = ss.getSheetByName('Plots');
var lR = sheetCoord.getLastRow();
var lC = sheetCoord.getLastColumn();
lR = lR-1;
var data = sheetCoord.getRange(2, 1, lR, lC).getValues();
// I modified below script.
var sheetId = sheetPlots.getSheetId();
var requests = data.map(function(e) {
var obj = {};
if (typeof e[2] == "string") obj.stringValue = e[2];
if (typeof e[2] == "number") obj.numberValue = e[2];
return {updateCells: {
range: {sheetId: sheetId, startRowIndex: e[1] - 1, endRowIndex: e[1], startColumnIndex: e[0] - 1, endColumnIndex: e[0]},
rows: [{values: [{userEnteredValue: obj}]}],
fields: "userEnteredValue"
}};
});
Sheets.Spreadsheets.batchUpdate({requests: requests}, ss.getId());
}
References:
getRange(row, column, numRows, numColumns)
Benchmark: Reading and Writing Spreadsheet using Google Apps Script
Advanced Google services
Method: spreadsheets.batchUpdate
UpdateCellsRequest
If I misunderstood your question and this was not the result you want, I apologize. At that time, can you provide a sample Spreadsheet? By this, I would like to confirm your situation.
Regarding the error
y and x are Arrays but the getRange method that uses four arguments require that each one of them are numbers, more specifically, integers. In other words, your code is passing the wrong data type for the first two arguments Ref. https://developers.google.com/apps-script/reference/spreadsheet/sheet#getrangerow-column-numrows-numcolumns
It's worth to note that v also is an Array (1D Array) but setValues requires a 2D Array.
Regarding the use of Array.protoype.map
In a broad sense you are using it correctly. Ref. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map, but not for the result that you are looking to get.
There are several ways to achieve that result
Use "brute force" to set the cell values of each coordinate one at a time, perhaps by using a for statement, Array.prototype.forEach or other similar methods.
Use "pseudo brute force", use Class RangeList
Set all the values at once by creating a 2D Array and set all the values at once by using setValues

Sorting raw data from one sheet into separate tabs on a new sheet in Google App Script

I have a large data file that I want to be separated/filtered out into separate tabs on a new sheet. They need to be filtered by a certain column containing "BGT" within the string.
I am very new to using Google Apps Script so this is a work in progress. I am able to pull the data from one sheet into another, convert it to an array and assign the column I want to sort by. I just can't properly set up the filter.
function CreativeReport() {
  var ss = SpreadsheetApp.openByUrl(SPREADSHEET_URL);
  var sheet = ss.getSheetByName(SHEET_NAME);
var folder = getDriveFolderFromPath("Daily Creative Audit");
var fileId = folder.getFiles().next().getId();
var RawMM = SpreadsheetApp.openById(fileId);
var ArrayRawData = RawMM.getRange(2,1,RawMM.getLastRow()-1,5).getValues();
var ConceptName = RawMM.getRange('A:A').getValues();
var BGTFilter = ConceptName.includes("BGT");
var BGTFilteredData = ArrayRawData.filter(function(item){ return item [6] === BGTFilter;});
Logger.log(BGTFilteredData);
The column I want to sort buy contains cells formatted like this
2019_BGT_Bigsale_300x50
2019_SWT_Bigsale_300x50
2019_AI_Bigsale_300x50
2019_SWO_Bigsale_300x50
2019_DCO_Bigsale_300x50.
The goal is to have the script filter this raw data, and sort it into separate sheets depending on the cells containing:
BGT
SWO
SWT
AI
DCO
The code I how SHOULD log out just the concepts containing "BGT" in the string but I get
TypeError: Cannot find function includes in object ,concept_name,
You have values at "Sheet1" in the source Spreadsheet.
You want to separate the values by checking whether the column "A" has the values of BGT, SWO, SWT, AI, DCO.
You want to put the separated values to the sheet names of BGT, SWO, SWT, AI, DCO in the destination Spreadsheet.
I could understand like above. If my understanding is correct, how about this modification? Unfortunately, the modified script was largely changed from your original script. I apologize for this. I think that there are several answers for your situation. So please think of this as just one of them.
The flow of the modified script is as follows.
Retrieve values from the source sheet.
Create an object for retrieving values for each destination sheet.
The object is like {"BGT": [values], "SWO": [values],,,}.
Put values for the destination sheets.
Modified script:
Before you run the script, please set srcSpreadsheetId, dstSpreadsheetId and srcSheetName.
function CreativeReport() {
// Variables: Please set them for your situation.
var srcSpreadsheetId = "###"; // Please set the source Spreadsheet ID. In your sample, the Spreadsheet name is "Sample Raw Data".
var dstSpreadsheetId = "###"; // Please set the destination Spreadsheet ID. In your sample, the Spreadsheet name is "Sorted Data".
var srcSheetName = "Sheet1"; // Please modify this. In this sample, it's the same with your sample Spreadsheet.
var dstSheetNames = ["BGT", "SWO", "DCO", "AI", "SWT"];
// Open Spreadsheets
var srcSS = SpreadsheetApp.openById(srcSpreadsheetId);
var dstSS = SpreadsheetApp.openById(dstSpreadsheetId);
// Retrieve values from the source sheet.
var srcSheet = srcSS.getSheetByName(srcSheetName);
var srcValues = srcSheet.getRange(2, 1, srcSheet.getLastRow() - 1, 5).getValues();
// Create an object for retrieving values for each destination sheet.
var object = srcValues.reduce(function(obj, e) {
dstSheetNames.forEach(function(f) {
if (e[0].indexOf(f) > -1) obj[f] = obj[f] ? obj[f].concat([e]) : [e];
});
return obj;
}, {});
// Put values for the destination sheets.
dstSheetNames.forEach(function(e) {
var sheet = dstSS.getSheetByName(e);
sheet.getRange(sheet.getLastRow() + 1, 1, object[e].length, object[e][0].length).setValues(object[e]);
});
}
Note:
As mentioned at my comment, the values retrieved by getValues() is 2 dimensional array. So if Array.includes() can be used at Google Apps Script, in your script, ConceptName.includes("BGT") is always false. And, unfortunately, Array.includes() cannot be used at Google Apps Script. Ref
This modified script was prepared using the sample Spreadsheets of "Sample Raw Data" and "Sorted Data" you shared. So if your actual Spreadsheet is different from these samples, please modify above script.
When the values of source Spreadsheet and the number of destination sheets are much increased, I recommend to use Sheets API for putting the values for each destination sheet.
References:
getValues()
Basic JavaScript features
includes()
indexOf()
reduce()
for​Each()

How Can I Use setValue() Based Off Of 2 Arrays That Match Value?

I should start with letting you know that I'm an extreme novice in JS. My background is almost solely in SQL/VBA. So, any tips you could provide would be greatly appreciated, whether it's in coding or in Stack Overflow etiquette.
Problem Background:
I've got a Script that I use for sending outbound e-mails from Google Form responses, hosted within a Google Sheet and set to OnFormResponse(). Typically, I'm only asked to send back specific bits of information from the form responses within a HTML template e-mail. However, the business case I have now is that I need to look up the values from another sheet, where an adjacent column's value matches a form response value. With the value that's matched, I need to set the value of a specific column/row (F:F) within the Form Response sheet with it.
Example:
Here's a simplified version of what the Form Responses sheet looks like, along with the formula that I would typically use:
Here's what the other tab, 'Unique Databases!', looks like:
So, my understanding of JavaScript arrays is that on the Form Responses Sheet, I would load all columns (A:E in this example) into a variable, and get the values. Then, get columns A:B of 'Unique Databases!', which loads those values into another array. If that is accurate, how do you compare the index of 1 array against the index of another, and return an adjacent match?
Thanks!
You can try this function:
function dbmanager(dbname) {
var ss=SpreadsheetApp.getActive();
var sh=ss.getSheetByName('Unique Databases!');
var rg=sh.getDataRange();
var vA=rg.getValues();
var r='';
for(var i=0;i<vA.length;i++){
if(dbname==vA[i][0]){
r=vA[i][1];
break;
}
}
return r;
}
I'm not sure you need to use a apps script to make a comparison like this. Perhaps a much faster way would be to use a query in the sheet. Something like this maybe:
=QUERY(Sheet1!A1:B3,"SELECT B WHERE A ='"&E2&"'",0)
The first part of the query is looking up the unique databases data. The second part selects column B in the unique databases data where column A is equal to the data base name in the form responses data. Note this query goes in column F of the responses data.
Another alternative using code might be something like this. with the code below running.
function getFormData(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('Sheet2');
var getRange = sheet.getRange('E2:E');
var data = getRange.getValues();
var lookup = getLookupData();
data.forEach(function(item,index){
sheet.getRange(index + 2 , 6).setValue(lookup[item])
})
}
function getLookupData() {
var obj = {};
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('Sheet1');
var range = sheet.getRange('A2:B6');
var data = range.getValues();
data.forEach(function(item){
obj[item[0]] = item[1];
})
Logger.log(obj)
return obj;
}

Categories