Is there a better way to compare against many strings with like? - excel

I have a long list of words I need to compare against.
As an example fruit and vegetables that needs to be stored cold vs warmer:
Cold
strawberries
raspberries
lettuce
Warm(er)
cucumber
bell pepper
tomatoes
I have a sheet with products and need to loop it:
For Each cel In rng
If LCase(cel.Value) Like "*strawberries*" Or LCase(cel.Value) Like "*raspberries*" Or LCase(cel.Value) Like "*lettuce*" Then
msgbox "Cold"
ElseIf LCase(cel.Value) Like "*cucumber*" or LCase(cel.Value) Like "*bell pepper*" or LCase(cel.Value) Like "*tomato*" Then
msgbox "Warmer"
End If
Next cel
Is there any way I could this better? The syntax to test against all products will be very very long.
Could I somehow group/list them and make the syntax easier to maintain?
Example of the workbook:

To demonstrate what I meant with a wildcard match:
Sub Test()
Dim rng As Range, cl As Range
Dim Cold As Variant, Warm As Variant
Set rng = ThisWorkbook.Worksheets("Sheet1").Range("A1:A4")
Cold = Array("*strawberries*", "*raspberries*", "*lettuce*")
Warm = Array("*cucumber*", "*bell pepper*", "*tomatoes*")
With Application
For Each cl In rng
If .IsNumber(.Match(True, .IsNumber(.Match(Cold, cl, 0)), 0)) Then
'Or: If UBound(Filter(.IsNumber(.Match(Warm, cl, 0)), True)) = 0 Then
MsgBox "Cold"
ElseIf .IsNumber(.Match(True, .IsNumber(.Match(Warm, cl, 0)), 0)) Then
MsgBox "Warm"
End If
Next
End With
End Sub
Alternatively, you could use regular expressions with word-boundaries:
Sub Test()
Dim rng As Range, cl As Range
Dim Cold As String, Warm As String
Set rng = ThisWorkbook.Worksheets("Sheet1").Range("A1:A4")
Cold = "strawberries|raspberries|lettuce"
Warm = "cucumber|bell pepper|tomatoes"
With CreateObject("vbscript.regexp")
.IgnoreCase = True
For Each cl In rng
.Pattern = "\b" & Cold & "\b"
If .Test(cl) Then
MsgBox "Cold"
Else
.Pattern = "\b" & Warm & "\b"
If .Test(cl) Then MsgBox "Warm"
End If
Next
End With
End Sub
You can also, match both in any case and see if it's supposed to be a combination of warm and cold.

