Compare user input value against dictionary in VBA - excel

My macro needs to do some calculations on rows and import data where the user enters an ID. Main thing it's running on the specified ID because there is some data that needs updating/doublechecking so running running the entire source file is not ideal.
My code basically asks the user to enter an ID as 'criteria', and then this gets compared with a dictionary I created containing all data from the source, the problem is that using dict.Item doesn't really compare IDs though it runs on every single row correctly in the destination sheet.
'dictionary filler
For indexsrsrow = 2 To indexsrslastrow
dict.Add CStr(srcWorksheet.Range("A" & indexsrsrow).Value), indexsrsrow
Next indexsrsrow
dim criteria as string
criteria = inputbox("enter id")
For indexdstrow = 2 To indexlastdstrow
'IF ID EXIST AND ITEM = CRITERIA AND C COLUMN IS EMPTY
If dict.Exist(criteria) And destinerow.Cells(indexdstrow, "C") = "" Then
'STUFF HAPPENS HERE
End If
Next indexdstrow
Set dict = Nothing
Is there other way to compare dictionary items with an specified user input?.
Thanks in advance.

#SiddharthRout Kinda, e.g. Im the user and I need to update the record (row) with id 123 so I run the macro, it asks me for the id that I need to update, I input 123 and when I press enter, in the code it should get the id on the source workbook, grab the data and paste it in the destination workbook where the id is 123. Hope this clarifies. –
I have commented the code so you should not have a problem understanding it. Let me know if this is what you want? If not then post your query and I will look at it when I wake up.
Option Explicit
Sub Sample()
Dim srcWorksheet As Worksheet, destinerow As Worksheet
Dim dict As New Dictionary
Dim lRow As Long, i As Long
'~~> Set your source and destination worksheets
Set srcWorksheet = Sheet1
Set destinerow = Sheet2
'~~> Add items to dict from Source worksheet
With srcWorksheet
lRow = .Range("A" & .Rows.Count).End(xlUp).Row
For i = 2 To lRow
dict.Add CStr(.Range("A" & i).Value), i
Next i
End With
'~~> Ask user for the criteria
Dim criteria As String
criteria = InputBox("enter id")
'~~> If user presses cancel or item doesn't
'~~> exists in dictionary then exit sub
If criteria = "" Then Exit Sub
If Not dict.Exists(criteria) Then Exit Sub
Dim rngToCopy As Range, aCell As Range
'~~> Find the id in source so we can identify the
'~~> range to copy
With srcWorksheet
Set aCell = .Range("A1:A" & lRow).Find(What:=criteria, LookIn:=xlValues, _
LookAt:=xlWhole, SearchOrder:=xlByRows, SearchDirection:=xlNext, _
MatchCase:=False, SearchFormat:=False)
If Not aCell Is Nothing Then
'~~> I am ssuming the data that you want to copy
'~~> is in Col B. If not then change as applicable
Set rngToCopy = .Range("B" & aCell.Row)
End If
End With
Set aCell = Nothing
'~~> Find the id in destinations so we can identify the
'~~> range where we need to copy
With destinerow
Set aCell = .Columns(1).Find(What:=criteria, LookIn:=xlValues, _
LookAt:=xlWhole, SearchOrder:=xlByRows, SearchDirection:=xlNext, _
MatchCase:=False, SearchFormat:=False)
If Not aCell Is Nothing Then
'~~> I am ssuming the data WHERE you want to copy
'~~> is in Col C. If not then change as applicable
rngToCopy.Copy .Range("C" & aCell.Row)
End If
End With
End Sub

Related

Copy paste data from one sheet to another and only pick filtered data and maintain target column sequence

I have a requirement to automate a step to copy data from one sheet to another using excel macro.
But below are the problem I am facing with this requirement:
Need to copy paste in scope data i.e. filter on 'Data Scope' = Yes
Column sequence of source and target are different and since there are around 127 columns so could not hardcode this part.
Please help if you have a handy code or logic to implement the same.
Found a simple way to implement this, posting it here for others to use.
Sub Reorganize_columns()
Dim v As Variant, x As Variant, findfield As Variant
Dim oCell As Range
Dim rng As Range
Dim iNum As Long
Dim sht_source As Worksheet, sht_target As Worksheet
Set sht_source = ActiveWorkbook.Sheets("Data")
Set sht_target = ActiveWorkbook.Sheets("Macro")
sht_source.Range("A1").AutoFilter Field:=1, Criteria1:="Yes"
Set rng = sht_target.Range("A1:HS1")
For Each cell In rng
iNum = iNum + 1
findfield = cell.Value
Set oCell = sht_source.Rows(1).Find(What:=findfield, LookIn:=xlValues, LookAt:=xlWhole, SearchOrder:=xlByRows, SearchDirection:=xlNext, MatchCase:=False, SearchFormat:=False)
sht_source.Columns(oCell.Column).Copy
sht_target.Columns(iNum).PasteSpecial Paste:=xlPasteValuesAndNumberFormats
Next cell
ActiveWorkbook.Save
MsgBox "Completed"
End Sub

