Dynamic concatenation by header names, lost on how to do this - excel

I need to a dynamic way to concatinate some cells in a row with a delimiter (in this instance |) as the columns move about (per project) this has to be by header names (there might be from 2 to many) columns that need to be concatinated for a project
I am trying to use arrays as there can be as many as 4000,000 rows
I have been trying for hours and here is my effort I know it is very wrong but I am at a loss
Thank you
Sub CAT()
fCAT "ElementsFile", "ElementsFile", "D", Array("Age", "Gender Identity", "Ethnicity1", "Ethnicity1")
End Sub
Sub fCAT(sShtName As String, pbShtName As String, InsertCol As String, ar As Variant)
Dim myresult
Dim col1 As String, col2 As String, col3 As String, col4 As String
Dim aLR As Long, i As Long, j As Long, k As Long
'Totaly at a loss here
For i = LBound(ar) To UBound(ar)
Dim ari As Variant
Dim coli As String
Next i
Set wsS = ThisWorkbook.Sheets(sShtName)
Set wsPB = ThisWorkbook.Sheets(pbShtName)
With wsS
aLR = .Range("A" & .Rows.Count).End(xlUp).Row
For i = LBound(ar) To UBound(ar)
j = .Rows(1).Find(ar(i)).Column
ari = .Range(Cells(1, j), Cells(aLR, j)).Select
Next i
End With
'Totaly at a loss here
ReDim myresult(1 To aLR, 1 To aLR)
For k = 1 To aLR
For i = LBound(ar) To UBound(ar)
j = wsS.Rows(1).Find(ar(i)).Column
myresult(k, 1) = Cells(k, j) & "|" & Cells(k, j + 1) & "|" & Cells(k, j + 2) & "|" & Cells(k, j + 3)
Next i
Next k
wsT.Range("D1").Resize(aLR, 1) = myresult
End Sub

Here is what I finally came up with a bit of a mess maybe but it works
Sub concat()
Dim myresult, CN
Dim HN As Variant
Dim wsS As Worksheet, wsPB As Worksheet
Dim str As String
Dim LR As Long, i As Long, j As Long, k As Long
HN = Array("Age", "Gender Identity", "Ethnicity1", "Ethnicity2")
Set wsS = ThisWorkbook.Sheets("ElementsFile")
Set wsPB = ThisWorkbook.Sheets("ElementsFile")
wsPB.Columns(4).Insert
ReDim CN(0 To UBound(HN))
With wsS
LR = .Range("A" & .Rows.Count).End(xlUp).Row
'Get Array of column numbers coresponding to Header names
For i = 0 To UBound(HN)
j = wsS.Rows(1).Find(HN(i)).Column
CN(i) = j
Next i
End With
ReDim myresult(1 To LR, 1 To 1)
For i = 1 To LR
str = vbNullString
If Not (IsEmpty(Cells(i, CN(0))) And IsEmpty(Cells(i, CN(1)))) Then
For k = UBound(HN) To 0 Step -1
If k <> UBound(HN) Then
str = Cells(i, CN(k)) & "|" & str
Else: str = Cells(i, CN(k)) & str
End If
Next k
myresult(i, 1) = str
Else
myresult(i, 1) = vbNullString
End If
Next i
str = vbNullString
wsPB.Range("D1").Resize(LR, 1) = myresult
End Sub

Related

Separate each word after line breaks into new rows