Here is a perfunctory system that would return the information you want from a list of produce.
Sub GetStorageInstruction()
' 187
Dim Veggie As Variant
Dim Storage As String
Dim Txt As String
Veggie = InputBox("Enter name of fruit or vegetable to store:", _
"Get storage instruction")
Veggie = Trim(Veggie)
If Len(Veggie) Then
Storage = StorageInstruction(Veggie)
If Len(Storage) Then
Txt = "Store " & Veggie & " at a " & Storage & " location."
Else
Txt = "Sorry, I couldn't find instructions on" & vbCr & _
"storage of """ & Veggie & """."
End If
MsgBox Txt, vbInformation, "Storage instructions"
End If
End Sub
Private Function StorageInstruction(ByVal Veggie As String) As String
' 187
' return vbNullString if not found
Dim ListRng As Range
Dim Fnd As Range ' found match
Dim C As Long ' column
' here items for "Cold" storage are in column A,
' items for "Cool" storage are in column B
Set ListRng = Range("A:B") ' adjust to suit
Set Fnd = ListRng.Find(Veggie, LookIn:=xlValues, Lookat:=xlWhole, MatchCase:=False)
If Not Fnd Is Nothing Then
' the return result is taken from the caption row (row 1)
' of the column in which a match was found
StorageInstruction = Cells(1, Fnd.Column).Value
End If
End Function
"Ordinarily", you wouldn't work with an InputBox because it's too error prone (typos) but with a validation list or combo box that is based on the same lists. But for the moment, if you are concerned about not finding "Bell peppers" (plural), consider either listing "Bell pepper" as well or modify the search to LookAt:=xlPart.
To make the above code work for you immediately, just type "Cold" in A1, "Cool" in B1 and a list of produce under each header. The code will return the header from the column where the item was found.
I see that you have now added a view of your worksheet. That is a much better base. Instead of the produce name, list a number from your columns C or D (whichever is unique), in my columns A and B, and enter that number in the InputBox. Once you implement that system you can modify the returned answer by using the number to VLOOKUP the product name so that the description appears in the answer along with the number you entered.
As an afterthought, the best way for you would probably be to just select the row you are interested in, click a button (or keyboard shortcut) and have the storage instruction pop up. But the presumption here is that you should be able to attach VBA code to your workbook.

No. Correct. I solved that with search for asparagus then search again in the same string for potatoes. But I know there will be false matches. There is no way around it. Let's just say, if there is a handful of false matches is better than looking through the full sheet manually (30-40 000 rows). – Andreas 1 hour ago
Here is an example of what I recommend. Feel free to go with other answers. If there are multiple matches then fill the cell with "Cold/Warm" as mentioend in the code comments below. This way you can simply filter on these and fix them manually.
Basic Preparation to test this
Create a master sheet in the file which has the code. Let's call it MasterList. The reason why we are doing this is so that it is easier to maintain and when you are distributing the code file, the masterlist is easily available. You can do version control on the file so that everyone uses the current version. Let's say the MasterList looks like this.
Let's say the file (as shown in your image) is called MyData.xlsx and the data is in Sheet1. Feel free to change it in the code below. It looks like this
Code
Option Explicit
Sub Sample()
Dim wsThis As Worksheet
'~~> This is the hidden sheet which has your master list in the file
'~~> which has the code
Set wsThis = ThisWorkbook.Sheets("MasterList")
Dim lRow As Long
Dim MasterList As Variant
With wsThis
lRow = .Range("A" & .Rows.Count).End(xlUp).Row
MasterList = .Range("A2:B" & lRow).Value2
End With
Dim wb As Workbook
Dim wsThat As Worksheet
'~~> Change this to the workbook where the data needs to be checked
Set wb = Workbooks.Open("C:\Users\Siddharth Rout\Desktop\MyData.xlsx")
'~~> Change this to the workseet where the data needs to be checked
Set wsThat = wb.Sheets("Sheet1")
Dim rngToProcess As Range
With wsThat
'~~> Find last row in Col E which has names
lRow = .Range("E" & .Rows.Count).End(xlUp).Row
'~~> Identify your range
Set rngToProcess = .Range("E2:E" & lRow)
'~~> Insert a blank column for output
.Columns(6).Insert Shift:=xlToRight
End With
Dim SearchText As String
Dim aCell As Range, bCell As Range
Dim i As Long
'~~> Loop through the masterlist
For i = LBound(MasterList) To UBound(MasterList)
SearchText = MasterList(i, 1)
Set aCell = rngToProcess.Find(What:=SearchText, LookIn:=xlValues, _
LookAt:=xlPart, SearchOrder:=xlByRows, SearchDirection:=xlNext, _
MatchCase:=False, SearchFormat:=False)
If Not aCell Is Nothing Then
Set bCell = aCell
'~~> Get the Warm - Cold - Warm/Cold Status
aCell.Offset(, 1).Value = GetStatus(MasterList(i, 2), aCell.Offset(, 1).Value)
'~~> Search again for multiple occurences
Do
Set aCell = rngToProcess.FindNext(After:=aCell)
If Not aCell Is Nothing Then
If aCell.Address = bCell.Address Then Exit Do
aCell.Offset(, 1).Value = GetStatus(MasterList(i, 2), aCell.Offset(, 1).Value)
Else
Exit Do
End If
Loop
End If
Next i
End Sub
'~~> Common function to asign the values
'~~> If there are multiple matches then fill the cell with "Warm/Cold".
'~~> This way you can simply filter on these and fix them manually.
Private Function GetStatus(MasterStatus As Variant, CurrentStatus As String) As String
Dim newStatus As String
If MasterStatus = "Cold" Then
Select Case CurrentStatus
Case "Warm": newStatus = "Warm/Cold"
Case Else: newStatus = MasterStatus
End Select
ElseIf MasterStatus = "Warm" Then
Select Case CurrentStatus
Case "Cold": newStatus = "Warm/Cold"
Case Else: newStatus = MasterStatus
End Select
End If
GetStatus = newStatus
End Function
Output
When you run the above code you get the below output

Here is an alternative to my earlier answer. Install the code below in a standard code module and make some arrangement to call it, perhaps with a keyboard shortcut or even a button on the sheet. Then simply select an item (anywhere in the list, no particular column) and run the code. You don't need to enter anything.
Sub Storage_Instruction()
' 187
Const SKUClm As String = "D" ' change to point at the SKU column in 'Data'
Const DescClm As String = "E" ' change to point at the Description column in 'Data'
Const StgClm As String = "C" ' change to point at Storage Instruction column in WsList
Dim WsData As Worksheet
Dim WsList As Worksheet
Dim SKU As String ' SKU number from row R
Dim Desc As String ' Description from row R
Dim R As Long ' the selected row
Dim LookUpRng As Range ' in WsList
Dim Fnd As Range ' found match
Dim Storage As Variant
Dim Txt As String
Set WsData = Worksheets("Data") ' insert your data sheet's name here
Set WsList = Worksheets("Storage") ' change name to suit
With WsList
' my list has an extra column for 'Description'
' the storage instruction is in column C (=StgClm)
Set LookUpRng = .Range(.Cells(2, "A"), .Cells(.Rows.Count, StgClm).End(xlUp))
End With
If ActiveSheet Is WsData Then
R = Selection.Row
With WsData
SKU = .Cells(R, SKUClm).Value
Desc = .Cells(R, DescClm).Value
End With
Set Fnd = LookUpRng.Find(SKU, LookIn:=xlValues, Lookat:=xlWhole)
If Fnd Is Nothing Then
Txt = "Sorry, I couldn't find instructions for the" & vbCr & _
"storage of " & Desc & "(SKU " & SKU & ")."
Else
Storage = WsList.Cells(Fnd.Row, StgClm).Value
Txt = "Store " & Desc & " (SKU " & SKU & ") at " & String(2, vbCr) & _
String(8, Chr(32)) & UCase(Storage) & String(2, vbCr) & "temperature."
End If
End If
MsgBox Txt, vbInformation, "Storage instruction"
End Sub
For the setup you do need to specify the 3 constants at the top of the procedure and the names of the two worksheets that are referenced.
The list is a simple copy of the SKU column from your big list. In my test I also copied the the descriptions. You may find that way easier to fill the 3rd column, which holds the words "Cool" and "Cold" (or whatever else you want) against each item. The middle column isn't used and not required y the above code.
According to your description, the 'List' sheet should be Very Hidden. In the VB Editor's Project Browser, click on the sheet, bring up its properties and set the Visible property to xlVeryHidden. The sheet can be made visible only by changing this property back to xlVisible. The property setting is saved when you save the workbook.

Related

Compare two Excel workbooks for differences and copy/paste all the information from one to another

On some difficult issue I am bumped in. And i guess it quite out of my knowledge, and I hope it is even possible to solve on some way.
ISSUE:
Two different workbooks: I am having one workbook with 10 sheets inside, with many formulas, dropdowns, calculations etc., and it is main version of the document which has to be filled with information.
Second workbook, or better to say another similar version to this workbook is like obsolete versions of main wb, where might be possible that some cells/format, or even sheet is missing, but in general almost the same from its structure.
PROCESS:
Sometimes the customers are not having the newest version of excel workbook, but still some of the obsolete versions (they are forgetting to use the newest version), and they are filling those fields inside those older versions and sending them back. The problem is, our ERP Software cant read the obsolete versions, because it is so adjusted to read only the newest version of the document. Meaning, it has to be manually checked every time when the document is sent back and finding discrepancies and copy/paste them into newest version of the document, and then upload it into ERP...
RESULT:
I am looking for some solution, with VBA or even formulas how to check every other workbook against "newest" and if there are any discrepancy and differences just to copy/paste everything from old to new version. When I say "everything" it means, all the fields, sheets, calculations, 1:1.
Unfortunately I am not writing any code or formula, because this is for me super advanced.
On the pic below is one example of one sheet how it looks like. There are lot of columns, calcs and so on.
Explanation:
To clarify bit better the content: inside one workbook is usually 10 sheets. 8x of them are the same (gas chambers from 1-8) and depending on the customer wishes, they can populated from 1-8. Sometimes 1 sometimes 5.
And range is from A1:Q54, full of data, tables, calculations, dropdowns, infos..
One sheet (9th) is customer details and last one (10th) is just instruction sheet with infos and screenshots.
So optimal would be to have macro that is taking everything from older versions, compare it with new one, and populate data that it finds, or on already given workbook or on new one but with the same content. I dont know if that is something possible.
An example of how to scan various cell ranges in a workbook and collate those that have values into a table. Second stage would be to transfer those values to the new format template.
Option Explicit
Sub extractAll()
Dim myfile As String, wb As Workbook, ws As Worksheet
Dim n As Long, rng1 As Range, rng2 As Range, msg As String
' select workbook to scan
With Application.FileDialog(msoFileDialogFilePicker)
.Title = "Please select a file"
.Show
.AllowMultiSelect = False
If .SelectedItems.Count = 0 Then 'If no folder is selected, abort
MsgBox "You did not select a file"
Exit Sub
End If
myfile = .SelectedItems(1)
End With
' collate data on sheet 1 and 2
Sheet1.Cells.Clear
Sheet1.Range("A1:E1") = Array("Sheet", "Addr", "Row", "Column", "Value")
Set rng1 = Sheet1.Range("A2")
Sheet2.Cells.Clear
Sheet2.Range("A1:D1") = Array("Addr", "Row", "Column", "Value")
Set rng2 = Sheet2.Range("A2")
' open workbook and scan worksheets
Set wb = Workbooks.Open(myfile, ReadOnly:=True)
For Each ws In wb.Sheets
If ws.Name Like "CH_recipe_#" Then
Call scanSheet(ws, rng1)
msg = msg & vbLf & ws.Name
ElseIf ws.Name = "Customer Details" Then
Call scanCustomer(ws, rng2)
msg = msg & vbLf & ws.Name
End If
Next
wb.Close savechanges:=False
MsgBox "Sheets scanned" & msg
End Sub
Sub scanSheet(ws As Worksheet, ByRef rng As Range)
Dim cell As Range, ar, s As String
Dim i As Long, n As Long
' old template layout
s = "D13,A15,C15,D15,E15,G15,J15,N15," ' process details
s = s & "E20:P24,E41:P41," ' Carrier, Gas 2-12, usage
s = s & "C45,D45,E45,G45,I45,K45,M45,N45,P45," ' exhaust line
s = s & "C48" ' notes and remarks
ar = Split(s, ",")
For i = 0 To UBound(ar)
For Each cell In ws.Range(ar(i))
If cell.Value <> "" Then
rng = ws.Name
rng.Offset(, 1) = cell.Address(0, 0)
rng.Offset(, 2) = cell.Row
rng.Offset(, 3) = cell.Column
rng.Offset(, 4) = cell.Value
Set rng = rng.Offset(1)
End If
Next
Next
Debug.Print ws.Name & " Done"
End Sub
Sub scanCustomer(ws As Worksheet, ByRef rng As Range)
Dim cell As Range, ar, s As String
Dim i As Long, n As Long
' old template layout
s = "B14:B25," ' contact details
s = s & "B28:B29," ' existing install
s = s & "B32:B35," ' hook up
s = s & "A38" ' remarks
ar = Split(s, ",")
For i = 0 To UBound(ar)
For Each cell In ws.Range(ar(i))
If cell.Value <> "" Then
rng = cell.Address(0, 0)
rng.Offset(, 1) = cell.Row
rng.Offset(, 2) = cell.Column
rng.Offset(, 3) = cell.Value
Set rng = rng.Offset(1)
End If
Next
Next
Debug.Print ws.Name & " Done"
End Sub

In VBA: Can I use a find function to define a variable that is then used to define a range?

I am writing a code to do some simple excel data manipulation. Basically a column called "End User" has blanks in it and if that cell is blank I want it to pull the name from the parent "External Business Unit"
I wrote the code to do this but the issue is the data I receive changes over time so end user might be in column c one month and column d in another.
My current code is this:
Sub FindColumn()
'Set ws = Sheets("group") 'sheet with data
Dim Lastrow As Long
Lastrow = Range("B" & Rows.Count).End(xlUp).Row
'ws.Select
'Dim xyz As Range
Dim rngaddress As Range
Set rngaddress = Range("A1:Z1").Find("End User", , , xlWhole)
If rngaddress Is Nothing Then
MsgBox "End User column was not found."
Exit Sub
End If
Range(rngaddress, rngaddress).Select
ActiveCell.EntireColumn.Insert
ActiveCell.Value = "Final End User"
ActiveCell.Offset(0, 1).Select
ActiveCell.Value = ""
'Set xyz = Range("A1:Z1").Find("Final End User").Offset(1, 0)'
'Range("xyz" & Lastrow) = "=IF(d2="""",b2,d2)"
Range("C2") = "=IF(d2="""",b2,d2)": Range("C2:C" & Lastrow).FillDown
I successfully used the fine function with a variable to target a specific cell but I am struggling to use it to select a range that goes to the last row. I am not sure if it is because this required two variables or if I just have the syntax wrong. I am t
I attempted to use xyz in place of the C2 data but it would not work (commented out). Is there any way to do this so I do not have to use the the cell address and instead can use the find function, store that info in a variable, and define the range that way?
Example Data set would be
two columns one called external business unit one called end user. External business unit always have a name end user is randomly blank. Creating a column called final end user where blanks are filled with corresponding external business unit values.
Thank You BigBen. Using the resize command worked:
Set xyz = Range("A1:Z1").Find("Final End User").Offset(1, 0)
xyz.Resize(Lastrow) = "=IF(d2="""",b2,d2)"
Once they pointed out it was an issue with how I was using the variable I also got the code to work using this method.
Range(xyz, xyz) = "=IF(d2="""",b2,d2)"
Range(xyz, xyz).Select
Selection.AutoFill Destination:=Range(Range(ActiveCell.Address), Cells(Lastrow, ActiveCell.Column))
Now I will try and use variables for the if function as well.
Insert Column and Fill In Blanks
The Code
Option Explicit
Sub findColumn()
Dim ws As Worksheet
Set ws = ThisWorkbook.Worksheets("group") 'sheet with data
Dim eb As Range
Set eb = ws.Rows(1).Find("External Business Unit", _
LookIn:=xlFormulas, LookAt:=xlWhole)
If eb Is Nothing Then
MsgBox "External Business Unit column was not found."
Else
Dim LastRow As Long
LastRow = ws.Cells(ws.Rows.Count, eb.Column).End(xlUp).Row
Dim eu As Range
Set eu = ws.Rows(1).Find(What:="End User", _
LookIn:=xlFormulas, LookAt:=xlWhole)
If eu Is Nothing Then
MsgBox "End User column was not found."
Else
eu.EntireColumn.Insert
' Get the column strings only now, because 'euCol'
' has surely shifted, but 'ebCol' might have also.
Dim ebCol As String
ebCol = Split(eb.Address, "$")(1)
Dim euCol As String
euCol = Split(eu.Address, "$")(1)
eu.Offset(, -1).Value = "Final End User"
eu.Offset(1, -1).Resize(LastRow - 1).Formula _
= "=IF(" & euCol & "2=""""," & ebCol & "2," & euCol & "2)"
End If
End If
End Sub

