Insert text in cells when above cell meets condition - vba

I have a schedule sheet for the whole year for a group.
The third row has all the dates of the year, the next couple of rows has the name of each group member.
Members specified in A4:A9.
Holidays are specified in cells A20:A28.
So, what I want to do, probably through VBA, is to make a rule like with conditional formatting, that inserts the word "Holiday" in B4:B9 if B3 matches a date in A$20:A$28, and then into C4:C9 if C3 matches and so on.
Now I cannot write this condition in every cell since it's a schedule used by every member, which is the suggestion I often got from googling, and I can't do it with conditional formatting, so I'm guessing VBA is the way to go, but the VBA code I've found so far seems to use specific cells and wouldn't go through each column.

Without questioning your general approach, a simple code that does what you plan to do could work like the following:
Option Explicit
Sub markhd()
' get calendardays, holidays and employees somehow into collections
' that can be looped. Since that information is stored in a worksheet,
' using ranges is one way to achive that
' holidays
Dim rhd As Range
Set rhd = ActiveSheet.Range("A20:A28")
' group employees
Dim remp As Range
Set remp = ActiveSheet.Range("A4:A9")
' calendar days
Dim rcal As Range
Set rcal = ActiveSheet.Range("B3:NB3")
Dim c As Object
' Loop every day (every c-object in your calendar days),
' then write "Holiday", if it matches an entry in your holiday-range
For Each c In rcal
If Not IsError(Application.Match(c, rhd.Cells, False)) Then
remp.Offset(0, c.Column - remp.Column).Value = "Holiday"
End If
Next c
End Sub
Note:
The concept #EganWolf mentioned is to look for similar objects (calendar days in this example) and iterate through them, performing an action (writing "Holiday") if a check is passed (calendarday is in the range of holidays). There is a gazillion of different ways implement this, i merely posted one easy to follow example
Looping each and every calendar day we're doing 365 iterations here, attempting to match each calendarday in the 9 holidays.
However, we could do this the other way around: loop the 9 holidays, match them in the 365 calendar days, therefore looping only 9 times. Performance could theoretically swing both ways, since despite having less iterations, a single iteration could take longer... but, obviously, at 365 days performance is merely a theoretical problem.
Hope that helps!

Related

Select only one column even if a merged range lies below

Test case:
Take an empty sheet, and merge the range "D2:F2". You can do this manually.
Then, activate the macro recorder and select the column E by just clicking on the E letter on the top of the spreadsheet. You will get the following:
Columns("E:E").Select
Now, try to run this line of code from the same macro directly: you will see that it selects the three columns D, E and F.
Question:
Is this a bug of the macro recorder? Or, rather, a bug of VBA itself (that detects the merged range in my column and decides to extend the selection even if explicitly asked to select one single column)? How should I do to select only one of the columns on which a merged range lies via VBA code, exactly as I can do manually?
Need:
I have a spreadsheet with year on a line, months on the below line and days on the below line.
Hence, the days are just cells but months and especially years are shared/merged cells among the several days.
My need is just to detect the current day and select the column, in order for the user to see on which day they should look the data at. But, because of the "years" cell widely merged just above, the entire year is selected.
No, this is not a bug.
Why: Try to manually select the range E1 to E5. That is what is going on when you use Columns("E:E").select. Think of it as .Select not selecting the column, but instead selecting each cell from top to bottom.
The .select method isn't something you should depend on. What exactly are you trying to use select for? There is another (quite arguably better way) to do this.
Edit: Also, as my father always says, merged cells shouldn't be used. He uses "center across selection" instead, which looks exactly like a merged cell without any of the seemingly buggy behavior.
Need: I would use the macro to highlight the data... probably with something like this...
Range("E7").Interior.ColorIndex = RGB(0, 0, 0)
I feel that the question is genuine unlike some of the comments here. I will try to explain.
Using the test case from the question, say I want to do some action only on column D (say change its column width), without changing the same for columns E to F. I can do that in excel by selecting column D specifically by pressing on column header (press on that "D" in the column names bar). If we select column using range selection (mouse or keyboard shortcut CTRL+SPACE), it extends the selection to include E and F columns. But if we press that column D on the header, it only selects one column. I expect VBA to do the same.
Sadly, I couldn't find anything to "select" a single column or range which includes cells merging through multiple columns or range. However, I could do the action on that single column.
I tried following that didn't work. And I feel that it should work.
Range("D:D").Select
Didn't work. Extends the selection to include merged cells. I guess, this is okay.
Columns("D").Select
Didn't work. Extends the selection to include merged cells. I feel this is not okay.
Columns("D").EntireColumn.Select
Even this didn't work. This definitely should've.
So finally I directly applied the action without selecting the cells.
Column("D").ColumnWidth = 10
And this did it. Only the column D width was changed, leaving column E and F untouched. Similarly, I could do font change and other actions.
Only drawback is that I have to do all actions individually. So, I use a loop to perform action on the selection.
Something like this:
For Each x in Range("D:D")
x.font.size = 10
x.font.name = "Calibri"
'...and so on...
Next x
You probably know the row in which the days start. Therefore, instead of selecting the entire column, you could define a range starting from the first day row to the last day row and select that range.
REQUIREMENTS:
Your table should have this values and formats
Then you can loop through each column on row 4 -just assumed- and check each value if they match today. Next you can scroll to that cell using Application.Goto.
CODE:
Sub FindToday()
Dim wsTable As Worksheet '<~ worksheet with your table
Set wsTable = Sheet2
Dim Cols As Integer '<~ a variable to loop through columns
With wsTable
For Cols = 1 To .Cells(4, .Cells.Columns.Count).End(xlToLeft).Column + 1
If .Cells(4, Cols).Value = Date Then '<~ check if the date is today
Application.Goto wsTable.Cells(1, Cols), True '<~ scroll to that cell if true
Exit For
End If
Next
End With
End Sub
If you want just to hide the particular column if there is merged cell try not to select the column just use like this for example -- Columns("N").EntireColumn.Hidden = True... This will solve your doubt.

