VBA second match with two column criteria - vba

I'm trying to develop a code that brings the nth matching which is selected by the user, I already found a code that execute this but only with only one column
I want to get the third occurence of string "castro" but with the row value a2 which is "19". any suggestions?
below is the code I used to get the second ocurrence but only using one column.
Sub test1()
Dim teste As String
teste = VLOOKUPNTH("prysmian", Range("B1:C22"), 2, 2)
End Sub
Function VLOOKUPNTH(lookup_value, table_array As Range, col_index_num
As Integer, nth_value)
Dim nRow As Long
Dim nVal As Integer
Dim bFound As Boolean
VLOOKUPNTH = "No Match"
With table_array
For nRow = 1 To .Rows.Count
If .Cells(nRow, 1).Value = lookup_value Then
nVal = nVal + 1
End If
If nVal = nth_value Then
VLOOKUPNTH = .Cells(nRow, col_index_num).Text
Exit Function
End If
Next nRow
End With
End Function
the table
A B C
a1 castro 1
a1 castro 3
a1 castro 4
a1 castro 5
a1 castro 6
a1 castro 7
a2 castro 17
a2 castro 18
a2 castro 19
a2 castro 20
a2 castro 21
a2 castro 22
a2 castro 23

I modified your code so that it will check two columns.
1. Changed the range to include column A
2. Changed the column offsets to allow for the added column.
Option Explicit
Sub test1()
Dim teste As String
teste = VLOOKUPNTH("castro", Range("A1:C22"), 3, 3)
Debug.Print "Result: " & teste
End Sub
Function VLOOKUPNTH(lookup_value, table_array As Range, col_index_num As Integer, nth_value As Integer) As String
Dim nRow As Long
Dim nVal As Integer
Dim bFound As Boolean
VLOOKUPNTH = "No Match"
With table_array
For nRow = 1 To .Rows.Count
' Must match both columns to be counted.
If .Cells(nRow, 2).Value = lookup_value And .Cells(nRow, 1).Value = "a2" Then
nVal = nVal + 1
End If
If nVal = nth_value Then
' Now we have found the nth occurence of the lookup value.
VLOOKUPNTH = .Cells(nRow, col_index_num).Text
Exit Function
End If
Next nRow
End With
End Function

Related

Copy data from 2 sheets into a single sheet in interleaving format

