Loop and compare values from array - arrays

In Sheet1 I have a single column with populated cells from 1-20 row with regular numbers. Look at picture bellow:
In Sheet2 I have also a single column, cells are starting from 5-25. If I enter some values into these cells, in Sheet1 from cell with that same value to column "D" background color is changed. Look at pictures bellow to see how it works:
I'm doing it with this piece of code:
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Column = 1 And Target.Row >= 5 Then
If Target.Offset(0, 0).Text <> "" Then
Dim n As Long
For n = 5 To 25
If Target.Offset(0, 0).Value = Worksheets("Sheet1").Range("A" & n).Value Then
Worksheets("Sheet1").Range("A" & n & ":D" & n).Interior.ColorIndex = 3
End If
Next n
End If
End If
End Sub
Now, I'd like to make some kind of check every time Sheet1 is activated, in a way if there are no values in cells Interior.ColorIndex = xlNone for all the cells in Range("A1:A20"), if there is a value in some cells Interior.ColorIndex = 3 for those cells. I was thinking about to put these values into an array and then loop through it to compare values but I'm new to VBA so help would be welcome. If there is a better solution, just bring it on.
Also, I'd like to make a piece of code for a situation if I replace value 12 with 17 that Interior.ColorIndex of cell that contains 12 goes to xlNone and of 17 Interior.ColorIndex goes to "3".
So, every suggestion is welcome.

No VBA is needed, as pnuts says.
The easiest way to do this with the colours is with Conditonal Formatting using Formula to check the value of the cells in column A on one sheet against the values in the rows where you want the colour to change and apply the formatting.
See:
MSDN Office Online: Use a formula to apply conditional formatting
Contextures.com: Excel Conditional Formatting -- Examples
Chip Pearson Excel MVP: Conditional Formatting
J-Walk Mr Spreadsheet: Conditional Formatting
BUT if you want VBA, then you can do this in the workbook and worksheet event handlers.
Worksheet Selection/Activation:
in Excel on Sheet1, right-click on the sheetname at the bottom, and click View Code. This will open the class module for Sheet1 in the VB Editor.
At the top of the code module on the left, select Worksheet from the drop-down, and on the right drop-down, click the Activate event-handler.
This will create an empty sub-routine that will be executed by Excel everytime that you select the Sheet1 worksheet.
In that code you can make your checks.
Cell changes on sheet2:
To get code to run everytime you make a cell change on sheet2, you need to open the class module for sheet2, select Worksheet from the drop-down on the left, and Change for the event.
This code will run everytime you change a cell on sheet2 and in here, you would write code that first checks if the Target argument is in your range like this:
If not Application.Intersect(Target, "A5:25") Then Exit Sub
Next you want to write your code to check if the value is no longer matching, and reset the colours.
HTH
Philip

Related

VBA code to paste a range into Excel as an array?

So I have a range of items plugged in an Excel spreadsheet and I am looking for some code to paste those same values as an array (i.e. a ctrl + shift + enter formula) elsewhere in my spreadsheet. I've been trying to use the Range.FormulaArray property but can't seem to get it to work.
As an example, I have a list of text items such as:
Apple
Orange
Banana
And I want to paste these items as a hardcoded array elsewhere in the spreadsheet so that the formula in cell A1 will read:
={"Apple";"Orange";"Banana"}
And the values will spill in to the subsequent two cells
I'm using the latest version of Office 365.
Any ideas?
Use .Formula2:
Sub EasyAsOneTwoThree()
Range("A1").Formula2 = "={""Apple"";""Orange"";""Banana""}"
End Sub
As you see, it spills down very nicely.
(notice I had to double-up on the double quotes)

Update cell value if new value is availiable