My loop seems to create infinite rows and is bugging
For Each Cell In Workbooks(newBook).Sheets(1).Range("A1:A" & lRow)
Checker = Cell.Value
For Counter = 1 To Len(Checker)
If Mid(Checker, Counter, 1) = vbLf Then
holder = Right(Mid(Checker, Counter, Len(Checker)), Len(Checker))
Workbooks(newBook).Sheets(1).Range(Cell.Address).EntireRow.Insert
End If
Next
Next Cell
Use a reverse loop. For i = lRow to 1 Step -1. Also to separate word, you can use SPLIT().
Is this what you are trying?
Option Explicit
Sub Sample()
Dim ws As Worksheet
Dim lRow As Long
Dim i As Long, j As Long
Dim Ar As Variant
'~~> Change this to the relevant worksheet
Set ws = Sheet2
With ws
'~~> Find last row in Column A
lRow = .Range("A" & .Rows.Count).End(xlUp).Row
'~~> Reverse Loop in Column A
For i = lRow To 1 Step -1
'~~> Check if cell has vbLf
If InStr(1, .Cells(i, 1).Value, vbLf) Then
'~~> Split cell contents
Ar = Split(.Cells(i, 1).Value, vbLf)
'~~> Loop through the array from 2nd position
For j = LBound(Ar) + 1 To UBound(Ar)
.Rows(i + 1).Insert
.Cells(i + 1, 1).Value = Ar(j)
Next j
'~~> Replace cells contents with content from array from 1st position
.Cells(i, 1).Value = Ar(LBound(Ar))
End If
Next i
End With
End Sub
BEFORE
AFTER
This is my solution, works with 2 dimensional ranges as well and it works on Selection, so select the range with the cells you want to split and run the code.
Sub splitByNewLine()
Dim pasteCell As Range, rowCumulationTotal As Integer
rowCumulationTotal = 0
Dim arr() As Variant
arr = Selection
Selection.Clear
For i = 1 To UBound(arr)
Dim rowCumulationCurrent As Integer, maxElemsOnRow As Integer
rowCumulationCurrent = 0
maxElemsOnRow = 0
For j = 1 To UBound(arr, 2)
Dim elems() As String, elemCount As Integer
elems = Split(arr(i, j), vbLf)
elemCount = UBound(elems)
For k = 0 To elemCount
Cells(Selection.Row + i + rowCumulationTotal + k - 1, Selection.Column + j - 1) = elems(k)
If maxElemsOnRow < k Then
rowCumulationCurrent = rowCumulationCurrent + 1
maxElemsOnRow = k
End If
Next k
Next j
rowCumulationTotal = rowCumulationTotal + rowCumulationCurrent
Next i
Exit Sub
End Sub
Input:
Output:

How to split cell contents from multiple columns into rows by delimeter?

