Sorting by 2 variables - sorting

This is a follow up to my previous question that was answered: How to Not Transfer Blank Column to Master Sheet?
2 new questions.
How do I sort by 2 items? I need to sort by Person and their Status (ie Completed). In the status cell is also a date, I just want it to find the completed part.
How to skip if the status is completed? Both of these are for the page that has already been combined. Hope this makes sense.
Sample Sheet: https://docs.google.com/spreadsheets/d/1Y48N8W8CdaNlrxXopj5lnHTiNep33g_qA2-kTrPdt3A/edit#gid=130911536
function combineSheets() {
const sApp = SpreadsheetApp.getActiveSpreadsheet();
const months = ['January','February','March','April','May','June','July',
'August','September','October','November','December'];
const master = sApp.getSheetByName('Master');
const sourceData = [];
months.forEach(m=>{
let sh = sApp.getSheetByName(m);
let vals = sh.getRange(1,2,sh.getLastRow(),27).getValues().filter(r=>r[0]!='');
sourceData.push(...vals);
});
master.getRange(1,1,sourceData.length,sourceData[0].length).setValues(sourceData);
}

Note: In my example, I removed the Other Data columns from your example to make the sheet cleaner. I also added Person column.
Try this:
Sheet1:
Code:
function filternsort() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet1');
var sheet2 = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet2');
var sheet3 = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet3');
//get sheet1 data
var val = sheet.getDataRange().getValues();
var completed = [];
var notcompleted = [];
//start to 1 to exclude header
for (var i = 1; i < val.length; i++) {
if(val[i][5].toString().match(/Completed.+/)){
//add to array completed if it matches the word Completed in the Date Due column
completed.push(val[i]);
}else{
//add to array notcompleted if not match.
notcompleted.push(val[i]);
}
}
//insert header to the beginning of the array
completed.unshift(val[0]);
//set filtered values
sheet2.getRange(1, 1, completed.length, completed[0].length).setValues(completed);
//sort by Person
sheet2.getRange(2, 1, completed.length, completed[0].length).sort(1);
//insert header to the beginning of the array
notcompleted.unshift(val[0]);
//set filtered values
sheet3.getRange(1, 1, notcompleted.length, notcompleted[0].length).setValues(notcompleted);
//sort by Person
sheet3.getRange(2, 1, notcompleted.length, notcompleted[0].length).sort(1);
}
Sheet2:
Sheet3:
References:
Array.prototype.unshift()
Range.sort()
String.prototype.match()

Related

App Script For Loop is Slow Need to optimize code for faster update

This code works perfectly but its slow like turtle. Generally I don't take this approach but I was not able to find any other option.
Well my requirement is to where ever in the column code finds 1 get the key and value of the that index defined range and go to To sheet find the key and paste the value in the required columns.
If I can jump to cells where 1 is in selected column and get the index to find the key and value and then jump to To page key column where key is instead of going to every cell and checking for it whether it is there or not.
I am new to app script and little help would be great.
Thank you in advance.
function Data_Update(){
//assigning sheet name to variables
var ss = SpreadsheetApp.getActive()
var from = ss.getSheetByName("From Sheet")//update here from sheet name(Updated)
var To= ss.getSheetByName("To Sheet")//update here to sheet name(Updated)
// Creating Loop
for (var i = 4;i<=7000;i++){//Update here from which row to start
//assigning values to check if there is 1 in column K
var udaterang = from .getRange("K"+i).getValue()//update here from which column to check for
Logger.log(i)
// Checking condition if the value is diffrent from the value already is
if (udaterang == 1) {
//creating key to find the value
var name1 = from .getRange("A"+i).getValue()//update here the key column 1
var name2 = from .getRange("B"+i).getValue()//update here the key column 2
var name3 = from .getRange("C"+i).getValue()//update here the key column 3
var name = name1.trim()+name2.trim()+name3.trim()
var rng = from .getSheetValues(i,4,1,7)//start row, start column, # rows, # columns
// Looping through each cell to check if the data needs update
for(var j=2;j<=12500;j++){
var key = To.getRange("AP"+j).getValue()
if(key == name){ //[1] because column B
To.getRange("AI"+j+":"+"AO"+j).setValues(rng)
break
}
}
}
}
}
Try this:
function Data_Update() {
const ss = SpreadsheetApp.getActive();
const fsh = ss.getSheetByName("From Sheet");
const fkvs = fsh.getRange(4, 11, fsh.getLastRow() - 3).getValues().flat();
const fvs = fsh.getRange(4, 1, fsh.getLastRow() - 3, fsh.getLastColumn()).getValues();
const tapvs = tsh.getRange(2, 42, tsh.getLastRow() - 1).getValues().flat();
fkvs.forEach((k, i) => {
let udaterang = k;
if (k == 1) {
let name = fvs[i][0].toString().trim() + fvs[i][1].toString().trim() + fvs[i][2].toString().trim();
let idx = tapvs.indexOf(name);
if (~idx) {
tsh.getRange(idx + 2, 35, 1, 7).setValues(tsh.getRange(i + 2, 4, 1, 7).getValues());
}
}
});
}
This may take some tweaking because I have not tested this as I don't have the data to do so.