Find columns with a specific headers that may be spelled differently

Good evening. I am developing a subroutine for a project whereby the user is able to upload specific data from a separate workbook into the master. The routine will search through the chosen excel file for specific column headers and only copy/paste those desired columns to the master sheet. This is my first coding project and I think I have the process mostly sorted, however there is one bit of functionality that is eluding me: The specific column titles are moderately similar no matter the workbook, except they may vary between full name and abbreviation. For example the title of the column may be "AZM" or it may be "Azimuth". Alternatively one column title may be "N/S", "Northing" or "NS". There will never be multiple of these titles, just the one in the format that the workbook creator decided to go with.
My current code does not currently account for that:
Private Sub CommandButton1_Click()
Application.ScreenUpdating = False
Dim wb As Workbook
Dim filename As String, colName As String
Dim LRow As Long, LCol As Long
Dim pColName As String, MyHead(1 To 8) As String
Dim sCell As Range, PRng As Range
Dim col As Long, pCol As Long
MsgBox "Ensure plan includes MD/INC/AZM/TVD/NS/EW/VS/DLS"
With Application.FileDialog(msoFileDialogOpen) 'Open file explorer
.AllowMultiSelect = False 'Only allow one file to be chosen
.Filters.Add "Excel Files", "*.xlsx; *.xlsm; *.xls; *.xlsb", 1 'Limit selection options to excel files
If .Show Then
filename = .SelectedItems(1) 'Assign file path to variable filename
Set wb = Workbooks.Open(filename:=filename) 'Set selected Excel file to variable wb
MyHead(1) = "MD"
MyHead(2) = "Inc"
MyHead(3) = "Azimuth"
MyHead(4) = "TVD"
MyHead(5) = "N/S"
MyHead(6) = "E/W"
MyHead(7) = "VS"
MyHead(8) = "DLS"
If Not IsEmpty(ThisWorkbook.Worksheets("5D-Lite").Range("M33")) Then
LRow = Cells(Rows.Count, 13).End(xlUp).Row 'Find the last row of data in column M from previous plan
LCol = Cells(LRow, Columns.Count).End(xlToLeft).Column 'Find the last column of data in the last row
ThisWorkbook.Worksheets("5D-Lite").Range("M33:" & Col_Letter(LCol) & LRow).ClearContents 'Clear the contents of the range determined by the Last functions
End If
With wb.Worksheets(1)
For i = LBound(MyHead) To UBound(MyHead)
Set sCell = .Range("A1:R50").Find(What:=MyHead(i), LookIn:=xlValues, LookAt:=xlWhole, _
SearchOrder:=xlByRows, MatchCase:=False, SearchFormat:=False) 'Search for the desired directional plan items in column headers
If Not sCell Is Nothing Then
col = sCell.Column 'Located item's column number
pCol = i + 12 'Column number in master workbook to paste in
colName = Split(.Cells(, col).Address, "$")(1) 'Located item's column letter
pColName = Split(.Cells(, pCol).Address, "$")(1) 'Column letter in master workbook to paste in
LRow = FindLastNumeric() 'Find the final row with numeric data
Set PRng = .Range(sCell.Address & ":" & colName & LRow) 'Set total data range of desired column
wb.Activate
wb.Worksheets(1).Range(PRng.Address).Copy ThisWorkbook.Worksheets("5D-Lite").Range(pColName & "32") 'Copy contents of selected file to the 5D sheet
End If
Next
Range("M32:T" & LRow + 33).NumberFormat = "0.00" 'Assigns numeric formatting to the pasted data range
wb.Close SaveChanges:=False
Set wb = Nothing
End With
Else
MsgBox "No Plan Selected"
End If
End With
Application.ScreenUpdating = True
End Sub
Is there any way to modify the .Find function or the MyHead(i) variables to account for multiple possible variations on the same header name? Thanks for any ideas.
It looks to me like you need prepare some kind of a dictionary. A simple solution would be to have an Excel table which stores all the information, which is stored in an array on startup (for quicker references) and then used to translate inputs to outputs. It could look something like this:
POSSIBLE_SOURCE VALID_NAME
appl apple
apple apple
orng orange
orange orange
To use this you would search the source files for matches in POSSIBLE_SOURCE column, find corresponding value in VALID_NAME column and use the latter for whatever you need to do with the input row.

