Sorting Google Sheet by multiple criteria with script - google-apps-script

I have some data that I need to sort, first by date, then by ID. So basically I want to group all the IDs together, but have those individual groups sorted by date so that the groups with the earliest dates appear first. This is the code I'm using:
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet1");
sheet.getRange("A1:C").sort([{column: 1, ascending: true}, {column: 3, ascending: true}]);
However, it seems to only be sorting by date and that's it. Here's a sample spreadsheet to demonstrate.

Try:
function myFunction() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet1");
var data = sheet.getRange("A1:C" + sheet.getLastRow()).getValues();
var dates = sheet.getRange(1, 1, sheet.getLastRow()).getValues();
var dummyRange = sheet.getRange(1, 5, data.length, data[0].length).setValues(data);
var newDate = [];
dates.forEach(function (date) {
newDate.push([date[0].slice(0, 10)]);
})
sheet.getRange(1, 5, newDate.length).setValues(newDate);
sheet.getRange("E1:G").sort([{ column: 5, ascending: true }, { column: 7, ascending: true }])
}
Result:
Explanation:
The reason it only sorts by date is because in your sample data you have time included which also includes it in the sort so it just makes sense that they are sorted that way since there really are no equal values.
A workaround would be to cut only the date from the value and use it for sorting. Using the .slice() to get the first N characters. In your sample you can use date[0].slice(0, 10) to get only the date.
In my sample code I have set the updated values to a different range and used it for sorting.

Although I'm not sure whether I could correctly understand your expected result, in your situation, how about the following modification?
From:
sheet.getRange("A1:C").sort([{column: 1, ascending: true}, {column: 3, ascending: true}]);
To:
sheet.getRange("A1:C").sort([{column: 3, ascending: true}, {column: 1, ascending: true}]);

Related

Google Script for Sheet Auto Sort not triggering

I have the following script that works during testing but it does not trigger on edit. Basically in Column 5 I have a dropdown and want them to sort when changed.
// Define the order by which you want to sort
SORT_ORDER = [
{column: 5, ascending: true}, // index 1 for category column
{column: 17, ascending: false} // Index 3 for Revenu column
];
// On sheet edit, apply the function multiSortColumns()
function onEdit(e){
var ss = SpreadsheetApp.openByUrl("https://docs.google.com/spreadsheets/d/19elFArXzf32sSkeyxJ39cSYOVG4PH51px42-ocW3HYI/edit#gid=1695682723");
var sheet = ss.getSheetByName("Deals"); // Get the working sheet name
var range = sheet.getRange("A2:T"); // Get the range of data
range.sort(SORT_ORDER);
}
I tried changing the form onEdit to onChange and no luck.
If it's the same worsheet you're working on, try changing the definition of ss to:
var ss = e.source
That should overcome the permissions issue for onEdit. Let me know!

G Sheets: Group/Separate Rows based on column value, Adding Rows with value or conditional border format