For loop to check values from two spreadsheets

I have two spreadsheets:
Column A on sheet 6th&7thRoster lists all IDs in a sample, contains 853 items.
Column C on sheet alreadySubmitted contains the IDs of users who've completed a task. Contains 632 items.
I'm trying to parse through both columns. If a user from Column A of sheet 6th&7thRoster matches a user from Column C of sheet sandboxAlreadySubmitted, I want to write the word "Yes" on Column I of the current row of sheet 6th&7thRoster. When using the code below, I'm not seeing not seeing any instances of the word "Yes" on Column I of 6th&7thRoster, even though I know there's multiple places where that should be the case.
function checkRoster() {
var mainSheet = SpreadsheetApp.openById('XXXXXXX');
var roster = mainSheet.getSheetByName('6th&7thRoster');
var submissions = mainSheet.getSheetByName('alreadySubmitted');
var rosterLastRow = roster.getLastRow();
var submissionsLastRow = submissions.getLastRow();
var rosterArray = roster.getRange('A2:A853').getValues();
var submissionsArray = submissions.getRange('C2:C632').getValues;
var i;
var x;
for (i = 1; i < 853; i++) {
for (x = 1; x < 632; x++){
if (rosterArray[i] == submissionsArray[x]){
roster.getRange(i, 9).setValue("Yes");
}
}
}
}
Feedback on how to solve and achieve this task will be much appreciated. For confidentiality, I cannot share the original sheets.
You want to compate the values of A2:A853 of 6th&7thRoster and C2:C632 of alreadySubmitted.
When the values of C2:C632 of alreadySubmitted are the same with the values of A2:A853 of 6th&7thRoster, you want to put Yes to the column "I".
If my understanding is correct, how about this modification? Please think of this as just one of several possible answers.
Modified script:
function checkRoster() {
var mainSheet = SpreadsheetApp.openById('XXXXXXX');
var roster = mainSheet.getSheetByName('6th&7thRoster');
var submissions = mainSheet.getSheetByName('alreadySubmitted');
var rosterLastRow = roster.getLastRow();
var submissionsLastRow = submissions.getLastRow();
var rosterArray = roster.getRange('A2:A853').getValues();
var submissionsArray = submissions.getRange('C2:C632').getValues(); // Modified
// I modified below script.
var obj = submissionsArray.reduce(function(o, [v]) {
if (v) o[v] = true;
return o;
}, {});
var values = rosterArray.map(function([v]) {return [obj[v] ? "Yes" : ""]});
roster.getRange(2, 9, values.length, values[0].length).setValues(values);
}
Flow:
Retrieve values from A2:A853 of 6th&7thRoster and C2:C632 of alreadySubmitted.
Create an object for searching the values from the values of alreadySubmitted.
Create the row values for putting to 6th&7thRoster.
References:
reduce()
map()
If I misunderstood your question and this was not the direction you want, I apologize.

Dropdown list with conditions on other dropdown list