I have 2 sets of data in 2 sheets having same columns in each sheet.
I want to copy both the sets of data from 2 sheets into a 3rd sheet but in the following format:-
Sheet1
Name Age Gender
Mayur 23 M
Alex 24 M
Maria 25 F
April 19 F
Sheet2
Name Age Gender
Mayur 21 M
Maria 24 F
Alex 24 M
June 20 F
Sheet3
Name1 Name2 Age1 Age2 Gender1 Gender2
Mayur Mayur 23 21 M M
Alex Alex 24 24 M M
Maria Maria 25 24 F F
April 19 F
June 20 F
Now there is one primary column i.e. Name. This column will never be empty.
Both the sheets may not have the data in the same sequence.
Both the sheets may have different entries for the same name.
There could be a name missing in any of the sheets
I have written the whole code which does the following:-
I find out Names from sheet1 in sheet2 & then copy corresponding entries for that name from both the sheets to sheet3.
If a name is not found in sheet2 then it's data is copied as it is as shown above & finally Names in sheet2 are searched in sheet1 if any name is not present in there those entries are copied in sheet3.
Now the searching part runs quite well performance wise but the copying part takes a lot of time.
I have tried other methods of copying the data as well but none runs quite fast.
In actual data there are more than 200 columns & millions of rows.
The whole process runs for more than 6-7 hours.
Could anyone please let me know any alternative faster way of achieving this.
Even if that could reduce the time to an hour or 2 from 7 hours that's still great.
Also I need to highlight the descrepancies which I'm doing that by changing the cell color when there is a mismatch in the data while copying from both the sheets.
Below is the code
Sub findUsingArray()
Dim i As Long
Dim j As Variant
Dim noOfColumnsA As Integer
Dim maxNoOfColumns As Integer
Dim noOfRowsA As Long
Dim noOfRowsB As Long
Dim arrayColumnA() As Variant
Dim arrayColumnB() As Variant
Dim sheet1 As Worksheet
Dim sheet2 As Worksheet
Dim primaryKeyColumn As Integer
Dim result As Long
Set sheet1 = ThisWorkbook.Sheets("Sheet1")
Set sheet2 = ThisWorkbook.Sheets("Sheet2")
noOfColumnsA = sheet1.Cells(1, Columns.Count).End(xlToLeft).Column
maxNoOfColumns = noOfColumnsA * 2
noOfRowsA = sheet1.Cells(Rows.Count, 1).End(xlUp).Row
noOfRowsB = sheet2.Cells(Rows.Count, 1).End(xlUp).Row
'createHeader maxNoOfColumns Used to create header in 3rd sheet
primaryKeyColumn = 1
ReDim arrayColumnA(noOfRowsA)
ReDim arrayColumnB(noOfRowsB)
arrayColumnA = sheet1.Range(sheet1.Cells(1, primaryKeyColumn), sheet1.Cells(noOfRowsA, primaryKeyColumn))
arrayColumnB = sheet2.Range(sheet2.Cells(1, primaryKeyColumn), sheet2.Cells(noOfRowsB, primaryKeyColumn))
result = 2
For i = 2 To noOfRowsA
j = Application.Match(arrayColumnA(i, 1), sheet2.Range(sheet2.Cells(1, primaryKeyColumn), sheet2.Cells(noOfRowsB, primaryKeyColumn)), 0)
If Not IsError(j) Then
result = copyInaRowUsingArray(i, result, j, maxNoOfColumns)
Else
result = copyMissingRow(1, i, result, maxNoOfColumns)
End If
Next i
For i = 2 To noOfRowsB
j = Application.Match(arrayColumnB(i, 1), sheet1.Range(sheet1.Cells(1, primaryKeyColumn), sheet1.Cells(noOfRowsA, primaryKeyColumn)), 0)
If IsError(j) Then
result = copyMissingRow(2, i, result, maxNoOfColumns)
End If
Next i
End Sub
Function copyInaRowUsingArray(sheet1Index As Long, newRowIndex As Long, sheet2index As Variant, noOfColumns As Integer)
Dim i As Long
Dim j As Long
Dim val As Variant
Dim valueA As String
Dim valueB As String
Dim arrayA() As Variant
Dim arrayB() As Variant
Dim sheet1 As Worksheet
Dim sheet2 As Worksheet
Dim sheet3 As Worksheet
Dim rowColoured As Boolean
j = 1
Set sheet1 = ThisWorkbook.Sheets("Sheet1")
Set sheet2 = ThisWorkbook.Sheets("Sheet2")
Set sheet3 = ThisWorkbook.Sheets("Sheet3")
arrayA = Application.Transpose(Application.Transpose(sheet1.Range(sheet1.Cells(sheet1Index, 1), sheet1.Cells(sheet1Index, noOfColumns)).Value))
arrayB = Application.Transpose(Application.Transpose(sheet2.Range(sheet2.Cells(sheet2index, 1), sheet2.Cells(sheet2index, noOfColumns)).Value))
rowColoured = False
With sheet3
For i = 1 To noOfColumns
valueA = arrayA(j)
If Not valueA = "" Then
.Cells(newRowIndex, i).Value = valueA
End If
i = i + 1
valueB = arrayB(j)
If Not valueB = "" Then
.Cells(newRowIndex, i).Value = valueB
End If
If Not StrComp(CStr(valueA), CStr(valueB)) = 0 Then
If Not rowColoured Then
.Range(.Cells(newRowIndex, 1), .Cells(newRowIndex, noOfColumns)).Interior.ColorIndex = 35
rowColoured = True
End If
.Cells(newRowIndex, i).Interior.ColorIndex = 34
.Cells(newRowIndex, i - 1).Interior.ColorIndex = 34
End If
j = j + 1
Next i
copyInaRowUsingArray = newRowIndex + 1
End With
End Function
Function copyMissingRow(sheetNo As Integer, sheetIndex As Long, newRowIndex As Long, noOfColumns As Integer)
Dim i As Long
Dim j As Long
Dim val As Variant
Dim valueA As String
Dim valueB As String
Dim arrayA() As Variant
Dim arrayB() As Variant
Dim sheet1 As Worksheet
Dim sheet2 As Worksheet
Dim sheet3 As Worksheet
j = 1
Set sheet3 = ThisWorkbook.Sheets("Sheet3")
With sheet3
If sheetNo = 1 Then
Set sheet1 = ThisWorkbook.Sheets("Sheet1")
ReDim arrayA(noOfColumns)
arrayA = Application.Transpose(Application.Transpose(sheet1.Range(sheet1.Cells(sheetIndex, 1), sheet1.Cells(sheetIndex, noOfColumns)).Value))
For i = 1 To noOfColumns
valueA = arrayA(j)
If Not valueA = "" Then
.Cells(newRowIndex, i).Value = valueA
End If
i = i + 1
j = j + 1
Next i
.Range(.Cells(newRowIndex, 1), .Cells(newRowIndex, noOfColumns)).Interior.ColorIndex = 46
Else
Set sheet2 = ThisWorkbook.Sheets("Sheet2")
ReDim arrayB(noOfColumns)
arrayB = Application.Transpose(Application.Transpose(sheet2.Range(sheet2.Cells(sheetIndex, 1), sheet2.Cells(sheetIndex, noOfColumns)).Value))
For i = 1 To noOfColumns
i = i + 1
valueB = arrayB(j)
If Not valueB = "" Then
.Cells(newRowIndex, i).Value = valueB
End If
j = j + 1
Next i
.Range(.Cells(newRowIndex, 1), .Cells(newRowIndex, noOfColumns)).Interior.ColorIndex = 3
End If
copyMissingRow = newRowIndex + 1
End With
End Function
As per one of the comments, a dictionary should help do what it is you're after. The dictionary used here saves, from sheet(2), the name as the key and the corresponding row as the value.
Option Explicit
Sub CopyRng(frmSht As Worksheet, frmRow As Integer, offset As Integer, toRow As Integer)
Dim r As Integer
For r = 1 To 3:
Sheets(3).Cells(toRow, offset + 2 * r).Value = frmSht.Cells(frmRow, r).Value
Next
End Sub
Sub InterleaveRows()
Dim dict As Object
Set dict = CreateObject("Scripting.Dictionary")
With Sheets(2)
Dim r As Integer, r2 As Integer, r3 As Integer: r3 = 2
Dim val As String
For r = 2 To .Range("A" & .Rows.Count).End(xlUp).row:
dict(.Cells(r, "A").Value) = r
Next
End With
CopyRng Sheets(1), 1, -1, 1
CopyRng Sheets(2), 1, 0, 1
For r = 2 To Sheets(1).Range("A" & Sheets(1).Rows.Count).End(xlUp).row:
val = Sheets(1).Cells(r, "A").Value
If (dict.Exists(val)) Then
r2 = dict(val)
CopyRng Sheets(1), r, -1, r3
CopyRng Sheets(2), r2, 0, r3
dict.Remove val
Else
CopyRng Sheets(1), r, -1, r3
End If
r3 = r3 + 1
Next
For r = 0 To dict.Count - 1
r2 = dict.items()(r)
CopyRng Sheets(2), r2, 0, r3
r3 = r3 + 1
Next
End Sub
The first loop of the 'InterLeaveRows' subroutine populates the dictionary by going through all the entries in Sheet(2). The next two lines writes out the header to sheet(3). The 2nd loop then writes out all values to Sheet(3) that are either in the dictionary (ie in both Sheet(1) and Sheet(2)) or just in Sheet(1); note while doing so entries from the dictionary that are written to Sheet(3) are deleted from the dictionary. The last loop writes out key/val pairs that remain in the dictionary. These are entries that are only in Sheet(2).