The code I have takes cells containing the delimiter (; ) from a column, and creates new rows (everything except the column is duplicated) to separate those values.
What I have
I need this for multiple columns in my data, but I don't want the data to overlap (ex: for 3 columns, I want there to be only one value per row in those 3 columns). It would be ideal if I could select multiple columns instead of only one as my code does now.
What I want
Sub splitByCol()
Dim r As Range, i As Long, ar
Set r = Worksheets("Sheet").Range("J2000").End(xlUp)
Do While r.Row > 1
ar = Split(r.Value, "; ")
If UBound(ar) >= 0 Then r.Value = ar(0)
For i = UBound(ar) To 1 Step -1
r.EntireRow.Copy
r.Offset(1).EntireRow.Insert
r.Offset(1).Value = ar(i)
Next
Set r = r.Offset(-1)
Loop
End Sub
Try this code
Sub Test()
Dim a, x, e, i As Long, ii As Long, iii As Long, k As Long
a = Range("A1").CurrentRegion.Value
ReDim b(1 To 1000, 1 To UBound(a, 2))
For i = LBound(a) To UBound(a)
For ii = 2 To 3
x = Split(a(i, ii), "; ")
For Each e In x
k = k + 1
b(k, 1) = k
b(k, 2) = IIf(ii = 2, e, Empty)
b(k, 3) = IIf(ii = 3, e, Empty)
b(k, 4) = a(i, 4)
Next e
Next ii
Next i
Range("A5").Resize(UBound(b, 1), UBound(b, 2)).Value = b
End Sub
I'd go this way
Sub SplitByCol()
With Worksheets("Sheet")
With .Range("B2", .Cells(.Rows.Count, "B").End(xlUp))
Dim firstColValues As Variant
firstColValues = .Value
Dim secondColValues As Variant
secondColValues = .Offset(, 1).Value
Dim thirdColValues As Variant
thirdColValues = .Offset(, 2).Value
.Offset(, -1).Resize(, 4).ClearContents
End With
Dim iRow As Long
For iRow = LBound(firstColValues) To UBound(firstColValues)
Dim currFirstColValues As Variant
currFirstColValues = Split(firstColValues(iRow, 1), "; ")
Dim currSecondColValues As Variant
currSecondColValues = Split(secondColValues(iRow, 1), "; ")
With .Cells(.Rows.Count, "C").End(xlUp).Offset(1, -1)
With .Resize(UBound(currFirstColValues) + 1)
.Value = currFirstColValues
.Offset(, 2).Value = thirdColValues(iRow, 1)
End With
End With
With .Cells(.Rows.Count, "B").End(xlUp).Offset(1, 1)
With .Resize(UBound(currSecondColValues) + 1)
.Value = currSecondColValues
.Offset(, 1).Value = thirdColValues(iRow, 1)
End With
End With
Next
End With
End Sub
Follow the code step by step by pressing F8 while the cursor is in any code line in the VBA IDE and watch what happens in the Excel user interface
EDIT
adding edited code for a more "parametric" handling by means of a helper function
Sub SplitByCol()
With Worksheets("Sheet")
With .Range("B2", .Cells(.Rows.Count, "B").End(xlUp))
Dim firstColValues As Variant
firstColValues = .Value
Dim secondColValues As Variant
secondColValues = .Offset(, 1).Value
Dim thirdColValues As Variant
thirdColValues = .Offset(, 2).Value
.Offset(, -1).Resize(, 4).ClearContents
End With
Dim iRow As Long
For iRow = LBound(firstColValues) To UBound(firstColValues)
Dim currFirstColValues As Variant
currFirstColValues = Split(firstColValues(iRow, 1), "; ")
Dim currSecondColValues As Variant
currSecondColValues = Split(secondColValues(iRow, 1), "; ")
WriteOne .Cells(.Rows.Count, "C").End(xlUp).Offset(1), _
currFirstColValues, thirdColValues(iRow, 1), _
-1, 2
WriteOne .Cells(.Rows.Count, "B").End(xlUp).Offset(1), _
currSecondColValues, thirdColValues(iRow, 1), _
1, 1
Next
End With
End Sub
Sub WriteOne(refCel As Range, _
currMainColValues As Variant, thirdColValue As Variant, _
mainValuesOffsetFromRefCel As Long, thirdColValuesOffsetFromRefCel As Long)
With refCel.Offset(, mainValuesOffsetFromRefCel)
With .Resize(UBound(currMainColValues) + 1)
.Value = currMainColValues
.Offset(, thirdColValuesOffsetFromRefCel).Value = thirdColValue
End With
End With
End Sub
Please, use the next code. It uses arrays and should be very fast for big ranges to be processed, working mostly in memory:
Sub testSplitInsert()
Dim sh As Worksheet, lastR As Long, arr, arrSp, arrFin, i As Long, j As Long, k As Long
Set sh = ActiveSheet
lastR = sh.Range("B" & sh.rows.count).End(xlUp).row
arr = sh.Range("B1:D" & lastR).Value
ReDim arrFin(1 To UBound(arr) * 10, 1 To 3) 'maximum to keep max 10 rows per each case
k = 1 'initialize the variable to load the final array
For i = 1 To UBound(arr)
arrSp = Split(Replace(arr(i, 1)," ",""), ";") 'trim for the case when somebody used Red;Blue, instead of Red; Blue
For j = 0 To UBound(arrSp)
arrFin(k, 1) = arrSp(j): arrFin(k, 3) = arr(i, 3): k = k + 1
Next j
arrSp = Split(Replace(arr(i, 1)," ",""), ";")
For j = 0 To UBound(arrSp)
arrFin(k, 2) = arrSp(j): arrFin(k, 3) = arr(i, 3): k = k + 1
Next j
Next
sh.Range("G1").Resize(k - 1, 3).Value = arrFin
End Sub
It processes the range in columns "B:D" and returns the result in columns "G:I". It can be easily adapted to process any columns range and return even overwriting the existing range, but this should be done only after checking that it return what you need...

Grouping two columns to shrink row count by comparing | code optimization