Why doesn't For each loop go to Next sheet?

I need to handle an Excel workbook with multiple tabs, and format dates.
I have found a way to format one date, and I wanted to put a loop around it. However the loop does not work, and it only updates one sheet.
Sub dotoall()
Dim LastRow As Integer
Dim FindCol As Range
Dim sAdd As String
Dim ws As Worksheet
For Each Sheet In Worksheets
Set ws = ActiveSheet
With ws
LastRow = .Cells.Find("*", SearchOrder:=xlByRows, SearchDirection:=xlPrevious).Row
'find first instance where DATE exists in row 1 (headers)
Set FindCol = .Rows(1).Find(What:="DTE", LookAt:=xlPart, SearchOrder:=xlByRows, SearchDirection:=xlNext, MatchCase:= _
False, SearchFormat:=False)
'store address of first found instance (to check in loop)
sAdd = FindCol.Address
Do
'format column (row 2 to last used row)
.Range(.Cells(2, FindCol.Column), .Cells(LastRow, FindCol.Column)).NumberFormat = "DD-MM-YYYY"
'find next instance (begin search after current instance found)
Set FindCol = .Cells.FindNext(After:=FindCol)
'keep going until nothing is found or the loop finds the first address again (in which case the code can stop)
Loop Until FindCol Is Nothing Or FindCol.Address = sAdd
End With
Next Sheet
End Sub
Instead of:
For Each Sheet In Worksheets
Set ws = ActiveSheet
You want just:
For Each ws In Worksheets

Create a looping search

I have row data dumped in sheet named "PDFtoEXCEL" and inside this data I have tables that I want to extract into my sheet named "CCE_Lab"
To find the tables I do a search for a keyword that is only available in those tables I am looking for, I search for "Compressibility2"
Then i offset from the active cell which was automatically selected by the search to copy the table and its title from sheet "PDFtoEXCEL" to sheet "CCE_Lab"
After the paste I offset one row below the pasted table
After that is where I need the help, I want the macro to search for the next table with keyword "Compressibility2" and paste it from sheet "PDFtoEXCEL" to sheet "CCE_Lab" one line below the first paste.
I want this search loop to keep going on until all my tables in sheet "PDFtoEXCEL" are copied and pasted to sheet "CCE_Lab"
This is the code I currently have, looking for your help to complete it:
Sub CCE_Tables_Group()
'
' CCE_Tables_Group Macro
' grouping CCE tables from PDF input
'
'
Sheets("PDFtoEXCEL").Select
ActiveCell.Offset(-2546, 0).Range("A1").Select
Cells.Find(What:="Compressibility2", After:=ActiveCell, LookIn:= _
xlFormulas, LookAt:=xlPart, SearchOrder:=xlByRows, SearchDirection:= _
xlNext, MatchCase:=False, SearchFormat:=False).Activate
ActiveCell.Offset(-2, -4).Range("A1:F25").Select
Selection.Copy
Sheets("CCE_Lab").Select
ActiveCell.Select
ActiveSheet.Paste
ActiveCell.Offset(26, 0).Range("A1").Select
End Sub
If your "tables" aren't Excel tables, then obviously you can't solve this by conveniently looping over ListObjects.
So instead try a Do-Until loop, and loop through all Find results until you're back at your first one (it should loop back to your first result eventually).
Something like:
Option Explicit
Private Sub CopyMatchingTablesToSheet()
Const NUMBER_OF_ROWS_BETWEEN_PASTES As Long = 1
With ThisWorkbook
Dim outputSheet As Worksheet
Set outputSheet = .Worksheets("CCE_Lab")
'outputSheet.Cells.Clear ' Uncomment this if you want to clear the sheet before pasting.
Dim sourceSheet As Worksheet
Set sourceSheet = .Worksheets("PDFtoExcel")
End With
Dim findResult As Range
Set findResult = sourceSheet.Cells.Find(What:="Compressibility2", LookIn:=xlFormulas, LookAt:=xlPart, SearchOrder:=xlByRows, SearchDirection:=xlNext, MatchCase:=False)
If findResult Is Nothing Then
MsgBox ("Could not find a single 'Compressibility2' in worksheet '" & sourceSheet.Name & "'." & vbNewLine & vbNewLine & "Code will stop running now.")
Exit Sub
End If
Dim lastRow As Long
lastRow = outputSheet.Cells(outputSheet.Rows.Count, "A").End(xlUp).Row
If lastRow > 1 Then lastRow = lastRow + 1 + NUMBER_OF_ROWS_BETWEEN_PASTES
Dim firstAddressFound As String
firstAddressFound = findResult.Address
Dim addressFound As String
Do
With findResult.Offset(-2, -4).Range("A1:F25") 'Magic numbers used in offset.
.Copy
outputSheet.Cells(lastRow, "A").PasteSpecial xlPasteValuesAndNumberFormats ' If you want to paste "everything", then use something like xlPasteAll below
lastRow = lastRow + .Rows.Count + NUMBER_OF_ROWS_BETWEEN_PASTES
End With
Set findResult = sourceSheet.Cells.FindNext(findResult)
addressFound = findResult.Address
DoEvents ' Get rid of this if you want.
Loop Until (firstAddressFound = addressFound) Or (findResult Is Nothing) ' This second condition is likely unnecessary
Application.CutCopyMode = False
End Sub
Maybe something like the below will do what you're after.
In short, we loop through every table on "PDFtoExcel" sheet, check if it contains the sub-string and then handle the copy-paste from there.
Option Explicit
Private Sub CopyMatchingTablesToSheet()
With ThisWorkbook
' Uncomment the line below if you want to clear the sheet before pasting.
' .Worksheets("CCE_LAB").Cells.Clear
Const NUMBER_OF_ROWS_BETWEEN_PASTES As Long = 1
Dim table As ListObject
For Each table In .Worksheets("PDFtoExcel").ListObjects
' table.Range (below) will search the table's body and headers for "Compressibility2"
' If you only want to search the table's body, then change to table.DataBodyRange
Dim findResult As Range
Set findResult = table.Range.Find(What:="Compressibility2", LookIn:=xlFormulas, LookAt:=xlPart, SearchOrder:=xlByRows, SearchDirection:=xlNext, MatchCase:=False)
If Not (findResult Is Nothing) Then
' Again, if you only to copy-paste the table's body,
' then change below to table.DataBodyRange.Copy
table.Range.Copy
With .Worksheets("CCE_LAB")
Dim lastRow As Long
lastRow = .Cells(.Rows.Count, "A").End(xlUp).Row
If lastRow > 1 Then lastRow = lastRow + 1 + NUMBER_OF_ROWS_BETWEEN_PASTES
' If you want to paste "everything", then use something like xlPasteAll below
' But I think xlPasteAll will create another Excel table on your CCE_Lab sheet
' with some new, unique name -- which can make the document a mess.
' Your call.
.Cells(lastRow, "A").PasteSpecial xlPasteValuesAndNumberFormats
End With
End If
Next table
Application.CutCopyMode = False
End With
End Sub