For loop with multiple variables

Here is a excel vba sub procedure example. I have two columns of data, range v and range c - How could I concatenate each cell rows' value with the parallel row call value.
Ideally, what I am trying to do would be this
For Each c,b In v,bb
...
next c,b
Pleas let me further explain: cell G2 value is only related to J2, and G3 with J3
G2 value = Blue
J2 value = Spaghetti
I am trying to return "Blue Spaghetti" with one for loop?
G2 value = Red
J2 value = Noodles
I am trying to return "Red Noodles" with one for loop?
Dim c As Variant
Dim b As Variant
Dim v As Range
Dim bb As Range
Dim brow As Long
Dim vrow as long
Set v = ActiveSheet.Range("G:G")
vrow = v(v.Cells.Count).End(xlUp).Row
Set v = Range(v(2), v(brow))
Set bb = ActiveSheet.Range("J:J")
brow = bb(bb.Cells.Count).End(xlUp).Row
Set bb = Range(bb(2), bb(brow))
For Each c In v
c = Mid(c, 1, 4)
msgbox c
Next c
For each b in bb
msgbox b
next b
Looking at your original post, I'm going to say I'm confused with all the extra stuff. Look at what goes on here, and comment with questions. I think you are over complicating what you are attempting.
Sub ConcatCols()
Dim lastRow As Long
Dim tempValue As String
lastRow = Sheets("Sheet1").Range("A" & Rows.Count).End(xlUp).row
For iRow = 2 to lastRow
tempValue = Sheets("Sheet1").Cells(iRow, "G").Text & " " & _
Sheets("Sheet1").Cells(iRow, "J").Text
MsgBox tempValue
Next iRow
End Sub

