first time poster, long time reader.
Apologies if this is hard to follow.
I have a spreadsheet which has a list of first names and last names. What I am wanting to do is take all of the first names which have the same last name and place them, evenly(ish) and separated by a comma, into the 3 reference columns in the same spreadsheet for example;
Example of Completed Sheet
I would like to do this in VBA because there are 200+ names and growing, and later the code will use this information to create and populate more workbooks.
So far, what I have works for all last names which have 3 or less first names (ie; one per column) but I cannot get it to work for last names where there are more than 3 first names.
My thought was to read all of the names into an array, split out the elements which have more than 3 names into another array, join these together separated by a comma, to then be transferred to the relevant column on the sheet.
However for some reason, I cannot get it to output more than one name into the column.
I have had a few attempts at this, but this is my latest attempt;
Private Sub cmdUpdate_Click()
Dim i As Long
Dim j As Long
Dim k As Long
Dim l As Long
Dim m As Long
Dim namesPerCol As Long
Dim strLastNameMatches As String
Dim arrNames() As String
Dim arrMultiNames(3) As String
Application.ScreenUpdating = False
With ActiveSheet
'Finds the last row with data in it
lngLastRow = .Cells(.Rows.count, "A").End(xlUp).Row
End With
'Sort the Columns
Columns("A:E").Sort key1:=Range("A1"), Header:=xlYes
'Loop through the LastNames
For i = 2 To lngLastRow
'Second loop through the LastNames
For j = 2 To lngLastRow
'If the last name matches
If Cells(i, 2).Value = Cells(j, 2).Value Then
'If the cell is empty then
If Range("C" & i).Value = "" Then
'Place the name in colA into colC
Range("C" & i).Value = Range("A" & j).Value
Else
'If the cell is not empty, then place a comma and space and then the value from colA
Range("C" & i).Value = Range("C" & i).Value & ", " & Range("A" & j).Value
End If
End If
Next j
Next i
For i = 2 To lngLastRow
strLastNameMatches = Range("C" & i).Value
arrNames = Split(strLastNameMatches, ", ")
If UBound(arrNames) > 2 Then
namesPerCol = UBound(arrNames) / 3
For l = 0 To 1
For k = LBound(arrNames) To namesPerCol
arrMultiNames(l) = arrNames(k) & ", "
Next k
Next l
For m = LBound(arrMultiNames) To UBound(arrMultiNames)
Select Case m
Case 0
Range("C" & i).Value = arrMultiNames(m)
Case 1
Range("D" & i).Value = arrMultiNames(m)
Case 2
Range("E" & i).Value = arrMultiNames(m)
End Select
Next m
Else
For j = LBound(arrNames) To UBound(arrNames)
Select Case j
Case 0
Range("C" & i).Value = arrNames(j)
Case 1
Range("D" & i).Value = arrNames(j)
Case 2
Range("I" & i).Value = arrNames(j)
End Select
Next j
End If
Next i
Application.ScreenUpdating = True
End Sub
Apologies for the poor quality coding, I will work on tiding it up once it is all working.
Any help I can get to get this code splitting out the names evenly across the three columns will be greatly appreciated
This task might be simpler if you could store your data into a more tree-like structure. There are many ways to do this; I've used the Collection object as it's easy to handle an unknown number of items. Basically, there are collections within a collection, ie one collection of first names for each last name.
The sample below uses very rudimentary distribution code (which is also hard-coded to a split of 3), but the point is that iterating through and down the tree is far simpler:
Dim lastList As Collection, firstList As Collection
Dim lastText As String, firstText As String
Dim data As Variant, last As Variant, first As Variant
Dim output() As Variant, dist(1 To 3) As Long
Dim str As String
Dim r As Long, c As Long, i As Long
'Read data into an array
With Sheet1
data = .Range(.Range("A1"), .Cells(.Rows.Count, "B").End(xlUp)).Value2
End With
'Create lists of unique lastnames containing the firstnames
Set lastList = New Collection
For r = 2 To UBound(data, 1)
firstText = CStr(data(r, 1))
lastText = CStr(data(r, 2))
Set firstList = Nothing
On Error Resume Next
Set firstList = lastList(lastText)
On Error GoTo 0
If firstList Is Nothing Then
Set firstList = New Collection
lastList.Add firstList, lastText
End If
firstList.Add firstText
Next
'Write results to sheet
ReDim output(1 To UBound(data, 1) - 1, 1 To 3)
For r = 2 To UBound(data, 1)
lastText = CStr(data(r, 2))
Set firstList = lastList(lastText)
'Calculate the distribution
dist(3) = firstList.Count / 3 'thanks #Comitern
dist(2) = dist(3)
dist(1) = firstList.Count - dist(2) - dist(3)
i = 1: c = 1: str = ""
For Each first In firstList
str = str & IIf(i > 1, ", ", "") & first
i = i + 1
If i > dist(c) Then
output(r - 1, c) = str
i = 1: c = c + 1: str = ""
End If
Next
Next
Sheet1.Range("C2").Resize(UBound(output, 1), UBound(output, 2)).Value = output
Related
I am trying to do a vlookup using vba to look up each country per continent and return a true value on each column. For example, I get a true under Europe Lookup if Belgium is listed as one of the countries. See below for the current code I have. Issues I want to fix are:
First, I want to be able to lookup each country to confirm if they are in the countries tab. That way, I know if there is a new country I need to add. What I have currently looks up to check if at least one of the countries is listed but would like to make sure all countries are listed.
Additionally, I want to make it automated such that if a new country is added to the countries list, I don't have to edit the macros.
Also, is there a way to do the lookup for separate continents one at a time? Currently, it just returns a true if the country is in the list irrespective of the continents. I know this means I'll have separate lines of codes for each continent but that's fine.
Sub Macro1()
Sheets("Sales Table").Select
Range("D2").Select
Dim LastRowColumnD As Long
LastRowColumnD = Cells(Rows.Count, 1).End(xlUp).Row
Range("D2:D" & LastRowColumnD).Formula = "=SUMPRODUCT(--ISNUMBER(SEARCH('Countries'!R2C1:R11C1,RC[-2])))>0"
Range("E2").Select
Dim LastRowColumnE As Long
LastRowColumnE = Cells(Rows.Count, 1).End(xlUp).Row
Range("E2:E" & LastRowColumnE).Formula = "=SUMPRODUCT(--ISNUMBER(SEARCH('Countries'!R2C1:R11C1,RC[-3])))>0"
End Sub
Not sure what kind of output are you trying to get, but something like this may work for you and you'll need to adapt a little:
Sub test()
Dim i As Long, j As Long, k As Long
Dim LR As Long
Dim Mydata As Variant
Dim WKData As Worksheet
Dim rngCountries As Range
Dim MyF As WorksheetFunction
Set MyF = WorksheetFunction
With ThisWorkbook.Worksheets("Countries")
LR = .Range("B" & .Rows.Count).End(xlUp).Row
Set rngCountries = .Range("A2:B" & LR)
End With
Set WKData = ThisWorkbook.Worksheets("Sales Table")
With WKData
LR = .Range("B" & .Rows.Count).End(xlUp).Row
Mydata = .Range("B2:B" & LR).Value
For i = 1 To UBound(Mydata) Step 1
j = UBound(Split(Mydata(i, 1), ", "))
'j = how many countries -1, so j+1= total countries in cell
For k = 0 To j Step 1
'we loop trough each country in cell
If MyF.CountIfs(rngCountries.Columns(1), Split(Mydata(i, 1), ", ")(k), rngCountries.Columns(2), "Europe") <> 0 Then .Range("E" & (i + 1)).Value = .Range("E" & (i + 1)).Value + 1 'Europe Check
If MyF.CountIfs(rngCountries.Columns(1), Split(Mydata(i, 1), ", ")(k), rngCountries.Columns(2), "Africa") <> 0 Then .Range("F" & (i + 1)).Value = .Range("F" & (i + 1)).Value + 1 'Africa Check
If MyF.CountIfs(rngCountries.Columns(1), Split(Mydata(i, 1), ", ")(k), rngCountries.Columns(2), "Asia") <> 0 Then .Range("G" & (i + 1)).Value = .Range("G" & (i + 1)).Value + 1 'Asia Check
Next k
'check all countries: if the sum equals k+1, then all countries in cell are present
.Range("D" & (i + 1)).Value = IIf(MyF.Sum(Range("E" & (i + 1) & ":G" & (i + 1))) = k, "YES", "NO")
Next i
End With
'clean variables
Erase Mydata
Set MyF = Nothing
Set rngCountries = Nothing
Set WKData = Nothing
End Sub
I've used arrays and Splits to create arrays so you can loop trough each individual country:
Notice I added "Portugal" to check the "NO" value in the "All countries" column. Every NO means there is a country in that cell that is not present in your range of countries.
The CurrentRegion property of Range will catch countries added to the Countries tab.
You don't necessarily need a separate line of code for each continent. If you keep a consistent column naming strategy, you can make the code flexible enough to catch added continents.
Sub macroSplitter()
Dim wb As Workbook
Dim salesSheet As Worksheet, Countries As Worksheet, continent As String
Dim j As Long
Set wb = ThisWorkbook
Set salesSheet = wb.Worksheets("Sales Table")
Set Countries = wb.Worksheets("Countries")
'Range.CurrentRegion selects a region contiguous with the designated cell
countryArray = Countries.Range("A2").CurrentRegion.Value2
For Each cell In salesSheet.Range("B2", salesSheet.Range("B2").End(xlDown))
splitCell = Split(cell, ", ")
For Each country In splitCell
If inCountry(country, countryArray) Then
continent = whichcontinent(country, countryArray)
'using match to look for continent column, and End(xlToRight) to allow for additional continents to be added
Cells(cell.Row, WorksheetFunction.Match(continent & " Lookup", salesSheet.Range("A1", salesSheet.Range("A1").End(xlToRight)), 0)).Value2 = True
Else
cell.Offset(0, 2).Value2 = False
End If
Next
If cell.Offset(0, 2).Value2 = vbNullString Then cell.Offset(0, 2).Value2 = True
For j = WorksheetFunction.Match("Europe Lookup", salesSheet.Range("A1", salesSheet.Range("A1").End(xlToRight)), 0) To salesSheet.Range("A1").End(xlToRight).Column
If Cells(cell.Row, j).Value2 = vbNullString Then Cells(cell.Row, j).Value2 = False
Next
Next cell
End Sub
Private Function inCountry(c, arr) As Boolean
Dim i As Long
For i = 2 To UBound(arr, 1)
If c = arr(i, 1) Then inCountry = True
Next
End Function
Private Function whichcontinent(c, arr) As String
Dim i As Long
For i = 2 To UBound(arr, 1)
If c = arr(i, 1) Then whichcontinent = arr(i, 2)
Next
End Function
The project consist to add lines in a new table based on value coming from 2 different table (or Excel file).
There are 3 files, called by :
Reference : the content of the file will not change
Data : the content of the file will always change
Result : the content of the file is a combination of the Reference and Date based on my request below. It is want I need.
I create 3 files, all manually with some value in order to help you to understand, called Example_Reference, Example_Data and Example_Result.
What as to be done:
First step:
Write a new line (in the new file/table) and copy exactly all the cells of the first line of Reference file.
Second step:
We take the content of the cell (column A) of Reference file (same line that point 1.) and we look in the Data file if one cell at least (column A) is the exactly the same :
a. If NOT : Do nothing, and continue for next line of the Reference file (do that until end of line of the Reference line (not end of Excel, but when no more line with something inside))
b. If YES :
i. Look how many line are with the same value (text) in the column A (Data file), create (in the Result file) a number of line equal to the number of same value and copy all data and line from Data file (for the same Column A of course).
ii. Modify in the first line (created on point 1) the cell (column R) with the different value of the column R added in point 2.b. of each line with specific “;” as in example. (T1;T2;T3… if T1 T2 and T3 are on the line).
iii. For main line (where a Product is written, like in the Reference file and line), on column N, it should be the sum of all the number below (0, 3 or 😎 for all the subline (Variant).
3. If sum = 0, write FALSE on column K. If sum is different from 0, write on column K TRUE.
c. Do that until we finish to read all the line of the Reference
Below are the Images of example three files:
Reference
Data
Result
So far I have done with the First Step as follows:
Dim cel As Range
Dim oFoundRng As Range
Range("A1").End(xlUp).Select ' looking for first empty cell on result sheet
With Workbooks("Example_Reference").Worksheets("Feuil1")
With .Range("a1", .Cells(.Rows.Count, "a").End(xlUp))
For Each cel In .SpecialCells(xlCellTypeConstants) ' loop through referenced range not empty cells
.Range(cel.Address).EntireRow.Copy Workbooks("result").Worksheets("feuil1").Cells(Rows.Count, 1).End(xlUp).Offset(1, 0)
Next
End With
End With
Now I need to take the content of the cell (column A) of Reference file (same line that point 1.) and we look in the Data file if one cell at least (column A) is the exactly the same.
can you guys help?
I will update my question as I go along ...
Here you have, let me know if works as you expected :)
Just set the workbook variables with your names or paths.
The sub is ready to work with the three workboos already opened but if
you want the macro to open the wbks just add workbooks.open method at the beginning.
Sub ProcessData()
'Workbook ans worksheet declaration
Dim referenceWbk As Workbook
Set referenceWbk = Workbooks("Reference.xlsx")
Dim dataWbk As Workbook
Set dataWbk = Workbooks("Data.xlsx")
Dim exampleWbk As Workbook
Set exampleWbk = Workbooks("Example.xlsm")
Dim referenceWsh As Worksheet
Set referenceWsh = referenceWbk.Sheets(1)
Dim dataWsh As Worksheet
Set dataWsh = dataWbk.Sheets(1)
Dim exampleWsh As Worksheet
Set exampleWsh = exampleWbk.Sheets(1)
'Loop reference workbook
Dim exampleLastRow As Long: exampleLastRow = 1
Dim i As Long
For i = 1 To referenceWsh.Range("A" & referenceWsh.Rows.Count).End(xlUp).Row
referenceWsh.Range("A" & i).EntireRow.Copy
exampleWsh.Range("A" & exampleLastRow).PasteSpecial xlPasteValues
'loop data wsh
Dim coicidenceCount As Long: coicidenceCount = 0
'Delete header in column N, R and K
exampleWsh.Range("N" & exampleLastRow).Value = ""
exampleWsh.Range("R" & exampleLastRow).Value = ""
exampleWsh.Range("K" & exampleLastRow).Value = ""
Dim j As Long
For j = 1 To dataWsh.Range("A" & dataWsh.Rows.Count).End(xlUp).Row
If dataWsh.Range("A" & j).Value = exampleWsh.Range("A" & exampleLastRow).Value Then
coicidenceCount = coicidenceCount + 1
exampleWsh.Range("A" & exampleLastRow + coicidenceCount).Value = dataWsh.Range("A" & j).Value
exampleWsh.Range("R" & exampleLastRow + coicidenceCount).Value = dataWsh.Range("B" & j).Value
exampleWsh.Range("N" & exampleLastRow + coicidenceCount).Value = dataWsh.Range("C" & j).Value
exampleWsh.Range("B" & exampleLastRow + coicidenceCount).Value = "Variant"
'add value to R header (plus ';')
exampleWsh.Range("R" & exampleLastRow).Value = exampleWsh.Range("R" & exampleLastRow).Value & dataWsh.Range("B" & j).Value & ";"
'add value to N header
exampleWsh.Range("N" & exampleLastRow).Value = exampleWsh.Range("N" & exampleLastRow).Value + dataWsh.Range("C" & j).Value
End If
Next j
'add value to K header
If exampleWsh.Range("N" & exampleLastRow).Value > 0 Then
exampleWsh.Range("K" & exampleLastRow).Value = True
Else
exampleWsh.Range("K" & exampleLastRow).Value = False
End If
'delete last ';' from R header
If exampleWsh.Range("R" & exampleLastRow).Value <> "" Then
exampleWsh.Range("R" & exampleLastRow).Value = Left(exampleWsh.Range("R" & exampleLastRow).Value, Len(exampleWsh.Range("R" & exampleLastRow).Value) - 1)
End If
exampleLastRow = exampleWsh.Range("A" & exampleWsh.Rows.Count).End(xlUp).Row + 1
Next i
End Sub
Try the next code, please. We cannot see which is the last column of 'Reference' sheet, but looking to the 'Result' one I assumed that it should be column "Q:Q":
Sub testProcessThreeWorkbooks()
Dim wsRef As Worksheet, wsData As Worksheet, wsRes As Worksheet
Dim lastRR As Long, lastRD As Long, rowRes As Long, i As Long, j As Long
Dim count As Long, k As Long, arr, arrT
Set wsRef = Workbooks("Example_Reference.xlsx").Sheets(1) 'use here the necessary sheet
Set wsData = Workbooks("Example_Data.xlsx").Sheets(1) 'use here the necessary sheet
Set wsRes = Workbooks("Example_Result.xlsx").Sheets(1) 'use here the necessary sheet
lastRR = wsRef.Range("A" & rows.count).End(xlUp).row 'last row of 'Reference` sheet
lastRD = wsData.Range("A" & rows.count).End(xlUp).row 'last row of 'Data' sheet
rowRes = 1 'Row of the 'Result' sheet, where the first processed line should be placed
For i = 1 To lastRR 'iterate between all existing cells of A:A 'Reference' sheet column
wsRes.Range("A" & rowRes).Resize(1, 17).Value = wsRef.Range("A" & i, "Q" & i).Value 'copy the row to be processed
count = WorksheetFunction.CountIf(wsData.Range("A1:A" & lastRD), wsRef.Range("A" & i).Value) 'count the occurrences
If count > 0 Then 'if any occurence exists:
ReDim arrT(count - 1) 'redim the array keeping 'T' type data
ReDim arr(count - 1) 'redim the array to keep the values from C:C column
k = 0 'initialize the variable to fill in the above arrays
For j = 1 To lastRD 'iterate between all existing cells of A:A 'Data' sheet column
If wsRef.Range("A" & i).Value = wsData.Range("A" & j).Value Then 'for occurrences:
arrT(k) = wsData.Range("B" & j).Value 'load 'T' type values
arr(k) = wsData.Range("C" & j).Value: k = k + 1 'Load values of C:C column
End If
Next j
With wsRes 'process the 'Result' range:
.Range("R" & rowRes).Value = Join(arrT, ";") 'place the string in column R:R
.Range("A" & rowRes + 1 & ":A" & rowRes + count).Value = wsRef.Range("A" & i).Value 'copy the 'Codes'
.Range("B" & rowRes + 1 & ":B" & rowRes + count).Value = "Variant" 'write 'Variant'
.Range("N" & rowRes + 1).Resize(UBound(arr) + 1, 1).Value = WorksheetFunction.Transpose(arr) 'drop the array values
.Range("N" & rowRes).Formula = "=Sum(N" & rowRes + 1 & ":N" & rowRes + count & ")" 'sumarize the values of N:N col
'Evaluate the value in N:N and place 'TRUE' or 'FALSE' accordingly:
If .Range("N" & rowRes).Value = 0 Then .Range("K" & rowRes).Value = False Else: .Range("K" & rowRes).Value = True
End With
End If
rowRes = rowRes + count + 1: count = 0 'reinitialize the necessary variables
Next i
End Sub
If big files/ranges are involved, I can prepare a faster solution using arrays instead of all ranges.
Edited
I found some time and prepared the faster version, using only arrays, all processing being done in memory:
Sub testProcessThreeWorkbooksArrays()
Dim wsRef As Worksheet, wsData As Worksheet, wsRes As Worksheet
Dim lastRR As Long, lastRD As Long, rowRes As Long, i As Long, j As Long
Dim count As Long, k As Long, arrRef, arrDat, arrRes, arrSlice, arr, arrT
Dim m As Long, sumV As Double
Set wsRef = Workbooks("Example_Reference.xlsx").Sheets(1) 'use here the necessary sheet
Set wsData = Workbooks("Example_Data.xlsx").Sheets(1) 'use here the necessary sheet
Set wsRes = Workbooks("Example_Result.xlsx").Sheets(1) 'use here the necessary sheet
lastRR = wsRef.Range("A" & rows.count).End(xlUp).row 'last row of 'Reference` sheet
lastRD = wsData.Range("A" & rows.count).End(xlUp).row 'last row of 'Data' sheet
arrRef = wsRef.Range("A1:Q" & lastRR).Value
arrDat = wsData.Range("A1:C" & lastRD).Value
ReDim arrRes(1 To 18, 1 To UBound(arrRef) + UBound(arrDat))
rowRes = 1 'Row of the 'Result' sheet, where the new processed line should be placed
For i = 1 To UBound(arrRef) 'iterate between all existing 'arrRef' array rows
arrSlice = Application.Index(arrRef, i, 0) 'extract a slice of the row number i
'Place the slice values in the arrRes appropriate row:
For m = 1 To UBound(arrSlice): arrRes(m, rowRes) = arrSlice(m): Next m
arrSlice = Application.Index(arrDat, 0, 1) 'extract a slice of the 'arrDat' first column
For m = 1 To UBound(arrSlice)
If arrSlice(m, 1) = arrRef(i, 1) Then count = count + 1 'extract number of occurrences
Next m
If count > 0 Then 'if any occurence exists:
ReDim arrT(count - 1) 'redim the array keeping 'T' type data
ReDim arr(count - 1) 'redim the array to keep the values from C:C column
k = 0 'initialize the variable to fill in the above arrays
For j = 1 To UBound(arrDat) 'iterate between all 'arrDat' array rows:
If arrRef(i, 1) = arrDat(j, 1) Then 'in case of occurrences:
arrT(k) = arrDat(j, 2) 'load 'T' type values
arr(k) = arrDat(j, 3): k = k + 1 'Load values of C:C column
End If
Next j
arrRes(18, rowRes) = Join(arrT, ";") 'place the string in column R:R
For m = rowRes + 1 To rowRes + count
'place the code ("A:A" content) and "Variant" string:
arrRes(1, m) = arrRef(i, 1): arrRes(2, m) = "Variant"
Next m
For m = 0 To UBound(arr) 'place the values in the 14th column
arrRes(14, rowRes + m + 1) = arr(m)
sumV = sumV + arr(m) 'calculate the values Sum
Next m
arrRes(14, rowRes) = sumV 'place the Sum in the 14th array column
If sumV > 0 Then arrRes(11, rowRes) = True Else: arrRes(11, rowRes) = False 'True/False
End If
rowRes = rowRes + count + 1: count = 0: sumV = 0 'reinitialize the necessary variables
Next i
ReDim Preserve arrRes(1 To 18, 1 To rowRes - 1) 'keep only the non empty array elements
wsRes.Range("A1").Resize(UBound(arrRes, 2), UBound(arrRes)).Value = Application.Transpose(arrRes)
MsgBox "Ready..."
End Sub
Please, test it and send some feedback.
Edited: lol you changed your question.. ;)
If you like make everything with "Select" then:
Sub Macro1()
Set ref = Workbooks("book1").Sheets("sheet1")
Set res = Workbooks("book2").Sheets("sheet2")
ref.Rows("6:6").Copy
res.Activate
res.Rows("9:9").Select
ActiveSheet.Paste
End Sub
But you should avoid using select if you will have a lot of data, as its perfomance is slow as hell.
In Column A of Sheet 1, I have a list of serial numbers which contain duplicates. I want to delete all duplicates and instead come up with a history column which captures all the information of the adjacent cells with regards to that serial number. The logic of my script goes like this: 1) Filter all distinct serial numbers into a new sheet 2) For each cell in new sheet, find all matching cells in sheet 1 3) If they match then copy adjacent columns information and create an new column with new matching information 4) The more serial duplicates are, the bigger the "history" cell of that serial number is going to have
Here is a screenshot of what I'm trying to do:
https://imgur.com/a/KEn0RIP
When I use "FindPN.Interior.ColorIndex = 3", the program does fine, finding all the 1's in the column and coloring them red. I just want to copy each the 3 cells' values that are adjacent to each '1' in Column A. I have used a Dictionary to create a dynamic variable to spit out the final cell that I want, but when I run the program, I am having problems understanding how the place the variables in the FindNext loop to spit out each different B2, C2, and D2.
Sub FindPN1() 'simplified script finding all the 1's in Sheet 1
Dim I, J, K, L, Atotal As Integer
Dim FindPN, FoundPN As Range
Dim UniqueValue As Range
Dim strStatus, strDate, strComments As Object
Atotal = Sheets(1).Range("A" & Rows.Count).End(xlUp).Row
With Sheets(1)
For I = 2 To Atotal
Set FindPN = Sheets(1).Columns(1).Find(1, LookIn:=xlValues)
If Not FindPN Is Nothing Then
Set FoundPN = FindPN
Set strStatus = CreateObject("Scripting.Dictionary")
For J = 1 To Atotal
strStatus(J) = Range("B" & I).Value
Next
Set strComments = CreateObject("Scripting.Dictionary")
For K = 1 To Atotal
strComments(K) = Range("C" & I).Value
Next
Set strDate = CreateObject("Scripting.Dictionary")
For L = 1 To Atotal
strDate(L) = Range("D" & I).Value
Next
Range("A15").Value = strDate(1)
'FindPN.Interior.ColorIndex = 3
Do
Set FindPN = .Columns(1).FindNext(After:=FindPN)
If Not FindPN Is Nothing Then
strStatus(J) = Range("B" & I).Value
strComments(K) = Range("C" & I).Value
strDate(L) = Range("D" & I).Value
'FindPN.Interior.ColorIndex = 3
Range("B15").Value = strDate(3)
If FindPN.Address = FoundPN.Address Then Exit Do
Else
Exit Do
End If
Loop
End If
Next
End With
The problem I am having is not knowing how to store my variables and having them spit out the 'History' Cell the way that I want. I have been practicing by going inside the loop to see where each variable gets defined but it seems like the strDate is always spitting out the date corresponding to the first 1.
You can make this much simpler - use a single dictionary and loop over the rows.
Add new Id's (and their "history" value) where they don't exist: if an id is already in the dictionary then append the new piece of history to the existing value.
When done, loop over the dictionary and write out the keys and the values.
Sub CombineRows()
Dim i As Long, h, k, lastRow As Long
Dim dict As Object, wsSrc As Worksheet
Set wsSrc = Sheets(1)
lastRow = wsSrc.Range("A" & Rows.Count).End(xlUp).Row
Set dict = CreateObject("Scripting.Dictionary")
For i = 2 To lastRow
With Sheets(1).Rows(i)
k = .Cells(1).Value
h = .Cells(2).Value & "|" & _
.Cells(4).Text & "|" & _
.Cells(3).Value
If dict.exists(k) Then
dict(k) = dict(k) & vbLf & h
Else
dict.Add k, h
End If
End With
Next i
DumpDict dict, Sheets(2).Range("A1")
End Sub
'write out dictionary content starting at "rng"
Sub DumpDict(dict As Object, rng As Range)
Dim c As Range, k
Set c = rng.Cells(1)
For Each k In dict.keys
c.Value = k
c.Offset(0, 1).Value = dict(k)
Set c = c.Offset(1, 0)
Next k
End Sub
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
I have data that I need to split into individual points. My macro charts the data, as a scatter plot, with: Column A as the title of the chart, Column B as the X axis, and Columns C and D as the Y axis. What I need is for when the Product ID has more than 1 number listed to split the numbers out into their own rows and keep the columns B, C, and D the same for each row created form the original. So for row 167, I would want 3 rows (001,002,003) each with packaging, 200, and 100, in B, C, and D respectively. I am not sure where to begin. I tried to build a macro but, I immediately got tripped up when I tried to record a "Find" Formula to run on the data. Any help would be greatly appreciated.
Column A: 001, 002, 003 // Column B:packaging // Column C:200 // Column D:100
Sorry I couldn't post a screenshot of my data, the forum won't let me. If you have any questions please let me know, I will be sure to check in frequently.
Thanks in advance.
I worte this VERY quickly and without much care for efficiency, but this should do the trick:
Sub SplitUpVals()
Dim i As Long
Dim ValsToCopy As Range
Dim MaxRows As Long
Dim ValToSplit() As String
Dim CurrentVal As Variant
MaxRows = Range("A1").End(xlDown).Row
For i = 1 To 10000000
ValToSplit = Split(Cells(i, 1).Value, ",")
Set ValsToCopy = Range("B" & i & ":D" & i)
For Each CurrentVal In ValToSplit
CurrentVal = Trim(CurrentVal)
Cells(i, 1).Value = CurrentVal
Range("B" & i & ":D" & i).Value = ValsToCopy.Value
Cells(i + 1, 1).EntireRow.Insert
i = i + 1
MaxRows = MaxRows + 1
Next
Cells(i, 1).EntireRow.Delete
If i > MaxRows Then Exit For
Next i
End Sub
As a note, make sure there's no data in cells beneath your data as it might get deleted.
You will need to parse the data in column A. I would do this by splitting the string in to an array, and then iterate over the array items to add/insert additional rows where necessary.
Without seeing your worksheet, I would probably start with something like this, which will split your cell value from column A in to an array, and then you can iterate over the items in the array to manipulate the worksheet as needed.
Sub TestSplit()
Dim myString as String
Dim myArray() as String
Dim cell as Range
Dim i as Long
For each cell in Range("A2",Range("A2").End(xlDown))
myString = cell.Value
myArray = Split(myString, ",") '<-- converts the comma-delimited string in to an array
For i = lBound(myArray) to uBound(myArray)
If i >= 1 Then
'Add code to manipulate your worksheet, here
End If
Next
Next
End Sub
This is a better solution (now that I had more time :) ) - Hope this does the trick!
Sub SplitUpVals()
Dim AllVals As Variant
Dim ArrayIndex As Integer
Dim RowLooper As Integer
AllVals = Range("A1").CurrentRegion
Range("A1").CurrentRegion.Clear
RowLooper = 1
For ArrayIndex = 1 To UBound(AllVals, 1)
ValToSplit = Split(AllVals(ArrayIndex, 1), ",")
For Each CurrentVal In ValToSplit
CurrentVal = Trim(CurrentVal)
Cells(RowLooper, 1).Value = CurrentVal
Cells(RowLooper, 2).Value = AllVals(ArrayIndex, 2)
Cells(RowLooper, 3).Value = AllVals(ArrayIndex, 3)
Cells(RowLooper, 4).Value = AllVals(ArrayIndex, 4)
RowLooper = RowLooper + 1
Next
Next ArrayIndex
End Sub
Sub DivideData()
'This splits any codes combined into the same line, into their own separate lines with their own separate data
Dim a, b, txt As String, e, s, x As Long, n As Long, i As Long, ii As Long
With Range("a1").CurrentRegion
a = .Value
txt = Join$(Application.Transpose(.Columns(1).Value))
x = Len(txt) - Len(Replace(txt, ",", "")) + .Rows.Count
ReDim b(1 To x * 2, 1 To UBound(a, 2))
For i = 1 To UBound(a, 1)
For Each e In Split(a(i, 1), ",")
If e <> "" Then
For Each s In Split(e, "-")
n = n + 1
For ii = 1 To UBound(a, 2)
b(n, ii) = a(i, ii)
Next
b(n, 1) = s
Next
End If
Next
Next
With .Resize(n)
.Columns(1).NumberFormat = "#"
.Value = b
End With
End With
End Sub