So I have everything working with multiple scripts and one of them is a script to automatically sort the sheet based on Column C (Date).
Here is the sample sheet: https://docs.google.com/spreadsheets/d/1nP4kZEikx1li8JNwCjsEc3ooadr0fiboglUizUcUdAQ/edit?usp=sharing
Basically the idea is for people to be able to add rows in the bottom, add the information and as soon as the date is set for it to move where it belongs.
The problem is that I need a space between the different days but to manually add an empty row can't work because the auto sort sends it to the bottom because the date cell is blank.
Is there anyway to group each date with a sub-header row for each day with a OnOpen trigger? Basically something that recognizes the different values from Column C and adds one row with each value or literally any text with color formatting? Since I have the auto sort script it doesn't even matter if they get added at the end of the sheet, since all the other cells besides column D are empty it should just send it at the top of each date.
I did come across this solution https://stackoverflow.com/a/55944980/13895051
but I can't seem to make it work.
This is the auto sort script I'm using in case it matters
SHEET_NAME = "North Tonawanda";
SORT_DATA_RANGE = "A:S";
SORT_ORDER = [
{column: 3, ascending: true}, // 3 = column number, sorting by descending order
{column: 4, ascending: true} // 1 = column number, sort by ascending order
];
function onEdit(e){
multiSortColumns();
}
function multiSortColumns(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName(SHEET_NAME);
var range = sheet.getRange(SORT_DATA_RANGE);
range.sort(SORT_ORDER);
ss.toast('Sort complete.');
}
I've only worked with a couple scripts in the past so I have no idea what I'm doing and pivot tables are not really an option. I would appreciate the help!
Thanks You!
I figured it out. I ended up creating a menu to trigger everything. I was getting frustrated so I created a menu item for the sort and to speed up the process I recorded a macro adding a row to the top and changing the date cell to "Add Date". So anytime someone adds a row with a new date they can add a row to the top, adding the date, hitting sort and everything goes where it belongs.
It's not an eloquent solution but at the end it worked out better. This way if multiple rows need to be edited they're not all jumping around.
In a previous comment I mention I tried the solution from https://stackoverflow.com/a/55944980/13895051 and said it didn't work.
It was a simple fix. I had to change the format of the date column to plain text.
Below is the finished product sorting by 2 columns and adding border between groups of rows.
Thanks for the help and patience!
SHEET_NAME = "North Tonawanda";
SORT_DATA_RANGE = "A:S";
SORT_ORDER = [
{column: 3, ascending: true}, // 3 = column number, sorting by descending order
{column: 4, ascending: true} // 1 = column number, sort by ascending order
];
function multiSortColumns(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName(SHEET_NAME);
var range = sheet.getRange(SORT_DATA_RANGE);
range.sort(SORT_ORDER);
ss.toast('Sort complete.');
sheet.getRange(1, 1, sheet.getMaxRows(), sheet.getMaxColumns()).setBorder(null,
null, null, null, false, false);
var values = sheet.getRange(3, 3, sheet.getLastRow() - 1, 1).getValues();
var rangeList = values.reduce(function(ar, e, i) {
if (i > 0 && values[i - 1][0] != e[0] && e[0] != "") {
ar.push("A" + (i + 2) + ":S" + (i + 2));
}
return ar;
}, [])
rangeList.push(sheet.getRange(sheet.getLastRow(), 1, 1,
sheet.getLastColumn()).getA1Notation());
sheet.getRangeList(rangeList).setBorder(null, null, true, null, false, false,
"black", SpreadsheetApp.BorderStyle.SOLID_THICK);
}

How can I sort a specific dynamic range?

I have a sheet that sorts on specific criteria, but I want certain rows to remain at the bottom of the sheet and be sorted by different criteria - thus requiring a 2nd sort function with a limited, dynamic sort range.
Here is a minimalised version of the spreadsheet:
https://docs.google.com/spreadsheets/d/1NCdMziBpj0joSv9lQfqT9etz9hMsvgMeuT8X9XTxR20/edit#gid=1466294505
I have modified code that finds the last populated column in a specific row with the intention of finding the row number of the last completed Quest (Where Col C checkbox is unticked), but it doesn't work as intended. I have tried error-trapping with Logger.log(lastCompleteQuest) and Logger.log(i) to try and identify the problem, but they are not logging any values.
Here is the code:
var lastCompleteQuest = ss.getDataRange().getLastRow();
for (var i=lastCompleteQuest;i>=1;i--){
if(ss.getRange(i,activeCell.getCol()).getValue()!="TRUE") {break;}
}
Below is the entire code for the onEdit(e) function. When a checkbox is ticked/unticked it should:
1. Sort the entire sheet based on the criteria in the code
2. Sort the unticked rows based on the criteria in the code
The result should be that all ticked rows should be sorted primarily by Col A, while all unticked rows should remain at the bottom of the sheet and be sorted primarily by Col B.
function onEdit(e) {
if(e.range.getSheet().getName() != 'Quests') { return; }
if(e.range.columnStart==3 && e.value=="TRUE") {
e.range.setNote('Completed: ' + Utilities.formatDate(new Date(), SpreadsheetApp.getActive().getSpreadsheetTimeZone(), "dd-MM-yy HH:mm:ss"));
}
else if (e.range.columnStart==3 && e.value=="FALSE") {
e.range.clearNote();
}
var ss = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var sortRange = "A3:AN";
var range = ss.getRange(sortRange);
var sortOrder = [
{column: 3, ascending: false}, // Sort the quests based on status with incomplete at the bottom
{column: 1, ascending: true}, // Sort all quests in chronological order (incomplete remain at bottom)
{column: 2, ascending: true} // To group incomplete quests based on region so they're easier to tick off
];
range.sort(sortOrder);
var lastCompleteQuest = ss.getDataRange().getLastRow();
for (var i=lastCompleteQuest;i>=1;i--){
if(ss.getRange(i,activeCell.getCol()).getValue()!="TRUE") {break;}
}
var newSortRow = i+1;
var newSortRange = "A"+newSortRow+":AN";
var newRange = ss.getRange(newSortRange);
var newSortOrder = [
{column: 2, ascending: true}, // To group incomplete quests based on region so they're easier to tick off
{column: 1, ascending: true} // Then sort by chronological order
];
newRange.sort(newSortOrder);
}
Thank you to anyone who can offer some help instead of telling me what is wrong with my question. If I knew why it didn't work, I obviously wouldn't have to be asking the question in the first place!
tehhowch inspired me to keep smashing my head against the wall, and surprisingly I was able to come up with a solution basically though trial and error. If anyone else is interested in creating dynamical range sorting for their spreadsheet, here is the code that works now for me:
var lastCompleteQuest = ss.getDataRange().getLastRow();
for (var i=lastCompleteQuest;i>=1;i--){
if(ss.getRange(i,3).getValue()!==false) {break;}
}
var newSortRow = i+1;
var newSortRange = "A"+newSortRow+":AN";
var newRange = ss.getRange(newSortRange);
var newSortOrder = [
{column: 2, ascending: true}, // To group incomplete quests based on region so they're easier to tick off
{column: 1, ascending: true} // Then sort by chronological order
];
newRange.sort(newSortOrder);