How can I make a drop-down list from several lists with a condition from another drop-down list?
Using my image above, let's say I on the first drop-down list (this one is a simple one) select "Furniture"... I would like the second drop-down list to only show the furniture. Same thing with the third drop-down list, would like only the color of my second choice to be shown there.
Did try to place on the criteria "Custom formula" in the "Data validation" one of this two formulas but does not work...
=FILTER(Object,Type = E2)
or
=QUERY(A:C,"SELECT B WHERE A='"&E2&"' ", 0)
Did read in some other topic here that it was not possible with formulas and I could not find an app script for it. How can i place conditional rules and make only appear the values i want on the drop-down menu instead all of them ? I think it has something to do with the "withCriteria(criteria, args)" however i am not understanding how to apply it.
About the list ... it will be compose maybe with 2k lines (each line 3 columns). First column will only have (maybe) 6 or 7 different values. Second about 70 or 80 and third all different. The order will be random cause new values could be added and i can be adding a new Furniture or Animal ...
This is the code i have now
function onEdit(e) {
var range = e.range;
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet1');
if ( range.getRow() > 1) {
if ( range.getColumn() == 5) {
var cell_Range = ss.getRange( range.getRow(), range.getColumn() + 1);
var cell = cell_Range.getCell( 1, 1);
var rangeV = SpreadsheetApp.getActive().getRange('B2:B13');
var rule = SpreadsheetApp.newDataValidation().requireValueInRange(rangeV).build();
cell.setDataValidation(rule);
}
else if ( range.getColumn() == 6 ) {
var cell_Range = ss.getRange( range.getRow(), range.getColumn() + 1);
var cell = cell_Range.getCell( 1, 1);
var rangeV = SpreadsheetApp.getActive().getRange('C2:C13');
var rule = SpreadsheetApp.newDataValidation().requireValueInRange(rangeV).build();
cell.setDataValidation(rule);
}
}
}
Example sheet at https://docs.google.com/spreadsheets/d/1aLpYd8fC0jpwvQOPVTj_yvY7DVKeFFnPvpJSF27if6w/edit?usp=sharing
Thanks in advance
Solution:
Use Datavalidation requireValueInList
Flow:
Get All options A1:C as a array
Filter A(Col1) if edited value in E(Col5) is present in it
Retrieve corresponding Col2(B) as options and build data validation based on it
Offset the edited range by 1 column and set DataValidation
Sample Script:
function onEdit(e) {
const SETTINGS = {
//Edited Column : Column to Check(First col in optionsDataRange is considered 1)
5: 1,
6: 2,
};
var editedRange = e.range,
editedSheet = editedRange.getSheet(),
val = e.value,
col = editedRange.columnStart,
row = editedRange.rowStart;
/*Exit clause(s)*/
if (
Object.keys(SETTINGS).indexOf(col.toString()) === -1 || //If edited col is not in settings
editedSheet.getName() !== 'Sheet1' ||
row > 5
)
return;
var optionsDataRange = editedSheet
.getRange(1, 1, editedSheet.getLastRow(), 3)
.getValues();
/*Only get options where val is present in optionsDataRange*/
var options = optionsDataRange
.map(function(e) {
return e[SETTINGS[col] - 1] == val ? e[SETTINGS[col]] : null;
})
.filter(function(e, i, a) {
return e !== null && a.indexOf(e) === i;
});
var dv = SpreadsheetApp.newDataValidation()
.requireValueInList(options)
.build();
editedRange.offset(0, 1).setDataValidation(dv);
}
References:
Range
Array

Date Validation with If/Then Function in Google Apps Script

