I am working with a VB macro. Essentially what I am trying to do is for the macros to read the input and first determine whether or not a cells ID number matches the one in the row. Example: If row 1 has an ID of 1122 and rows 2,3,4 and 5 all match, I want the macro to read that and create a count in the NbrOfA cell. Once it realizes that there is not an ID match it moves on to the next ID and looks for matches of that ID number and continues to create a count. While it is doing this, I also need it to read from another column that has specific strings such as "open", "closed" ect. read that input, and create a separate row titled NbrofOpenA. Once it runs out of data, I then want to have a singular cell that shows the number of actions (NbrOfA) that match the ID number as well as the number of open actions (NbrOfOpenA).
Currently I receive the error: “compile error: sub or function not defined” highlighting the Set Cell(Sheet2.Cells(FirstRowOfI, 23) = NbrOfA
Attached in the excel sheet attached it shows 2 cells deleted. They will not actually be deleted, just wanted to give an idea of what I was looking for
Sub ACount()
Dim FirstRowofI
Dim NbrOfA as Integer
Dim NbrOfOpenA as Integer
Row = 2
Set FirstRowofI = (Sheet2.Cells.Range(Row, 14))
NbrOfA = 0
NbrOfOpenA = 0
If (Sheet2.Cells(Row, 14).Value <> "") Then
NbrOfA = 1
If (Sheet2.Cells(Row, 22) <> "Closed") Then
NbrOfOpenA = 1
Set Row = FirstRowofI
Row = Row + 1
Do While (Sheet2.Cells(Row, 14) = (Sheet2.Cells(FirstRowofI, 14)))
NbrOfOpenA = NbrOfOpenA + 1
If (Sheet2.Cells(Row, 22) <> "Closed" Then
NbrOfOpenA = NbrOfOpenA + 1
Range(Row).EntireRow.Delete
Return
End If
Set Cell(Sheet2.Cells(FirstRowofI, 23)) = NbrOfA
Set Cell(Sheet2.Cells(FirstRowofI, 24)) = NbrOfOpenA
Loop
End Sub
[1
Do you need VBA? You can easily achieve what you're looking for with formulas, heck even a Pivot Table! Here's an example with formulas:
Related
Background: I'm relatively new to VBA, but I see the value in becoming more comfortable using the skillset.
Goal: Move unorganized data (srce) from one spreadsheet into a different more structured spreadsheet (dest) that can later be uploaded into a software application. I have ~500 of these spreadsheets that need to be migrated, so there is an immense amount of time that could be saved by automating this.
Data: The data is a history of truck maintenance. Periodic maintenance takes place throughout the year with multiple services often performed during a single maintenance routine. Under each routine maintenance, there is a date, # of hours on the vehicle when maintenance is performed, and the type of service performed (consistently column "A").
Data Structure: All service types are contained in column A. Starting in column C & D, I have all of the dates the services performed in 2021 from C11:C34. The # of hours the vehicle has operated at the time of maintenance are contained in cells D11:D34. Subsequently, the dates and # of hours for each maintenance in 2022 are contained in columns E and F.
Challenge: While moving down the rows and before switching to the next column, I need to:
Check for repeat dates
Copy the type of services performed at that date
Paste all of those services performed under a single line item in my destination spreadsheet starting in column T and ending in Column Y (In case ~8 services are performed under a single maintenance routine.)
Question:
How can I complete the above challenge without duplicating entries and keep all services performed on the same date within a single line in my dest spreadsheet?
Below is my code thus far (I've left a comment in the section that is where I intended to craft an answer to my dilemma):
Sub VehicleDataExport()
Application.ScreenUpdating = False
'Set reference cell for output called "dest"
Set dest = Sheets("Dest").Range("A2")
'Initialize counter for destination for how many rows down we are so far
dindx = 0
'Set reference cell for source data called "srce"
Set srce = Sheets("Srce").Range("C11")
'Set reference cell for source for how many columns over we are
cindx = 0
'Set the service type index
Set serviceindex = Sheets("Srce").Range("A11")
'Collect name, vin, and in-service date
vehicle_name = Sheets("Srce").Range("A1")
vehicle_vin = Sheets("Srce").Range("B7")
started_at = Sheets("Srce").Range("B8")
'Go over from anchor column while not empty
While srce.Offset(-1, cindx) <> ""
'set row index so that it can restart everytime you switch columns
rindx = 0
'Cycle down through rows until an "DATE" is found
While srce.Offset(rindx, cindx) <> "DATE"
'Set counter for duplicate index so the program will move through the data while looking for duplicate DATES
duplicateindx = 0
'If statement to determine if something is in the cell - 2nd header row
If srce.Offset(rindx, cindx) > 0 Then
'True Case: copy the date, hours, and service type
service_date = srce.Offset(rindx, cindx)
service_hours = srce.Offset(rindx, cindx + 1)
service_type = serviceindex.Offset(rindx, 0)
meter_void = ""
'Properly label and account for Dot Inspection
If service_type = "DOT Inspection" Then
service_hours = 0
meter_void = True
'secondary_meter_value needs to be 0
'secondary_meter_void needs true
End If
'CHECK FOR DUPLICATE DATES AND COPY THEM TO A SINGLE ROW IN THE DESTINATION
'Paste all of the numbers into a destination row
dest.Offset(dindx, 0) = vehicle_name
dest.Offset(dindx, 1) = vehicle_vin
dest.Offset(dindx, 2) = started_at
'Variable inputs
dest.Offset(dindx, 3) = service_date
dest.Offset(dindx, 13) = service_hours
dest.Offset(dindx, 17) = service_type
dest.Offset(dindx, 14) = meter_void
'Add to both the row and destination indexes
rindx = rindx + 1
dindx = dindx + 1
'If no inspection is found, move down one row
Else: rindx = rindx + 1
'End if statement
End If
'end column specific while loop
Wend
'add two to the column index - account for both the date and hours column
cindx = cindx + 2
'End the initial while loop
Wend
Application.ScreenUpdating = True
End Sub
This really sounds like a job for PowerQuery but if I was to tackle it with VBA I'd use a Scripting.Dictionary. I would also write a small data class that includes all of your service types as Boolean.
I don't fully understand your data structure but some pseudo code might look like this:
Const SRVCECOL As Long = 1
Const HOURSCOL As Long = 2
Function ExtractTransformServiceData(src As Workbook) As Object
Dim dict As Object
Set dict = CreateObject("Scripting.Dictionary")
Dim svcDates As Range
Set svcDates = src.Sheets(1).Range("C11:C34")
Dim svcDate As Range
For Each svcDate in svcDates
Dim tsd As TruckServiceData
If dict.Exists(svcDate.Value) Then
Set tsd = dict.Item(svcDate.Value)
Else
Set tsd = New TruckServiceData
dict.Add svcDate.Value, tsd
End If
tsd.SetHoursForService( _
svcDate.Offset(0, SRVCECOL).Value, _
svcDate.Offset(0, HOURSCOL).Value)
Next svcDate
Set ExtractTransformServiceData = dict
End Sub
I would briefly like to start off with I have never touched VBA let alone excel macros until a couple days ago.
I need to transfer and convert data of 1000 rows (4 columns) from one sheet (Sheet 1) to another (Sheet 2).
A quick description of what I'm given, each row is an object, I have 4 columns.
The first one (column) is the Object ID, the second one is the Object name, the third one explain the what of the object and the final column explains the how. This is a very simplified version as explaining the entire project would be complicated.
On the second sheet, I have 6000 rows all with the object's IDs and Names however the What and How are missing.
My goal is to take the what and how of an object from this sheet, convert the wording to a form in which the second sheet accepts and make sure it gets added to the proper ID.
I have tried multiple code samples I have found online to try and select and organize into tables (arrays) the information from the first sheet, I failed miserably.
Converting the What and How
The second sheet has a very strict format in which everything can be written. In my mind (Lua is my main language), I would have a dictionary or table with all possible ways of the How/What could be written on the first sheet and checking each one to see if they match then change it to the corresponding sheet 2 format. Let me show you. (This is the what. There'd be another table for the how which I'll show below)
local MType = {
["Industrial"] = {"MILPRO : Industrial","Industrial"};
["Public Saftey"] = {"MILPRO : Public Saftey", "Public Saftey"};
["Military"] = {"MILPRO : Military","Military"};
["Paddling"] = {"Recreation : Paddling","Paddling"};
["Sporting Goods"] = {"Recreation : Sporting Goods","Sporting Goods"};
["Outdoor"] = {"Recreation : Outdoor", "Outdoor"};
["Hook & Bullet"] = {"Recreation : Hook & Bullet", "Hook & Bullet"};
["Marine"] = {"Recreation : Marine","Marine","Marina / Lodge"};
["Sailing"] = {"Recreation : Sailing","Sailing"};
["Unknown"] = {"UNKNOWN"}
}
local CType = {
["Multi-Door"] = {"Multi-Door","Multi-door"};
["Dealer & Distributor"] = {"Distributor","Dealer & Distributor"};
["Independant Specialty"] = {"Independant Specialty","Specialty"};
["OEM"] = {"OEM","OEM - VAR"};
["Internal"] = {"Internal","Sales Agency","Repairs Facility"};
["Rental"] = {"Rental / Outfitter", "Rental"};
["End User"] = {"End User"};
["Institution"] = {"Institution","Government Direct"};
["Unknown"] = {"UNKNOWN"}
}
The first position in each table (table = the curly brackets) is the format in which the second sheet accepts. The rest in the tables is how they might be written in the first sheet. (This is how I imagine this would go down. Idk the functions and limits of VBA)
Matching the Information to the Proper IDs
Every object has an ID 6 characters long ranging from 000100 to 999999. When taking information from the first sheet, I need to make sure it gets placed back in the row with the right ID in the second sheet (Note there's 1000 rows on the first sheet and 6000 on the second sheet).
Final notes: The IDs are stored as text and not numbers (If they need to change lmk). Both sheet's information are within tables. I'll probably be using this method for other similar sheet 1s. Any conversions (for the what and how) that fail should be marked down as Unknown.
A Visual Representation of the 2 Sheets
Sheet 1 Format
Sheet 2 format
We can create a 2 dimensional array to hold all the pairs of one dictionary, then check against each element using a For..Next loop.
Sub transcribe()
On Error GoTo Handler
Application.ScreenUpdating = False
Dim WS1 As Worksheet, WS2 As Worksheet
Dim ID1 As Range, ID2 As Range
'This is assuming youre working in Sheets 1 and 2
Set WS1 = Sheets(1)
Set WS2 = Sheets(2)
'This is assuming your tables are in these locations
Set ID1 = WS1.Range(WS1.Cells(1, 1), WS1.Cells(10, 1))
Set ID2 = WS2.Range(WS2.Cells(1, 1), WS2.Cells(20, 1))
Dim cellx As Range
Dim rowID1 As Integer
Dim FieldA As String, FieldB As String
Dim IDfound As Boolean
IDfound = True
Dim arrayA(1 To 10, 1) As String
arrayA(1, 0) = "MILPRO : Industrial"
arrayA(1, 1) = "Industrial"
arrayA(2, 0) = "MILPRO : Public Saftey"
arrayA(2, 1) = "Public Saftey"
'... etc. You have to complete this array with all the pairs of your dictionary of Field A
'array(X, 1) holds what you expect to find in table 1, and array(X, 0) holds what you want to write down in table 2.
Dim arrayB(1 To 9, 1) As String
arrayB(1, 0) = "Multi-Door"
arrayB(1, 1) = "Multi-Door"
arrayB(2, 0) = "Distribuitor"
arrayB(2, 1) = "Dealer & Distribuitor"
'... etc. You have to complete this array with all the pairs of your dictionary of Field B
'array(X, 1) holds what you expect to find in table 1, and array(X, 0) holds what you want to write down in table 2.
'Now we sweep each cell in Table 2
For Each cellx In ID2.Cells
'And we search its ID for a match in Table 1.
rowID1 = Application.Match(cellx.Value, ID1, 0)
If IDfound = True Then
'We then write down the values of Field A and B in the found row
FieldA = ID1.Resize(1).Offset(rowID1 - 1, 2).Value
FieldB = ID1.Resize(1).Offset(rowID1 - 1, 3).Value
'And we call a function (see below) to correct their values
cellx.Offset(0, 2).Value = corrected(FieldA, arrayA, 10)
cellx.Offset(0, 3).Value = corrected(FieldB, arrayB, 9)
Else
cellx.Offset(0, 2).Value = "ID not found"
cellx.Offset(0, 3).Value = "ID not found"
IDfound = True
End If
Next
Application.ScreenUpdating = True
Exit Sub
Handler:
IDfound = False
Resume Next
End Sub
Function corrected(Field As String, arrayX As Variant, UB As Integer) As String
'This is the dictionary-like function
Dim found As Boolean
'We sweep each element in the dictionary array until we find a match
For i = 1 To UB
If Field = arrayX(i, 1) Then
corrected = arrayX(i, 0)
found = True
Exit Function
Exit For
End If
Next
'If no match was found, we will write that down in the result
If found = False Then
corrected = Field & " - Not found in dictionary"
Exit Function
End If
'This code should never be reached, its just for foolproofing
corrected = "Error"
End Function
I am trying to store ranges in range variables startID, endID, and endDest, but I keep getting runtime error 1004 "Method Range of object _Global failed."
I have a list of loans on the Control Page of my workbook, and they all have multiple lines (anywhere from 2 to 6 lines) like this:
A | B | C
loan 1 | stuff | 1.0
loan 1 | stuff | 1.1
loan 2 | stuff | 2.0
loan 2 | stuff | 2.1
loan 2 | stuff | 2.2
The whole purpose is to move the loan where the activecell is down to the bottom of the list. Here's what I'm having trouble with (so far): Say the activecell is B2 in the little bulleted list makeshift worksheet above--the row of the activecell along with a fixed column index of 3 indicates that I'm on line 1.1. I then "fix" 1.1 to 1 and store that integer in currentID. Now I can Find the first occurrence of currentID in Column C, then store the address of the range where currentID appears in the range variable startID. In this case, I should be storing C1 in startID.
I have read that Find returns a range. So I tried Set startID = .Range("C:C").Find(what:=currentID), but that just stores the currentID of 1 in startID. So then I tried Set startID = Range(.Range("C:C").Find(what:=currentID)) (which is what appears below in my code block), but that causes the runtime error. I have also tried adding and removing periods in case I was messing up the With statement, but that didn't help. Any thoughts?
If there is some obvious thing I'm missing, I apologize. I have been using VBA for about three weeks now, so I typically have to google every single thing I need until I start remembering recurring themes.
Sub ExpireLoan()
With Workbooks("1908 AUS IC Loans Recon.xlsm").Worksheets("Control Page")
Dim currentID As Integer
Dim startID As Range
Dim endID As Range
Dim endDest As Range
Dim rowCount As Integer
'find the current loan range
currentID = Fix(.Cells(ActiveCell.Row, 3).Value)
Set startID = Range(.Range("C:C").Find(what:=currentID))
Set endID = Range(.Range("C:C").Find(what:=currentID + 1))
Set endID = Range(endID.Offset(-1, 0))
'find the last row
Set endDest = .Range("A7").End(xlDown)
Set endDest = endDest.Offset(-1, 0)
'copy the current loan and paste into the end of the table
rowCount = .Range(startID, endID).Count
.Range(startID, endID).EntireRow.Copy
.endDest.EntireRow.PasteSpecial
'set bottom border at new end of the table
endDest.Select
ActiveCell.Offset(0, 0).Range("A1:AF1").Borders(xlEdgeBottom).LineStyle = xlDouble
'delete rows from loan's original position above
.Range(startID, endID).EntireRow.Delete
'set sort ID
ActiveCell.Offset(0, 2).Value = Fix(ActiveCell.Offset(-1, 2).Value) + 1
For i = 0 To rowCount - 2
ActiveCell.Offset(i + 1, 2).Value = ActiveCell.Offset(i, 2).Value + 0.1
Next
'delete loanTypeCode
ActiveCell.Offset(0, 1).ClearContents
For i = 1 To rowCount - 1
ActiveCell.Offset(i, 1).ClearContents
Next
'fix sortID
Set sortIdRange = Range(startID, startID.End(xlDown))
Call subtractOneFromCells
'make it red
ActiveCell.Offset(0, 0).Range("A1:AF1").Interior.TintAndShade = 0.399975585192419
For i = 1 To rowCount - 1
ActiveCell.Offset(i, 17).Range("A1:P1").Interior.TintAndShade = 0.399975585192419
Next
End With
End Sub
In my experience .Find can act a little weird when looking for numbers. Using xlWhole usually fixes it for me and it also did when I tried it on your sample data. Otherwise, if you're looking for the currentID = 3 for example, .Find would return the fist cell that has a 3 in it as a sub-string. That means a cell with the value 1.3 could be returned as the first occurrence of 3. Try this line:
Set startID = .Range("C:C").Find(What:=currentID, LookAt:=xlWhole)
To debug this you might use F8 to step through the code line by line and use Debug.Print currentID and Debug.Print startID.Address to see what is happening through the immediate window.
A tip for shorter code (not necessesarily always more readable) is the following: You can consolidate two lines like these:
Set endDest = .Range("A7").End(xlDown)
Set endDest = endDest.Offset(-1, 0)
into one line like this:
Set endDest = .Range("A7").End(xlDown).Offset(-1, 0)
Since your first line returns a Range object there is no need to store it in a variable inbetween steps.
On another note, regarding these two lines:
endDest.Select
ActiveCell.Offset(0, 0).Range("A1:AF1").Borders(xlEdgeBottom).LineStyle = xlDouble
First of all it is recommended to avoid using .Select whenever possible as it is described here: How to avoid using Select in Excel VBA. Secondly, that second line is hard to understand. Try using Resize like this instead:
ActiveCell.Resize(1, 32).Borders(xlEdgeBottom).LineStyle = xlDouble
I'm trying to get excel to put together a series of text strings that haven't been formatted systematically, so that they end up split into different rows on a data sheet.
I'm aware this might've been solved elsewhere so sorry for that but I'm struggling to describe the issue, and I can't post images on it but basically it's
Column 1 with a list of the entries, and
Column 2 with text strings that are spread over 2 or more rows
Is it possible to write some kind of formula or macro that would be able to check the first column and then stitch together all entries in the second column going down until it found a new entry in the first column? I've got a feeling it might be possible using some sort of loop thing with index functions, but I've no idea where to start even.
Thanks,
Mike
Mike give this a ty
Sub appendValues()
'The sub is designed to loop through code and when ever there is a null value and column a it will take the value of what is in column B and appended to the row above it and delete the row.
Dim row As Integer
row = 1
'This code starts with row one but this can be changed at will.
Do Until ThisWorkbook.Sheets("sheet1").Cells(row, 2).Value = ""
'loop statement is designed to continue to Loop until there is a null value inside of you the value in the second column.
If ThisWorkbook.Sheets("sheet1").Cells(row, 1).Value = "" Then
ThisWorkbook.Sheets("sheet1").Cells(row - 1, 2).Value = ThisWorkbook.Sheets("sheet1").Cells(row - 1, 2).Value & ThisWorkbook.Sheets("sheet1").Cells(row, 2).Value
Rows(row).Delete
Else
'else statement is needed because there is an implied looping by decreasing the total number of rows after the delete.
row = row + 1
End If
Loop
End Sub
Sub appendValues()
'The sub is designed to loop through code and when ever there is a null value and column a it will take the value of what is in column B and appended to the row above it and delete the row.
Dim row As Integer
row = 1
'This code starts with row one but this can be changed at will.
Do Until ThisWorkbook.Sheets("sheet1").Cells(row, 2).Value = ""
'loop statement is designed to continue to Loop until there is a null value inside of you the value in the second column.
If ThisWorkbook.Sheets("sheet1").Cells(row, 1).Value = "" Then
ThisWorkbook.Sheets("sheet1").Cells(row - 1, 2).Value = ThisWorkbook.Sheets("sheet1").Cells(row - 1, 2).Value & ThisWorkbook.Sheets("sheet1").Cells(row, 2).Value
Rows(row).Delete
Else
'else statement is needed because there is an implied looping by decreasing the total number of rows after the delete.
row = row + 1
End If
Loop
End Sub
I need to merge cells using a formula so that the cells only merge when cells on another tab are filled.
I have 2 tabs with the same amount of columns in each. I want cells a1-d1 to merge in tab 1 when cells a1-d1 in tab 2 are filled and for the value of d1 in tab 2 to be inputted into the newly merged cells in tab 1.
this is what I have:
Excel VBA Methods and Function (Excel Macros) overview
Since you want to change cells i do not believe that you can use a formula (even not a user defined one). Therefore i wrote an excel vba macro for your problem.
FirstRows(): Is the starting point. It loops over 10 rows and calls the other methods
CheckEmptyCellValues(curRow): This method checks for empty cells in tab2 (sheet 2 in excel)
MergeCells(curRow) takes the current row as a number (any integer from 1 to max amount of rows) and merges the cells from column 1 to 4 on Sheet 1 (the first sheet in excel)
Fully working demo tested with 4 columns and 10 rows
Sub FirstRows()
Sheets(1).Select
For curRow = 2 To 11
Merge = CheckEmptyCellValues(curRow)
If Merge = 4 Then
MergeCells (curRow)
cellValue = Sheets(2).Cells(curRow, 4).Value
Sheets(1).Cells(curRow, 1).Value = cellValue
End If
Next
End Sub
Sub MergeCells(curRow)
Sheets(1).Select
Range(Cells(curRow, 1), Cells(curRow, 4)).MergeCells = True
End Sub
Function CheckEmptyCellValues(curRow)
Merge = 0
Sheets(2).Select
For i = 1 To 4
If Len(Cells(curRow, i).Value) > 0 Then
Merge = Merge + 1
End If
Next
CheckEmptyCellValues = Merge
End Function
Below you can see the result. The values from sheet 2 haven been copied to sheet 1 (second image). In Sheet 1 the Cells in a row are merged (in row 2 from Cell A2 up to Cell D2 (A2-D2 is now just one cell) if in the first image (sheet 2) every cell (from column a to column d) in a row had a value.
Bugs in the modified code
There are a few things in the modifiend code that are not possible or could lead to a wrong understanding
Function CheckEmptyCellValues(curColumn)
Merge = 0
Sheets(2).Select
For i = A To d
If Len(Cells(curColumn, 11).Value) > 0 Then
Merge = Merge + 1
End If
Next
CheckEmptyCellValues = Merge
End Function
The line For i = A To d is not possible. If you want to use a loop you have to use numbers: For i = 1 To 4 this would repeat the code between For and Next4 times starting with 1
This line Cells(curColumn, 11).Value is technical correct but misleading. Excel uses the first value after (for the row-index and the second value for the column-index. Both values have to be a number: Cells(4,2).Value returns the Cell value from the 4th. row and the second Column (in the Excel Gui the Cell B4)
Try changing this line For i = A To d to this For i = 1 To 4 and see if that returns the wished result.
Bugs part 2
In your other modification you have some of the same bugs:
The loop For curColumn = A to d needs numbers instead of letters (unless A and d were a variable filled with a number but according to your code sample this is not the case
The line cellValue = Sheets(2).Cells(curColumn, d).Value has the same bug, if d is just the letter d and not something like d = 4 than you can not use it in a loop.
This is the code from your comment:
Sub FirstRows()
Sheets(1).Select
For curColumn = A To d
Merge = CheckEmptyCellValues(curColumn)
If Merge = d Then
MergeCells(curColumn)
cellValue = Sheets(2).Cells(curColumn, d).Value
Sheets(1).Cells(curColumn, d).Value = cellValue
End If
Next
End
Sub Sub MergeCells(curColumn)
Sheets(1).Select
Range(Cells(curColumn, 1), Cells(curColumn, d)).MergeCells = True
End Sub
Be carefull it is not running.