Related
I have a range with several series of dates and values
Input
Output
And i need this output, a series of dates ( using the min date and max date from input ).
If output date matches with the input date of a series then set the value of this day if not set a 0. I have tried all kind of loops but i have 40 series o dates and values ( 80 columns x 2000 rows ) and i can't get anything fast.
Please, test the next code. You must take care that the format in the analyzed range to be the same as the one in the built range (dd/mm/yyyy). It returns the processed array in another sheet (sh1). I used the next sheet. If it is empty in your case, you can use the code as it is. There must not exist other records in the first row, except the last Valuex. The code can be adapted to search this header type, but it is not the object of the solution:
Sub CentralizeDateValues()
Dim sh As Worksheet, sh1 As Worksheet, lastR As Long, rngD As Range, lastCol As Long, lastColL As String
Dim arrD1, arrD2, arrGen, minD As Date, maxD As Date, i As Long, j As Long
Dim arrOddCols, arrCols, strCols As String, NoD As Long, mtch, col As Long, StartTime As Date
Set sh = ActiveSheet
Set sh1 = sh.Next 'use here the sheet you need (where to return the processed range)
lastR = sh.UsedRange.rows.Count 'last row
lastCol = sh.cells(1, sh.Columns.Count).End(xlToLeft).Column 'last column
'extract the odd columns number in an array:
arrOddCols = Evaluate("TRANSPOSE(ROW(1:" & lastCol / 2 & ")*2-1)")
Debug.Print Join(arrOddCols, "|"): 'just to visually check it. Comment the line after understanding what the above line does
'obtain the columns letters array:
ReDim arrCols(1 To UBound(arrOddCols))
For i = 1 To UBound(arrOddCols)
arrCols(i) = Split(cells(1, arrOddCols(i)).Address, "$")(1)
Next i
strCols = Join(arrCols, "1,") & "1": Debug.Print strCols 'just to visually check it.
Set rngD = Intersect(sh.UsedRange, sh.Range(strCols).EntireColumn) ' build the range where to match max/min dates
minD = WorksheetFunction.min(rngD)
maxD = WorksheetFunction.Max(rngD)
NoD = maxD - minD + 1 'number the days in the range betweenthe min and max dates
'build a continuous date array from min to max:
arrD1 = Evaluate("TEXT(DATE(" & Year(minD) & "," & month(minD) & ",row(" & Day(minD) & ":" & NoD & ")),""dd/mm/yyyy"")")
Debug.Print Join(Application.Transpose(arrD1), "|") 'just to visually check it.
arrD2 = arrD1 'clone the built dates array
ReDim Preserve arrD2(1 To UBound(arrD1), 1 To UBound(arrCols) + 1) 'add the necessary columns for Values
StartTime = Timer 'start the timer to count the time spent by the following code.
arrGen = sh.Range("A2", sh.cells(lastR, lastCol)).Value: col = 1
For i = 1 To UBound(arrGen)
For j = 1 To UBound(arrGen, 2) - 1 Step 2 'iterate from two to two columns to check dates (as string) and extract values
If arrGen(i, j) <> "" Then
col = col + 1
mtch = Application.match(CStr(arrGen(i, j)), arrD1, True)
If IsNumeric(mtch) Then
arrD2(mtch, col) = arrGen(i, j + 1)
Else
arrD2(mtch, col) = "strange..." 'the code reaches this line only if a mistake is in the Dates range...
End If
End If
Next j
col = 1 'reinitialize the variable to set the column where the value to be placed
Next i
'drop the processed array content at once
sh1.Range("A2").Resize(UBound(arrD2), UBound(arrD2, 2)).Value = arrD2
Sub CentralizeDateLongValues()
Dim sh As Worksheet, sh1 As Worksheet, lastR As Long, rngD As Range, lastCol As Long, lastColL As String
Dim arrD1, arrD2, arrGen, minD As Date, maxD As Date, i As Long, j As Long
Dim arrOddCols, arrCols, strCols As String, NoD As Long, mtch, col As Long, StartTime As Date
Set sh = ActiveSheet
Set sh1 = sh.Next 'use here the sheet you need (where to return the processed range)
lastR = sh.UsedRange.rows.Count 'last row
lastCol = sh.cells(1, sh.Columns.Count).End(xlToLeft).Column 'last column
'extract the odd columns number in an array:
arrOddCols = Evaluate("TRANSPOSE(ROW(1:" & lastCol / 2 & ")*2-1)")
Debug.Print Join(arrOddCols, "|"): 'just to visually check it. Comment the line after understanding what the above line does
'obtain the columns letters array:
ReDim arrCols(1 To UBound(arrOddCols))
For i = 1 To UBound(arrOddCols)
arrCols(i) = Split(cells(1, arrOddCols(i)).Address, "$")(1)
Next i
strCols = Join(arrCols, "1,") & "1": Debug.Print strCols 'just to visually check it.
Set rngD = Intersect(sh.UsedRange, sh.Range(strCols).EntireColumn) ' build the range where to match max/min dates
minD = WorksheetFunction.min(rngD)
maxD = WorksheetFunction.Max(rngD)
NoD = maxD - minD + 1 'number the days in the range betweenthe min and max dates
'build a continuous date array from long numbers, corespondent to min and max dates:
arrD1 = Evaluate("row(" & CLng(minD) & ":" & CLng(maxD) & ")")
'Debug.Print Join(Application.Transpose(arrD1), "|"): 'Stop
arrD2 = arrD1 ''clone the built dates arary
ReDim Preserve arrD2(1 To UBound(arrD1), 1 To UBound(arrCols) + 1) 'add the necessary columns for Values
StartTime = Timer 'start the timer to count the time spent by the following code.
arrGen = sh.Range("A2", sh.cells(lastR, lastCol)).Value2: col = 1
For i = 1 To UBound(arrGen)
For j = 1 To UBound(arrGen, 2) - 1 Step 2 'iterate from two to two columns to check dates (as string) and extract values
If arrGen(i, j) <> "" Then
col = col + 1
mtch = Application.match(arrGen(i, j), arrD1, True)
If IsNumeric(mtch) Then
arrD2(mtch, col) = arrGen(i, j + 1)
Else
arrD2(mtch, col) = "strange..." 'the code reaches this line only if a mistake is in the Dates range...
End If
End If
Next j
col = 1 'reinitialize the variable to set the column where the value to be placed
Next i
'drop the processed array content at once
With sh1.Range("A2").Resize(UBound(arrD2), UBound(arrD2, 2))
.Value2 = arrD2
.Columns(1).NumberFormat = "dd/mm/yyyy"
End With
'put headers:
Dim arrHd: arrHd = Application.Transpose(Evaluate("row(1:" & UBound(arrD2, 2) - 1 & ")"))
arrHd = Split("Date|Value" & Join(arrHd, "|Value"), "|")
sh1.Range("A1").Resize(1, UBound(arrHd) + 1).Value = arrHd: sh1.Activate
MsgBox "Ready..." & vbCrLf & _
" (" & Format(Timer - StartTime, "00.00") & " seconds)"
End Sub
End Sub
It returns in "A1" of the next sheet the header and in "A2" the processed array.
Please, send some feedback after testing it. I am curious how much it takes for a big range. I tested it on a small range, but solution must run on any range...
Edited:
Please, test the following version. It uses a Long numbers array, corresponding to the necessary Dates range. This allows using value2 to create the global array, which allows a (little) faster iteration and does no need the CStr conversion. Not date format dependent, too:
Sub CentralizeDateLongValues()
Dim sh As Worksheet, sh1 As Worksheet, lastR As Long, rngD As Range, lastCol As Long, lastColL As String
Dim arrD1, arrD2, arrGen, minD As Date, maxD As Date, i As Long, j As Long
Dim arrOddCols, arrCols, strCols As String, NoD As Long, mtch, col As Long, StartTime As Date
Set sh = ActiveSheet
Set sh1 = sh.Next 'use here the sheet you need (where to return the processed range)
lastR = sh.UsedRange.rows.Count 'last row
lastCol = sh.cells(1, sh.Columns.Count).End(xlToLeft).Column 'last column
'extract the odd columns number in an array:
arrOddCols = Evaluate("TRANSPOSE(ROW(1:" & lastCol / 2 & ")*2-1)")
Debug.Print Join(arrOddCols, "|"): 'just to visually check it. Comment the line after understanding what the above line does
'obtain the columns letters array:
ReDim arrCols(1 To UBound(arrOddCols))
For i = 1 To UBound(arrOddCols)
arrCols(i) = Split(cells(1, arrOddCols(i)).Address, "$")(1)
Next i
strCols = Join(arrCols, "1,") & "1": Debug.Print strCols 'just to visually check it.
Set rngD = Intersect(sh.UsedRange, sh.Range(strCols).EntireColumn) ' build the range where to match max/min dates
minD = WorksheetFunction.min(rngD)
maxD = WorksheetFunction.Max(rngD)
NoD = maxD - minD + 1 'number the days in the range betweenthe min and max dates
'build a continuous date array from long numbers, corespondent to min and max dates:
arrD1 = Evaluate("row(" & CLng(minD) & ":" & CLng(maxD) & ")")
'Debug.Print Join(Application.Transpose(arrD1), "|"): 'Stop
arrD2 = arrD1 ''clone the built dates arary
ReDim Preserve arrD2(1 To UBound(arrD1), 1 To UBound(arrCols) + 1) 'add the necessary columns for Values
StartTime = Timer 'start the timer to count the time spent by the following code.
arrGen = sh.Range("A2", sh.cells(lastR, lastCol)).Value2: col = 1
For i = 1 To UBound(arrGen)
For j = 1 To UBound(arrGen, 2) - 1 Step 2 'iterate from two to two columns to check dates (as string) and extract values
If arrGen(i, j) <> "" Then
col = col + 1
mtch = Application.match(arrGen(i, j), arrD1, True)
If IsNumeric(mtch) Then
arrD2(mtch, col) = arrGen(i, j + 1)
Else
arrD2(mtch, col) = "strange..." 'the code reaches this line only if a mistake is in the Dates range...
End If
End If
Next j
col = 1 'reinitialize the variable to set the column where the value to be placed
Next i
'drop the processed array content at once
Dim rngBlank As Range
With sh1.Range("A2").Resize(UBound(arrD2), UBound(arrD2, 2))
.Value2 = arrD2
.Columns(1).NumberFormat = "dd/mm/yyyy"
.EntireColumn.AutoFit
.Borders(xlEdgeLeft).Weight = xlThin
.Borders(xlEdgeTop).Weight = xlThin
.Borders(xlEdgeBottom).Weight = xlThin
.Borders(xlEdgeRight).Weight = xlThin
.Borders(xlInsideVertical).Weight = xlThin
.Borders(xlInsideHorizontal).Weight = xlThin
.BorderAround Weight:=xlThick
On Error Resume Next 'for the case (even imporbable) that no any blank cell will exist...
Set rngBlank = .SpecialCells(xlCellTypeBlanks)
On Error GoTo 0
End With
If Not rngBlank Is Nothing Then rngBlank.Value = 0
'put headers:
Dim arrHd: arrHd = Application.Transpose(Evaluate("row(1:" & UBound(arrD2, 2) - 1 & ")"))
arrHd = Split("Date|Value" & Join(arrHd, "|Value"), "|")
With sh1.Range("A1").Resize(1, UBound(arrHd) + 1)
.Value = arrHd
.Font.Bold = True
.EntireColumn.AutoFit
.Borders(xlInsideVertical).Weight = xlThin
.BorderAround Weight:=xlThick
End With
sh1.Activate
MsgBox "Ready..." & vbCrLf & _
" (" & Format(Timer - StartTime, "00.00") & " seconds)"
End Sub
Please, send some feedback after testing it...
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.
I am creating hierarchies and need to outline them in the format on the right-hand side. It would be a lot easier if I could simply outline the hierarchy in one column and automatically have it expand (left -> right in the sample).
A few considerations:
Within the first column, the start of a new hierarchy will always be the value 'A'
Hierarchies can range from 2-10 children in length
Any thoughts?
Type the letters in column A only, start each new sequence with the word HEADER. Then run the macro and the expansions should be created.
Sub expand()
Dim ws As Worksheet
Set ws = ActiveSheet
Dim cell As Range, cellHeader As Range
Dim irow As Integer, i As Integer
Dim iCount As Integer, iLast As Long
' find last row in col A
iLast = ws.Range("A" & Rows.Count).End(xlUp).Row
'scan down the sheet
For Each cell In ws.Range("A1:A" & iLast)
If UCase(cell) = "DIRECT" Then
' remember the header line
Set cellHeader = cell
With cellHeader
.BorderAround xlContinuous
.Font.Bold = True
End With
ElseIf Len(cell) > 0 Then
cell.BorderAround xlContinuous
' start of sequence
If cell = "A" Then
irow = 1
iCount = 0
End If
' add header value
With cellHeader.Offset(0, irow)
.Value = "L" & irow
.Font.Bold = True
.BorderAround xlContinuous
End With
' copy cell diagonally upwards
If irow > 1 Then
For i = 1 To irow - 1
cell.Offset(-i, i) = cell.Value
cell.Offset(-i, i).BorderAround xlContinuous
Next
End If
' check max children
iCount = iCount + 1
If iCount > 10 Then
MsgBox "Children count > 10", vbCritical, "Error"
Exit Sub
End If
irow = irow + 1
End If
Next
MsgBox "Expansion Complete", vbInformation
End Sub
You do not answer my questions and I cannot wait, anymore...
Please test the next code, which works based on the thowe assumptions: Your hierarchies in discussion have all the time a kind of header (Direct in column A:A and L1 in B:B). This, or an empty row sets the bottom part of the hierarchy.
Here's the code:
Sub HierarchyArrangeMultipleR()
Dim sh As Worksheet, i As Long, j As Long, lastR As Long, lastH As Long
Dim arrI As Variant, arrTr As Variant, colN As Long, k As Long, h As Long
Set sh = ActiveSheet 'please, use here your worksheet
lastR = sh.Range("A" & sh.Rows.count).End(xlUp).Row
For k = 1 To lastR
If lastH > 0 Then k = lastH + 1
If k >= lastR Then Exit For
Start:
If sh.Range("A" & k).Value = "Direct" And sh.Range("B" & k).Value = "L1" Then
For i = 1 To 10
If sh.Range("A" & k + i).Value = "Direct" Or _
sh.Range("A" & k + i).Value = Empty Then
lastH = k + i - 1: Exit For
End If
Next i
For h = 3 To lastH - k
sh.Cells(k, h) = "L" & h - 1
Next h
Else
k = k + 1: GoTo Start
End If
arrI = sh.Range("A" & k + 1 & ":A" & lastH).Value
ReDim arrTr(1 To UBound(arrI) - 1)
colN = 1
For i = k To lastH - 2
For j = 1 To UBound(arrTr) 'lastH - i + k - 2
arrTr(j) = arrI(j, 1)
Next j
colN = colN + 1
sh.Range(sh.Cells(k + 1, colN), sh.Cells(lastH + 1 - colN, colN)).Value = WorksheetFunction.Transpose(arrTr)
Next i
Erase arrTr
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
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