I try to find a vba solution for the following problem:
I have two columns and try to group column1 in a comma separate way to have less rows.
e.g.
example:
I tried this, and it worked - but It take too long (about 300.000 Rows). Is there any better solution that task?
*Its just one part of my macro
For Each r In fr
If st = "" Then
st = Application.WorksheetFunction.Clean(Trim(ws.Cells(r.row, "L").Value))
Else
If Not IsInArray(Split(st, ","), ws.Cells(r.row, "L").Value) Then
st = st & ", " & Application.WorksheetFunction.Clean(Trim(ws.Cells(r.row, "L").Value))
End If
End If
If usrCheck = True Then
If str = "" Then
str = Application.WorksheetFunction.Clean(Trim(ws.Cells(r.row, "A").Value))
Else
If Not IsInArray(Split(str, ","), ws.Cells(r.row, "A").Value) Then
str = str & ", " & Application.WorksheetFunction.Clean(Trim(ws.Cells(r.row, "A").Value))
End If
End If
End If
Next
Maybe using Dictionary would be fast. What about:
Sub Test()
Dim x As Long, lr As Long, arr As Variant
Dim dict1 As Object: Set dict1 = CreateObject("Scripting.Dictionary")
Dim dict2 As Object: Set dict2 = CreateObject("Scripting.Dictionary")
With Sheet1 'Change accordingly
'Return your last row from column A
lr = .Cells(.Rows.Count, 1).End(xlUp).Row
'Get array and loop through it
arr = .Range("A2:B" & lr).Value
For x = LBound(arr) To UBound(arr)
dict1(arr(x, 2)) = arr(x, 2)
Next
'Loop through dictionary filling a second one
For Each Key In dict1.keys
For x = LBound(arr) To UBound(arr)
If arr(x, 2) = Key Then dict2(arr(x, 1)) = arr(x, 1)
Next x
.Range("F" & .Cells(.Rows.Count, 6).End(xlUp).Row + 1) = Key
.Range("G" & .Cells(.Rows.Count, 7).End(xlUp).Row + 1) = Join(dict2.Items, ", ")
dict2.RemoveAll
Next
End With
End Sub
This will get you all unique items from column A though, so if there can be duplicates and you want to keep them, this is not for you =)
Try also this, please. It works only in memory and on my computer takes less then 3 seconds for 300000 rows. The range must be filtered, like in your picture. If not, the filtering can also be easily automated.
Private Sub CondensData()
Dim sh As Worksheet, arrInit As Variant, arrIn As Variant, i As Long
Dim arrFinal() As Variant, lastRow As Long, Nr As Long, El As Variant
Dim strTemp As String, k As Long
Set sh = ActiveSheet
lastRow = sh.Cells(sh.Rows.count, "A").End(xlUp).Row
arrIn = sh.Range("B2:B" & lastRow + 1).Value
'Determine the number of the same accurrences:
For Each El In arrIn
i = i + 1
If i >= 2 Then
If arrIn(i, 1) <> arrIn(i - 1, 1) Then Nr = Nr + 1
End If
Next
ReDim arrFinal(Nr, 1)
arrInit = sh.Range("A2:B" & lastRow).Value
For i = 2 To UBound(arrInit, 1)
If i = 1 Then
strTemp = arrInit(1, 1)
Else
If arrInit(i, 2) = arrInit(i - 1, 2) Then
If strTemp = "" Then
strTemp = arrInit(i, 1)
Else
strTemp = strTemp & ", " & arrInit(i, 1)
End If
Else
arrFinal(k, 0) = arrInit(i - 1, 2)
arrFinal(k, 1) = strTemp
k = k + 1: strTemp = ""
End If
End If
Next i
sh.Range("C2:D" & lastRow).Clear
sh.Range("C2:D" & k - 1).Value = arrFinal
sh.Range("C:D").EntireColumn.AutoFit
MsgBox "Solved..."
End Sub
It will return the result in columns C:D

Working with jagged arrays, printing sub-array to sheet, vba