Excel lookup based on a condition

sheet1 sheet2 sheet3
---------
| |
V V * V-----
123 | A 123 | 456 C | |
* | B 123 | 789 D | |
| C 123 | 345 E | |
^ |
|-----------------
Can I look up 123 from sheet 1 to sheet 2 to return a letter (but that letter must appear in sheet 3 (C), look up the letter that is in sheet 3 and return 456? the problem is there are multiple 123's in sheet 2; I'm only used to dealing with unique numbers. Can it go A is not in sheet 3 so go to next letter until hits C. then lookup value to the left which is 456.
Thanks
Using VBA, inside a Module, write this new function:
Public Function LookFx(Sh1 As Range, Sh2 As Range, Sh3 As Range) As String
Dim BaseVal As String
Dim FoundV As Boolean
Dim SecVal As String
Application.Volatile
BaseVal = Sh1.Value
FoundV = False
For Each xx In Sh2
If xx.Value = BaseVal Then
SecVal = xx.Offset(0, -1).Value
For Each yy In Sh3
If yy.Value = SecVal Then
LookFx = yy.Offset(0, -1).Value
End If
Next
End If
Next
End Function
the value to be add in the function are:
Lets this is your data:
Sheet1:
Sheet2 :
Sheet 3:
The code below will loop through the values in sheet2 if a match is found it will loop through the values in sheet3. If a match is found it will be displayed, else it will c continue its loop in sheet.
Sub main()
Dim intValue As Integer
Dim i As Integer
Dim j As Integer
Dim strChar As String
intValue = Sheet1.Cells(1, 1)
For i = 1 To 3
If intValue = Sheet2.Cells(i, 2) Then
strChar = Sheet2.Cells(i, 1)
For j = 1 To 3
If strChar = Sheet3.Cells(j, 2) Then
MsgBox (Sheet3.Cells(j, 1))
Exit Sub
End If
Next j
End If
Next i
End Sub

How to Combine 4 column into 1 column?

Using office 2010. everything is in same sheet.
Data in Column A B C & D can change (increase or decrease daily)
I have 4 column
OUTPUT --> IN column F should be
---A-----B-----C------D---------------------------------------F
1 5 8 AP 1
2 6 9 BP 2
3 7 1 CD 3
4 5 QW 4
5
6
7
8
9
1
5
AP
BP
CD
QW
length of columns A B C & D can increase of decrease.
How about this?
Sub move()
Dim ws As Worksheet
Dim outputColumn As Long
Dim currentColumn As Long
Dim currentOutputRow As Long
Set ws = ActiveSheet
outputColumn = 6 ' column f
For currentColumn = 1 To 4
currentOutputRow = ws.Cells(ws.Rows.Count, outputColumn).End(xlUp).Row
If (currentOutputRow > 1) Then
currentOutputRow = currentOutputRow + 1
End If
ws.Range(ws.Cells(1, currentColumn), ws.Cells(ws.Rows.Count, currentColumn).End(xlUp)).Copy _
ws.Cells(currentOutputRow, outputColumn)
Next
End Sub
use the below. It accepts the range you need to change and will return a vertical array of values. To fill the values use an array formula.
Function ToVector(rng As Range)
Dim cells()
ReDim cells(rng.cells.Count)
Dim i As Double
For Each cell In rng
cells(i) = cell
i = i + 1
Next cell
ToVector = Application.WorksheetFunction.Transpose(cells)
End Function
With the help of this site get-digital-help.com/
Combine Columns But this is only static.
I converted it to dynamic meaning changing range.
for example I posted A B C D IN F
To make formula more clear will enter formula in Name Manager
BELOW IS DYNAMIC FORMULA FOR EACH COLUMN (goes in name manger)
ALIST = =OFFSET($A$1,0,0,COUNTA($A:$A),1)
BLIST = =OFFSET($B$1,0,0,COUNTA($B:$B),1)
CLIST = =OFFSET($C$1,0,0,COUNTA($C:$C),1)
DLIST = =OFFSET($D$1,0,0,COUNTA($D:$D),1)
FORMULA IN COLUMN F and drag down
=IFERROR(INDEX(ALIST, ROWS(F$1:$F1)),
IFERROR(INDEX(BLIST, ROWS(F$1:$F1)-ROWS(ALIST)),
IFERROR(INDEX(CLIST, ROWS(F$1:$F1)-ROWS(ALIST)-ROWS(BLIST)),
IFERROR(INDEX(DLIST, ROWS(F$1:$F1)-ROWS(ALIST)-ROWS(BLIST)-ROWS(CLIST)),""))))
Screenshot

Excel VBA Loop on columns

when we are going to do a loop in the rows, we can use code like the following:
i = 1
Do
Range("E" & i & ":D" & i).Select
i = i + 1
Loop Until i > 10
but what if we want to do a loop on a column?
Can we use the same method as above?
while the columns in Excel is a complex such as A, B, C, ..., Y, Z, AA, AB, AC, ..., etc.
problems will arise between loop from the "Z" to the "AA".
how we do looping alphabet column from "A" to "Z" and then continued into "AA", "AB" and so on
is there anything that can help?
Yes, let's use Select as an example
sample code: Columns("A").select
How to loop through Columns:
Method 1: (You can use index to replace the Excel Address)
For i = 1 to 100
Columns(i).Select
next i
Method 2: (Using the address)
For i = 1 To 100
Columns(Columns(i).Address).Select
Next i
EDIT:
Strip the Column for OP
columnString = Replace(Split(Columns(27).Address, ":")(0), "$", "")
e.g. you want to get the 27th Column --> AA, you can get it this way
Another method to try out.
Also select could be replaced when you set the initial column into a Range object. Performance wise it helps.
Dim rng as Range
Set rng = WorkSheets(1).Range("A1") '-- you may change the sheet name according to yours.
'-- here is your loop
i = 1
Do
'-- do something: e.g. show the address of the column that you are currently in
Msgbox rng.offset(0,i).Address
i = i + 1
Loop Until i > 10
** Two methods to get the column name using column number**
Split()
code
colName = Split(Range.Offset(0,i).Address, "$")(1)
String manipulation:
code
Function myColName(colNum as Long) as String
myColName = Left(Range(0, colNum).Address(False, False), _
1 - (colNum > 10))
End Function
If you want to stick with the same sort of loop then this will work:
Option Explicit
Sub selectColumns()
Dim topSelection As Integer
Dim endSelection As Integer
topSelection = 2
endSelection = 10
Dim columnSelected As Integer
columnSelected = 1
Do
With Excel.ThisWorkbook.ActiveSheet
.Range(.Cells(columnSelected, columnSelected), .Cells(endSelection, columnSelected)).Select
End With
columnSelected = columnSelected + 1
Loop Until columnSelected > 10
End Sub
EDIT
If in reality you just want to loop through every cell in an area of the spreadsheet then use something like this:
Sub loopThroughCells()
'=============
'this is the starting point
Dim rwMin As Integer
Dim colMin As Integer
rwMin = 2
colMin = 2
'=============
'=============
'this is the ending point
Dim rwMax As Integer
Dim colMax As Integer
rwMax = 10
colMax = 5
'=============
'=============
'iterator
Dim rwIndex As Integer
Dim colIndex As Integer
'=============
For rwIndex = rwMin To rwMax
For colIndex = colMin To colMax
Cells(rwIndex, colIndex).Select
Next colIndex
Next rwIndex
End Sub
Just use the Cells function and loop thru columns.
Cells(Row,Column)