How to ignore both the first and last row in named ranges when sorting?

I am a beginner and hope the following makes sense.
Here is a script that employs a custom menu to sort three sections in a sheet.
I now need to amend the script so that it ignores both the first and last row in each named range when sorting.
I believe the first row can be ignored by referencing row 2, but am not sure how to incorporate this into the script.
Is it possible to ignore the last row?
// Sort multiple sections
function onOpen(e) {
var menu = [{name: "Sections", functionName: "SortSections"}]
SpreadsheetApp.getActiveSpreadsheet().addMenu("Sort", menu);
}
function Sort() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet(), rangeToSort = sheet.getRange("Section1");
rangeToSort.sort([{column: 3, ascending: false}, {column: 2, ascending: true}]);
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet(), rangeToSort = sheet.getRange("Section2");
rangeToSort.sort([{column: 2, ascending: true}]);
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet(), rangeToSort = sheet.getRange("Section3");
rangeToSort.sort([{column: 3, ascending: false}, {column: 2, ascending: true}]);
}
You'll need to build a new range to sort on from your named ranges. It takes a bit of logic, but should look something like this:
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet(), rangeToSort = sheet.getRange("Section1");
var trimmedRange = sheet.getRange(rangeToSort.getRow()+1, //skip first row,
rangeToSort.getColumn(), //start column
(rangeToSort.getLastRow() - rangeToSort.getRow())-1, //calculate range height, drop last row
rangeToSort.getLastColumn()-rangeToSort.getColumn() //calculate range width
);
trimmedRange.sort([{column: 3, ascending: false}, {column: 2, ascending: true}]);
See docs for getRange(row, column, numRows, numColumns)
https://developers.google.com/apps-script/reference/spreadsheet/sheet#getRange(Integer,Integer,Integer,Integer)
And the various methods to determine columns/rows of a Range:
https://developers.google.com/apps-script/reference/spreadsheet/range
You can loop through the named ranges, get the part of the range you want to sort and sort ascending or descending:
function sort(){
var ss=SpreadsheetApp.getActiveSpreadsheet()
var s=ss.getSheets()[0].activate() //get Sheet1
var allNamed=s.getNamedRanges() //get named ranges
for(var i=0;i<allNamed.length;i++){ //for each named range
var lr=ss.getRangeByName(allNamed[i].getName()).getLastRow() //get last row number of named range
var col=ss.getRangeByName(allNamed[i].getName()).getColumn() //get column of named range
var toSort=s.getRange(2,col,lr-2,1).getValues().sort() //get row 2 through next to last row. Sort
if(allNamed[i].getName()=="Section3"){ //if named range is Section3, sort descending
toSort.reverse()
}
s.getRange(2,col,lr-2,1).setValues(toSort) //return sorted part of nam ed range.
}}
This leaves the column header and the last row of each named range as it was.

Google Sheets Script Custom Sort

my range has following values "Postpone","Yes","No". The values need to be sorted in order
"Yes"
"No"
"Postpone".
There is no room for additional columns with 1,2,3 values and worksheet function are also not an option. Is it possible to do this just with Google Aps Script?
Extending the issue:
There is two additional columns that contain text values and need to be sorted ascending. Currently I am doing this way:
rng.sort([{column: 1, CUSTOM_SORT_NEEDED}, {column: 2, ascending: true}, {column: 3, ascending: true}, ]);
So, here is what works for me, I used a dictionary. Not sure if best, or nicest but works.
function compare(a,b) {
var crits = {"Yes":1,"No":2,"Postpone":3};
var col =1;
var result = 0;
if (crits[a[col]] > crits[b[col]]) {result = 1}
else if (crits[a[col]] == crits[b[col]]) {result = 0}
else {result = -1}
return result;
}