Background:
Was trying to come up with efficient ways to handle a large amount of tables on a single sheet and came across Jagged Arrays (herein "Jars").
To simply understand some basics of Jars, I was trying to build a simple scenario of staggered information to be able to create the Jar.
My Jar is labeled big_arr and each array inside is called lil_arr.
Here is the data for the scenario:
ColA 'adding row number in front of each word
1 cat
2 dog
3
4 mouse
5 elephant
6
7 zebra
8 snake
9
10 cheese
11 pickle
12
13 anteater
14 mirkat
15
16 skunk
17 smurf
In the above scenario, big_arr(2) = lil_arr where `lil_arr = array("mouse","elephant").
I would then have big_arr(i) print to a sheet; the sheet is labeled as i, when looping. So sheet 2 would have cells(1,1).value = "mouse" and cells(1,2).value = "elephant".
Issue:
I am having issues getting the data to print as expected.
The exact printing that is happening (based on i as the sheet name):
1 has cells(1,1).value = 0
2 has cells(1,1).value = "skunk"
3 has cells(1,1).value = 0
4 has cells(1,1).value = 0
5 has cells(1,1).value = 0
6 has cells(1,1).value = 0
I don't seem to be able to print using Application.Transpose(big_arr(i)). I have attempted to loop, but don't seem to have appropriate syntax.
Question:
Any help to resolve the issue with Application.Transpose(), which does not trigger an error message, would be appreciated.
Otherwise, help to get the loop to work with appropriate syntax would be phenomenal.
Code in question:
Code with Application.Transpose() for printing
Sub create_jagged_array_of_tables()
Dim big_arr As Variant, lil_arr As Variant, lr As Long, i As Long, j As Long, k As Long, ws As Worksheet
lr = Cells(Rows.Count, 1).End(xlUp).Row
ReDim big_arr(1 To lr)
For i = 1 To lr
j = 1
Do Until IsEmpty(Cells(i + j, 1))
j = j + 1
Loop
If j > 1 Then
lil_arr = Cells(i, 1).Resize(j).Value
big_arr(j) = lil_arr
i = i + j
k = k + 1
Else
MsgBox "row " & i & " is not part of an array"
End If
Next i
For i = 1 To k
Set ws = Sheets.Add
ws.Name = i
Cells(1, 1).Value = Application.Transpose(big_arr(i))
Next i
End Sub
Code for the loop I attempted, giving type-mismatch, focusing only on the for i = 1 to k loop:
For i = 1 To k
Set ws = Sheets.Add
ws.Name = i
'Cells(1, 1).Value = Application.Transpose(big_arr(i))
For j = 1 To UBound(big_arr(i), 1)
Cells(j, 1).Value = big_arr(i)(j)
Next j
Next i
In this case j will always = 2 at the line:
big_arr(j) = lil_arr
so you keep overwriting that.
I assume you want to use k instead of j for the counter of big_arr:
big_arr(k) = lil_arr
But that will require you to have a k=1 before the i loop.
Also you need to resize the output to the size of the lil_array:
Sub create_jagged_array_of_tables()
Dim big_arr As Variant, lil_arr As Variant, lr As Long, i As Long, j As Long, k As Long, ws As Worksheet
lr = Cells(Rows.Count, 1).End(xlUp).Row
ReDim big_arr(1 To lr)
k = 0
For i = 1 To lr
j = 1
Do Until IsEmpty(Cells(i + j, 1))
j = j + 1
Loop
If j > 1 Then
lil_arr = Cells(i, 1).Resize(j).Value
k = k + 1
big_arr(k) = lil_arr
i = i + j
Else
MsgBox "row " & i & " is not part of an array"
End If
Next i
For i = 1 To k
Set ws = Sheets.Add
ws.Name = i
Cells(1, 1).Resize(1, UBound(big_arr(i), 1)).Value = Application.Transpose(big_arr(i))
Next i
End Sub
Did just a little tweaking and it's working for me:
Sub create_jagged_array_of_tables()
Dim big_arr As Variant, lil_arr As Variant, lr As Long, i As Long, j As Long, k As Long, ws As Worksheet
lr = Cells(Rows.Count, 1).End(xlUp).Row
Dim big_arr_size As Long
' Assumes you have groups of 2 per small array
big_arr_size = WorksheetFunction.CountA(Range("A1:A" & lr)) / 2
ReDim big_arr(1 To big_arr_size)
k = 1
For i = 1 To lr
j = 1
Do Until IsEmpty(Cells(i + j, 1))
j = j + 1
Loop
If j > 1 Then
lil_arr = Cells(i, 1).Resize(j).Value
big_arr(k) = lil_arr ' changed `j` to `k`
i = i + j
k = k + 1
Else
MsgBox "row " & i & " is not part of an array"
End If
Next i
For i = 1 To big_arr_size
Set ws = Sheets.Add
ws.Name = i
ws.Cells(1, 1).Value = big_arr(i)(1, 1)
ws.Cells(1, 2).Value = big_arr(i)(2, 1)
Next i
End Sub
Edit: Here's a perhaps different way you can do this. It avoids using a "small array" to set as part of a larger array.
Sub t()
Dim big_arr As Variant
Dim lr As Long
lr = Sheets("Sheet1").Cells(Rows.Count, 1).End(xlUp).Row
Dim big_arr_size As Long
' Assumes you have groups of 2 per small array
big_arr_size = WorksheetFunction.CountA(Range("A1:A" & lr)) / 2
ReDim big_arr(1 To lr)
big_arr = Range("A1:A" & lr).Value
Dim i As Long, wsName As Long
Dim ws As Worksheet
wsName = LBound(big_arr)
For i = LBound(big_arr) To UBound(big_arr) - 1
If Not IsEmpty(big_arr(i, 1)) And Not IsEmpty(big_arr(i + 1, 1)) Then
Set ws = Sheets.Add
ws.Name = wsName
ws.Cells(1, 1).Value = big_arr(i, 1)
ws.Cells(1, 2).Value = big_arr(i + 1, 1)
wsName = wsName + 1
End If
Next i
End Sub
The Post already had two brilliant answers (one accepted) and both have there unique characteristics. But just want to share some of my idea since I find the the post highly interesting. I just tried to simplify the creation of jagged array using single loop using a flag and avoided transpose. May please not taken as contravention.
Sub create_jagged_array_of_tables()
Dim big_arr() As Variant, lil_arr As Variant, lr As Long, i As Long, j As Long, k As Long, ws As Worksheet
Dim Nw As Boolean, Xval As Variant
lr = Cells(Rows.Count, 1).End(xlUp).Row
k = 0
j = 0
For i = 1 To lr
Xval = Cells(i, 1).Value
If IsEmpty(Xval) = False Then
If Nw = False Then
Nw = True
k = k + 1
j = 1
ReDim lil_arr(1 To 1, 1 To j)
lil_arr(1, j) = Xval
ReDim Preserve big_arr(1 To k)
big_arr(k) = lil_arr
Else
j = j + 1
ReDim Preserve lil_arr(1 To 1, 1 To j)
lil_arr(1, j) = Xval
big_arr(k) = lil_arr
End If
Else
Nw = False
End If
Next i
For i = 1 To k
Set ws = Sheets.Add
ws.Name = i
Cells(1, 1).Resize(1, UBound(big_arr(i), 2)).Value = big_arr(i)
Next i
End Sub
And if creation of jagged Array is not required and sole objective is to copy the content in the desired fashion, the it could be further simplified to
Sub test1()
Dim lr As Long, Rng As Range, Area As Range, Cnt As Long, Arr As Variant
lr = Cells(Rows.Count, 1).End(xlUp).Row
Set Rng = Range("A1:A" & lr)
Rng.AutoFilter Field:=1, Criteria1:="<>"
Set Rng = Rng.SpecialCells(xlCellTypeVisible)
Cnt = 0
For Each Area In Rng.Areas
Cnt = Cnt + 1
Set ws = Sheets.Add
ws.Name = Cnt
Arr = Area.Value
If IsArray(Arr) Then
ws.Cells(1, 1).Resize(UBound(Arr, 2), UBound(Arr, 1)).Value = Application.Transpose(Arr)
Else
ws.Cells(1, 1).Value = Arr
End If
Next
Rng.AutoFilter Field:=1
End Sub

Count string within string using VBA

I have product codes: (they are in C column of active sheet)
DO-001
DO-002
DO-003
DO-004
And I have big list of data: (they are in C column of "Sheet1")
41300100_DO-001_14215171
41300104_DO-001_14215173
K1_ISK_41300661_DO-002_13190369
NP_41533258_DO-003_14910884
DO-003_DD_44_ddd
And I want to count how many times do the product codes appear in the list of data. So the result for this case'd be: (result is H column of active sheet)
DO-001 2
DO-002 1
DO-003 2
DO-004
I have done this with this code:
Sub CountcodesPLC()
Dim i, j As Integer, icount As Integer
Dim ldata, lcodes As Long
icount = 0
lcodes = Cells(Rows.Count, 3).End(xlUp).Row
ldata = Worksheets("Sheet1").Cells(Rows.Count, 3).End(xlUp).Row
For i = 10 To lcodes
For j = 2 To ldata
If InStr(Worksheets("Sheet1").Range("C" & j), Range("C" & i)) <> 0 Then
icount = icount + 1
End If
Next j
If icount <> 0 Then
Range("H" & i).Value = icount
End If
icount = 0
Next i
End Sub
But I want to change it, so if the list of data contains some key words like "NP", "ISK", then not to count them, or if the first part of the data is the code then also not to count them, so the result for this example would be:
DO-001 2
DO-002
DO-003
DO-004
Also, I'll have around 1.000 product codes, and around 60.000 strings of data.
Will my code crash?
Seems your code is OK. But if you want to match only the first part of string (a'ka StartsWith), i'd change only this line:
If InStr(Worksheets("Sheet1").Range("C" & j), Range("C" & i)) <> 0 Then
to:
If Worksheets("Sheet1").Range("C" & j) Like Range("C" & i) & "*" Then
For further details, please see: Wildcard Characters used in String Comparisons
Use Dictionnary
Dim Dict As Scripting.Dictionary
Set Dict = New Scripting.Dictionary
Arr = Split("refer your text here", "_")
For I = LBound(Arr) To UBound(Arr)
If Dict.Exists(Arr(I)) Then
Dict(Arr(I)) = Dict(Arr(I)) + 1 'increment
Else
Dict.Add Arr(I), 1
End If
Next I
This may be OTT for the requirement but should work quite quickly.
Public Sub Sample()
Dim WkSht As Worksheet
Dim LngRow As Long
Dim AryLookup() As String
Dim VntItem As Variant
'We put what we want to search into an array, this makes it a lot quicker to search
Set WkSht = ThisWorkbook.Worksheets("Sheet1")
ReDim AryLookup(0)
LngRow = 1
Do Until WkSht.Range("A" & LngRow) = ""
If AryLookup(UBound(AryLookup, 1)) <> "" Then ReDim Preserve AryLookup(UBound(AryLookup, 1) + 1)
AryLookup(UBound(AryLookup, 1)) = Trim(UCase(WkSht.Range("A" & LngRow)))
LngRow = LngRow + 1
Loop
Set WkSht = Nothing
'Then we go down the list and check the array against each item
Set WkSht = ActiveSheet
LngRow = 1
Do Until WkSht.Range("A" & LngRow) = ""
WkSht.Range("B" & LngRow) = 0
For Each VntItem In AryLookup()
'This looks for the match without any of the exclusion items
If (InStr(1, VntItem, Trim(UCase(WkSht.Range("A" & LngRow)))) <> 0) And _
(InStr(1, VntItem, "NP") = 0) And _
(InStr(1, VntItem, "ISK") = 0) Then
WkSht.Range("B" & LngRow) = WkSht.Range("B" & LngRow) + 1
End If
Next
LngRow = LngRow + 1
Loop
Set WkSht = Nothing
MsgBox "Done"
End Sub
Basically, the 60,000 data strings will go into an array in memory, then the array will be searched against the 1,000 products. Searching in memory should be quick.
One thing I would raise is the exclusion method may produce false positives.
For example, excluding NP will exclude: -
NP_41533258_DO-003_14910884
NPA_41533258_DO-003_14910884
41533258_ANP_DO-003_14910884
You may want to think about the method overall.
Have you considered an array formula, not sure how it will perform vs code, but, you could do something along these lines, where list is in A and prod numbers in B
=SUM(IF(NOT(ISERR(SEARCH(B1 & "_",$A$1:$A$5,1)))*(ISERR(SEARCH("NP_",$A$1:$A$5,1))),1,0))
Where "NP" would be replaced by a range containing the exclusions, I've left as NP to show what's happening.
The code would be like this. But I don't know the speed.
Sub test()
Dim vDB, vLook, vSum(), Sum As Long
Dim Ws As Worksheet, dbWs As Worksheet
Dim s As String, sF As String, sCode As String
Dim i As Long, j As Long, n As Long
Set dbWs = Sheets("Sheet1")
Set Ws = ActiveSheet
With Ws
vLook = .Range("a1", .Range("a" & Rows.Count).End(xlUp))
End With
With dbWs
vDB = .Range("c1", .Range("c" & Rows.Count).End(xlUp))
End With
n = UBound(vLook, 1)
ReDim vSum(1 To n, 1 To 1)
For i = 1 To n
sF = Split(vLook(i, 1), "-")(0)
sCode = Replace(vLook(i, 1), sF, "")
Sum = 0
For j = 1 To UBound(vDB, 1)
s = vDB(j, 1)
If Left(s, Len(sF)) = sF Or InStr(s, "NP") Or InStr(s, "ISK") Then
Else
If InStr(s, sCode) Then
Sum = Sum + 1
End If
End If
Next j
If Sum > 0 Then
vSum(i, 1) = Sum
End If
Next i
Ws.Range("h1").Resize(n) = vSum
End Sub

Resources