Find range of cells, when given 2 Dates

I have a table with numbers from 1 to 10. (Starting from D2 to M2)
Suppose in A1 there is 03/09/2019
AND in B1 there is 06/09/2019
AND in C1 there is Hello
In COLUMN A I have a multiple series of words starting from A3 to A10
Here is an Example of the Excel Table
What I would like to do is: Search for the word Student in Column A, when I find it, get the numbers from A1 --> 3
and A2 --> 6 and write the word Hello that is in C1 in the cells that go to 3 to 6 in the row of the finded word Student
So my output would be like:
This is my code so far:
Dim Cell As Range
Columns("A:A").Select
Set Cell = Selection.Find(What:="Student", After:=ActiveCell, LookIn:=xlFormulas, _
LookAt:=xlWhole, SearchOrder:=xlByRows, SearchDirection:=xlNext, _
MatchCase:=False, SearchFormat:=False)
If Cell Is Nothing Then
MsgBox "Word not found"
Else
MsgBox "Word found"
End If
Basically I can find the word Student but don't know how to write the word Hello in the cells between 3 to 6
A few notes regarding the code below (not tested!).
1) Always try use worksheet qualifiers when working with VBA. This will allow for cleaner code with less room for unnecessary errors
2) When using .Find method I use LookAt:=xlWhole because if you do not explicitly define this your code will use the last known method that you would have used in Excel. Again, explicit definition leaves less room for error.
3) Try include error handling when you code. This provides “break points” for easier debugging in the future.
4) You can make the below much more dynamic that it currently is. But I'll leave that up to you to learn how to do!
Option Explicit
Sub SearchAndBuild()
Dim rSearch As Range
Dim lDayOne As Long, lDayTwo As Long
Dim lColOne As Long, lColTwo As Long
Dim sHello As String
Dim wsS1 As Worksheet
Dim i As Long
'set the worksheet object
Set wsS1 = ThisWorkbook.Sheets("Sheet1")
'store variables
lDayOne = Day(wsS1.Range("A1").Value)
lDayTwo = Day(wsS1.Range("B1").Value)
sHello = wsS1.Range("C1").Value
'find the student first
Set rSearch = wsS1.Range("A:A").Find(What:="Student", LookAt:=xlWhole)
'error handling
If rSearch Is Nothing Then
MsgBox "Error, could not find Student."
Exit Sub
End If
'now loop forwards to find first date and second date - store column naumbers
'adjust these limits where necessary - can make dynamic
For i = 4 To 13
If wsS1.Cells(2, i).Value = lDayOne Then
lColOne = i
End If
If wsS1.Cells(2, i).Value = lDayTwo Then
lColTwo = i
Exit For
End If
Next i
'now merge the range
wsS1.Range(wsS1.Cells(rSearch.Row, lColOne), wsS1.Cells(rSearch.Row, lColTwo)).Merge
'set the vvalue
wsS1.Cells(rSearch.Row, lColOne).Value = sHello
End Sub
This is just one way to approach the problem. Hopefully this helps your understanding!
No need for a loop here - just find your value and parse the dates. Assuming your value to be found exists in Column A and your table starts in Column D, there is clear relationship between the columns which is Day(date) + 3.
Sub Test()
Dim ws As Worksheet: Set ws = ThisWorkbook.Sheets("Sheet1")
Dim lr As Long, Found As Range
Dim date_a As Long, date_b As Long
lr = ws.Range("A" & ws.Rows.Count).End(xlUp).Row
Set Found = ws.Range("A2:A" & lr).Find("Student", LookIn:=xlValues)
If Not Found Is Nothing Then
date_a = Day(Range("A1")) + 3
date_b = Day(Range("B1")) + 3
With ws.Range(ws.Cells(Found.Row, date_a), ws.Cells(Found.Row, date_b))
.Merge
.Value = ws.Range("C1")
End With
Else
MsgBox "Value 'Student' Not Found"
End If
End Sub
I've tried this:
Dim ThisRow As Long
Dim FindWhat As String
FindWhat = "Student"
Dim MyStart As Byte
Dim MyEnd As Byte
MyStart = Day(Range("A1").Value) + 3 'we add +3 because starting 1 is in the fourth column
MyEnd = Day(Range("B1").Value) + 3 'we add +3 because starting 1 is in the fourth column
Dim SearchRange As Range
Set SearchRange = Range("A3:A10") 'range of values
With Application.WorksheetFunction
'we first if the value exists with a count.
If .CountIf(SearchRange, FindWhat) > 0 Then 'it means findwhat exists
ThisRow = .Match(FindWhat, Range("A:A"), 0) 'we find row number of value
Range(Cells(ThisRow, MyStart), Cells(ThisRow, MyEnd)).Value = Range("C1").Value
Application.DisplayAlerts = False
Range(Cells(ThisRow, MyStart), Cells(ThisRow, MyEnd)).Merge
Application.DisplayAlerts = True
Else
MsgBox "Value 'Student' Not Found"
End If
End With
Note I've used worksheets function COUNTIF and MATCH. MATCH will find the position of an element in a range, so if you check the whole column, it will tell you the row number. But if it finds nothing, it will rise an error. Easy way to avoid that is, first, counting if the value exists in that range with COUNTIF, and if it does, then you can use MATCH safely
Also, note that because we are using MATCH, this function only finds first coincidence, so if your list of values in column A got duplicates, this method won't work for you!.

