The code breaks here
ws.Cells(j, "M").Value = dict(ws.Cells(j, "H").Value).volume
ws.Cells(j, "N").Value = dict(ws.Cells(j, "H").Value).rate
I believe the issue is trying to retrieve the dictionary value when the key is dynamic so
dict(ws.Cells(j, "H").Value).volume errors out
but
dict(5455).volume
will work.
Am I correct that this is the issue and any solutions?
Sub RetrieveData()
Dim dict As New Dictionary
Dim group As clsgroup
Dim lrowRetrieve As Long, lrowLookup As Long
Dim ws As Worksheet
Dim i As Long, j As Long, name As String, rate As Long, volume As Long
lrowRetrieve = Cells(Rows.Count, "C").End(xlUp).Row
lrowLookup = Cells(Rows.Count, "H").End(xlUp).Row
Set ws = Sheets("Test")
dict.RemoveAll
For i = 1 To lrowRetrieve
'add criteria for month/year of reporting
If ws.Cells(i, "C").Value <> "" And ws.Cells(i, "C").Value <> 0 And ws.Cells(i, "G").Value <> "" And ws.Cells(i, "G").Value <> 0 Then
name = ws.Cells(i, "C").Value
If dict.Exists(name) = False Then
Set group = New clsgroup
group.name = name
dict.Add key:=group.name, Item:=group
Else
Set group = dict(name)
'dict(ws.Cells(i, "C").Value) = ws.Cells(i, "G").Value
End If
With group
.rate = .rate + ws.Cells(i, "F").Value
.volume = .volume + ws.Cells(i, "G").Value
End With
End If
Next i
Dim key As Variant
For Each key In dict
Set group = dict(key)
With group
Debug.Print .name, .rate, .volume
End With
Next key
For j = 25 To lrowLookup
Select Case ws.Cells(j, "H").Value
Case "5455"
If ws.Cells(j, "K").Value = "Medical" Then
ws.Cells(j, "M").Value = dict("5455/5456").volume
ws.Cells(j, "N").Value = dict("5455/5456").rate
Else
ws.Cells(j, "M").Value = dict("5455/5456 (non med)").volume
ws.Cells(j, "N").Value = dict("5455/5456 (non med)").rate
End If
Case Else
Debug.Print ws.Cells(j, "H").Value
ws.Cells(j, "M").Value = dict(ws.Cells(j, "H").Value).volume
ws.Cells(j, "N").Value = dict(ws.Cells(j, "H").Value).rate
End Select
Next j
End Sub
dict(5455) is not the same as dict("5455") - it matters what data type your keys have when they're added - reliable retrieval from the dictionary requires the same type be used....
You might find it best to always convert your keys to String before adding them, and always retrieve with String-type keys
Sub Tester()
Dim dict As Object
Set dict = CreateObject("scripting.dictionary")
dict(5455) = "Numeric"
dict("5455") = "String" '"5455" is a different key from 5455 !
Debug.Print dict(5455) '>> "Numeric"
Debug.Print dict("5455") '>> "String"
End Sub
Related
I have a user Form through which I am able to upload transactions into a cashflow worksheet. By using cDbl in two controls I am able to ensure that amounts are added to the Worksheet in a format that can be used in calculations. However, by nature these fields are mutually exclusive (Credit & Debit). cDbl requires a value to be populated in each control so I am looking for a method that will check the value of each of the two relevant controls and to ignore them when the value is Null
Private Sub cmdAddRecord_Click()
'Used to add new transation records to the database
lastrow = Sheets("Spending Account").Range("A" & Rows.Count).End(xlUp).Row
Cells(lastrow + 1, "A").Value = DTPicker1
Cells(lastrow + 1, "B").Value = cboVendorDetails
Cells(lastrow + 1, "C").Value = cboTransactionType
Cells(lastrow + 1, "D").Value = CDbl(Me.txtTransactionAmountDebit)
Cells(lastrow + 1, "E").Value = CDbl(Me.txtTransactionAmountCredit)
Cells(lastrow + 1, "F").Value = cboTransactionStatus
With ActiveSheet
Application.Goto Reference:=.Cells(.Rows.Count, "A").End(xlUp).Offset(-20), Scroll:=True
End With
Unload Me
frmRegularTransactions.Show
End Sub
I would welcome any solution
Private Sub cmdAddRecord_Click()
'Used to add new transation records to the database
Dim r As Long, sCredit As String, sDebit As String
sDebit = Me.txtTransactionAmountDebit
sCredit = Me.txtTransactionAmountCredit
With Sheets("Spending Account")
r = 1 + .Cells(.Rows.Count, "A").End(xlUp).Row
.Cells(r, "A").Value = DTPicker1
.Cells(r, "B").Value = cboVendorDetails
.Cells(r, "C").Value = cboTransactionType
.Cells(r, "F").Value = cboTransactionStatus
' credit or debit
If Len(sDebit) > 0 Then
If Len(sCredit) > 0 Then
MsgBox "Warning - Both Credit and Debit", vbExclamation
Else
.Cells(r, "D").Value = CDbl(sDebit)
End If
ElseIf Len(sCredit) > 0 Then
.Cells(r, "E").Value = CDbl(sCredit)
End If
If r > 21 Then
Application.Goto Reference:=.Cells(r - 20, "A"), Scroll:=True
End If
End With
Unload Me
frmRegularTransactions.Show
End Sub
Currently i have this code to register daily all meals at a canteen.
Sub register()
Dim s As Worksheet
Set s = Worksheets("Lista_" & Range("K9").Value)
Dim row As Long
row = s.Cells(s.Rows.Count, "B").End(xlUp).row + 1
s.Cells(row, "B").Value = Range("C7").Value
s.Cells(row, "C").Value = Range("C9").Value
s.Cells(row, "H").Value = Range("L9").Value
s.Cells(row, "I").Value = Range("P20").Value
s.Cells(row, "N").Value = Range("P21").Value
s.Cells(row, "O").Value = Range("P1").Value
Range("M6:M19").Select
Range("M19").Activate
Selection.ClearContents
Range("C7:D7").Select
Selection.ClearContents
Range("C7").Select
End Sub
--
I would like there to be a message if an employee's number has already been registered (so as not to duplicate it)... for that the vba code should search on all pages if that number already existed or not.If the number appears in column B of sheets that begin with "Lista_" a message should appear
I think you just need to add a function that checks for the employee number
Something like this worked for me using your sample data
You can change constants and data types to match your situation
Option Explicit
Sub register()
Dim s As Worksheet
Dim row As Long
Dim employeeNum As String
Set s = Worksheets("Lista_" & Range("K9").Value)
row = s.Cells(s.Rows.Count, "B").End(xlUp).row + 1
employeeNum = Range("C7").Value
If AlreadyRegistered(employeeNum) Then
MsgBox "Ignoring Preexisting Employee Number: " & employeeNum
Else
s.Cells(row, "B").Value = employeeNum
s.Cells(row, "C").Value = Range("C9").Value
s.Cells(row, "H").Value = Range("L9").Value
s.Cells(row, "I").Value = Range("P20").Value
s.Cells(row, "N").Value = Range("P21").Value
s.Cells(row, "O").Value = Range("P1").Value
Range("M6:M19").Select
Range("M19").Activate
Selection.ClearContents
Range("C7:D7").Select
Selection.ClearContents
Range("C7").Select
End If
End Sub
Function AlreadyRegistered(employeeNum As String) As Boolean
Const EmployeeColumn As String = "B:B"
Dim varSheets As Variant
Dim intSheet As Integer
Dim xlSheet As Worksheet
Dim rgeFound As Range
Dim i As Integer
AlreadyRegistered = False
varSheets = Array("Lista_AA", "Lista_BB", "Lista_CC")
For intSheet = LBound(varSheets) To UBound(varSheets)
Set xlSheet = Sheets(varSheets(i))
Set rgeFound = xlSheet.Range(EmployeeColumn).Find(employeeNum)
If Not (rgeFound Is Nothing) Then
AlreadyRegistered = True
Exit For
End If
Next intSheet
End Function
I am a complete novice at Excel VBA and I am currently attempting a project on Excel VBA. I have created a UserForm that would allow the user to enter data onto the Excel Sheet by completing the fields in the UserForm. I have tested all the codes individually and they have worked fine.
For the user to access the UserForm, I have added an ActiveX Command Button on a separate sheet on the same workbook. However, when accessing the UserForm from the ActiveX Command Button, some of the codes do not run (mainly the code that flags out the duplicate entry, as well as the code that generates serial numbers).
Where did I go wrong in my code?
This is my code to adding new data as well as the code to flag out duplicate entries. When opening the UserForm from the ActiveX Command Button, adding new data works fine but it does not flag out duplicate entries in the data. (However, testing the code itself in VBA works perfectly fine).
Private Sub cmdAddNewCustomer_Click()
Dim count As Long
Dim lastrow As Long
Dim lCustomerID As String
Dim ws As Worksheet
Set ws = Worksheets("Customer Data")
'find first empty row in database
lrow = ws.Cells.Find(what:="*", searchorder:=xlRows, _
Searchdirection:=xlPrevious, LookIn:=xlValues).Row + 1
lCustomerID = txtCustomerID
count = 0
With ws
For currentrow = 1 To lrow
If lCustomerID = Cells(currentrow, 1) Then
count = count + 1
End If
If count > 1 Then
.Cells(currentrow, 1).Value = ""
.Cells(currentrow, 2).Value = ""
.Cells(currentrow, 3).Value = ""
.Cells(currentrow, 4).Value = ""
.Cells(currentrow, 5).Value = ""
.Cells(currentrow, 6).Value = ""
.Cells(currentrow, 7).Value = ""
.Cells(currentrow, 8).Value = ""
.Cells(currentrow, 9).Value = ""
.Cells(currentrow, 10).Value = ""
.Cells(currentrow, 11).Value = ""
.Cells(currentrow, 12).Value = ""
.Cells(currentrow, 13).Value = ""
.Cells(currentrow, 14).Value = ""
MsgBox ("CustomerID already exists!")
End If
If count = 0 Then
.Cells(lrow, 1).Value = Me.txtCustomerID.Value
.Cells(lrow, 2).Value = Me.txtCustomerName.Value
.Cells(lrow, 3).Value = Me.cboCustomerStatus.Value
.Cells(lrow, 4).Value = Me.txtContactPerson.Value
.Cells(lrow, 5).Value = Me.cboDepartment.Value
.Cells(lrow, 6).Value = Me.txtPosition.Value
.Cells(lrow, 7).Value = Me.cboRoleType.Value
.Cells(lrow, 8).Value = Me.txtofficeHP1.Value
.Cells(lrow, 9).Value = Me.txtOfficeHP2.Value
.Cells(lrow, 10).Value = Me.txtMobileHP1.Value
.Cells(lrow, 11).Value = Me.txtMobileHP2.Value
.Cells(lrow, 12).Value = Me.txtEmail1.Value
.Cells(lrow, 13).Value = Me.txtEmail2.Value
.Cells(lrow, 14).Value = Me.txtEmail3.Value
End If
Next currentrow
End With
'clear the data
Me.txtCustomerName.Value = ""
Me.cboCustomerStatus.Value = ""
Me.txtContactPerson.Value = ""
Me.cboDepartment.Value = ""
Me.txtPosition.Value = ""
Me.cboRoleType.Value = ""
Me.txtofficeHP1.Value = ""
Me.txtOfficeHP2.Value = ""
Me.txtMobileHP1.Value = ""
Me.txtMobileHP2.Value = ""
Me.txtEmail1.Value = ""
Me.txtEmail2.Value = ""
Me.txtEmail3.Value = ""
End Sub
This is the code to generate serial numbers. (Same problem, does not work when accessed via ActiveX Command Button but works fine when tested individually in VBA).
Sub FindCustomerID()
Dim lastrow
Dim lastnum As Long
Dim ws As Worksheet
Set ws = Worksheets("Customer Data")
If Me.cboCountry = "" Or Me.txtCustomerName = "" Then
Exit Sub
End If
serialno = 1
lastrow = ws.Cells(Rows.count, 1).End(xlUp).Row
CountryCode = UCase(Left(Me.cboCountry, 3))
CustomerCode = UCase(Left(Me.txtCustomerName, 10))
'assemble them into CustomerID
CustomerID = CountryCode & CustomerCode & serialno
For currentrow = 2 To lastrow
If CustomerID = Cells(currentrow, 1) Then
'find last number that applies
serialno = serialno + 1
End If
're-assign customerID with new serial number
CustomerID = CountryCode & CustomerCode & serialno
Next currentrow
Me.lblCustomerID = CustomerID
End Sub
And lastly, this is the code from the ActiveX Command Button that brings out the UserForm.
Private Sub cmdNCustomerData_Click()
frmCustomerdata.Show
End Sub
The cause of the problem you described is a missing . to qualify Cells(currentrow, 1). Because you added the ActiveX button to a different sheet, the line
If lCustomerID = Cells(currentrow, 1) Then
accesses Cells(currentrow, 1) of that sheet. To fix this the range needs to be qualified with a . to become
If lCustomerID = .Cells(currentrow, 1) Then
I would also take
If count = 0 Then
.
.
.
End If
outside the loop. You are repeating these lines many times unnecessarily.
The first block of code then becomes:
Private Sub cmdAddNewCustomer_Click()
Dim count As Long
Dim lastrow As Long
Dim lCustomerID As String
Dim ws As Worksheet
Set ws = Worksheets("Customer Data")
'find first empty row in database
lrow = ws.Cells.Find(what:="*", searchorder:=xlRows, _
Searchdirection:=xlPrevious, LookIn:=xlValues).Row + 1
lCustomerID = txtCustomerID
count = 0
With ws
' Count backward to delete rows completely
For currentrow = lrow - 1 To 1 Step -1
If lCustomerID = .Cells(currentrow, 1) Then
count = count + 1
End If
If count > 1 Then
.Cells(currentrow, 1).Resize(1, 14).ClearContents
' Uncomment the following line to delete the whole row completely
'.Rows(currentrow).Delete
End If
Next currentrow
If count > 1 Then
MsgBox (count - 1 " duplicates of CustomerID found and cleared!")
ElseIf count = 0 Then
.Cells(lrow, 1).Value = Me.txtCustomerID.Value
.Cells(lrow, 2).Value = Me.txtCustomerName.Value
.Cells(lrow, 3).Value = Me.cboCustomerStatus.Value
.Cells(lrow, 4).Value = Me.txtContactPerson.Value
.Cells(lrow, 5).Value = Me.cboDepartment.Value
.Cells(lrow, 6).Value = Me.txtPosition.Value
.Cells(lrow, 7).Value = Me.cboRoleType.Value
.Cells(lrow, 8).Value = Me.txtofficeHP1.Value
.Cells(lrow, 9).Value = Me.txtOfficeHP2.Value
.Cells(lrow, 10).Value = Me.txtMobileHP1.Value
.Cells(lrow, 11).Value = Me.txtMobileHP2.Value
.Cells(lrow, 12).Value = Me.txtEmail1.Value
.Cells(lrow, 13).Value = Me.txtEmail2.Value
.Cells(lrow, 14).Value = Me.txtEmail3.Value
End If
End With
'clear the data
Me.txtCustomerName.Value = ""
Me.cboCustomerStatus.Value = ""
Me.txtContactPerson.Value = ""
Me.cboDepartment.Value = ""
Me.txtPosition.Value = ""
Me.cboRoleType.Value = ""
Me.txtofficeHP1.Value = ""
Me.txtOfficeHP2.Value = ""
Me.txtMobileHP1.Value = ""
Me.txtMobileHP2.Value = ""
Me.txtEmail1.Value = ""
Me.txtEmail2.Value = ""
Me.txtEmail3.Value = ""
End Sub
In the FindCustomerID subroutine you have exactly the same problem with the line
If CustomerID = Cells(currentrow, 1) Then
as Cells(currentrow, 1) is not qualified and therefore, should become
If CustomerID = ws.Cells(currentrow, 1) Then
You are also reassigning the CustomerID many times unnecessarily. I would take the reassignment inside the If statement and the loop will become
For currentrow = 2 To lastrow
If CustomerID = ws.Cells(currentrow, 1) Then
'find last number that applies
serialno = serialno + 1
're-assign customerID with new serial number
CustomerID = CountryCode & CustomerCode & serialno
End If
Next currentrow
This way CustomerID is only reassigned if and only if serialno changes.
I want to combine duplicate rows with the same A and C columns values and sum their cells values for the column B (by adding the value of the textbox2 from the duplicate to the original). My problem is about the condition of the "If" in the Loop. It doesn't consider those conditions when I have duplicates and just add a new row. Is there a better way to do this?
Private Sub CommandButton1_Enter()
ActiveSheet.Name = "Sheet1"
Dim lastrow As Long
With Sheets("Sheet2")
lastrow = .Cells(Rows.Count, "H").End(xlUp).Row
For x = lastrow To 3 Step -1
For y = 3 To lastrow
If .Cells(x, 1).Value = .Cells(y, 1).Value And .Cells(x, 3).Value = .Cells(y, 3).Value And x > y Then
.Cells(y, 8).Value = .Cells(y, 8).Value + TextBox2.Text
.Cells(y, 2).Value = .Cells(y, 2).Value + TextBox2.Text
.Rows(lastrow).EntireRow.Delete
Else
.Cells(lastrow + 1, 8).Value = TextBox2.Text
.Cells(lastrow + 1, 2).Value = TextBox2.Text
.Cells(lastrow + 1, 1).Value = TextBox1.Text
.Cells(lastrow + 1, 3).Value = TextBox3.Text
Exit For
End If
Next y
Next x
End With
End Sub
Here's a picture of the data
There's no blank cell in the column H (I changed the color of the font to make it invisible).
Create a primary key by joining the 2 columns with tilde ~ and use a Dictionary Object to locate duplicates.
Option Explicit
Private Sub CommandButton1_Click()
Dim wb As Workbook, ws As Worksheet
Dim iLastRow As Long, iRow As Long, iTarget As Long
Set wb = ThisWorkbook
Set ws = wb.Sheets("Sheet2")
iLastRow = ws.Cells(Rows.Count, "H").End(xlUp).Row
Dim dict As Object, sKey As String
Set dict = CreateObject("Scripting.Dictionary")
' build dictionary and
' consolidate any existing duplicates, scan up
For iRow = iLastRow To 3 Step -1
' create composite primary key
sKey = LCase(ws.Cells(iRow, 1).Value) & "~" & Format(ws.Cells(iRow, 3).Value, "yyyy-mm-dd")
If dict.exists(sKey) Then
iTarget = dict(sKey)
' summate and delete
ws.Cells(iTarget, 2) = ws.Cells(iTarget, 2) + ws.Cells(iRow, 2)
ws.Cells(iTarget, 8) = ws.Cells(iTarget, 8) + ws.Cells(iRow, 8)
ws.Rows(iRow).EntireRow.Delete
Else
dict(sKey) = iRow
End If
Next
' add new record from form using dictionary to locate any existing
iLastRow = ws.Cells(Rows.Count, "H").End(xlUp).Row
sKey = LCase(TextBox1.Text) & "~" & Format(DateValue(TextBox3.Text), "yyyy-mm-dd")
If dict.exists(sKey) Then
iTarget = dict(sKey)
ws.Cells(iTarget, 2) = ws.Cells(iTarget, 2) + TextBox2.Text
ws.Cells(iTarget, 8) = ws.Cells(iTarget, 8) + TextBox2.Text
Else
iTarget = iLastRow + 1
ws.Cells(iTarget, 1) = TextBox1.Text
ws.Cells(iTarget, 2) = TextBox2.Text
ws.Cells(iTarget, 3) = TextBox3.Text
ws.Cells(iTarget, 8) = TextBox2.Text
End If
End Sub
I am trying to store data from "details" sheet into strings from different columns to different strings for every row in a linear manner and then assign the strings the same value 51 times in Cells of other sheet named "output".
Option Explicit
Sub Arrange()
Dim FinalRow, FinalRow1 As Long
Dim ws, wr As Worksheet
Dim strCN, strAdd, strCity, strState, strZip, strPhone, strWeb As String
Application.ScreenUpdating = False
Dim i, j As Long
Set ws = Sheets("details")
FinalRow = ws.Range("A900000").End(xlUp).Row
For j = 2 To FinalRow
strCN = Cells(j, "A")
strAdd = Cells(j, "H")
strCity = Cells(j, "I")
strState = Cells(j, "J")
strZip = Cells(j, "K")
strPhone = Cells(j, "R")
strWeb = Cells(j, "U")
Set wr = Sheets("output")
FinalRow1 = wr.Range("A900000").End(xlUp).Row
For i = FinalRow1 To FinalRow1 + 51
With Sheets("output")
Cells(i, "A").Text = strCN 'Error Line
Cells(i, "B").Text = strAdd
Cells(i, "C").Text = strCity
Cells(i, "D").Text = strState
Cells(i, "E").Text = strZip
Cells(i, "F").Text = strPhone
Cells(i, "G").Text = strWeb
End With
Next i
Next j
End Sub
As per our conversation above. I have made the changes I suggested.
The last problem was that the details sheet was not being called and if the other sheet was active at the time it was looking at empty cells.
Dim FinalRow, FinalRow1 As Long
Dim ws, wr As Worksheet
Dim strCN, strAdd, strCity, strState, strZip, strPhone, strWeb As String
Application.ScreenUpdating = False
Dim i, j As Long
Set ws = Sheets("details")
FinalRow = ws.Range("A900000").End(xlUp).Row
For j = 2 To FinalRow
With ws
strCN = .Cells(j, "A")
strAdd = .Cells(j, "H")
strCity = .Cells(j, "I")
strState = .Cells(j, "J")
strZip = .Cells(j, "K")
strPhone = .Cells(j, "R")
strWeb = .Cells(j, "U")
End With
Set wr = Sheets("output")
FinalRow1 = wr.Range("A900000").End(xlUp).Row
For i = FinalRow1 To FinalRow1 + 51
With Sheets("output")
.Cells(i, "A").Value = strCN 'Error Line
.Cells(i, "B").Value = strAdd
.Cells(i, "C").Value = strCity
.Cells(i, "D").Value = strState
.Cells(i, "E").Value = strZip
.Cells(i, "F").Value = strPhone
.Cells(i, "G").Value = strWeb
End With
Next i
Next j
Application.ScreenUpdating = True