Related
I am trying to populate a calendar with google sheets in the following way :
I have a sheet called processing which has all the projects and their dates (some use one date and some use up until 14 consecutive days). And then I have a sheet called Calendar with the actual calendar to filter all the dates.
I would like to display the project name for every calendar day that it is booked in the processing sheet.
Please see below screenshots
Processing sheet :
Here is the closest to working function I have tried :
=IFERROR(QUERY(Processing!$A$3:$V,"select A where E = date '"&text(H4,"yyyy-mm-dd")&"' or F = date '"&text(H4,"yyyy-mm-dd")&"' or G = date '"&text(H4,"yyyy-mm-dd")&"' or H = date '"&text(H4,"yyyy-mm-dd")&"' or I = date '"&text(H4,"yyyy-mm-dd")&"' or H = date '"&text(H4,"yyyy-mm-dd")&"' or J = date '"&text(H4,"yyyy-mm-dd")&"' or K = date '"&text(H4,"yyyy-mm-dd")&"' or L = date '"&text(H4,"yyyy-mm-dd")&"' or M = date '"&text(H4,"yyyy-mm-dd")&"' or N = date '"&text(H4,"yyyy-mm-dd")&"' or O = date '"&text(H4,"yyyy-mm-dd")&"' or P = date '"&text(H4,"yyyy-mm-dd")&"' or Q = date '"&text(H4,"yyyy-mm-dd")&"' or R = date '"&text(H4,"yyyy-mm-dd")&"' or S = date '"&text(H4,"yyyy-mm-dd")&"' or T = date '"&text(H4,"yyyy-mm-dd")&"' or U = date '"&text(H4,"yyyy-mm-dd")&"' or V = date '"&text(H4,"yyyy-mm-dd")&"'",1),"")
So basically on the Calendar I want to reflect/filter all values in Processing sheets column A's value that has a date matching the calendar day - so far it works only for every project's first day - but not all days.
Any help would be greatly appreciated.
Spreadsheet Link
https://docs.google.com/spreadsheets/d/1sowkIl_BcsbPoc9PUMZHL2bM7-XNMql2dV3yJo58-Sc/edit?usp=sharing
You can do the following, using Apps Script:
Store an object based on the data in Processing, in which the keys refer to the different dates, and the values are arrays with the different projects (in the code below, that's called datesAndProjects).
Using the A1 notation where the dates are located in Calendar (that is, B4:K4, B16:K16, B28:K28), loop through the different dates, and for each date, find the corresponding property in datesAndProjects and write the corresponding values (the projects) in that column.
Below is a detailed example of how this can be done (check inline comments for more in-depth explanation).
Code snippet:
// Copyright 2020 Google LLC.
// SPDX-License-Identifier: Apache-2.0
function projectToCalendar() {
const ss = SpreadsheetApp.getActive();
const sourceSheet = ss.getSheetByName("Processing");
const sourceData = sourceSheet.getRange(2, 1, sourceSheet.getLastRow()-1, sourceSheet.getLastColumn()).getValues();
let datesAndProjects = {};
sourceData.forEach(row => { // Iterate through each row in "Processing"
const project = row.shift(); // Retrieve first value in each row (project names)
row.forEach(val => {
if (val instanceof Date) { // Check if value is Date
const time = getDayTime(val); // Convert date to milliseconds since Epoch in order to compare them
if (!datesAndProjects[time]) datesAndProjects[time] = []; // Inititalize property if it doesn't exist
datesAndProjects[time].push([project]); // Add project to date
}
});
});
const targetSheet = ss.getSheetByName("Calendar");
const dateRangeNotations = ["B4:K4", "B16:K16", "B28:K28"]; // A1 notations where dates are located
targetSheet.getRangeList(dateRangeNotations).getRanges().forEach(range => { // Iterate through each row with dates
const times = range.getValues()[0].map(date => getDayTime(date)); // Get ms since Epoch in order to compare dates
times.forEach((time, i) => { // Iterate each date (ms)
if (datesAndProjects[time]) { // Check if property exists
targetSheet.getRange(range.getRow() + 2, range.getColumn() + i, datesAndProjects[time].length).setValues(datesAndProjects[time]); // Write values at corresponding column, starting two rows after date cell
}
});
});
}
function getDayTime(date) {
return new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime();
}
Note:
In this snippet, setValues is being called inside a loop, something which is not optimal for efficiency purposes. This could probably be improved, but I think this snippet is enough for you to get the idea.
How to make Google sheets automatically add the ordinal number of the current row based on the following criteria 'new entry on this particular row?
How to make it work even when the data is not entered on a row contiguous basis (for example 1st entry on row 1 (say cell B1), 2nd entry on row 2 (in B2), 3rd entry on row 3 (B3), but then 4th entry on row 9 (B9), 5th on row 11 (B11), 6th on row (B7))?
And not adding ordinals to intermediary empty rows (for example when B5/row5, B6/row6, B8/row8, B10/row10 etc. are blanks, don't add ordinals to A5/row5, A6/row6, A8/row8, A10/row10, etc.)?
Here's the expected result:
The Sheet:
Ordinals to non-contiguous rows based on data entry timestamps
So far I've come up with this pull down formula (to be pre-set in A2 and dragged down to A12):
=IF(B2<>"",MAX($A$2:$A)+1,"") [in A2]
=IF(B3<>"",MAX($A$2:$A)+1,"") [in A3]
=IF(B4<>"",MAX($A$2:$A)+1,"") [in A4]
=IF(B5<>"",MAX($A$2:$A)+1,"") [in A5]
=IF(B6<>"",MAX($A$2:$A)+1,"") [in A6]
=IF(B7<>"",MAX($A$2:$A)+1,"") [in A7]
=IF(B8<>"",MAX($A$2:$A)+1,"") [in A8]
=IF(B9<>"",MAX($A$2:$A)+1,"") [in A9]
=IF(B10<>"",MAX($A$2:$A)+1,"") [in A10]
=IF(B11<>"",MAX($A$2:$A)+1,"") [in A11]
=IF(B12<>"",MAX($A$2:$A)+1,"") [in A12]
But the result I get when I enter then data succesively one by one in B2, B3, B4, B7, B9, and B12, is that it updates all previous cells with the new max value so that the succesion isn't achieved.
Here's a gif that shows the live result:
https://i.imgur.com/1I1Dk8f.gifv
How to lock the previous results so that the ordinal count can proceed to the next entry when it occurs consecutively?
I've consulted those post that are close to what I'm looking for:
How do you replace a formula with its result?
using this script:
function freezeOutput(){
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("SummarySheet");
var range = sheet.getRange("A1:A10");
range.copyTo(range, {contentsOnly:true});
}
Stop Excel from updating formula after a criteria is matched
using this formula:
=IF(NOW() < C1,2*A1,A2)
Alternatively this VBA code:
Function EvaluateIf(expression As String, condition As Boolean) As Variant
Application.Volatile
Dim myText As String
Dim myVal As Variant
If condition Then
myVal = Application.Evaluate(expression)
Else
myText = Application.Caller.Text
If IsNumeric(myText) Then
myVal = Val(myText)
Else
myVal = myText
End If
End If
EvaluateIf = myVal
End Function
Function FreezeAfter(expression As String, deadline As Date) As Variant
Application.Volatile
Dim myText As String
Dim myVal As Variant
If Now > deadline Then
myText = Application.Caller.Text
If IsNumeric(myText) Then
myVal = Val(myText)
Else
myVal = myText
End If
Else
myVal = Application.Evaluate(expression)
End If
FreezeAfter = myVal
End Function
How do I copy cell values programatically?
using this script:
function getRangeValues() {
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getRange("A2:B4");
var values = range.getValues();
return values;
};
With Sheet here:
Copy cell values programmatically (72539)
Lastly:
Add the Current Date to a Sheet When Data Is Added So That The Date Does Not Change(Static) – Google Sheets
using this script:
/**
* Creates a Date Stamp if a column is edited.
* https://yagisanatode.com/2018/02/21/add-the-current-date-to-a-sheet-when-data-is-added-so-that-the-date-does-not-changestatic-google-sheets/
*/
//CORE VARIABLES
// The column you want to check if something is entered.
var COLUMNTOCHECK = 6;
// Where you want the date time stamp offset from the input location. [row, column]
var DATETIMELOCATION = [0,-5];
// Sheet you are working on
var SHEETNAME = 'Sheet3'
function onEdit(e) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
//checks that we're on the correct sheet.
if( sheet.getSheetName() == SHEETNAME ) {
var selectedCell = ss.getActiveCell();
//checks the column to ensure it is on the one we want to cause the date to appear.
if( selectedCell.getColumn() == COLUMNTOCHECK) {
var dateTimeCell = selectedCell.offset(DATETIMELOCATION[0],DATETIMELOCATION[8]);
dateTimeCell.setValue(new Date());
}
}
}
But I don't see how to make it work.
The expected result shoud be simply add ordinal number to corresponding cell in column A upon entering data on corresponding cell in column B.
How do we do that?
Flow:
Use edit trigger to capture events in Col B
Get all of Col A on edit of Col B
Calculate Max of Col A and add 1
SetValue the max to Col A
Snippet:
function onEdit(e) {
var rg = e.range,
row = rg.rowStart,
col = rg.columnStart,
sht = rg.getSheet();
//exit code
if (col !== 2 || sht.getName() !== 'Sheet1' || row === 1) return;
//Calculate max value and add 1
rg.offset(0, -1, 1, 1).setValue(//setvalue in colA
rg
.offset(2 - row, -1, sht.getLastRow() - 1, 1) //get all of colA
.getValues()
.reduce(function(acc, curr) {//get max of colA
return Math.max(acc, Number(curr[0]));
}, 0) + 1
);
}
To Read and Practice:
Array#Reduce
Range#Offset
Event Objects
I believed the condtions written will be quite long and i am not really good in writing this long formula
There are 6 columns i've used which is D ,E, M, N, O, P
Sample data:
D3=123456(Changing variable as it can be 12345, 12345A,123456A)
E3=1
M3=31
N3=_
O3=00
P3=0
The formula are design based on this Column D field(the variable changes is in this field) let say
if length of D3 = 6 then (the current formula i've done)
=IF(LEN(D3)=6,CONCATENATE(M3,D3,N3,O3,E3),CONCATENATE(M3,D3,O3,E3))
The outcome for this will be 31123456_001, if let say the D variable is changed to 123456A( the else
in the formula i've shown as no concatenate N3)
then the outcome will be 31123456A001.
I have added in column p, so that i can use it to concatenate to the format that i need.
There are a few more conditions i need to add in,
Which is
1. If the D3= 12345, the format outcome will be 31012345_001 (concatenate M3,P3,D3,N3,O3,E3)
2. If the D= 12345A, the format outcome will be 31012345A001 (concatenate M3,P3,D3,O3,E3)
3. Data for the column D3 field, 12345A, the A alphabet can be in A-Z.
These are the list of all conditions and outcome that i required in a formula.
1. D3 = 123456 then the outcome will be 31123456_001
2. D3 = 123456A then outcome will be 31123456A001
3. D3 = 12345 then outcome will be 31012345_001
4. D3 = 12345A then outcome will be 31012345A001
Additional info:
These are just format as it can be any numbers combinations, the last letter alphabet can be A-Z
D3 = 123456
D3 = 123456A
D3 = 12345
D3 = 12345A
As I couldn't quite catch all the conditions and outcomes, here is an example of how your formula could look:
=IF(LEN(D3)=5,Outcome_1_Concatenation,IF(LEN(D3)=7,Outcome_2_Concatenation,IF(ISNUMBER(VALUE(RIGHT(D3,1))),Outcome_3_Concatenation,Outcome_4_Concatenation)))
Outcome_1_Concatenation => replace with formula when LEN = 5
Outcome_2_Concatenation => replace with formula when LEN = 7
Outcome_3_Concatenation => replace with formula when LEN = 6 and all are numbers
Outcome_4_Concatenation => replace with formula when LEN = 6 and last is character
If you give all examples in a condition => outcome list, I would be glad to help further.
I would look at creating a lookup table range with 3 options for lengths of 5,6,7.
I named my lookup table range "Length".
First setup this lookup table like this:
5 |
=CONCATENATE(M$3,P$3,D$3,IF(ISNUMBER(VALUE(RIGHT(D3,1))),N3,""),O$3,E$3)
6 |
=CONCATENATE(M$3,IF(ISNUMBER(VALUE(RIGHT(D$3,1))),"",P$3),D$3,IF(ISNUMBER(VALUE(RIGHT(D3,1))),N$3,""),O$3,E$3)
7 |
=CONCATENATE(M$3,D$3,IF(ISNUMBER(VALUE(RIGHT(D$3,1))),N$3,""),O$3,E$3)
For any D3 value, it is checking if that last character is a letter, and if not it will insert N3, otherwise it leaves it out.
Also, for any 6 character value, it checks if the last character is a letter, and if so, it will insert P3, otherwise it leaves it out.
Then, your output formula should be:
=VLOOKUP(LEN(D3),Length,2,FALSE)
This makes it clean and simple.
This is your formula plus the added conditions 1 and 2:
=IF(D3=12345,CONCATENATE(M3,P3,D3,N3,O3,E3),IF(D3="12345A",CONCATENATE(M3,P3,D3,O3,E3),IF(LEN(D3)=6,CONCATENATE(M3,D3,N3,O3,E3),CONCATENATE(M3,D3,O3,E3)))
If you want a more generalized version you can check if D3 is a number, the length of it, if D3 ends with a letter, and replace the nested ifs according to your needs
I got my answers, it's
=IF(AND(LEN(D3)>=6,ISNUMBER(RIGHT(D3,1)*1)),M3&D3&N3&O3&E3,IF(AND(LEN(D3)<6,ISNUMBER(RIGHT(D3,1)*1)),M3&P3&D3&N3&O3&E3,IF(AND(LEN(D3)=6,ISTEXT(RIGHT(D3,1))),M3&P3&D3&O3&E3,M3&D3&O3&E3)))
How do you get the formatted value of a cell when using =CONCATENATE(E1, " - " , D1)?
E1 = 08/21/2014 8:00 PM EST (Formatted value = 08/21)
D1 = Task Item 1
Wanted output: = 08/21 - Task Item 1
Use the TEXT() function:
TEXT(value, format_text)
So if the value is 23.5 and you pass =TEXT(A1, "$0.00") it will return $23.50
Source: http://office.microsoft.com/en-us/excel-help/text-function-HP010062580.aspx
Building on #mmmmmpie answer,
I would go one further an argue that =TEXT(E1,CELL("format",E1)) is the better solution
I have a date store like a string in the format
anomesdia_mail='2014-01-31'
I would need to convert it to a date in order to compare it with another date:
sevendays_after='13FEB14:00:00:00'.
I tried this but didn't work:
anomesdia_mail1 = input(substr(strip(anomesdia_mail),1,10),MMDDYY10.);
and after this I would need to compare both:
sum(case when b.anomesdia_mail<=a.sevendays_after then 1 else 0 end)
Thanks!
Try treating sevendays_after as a datetime variable and use datepart to extract the date from it before you compare it with your date variable, e.g.
data _null_;
a = "2014-01-31";
b = "13FEB14:00:00:00";
a2 = input(a,yymmdd10.);
b2 = datepart(input(b,datetime.));
c= (a2 = b2);
put _all_;
run;
to convert it in a date I have to use the input function.
for example= mail date was a string in the format DD-MM-YYYY
so first:
I have to give it the proper order:
mesdiaano_mail_string = catx(separator,substr(mail_date,6,2),substr(mail_date,9,2),substr(mail_date,1,4));
then convert it in date format:
mail_date_int = input(substr(strip(mesdiaano_mail_string),1,10),MMDDYY10.);
with datepart function I can convert it to an integer with the numbers of days since 1960 and perform operation: