Consilidating and Transferring data from multiple sheets - vba

I am stuck on a transfer of data in to a summary sheet. I have 2 sheets and want to summarize it in to a third sheet.
Sheet A
A B C D
1 Apple Orange Peach
2 Period Apple_Price Orange_price peach_price
3 1 5 5 3
4 2 6 4 9
5 3 7 7
Sheet B
A B C D
1 Apple Orange Peach
2 Period Apple_weight Orange_Weight peach_Weight
3 1 2.1 2.5 3.1
4 2 2.1 1.1 2.1
5 3 3.1 2.5
Summary sheet or sheet c (expected)
A B C D
1 Period Price Weight
2 Apple 1 5 2.1
3 2 6 2.1
4 3 7 3.1
5 Orange 1 5 2.5
6 2 4 1.1
7 Peach 1 3 3.1
8 2 9 2.1
9 3 7 2.5
The code I have started writing is somewhat like
For Each Name In Range("B1:D1")
' To copy each name in to first column of summary
Name.Cells.value.copy Worksheets("Summary").Offset(2,0)
' Now to copy a column from each sheet in front of corresponding name
Worksheets("SheetA").Range(Name & lastrow).Copy
Worksheets("summary").Range("a65536").End(xlUP).Offset(2,1)
'Now copy Periods and prices
Worksheets("SheetA").Range(Name & lastrow).Copy
Worksheets("summary").Range("a65536").End(xlUP).Offset(2,2)
'Now copy weights
Worksheets("SheetB").Range(Name & lastrow).Copy
Worksheets("summary").Range("a65536").End(xlUP).Offset(2,3)
Next
Unfortunately I am not able to get this work. There's some problem with offset I guess.