Thanks already to Serge insas for his insight both here and here, which have been a godsend for me already. But...I'm having trouble tying everything together with date validation.
To clarify, I have a GAS intended to verify that the date in Column A is (a) more than seven days old and (b) not null. If both pass, the script determines the first empty row in Column G, and then pauses before completing various functions. The beginning of the script looks like...
function getStats() {
var doc = SpreadsheetApp.getActiveSpreadsheet();
var sheet = doc.getSheetByName("Main");
var TITLE_ROW = 1;
var DATE_COL = 1;
var URL_COL = 4;
var sevendaysBefore = new Date(new Date().getTime()-7*24*60*60*1000);
if (DATE_COL != ''||(DATE_COL != null || DATE_COL< sevendaysBefore)) {
var end = sheet.getLastRow();
for( var i = 1; i < end; i++) {
var Gvals = sheet.getRange("G1:G").getValues();
var Glast = Gvals.filter(String).length;
var rowNum = TITLE_ROW+Glast;
var itemurl = sheet.getRange(rowNum,URL_COL).getValues();
Utilities.sleep(500);
...
I've clearly implemented something wrong, though, because the date validation doesn't work—the script appears to function as though the data in Column A doesn't matter. I'm sure I've done something incredibly idiotic, but I'm too ignorant to spot it on my own. So...anyone know what I've overlooked?
While the other answer is probably working (didn't test), its approach is very different from yours.
Below is code that follows the same logic as yours but works at the array level (to follow recommendations in Best practices).
I added a few comments to show the differences, hoping it will help you to understand how it works.
function getStats() {
var doc = SpreadsheetApp.getActiveSpreadsheet();
var sheet = doc.getSheetByName("Main");
var Glast; // define the variable for later use
var vals = sheet.getDataRange().getValues();// get all data in an array (do that before loop)
var TITLE_ROW = 0;// use array index instead of real row numbers
var DATE_COL = 0;// use array index instead of real column numbers
var URL_COL = 3;// use array index instead of real column numbers
var sevendaysBefore = new Date(new Date().getTime()-7*24*60*60*1000).getTime();// get native value in milliseconds to make comparison easier below
for( var i = 1; i < vals.length; i++) { // start loop from Row 2 (=array index 1)
if(vals[i][0]!='' && vals[i][0]!=null&&vals[i][0].getTime()<sevendaysBefore){continue};// use && instead of ||, we want ALL conditions to be true ( see !='' and !=null)
Glast = i; break ;// first occurrence of data meeting above condition (non null and date < 7 days before)
}
var itemurl = vals[Glast][URL_COL];// get the value from the array
Utilities.sleep(500);
//...
Mistake : You are hard coding DATE_COL = 1 and you are using this in if statement. It doesn't get the value of the cell. Also I am not getting your statement "date in Column A is (a) more than seven days old". Is that date is from a cell or you are iterating through all the cells in column A ?.
Below code will satisfy your need and I tested. Here as example I am checking date validation for cell R1C1(A1).
1)Get the date from cell. You can change it or Iterate the cells in column for date.
2) We have date.valueOf() method which returns the number of milliseconds since midnight 1970-01-01.
3) Validation : check the cell data is date and greater than 7 days
function compDate()
{
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var cell = sheet.getRange("A1"); //point1
var date01 = new Date();
var date02 = cell.getValue(); //point2
var dateDiff = (date01.valueOf()-date02.valueOf())/(24*60*60*1000);
if((isValidDate(date02)) == true && dateDiff > 7) //point3
Logger.log("success");
}
//below function will return true if the arg is valid date and false if not.
function isValidDate(d) {
if ( Object.prototype.toString.call(d) !== "[object Date]" )
return false;
return !isNaN(d.getTime());
}

Row number in LINQ

I have a linq query like this:
var accounts =
from account in context.Accounts
from guranteer in account.Gurantors
where guranteer.GuarantorRegistryId == guranteerRegistryId
select new AccountsReport
{
recordIndex = ?
CreditRegistryId = account.CreditRegistryId,
AccountNumber = account.AccountNo,
}
I want to populate recordIndex with the value of current row number in collection returned by the LINQ. How can I get row number ?
Row number is not supported in linq-to-entities. You must first retrieve records from database without row number and then add row number by linq-to-objects. Something like:
var accounts =
(from account in context.Accounts
from guranteer in account.Gurantors
where guranteer.GuarantorRegistryId == guranteerRegistryId
select new
{
CreditRegistryId = account.CreditRegistryId,
AccountNumber = account.AccountNo,
})
.AsEnumerable() // Moving to linq-to-objects
.Select((r, i) => new AccountReport
{
RecordIndex = i,
CreditRegistryId = r.CreditRegistryId,
AccountNumber = r.AccountNo,
});
LINQ to objects has this builtin for any enumerator:
http://weblogs.asp.net/fmarguerie/archive/2008/11/10/using-the-select-linq-query-operator-with-indexes.aspx
Edit: Although IQueryable supports it too (here and here) it has been mentioned that this does unfortunately not work for LINQ to SQL/Entities.
new []{"aap", "noot", "mies"}
.Select( (element, index) => new { element, index });
Will result in:
{ { element = aap, index = 0 },
{ element = noot, index = 1 },
{ element = mies, index = 2 } }
There are other LINQ Extension methods (like .Where) with the extra index parameter overload
Try using let like this:
int[] ints = new[] { 1, 2, 3, 4, 5 };
int counter = 0;
var result = from i in ints
where i % 2 == 0
let number = ++counter
select new { I = i, Number = number };
foreach (var r in result)
{
Console.WriteLine(r.Number + ": " + r.I);
}
I cannot test it with actual LINQ to SQL or Entity Framework right now. Note that the above code will retain the value of the counter between multiple executions of the query.
If this is not supported with your specific provider you can always foreach (thus forcing the execution of the query) and assign the number manually in code.
Because the query inside the question filters by a single id, I think the answers given wont help out. Ofcourse you can do it all in memory client side, but depending how large the dataset is, and whether network is involved, this could be an issue.
If you need a SQL ROW_NUMBER [..] OVER [..] equivalent, the only way I know is to create a view in your SQL server and query against that.
This Tested and Works:
Amend your code as follows:
int counter = 0;
var accounts =
from account in context.Accounts
from guranteer in account.Gurantors
where guranteer.GuarantorRegistryId == guranteerRegistryId
select new AccountsReport
{
recordIndex = counter++
CreditRegistryId = account.CreditRegistryId,
AccountNumber = account.AccountNo,
}
Hope this helps.. Though its late:)

Resources