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!.
Related
Is it possible to find a row with 2 criteria?
I'm importing survey anwsers to a worksheet, now I want to find the answers of a specified person
I need to find the row in the worksheet(ImportLimesurvey) that has 2 specified cell values:
In that row:
the value of the C-cell has to be one of the highest value in that column (I used the function Application.WorksheetFunction.Max(rng))
This value means how much of the survey is filled in. The highest value stands in multiple answer-rows. The highest value is different for every survey. (example, if a survey has 7 pages and the participant fills in all pages :the highest value is 7 for that person, but if the person didn't complete that survey, the value could be e.g. 3), So the filter of the highest value is if the participant completed the whole survey.
the value of the L-cell has to be the same as the cell (Worksheets("Dataimport").Range("M2")
M2= accountnumber of the person I need the answers from
The correct row has to be pasted to (Worksheets("Dataimport").Range("A7")
This is my current code:
Dim g As Range
Dim rng As Range
Set rng = Worksheets("ImportLimesurvey").Range("C:C")
d = Application.WorksheetFunction.Max(rng)
With Worksheets("ImportLimesurvey").Range("L:L")
Set g = .Find(Worksheets("Dataimport").Range("M2"), LookIn:=xlValues)
g.Activate
End With
e = Range("C" & (ActiveCell.Row))
If e = d Then
ActiveCell.EntireRow.Copy _
Destination:=Worksheets("Dataimport").Range("A7")
End If
The problem here is that he finds the row with the right account number, but the answer with the C-value isn't always the highest. It picks (logically) just the first row with that accountnumber. So how can I find the row that matches those 2 criteria?
Thanks in advance
P.S. I'm new to VBA so I tried to be as specific as possible but if you need any additional info, just ask for it ;)
dmt32 forom mrexcel.com found a solution.
Link to topic: https://www.mrexcel.com/board/threads/find-row-with-2-criteria.1157983/
His code works fine:
Sub FindMaxValue()
Dim FoundCell As Range, rng As Range
Dim MaxValue As Long
Dim Search As String, FirstAddress As String
Dim wsDataImport As Worksheet, wsImportLimesurvey As Worksheet
With ThisWorkbook
Set wsDataImport = .Worksheets("Dataimport")
Set wsImportLimesurvey = .Worksheets("ImportLimesurvey")
End With
Search = wsDataImport.Range("M2").Value
If Len(Search) = 0 Then Exit Sub
With wsImportLimesurvey
Set FoundCell = .Range("L:L").Find(Search, LookIn:=xlValues, lookat:=xlWhole)
If Not FoundCell Is Nothing Then
FirstAddress = FoundCell.Address
Do
With FoundCell.Offset(, -9)
If .Value > MaxValue Then Set rng = FoundCell: MaxValue = .Value
End With
Set FoundCell = .Range("L:L").FindNext(FoundCell)
If FoundCell Is Nothing Then Exit Do
Loop Until FoundCell.Address = FirstAddress
rng.EntireRow.Copy wsDataImport.Range("A7")
MsgBox Search & Chr(10) & "Record Copied", 64, "Match Found"
Else
MsgBox Search & Chr(10) & "Record Not Found", 48, "Not Found"
End If
End With
End Sub
Still thanks for the tips.
Firstly, Visual Basic conceptual topics is a great read to help in writing 'better' code. The biggest thing I encourage is to use meaningful variable names.
It's much easier to understand your code when you have variable names like HighestCount or TargetSheet etc. rather than names like a or b etc.
The answer to your question is yes.
I would write something like this:
Option Explicit
Public Function HighestSurveyRow(ByVal TargetAccountNumber As Long) As Long
Dim ImportLimeSurveySheet As Worksheet
Set ImportLimeSurveySheet = ThisWorkbook.Sheets("ImportLimeSurvey")
Dim LastRow As Long
Dim TargetRow As Long
Dim SurveyCountArray As Variant
Dim ArrayCounter As Long
With ImportLimeSurveySheet
ArrayCounter = 1
LastRow = .Cells(.Rows.Count, 12).End(xlUp).Row
ReDim SurveyCountArray(1 To LastRow, 1 To 2)
For TargetRow = 1 To LastRow
If .Cells(TargetRow, 12).Value = TargetAccountNumber Then
SurveyCountArray(ArrayCounter, 2) = TargetRow
SurveyCountArray(ArrayCounter, 1) = .Cells(TargetRow, 3).Value
ArrayCounter = ArrayCounter + 1
End If
Next TargetRow
End With
Dim ResultArray(1 To 2) As Variant
Dim ArrayElement As Long
For ArrayElement = 1 To UBound(SurveyCountArray, 1)
If SurveyCountArray(ArrayElement, 1) > ResultArray(1) Then
ResultArray(1) = SurveyCountArray(ArrayElement, 1)
ResultArray(2) = SurveyCountArray(ArrayElement, 2)
End If
Next ArrayElement
HighestSurveyRow = ResultArray(1)
End Function
Sub FindRowForSurveyResults()
With ThisWorkbook.Sheets("DataImport")
.Range("A7").Value = HighestSurveyRow(.Range("M2").Value)
End With
End Sub
It's split into a Function and a Subroutine. The Function executes most of the code and returns the row number. The Sub calls this function and writes this returned value to cell A7 on "DataImport".
The sub can be broken down as follows;
Using a with statement helps reduce code clutter of defining the worksheet twice.
The only thing the sub is doing is assigning a value to cell A7. To get the value it calls the function and assigns the parameter TargetAccountNumber as the value from cell M2.
The function can be broken down into the following steps;
All variables are declared and the target worksheet for the function is set.
The LastRow of column L is found to establish our maximum length of the Array and search range.
The Loop searches from Row 1 to the LastRow and compares the values from column L. If it matches the TargetAccountNumber parameter then the column C value and the row number are stored into the Array.
Once the Loop is done, another Loop is run to find the highest number. The first iteration will always store the first row's data. Each iteration after that compares the values stored in the SurveyCountArray with the current value of ResultArray(1) and if the value is greater, ResultArray(1) is updated with the value, ResultArray(2) is updated with the Row number.
Once the 2nd loop is done, the Row in ResultArray(2) is assigned to the function for the Sub to write to the worksheet.
It can definately be improved and refined to work faster and more efficiently, especially if you have a very large data set, but this should help get you thinking about ways you can use loops and arrays to find data.
Note: There could be duplicate rows for the outcome (say a user submits the same survey 3 times with the same answers), which I haven't tested for - I think this code would return the highest row number that matches the required criteria but could be tweaked to throw an error or message or even write all row numbers to the sheet.
Hello all this is my first question so I will try my best to format this best I can.
Quick description without specific cell names below
I am trying to write a macro where a user enters a value(X) and a macro searches a range of cells for a value(X), and then the macro returns the cell values in the 3 spaces next to wherever the location of value(X) is.
A couple things that are making this impossible to solve are the fact that the user inputs the value on Sheet1 and the value is moved to Sheet2 by a formula, I can't seem to figure out how to use Find where the values I am searching for isn't already defined in the macro.
The other thing making this difficult is that the range is not strictly definable either, as the list could be longer or shorter than it currently is, and I can't know when it will change. So the range of the search has to start based on which List is input by the User and needs to go until it hits a blank spot.
For example: Range.("C7:D10") wont work because the user could enter new info that changes the working range as described below.
Below is a screenshot with further explanation
https://i.stack.imgur.com/wlnhg.jpg
So in this screenshot the cells C3 and D3 are imported values from Sheet1.
C3 is (=Sheet1!B2)
D3 is (=Sheet1!B3)
The idea is that the macro runs and searches down column A till it has a match with C3.
Then the search function moves over two cells and searches down till it has a match with D3 or until it hits an empty space.
I don't know how to ask a macro to search based on an imported value, and I don't know how to ask it to search this weird certain range I need. The idea is that someone at my work could come along and add a row below C10 and add the necessary information and the macro would still work and search to C11 and there would be a blank space after to tell the macro to stop.
After the search finds a match for D3 it would return the values adjacent to the match to the corresponding cells at the top, E3, F3, and G3.
I hope this question is asked in a way that people can understand, I am very tired so can't tell if I wrote something that makes sense. Thank you for reading my post, y'all are the best!!
Search Twice
Workbook Download (Dropbox)
Sub SearchTwice()
Const cSheet As String = "Sheet2" ' Source Worksheet Name
Const cList As String = "C3" ' List Cell Range Address
Const cName As String = "D3" ' Name Cell Range Address
Const cListCol As String = "A" ' List Column Letter
Const cNameCol As String = "C" ' Name Column Letter
Const cFirst As Long = 6 ' First Row
Const cCol As Long = 3 ' Number of Columns
Dim rng1 As Range ' Find List Cell Range
' Found Name Cell Range
Dim rng2 As Range ' Next List Cell Range
' Name Search Range
Dim strList As String ' List
Dim strName As String ' Name
' In Source Worksheet
With ThisWorkbook.Worksheets(cSheet)
' Write from List Cell Range to List.
strList = .Range(cList)
' Write from Name Cell Range to Name.
strName = .Range(cName)
' Check if Cell Ranges do NOT contain data.
If strList = "" Or strName = "" Then ' Inform user.
MsgBox "Missing List or Name.", vbCritical, "Missing data"
Exit Sub
End If
' In List Column
With .Columns(cListCol)
' Create a reference to Find List Cell Range (rng1) containing
' List (strList).
Set rng1 = .Find(strList, .Cells(cFirst - 1), xlValues, xlWhole)
' Check if List has not been found.
If rng1 Is Nothing Then ' Inform user and exit.
MsgBox "The list '" & strList & "' has not been found", _
vbCritical, "List not found"
Exit Sub
End If
' Create a reference to Next List Cell Range (rng2).
Set rng2 = .Find("*", .Cells(rng1.Row), xlValues, xlWhole)
End With
' In Name Column
With .Columns(cNameCol)
' Check if the row of Next List Cell Range (rng2) is greater than
' the row of List Cell Range (rng1) i.e. if a cell with a value
' has been found below List Cell Range (rng1) in List Column.
If rng2.Row > rng1.Row Then ' Next List Cell Range FOUND.
' Create a reference to Name Search Range (rng2) which spans
' from the cell below Find List Cell Range (rng1) to the cell
' above the Next List Cell Range (rng2), but in Name Column.
Set rng2 = .Cells(rng1.Row + 1).Resize(rng2.Row - rng1.Row - 1)
Else ' Next List Cell Range NOT found.
' Create a reference to Name Search Range (rng2) which spans
' from the cell below Find List Cell Range (rng1) to the bottom
' cell, but in Name column.
Set rng2 = .Cells(rng1.Row + 1).Resize(.Rows.Count - rng1.Row)
End If
End With
' In Name Search Range (rng2)
With rng2
' Create a reference to Found Name Cell Range (rng1).
Set rng1 = .Find(strName, .Cells(.Rows.Count), xlValues, xlWhole)
End With
' Check if Name has not been found.
If rng1 Is Nothing Then ' Inform user and exit.
MsgBox "The name '" & strName & "' has not been found", _
vbCritical, "Name not found"
Exit Sub
End If
' Remarks:
' Source Range is calculated by moving the Found Name Cell Range (rng1)
' one cell to the right and by resizing it by Number of Columns (cCol).
' Target Range is calculated by moving the Name Cell Range one cell
' to the right and by resizing it by Number of Columns (cCol).
' Copy values of Source Range to Target Range.
.Range(cName).Offset(, 1).Resize(, cCol) _
= rng1.Offset(, 1).Resize(, cCol).Value
End With
' Inform user of succes of the operation.
MsgBox "The name '" & strName & "' was successfully found in list '" & _
strList & "'. The corresponding data has been written to the " _
& "worksheet.", vbInformation, "Success"
End Sub
One reason for being tired is that you tried to go for the kill before you had set up for slaughter. The solution below took an hour to prepare and 10 minutes to encode. Paste the entire code in a standard code module and call the function MatchRow either from the Immediate window (? MatchRow) or from your own code as shown in the test proc further down.
Option Explicit
Enum Nws ' worksheet navigation
' 01 Mar 2019
NwsCriteriaRow = 3
NwsList = 1 ' Columns: (1 = A)
NwsID = 3
NwsNumber ' (undefined: assigns next integer)
End Enum
Function MatchRow() As Long
' 01 Mar 2019
' return 0 if not found
Dim Ws As Worksheet
Dim Rng As Range
Dim R As Long
' The ActiveWorkbook isn't necessarily ThisWorkbook
Set Ws = ActiveWorkbook.Worksheets("Sheet2") ' replace tab's name here
With Ws
Set Rng = .Range(.Cells(NwsCriteriaRow, NwsList), .Cells(.Rows.Count, NwsList).End(xlUp))
R = FindRow(.Cells(NwsCriteriaRow, NwsID).Value, Rng, True)
If R Then ' skip if no match was found
Set Rng = .Cells(R + 1, NwsID)
Set Rng = .Range(Rng, Rng.End(xlDown))
MatchRow = FindRow(.Cells(NwsCriteriaRow, NwsNumber).Value, Rng)
End If
End With
End Function
Private Function FindRow(Crit As Variant, _
Rng As Range, _
Optional ByVal SearchFromTop As Boolean) As Long
' 01 Mar 2019
' return 0 if not found
Dim Fun As Range
Dim StartCell As Long
With Rng
If SearchFromTop Then
StartCell = 1
Else
StartCell = .Cells.Count
End If
Set Fun = .Find(What:=Crit, _
After:=.Cells(StartCell), _
LookIn:=xlValues, _
LookAt:=xlWhole, _
MatchCase:=False)
End With
If Not Fun Is Nothing Then FindRow = Fun.Row
End Function
The function MatchRow returns the row number of Sheet2 where D3 is found, searching only that part of column D which belongs to the list identified in C3. The function returns 0 if no match was found, either of the list or the ID.
You didn't specify what you want to do with the found row. The procedure below will return data from that row. You might use the capability to address the cells to write to them instead.
Private Sub RetrieveData()
Dim R As Long
R = MatchRow
MsgBox "ID = " & Cells(R, NwsID).Value & vbCr & _
"Number = " & Cells(R, NwsNumber).Value
End Sub
Being intended for testing only the above proc doesn't specify the worksheet and, therefore, returns data from the ActiveSheet, presumed to be Sheet2.
VBA Solution
I think the non-VBA solution is ideal here, but I will leave this here separately just in case. This should work for your situation assuming no values in your tables are blank.
Sub Test()
Dim ws As Worksheet: Set Worksheet = ThisWorkbook.Sheets("Sheet2")
Dim iList As Range, iName As Range
Dim aLR As Long, cLR As Long
aLR = ws.Range("A" & ws.Rows.Count).End(xlUp).Row
Set iList = ws.Range("A1:A" & aLR).Find(ws.Range("C3"), LookIn:=xlWhole)
If Not iList Is Nothing Then
cLR = iList.Offset(0, 2).End(xlDown).Row
Set iName = ws.Range(ws.Cells(iList.Row, 3), ws.Cells(cLR, 3)).Find(ws.Range("C4"), LookIn:=xlWhole)
If Not iName Is Nothing Then
ws.Range("E3:G3").Value = iName.Offset(0, 1).Resize(1, 3).Value
End If
End If
End Sub
Non VBA Solution
Convert your two list ranges to tables
Change the name of your tables by (Formulas Tab > Name Manager > Select Table/Change Name). Specifically, you will want to change the names to your desired list name. (Table 1 Name = List1 & Table 2 Name = List2)
Next, drop these formulas inside E3, F3, & G3
E3 = VLOOKUP(D3, Indirect(C3), 2, 0)
F3 = VLOOKUP(D3, Indirect(C3), 3, 0)
G3 = VLOOKUP(D3, Indirect(C3), 4, 0)
This wil update dynamically as your table sizes expand. you can also add as many tables as you'd like and this will continue to work.
In use, it looks something like below
My last suggestion would be to nest each formula above inside an IFERROR()
I am comparing two excelsheets in the same workbook.
I want to check whether the records from sheet1 are exactly same as records in sheet2 based on common Question_id(Column A of both worksheets)
This question_id(column A) has values such as
1
1a
1a.1
1a.1a
1a.1b
1a.1c
2
2a
2a.1
2a.1a
2a.1b
2a.1c etc....
I want to compare the records based on this Question_id(Column A Value).
If Question_id is same and records(the remaining row) are not same then am coloring those records in red background(only specific cells and not the whole row)
For the same, I have following code.
Sub RunCompare()
Call compareSheets("Sheet1", "Sheet2")
End Sub
Sub compareSheets(shtSheet1 As String, shtSheet2 As String)
Dim mycell As Range
Dim mydiffs As Integer
Application.ScreenUpdating = false
'Color Uncommon records in Red Background
For Each mycell In ActiveWorkbook.Worksheets(shtSheet2).UsedRange
If Not mycell.Value = ActiveWorkbook.Worksheets(shtSheet1).Cells(mycell.Row, mycell.Column).Value Then
mycell.Interior.Color = vbRed
mydiffs = mydiffs + 1
End If
Next
'Display no. of differences
MsgBox mydiffs & " differences found", vbInformation
ActiveWorkbook.Sheets(shtSheet2).Select
MsgBox "Data Scrubbed Successfully..."
Application.ScreenUpdating = True
End Sub
The above code runs fine when I have same sequence of Question_id (and therefore of records) in both the excelsheets.
Assume I have different sequence of Question_id (and therefore of records) in both the sheets.
Then how I can achieve this...?
Something Like using where clause in my code Where Sheet1.Question_id = Sheet2.Question_id
i.e. I'll pick up question_id and the full row from sheet1 and I will compare it against records in sheet2 based on matching Question_id(value of Column A) only.
Can someone tell where I can put the condition and what type of condition so that, even if both the excelsheets have random sequences of Question_id; I will be able to compare the records from sheet1 and sheet2.
EDIT: on 23rd March 2015
I have changed the code using find() method instead of loops as below:
Still I havn't arrived at my solution.
Here am trying to list Question_Ids of all non-matching rows from sheet2 in sheet3 - Column A.
Option Explicit
Sub test()
Dim rng As Range, c As Range, cfind As Range, mycell As Range, cfindRow As Range
On Error Resume Next
Worksheets("Sheet3").Cells.Clear
With Worksheets("Sheet2")
Set rng = .Range(.Range("A2"), .Range("a2").End(xlDown))
For Each c In rng
With Worksheets("Sheet1")
Set cfind = .Columns("A:A").Cells.Find _
(what:=c.Value, lookat:=xlWhole)
'Find method always returns Range; So the following line should be something If cfind is not Nothing OR cfind <> Nothing (Both the syntaxes are wrong. Suggest me the right syntax please.
If cfind = 1 Then
'Here please tell me how to reference a whole row based on Column A value
'Here using cfind and again using mycell is something wrong as mycell variable again compares rows in sheet2 with rows in sheet1 which include Question_Id too.
Set mycell = ActiveWorkbook.Worksheets("Sheet2").UsedRange.End(xlDown)
'My both the excelsheets have values from columns A to AD. Still I want to make the code for all used Ranges of columns instead of only A to AD.
Set cfindRow = Worksheets("Sheet1").Rows("A2:AD").Cells.Find _
(what:=mycell.Value, lookat:=xlWhole)
'Find method always returns Range; So the following line should be something If cfindRow is not Nothing OR cfindRow <> Nothing (Both the syntaxes are wrong. Suggest me the right syntax please.
If cfindRow = 1 Then
'MsgBox "Match Found" 'Right Now do Nothing
End If
Else
' mycell.Interior.Color = vbRed
' mydiffs = mydiffs + 1
'Copy the question numbers to sheet3 either if they are new in new sheet (Sheet2) or content against them (in the whole row-any column value) is changed.
cfind.Copy Worksheets("sheet3").Cells(Rows.Count, "A").End(xlUp).Offset(1, 0)
End If
End With
Next c
Application.CutCopyMode = False
End With
MsgBox "Data Scrubbed Successfully..."
End Sub
Can someone tell me how to refer those ranges based on key column values?
My new approach towards solution:
(It may be a hint to give me answer on how to reference Row values based on key column)
Getting row indices of both the sheets; column A values
(Question_Id's) i.e.
c.Row and cfind.Row
Then
Check If(Sheet2.Cells(c.Row, Columns) = Sheet1.Cells(cfind.Row,
Columns) (To compare columns against matching Question_Ids only.)
So Finally this what all am trying to achieve :
1)Compare two sheets based on key column:
Pick up the Question_Id from Sheet2 - column A and compare it against column A in Sheet1. If the key columns from both the sheets match and also the contents against them(the complete row) matches- then Do nothing.
If the key column value(Question_Id - Column A) matches but values(Row) against it do not match them color those specific cells (Only cells) and not the whole row in Red background.
The Question_Id's which are there in sheet2 but not in sheet1 should be listed under first column in sheet3. Starting from A2.
The Question_Id's which are there in sheet1 but not in sheet2 should be listed under second column in sheet3. Starting from B2.
I am basing my code off of your first approach, because I found it simpler and more readable than the second approach.
We'll just do the most naive algorithm, which is to iterate through every row in the used range of both worksheets. (The fastest algorithm would probably be to sort both ranges in memory and then compare, but simplicity of code over performance optimization for now.)
Sub compareSheets(shtSheet1 As String, shtSheet2 As String)
Dim range1 As Range, range2 as Range
Dim mydiffs As Integer, row1 As Integer, row2 As Integer, col As Integer
Application.ScreenUpdating = False
'First create the two ranges we will be using
Set range1 = ActiveWorkbook.Worksheets(shtSheet1).UsedRange
Set range2 = ActiveWorkbook.Worksheets(shtSheet2).UsedRange
'Iterate through the rows of both ranges
For row1 = 1 To range1.Rows.Count
For row2 = 1 To range2.Rows.Count
'Only process the ranges if they share a common key in column 1
If range1.Cells(row1, 1) = range2.Cells(row2, 1) Then
'If they share the same key, iterate through columns and compare
For col = 1 To WorksheetFunction.Max(range1.Columns.Count, range2.Columns.Count)
If Not range1.Cells(row1, col).Value = range2.Cells(row2, col).Value Then
range1.Cells(row1, col).Interior.Color = vbRed
range2.Cells(row2, col).Interior.Color = vbRed
mydiffs = mydiffs + 1
End If
Next
End If
Next
Next
'Display no. of differences
MsgBox mydiffs & " differences found", vbInformation
Application.ScreenUpdating = True
End Sub
There are some specifications I wasn't sure of. For example, what if a key is in one spreadsheet but not the other? Should it be colored red in the sheet where it exists?
Nevertheless, I think the above code should give you a good start to address your more conceptual questions, and I'm happy to help adjust as needed, so please comment if there are specific requirements I'm missing.
Update 1
Here's the update code after our discussion in chat (link in comments), which takes the unmatched keys from the full outer join and copies them to a third sheet.
Sub compareSheets(shtSheet1 As String, shtSheet2 As String, shtSheet3 As String)
Application.ScreenUpdating = False
Dim range1 As Range, range2 As Range
Dim myDiffs As Integer, row1 As Integer, row2 As Integer, col As Integer
Dim sheet3index1 As Integer, sheet3index2 As Integer, i As Integer
Dim leftKeyMatched As Boolean 'Boolean to keep track of whether the key in sheet1 has a match as we are looping
Dim rightKeysMatched() As Boolean 'Array to keep track of which keys in sheet2 have matches
Set range1 = ActiveWorkbook.Worksheets(shtSheet1).UsedRange
Set range2 = ActiveWorkbook.Worksheets(shtSheet2).UsedRange
ReDim rightKeysMatched(range2.Rows.Count)
For row1 = 1 To range1.Rows.Count
leftKeyMatched = False
For row2 = 1 To range2.Rows.Count
If range1.Cells(row1, 1) = range2.Cells(row2, 1) Then
'We have a match, so mark both sides as matched
leftKeyMatched = True
rightKeysMatched(row2 - 1) = True 'This -1 is because the array indexing starts at 0 but the rows in the spreadsheet start at 1
For col = 1 To WorksheetFunction.Max(range1.Columns.Count, range2.Columns.Count)
If Not range1.Cells(row1, col).Value = range2.Cells(row2, col).Value Then
range1.Cells(row1, col).Interior.Color = vbRed
range2.Cells(row2, col).Interior.Color = vbRed
myDiffs = myDiffs + 1
End If
Next
End If
Next
'Print out the key from sheet1 if it didn't find a match in sheet2
If leftKeyMatched = False Then
sheet3index1 = sheet3index1 + 1
ActiveWorkbook.Worksheets(shtSheet3).Cells(sheet3index1, 1) = range1.Cells(row1, 1)
End If
Next
'Now print out any key that still hasn't been matched in sheet2
For i = 0 To range2.Rows.Count
If rightKeysMatched(i) = False Then
sheet3index2 = sheet3index2 + 1
ActiveWorkbook.Worksheets(shtSheet3).Cells(sheet3index2, 2) = range2.Cells(i + 1, 1) '+1 for same reason as above, index starts at 0 versus 1
End If
Next
'Display no. of differences
'MsgBox myDiffs & " differences found", vbInformation
Application.ScreenUpdating = True
End Sub
I'll take a crack at this
Sub compareSheets(shtSheet1 As String, shtSheet2 As String)
Dim mycell As Range
Dim mydiffs As Integer
Dim ws1 as WorkSheet
Dim ws2 as WorkSheet
Dim rng as Range
Dim SourceRow as integer
Dim Col as integer
set ws1 = ActiveWorkbook.Worksheets(shtSheet1)
set ws2 = ActiveWorkbook.Worksheets(shtSheet2)
myDiffs = 0
'Application.ScreenUpdating = false 'enable this later, once it's all working
'Color Uncommon records in Red Background
'your key is in column A, so we'll only loop through that column
For sourceRow = 1 to ws2.usedrange.Rows.Count
set rng = ws1.range(ws1.address).find(what:=ws2.cells(sourcerow, 1), LookIn:=xlValues, _
LookAt=xlWhole, MatchCase:=False)
'making an assumption on MatchCase, change as needed
if not rng is Nothing then 'we found the key, now let's look at the rest of the row
col = 2
'loop through the rest of the columns for this row
while col < ws2.usedRange.Columns.Count
'if the cell in the row we just found on sheet1 <> the cell that we were looking for from sheet2
if rng.cells(1,col) <> ws2.cells(sourcerow,col) then
rng.cells(1,col).Interior.Color = vbRed
mydiffs = mydiffs+1
end if
col = col + 1
wend
else
'we didn't find the key. pop up a msgbox. you may want something else
MsgBox ("Sheet2 key: " & ws1.value & " not found on Sheet1")
end if
'Display no. of differences
MsgBox mydiffs & " differences found", vbInformation
ActiveWorkbook.Sheets(shtSheet2).Select
MsgBox "Data Scrubbed Successfully..."
Application.ScreenUpdating = True
End Sub
If you want to find a value in a range use the following:
.Find(What, After, LookIn, LookAt, SearchOrder, SearchDirection, MatchCase, MatchByte, SearchFormat)
Like this :
Application.ScreenUpdating = False
'On Error Resume Next 'Err.Numbers 9, 91 => Find: value not found
Dim findCell as range
ActiveWorkbook.Worksheets(shtSheet2).Select
ActiveWorkbook.Worksheets(shtSheet2).UsedRange.Select
'Color Uncommon records in Red Background
For Each mycell In ActiveWorkbook.Worksheets(shtSheet1).UsedRange
Set findCell = Selection.Find(What:=Trim(mycell.value & ""), LookIn:=xlValues)
If findCell Is Nothing Then
mycell.Interior.Color = vbRed
mydiffs = mydiffs + 1
End If
Next
Note :
Please change Application.ScreenUpdating = True to Application.ScreenUpdating = False
And for more information use this MSDN article
And for using a function like that you want:
Public Function look_up_id (r as Range) As Variant
'
'Function body
'
End Function
'....
Call look_up_id(ActiveWorkbook.Worksheets(shtSheet2).Range("A:A", table))
'....
I am trying to create a form which hopefully updates the list of values for a particular dropdown list automatically (without VBA codes) upon user's input immediately.
Here is the form that the user will see:
Currently, both Columns F and H is based on a data-validation formula:
INDIRECT("VList!"&SUBSTITUTE(ADDRESS(1,MATCH($B11,VList!$1:$1,0),1),"1","")&"2:"&SUBSTITUTE(ADDRESS(1,MATCH($B11,VList!$1:$1,0),1),"1","")&COUNTA(INDIRECT("VList!"&ADDRESS(1,MATCH($B11,VList!$1:$1,0),4)&":"&ADDRESS(100,MATCH($B11,VList!$1:$1),4))))
... where VList refers to the sheet as shown below:
So my question here is, based on the Project Name in Column B, is there a way to update the list in sheet VList with the value "Cost Per Unit" [Cell E11], so that the dropdown list in F12 and H12 get automatically updated with the value "Cost Per Unit"?
Been researching a long time for this with no avail, so I'm hoping to seek some experts here to see if such a scenario is even possible without VBA. Thanks!
Edit: So I've been told that VBA codes can be triggered automatically upon changes in the cell value, so I am open to any solutions/help with VBA as well. Will be researching on that direction in the meantime!
Edit2: Added a simple illustration below that hopefully better depicts what I'm trying to achieve on excel:
*Edit3: I'm starting to explore the Worksheet_SelectionChange method, and this is what I've come out so far:
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Dim projectName As String
Dim VariableList As Worksheet
Dim Form As Worksheet
Dim thisRow As Integer
Dim correctColumn As Integer
Dim lastRow As Integer
Set VariableList = ThisWorkbook.Sheets("VList")
Set Form = ThisWorkbook.Sheets("Form")
On Error GoTo EndingSub
If Target.Column = 5 Then
thisRow = Target.Row
projectName = Form.Cells(thisRow, 2)
correctColumn = Application.Match(projectName, VariableList.Range("1:1"), 0)
lastRow = VariableList.Columns(correctColumn).Find("*", SearchOrder:=xlByRows, SearchDirection:=xlPrevious).Row
VariableList.Cells(lastRow + 1, correctColumn).value = Form.Cells(5, thisRow).value
End If
EndingSub:
End Sub
Somehow the value of Form.Cells(5, thisRow).Value is always empty.
If I change it to Target.Value it still takes the previous value that was being input (e.g. I first put "ABC" as New Variable, it doesn't get updated. I changed New Variable to "DEF", it updates the list with "ABC" instead of "DEF"). It also takes ALL the values that are under Column E somehow.
Also, pressing Enter after I placed one input in E11 also causes both values of E11 and E12 to be updated when only E12 has been changed. However if I click away after E11 is being input, then only E11's value gets updated.
What exactly am I doing wrong here?
I was almost having fun with this one, if anyone can refine the screwed-up parts feel free to amend.
I furthermore recommend using tables. I do realise you can write lengthy formulae to refer to ranges but giving a name to your table gives an expanding list with a simple reference.
Private Sub Worksheet_Change(ByVal Target As Range)
Dim NewVar As Range
On Error GoTo Err
Set NewVar = Range("C:C") 'data entered here, could be a referstorange kind of named range reference
If Application.WorksheetFunction.CountA(Intersect(Target, NewVar)) <> 0 Then Call ertdfgcvb(Target, NewVar) 'only run if there's an intersect, f*ed up but works anyway
Err:
End Sub
Sub ertdfgcvb(Target As Range, NewVar As Range)
Dim ws As Worksheet, Valid As Long, project As String, ListElmnt As String, Unlisted As Boolean, rng1 As Range, rng2 As Range
Set ws = Sheets("VList") 'the data that you refresh
Valid = 2 'projects in column B
HeaderRow = 1 'headers in Vlist are in row #1
uRow = Cells.Rows.Count 'f* yeah, compatibility considerations
For Each Cell In Intersect(Target, NewVar) 'will evaluate for each cell individually, in case you were to insert columns
ListElmnt = Cell.Value2 'stores the prospective list element
r = Cell.Row 'stores the list element's row to...
project = Cells(r, Valid).Value2 'identify the related project
HeaderRowRef = HeaderRow & ":" & HeaderRow
ColumnNum = ws.Range(HeaderRowRef).Find(What:=project, SearchDirection:=xlPrevious, SearchOrder:=xlByColumns, LookAt:=xlWhole).Column 'finds the project in VList
'MsgBox ws.Name
Set rng1 = ws.Cells(HeaderRow + 1, ColumnNum)
Set rng2 = ws.Cells(uRow, ColumnNum)
LastRow = ws.Range(ws.Cells(HeaderRow + 1, ColumnNum), ws.Cells(uRow, ColumnNum)).Find("*", SearchOrder:=xlByRows, SearchDirection:=xlPrevious).Row 'finds the last row for the project in VList 'f*ed up but works
Unlisted = True 'assumes it's unlisted
For x = HeaderRow + 1 To LastRow
If ListElmnt = CStr(ws.Cells(x, ColumnNum).Value2) Then Unlisted = False 'unless proven otherwise
Next
If Unlisted Then ws.Cells(LastRow + 1, ColumnNum) = ListElmnt 'if it's unlisted it gets appended to the end of the list
Next
End Sub
EDIT:
How to purge the table, example:
Sub ert()
Dim rng As Range
Set rng = Range("Táblázat1") 'obviously the table name
Do While x < rng.Rows.Count 'for each row
If rng(x, 1).Value2 = "" Then 'if it's empty
rng(x, 1).Delete Shift:=xlUp 'then delete but retaining the table format
Else
x = x + 1 'else go to the next line (note: with deletion comes a shift up!)
End If
Loop
End Sub
I am trying to search for a "/" in a cell within the first column. I need to go through 13 worksheets, find the cell that contains that "/" (which may also contain other text), and highlight that row. Ive been testing out code I've found online and haven't had much luck in getting through the whole workbook.
Dim value As String
value = "/"
x = 1
For x = 1 To 13 Step -1
lastrow = Cells(Rows.Count, "A").End(xlUp).Row
For y = 1 To lastrow
Range("a" & i).Find (value)
Range("a" & i).Rows.Interior.Color = RGB(256, 1, 1)
Next y
Next x
This is the code I'm using. If anyone could offer some assistance I'd greatly appreciate it.
It's irrelevant to your question, but the first thing I noticed was:
For x = 1 To 13 Step -1
This will never work; a negative step will only work if the order of the values is descending.
You're missing any selection of a worksheet. If you are iterating through all worksheets in the workbook, then easiest way to do it is with For each wks in Worksheets.
Find in Excel VBA is a little convoluted. A call to Find returns the first cell in the range that contains the specified text. You then need to make subsequent calls to FindNext to get any other matching cells. But, FindNext will run forever -- you need to save a reference to the first matched cell, then compare the result you get from FindNext until the first matched cell shows up again.
Here's how I would do it:
Sub foo()
Dim value As String: value = "/"
Dim rSearch As Range
Dim firstFound As Range
Dim nextFound As Range
Dim wks As Worksheet
For Each wks In Worksheets
wks.Activate
Set rSearch = Range("a1", Cells(Rows.Count, "a").End(xlUp))
Set firstFound = rSearch.Find(value)
If Not firstFound Is Nothing Then
Set nextFound = firstFound
Do
nextFound.EntireRow.Interior.Color = RGB(256, 1, 1)
Set nextFound = rSearch.FindNext(nextFound)
Loop While nextFound.Address <> firstFound.Address
End If
Next
End Sub