First let us look at your existing code.
For Each Name In Range("B1:D1")
This assumes three fruit. When you add a fourth, you will have to update this code and again when you add a fifth. Does the person who decides which fruit are of interest, maintain the macro? If not, every time they add a fruit, they will have to request an update to the macro.
Deciding what possible future changes to allow for is a balance:
It is almost no effort to allow for extra fruit or extra periods and in most situations this is a very likely change so I normally allow for it.
Currently you have price and weight as interesting properties. Allowing for new properties could be tricky; I would not normally bother.
Are the fruit in the same sequence? Are the periods in the same sequence? Allowing for these changes is more bother than allowing for extra fruit or periods so should I allow for them? In an earlier life, I was responsible for a lot of similar tasks. Worksheets formats were often changed for no reason I could understand. If I simply assumed the worksheets were the format I expected, I could create realistic but wrong summaries and the error might not be recognised for some time. At the very least, I always performed checks for worksheets being in the format I expected.
I am not asking you to agree with my assessment of what changes to prepare for since I know nothing about your application. I am asking you to think about the issue. A change you have not checked for could lead to a corrupt summary or a crashed macro. How important is this? A change you have checked for but do not handle means the macro cannot be run until you update it. How important is this?
Worksheets("summary").Range("a65536").End(xlUP).Offset(2,1)
Prior to Excel 2007, a worksheet had 65536 rows so cell A65536 was the bottom of column A. Anyone who has coded since 2007 would have suggested Cells(Rows.Count, 1) instead of Range("a65536")because it specifies the bottom of column A for the current version of Excel whatever it is.
I do not like Offset because you have to perform mental arithmetic to determine which cell is being addressed. If the number of periods is not always exactly three, you will have to perform arithmetic on the offset row. That is: Offset(2, 1) will have to be replaced by something like Offset(2+Period-1, 1). In addition you have started at the bottom of column A, moved up to the first cell in the column with a value before performing the offset.
If your code is to be performed millions of times per day, shaving a millisecond off the run time might be appropriate but is it appropriate here? How long did it take you to write this code (which does not work anyway) and how long will it take a future maintainer of your code to understand what you are doing? My advice is to make code simple and easy to write unless there is some overwhelming reason for it to be complex and difficult to write.
My code included little tricks for saving time. These are all easy to implement and can become automatic. If it takes you 10 or 20 seconds to type a statement that saves the user a noticeable fraction of a second, the company can get a return on its investment (Your coding time < User's waiting time) within a few months. Also, some of these tricks make future maintenance easier. Always make life easier for the person who has to update this macro in 6 or 12 months because that person might be you.
Please do not use name like “SheetA” or “SheetB”. Names like “Price” and “Weight” immediately tell you the worksheet’s purpose. Meaningful names make like so much easier.
I think that is enough criticism.
Work through this code carefully. There are lots of comments explaining what I am attempting but few comments explaining what each statement does so you will have to look those up if you don’t know and cannot guess. Use F8 to step through the macro statement by statement. Do you understand what each statement does and why I wanted that done? Come back with questions if necessary but the more you can work out for yourself the faster you will develop your own skills.
Option Explicit
' Constants make maintenance so much easier:
' * You code is full of meaningful names rather than numbers whos purpose
' must be looked up.
' * If columns are rearranged or an extra heading line added to one of the
' worksheets, one change here and the problem is fixed.
Const ColPWPeriod As Long = 1
Const ColPWDataFirst As Long = 2
Const ColSummaryFruit As Long = 1
Const ColSummaryPeriod As Long = 2
Const ColSummaryPrice As Long = 3
Const ColSummaryWeight As Long = 4
Const ColSummaryLast As Long = 4
Const RowPWFruit As Long = 1
Const RowPWDataFirst As Long = 3
Sub CombineABIntoS()
Dim ColPriceLast As Long
Dim ColPWCrnt As Long
Dim ColWeightLast As Long
Dim FruitCrnt As String
Dim RowPriceLast As Long
Dim RowPWCrnt As Long
Dim RowSummaryCrnt As Long
Dim RowWeightLast As Long
Dim WshtPrice As Worksheet
Dim WshtWeight As Worksheet
Dim WshtSummary As Worksheet
' Updating the screen for each change can be very time consuming.
Application.ScreenUpdating = False
' * It takes the interpreter a noticable fraction of a second to process
' Worksheets("Xxxxx") because it has to look "Xxxxx" up in its collection
' of worksheet names. These cause these look ups to be performed once and
' the result stored. With all the switching between worksheets this can
' reduce duration noticably.
' * If the names of the worksheets change, only these statements will need
' amendment to fully update the macro.
' * These are not your names. If you do not accept my advice, change to
' your worksheet names
Set WshtPrice = Worksheets("Price")
Set WshtWeight = Worksheets("Weight")
Set WshtSummary = Worksheets("Summary")
' For price worksheet, find last row with a period and last column with a fruit
With WshtPrice
ColPriceLast = .Cells(1, Columns.Count).End(xlToLeft).Column
RowPriceLast = .Cells(Rows.Count, ColPWPeriod).End(xlUp).Row
End With
' For weight worksheet, find last row with a period and last column with a fruit
With WshtWeight
ColWeightLast = .Cells(1, Columns.Count).End(xlToLeft).Column
RowWeightLast = .Cells(Rows.Count, ColPWPeriod).End(xlUp).Row
End With
' Check worksheets match.
' Check same number of fruits
If ColPriceLast <> ColWeightLast Then
Call MsgBox("Worksheet " & WshtPrice.Name & " has " & _
ColPriceLast - ColPWDataFirst + 1 & _
" fruit while worksheet " & WshtWeight.Name & " has " & _
ColWeightLast - ColPWDataFirst + 1 & _
". Sorry I cannot handle this situation", _
vbOKOnly, "Combine Price and Weight worksheets")
Exit Sub
End If
' Check same number of periods
If RowPriceLast <> RowWeightLast Then
Call MsgBox("Worksheet " & WshtPrice.Name & " has " & _
RowPriceLast - RowPWDataFirst + 1 & _
" periods while worksheet " & WshtWeight.Name & " has " & _
RowWeightLast - RowPWDataFirst + 1 & _
". Sorry I cannot handle this situation", vbOKOnly, _
"Combine Price and Weight worksheets")
Exit Sub
End If
' Check same fruits in same sequence.
' Note: have already checked ColPriceLast = ColWeightLast
For ColPWCrnt = ColPWDataFirst To ColPriceLast
If WshtPrice.Cells(RowPWFruit, ColPWCrnt).Value <> _
WshtWeight.Cells(RowPWFruit, ColPWCrnt).Value Then
Call MsgBox("Cell " & ColNumToCode(ColPWCrnt) & RowPWFruit & _
" of worksheet " & WshtPrice.Name & " = """ & _
WshtPrice.Cells(RowPWFruit, ColPWCrnt).Value & _
""" while the same cell in worksheet " & _
WshtWeight.Name & " = """ & _
WshtWeight.Cells(RowPWFruit, ColPWCrnt).Value & _
""". Sorry I cannot handle this situation", vbOKOnly, _
"Combine Price and Weight worksheets")
Exit Sub
End If
Next
' Check same periods in same sequence.
' Note: have already checked RowPriceLast = RowWeightLast
For RowPWCrnt = RowPWDataFirst To RowPriceLast
If WshtPrice.Cells(RowPWCrnt, ColPWPeriod).Value <> _
WshtWeight.Cells(RowPWCrnt, ColPWPeriod).Value Then
Call MsgBox("Cell " & ColNumToCode(ColPWPeriod) & RowPWCrnt & _
" of worksheet " & WshtPrice.Name & " = """ & _
WshtPrice.Cells(RowPWCrnt, ColPWPeriod).Value & _
""" while the same cell in worksheet " & _
WshtWeight.Name & " = """ & _
WshtWeight.Cells(RowPWCrnt, ColPWPeriod).Value & _
""". Sorry I cannot handle this situation", vbOKOnly, _
"Combine Price and Weight worksheets")
Exit Sub
End If
Next
' Formats of two worksheets match
' For summary worksheet, clear existing contents, create header row
' and initialise row counter
With WshtSummary
.Cells.EntireRow.Delete ' Clear any existing contents
.Cells(1, ColSummaryFruit).Value = "Fruit"
.Cells(1, ColSummaryPeriod).Value = "Period"
.Cells(1, ColSummaryPrice).Value = "Price"
.Cells(1, ColSummaryWeight).Value = "Weight"
.Range(.Cells(1, 1), .Cells(1, ColSummaryLast)).Font.Bold = True
RowSummaryCrnt = 2
End With
For ColPWCrnt = ColPWDataFirst To ColPriceLast
' Can copy across fruit from either worksheet since checked to match
WshtSummary.Cells(RowSummaryCrnt, ColSummaryFruit).Value = _
WshtPrice.Cells(RowPWFruit, ColPWCrnt).Value
For RowPWCrnt = RowPWDataFirst To RowPriceLast
If WshtPrice.Cells(RowPWCrnt, ColPWCrnt).Value <> "" Or _
WshtWeight.Cells(RowPWCrnt, ColPWCrnt).Value <> "" Then
' There is either a price or a weight or both for this period and fruit
' Can copy across period from either worksheet since checked to match
WshtSummary.Cells(RowSummaryCrnt, ColSummaryPeriod).Value = _
WshtPrice.Cells(RowPWCrnt, ColPWPeriod).Value
' Copy across price and weight
WshtSummary.Cells(RowSummaryCrnt, ColSummaryPrice).Value = _
WshtPrice.Cells(RowPWCrnt, ColPWCrnt).Value
WshtSummary.Cells(RowSummaryCrnt, ColSummaryWeight).Value = _
WshtWeight.Cells(RowPWCrnt, ColPWCrnt).Value
' Step summart row ready fro next period or fruit
RowSummaryCrnt = RowSummaryCrnt + 1
End If
Next RowPWCrnt
Next ColPWCrnt
End Sub
Function ColNumToCode(ByVal ColNum As Long) As String
Dim Code As String
Dim PartNum As Long
' Last updated 3 Feb 12. Adapted to handle three character codes.
If ColNum = 0 Then
ColNumToCode = "0"
Else
Code = ""
Do While ColNum > 0
PartNum = (ColNum - 1) Mod 26
Code = Chr(65 + PartNum) & Code
ColNum = (ColNum - PartNum - 1) \ 26
Loop
End If
ColNumToCode = Code
End Function

Related

Dynamic reference to closed workbook in VBA

I partially solved the problem that I initially had and figured that my description of the problem was a bit too detailed. I decided to rewrite my question so it's easier to understand the problem and people who are looking for the same thing can relate faster.
I've got several topic files (each with a different name) with 21 rows and 21 columns that need to be gathered into 1 file (called Summary). In Summary, I want a code that looks at a list of the topic names and then places a reference in the cells to the corresponding cells in the topic file. As you can see in the code below, I've accomplished a simplified version of this. It looks at the cell with the name of the first topic file and then created a reference for all rows and columns in that file.
Sub PullValue()
Dim path, file, sheet
Dim i As Integer, j As Integer
Application.ScreenUpdating = False
path = Worksheets("Settings").Range("B23")
file = Worksheets("Consolidation").Range("A1")
sheet = "Overview"
For i = 2 To 22
For j = 1 To 21
Cells(i, j).Formula = "='" & path & "[" & file & ".xlsm]" & _
sheet & "'!" & Cells(i - 1, j).Address & ""
Next j
Next i
Application.ScreenUpdating = True
End Sub
This works as it should, but after that, it has to do this for all the files in that topic name table. I'll keep on trying, but help would be much appreciated, thanks.
If more info is required, don't hesitate to ask.
Thanks!
Bart
After a lot of research and trial & error, I came up with my own solution. I'll share it here so people who are dealing with the same issue can get some input here.
I've added comments to the code so it's easier to understand.
Sub PullValue()
Dim path, file, sheet
Dim LastRow As Long, TopicCount As Long
Dim i As Integer, j As Integer, a As Integer
Application.ScreenUpdating = False
'1. We count how many topics are written in the Topics table to decide the amount of loops
'I do this by checking the total rows in the table and subtract the empty ones
With Worksheets("Settings").ListObjects("Topics")
TopicCount = .DataBodyRange.Rows.Count - _
Application.CountBlank(.DataBodyRange)
End With
'2. We loop the code for the amount of times we just calculated so it does it for all topics
'I'll make a note where we can find that a in the code
For a = 1 To TopicCount
'3. In the consolidation sheet where all the data will be, we want to check what the
'LastRow is in column A to get the starting point of where the data is entered
With Worksheets("Consolidation")
LastRow = .Cells(.Rows.Count, "A").End(xlUp).Row
End With
'4. This If function puts a spacing between the blocks of data if the LastRow is not the
'first row. This is only to make it visually look better.
If LastRow <> 1 Then
LastRow = LastRow + 2
End If
'5. The first thing we want to do is put the name of the topic below the LastRow so it's
'easy to check afterwards what topic the block of data belongs to. Here you can find the
'a from the loop we have in the beginning.
Cells(LastRow, "A").Value = [Topics].Cells(a, [Topics[Topics]].Column)
'6. Locate where the path, file and sheet names are located in the document. Don't
'forget to put the / at the end if it's a website or the \ if it's on your computer.
'If you look to the code at comment number 7, we already have the .xlsm in the formula
'so we don't need to enter that for the file.
path = Worksheets("Settings").Range("D2")
file = Worksheets("Consolidation").Cells(LastRow, 1)
sheet = "Overview"
'This is the core of the code and will the right reference in the right cell. This loops
'for all the 21 rows and columns.
For i = LastRow + 1 To LastRow + 21
For j = 1 To 21
Cells(i, j).Formula = "='" & path & "[" & file & ".xlsm]" & _
sheet & "'!" & Cells(i - LastRow, j).Address & ""
Next j
Next i
Next a
Application.ScreenUpdating = True
End Sub
Any questions you might have regarding the code, let me know. I hope this might help some people. Improvements are of course welcome as well.
Bart

How to Assign values to varying range of cells in VBA

I am trying randomly generate a whole number between 1 and 100, whether that be in a cell or in the vba code directly. Then I want to use that value as the lookup value for a VLookup that will pull another randomly generated whole number between 1 and 10 from a different sheet. Then I want to use that second number between 1 and 10 as an indicator to fill in that many cells in a column with the first number between 1 and 100.
So for example if I were doing it manually: I would have in cell "C27" on Sheet1 =MROUND(RANDBETWEEN(1,100),1). Let's say it returns 40. Then I would look on Sheet2 for number 40 in column A, look over to Column D where there is another =MROUND(RANDBETWEEN(1,10),1). Let's say that one returns 5 (so I need to fill in 5 cells of a column). Then I would head back to Sheet1 and enter 40 into cells K31 through K35 (the original random whole number).
I'm aware that RAND and RANDBETWEEN update anytime the worksheet recalculates. I use triggered IF statements to keep them from updating unless I change a value in a trigger cell. If generating a random number with VBA makes that even easier, I'm all for it.
I don't think it will be helpful for me to post the many iterations I've attempted as I've tried to apply solutions to each individual task of this macro. None of them have seemingly even gotten me close. But here's what I'm using right now that's also not even close. This code was for me to try and get it to work period. So the numbers are static and not random. But I need them random. And yes, this is for me to generate random monsters for my D&D game mastering :)
Thanks to anyone who might be able to get me on the right track!
Sub MonsterRoll()
'
' MonsterRoll
Dim ws As Worksheet
Dim roll As Integer
Dim No1 As Integer
Dim No2 As Integer
Set ws = Sheets("Combat Helper")
roll = 5
No1 = 31
No2 = 31 + 5
On Error Resume Next
For i = No1 To No2
area.Cells(i, 11).Value = 5
Next
End Sub
This table houses the vlookups into sheet "Encounters"
This table contains the source data, with column D being a RANDBETWEEN
I'm still not sure about a few cell references, but think I have a general idea. The code below can be a starting point to do most of what you want -- with a few warnings...
Since you are monitoring for changes in Sheet1 cells K31:K50, and then making changes to that same range, that will trigger the change event again. So, to avoid crazy results, I added a flag so that it will ignore changes untill you tell it to stop ignoring. That will be when you have finished all processing for your original change.
Personally, I would prefer to generate my own random numbers via code for the simple reason that ANY change to any cell will trigger all of your 'random' numbers to regenerate.
Go to Function 'Set_All_Cell_Values' and add whatever code you need to fill other cells.
Option Explicit
Dim blnIgnoreChanges As Boolean
Private Sub Worksheet_Change(ByVal Target As Range)
Dim ws1 As Worksheet
Dim ws2 As Worksheet
Dim i As Integer
Dim iYourNbr As Integer
Dim iMyNbr As Integer
Dim iRow As Integer
Dim iHowMany As Integer
Dim Why As String
' The following code can be dangerous if your code is not working properly!!!!
' Since you want to 'monitor' changes to K31:K50, and then change those same cells via code,
' which will in turn trigger this 'Worksheet_Change' subroutine to fire again,
' you need to be able to ignore changes on demand.
' If this flag gets set and your code didn't complete (AND turn the flag off), then
' any monitoring of future changes will be ignored!!
' If the flag fails to get reset, then just execute the following code in the immediate window:
' blnIgnoreChanges = false
If blnIgnoreChanges = True Then
Exit Sub
End If
Set ws1 = ThisWorkbook.Worksheets("Combat Helper")
Set ws2 = ThisWorkbook.Worksheets("Encounters")
' Sample data in Sheet2
' A B C D E F G H I J
'40 Bird, Falcon 1 1 1 -10 5 2 1d4 t
'41 Men: Wild Man 2 3 2 -9 2 3 1d5 u
'42 Beast 3 5 3 -8 3 4 1d6 v
'43 Elephant 4 7 4 -7 4 5 1d7 w
' Monitor only cells K31:K50
If Target.Row >= 31 And Target.Row <= 50 And Target.Column = 11 Then
' Value must be between 1 and 100
If Target.Value < 1 Or Target.Value > 100 Then
MsgBox "Must enter between 1 and 100"
Exit Sub
Else
' If you want to Lookup match in Col A of Sheet2, and then get value from col D.
iYourNbr = Application.VLookup(Target.Value, ws2.Range("A3:N102"), 4, False)
' I prefer to Generate my own random number between 1 and 10
iMyNbr = Int((10 - 1 + 1) * Rnd + 1)
iRow = Find_Matching_Value(Target.Value)
Debug.Print "Matching Row in Sheet2 is: " & iRow
' DANGER!! If you execute the following line of code, then you MUST set to FALSE
' when you have finished one change!!!
blnIgnoreChanges = True
iHowMany = Sheet2.Cells(iRow, 4).Value
Sheet1.Cells(Target.Row, 13) = iHowMany
Set_All_Cell_Values Target.Row, iRow, iHowMany
End If
' We can ignore all other cell changes
Else
'Debug.Print "Change made to: " & "R" & Target.Row & ":C" & Target.Column & " but not my row or column! Value is:" & Target.Value
End If
End Sub
Function Set_All_Cell_Values(iS1Row As Integer, iS2Row As Integer, iHowMany As Integer)
Dim i As Integer
Debug.Print "Add code to set cells for Sheet1 R:" & iS1Row & " Sheet2 R:" & iS2Row
For i = iS1Row + 1 To iS1Row + iHowMany - 1
Sheet1.Cells(i, 11) = Sheet1.Cells(iS1Row, 11)
'#################################################
' ADD CODE TO FILL OTHER CELLS as needed!!!
'#################################################
Next i
blnIgnoreChanges = False
End Function
Function Find_Matching_Value(iFind As Integer) As Integer
Dim Rng As Range
If Trim(iFind) <> "" Then
With Sheets("Encounters").Range("A:A")
Set Rng = .Find(What:=iFind, _
After:=.Cells(.Cells.Count), _
LookIn:=xlValues, _
LookAt:=xlWhole, _
SearchOrder:=xlByRows, _
SearchDirection:=xlNext, _
MatchCase:=False)
If Not Rng Is Nothing Then
Find_Matching_Value = Rng.Row
Else
MsgBox "Did not find match for value: " & iFind
End If
End With
Else
MsgBox "You passed an empty value to 'Find_Matching_Value'"
End If
End Function

VBA: match of a variable range

I have a problem. For a certain spreadsheet I want to find out the position (only column) of a value smaller than threshold (further called maxt). I have to solve this in VBA as I need them on a different worksheet to give out accumulated numbers.
I am able to retrieve the max smaller than threshold but the vba match function gives back an error that the number couldnt be found.
However, if the value maxt is copied to a cell and I use the the normal match function on the sheet with the cell containing maxt as condition (=MATCH(cell of maxt; range), it works without any issues.
Problem (I only have A to C filled in my example; irrelevant as it doesnt work on only a few constellations).
A B C
8 5 6 -> doesn't work (Error: 1004)
5 6 7 -> works
7 6 7 -> works
4 8 5 -> works
Below is the code.
Dim myVar As Double
Dim myVarAdress As Long
For I = 1 To 10
myVar = Evaluate("=MAX(IF(A" & I & ":M" & I & "<6, A" & I & ":M" & I & "))")
myVarAdress = Application.WorksheetFunction.Match(myVar, Range("A" & I & ":M" & I))
Next I
End Sub
Thanks in advance
Change myVarAdress = Application.WorksheetFunction.Match(myVar, Range("A" & I & ":M" & I))
to myVarAdress = Application.WorksheetFunction.Match(myVar, Range("A" & I & ":M" & I), 0)
That "0" means that you are looking for the exact match. Also you should add a conditon that will skip the 0 value of your "myVar" variable. For example:
If myVar > 0 Then
myVarAdress = Application.WorksheetFunction.Match(myVar, Range("A" & i & ":M" & i), 0)
End If
Your setup has a couple of different problems that must be addressed before a true solution can be found.
1) You are trying to evaluate a MAX() function, that only has one argument. Your IF() function will either return a value less than 6, or FALSE (0). So either your MAX() function is irrelevant, your IF() function is irrelevant, or you left out one or more arguments on either/both of those functions. In any case, there is no set behavior for what to do when there is no item less than 6 in a row. This raises the possibility that myVar is 0, which is likely to cause faulty results because:
2) You left off the third argument in the MATCH() function. Because your range is (currently) larger than your data set, when you leave off the third argument for MATCH() it will fail any time the data is not organized correctly. This is particularly problematic when you are returning FALSE from your IF() function (treated as 0 by MAX()) because MATCH() is matching to the blanks in your data. Which means that the size of your data set does matter. If you had all 13 rows filled in, your first line would (probably) not fail, but it would not actually match to the 5 you want it to if there were any values LOWER than 5 to the right of column B. Also, it potentially causes some of the other lines to fail if suddenly there aren't any values below 6 in any of the columns and there are no blanks for MATCH() to find and treat as a 0.
All that being said, without further clarification of how you want to clean up these problems, here is a proposed solution (that assumes you want the first occurrence of your max value less than 6, regardless of how many occurrences there are):
Sub MatchSub()
Dim myVar As Double
Dim myVarAdress As Long
Dim rngMaxT As Range
Dim wsFindMax As Worksheet
Set wsFindMax = ActiveSheet
For i = 1 To 10
myVar = Evaluate("=IF(A" & i & ":M" & i & "<6, A" & i & ":M" & i & ")")
Set rngMaxT = wsFindMax.UsedRange.Rows(i)
If rngMaxT(1, 1).Value = myVar Then
myvaraddress = 1
Else
Set rngMaxT = rngMaxT.Find(myVar, , xlValues, xlWhole, xlByRows, xlNext, False)
If rngMaxT Is Nothing Then
'There is no value in the row less than 6
Else
myVarAdress = rngMaxT.Column
End If
End If
Next i
End Sub

VBA complex vlookup between worksheets to get average of relative cells

I have a workbook with 2 worksheets. On Sheet1 is a list of names in ColC, and on Sheet2 in column C is the same list of names, but spaced out with data in Column D relating to each name almost as a heading. i.e.
Ben 678
700
450
200
Janet 9
23
So I need a vlookup function to Look up the name in ColC Sheet1, and then find the corresponding name in ColC Sheet2, and do an average of the values for that name in ColD until the value in ColC changes (and the next name appears). The number of values in ColD per name changes between 1 and 100 so theres no set range.
(I'm looking for a solution to calculate the average of the last 6 values per name before the next appears - but I can try to modify that later on by myself once I have a structure.)
I am familiar with VBA but no expert, and this is just beyond my ability - I have tried a few things for a few hours and no luck. I have also this code that does a similar thing (I found it on a forum) but only pastes one value and I am not able to modify it enough to suit my needs - it uses VBA to put formulas in specific cells. (it's pretty useless but I thought it was a start)
Sub MCInternet()
'CODE OFF WEB FOR RETURNING VALUE IN COL ... AFTER A LOOKUP OF VALUE IN RANGE - DOESNT ADDRESS RANGE JUST SINGLE CELL
Dim Cll As Range
Dim lngLastRow As Long
lngLastRow = Cells(rows.count, "C:C").End(xlUp).Row
'Sheets("Unpaid List").Range("H2:H" & lngLastRow).ClearContents
For Each Cll In Sheets("Sheet2").Range("C1:C" & Sheets("Sheet2").Range("C1").End(xlDown).Row)
'Cll.Offset(, 6).Formula = "=Vlookup(" & Cll.Address & ", " & Sheets("Sheet1").Name & "!A:C,1,False)"
Cll.Offset(, 6).Formula = "=Vlookup(" & Cll.Address & ", " & Sheets(Sheets.count).Name & "!A:C,1,False)"
Next Cll
End Sub
I think it's better to define in a new module a Public Function like:
Public Function FindP(xx As Range) As Long
Application.Volatile
Dim FoundIndex
Dim SumFound, i As Long
Set FoundIndex = Sheets("Sheet2").Range("C:C").Find(xx.Value)
If (FoundIndex Is Nothing) = True Then
FindP = 0
Exit Function
Else
SumFound = 0
For i = 0 To 100
If (FoundIndex.Offset(i, 0) = "") Or (FoundIndex.Offset(i, 0) = xx.Value) Then
SumFound = SumFound + FoundIndex.Offset(i, 1).Value
Else
Exit For
End If
Next
FindP = SumFound
End If
End Function
and in every cells in the sheet1:
D1 -> =FindP(C1)
and autocomplete.
The function search in the column C of the sheet2 the name, after loop to sum every value if the name in column C it's equal (1st line) or empty (2nd ... n line).

Merge cells and delete duplicate data

I have a list of companies and each has a scope of work, address and phone number. Some of the companies have multiple scopes of work. It looks something like this:
I want to get rid of the second copy of the stuff like the address (and in my case phone numbers and such) while copying the unique data in the second line and putting it in the first line and then getting rid of the second line.
I have very little experience of coding. I looked up how to do this step by step but something is wrong within the code or the syntax:
I found code for going down a column for a blank space.
I looked up how I would copy a cell to the right of the active blank cell.
I found code for merging the info into the cell one above and one to the right of the active cell.
I found code that deletes the row with the active cell.
I want it to loop until there are no more blank company cells.
So this is how I put it together:
Public Sub SelectFirstBlankCell()
Dim sourceCol As Integer, rowCount As Integer, currentRow As Integer
Dim currentRowValue As String
Do
sourceCol = 6 'column F has a value of 6
rowCount = Cells(Rows.Count, sourceCol).End(xlUp).Row
'for every row, find the first blank cell and select it
For currentRow = 1 To rowCount
currentRowValue = Cells(currentRow, sourceCol).Value
If IsEmpty(currentRowValue) Or currentRowValue = "" Then
Cells(currentRow, sourceCol).Select
End If
Next
Loop Until A647
End Sub
.
Sub mergeIt()
Range(ActiveCell.Offset(0, 1), ActiveCell.Offset(1, 1)).Merge
ActiveCell.Select
End Sub
.
Sub DeleteRow()
RowNo = ActiveCell.Row
If RowNo < 7 Then Exit Sub
Range("A" & ActiveCell.Row).EntireRow.Delete
Sheets("Summary").Select
Range("A4:O4").Select
Selection.Copy
LastRow = Range("A65536").End(xlUp).Offset(1, 0).Row
End Sub
Please never post code as an image since someone who wants to try it out must type it. You can edit your question and add a new section including revised code if necessary.
My copy of your code (plus line numbers) is:
1 Public Sub SelectFirstBlankCell()
2 Dim sourceCol As Integer, rowCount As Integer, currentRow As Integer
3 Dim currentRowValue As String
4 sourceCol = 1 'column F has a value of 6
5 rowCount = Cells(Rows.Count, sourceCol).End(xlUp).Row
6 'for every row, find the first blank cell and select it
7 For currentRow = 1 To rowCount
8 currentRowValue = Cells(currentRow, sourceCol).Value
9 If IsEmpty(currentRowValue) Or currentRowValue = "" Then
10 Cells(currentRow, sourceCol).Select
11 End If
12 Range(ActiveCell.Offset(0, 0), ActiveCell.Offset(1, 1)).Merge
13 ActiveCell.Select
14 If IsEmpty(currentRowValue) Or currentRowValue = "" Then
15 Cells(Range("sourceCol:21")).Delete
16 End If
17 Next
18 End Sub
I am sure we all started selecting cells and accessing the ActiveCell because the macro recorder does this. However, selecting cells is slow and it is very easy to lose track of what is selected. I believe this is your main problem.
Problem 1 The end value for a For-Loop is fixed at the start; Any attempt to reduce rowCount when you delete something will have no effect on the For-Loop.
Problem 2 I suspect you mean the range in line 15 to be sourceCol & ":" & currentRow.
Problem 3 In line 10 you select a cell if it is blank. In line 12 you merge the active cell whether or not you have just selected it. This means your code attempts a merge for every row.
Problem 4 Column 1 is the column that might be blank. Suppose row 1000 is the last row with a supplier's name but row 1005 is the last row with a product. Your code would not process rows 1001 to 1005.
Problem 5 Function IsEmpty() only returns sensible values for Variants. A Variant is either a cell or a variable that can hold different types of value.
I have not tried your code so there may be more mistakes. Do get dispirited. To the best of my knowledge, problem 1 is not documented. I had to discover this "feature" for myself by attempting code similar to yours. The specification for Function IsEmpty() states its limitations but, unless you fully understand Variants, the significance is not obvious. The other problems are easy errors to make and only practice will reduce their frequency.
Below is my solution to your problem. It is not how I would code it for myself but I think I have introduced enough new concepts for one solution.
I do not say much about the syntax of the VBA statements I use since it is usually easy to look up a statement once you know it exists. Ask if necessary but please try to understand the code before asking.
I do not like deleting in situ; it is slow and, if your code is faulty, you have to load the previous version of the worksheet and start again. I have a source (Src) and a Destination (Dest) worksheet.
I use constants for values that might change but not during a single run of your macro.
You assume the address and other details for Jan's Supply on rows 2 and 3 match. I am paranoid and never make assumptions like this. If my code would discard important information if rows 2 and 3 did not match, I check they match. I also allow for rows like this because I have encountered them:
John's supply Cookies 555 Main Street CA
Cakes Littleville CA
This will become:
John's supply Cookies & Cakes 555 Main Street Littleville CA
Some of the comments explain my choice of VBA statement but most do not. When you have to update a macro you wrote 12 months ago for new requirements, the few minutes you spent adding comments can save you hours finding your way around the code.
You may not like my system of naming variables. Fine; develop your own. When you return to this macro in 12 months, an immediate understanding of the variables will save more time.
Option Explicit
Const WkshtSrcName As String = "Sheet1" ' \ Replace "Sheet1" and "Sheet2"
Const WkshtDestName As String = "Sheet2" ' / with the names of your worksheets
Const ColSupplier As String = "A" ' \ In Cells(R, C), C can be a
Const ColProduct As String = "B" ' / number or a column identifier
Const RowDataFirst As Long = 1
Sub MergeRowsForSameSupplier()
Dim ColCrnt As Long ' \ Columns in source and destination are the
Dim ColMax As Long ' / same so single variables are adequate.
Dim RowDestCrnt As Long ' \ Rows in source and destination
Dim RowSrcCrnt As Long ' | worksheets are different
Dim RowSrcMax As Long ' / so need separate variables.
Dim ProductCrnt As String
Dim Join As String
Dim SupplierCrnt As String
Dim WkshtSrc As Worksheet
Dim WkshtDest As Worksheet
Set WkshtSrc = Worksheets(WkshtSrcName)
Set WkshtDest = Worksheets(WkshtDestName)
With WkshtSrc
' I consider this to be the easiest technique of identifying the last used
' row and column in a worksheet. Note: the used range includes trailing
' rows and columns that are formatted but otherwise unused or were used but
' aren't now so other techniques can better match what the user or the
' programmer usually mean by "used".
ColMax = .UsedRange.Columns.Count
RowSrcMax = .UsedRange.Rows.Count
End With
With WkshtDest
.Cells.EntireRow.Delete ' Delete any existing contents
End With
RowDestCrnt = RowDataFirst
For RowSrcCrnt = RowDataFirst To RowSrcMax
With WkshtSrc
SupplierCrnt = .Cells(RowSrcCrnt, ColSupplier).Value
ProductCrnt = .Cells(RowSrcCrnt, ColProduct).Value
End With
If SupplierCrnt <> "" Then
' This is the first or only row for a supplier.
' Copy it to Destination worksheet.
With WkshtSrc
.Range(.Cells(RowSrcCrnt, 1), .Cells(RowSrcCrnt, ColMax)).Copy _
Destination:=WkshtDest.Cells(RowDestCrnt, 1)
End With
RowDestCrnt = RowDestCrnt + 1
ElseIf ProductCrnt = "" Then
' Both Supplier and Product cells are empty.
With WkshtSrc
If .Cells(RowSrcCrnt, Columns.Count).End(xlToLeft).Column = 1 And _
.Cells(RowSrcCrnt, 1).Value = "" And _
.Cells(RowSrcCrnt, Columns.Count).Value = "" Then
' If you do not understand why I have so many tests,
' experiment with Ctrl+Left
' Row empty so ignore it
Else
' Don't know what to do with this error so give up
Call MsgBox("Cells " & ColSupplier & RowSrcCrnt & " and " & _
ColProduct & RowSrcCrnt & " of worksheet " & _
WkshtSrcName & _
" are blank but the entire row is not blank", _
vbOKOnly + vbCritical, "Merge rows for same supplier")
Exit Sub
End If
End With
Else
' Supplier cell is empty. Product cell is not.
' Row RowDestCrnt-1 of the Destination worksheet contains the first row
' for this supplier or the result of merging previous rows for this
' supplier.
If WkshtSrc.Cells(RowSrcCrnt + 1, ColSupplier).Value = "" And _
WkshtSrc.Cells(RowSrcCrnt + 1, ColProduct).Value <> "" Then
' The next row is for the same supplier but is not a blank row
Join = ","
Else
' This is last row for this supplier
Join = " &"
End If
' Add to list of products
With WkshtDest
.Cells(RowDestCrnt - 1, ColProduct).Value = _
.Cells(RowDestCrnt - 1, ColProduct).Value & Join & " " & _
ProductCrnt
End With
For ColCrnt = 1 To ColMax
If ColCrnt = Cells(1, ColSupplier).Column Or _
ColCrnt = Cells(1, ColProduct).Column Then
' You may think (and you may be right) that the supplier and product
' will always be in the first two columns. But have seen the
' weirdest arrangements and make no assumptions
' Ignore this column
Else
If WkshtSrc.Cells(RowSrcCrnt, ColCrnt).Value = "" Then
' The most likely arrangement: the subsequent row has no
' value in this column. Nothing to do.
ElseIf WkshtDest.Cells(RowDestCrnt - 1, ColCrnt).Value = "" Then
' This source row has a value in this column but [the] previous
' row[s] did not.
' Note: I use the copy statement because it copies formatting as
' well as the value which may be useful.
WkshtSrc.Cells(RowSrcCrnt, ColCrnt).Copy _
Destination:=WkshtDest.Cells(RowDestCrnt - 1, ColCrnt)
ElseIf WkshtSrc.Cells(RowSrcCrnt, ColCrnt).Value = _
WkshtDest.Cells(RowDestCrnt - 1, ColCrnt).Value Then
' Values match. Nothing to do.
Else
' Values do not match.
' Don't know what to do with this error so give up.
Call MsgBox("The value in cell " & ColNumToCode(ColCrnt) & _
RowSrcCrnt & " of worksheet " & WkshtSrcName & _
" does not match a value in an earlier row " & _
"for the same supplier", _
vbOKOnly + vbCritical, "Merge rows for same supplier")
Exit Sub
End If
End If
Next
End If
Next
With WkshtDest
.Cells.Columns.AutoFit
End With
End Sub
Function ColNumToCode(ByVal ColNum As Long) As String
' Convert a column identifier (A, AA, etc.) to its number
Dim Code As String
Dim PartNum As Long
' Last updated 3 Feb 12. Adapted to handle three character codes.
If ColNum = 0 Then
ColNumToCode = "0"
Else
Code = ""
Do While ColNum > 0
PartNum = (ColNum - 1) Mod 26
Code = Chr(65 + PartNum) & Code
ColNum = (ColNum - PartNum - 1) \ 26
Loop
End If
ColNumToCode = Code
End Function