So I am brainstorming a few different ways to accomplish this task... but none of the ways I am thinking are very clean. I am looking for a clean way to accomplish this.
I have 2 workbooks (workbook A, workbook B).
Workbook A looks like this:
A B C D E
Tom Bob Sam Ted Meg
1 4 9 3 2
The A,B,C... are the columns (not actually on the sheet) and the 1,4,9,3,2 is data on the last row (could be row 10 or row 1000, etc...)
Workbook B looks like this:
A B
Sam 5
Meg 1
I want to update workbook A with any values on workbook B. So... in this example... Sam and Meg has a new value... So I want to update workbook A to look like this:
A B C D E
Tom Bob Sam Ted Meg
1 4 5 3 1
I feel like the simplest way to do this may be to make something like a dictionary or something like that but I have never used a dictionary and don't know if some other method would be easier / simpler.
So if you want to get data from one book to another then vlookup is the simplest solution
https://photos.app.goo.gl/CRjxhJHVKm6xKJ8x7
I nested it within an if statement that checks to see if there is an error, if there is an error then it just grabs the value from the line above. I.e. if the Name doesn't exist on your second sheet, then there is an error and it will just grab from the line above.
=IF(ISERROR(VLOOKUP(A1,[Book2]Sheet1!$A$2:$B$5,2,FALSE)),A11,VLOOKUP(A1,[Book2]Sheet1!$A$2:$B$5,2,FALSE))
On the face value, this does what you are asking but the way you phrased the question implies that there is some other functionality and some other things you need to do that this solution will possibly get in the way of. That's why I was trying to clarify what you really want to achieve.
Anyway, hopefully its a step in the right direction... Goodluck!
Here is my take on it. No dictionary, however going through memory in an array:
Sample data:
Sheet1:
Sheet2:
Sample code:
Sub Test()
Dim lr As Long, x As Long, col As Long
Dim arr As Variant
'Step 1: Get the values to update
With Sheet1
lr = .Cells(.Rows.Count, 1).End(xlUp).Row
arr = .Range("A1:B" & lr)
End With
'Step 2: Go through our values to update
With Sheet2
For x = LBound(arr) To UBound(arr)
col = .Rows(1).Find(arr(x, 1), Lookat:=xlWhole).Column
.Cells(.Cells(.Rows.Count, col).End(xlUp).Row + 1, col) = arr(x, 2)
Next x
End With
End Sub
Result:
Remove the +1 in case you want to rewrite the last entry in the found column. I just assumed you wanted to paste the value underneath. Also, I refered to sheets, you can simply refer to the other open workbook.
Firstly, a little more background on my problem. I am opening text files, extracting data, re-arranging the data on an excel sheet (wkbTemp), and then copying the re-arranged data to "wkbCompiledDataTable."
Here is how i solved my problem.
wkbTemp.Sheets(1).Activate
LRow = Cells(Rows.Count, 9).End(xlUp).Offset(0, 0).row
wkbTemp.Sheets(1).Range("J1:N" & LRow).Copy
wkbCompiledDataTable(1).Activate
LRow = Cells(Rows.Count, 1).End(xlUp).Offset(0, 0).row
wkbCompiledDataTable(1).Sheets(1).Range("D" & LRow).PasteSpecial _
Paste:=xlPasteValues, _
SkipBlanks:=True, _
Transpose:=True
As I said earlier, the actual application is much more complicated. This routine is actually in a loop. So for example I actually have "wkbCompiledDataTable(i)" instead of "wkbCompiledDataTable(1)." But for me this was the simplest solution. wkbTemp.Sheets(1) Column "J" has data. Column "K" is blank. Column "L", Column "M", Column "N" has data (These 3 columns are the columns I was concerned with data changing).

Excel - setting a dynamic print area for a range of cells covered by an array formula

