I'm pretty new to VBA and programming in general.
This is what I'm trying to do:
I have a database starting on row 21 of my worksheet (storing a name, second name, job). I'm trying to use a for loop, to create a new worksheet for every name in the database - for example (1 - William), (2 - John) ...
When we add a new member to our database and run our macro - It should add the a new sheet (but don't change anything to the existing ones), so with other words just skip (1 - William) and (2 - John) but adds (3 - Kera).
So far it's creating the 2 first worksheets, but when I add someone to my database - the new worksheet isn't added.
Could any of you experts help me with this issue, and brake down what I'm doing wrong?
Sub test()
Dim i As Long, LastRow As Long
LastRow = Cells.Find(What:="*", _
After:=Range("A1"), _
LookAt:=xlPart, _
LookIn:=xlFormulas, _
SearchOrder:=xlByRows, _
SearchDirection:=xlPrevious, _
MatchCase:=False).Row
Dim blnFound As Boolean
blnFound = False
For i = 21 To LastRow
For x = 1 To Worksheets.Count
If Worksheets(x).Name = ((i -21) + 1) & " - " & Worksheets("Database").Cells(i, 3).Value Then
blnFound = True
End If
Next
If blnFound = False Then
Worksheets.Add.Move After:=Sheets(1)
ActiveSheet.Name = ((i - 21) + 1) & " - " & Worksheets("Database").Cells(i, 3).Value
End If
Next i
End Sub
I hope you this will help you:
Sub CreateNewSheetFromRange()
Dim mySht As Worksheet
Dim mySheet As Worksheet
Dim BeginRow As Long
Dim myStr As String
Dim r As New Collection
Dim Pos As Integer
On Error Resume Next
For a = 1 To Sheets.Count
'To List Your Sheet With Name Begin With Number & " - "
'If you don't care how many time time the same value repeat,
'you can disobey this for loop
Pos = InStr(Sheets(a).Name, " - ")
If Pos > 0 Then
r.Add Sheets(a).Name, Mid(Sheets(a).Name, Pos + 3)
End If
On Error GoTo 0
Next
BeginRow = 21
Do While True
On Error Resume Next
myStr = ((BeginRow - 21) + 1) & " - " & Worksheets("Data").Cells(BeginRow, 3).Value
If Worksheets("Data").Cells(BeginRow, 3).Value = "" Then
Exit Do
End If
'If you hope avoid your repeated cells value to create new sheet
'perform this
myVal = r.Item(Worksheets("Data").Cells(BeginRow, 3).Value)
'If you don't care about repeated value, you change this with:
'set mySht=worksheets(myStr)
If Err.Description <> "" Then
On Error GoTo 0
Set mySheet = Sheets.Add(After:=Sheets(Sheets.Count))
'If you don't care about repeated value, remove this r.add
r.Add myStr, Worksheets("Data").Cells(BeginRow, 3).Value
mySheet.Name = myStr
a = 1
End If
BeginRow = BeginRow + 1
Loop
End Sub
Related
What I want the Macro to accomplish:
I want the user to be able to fill in data from E2 to E9 on the spreadsheet. When the user presses the "Add Car" button the macro is supposed to be executed. The makro then should take the handwritten data, copy everything from E2:E9 and put it into a table that starts at with C13 and spans over 7 columns, always putting the new set of data in the next free row. It is also supposed to check for duplicates and give an alert while not overwriting the original set of data
So my problem is, that I want the Macro I'm writing to take the information put into certain cells and then copy them into a table underneath.
I'm starting the Macro like this
Sub addData()
Dim lastrow As Long, nextBlankRow As Long
lastrow = Cells.Find(What:="*", _
After:=Range("A1"), _
LookAt:=xlPart, _
LookIn:=xlFormulas, _
SearchOrder:=xlPrevious, _
MatchCase:=False).Row
nextBlankRow = lastrow + 1
Here I try to define how the Macro is supposed to find the last empty cell and also define lastrow and nextBlankRow.
After that I'm starting with a simple If statement to see if the person has at least something in E2 on the same sheet.
If Range("E2") = "" Then
MsgBox "Wählen Sie ein KFZ aus!"
Range("E2").Select
Exit Sub
End If
This works. When I'm not putting something into E2 I get the textbox with the alert.
Anyway if the IF-Statement is not triggered to exit the sub the Macro is given the instructions to get the information and put it in the table below
Cells(nextBlankRow, 3) = Range("E2")
Cells(nextBlankRow, 4) = Range("E3")
Cells(nextBlankRow, 5) = Range("E4")
Cells(nextBlankRow, 6) = Range("E5")
Cells(nextBlankRow, 7) = Range("E6")
Cells(nextBlankRow, 8) = Range("E7")
Cells(nextBlankRow, 9) = Range("E8")
Here seems to be a problem that probably relates to me failing to define variables correctly?
Because the Macro finds the right row but only overwrites into that row. So it ignores the fact that it "should" skip to the nextBlankrow which I defined earlier as
nextBlankRow = lastrow + 1
In addition to that I also have a line of code inplace which is supposed to check for duplicates
Dim p As Long, q As Long
p = 13
q = p + 1
Do While Cells(p, 3) <> ""
Do While Cells(q, 3) <> ""
If Cells(p, 3) = Cells(q, 3) And Cells(p, 4) = Cells(q, 4) Then
MsgBox "Datensatz schon vorhanden!"
Range(Cells(q, 3), Cells(q, 9)).ClearContents
Else
q = q + 1
End If
Loop
p = p + 1
q = p + 1
Loop
End Sub
Which always gives a false return. So even if the same set of Data is copied twice into the same row (as it does) it only "refreshes" the data and doesn't say "you're not allowed to do that".
I'm at a loss here.
Here's the full code for ease of use
Sub addData()
Dim lastrow As Long, nextBlankRow As Long
lastrow = Cells.Find(What:="*", _
After:=Range("A1"), _
LookAt:=xlPart, _
LookIn:=xlFormulas, _
SearchOrder:=xlPrevious, _
MatchCase:=False).Row
nextBlankRow = lastrow + 1
If Range("E2") = "" Then
MsgBox "Wählen Sie ein KFZ aus!"
Range("E2").Select
Exit Sub
End If
Cells(nextBlankRow, 3) = Range("E2")
Cells(nextBlankRow, 4) = Range("E3")
Cells(nextBlankRow, 5) = Range("E4")
Cells(nextBlankRow, 6) = Range("E5")
Cells(nextBlankRow, 7) = Range("E6")
Cells(nextBlankRow, 8) = Range("E7")
Cells(nextBlankRow, 9) = Range("E8")
Dim p As Long, q As Long
p = 13
q = p + 1
Do While Cells(p, 3) <> ""
Do While Cells(q, 3) <> ""
If Cells(p, 3) = Cells(q, 3) And Cells(p, 4) = Cells(q, 4) Then
MsgBox "Datensatz schon vorhanden!"
Range(Cells(q, 3), Cells(q, 9)).ClearContents
Else
q = q + 1
End If
Loop
p = p + 1
q = p + 1
Loop
End Sub
```![enter image description here](https://i.stack.imgur.com/dJozM.jpg)![enter image description here](https://i.stack.imgur.com/Q90Ah.jpg)
Please, test the next code:
Sub copyRangeOnLastEmptyRow()
Dim sh As Worksheet, arr, lastERow As Long, matchCel As Range
Set sh = ActiveSheet
arr = sh.Range("E2:E9").value
lastERow = sh.Range("C" & sh.rows.Count).End(xlUp).row + 1
If lastERow < 13 Then lastERow = 13
'check if the range has not been alredy copied:
Set matchCel = sh.Range("C13:C" & lastERow - 1).Find(WHAT:=sh.Range("E2").value, LookIn:=xlValues, Lookat:=xlWhole, MatchCase:=False)
If Not matchCel Is Nothing Then
MsgBox sh.Range("E2").value & " has been found in cell " & matchCel.Address & "."
'bring up the data of the existing row:
sh.Range("E3:E9").value = Application.Transpose(sh.Range(matchCel.Offset(0, 1), matchCel.Offset(0, 7)).value)
Exit Sub
End If
sh.Range("C" & lastERow).Resize(1, UBound(arr)).value = Application.Transpose(arr)
sh.Range("E2:E9").ClearContents
End Sub
The situation is like this:
I have 40 worksheets embedded with same Macros called RetrieveNumbers. The end results in 40 sheets will be different based upon various parameters in each worksheet.
To update the numbers, I manually click the macro buttons to retrieve numbers in the 40 worksheets. As a result of that, I'm sick of it. To simplify the testing, I only use two sheets(Sheet1, Sheet2) to test if, by clicking a Macro named RunAll, it would run through the two Macros.
Surely, I have FAILED.
I have tried :
application.run
call
Two scenarios I tried:
I hit F5 as I was in the RunAll window and my other screen on the Sheet1 worksheet. It runs perfectly and yet it runs twice in Sheet1 rather than going to Sheet2.
I hit F5 as I was in the RunAll window and my another screen on the RunAll worksheet. After clicking it, I went back to see if there were any numbers. And surely, there weren't.
I thought the Macro would go to Sheet2 and then run Macro Retrivenumbers2. But it didn't. It stayed at the current worksheet. Please give me some guidance on how to run the next sheets I want. Let me know if I need to clarify more on this question.
The Macro RetrieveNumbers
(Since the Macro RetrieveNumbers2 is as same as RetrieveNumbers1, I don't include it)
Sub RetrieveNumbers1()
Dim NumberFiles As Integer, FilesVisited As Integer, RowNumber As Integer
Let NumberFiles = ActiveSheet.Cells("2", "A").Value
Let FilesVisited = 0 'start from 0
Let RowNumber = 4 'start from column B
If NumberFiles > 30 Then
MsgBox "Don't try to retrieve numbers from more than 30 files at a time!"
Else
For FilesVisited = 1 To NumberFiles
'Open files, get path, file, tab name and cells
Dim PathFileOpen As String, NameFileOpen As String, NameTab As String, FileDir As String
Let PathFileOpen = ActiveSheet.Cells(RowNumber, "A").Text
Let NameFileOpen = ActiveSheet.Cells(RowNumber, "B").Text
Let NameTab = ActiveSheet.Cells(RowNumber, "C").Text
Dim N As Integer, Cell As String, NumberYears As String, FullLink As String
NumberYears = ActiveSheet.Cells("2", "B").Value
For N = 4 To NumberYears + 3
Cell = ActiveSheet.Cells(RowNumber, N).Text
FullLink = "(=)'" & PathFileOpen & "\[" & NameFileOpen & ".xlsm]" & NameTab & "'!" & Cell
ActiveSheet.Cells(RowNumber, N + 13).Value = FullLink
Next N
RowNumber = RowNumber + 1
Next FilesVisited
End If
ActiveSheet.Range("A1").CurrentRegion.Replace What:="(=)", Replacement:="=", _
LookAt:=xlPart, SearchOrder:=xlByRows, MatchCase:=False,
SearchFormat:=False, ReplaceFormat:=False
End Sub
The Macro RunAll
Sub runall()
Call Sheet1.RetrieveNumbers1
Call Sheet2.RetrieveNumbers2
End Sub
Clear Example of the file
Working file example
There's a fair amount wrong with your code. As #PGCodeRider said in his answer - have one procedure that runs on all sheets. His code has the loop within the procedure.
This code uses a separate procedure to cycle through the sheets and passes a reference to the sheet to the RetrieveNumbers procedure.
I've replaced all instances of ActiveSheet (reference to the ActiveSheet) with wrkSht (reference to the sheet that the RunAllSheets procedure passes).
All Dims have been moved to the top of the code as they only need declaring once and not on each loop (you change the value the variables hold on each loop, but no need to declare them again).
Sub RunOnAllSheets()
Dim wrkSht As Worksheet
For Each wrkSht In ThisWorkbook.Worksheets
Select Case wrkSht.Name
Case "Sheet1", "Sheet2"
'Do nothing.
Case Else
'For all other sheets execute the RetrieveNumbers procedure
'and pass the wrkSht variable to it.
RetrieveNumbers wrkSht
End Select
Next wrkSht
End Sub
Sub RetrieveNumbers(wrkSht As Worksheet)
Dim NumberFiles As Integer, FilesVisited As Integer, RowNumber As Integer
'You only need to declare these once.
Dim PathFileOpen As String, NameFileOpen As String, NameTab As String, FileDir As String
Dim N As Integer, Cell As String, NumberYears As String, FullLink As String
'No need to use 'LET' it's a left-over from the days of Sinclair Basic
'ok, maybe not.... but it's an old way of doing it.
NumberFiles = wrkSht.Cells("2", "A").Value
FilesVisited = 0 'start from 0
RowNumber = 4 'start from column B
If NumberFiles > 30 Then
MsgBox "Don't try to retrieve numbers from more than 30 files at a time!"
Else
For FilesVisited = 1 To NumberFiles
'Open files, get path, file, tab name and cells
PathFileOpen = wrkSht.Cells(RowNumber, "A").Text
NameFileOpen = wrkSht.Cells(RowNumber, "B").Text
NameTab = wrkSht.Cells(RowNumber, "C").Text
NumberYears = wrkSht.Cells("2", "B").Value
For N = 4 To NumberYears + 3
Cell = wrkSht.Cells(RowNumber, N).Text
FullLink = "(=)'" & PathFileOpen & "\[" & NameFileOpen & ".xlsm]" & NameTab & "'!" & Cell
wrkSht.Cells(RowNumber, N + 13).Value = FullLink
Next N
RowNumber = RowNumber + 1
Next FilesVisited
End If
wrkSht.Range("A1").CurrentRegion.Replace What:="(=)", Replacement:="=", _
LookAt:=xlPart, SearchOrder:=xlByRows, MatchCase:=False, _
SearchFormat:=False, ReplaceFormat:=False
End Sub
Edit, after accepted as answer:
This method only references the sheet twice. Once to pull the link info, and once more to put the final formula back on the sheet.
Sub RunOnAllSheets()
Dim wrkSht As Worksheet
For Each wrkSht In ThisWorkbook.Worksheets
'Have removed the Select Case statement so it looks at all sheets.
RetrieveNumbers wrkSht
Next wrkSht
End Sub
Sub RetrieveNumbers(wrkSht As Worksheet)
Dim NumberFiles As Long, FilesVisited As Long
Dim vCellValues As Variant, vLinkValues() As Variant
Dim FullPath As String
Dim x As Long
With wrkSht
'Get the last row number that contains data in column N.
NumberFiles = .Cells(.Rows.Count, "N").End(xlUp).Row
If NumberFiles - 3 > 30 Then
MsgBox "Don't try to retrieve numbers from more than 30 files at a time!"
Else
'Pass the cell values to an array.
vCellValues = .Range("A4:C4")
'Create the full path excluding the cell reference.
FullPath = "='" & vCellValues(1, 1) & "[" & vCellValues(1, 2) & "]" & vCellValues(1, 3) & "'!"
'Create an array of full path & cell references.
ReDim vLinkValues(1 To NumberFiles - 3) 'Set the array size.
For x = 1 To NumberFiles - 3
vLinkValues(x) = FullPath & .Cells(x + 3, "N")
Next x
'Paste the array back to the sheet.
.Range(.Cells(4, "N"), .Cells(NumberFiles, "N")).Formula = vLinkValues
End If
End With
End Sub
Note: This assumes your path is just in cell A4:C4, as indicated by the code vCellValues = .Range("A4:C4") (I'm not sure this is the case now).
If your paths are on each row matching the cell values you'll need to:
Change vCellValues = .Range("A4:C4") to
vCellValues = .Range(.Cells(4, 1), .Cells(NumberFiles, 3))
Remove the FullPath='.... line.
Change vLinkValues(x) = FullPath & .Cells(x + 3, "N") to
vLinkValues(x) = "='" & vCellValues(x, 1) & "[" & vCellValues(x, 2) & "]" & vCellValues(x, 3) & "'!" & .Cells(x + 3, "N")
Try running a loop through all of the sheets in the workbook? Also make sure you run this in a module in your vba editor. Not your sheet code.
Sub RetrieveNumbers1()
Dim WS As Worksheet
'loop that goes through all sheets in your workbook. Where you used to have
'activesheet, I changed to ws
For Each WS In ThisWorkbook.Sheets
Dim NumberFiles As Integer, FilesVisited As Integer, RowNumber As Integer
Let NumberFiles = WS.Cells("2", "A").Value
Let FilesVisited = 0 'start from 0
Let RowNumber = 4 'start from column B
If NumberFiles > 30 Then
MsgBox "Don't try to retrieve numbers from more than 30 files at a time!"
Else
For FilesVisited = 1 To NumberFiles
'Open files, get path, file, tab name and cells
Dim PathFileOpen As String, NameFileOpen As String, NameTab As String, FileDir As String
Let PathFileOpen = WS.Cells(RowNumber, "A").Text
Let NameFileOpen = WS.Cells(RowNumber, "B").Text
Let NameTab = WS.Cells(RowNumber, "C").Text
Dim N As Integer, Cell As String, NumberYears As String, FullLink As String
NumberYears = WS.Cells("2", "B").Value
For N = 4 To NumberYears + 3
Cell = WS.Cells(RowNumber, N).Text
FullLink = "(=)'" & PathFileOpen & "\[" & NameFileOpen & ".xlsm]" & NameTab & "'!" & Cell
WS.Cells(RowNumber, N + 13).Value = FullLink
Next N
RowNumber = RowNumber + 1
Next FilesVisited
End If
ws.Range("A1").CurrentRegion.Replace What:="(=)", Replacement:="=", _
LookAt:=xlPart, SearchOrder:=xlByRows, MatchCase:=False,
SearchFormat:=False, ReplaceFormat:=False
'restarts on the next ws
Next WS
End Sub
Hopefully the title is clear. I am trying to search through multiple tables on a single sheet. The information I am looking for is the same for all of the tables, just that the corresponding column is located in different spots (e.g. in one table the column I want to search is in I, while for another table it could be in O.) which makes it a bit more challenging for me.
I want to search through each column that has the same title (Load Number) and depending on its value, copy that entire row over to a sheet that corresponds with that value.
Below is what I have so far in VBA as well as a picture to hopefully clarify my issue.
Any help/advice is appreciated!
http://imgur.com/a/e9DyH
Sub Load_Number_Finder()
Dim ws As Worksheet
Dim i As Integer
Dim j As Integer
j = 1
Set ws = Sheets.Add(After:=Sheets("Master"))
ws.Name = ("Test Load " & j)
i = 1
Sheets("Master").Select
For Each cell In Sheets("Master").Range("M:M")
If cell.Value = "1" Then
j = 1
'Set WS = Sheets.Add(After:=Sheets("Master"))
'WS.Name = ("Test Load " & j)
matchRow = cell.Row
Rows(matchRow & ":" & matchRow).Select
Selection.Copy
Sheets("Test Load " & j).Select
ActiveSheet.Rows(i).Select
ActiveSheet.Paste
Sheets("Master").Select
i = i + 1
ElseIf cell.Value = "" Then
' 2, 3, 4, 5, cases
Else
' Something needs to go here to catch when it doesnt have a load number on it yet
End If
' Err_Execute:
' MsgBox "An error occurred."
Next
End Sub
Try this function. This should work for you. Let me know what the results are with your sheet. I made a mock up sheet and tested it, it worked. I can make changes if this is not exactly what you are looking for.
Option Explicit
Sub copyPaste()
Dim rowCount, row_ix, temp, i As Integer
Dim TD_COL_IX As Integer
Dim td_value As String
Dim td_values() As String
rowCount = Worksheets("Master").Cells(Rows.Count, "A").End(xlUp).Row
For row_ix = 1 To rowCount
temp = isNewTable(CInt(row_ix))
If temp > 0 Then
TD_COL_IX = temp
ElseIf TD_COL_IX > 0 Then
td_value = Worksheets("Master").Cells(row_ix, TD_COL_IX)
If Not td_value = "" Then
td_values = Split(td_value, " ")
For i = 0 To UBound(td_values)
If Not sheetExists("Test Load " & td_values(i)) Then
Sheets.Add.Name = "Test Load " & td_values(i)
End If
If Worksheets("Test Load " & td_values(i)).Cells(1, 1).Value = "" Then
Worksheets("Master").Range(Worksheets("Master").Cells(row_ix, 1), Worksheets("Master").Cells(row_ix, TD_COL_IX - 1)).Copy _
Destination:=Worksheets("Test Load " & td_values(i)).Cells(1, 1)
Else
Dim rowCount_pasteSheet As Integer
rowCount_pasteSheet = Worksheets("Test Load " & td_values(i)).Cells(Rows.Count, "A").End(xlUp).Row
Worksheets("Master").Range(Worksheets("Master").Cells(row_ix, 1), Worksheets("Master").Cells(row_ix, TD_COL_IX - 1)).Copy _
Destination:=Worksheets("Test Load " & td_values(i)).Cells(rowCount_pasteSheet + 1, 1)
End If
Next i
End If
End If
Next row_ix
End Sub
Function isNewTable(row_ix As Integer) As Integer
Dim colCount, col_ix As Integer
colCount = Worksheets("Master").Cells(row_ix, Columns.Count).End(xlToLeft).Column
For col_ix = 1 To colCount
If Not IsError(Worksheets("Master").Cells(row_ix, col_ix).Value) Then
If Worksheets("Master").Cells(row_ix, col_ix).Value = "LD #" Then
isNewTable = col_ix
Exit Function
End If
End If
Next col_ix
isNewTable = 0
End Function
' ####################################################
' sheetExists(sheetToFind As String) As Boolean
'
' Returns true if the sheet exists, False otherwise
' ####################################################
Public Function sheetExists(sheetToFind As String) As Boolean
Dim sheet As Worksheet
sheetExists = False
For Each sheet In Worksheets
If sheetToFind = sheet.Name Then
sheetExists = True
Exit Function
End If
Next sheet
End Function
the code below works 100%. It scans for a match in Column B and copies and renames a group of cells when a match is found. However the is a line For lRow = Sheets("HR-Calc").Cells(Cells.Rows.count, "b").End(xlUp).Row To 7 Step -1
Where the step -1 will scan row by row from the bottom of the sheet until a match is found. It would be much easier if the step was set to End.(xlUp) instead of -1. searching every row is overkill because of how the data is set up End.(xlUp) would massive cut down the run time.
Is something like this possible?
Sub Fill_CB_Calc()
M_Start:
Application.ScreenUpdating = True
Sheets("summary").Activate
d_input = Application.InputBox("select first cell in data column", "Column Data Check", Default:="", Type:=8).Address(ReferenceStyle:=xlA1, RowAbsolute:=True, ColumnAbsolute:=False)
data_col = Left(d_input, InStr(2, d_input, "$") - 1)
data_row = Right(d_input, Len(d_input) - InStr(2, d_input, "$"))
Application.ScreenUpdating = False
Sheets("summary").Activate
Range(d_input).End(xlDown).Select
data_last = ActiveCell.Row
If IsEmpty(Range(data_col & data_row + 1)) = True Then
data_last = data_row
Else
End If
For j = data_row To data_last
CBtype = Sheets("summary").Range(data_col & j)
Sheets("HR-Calc").Activate
For lRow = Sheets("HR-Calc").Cells(Cells.Rows.count, "b").End(xlUp).Row To 7 Step -1
If Sheets("HR-Calc").Cells(lRow, "b") = CBtype Then
CBend = Sheets("HR-Calc").Range("C" & lRow).End(xlDown).Row + 1
Sheets("HR-Calc").Rows(lRow & ":" & CBend).Copy
CBstart = Sheets("HR-Calc").Range("c50000").End(xlUp).Row + 2
ActiveWindow.ScrollRow = CBstart - 8
Sheets("HR-Calc").Range("A" & CBstart).Insert Shift:=xlDown
CBold = Right(Range("c" & CBstart), Len(Range("C" & CBstart)) - 2)
box_name = Sheets("summary").Range(data_col & j).Offset(0, -10)
CBnew = Right(box_name, Len(box_name) - 2) & "-" ' <--this is custom and can be changed based on CB naming structure
If CBnew = "" Or vbCancel Then
End If
CBend2 = Range("c50000").End(xlUp).Row - 2
Range("C" & CBstart + 1 & ":" & "C" & CBend2).Select
Selection.Replace What:=CBold & "-", Replacement:=CBnew, LookAt:=xlPart, _
SearchOrder:=xlByRows, MatchCase:=False, SearchFormat:=False, _
ReplaceFormat:=False
Range("C" & CBstart).FormulaR1C1 = "CB" & Left(CBnew, Len(CBnew) - 1)
GoTo M_Start2
Else
End If
Next lRow
M_Start2:
Next j
YN_result = MsgBox("Fill info for another block/inverter?", vbYesNo + vbExclamation)
If YN_result = vbYes Then GoTo M_Start
If YN_result = vbNo Then GoTo jumpout
jumpout:
' Sheets("summary").Range(d_input).Select
Application.ScreenUpdating = True
End Sub
I'm not sure if this will help but I've had a great performance increase with pulling the entire range you need to loop through into a variant array and then looping through the array. If I need to loop through large data sets, this method has worked out well.
Dim varArray as Variant
varArray = Range(....) 'set varArray to the range you're looping through
For y = 1 to uBound(varArray,1) 'loops through rows of the array
'code for each row here
'to loop through individual columns in that row, throw in another loop
For x = 1 to uBound(varArray, 2) 'loop through columns of array
'code here
Next x
Next y
You can also define the column indexes prior to executing the loop. Then you only need to execute the you need to pull those directly in the loop.
'prior to executing the loop, define the column index of what you need to look at
Dim colRevenue as Integer
colRevenue = 5 'or a find function that searches for a header named "Revenue"
Dim varArray as Variant
varArray = Range(....) 'set varArray to the range you're looping through
For y = 1 to uBound(varArray,1) 'loops through rows of the array
tmpRevenue = CDbl(varArray(y, colRevenue))
Next y
Hope this helps.
Look at doing a .find from the bottom up.
Perform a FIND, within vba, from the bottom of a range up
That will eliminate the need to do the for loop from the last row to the first occurrence of the value you want to locate.
I get output from a program imported to Excel in the following format:
Item 1
1 10
2 10
3 20
5 20
8 30
13 30
Item 2
1 40
2 40
3 50
5 50
8 60
13 60
Item 3
1 50
2 50
3 40
5 40
8 30
13 30
Now, I want to create a table where the values for each item is placed next to each other as below:
Item 1 Item 2 Item 3
1 10 40 50
2 10 40 50
3 20 50 40
5 20 50 40
8 30 60 30
13 30 60 30
I can think of ways to do this using formulas with a combination of INDIRECT other functions, but I can see right away that it will be a huge pain. Is there a clever way of doing this?
My approach would be something like this:
=VLOOKUP($A6;indirect("A"&(6+G$5*$X$4):"D"&(30+G$5*$X$4));4;FALSE)
where my first lookup table is from A6:D30, the second from A32:D56. X4 contains the value 26 which is the number of rows for each Item, and G5:AA5 is 0, 1, 2 ....
I would place this besides the Item 1 list and drag it sideways and downwards. I think the procedure should work, but I get syntax error.
I don't have much experience writing VBA, but I'm capable of reading and understanding it.
UPDATE:
At Siddharth's request:
Can you check out this.
It assumes a fixed format as it is shown in your example.
It can be made dynamic, but then you need to customize the code.
Option Explicit
Sub test()
Dim oCollection As Collection
Dim oDict As Variant
Dim oItem As Object
Dim iCnt As Integer
Dim iCnt_B As Integer
Dim iCnt_items As Integer
Dim iCnt_records As Integer
Dim iID As Integer
Dim iValue As Integer
Dim strKey As Variant
'Nr of items
iCnt_items = 3
'Records per item
iCnt_records = 6
'This dictionary will store the items
Set oCollection = New Collection
'Store dictionaries in collection
For iCnt = 0 To iCnt_items - 1
Set oDict = CreateObject("Scripting.Dictionary")
For iCnt_B = 1 To iCnt_records
iID = ThisWorkbook.Sheets(1).Cells((iCnt * (iCnt_records) + (iCnt + 1) + iCnt_B), 1).Value
Debug.Print iID
iValue = ThisWorkbook.Sheets(1).Cells((iCnt * (iCnt_records) + (iCnt + 1) + iCnt_B), 2).Value
Debug.Print iValue
oDict.Add iID, iValue
Next iCnt_B
oCollection.Add oDict, "item " & iCnt
Next iCnt
'Write collection to sheet
iCnt = 0
For Each oItem In oCollection
iCnt = iCnt + 1
ThisWorkbook.Sheets(2).Cells(1, 1 + iCnt).Value = "item " & iCnt
iCnt_B = 0
For Each strKey In oItem.keys
iCnt_B = iCnt_B + 1
ThisWorkbook.Sheets(2).Cells(1 + iCnt_B, 1).Value = strKey
ThisWorkbook.Sheets(2).Cells(1 + iCnt_B, 1 + iCnt).Value = oItem(strKey)
Next
Next oItem
End Sub
Edit: sorry for interrupting the conversation -> I didn't follow up the comment section while programming.
Sidenote:
If the ranges you work with are dynamic, I would go with a dictionary.
The reason why I'm saying this is because the dictionary object uses indexing on its records.
The key - pair structure being: ID, value
allows you to directly access the values corresponding the given ID.
In your example you are working with a clear ID - value structure.
Using numeric id's would actually be the fastest.
Since I already worked on this... Here is another way..
Assumptions:
Data starts at row 5 in Sheet1
Output will be generated in Sheet2
Code:
The below code uses Collections and Formulas to achieve what you want.
Sub Sample()
Dim wsInput As Worksheet, wsOutput As Worksheet
Dim ColItems As New Collection, ColSubItems As New Collection
Dim lRow As Long, i As Long, N As Long
Dim itm
Set wsInput = ThisWorkbook.Sheets("Sheet1")
Set wsOutput = ThisWorkbook.Sheets("Sheet2")
With wsInput
lRow = .Range("B" & .Rows.Count).End(xlUp).Row
.Columns(1).Insert
.Range("A5:A" & lRow).Formula = "=IF(ISERROR(SEARCH(""Item"",B5,1)),A4,B5)"
For i = 5 To lRow
On Error Resume Next
If InStr(1, .Range("B" & i).Value, "item", vbTextCompare) Then
ColItems.Add .Range("B" & i).Value, CStr(.Range("B" & i).Value)
Else
ColSubItems.Add .Range("B" & i).Value, CStr(.Range("B" & i).Value)
End If
On Error GoTo 0
Next i
End With
With wsOutput
.Cells.ClearContents
N = 2
'~~> Create Header in Row 1
For Each itm In ColItems
.Cells(1, N).Value = itm
N = N + 1
Next
N = 2
'~~> Create headers in Col 1
For Each itm In ColSubItems
.Cells(N, 1).Value = itm
N = N + 1
Next
lRow = .Range("A" & .Rows.Count).End(xlUp).Row
lcol = .Cells(1, .Columns.Count).End(xlToLeft).Column
j = 2
For i = 2 To lcol
.Range(.Cells(j, i), .Cells(lRow, i)).Formula = "=SUMIFS(" & _
wsInput.Name & _
"!C:C," & wsInput.Name & _
"!A:A," & .Name & _
"!$" & _
Split(.Cells(, i).Address, "$")(1) & _
"$1," & _
wsInput.Name & _
"!B:B," & _
.Name & _
"!A:A)"
Next i
.Rows("1:" & lRow).Value = .Rows("1:" & lRow).Value
End With
wsInput.Columns(1).Delete
End Sub
Screenshot:
This is what I have tried.
Sheet 1 contains the data. The result is generated in Sheet 2
Sub createTable()
Dim counter As Integer
Dim countRow As Integer
Dim flag As Boolean
Dim cellAddress As String
flag = True
countRow = 2
counter = 2
ThisWorkbook.Sheets("Sheet1").Activate
For Each cell In Range("a:a")
If counter = 2 Then
If InStr(1, cell.Value, "Item") Then
ThisWorkbook.Sheets("Sheet2").Activate
ActiveSheet.Cells(1, counter).Value = cell.Value
firstItem = cell.Value
counter = counter + 1
End If
Else
ThisWorkbook.Sheets("Sheet2").Activate
If InStr(1, cell.Value, "Item") Then
ThisWorkbook.Sheets("Sheet2").Activate
ActiveSheet.Cells(1, counter).Value = cell.Value
counter = counter + 1
flag = False
End If
If flag = True Then
Cells(cell.Row, cell.Column) = cell.Value
End If
End If
If cell.Value = vbNullString Then
Exit For
End If
Next cell
ThisWorkbook.Sheets("Sheet1").Activate
Application.CutCopyMode = False
Dim counteradd As Integer
counteradd = 2
For Each cell In Range("a:a")
v = cell.Value
If InStr(1, cell.Value, "Item") Then
If cell.Offset(1, 1).Select <> vbNullString Then
Range(Selection, Selection.End(xlDown)).Select
Selection.Copy
Sheets("Sheet2").Select
Cells(2, counteradd).Select
ActiveSheet.Paste
Application.CutCopyMode = False
counteradd = counteradd + 1
ThisWorkbook.Sheets("Sheet1").Activate
End If
End If
Next cell
End Sub