Excel VBA - Looping to count cell values larger than / smaller than specific value

I'm currently working with a large amount of contract lists. Each list is in a separate worksheet for each year's quarter and it needs to stay that way. E.g.: 2007_Quarter1, 2007_Quarter2, etc. I have 10 years of data, so 40 quarter report worksheets.
Now I need to code a vba routine that would do the following, in the specified order:
In each quarter report worksheet, run through the column containing contract values; then
Count all values between 0 and 10,000; then
Count all values between 10,001 and 25,000; and so on
Then, when there are no more contract values in the range (which will vary from sheet to sheet), go to the next worksheet and repeat the procedure.
All results should be returned in a worksheet (let's call it Worksheets("Report")) in a table where rows are for value intervals (e.g. 0 to $10,000) and columns are for quarters (e.g. 2007 Q1, 2007 Q2, etc.).
One particular aspect of my problem is that, for practical reasons, I'd prefer to set the control intervals' min and max values in a table contained in another worksheet ("Variables"), where, for example, I'd have all minimum values in range C4 down and all maximum values in range D4 down.
For each contract value, an "If" condition should look like:
If ContractVal > Worksheets("Variables").Range("C4").Value And
If ContractVal < Worksheets("Variables").Range("D4").Value Then ...
So far I'm having no success at coding this efficiently. I suspect some loop would work but I can't find a way to make it happen. A loop would do:
In 2007_Quarter1 ws:
For each cell from A4 down to the end, count values included between Range("C4").Value and Range("D4").Value
Then for each cell from A4 down to the end, count values included between Range("C5").Value and Range("D5").Value
... and so on, then repeat for 2007_Quarter2, 2007_Quarter3, and so on.
I need rescue! Thanks in advance!
If it is ok to you to prepare the resulting matrix manually (intervals in rows and quarters in columns) you could write a macro that read the right file and count the contracts in the right range for every cell in the matrix.
If you want to build the matrix automatically you should tell the macro the year range. Then you can write a loop that read the ranges and for every range you write a loop to read every file in the right order.

Sumproduct dates

I have a spreadsheet worksheet called “ISO Procedure Master List” where the date a procedure is requested is in column G (Format m/dd/yyyy).
In column H the date the procedure is completed is entered. Column H uses the same date format but may contain blanks (procedure not completed yet).
There are currently 67 rows of information in the spreadsheet that span from 2011 to 2015 and that number will continue to grow.
QUESTION, I would like to get the average time (in days) it took from a procedure request (column G) to the completion of procedure (column H) for all of the procedures requested in a given year.
In other words the average (in days) time it took to complete procedures in a given year.
This answer will be in a cell C34 in the same workbook but a different worksheet called “ISO Matrix”. (This information probably does not matter but I thought I would add I just in case)
I have tried several Sumproduct variations with no success.
Assumptions:
Dates in columns G and H are dates, not text string,
Data start in row 2, using row 1 for headings.
For convenience, I assume you have named the ranges you wish to average requestDate and completeDate
You wish do ommit the rows where the procedure is not finished from the average calculation.
After naming your ranges, input the following formula into C34 on "ISO Matrix" tab:
=AVERAGE(IF(completeDate<>"";completeDate-requestDate))
This is an array formula, so when normally pressing enter, remember to press CTRL+SHIFT+ENTER instead. When highlighting the cell, it will then look like this:
{=AVERAGE(IF(completeDate<>"";completeDate-requestDate))}
NOTE: My language pack uses ; as formula separator, so if you language uses , instead, change the formula to:
=AVERAGE(IF(completeDate<>"",completeDate-requestDate))
EDIT: So, I didn't catch that you needed to average by year. In line with my recent formula, it could be update to suit your request.
=AVERAGE(IF(completeDate<>"";IF(YEAR(requestDate)=A15;completeDate-requestDate)))
where A15 holds the year you are interested in. I tried it with an AND in the first IF instead of two IFs, but that did not seem to work. Mind you, you might still need to change ; to , and you also need CTRL+SHIFT+ENTER for this one.
You could also add the code below, to handle the case where you test a year that is not present in the list:
=IFERROR(AVERAGE(IF(completeDate<>"";IF(YEAR(requestDate)=A15;completeDate-requestDate)));"No such year")
...or something like that

Find Last Row of Name with constantly changing names

I'm really stuck on this one. I have a spreadsheet with thousands of rows. I use this code to filter them based off of product in the E column.
Sub IsolateCCENCE()
Dim Operations As Workbook
Dim Operations_Sheet As Worksheet
Set Operations = Workbooks("Operations for Macros")
Set Operations_Sheet = Operations.Worksheets("Operations")
Operations_Sheet.Range("$A$6:$AH$13108").AutoFilter Field:=5, Criteria1:="=CCE" _
, Operator:=xlOr, Criteria2:="=NCE"
End Sub
Which works and leaves me with just under 1700 rows. Within these rows, in the A column, there are company names. Each company takes up approximately 20 rows. Each row represents a payment and has a corresponding date, in the D column. I need a macro (I'm assuming with a loop) that will then do the following:
Go through the rows, find the last row for each company
In that row, find the corresponding date
If that date is within 30 days from today, generate an email
Part 3 is easy. But Part 1 and 2 I can't seem to get. The data is always going to be changing.
Maybe it would be easier to have all of the data copy and pasted into another spreadsheet and then filter through every single company, find the last row (and thus the corresponding date)? But I don't know I would have a macro defined to filter through each company when the company names will be changing constantly.
I appreciate any help. Thanks in advance!
If a specific company name in say F1 then:
=MIN(IF(A:A=F1,D:D))
entered with Ctrl+Shift+Enter should give you the earliest date for the company named in F1, that if more recent than today()-30 (or less far into the future than today()+30 ?) you might use for your e-mail trigger (subject to other filtering etc).

VBA Function able to search through cells?

Back Story
At my job we use a desk calendar to keep track of a multitude of random details that occur: Everyone's daily hours, what type and how many reports were sent, etc. To help us in filling out a few forms my boss decided he wanted to create a spreadsheet for the month and keep it as up-to-date as possible day by day. He went on vacation before this month started, so I've been taking over his duties. Something I noticed is that he hardwired the calculations for the totals (everyone's total hours, total reports sent, etc), so that when I changed the dates from January to February we had some left over ones that lead into March. I deleted them and then the formulas all threw #REF! errors because of it.
How the data is set up is simple:
AB 2.50
CD 3.50
EF 8.00
...ETC...
With the AB,CD,EF being in one column, and the values being in the adjacent column.
The Question
I tried to get this to work, with no luck, so I'm thinking it might be impossible but I thought I'd ask a group of people who know more about the subject than I do: in a function I made I have it accept a string parameter to search for, if it finds it, it moves to the adjacent cell and adds that value to the running total. It then returns that number at the end, so a call to this function would resemble
=getTotals("AB") ' this would display whatever the total for AB is
But when I tried this all I got was a #VALUE! error in the cell.
Is there any way I can get this to work the way I want it to, without the use of a full macro?
The Function
I got this function to work, how I wanted to
Function getTotal(rng As Range, search As String) As Double
Dim r As Range
getTotal = 0
For Each r In rng
If r.Value = search Then
getTotal = getTotal + r.Offset(0, 1).Value
End If
Next
End Function
Calling it like so: =getTotal(A1:D2, "AD")
SUMIF should get you what you need:
=SUMIF(A1:A10,"AB",B1:B10)
Assuming your 'AB' values are in column A, rows 1-10 and the number values are in column B, rows 1-10.
The first argument in SUMIF is the range to check, the second argument is the condition that must be met (search criteria), and the 3rd [optional] argument is the corresponding range from which to get the values to be summed.