Excel VBA or not to VBA, replace text if different between two cells

I have a quandary, and I don't know if it will work better using excel VBA or not. Thinking about it I believe VBA will work best, but I don't know how to make it work.
I have two pages in a workbook, one is the form, the other is the database, I want the pulldown menu from the form to populate the rest of the form. It does... what I want then is to be able to change the value of the form press submit, and the new data will overwrite the old data.
Is this possible?
Here is the link to the sheet I'm talking about.
http://dl.dropbox.com/u/3327208/Excel/Change.xlsx
Here is the script I am working with now...it takes the sheet, copies everything to a row takes that row, moves it to the NCMR Data tab and then clears the data on the new row from the original sheet.
This code technically could work, but what I need to do is make it use the same concept, but instead of creating a new row at the end of the sheet find the original line and replace the data from B to U in whatever row it was originally in.
I know it's possible, I just don't know how.
'Copy Ranges Variable
Dim c As Variant
'Paste Ranges Variable
Dim p As Range
'Setting Sheet
Set wsInt = Sheets("Form")
Set wsNDA = Sheets("Data")
Set p = wsInt.Range("A14")
With wsInt
c = Array(.Range("B11"))
End With
For i = LBound(c) To UBound(c)
p(i + 1).Value = c(i).Value
Next
With wsNDA
Dim Lastrow As Long
Lastrow = .Range("B" & Rows.Count).End(xlUp).Row + 1
wsInt.Rows("14").Copy
With .Rows(Lastrow)
.PasteSpecial Paste:=xlPasteFormats
.PasteSpecial Paste:=xlPasteValues
.Interior.Pattern = xlNone
End With
With .Range("A" & Lastrow)
If Lastrow = 3 Then
.Value = 1
Else
.Value = Val(wsNDA.Range("A" & Lastrow - 1).Value) + 1
End If
.NumberFormat = "0#######"
End With
End With
End Sub
I found this code:
Sub CopyTest()
Dim selrow As Range, rngToCopy As Range
With Worksheets("PD DB")
Set selrow = .Range("B:B").Find(.Range("BA1").Value)
'find the cell containing the value
Set rngToCopy = Union(selrow.Offset(0, 9), selrow.Offset(0, 12))
'use offset to define the ranges to be copied
rngToCopy.Copy Destination:=Worksheets("Edit Sheet").Range("B50")
'copy and paste (without Select)
End With
End Sub
As far as I can tell this will do what I want mostly, but I can't seem to figure out where to break it up to add it where I need to to make it work the way I want it to.
What I can tell is this, it will copy and paste, but I want to make sure it will paste the data into row it finds, and not overwrite the number of said row.
Can someone help make that possible with the two scripts I have here?
Not tested, but should get you started. I added a 3rd sheet (shtMap) to hold the mmapping between the cell addresses on your form and the column numbers on the "Data" sheet. Useful to name your sheets directly in the VB editor: select the sheet and set the name in the property grid.
*EDIT:*If you want to trigger the transfer on selecting a record id from a list in Range AG3 then place this code in the code module for that worksheet:
Private Sub Worksheet_Change(ByVal Target As Range)
Static bProcessing As Boolean
Dim rng As Range
If bProcessing Then Exit Sub
Set rng = Target.Cells(1)
If Not Application.Intersect(rng, Me.Range("AG3")) Is Nothing Then
bProcessing = True
'this is where you call your macro to transfer the record
bProcessing = False
End If
End Sub
You could use something like this for the transfer:
Public Enum XferDirection
ToForm = 1
ToDataSheet = 2
End Enum
Sub FetchRecord()
TransferData XferDirection.ToForm
End Sub
Sub SaveRecord()
TransferData XferDirection.ToDataSheet
End Sub
Sub TransferData(Direction As XferDirection)
Dim rngMap As Range, rw As Range, f As Range, dataCell As Range
Dim formCell As Range, dataCol As Long, dataRow As Long
Dim sId As String
sId = shtForm.Range("AG3").Value
Set f = shtData.Columns(1).Find(sId, LookIn:=xlValues, lookat:=xlWhole)
If Not f Is Nothing Then
dataRow = f.Row
Else
'what do you want to do here?
' record doesn't exist on data sheet
MsgBox "Record '" & sId & "' not found on '" & shtForm.Name & "' !"
Exit Sub
End If
Set rngMap = shtMap.Range("A2:B10")
For Each rw In rngMap.Rows
'the cell on the edit form
Set formCell = shtForm.Range(rw.Cells(1).Value)
'column # on datasheet
Set dataCell = shtData.Cells(dataRow, rw.Cells(2).Value)
If Direction = XferDirection.ToDataSheet Then
dataCell.Value = formCell.Value
Else
formCell.Value = dataCell.Value
End If
Next rw
End Sub
Matt, there are two approaches I would take. The first is use find(), which returns a range object, then append ".row" so that you'll be able to modify the row on Sheet2 (wsNDA, I think). You may want to test that find() doesn't return Nothing.
Dim foundRow as Long
Dim foundRng as Range
set foundRng = wsNDA.find(wsInt.Range("B11").Value, ...)
If Not foundRng is Nothing Then
foundRow = foundRng.row
End If
'method without check: foundRow = wsNDA.find(wsInt.Range("B11").Value, ...).Row
The other is to use a Dictionary object. I'm not sure what you'd want for the key, but the item could be the row on the data sheet. When you make the change to what's on the form, check against the key and grab its item (the corresponding row) to determine where you need to replace the values.

Resources