I have a spreadsheet with a page (Sheet 3) which I would like to export as a PDF (using a macro to export the pdf).
Currently, this links in to another worksheet, in which a user can put in a date range to pull out relevant data from a larger worksheet. This uses the following array formula to populate the data on Sheet 3:
=IFERROR(INDEX(Sheet1!$V$3:$W$5998,SMALL(IF((Sheet1!$C$3:$C$5998>=Crynodeb!$D$3)*(Sheet1!$C$3:$C$5998<=Crynodeb!$F$3)*(Sheet1!$V$3:$V$5998<>""),ROW(Sheet1!$V$3:$W$5998)-2),ROW(18:18)),1),"")
The array formula on Sheet 3 has been applied to around 6000 rows of data. So there is potential for 6000 lines of data to be returned. However, depending on the criteria the user has put in, maybe only 5 rows of data will be returned.
In addition to this, I've applied cell formatting to the 6000 rows so that there's a print-friendly line in between the rows of data.
However, because this has been applied to the 6000 rows of data, there could be 61 or so pages exported, when in reality only a page worth of data is displaying.
Is there an easy way to continue having the array formula applied across a large range, while limiting the print function to only apply to pages containing data that is returned from the array formula?
I'm also using the Format > AutoFit Row Height function to adjust the row height in accordance to the length of the returned items, but at the moment I think I have to do this manually every time I return data. Is there a way of applying that automatically to adjust around the content of the page?
Many thanks
Since you want to export the data to PDF via macro, we can just put everything you want into that one macro.
The macro needs to find the range that has values. From your example, I'm assuming there will always be data returned starting in A3; and then there will no blanks in Column A until the end of the data.
So, this macro starts at A3 and looks at each cell until it finds a blank value to determine where the data ends.
I'm also assuming you have a fixed number of columns of data. I can only see 2 columns so I've used that figure in macro. You can update the code where indicated to the actual number of columns.
Then, from A1 to the last row and column of the data, it autofits the rows and then saves that range as a PDF.
Public Sub SaveCopy()
Dim intLastRow As Integer
'Loops through column A to find last row with data
intLastRow = 3
Do While Cells(intLastRow + 1, 1).Value <> ""
intLastRow = intLastRow + 1
Loop
'With the range of data...
'NOTE: REPLACE '2' WITH ACTUAL NUMBER OF COLUMNS OF DATA
With Range("A1", Cells(intLastRow, 2))
'Ensure text is wrapping - required to autofit
.WrapText = True
'Autofit row height
.Rows.AutoFit
'Save as PDF
.ExportAsFixedFormat Type:=xlTypePDF, _
Filename:=ThisWorkbook.FullName, _
Quality:=xlQualityStandard, _
IncludeDocProperties:=True, _
IgnorePrintAreas:=False, _
OpenAfterPublish:=True
End With
End Sub
This is a basic export and will just save the PDF into the same location as the file and with the same filename (including the excel file extension) and just appends .PDF file extension.
There are various options for handling the filename and location of the PDF. With more information of what is required I could help provide a robust solution.
Alternatively, you can remove the line of code for exporting to PDF and replace it with just ".Select"
This will select the range of data and then you can manually save to PDF. Just use Save As... change the type to PDF, click Options and choose "Selection" in the "Publish what" section.
If any of my assumptions are incorrect, please let me know.

VBA group by process using Public Types

I want to create a counter in a routine that will count how many times a specific entry has appeared so far.
The routine that i have created so far populates data in a spreadsheet through a For..Next Loop. For each of these rows i have an extra column that will represent the counter and count how many times a characteristic of the entry row has appeared so far in the previous rows. For that, I am using the application.worksheetfunction.CountIf function but the reference range has to be dynamic.
For example, I have the following table
Example Table
the overall idea is to group by month and expense type and have the sum amount. The role of the counter is to identify these rows that can be grouped together and loop through their values and sum them. The table has approximately 10,000 rows and 53 columns. For this process, i have created the following public type:
>public type OP
>>Month as string
>>expense_type as string
>>amount as double
>end type
Sub NewOuput()
with sheet1
>for i=1 lastrow 'output is the existing table that i get the data and i want to manipulate and then populate them into another table of the same format
>>op.month=output(i,1)
>>op.expense_type=output(i,2)
>>op.amount=output(i,3)
'----------------------------
>> .cells(i,1)=op.month 'this is the population of hte data in the new table
>> .cells(i,2)=op.expense_type
>> .cells(i,3)=op.amount
next i
end with
end sub
Through functions, i try to identify the rows that need to sum-up and then call the respective functions in the output part of the loop.
Countif excel function cannot be appied with arrays, so this is now out of hte question. I have read many posts on various ways of grouping including data connections, collections and other customised approaches. Collections appeared to be the best ones but i miss some of hte background there.
Does this make any sense? Any suggestions are appreciated
I didn't actually grasp your exact needs, but since the table example image I'd go like follows:
Sub NewOuput()
With sheet1
'fill in the voids of 1st column
With .Range("A1:A" & .Cells(.Rows.Count, "B").End(xlUp).row) '<--| change "A" and "B" to your actual 1st and 2nd columns index
.SpecialCells(xlCellTypeBlanks).FormulaR1C1 = "=R[-1]C"
.Value = .Value
End With
'more code to exploit a "full" database structure
End With
End Sub

Count the number of specific words in a list