EXCEL VBA Debug: Searching through the whole workbook

I'm working on a VBA Macro for a database I have in Excel. I've got one Worksheet that stores information such as names, emails etc. (sadly those are not consistently placed in the same columns across all worksheets, but the email adresses span from "B:F"), this database is split into multiple worksheets. Except all those worksheets, I have also got one other worksheet ("Sheet2" in the code below) that stores all the email addresses that have assigned to my newsletter. (The only information in this sheet are the email addresses in the "A" column).
The VBA I'm working on should loop through all the email adresses that have subscribed to the newsletter ("Sheet2") and check if they're stored in "the database" - in the other sheets as well. If not, then give a warning - write "NOTFOUND" in the cell next to the email.
For some reason, VBA gives me a run-time error "Object doesn't support this property or method" on the row:
With Sheets(sheetIndex).Range("B:F").
Originally I thought that the reason for that is that I have not activated the Sheets, but I'm still getting the error.
The code I came up with so far:
Sub Search_for_emails()
Dim scanstring As String
Dim foundscan As Range
Dim lastRowIndex As Long
Dim ASheet As Worksheet
Set ASheet = Sheets("Sheet2")
lastRowInteger = ASheet.Range("A1", ASheet.Range("A1").End(xlDown)).Rows.Count
For rowNum = 1 To lastRowInteger
scanstring = Sheets("Sheet2").Cells(rowNum, 1).Value
For sheetIndex = 1 To ThisWorkbook.Sheets.Count
Sheets(sheetIndex).Activate
If Sheets(sheetIndex).Name <> "Sheet2" Then
With Sheets(sheetIndex).Range("B:F")
Set foundscan = .Find(What:=scanstring, LookIn:=xlValues, LookAt:=xlWhole, _
SearchOrder:=xlByRows, SearchDirection:=xlNext, _
MatchCase:=False, SearchFormat:=False)
End With
If foundscan Is Nothing Then
ASheet.Cells(rowNum, 2).Value = "NOTFOUND"
Else
' ASheet.Cells(rowNum, 2).Value = foundscan.Rows.Count
End If
End If
Next
Next rowNum
End Sub
Some points:
You should avoid Activate - no need for that.
You should always qualify things like
sheet or range, else Excel will use the active workbook /
sheet, and that is not always what you want.
There is a difference between the Sheets and the Worksheets collection. A Chart-sheet, for example, has no cells and therefore no Range.
You are declaring a variable lastRowIndex but uses lastRowInteger. To avoid such errors, always put Option Explicit at the top of your code.
Change your Sub to
Sub Search_for_emails()
Dim scanstring As String
Dim foundscan As Range
Dim lastRowIndex As Long, rowNum As Long
Dim ASheet As Worksheet
Set ASheet = ThisWorkbook.Worksheets("Sheet2")
lastRowIndex = ASheet.Range("A1", ASheet.Range("A1").End(xlDown)).Rows.Count
For rowNum = 1 To lastRowIndex
Dim ws As Worksheet
For Each ws In ThisWorkbook.Worksheets
If ws.Name <> "Sheet2" Then
With ws.Range("B:F")
Set foundscan = .Find(What:=scanstring, LookIn:=xlValues, LookAt:=xlWhole, _
SearchOrder:=xlByRows, SearchDirection:=xlNext, _
MatchCase:=False, SearchFormat:=False)
End With
If foundscan Is Nothing Then
ASheet.Cells(rowNum, 2).Value = "NOTFOUND"
Else
' ASheet.Cells(rowNum, 2).Value = foundscan.Rows.Count
End If
End If
Next
Next rowNum
End Sub