I have some big computation to do since I have an Excel file with a column representing a list of unique IDs of people that worked on every incidents in our system. I would like to know the total number of interventions that have been done on all incidents. For example, let's say I have this:
ID|People working on that incident
¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
0|AA0000 BB1111 CC2222 ZZ1234
1|BB1111
2|CC2222 ZZ1234 CC2222 ZZ1234
3|BB1111 CC2222 AA0000 BB1111
I have a list named List which has a zone with the list of people IDs I actually want to include. For example, let's say that the first zone of List = {"AA0000","CC2222"}.
Now, I would like to know how many interventions have been done by our employees (in List) on all the incidents I have (we have 4 in the array above). The result would be 6: 2 interventions for incident ID 0, 0 for ID 1, 2 for ID 2 and 2 for ID 3.
Assuming the data are in a different (closed) workbook, how can I calculate that using my list List and the range above A1:B4 (I would like to eventually use the whole columns, so let's say A:B)?
EDIT:
I already got something working that count the number of times a specific word is in a whole column.
SUM(
LEN('[myFile.xlsx]Sheet1'!$A:$A)
-LEN(
SUBSTITUTE('[myFile.xlsx]Sheet1'!$A:$A;$Z$1;"")
)
)
/LEN($Z$1)
Z1 is the word I'm looking for (example: CC2222) and '[myFile.xlsx]Sheet1'!$A:$A is the column I'm searching in.
Isn't there a really simple way to make this working with an array instead of Z1? The length is always the same (six plus a space).
Source: http://office.microsoft.com/en-ca/excel-help/count-the-number-of-words-in-a-cell-or-range-HA001034625.aspx
Split your source data ColumnB with Text to Columns. Unpivot the result, delete the middle column and pivot what's left.
You could do this fairly easily with a User Defined Function. The function below takes two arguments. The first is the range constituting you second column labelled above "People working on that incident". The second is your List which is a range consisting of a single entry for each ID you wish to count. As shown in your example, if multiple identical ID's are shown in a single entry (e.g. your ID 2 has CC2222 repeated twice), they will each be counted.
To enter this User Defined Function (UDF), opens the Visual Basic Editor.
Ensure your project is highlighted in the Project Explorer window.
Then, from the top menu, select Insert/Module and
paste the code below into the window that opens.
To use this User Defined Function (UDF), enter a formula like
=InterventionCount(B2:B5,H1:H2)
in some cell.
Option Explicit
Function InterventionCount(myRange As Range, myList As Range) As Long
Dim RE As Object, MC As Object
Dim vRange As Variant, vList As Variant
Dim sPat As String
Dim I As Long
vRange = myRange
vList = myList
If IsArray(vList) Then
For I = 1 To UBound(vList)
If Not vList(I, 1) = "" Then _
sPat = sPat & "|" & vList(I, 1)
Next I
Else
sPat = "|" & vList
End If
sPat = "\b(?:" & Mid(sPat, 2) & ")\b"
Set RE = CreateObject("vbscript.regexp")
With RE
.Global = True
.ignorecase = True
.Pattern = sPat
End With
For I = 1 To UBound(vRange)
Set MC = RE.Execute(vRange(I, 1))
InterventionCount = InterventionCount + MC.Count
Next I
End Function
For a non-VBA solution you could use a helper column. Again, List is a single column which contains the list of people you want to add up, one entry per cell.
If your data is in Column B, then add a column and enter this formula in B2:
This formula must be array-entered; and the $A:$J terms represent a counter allowing for up to ten items in the entries in column B. If there might be more than that, expand as needed: e.g. for up to 26 items, you would change them to $A:$Z
=SUM(N(TRIM(MID(SUBSTITUTE(B2," ",REPT(" ",99)),(COLUMN($A:$J)=1)+(COLUMN($A:$J)>1)*(COLUMN($A:$J)-1)*99,99))=(List)))
Fill down as far as necessary, then SUM the column to get your total.
To array-enter a formula, after entering
the formula into the cell or formula bar, hold down
ctrl-shift while hitting enter. If you did this
correctly, Excel will place braces {...} around the formula.
I finally went for a completely different solution based on my working formula for 1 employee:
SUM(
LEN('[myFile.xlsx]Sheet1'!$A:$A)
-LEN(
SUBSTITUTE('[myFile.xlsx]Sheet1'!$A:$A;$Z$1;"")
)
)
/LEN($Z$1)
Instead of trying something more complicated, I just added a new column to my employee list where the total is evaluated for each employees (it was already needed elsewhere anyway). Then, I just have to sum up all the employees to get my total.
It is not as elegant as I would like and I feel like it is a workaround, but since it is the easiest solution on a programmation standpoint and that I need the individual datas anyway, it's what I really need for now.
+1 to all the other answers for your help though.

Resources