Copy column data consisting of blank cells

I am into a situation where I need to copy a range from a excel sheet and paste it to another. I have done the following coding which is going well...
Dim mpn As String
mpn = Application.InputBox(prompt:="Input the MPN column name:")
mpn1 = mpn
mpn2 = mpn1 & ":" & mpn
Set currentSheet = wbSource.Worksheets(1)
lastRow1 = currentSheet.Range(mpn1).End(xlDown).Row
ThisWorkbook.Sheets("Sheet2").Range("F2:F" & lastRow1) = currentSheet.Range(mpn2 & lastRow1).Value
This coding goes perfectly well untill there is any blank cell in the column. Can anyone please help me on this particular situation.
Like I mentioned in the comments above, instead of prompting for the column name, use .Find to locate the column name. What if user types Blah Blah in the input box?
Also as mentioned in comments use xlUp rather than xlDown to find the last row to counter for blank cells and other issues you may face. See this
Is this what you are trying? (Untested)
I have commented the code so you should not having a problem understanding it. But if you do then simply post back :)
Sub Sample()
Dim mpnCol As Long
Dim ColName As String, strSearch As String
Dim aCell As Range
Dim wbSource As Workbook
Dim wbInput As Worksheet, currentSheet As Worksheet
'~~> Change this to the Mpn Header
strSearch = "MPN"
'~~> This you have declared in your code
'~~> Change as applicable
Set wbSource = "Someworkbook"
Set currentSheet = wbSource.Worksheets(1)
Set wbInput = ThisWorkbook.Sheets("Sheet2")
With currentSheet
'~~> Search for the mpn header in row 1. Change as applicable
Set aCell = .Rows(1).Find(What:=strSearch, LookIn:=xlValues, _
LookAt:=xlWhole, SearchOrder:=xlByRows, SearchDirection:=xlNext, _
MatchCase:=False, SearchFormat:=False)
If Not aCell Is Nothing Then
'~~> Column Number
mpnCol = aCell.Column
'~~> Converting column number to column name
ColName = Split(.Cells(, mpnCol).Address, "$")(1)
'~~> Getting last row
lRow = .Range(ColName & .Rows.Count).End(xlUp).Row
'~~> Checking for excel versions. Comment this if the copying
'~~> will always happen in xl2007+ versions
If lRow > 65536 Then
MsgBox "Are you trying to copy from xl2007 to xl2003?. The number of rows exceed the row limit"
Exit Sub
End If
wbInput.Range("F2:F" & lRow).Value = .Range(ColName & "2:" & ColName & lRow).Value
Else
MsgBox strSearch & " header not found"
End If
End With
End Sub
To copy an entire column, reference your range with the .Columns() function.
You could use something like:
ThisWorkbook.Sheets("Sheet2").Columns("F") =
currentSheet.Columns(mpn1).Value
Another alternative would be to use the .Copy sub and specify a Destination for the copy:
currentSheet.Columns(mpn1).Copy
Destination:=ThisWorkbook.Sheets("Sheet2").Columns("F")
Application.CutCopyMode = false
This answer assumes both workbooks are saved with the same version of Excel. If one workbook is pre-2007, and one is 2007+, then the max number of rows allowed in a sheet will be different.
In that case, copying the entire column is not an option - check out Siddarth's answer for a longer solution to that extra complication. He checks for different number of rows to prevent the error.

Resources