Excel Get different permutation combination of the values - excel

I have an excel with 2 columns,say 10 values each as given in the below diagram. The 10 values in A and B are added in a drop down in column E and column F. I want the column D, "Result", to show me 100 different possible permutations of the values again in a drop down. I tried to write a macro but getting lost somewhere. EDIT: Added the error that i am getting. any help is greatly appreciated. Example of what is expected (remember column E and F are dropdowns)
Below is the macro i have tried:
Sub Combination()
Dim arr1 As Variant
Dim arr2 As Variant
Dim i As Long, j As Long, k As Long
Dim ws As Worksheet
Set ws = ThisWorkbook.Sheets("Sheet1")
arr1 = ws.Range("E1", ws.Range("E" & ws.Rows.Count).End(xlUp).Row).Value
arr2 = ws.Range("F1", ws.Range("F" & ws.Rows.Count).End(xlUp).Row).Value
ws.Range("D1").Value = "Result"
k = 1
For i = LBound(arr1, 1) To UBound(arr1, 1)
For j = LBound(arr2, 1) To UBound(arr2, 1)
ws.Range("D" & k + 1).Value = arr1(i, 1) & ", " & arr2(j, 1)
k = k + 1
If k = 101 Then Exit For
Next j
If k = 101 Then Exit For
Next i
End Sub
Debugger shows an error in this line of code:
arr1 = ws.Range("E1", ws.Range("E" & ws.Rows.Count).End(xlUp).Row).Value
How else am i supposed to read the values in the drop down?

This task doesn't necessarily require a VBA solution: it is achievable using dynamic spreadsheet functions (if you have a relatively recent version of Excel). To my mind, people reach for VBA too readily, when it would be better to exhaust the possibilities of spreadsheet functions first.
1. Calculate the permutations
Put this formula in cell H2:
=LET(a,A2:A11,b,B2:B10,na,ROWS(a),nb,ROWS(b),s,SEQUENCE(na*nb,,0),INDEX(a,1+(INT(s/nb))) & "," & INDEX(b,1+MOD(s,nb)))
2. Set the Data Validation:
Note the # on the end of the $D$2# reference for Source. This tells Excel that the reference is to a dynamic array.
If you don't want the intermediate column displayed, then it can be Hidden or even put on another tab. Currently Excel only allows relatively simple formulae for Data Validation ranges, otherwise this column would not be needed.
Display the selections for Options A & B:
Cell E2 has the formula =LEFT(D2,FIND(",",D2)-1)
Cell F2 has the formula =RIGHT(D2,LEN(D2)-LEN(E2)-1)
You can use MATCH() to recover the index of the option in input list if required, eg =MATCH(E2,A2:A11,0) if that is needed.
Notes:
Using spreadsheet formulae rather than VBA has three benefits:
The sheet can still be saved and shared as a .xlsx file and not
.xlsm, so reducing the number of security warnings;
It is easier to see the results and test;
The sheet will update automatically (if calculation is set to Automatic), whereas a VBA macro would have to be re-run.
EDIT: An alternative, slightly more complicated formula for H2 could be:
=LET(optA,A2,optB,B2,colA,A:A,colB,B:B,
rngA,INDEX(colA,ROW(optA),,1):INDEX(colA,COUNTA(colA),ROW(optA)-1),
rngB,INDEX(colB,ROW(optB),,1):INDEX(colB,COUNTA(colB),ROW(optB)-1),
na,ROWS(rngA),nb,ROWS(rngB),s,SEQUENCE(na*nb,,0),
INDEX(rngA,1+(INT(s/nb))) & "," & INDEX(rngB,1+MOD(s,nb)))
This would handle changes to size of the Option A and Option B columns. An even more adaptive formula could use INDIRECT(), but I am against that on principle!

Answering my own question:
Wrote Macro 1:
Sub Combination1()
Dim arr1 As Variant
Dim arr2 As Variant
Dim i As Long, j As Long, k As Long
Dim ws As Worksheet
Set ws = ThisWorkbook.Sheets("Sheet1")
arr1 = ws.Range("E1", ws.Range("E" & ws.Rows.Count).End(xlUp)).SpecialCells(xlCellTypeConstants).Value
arr2 = ws.Range("F1", ws.Range("F" & ws.Rows.Count).End(xlUp)).SpecialCells(xlCellTypeConstants).Value
ws.Range("D1").Value = "Result"
k = 1
For i = LBound(arr1, 1) To UBound(arr1, 1)
For j = LBound(arr2, 1) To UBound(arr2, 1)
ws.Range("D" & k + 1).Value = arr1(i, 1) & ", " & arr2(j, 1)
k = k + 1
If k = 101 Then Exit For
Next j
If k = 101 Then Exit For
Next i
' Add data validation to column D
With ws.Range("D2", ws.Range("D" & k).End(xlUp))
.Validation.Delete
.Validation.Add Type:=xlValidateList, Formula1:="=" & ws.Range("D2:D" & k).Address
End With
End Sub
This basically reads the values from drop downs.
Macro 2:
Sub Combination2()
Dim arr3 As Variant
Dim ws As Worksheet
Set ws = ThisWorkbook.Sheets("Sheet1")
arr3 = ws.Range("D2", ws.Range("D" & ws.Rows.Count).End(xlUp)).SpecialCells(xlCellTypeConstants).Value
ws.Range("G1").Value = "Result"
For i = LBound(arr3, 1) To UBound(arr3, 1)
ws.Range("G" & i + 1).Value = arr3(i, 1)
Next i
' Add data validation to column G
With ws.Range("G2")
.Validation.Delete
.Validation.Add Type:=xlValidateList, Formula1:="=" & ws.Range("D2:D" & UBound(arr3, 1) + 1).Address
End With
' Clear values in column G except for cell G2
ws.Range("G3", ws.Range("G" & ws.Rows.Count).End(xlUp)).ClearContents
End Sub
This helps to populate the values in another dropdown
Macro 3:
Sub CombinedMacros()
Call Combination1
Call Combination2
End Sub
Happy to "help" people if they have any doubts.

Related

Run through a range of cells and change the date of a linked cell more efficiently

I have a macro which will populate a range of around 216 cells, and 25/30 sheets with an index + match lookup to a separate spreadsheet for each column. (See below table.)
The more months that proceed, the larger this will get.
Each month the string that will be looked up will vary, and I need to show the evolution of elements pertaining to this string for each month since an inception date.
I tried two ways, both of which are fairly slow (typically ~30 secs to populate a sheet).
The first method populates the required range with the lookup formula, and then loops through using the replace function to update the formula to include the columns date.
E.g. pseudo-code.
For j = 1 To 10
s_Date = Format(ws.cells(1,1).value,"yyyy.mm.dd") 'eg say 5/31/2019
s_DStep = Format(ws.cells(1,j).value,"yyyy.mm.dd")'eg say 6/30/2019
For k = 10 To 17
s_formula = ws.Cells(k, j).Formula
s_formula = Replace(s_formula, s_Date, s_DStep)
ws.Cells(k, j).Formula = s_formula
Next k
Next j
The second method loops through the range, it will assign the cell with the required formula and date.
Simplified example:
For j = 1 To i_NumOfDates + 1
s_DStep = Format(ws.cells(1,j).value,"yyyy.mm.dd")'eg say 6/30/201
ws.Cells(10, j + 1) = "=INDEX('" & s_Dir & s_DStep & "\[ Workbook]Sheet1'!$E$1:$E$9999,MATCH($B$2,'" _
& s_Dir & s_DStep & "\["Workbook]Sheet1'!$a$1:$a$9999,0))"
next j
Neither of these methods are quick enough.
I appreciate that lookup formulas are taxing in terms of computing power.
I have done the below.
'Turn off Screen Update
Application.ScreenUpdating = False
'Turn off Automatic Calculation
Application.Calculation = xlManual
'Turn off display alert
Application.DisplayAlerts = False
5/31/2019
6/30/2019
=Index(dir & 5.31.2019 & range1, match(str,dir & 5.31.2019 & range2),0)
=Index(dir & 6.30.2019 & range1, match(str,dir & 6.30.2019 & range2),0)
You could try copying the formulas to an array, processing it and copying it back to the sheet
Option Explicit
Sub demo()
Dim ar1, ar, j As Long, k As Long
Dim s_Date As String, s_DStep As String
Dim rng As Range, ws As Worksheet
Set ws = Sheet1
Set rng = ws.Range("A10:J17")
ar = rng.Formula
ar1 = ws.Range("A1:J1").Value
s_Date = Format(ar1(1, 1), "yyyy.mm.dd") 'eg say 5/31/2019
For j = 1 To UBound(ar, 2) '
s_DStep = Format(ar1(1, j), "yyyy.mm.dd") 'eg say 6/30/2019
For k = 1 To UBound(ar)
ar(k, j) = Replace(ar(k, j), s_Date, s_DStep)
Next k
Next j
rng.Formula = ar
End Sub

Sum Values based on Duplicates - VBA

I am looking for a VBA solution to be able to:
Look for duplicated values in column "A" and format. (Possible with the code below)
With each subsequent duplicate found, the code should sum all the values from Columns "J" through "N" on the first value and fill the duplicated cell black (help)
Sub CombineDuplicates()
Dim Cell As Variant
Dim PList As Range
lRow = Worksheets("Material Planning").Cells(Rows.Count, 1).End(xlUp).Row
Set PList = Worksheets("Material Planning").Range("A4:A" & lRow)
For Each Cell In PList
'Checking whether value in cell already exist in the source range
If Application.WorksheetFunction.CountIf(PList, Cell) > 1 Then
'Highlight duplicate values in red color
cRow = Cell.Row
Range("A" & cRow & ":R" & cRow).Interior.Color = RGB(0, 0, 0)
Else
Cell.Interior.Pattern = xlNone
End If
Next
End Sub
Please see the picture for reference. Top is unfiltered data and the bottom is how it should look after the macro runs. Please let me know if you need any more information. Thanks in advance!
This uses a dictionary to detect duplicates and a class to keep your data organized
Place this piece inside of a class module:
Option Explicit
Private data As datasum
Private prow As Long
Private ptargetsheet As Worksheet
Private Type datasum
thirtyday As Long
threemonth As Long
expectedusage As Double
ordertarget As Double
stock As Long
avgdayleft As Long
dayleft As Long
pending As Long
End Type
Sub initialize(targetsheet As Worksheet, row As Long)
Set ptargetsheet = targetsheet
prow = row
End Sub
Sub addData(dataArray As Variant)
data.thirtyday = data.thirtyday + dataArray(1, 1)
data.threemonth = data.threemonth + dataArray(1, 2)
data.expectedusage = data.expectedusage + dataArray(1, 3)
data.ordertarget = data.ordertarget + dataArray(1, 4)
data.stock = data.stock + dataArray(1, 5)
data.avgdayleft = data.avgdayleft + dataArray(1, 6)
data.dayleft = data.dayleft + dataArray(1, 8)
data.pending = data.pending + dataArray(1, 9)
End Sub
Sub placeData()
With ptargetsheet
.Cells(prow, 6).Value = data.thirtyday
.Cells(prow, 7).Value = data.threemonth
.Cells(prow, 8).Value = data.expectedusage
.Cells(prow, 9).Value = data.ordertarget
.Cells(prow, 10).Value = data.stock
.Cells(prow, 11).Value = data.avgdayleft
.Cells(prow, 13).Value = data.dayleft
.Cells(prow, 14).Value = data.pending
End With
End Sub
And this piece in either your sheet module or a regular module:
Option Explicit
Sub CombineDuplicates()
Dim i As Long
Dim lRow As Long
Dim dict As Object
Set dict = CreateObject("Scripting.Dictionary")
Dim data As DataClass
With Sheets("Material Planning")
lRow = .Cells(.Rows.Count, 1).End(xlUp).row
For i = 4 To lRow
If Not dict.exists(.Cells(i, 1).Value) Then
Set data = New DataClass
data.initialize Sheets("Material Planning"), i
data.addData .Range(.Cells(i, 6), .Cells(i, 14)).Value
dict.Add .Cells(i, 1).Value, data
Else
dict(.Cells(i, 1).Value).addData .Range(.Cells(i, 6), .Cells(i, 14)).Value
dict(.Cells(i, 1).Value).placeData
.Range(.Cells(i, 1), .Cells(i, 14)).Interior.Color = RGB(0, 0, 0)
End If
Next i
End With
End Sub
This would be a simple, but probably not the fastest way of doing it:
Sub CombineDuplicates()
Dim Cell As Variant, PList As Range
Dim i As Long, j As Long, a As Long
Dim k(7) As Long
LRow = Worksheets(1).Cells(Rows.Count, 1).End(xlUp).Row
For i = 4 To LRow
Erase k
If Not Range("A" & i).Interior.Color = RGB(0, 0, 0) Then
For j = i + 1 To LRow
If Range("A" & i).Value = Range("A" & j).Value Then
For a = 0 To 7
k(a) = k(a) + Cells(j, a + 2)
Next a
Range("A" & j & ":N" & j).Interior.Color = RGB(0, 0, 0)
End If
Next j
For a = 0 To 7
Cells(i, a + 2) = Cells(i, a + 2) + k(a)
Next a
End If
Next i
End Sub
Essentially, for each row that isn't black (to avoid unnessecary calculaitons) we loop the rest of the range to look for duplicats. Add the values in the array k and keep looking.
Then we end the subloop by adding the number from the array to the current row, and keep going.
Should probably add something to clear the interior formatting first, for subsequent runs.
So after sitting and brainstorming for a while, I figured that I was trying to overcomplicate things. Thanks to your responses it helped me figure out the direction that I wanted to go. This is the current code that I have which is working flawlessly! It is a little slow, but since I am not going to be shifting through thousands of data points, its is manageable.
I tried to insert value added comments in the code to show the process:
Sub CombineDuplicates()
Dim Cell As Variant
Dim PList As Range
Worksheets("Material Planning").Unprotect
Set ws = Worksheets("Material Planning")
'set last row of working range
lRow = Worksheets("Material Planning").Cells(Rows.Count, 1).End(xlUp).Row
'Toggle parameter. If any cells in range are not colored then it will execute the macro to add common values
If Range("A4:A" & lRow).Interior.ColorIndex = xlColorIndexNone Then
For i = 1 To lRow
Application.ScreenUpdating = False
Application.EnableEvents = False
'since all of the "duplicate" values are listed near each oter, I just need to compare them one after another
Fst = ws.Range("A" & i)
Snd = ws.Range("A" & i + 1)
If Snd = Fst Then
'saves the Formula from the cell but just adds the value from the current cell to the next one
'this way even if there are more than 2 duplicates, the sum will continue on to the next cell
ws.Range("F" & i + 1).Formula = ws.Range("F" & i + 1).Formula & "+" & ws.Range("F" & i).Value
ws.Range("G" & i + 1).Formula = ws.Range("G" & i + 1).Formula & "+" & ws.Range("G" & i).Value
ws.Range("J" & i + 1).Formula = ws.Range("J" & i + 1).Formula & "+" & ws.Range("J" & i).Value
'The whole Row will be filled black so that it is not considered in the analysis
Range("A" & i & ":U" & i).Interior.Color = RGB(0, 0, 0)
End If
Next
Application.ScreenUpdating = True
Application.EnableEvents = True
Else
'if there is already formatting on any cells in column A, this will remove the filled black formatting from all cells in the range
Range("A4:U" & lRow).Interior.Color = xlNone
ws.Range("F4:N" & ws.Cells(Rows.Count, 6).End(xlUp).Row).FillDown
ws.Range("P4:U" & ws.Cells(Rows.Count, 6).End(xlUp).Row).FillDown
End If
Application.ScreenUpdating = True
Application.EnableEvents = True
Worksheets("Material Planning").Protect
End Sub
Thank you all for your help and advice on this!
Excel has a built-in dedup function. Can you not programmatically copy the 'Simple Description' column at the top to the area underneath, run the dedup on the range containing the copy, then add sumifs to the remaining columns?
The code below creates the bottom table from the top table shown in the picture.
Sub Dedup()
Range("A1:A9").Copy
Range("A12").PasteSpecial
Range("B1:E1").Copy
Range("B12").PasteSpecial
Range("A13:A20").RemoveDuplicates Columns:=1
Range("B13").Formula = "=SUMIF($A$2:$A$9,$A13,B$2:B$9)"
Range("B13").Copy Destination:=Range("B13:E17")
End Sub
Of course, this doesn't maintain the structure with the black rows, but I haven't understood why you need that anyway, since you still have the original table.
And you'll want to do something a little more sophisticated about identifying the correct ranges, particularly for the copied table and when copying the sumif formula from the first cell to the last cell in the range that results from the deduplication. I've kept it simple here for expediency.
Edit: If you want the bottom table to reflect the structure of the original table, you could do a countif on each of the rows in the copy and insert the requisit number of rows that that gives you, and make the new rows black.
Paste Special xlPasteSpecialOperationAdd
This is a slow solution but may be easily understood.
It loops through the cells in column A and uses Application.Match to find the index (position) of the first occurrence. If it is not the same then it colors the row and uses PasteSpecial with xlPasteSpecialOperationAdd to add the found values to the values defined by the index.
Application.ScreenUpdating will speed up the code hiding the on-going 'worksheet dance'.
The Code
Option Explicit
Sub CombineDuplicates()
Dim ws As Worksheet
Dim PList As Range
Dim Cell As Range
Dim ColsAll As Range
Dim Cols1 As Range
Dim Cols2 As Range
Dim cIndex As Variant
Dim lRow As Long
Dim cRow As Long
Set ws = Worksheets("Material Planning")
lRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row
Set PList = ws.Range("A4:A" & lRow)
Set ColsAll = ws.Columns("A:N")
Set Cols1 = ws.Columns("F:K")
Set Cols2 = ws.Columns("M:N")
Application.ScreenUpdating = False
For Each Cell In PList.Cells
cRow = Cell.Row
cIndex = Application.Match(Cell.Value, PList, 0) + 3
If cIndex < cRow Then
ColsAll.Rows(cRow).Interior.Color = RGB(0, 0, 0)
Cols1.Rows(cRow).Copy
Cols1.Rows(cIndex) _
.PasteSpecial xlPasteValues, xlPasteSpecialOperationAdd
Cols2.Rows(cRow).Copy
Cols2.Rows(cIndex) _
.PasteSpecial xlPasteValues, xlPasteSpecialOperationAdd
Else
ColsAll.Rows(cRow).Interior.Pattern = xlNone
End If
Next
Application.CutCopyMode = False
ws.Range("A3").Select
Application.ScreenUpdating = True
End Sub
Try this code, please. It should be very fast, using arrays and working only in memory and does not need to color anything. The processing result, meaning only the unique values with the necessary sum per each column will be dropped on a new sheet added after the processed one:
Sub CombineDuplicates()
`It needs a reference to 'Microsoft Scripting Runtime'
Dim LROW As Long, arrA, arr, arrR(4), arrF, dict As New Scripting.Dictionary
Dim sh As Worksheet, resSh As Worksheet, i As Long, j As Long, arrFin
Set sh = Worksheets("Material Planning")
LROW = sh.cells(rows.Count, 1).End(xlUp).row
arrA = sh.Range("A4:A" & LROW).value
arr = sh.Range("J4:N" & LROW).value
For i = 1 To UBound(arrA)
If Not dict.Exists(arrA(i, 1)) Then
For j = 0 To 4
arrR(j) = arr(i, j + 1)
Next j
dict.Add arrA(i, 1), arrR
Else
For j = 0 To 4
arrR(j) = dict(arrA(i, 1))(j) + arr(i, j + 1)
Next j
dict(arrA(i, 1)) = arrR
End If
Next i
ReDim arrFin(1 To dict.Count, 1 To 5)
ReDim arrF(1 To dict.Count, 1 To 1)
For i = 0 To dict.Count - 1
arrF(i + 1, 1) = dict.Keys(i)
For j = 0 To 4
arrFin(i + 1, j + 1) = dict.items(i)(j)
Next
Next i
Set resSh = Worksheets.Add(After:=sh) 'add a new sheet aftere the active one and drop the array at once
resSh.Range("A2").Resize(UBound(arrF), 1).value = arrF
resSh.Range("J2").Resize(UBound(arrFin), UBound(arrFin, 2)).value = arrFin
End Sub
This approach will allow running the code as many times you need, after eventual updates or just in case. Otherwise, it will return double dates each next time...
If you have a problem with adding the necessary reference, please run the next code before the one able to process your data:
Sub addScrRunTimeRef()
'Add a reference to 'Microsoft Scripting Runtime':
'In case of error ('Programmatic access to Visual Basic Project not trusted'):
'Options->Trust Center->Trust Center Settings->Macro Settings->Developer Macro Settings->
' check "Trust access to the VBA project object model"
Application.VBE.ActiveVBProject.References.AddFromFile "C:\Windows\SysWOW64\scrrun.dll"
End Sub
Edited:
If you insist to keep all the range, and making black the interior of duplicates, you can try the next code, also very fast. It will also return in a newly created sheet, but only for testing reason. If it does what you want, the code can be easily adapted to overwrite the existing range of the active sheet:
Sub CombineDuplicatesKeepAll()
Dim LROW As Long, arrA, arrR(14), arrF, dict As New Scripting.Dictionary
Dim sh As Worksheet, resSh As Worksheet, i As Long, j As Long, arrFin, firstR As Long
Dim rngCol As Range, k As Long
Set sh = Worksheets("Material Planning")
LROW = sh.cells(rows.Count, 1).End(xlUp).row
firstR = 4 'first row of the range to be processed
arrA = sh.Range("A" & firstR & ":N" & LROW).value 'place the range to be processed in an array
ReDim arrFin(1 To UBound(arrA), 1 To UBound(arrA, 2)) 'set the final array at the same dimensions
For i = 1 To UBound(arrA) 'iterate between the array elements
If Not dict.Exists(arrA(i, 1)) Then 'if not a dictionary key as value in column A:A (array column 1):
arrR(0) = sh.Range("A" & i + firstR - 1).Address 'place the cell address like forst dictionary item array element
arrR(1) = i 'the array second element will be the array row (to update it later)
arrFin(i, 1) = arrA(i, 1) 'first element of the final array, on i row will be the first column value
For j = 2 To 14
arrR(j) = arrA(i, j) 'input the rest of the row values in the array to be the dictionary item
arrFin(i, j) = arrA(i, j) 'place the same values in the final array
Next j
dict.Add arrA(i, 1), arrR 'add the array built above like dictionary item
Else
arrR(0) = dict(arrA(i, 1))(0) 'keep the same call address like the first element of the array to be input as item
arrFin(i, 1) = arrA(i, 1) 'place the value in column A:A in the first column of the final array
arrR(1) = dict(arrA(i, 1))(1) 'keep the row of the first dictionary key occurrence
For j = 2 To 14 'fill the array with the values of all row columns
If j <= 9 Then 'for first 9 columns keep their value
arrR(j) = dict(arrA(i, 1))(j)
Else 'for the rest (J to N) add the existing value (in dictionary) to the cells value
arrR(j) = dict(arrA(i, 1))(j) + arrA(i, j)
End If
arrFin(i, j) = arrA(i, j) 'fill the final array with the row data
Next j
dict(arrA(i, 1)) = arrR 'place the array like dictionary item
If rngCol Is Nothing Then 'if range to be colored does not exist, create it:
Set rngCol = sh.Range("A" & i + firstR - 1 & ":N" & i + firstR - 1)
Else 'if it exists, make a Union between existing and the new one:
Set rngCol = Union(rngCol, sh.Range("A" & i + firstR - 1 & ":N" & i + firstR - 1))
End If
End If
Next i
'adapt te final array rows which used to be the first occurrence of the same dictionary key:
For i = 0 To dict.Count - 1
k = dict.items(i)(1) 'extract the previously memorized row to be updated
For j = 2 To 14 'adapt the row content, for the row range equivalent columns
arrFin(k, j) = dict.items(i)(j)
Next
Next i
'just for testing, paste the result in a new added sheet.
'If everything OK, the code can drop the value in the active sheet
Set resSh = Worksheets.Add(After:=sh)
'drop the array content at once:
resSh.Range("A4").Resize(UBound(arrFin), UBound(arrFin, 2)).value = arrFin
If Not resSh Is Nothing Then _
resSh.Range(rngCol.Address).Interior.Color = vbBlack 'color the interior of the next occurrences
End Sub
I tried commenting the code lines, in a way to be easily understood. If something unclear, do not hesitate to ask for clarifications.
Please, send some feedback after testing it.

Using Index Match to get the Desired Result from the Date

I have been striving hard to make a formula which match the dates and populate the index.
I have created this formula but if there are more then similar two dates or three or more in the data then how the data for all the similar dates will be populated in the cell.
I have attached a sheet where 1st Table has Data, 2nd is my table where i have applied below formula and third table is the accurate example the result i have been looking for.
Your help will be appreciated.
=IFERROR(INDEX(C2:C92,MATCH(F3,A2:A92,0)))
Link:
https://docs.google.com/spreadsheets/d/1WT7MJuNqspJGU6wLtKQRp2BxpuiRknkEKfZR4MZd-0A/edit?usp=sharing
If you have Office 365 then in cell F3 put:
=TEXTJOIN(CHAR(10),TRUE,IF($A$2:$A$92=E3,$C$2:$C$92,""))
and in cell G3 put:
=TEXTJOIN(CHAR(10),TRUE,IF($A$2:$A$92=E3,($B$2:$B$92*100)&"%",""))
Please note that percentage is not actual percentage but a concatenated string in the output formula.
EDIT
You can try below UDF if you don't have TEXTJOIN
Public Function ConcatStringConditional(rngCritCol As Range, rngCrit As Range, rngConcat As Range) As String
Dim i As Long
For i = 1 To rngCritCol.Cells.Count
If rngCritCol.Cells(i, 1).Value2 = rngCrit.Value2 Then
If Len(ConcatStringConditional) > 0 Then
ConcatStringConditional = ConcatStringConditional & vbCrLf & Format(rngConcat.Cells(i, 1).Value, rngConcat.Cells(i, 1).NumberFormat)
Else
ConcatStringConditional = Format(rngConcat.Cells(i, 1).Value, rngConcat.Cells(i, 1).NumberFormat)
End If
End If
Next i
End Function
This shall be copied to a module in Visual Basic Editor by choosing Insert|Module. You can google to see the procedure if you are unsure. Once put in a module then it can be used like a normal formula e.g.
=ConcatStringConditional($A$2:$A$92,E4,$C$2:$C$92)
This is basic functionality, please feel free to edit to your requirements further.
Note: Macros must be enabled for the UDF to run properly!
Please, try the next UDF function. It uses array and will be very fast, for big ranges:
Function bringData(D As Range, Optional X As String) As String
Dim sh As Worksheet, arr, lastR As Long, i As Long, strD As String
Set sh = ActiveSheet
lastR = sh.Range("A" & sh.rows.Count).End(xlUp).row
arr = sh.Range("A2:C" & lastR).value
For i = 1 To UBound(arr)
If CDate(arr(i, 1)) = CDate(D.value) Then
strD = strD & IIf(X = "D", arr(i, 3), arr(i, 2) * 100 & "%") & vbLf
End If
Next i
If strD <> "" Then
strD = left(strD, Len(strD) - 1)
bringData = strD
Else
bringData = "No Match"
End If
End Function
It can be called in the next way:
To obtain the Data (per date) the formula will be:
=bringData(E2,"D")
In order to bring the percentage, the formula should be:
=bringData(E2)
The above code assumes that the first Table is in the range "A:C" and the the second one in the range "E:G", starting from the second row...
If in different ranges, the code should be easy to adapt, I think. If not clear enough, do not hesitate to ask.
Please, test it and send some feedback.
Edited:
Try the next code for the three options. Use "D", "D2" or nothing like the second function parameter:
Function bringData(D As Range, Optional X As String) As String
Dim Sh As Worksheet, arr, lastR As Long, i As Long, strD As String
Set Sh = ActiveSheet
lastR = Sh.Range("A" & Sh.rows.Count).End(xlUp).row
arr = Sh.Range("A2:D" & lastR).value
For i = 1 To UBound(arr)
If CDate(arr(i, 1)) = CDate(D.value) Then
strD = strD & IIf(X = "D", arr(i, 3), IIf(X = "D2", arr(i, 4), arr(i, 2) * 100 & "%")) & vbLf
End If
Next i
If strD <> "" Then
strD = left(strD, Len(strD) - 1)
bringData = strD
Else
bringData = "No Match"
End If
End Function
I will be available only after 3 - 4 hours...

excel vba cycle through rows and output to new sheet

I have a DATA sheet which contains data rows as follows:
And I have a sheet named ROWBUILDER that has formulas and produces results like this:
I would like to write a VBA code that will cycle through every row in the ROWBUILDER sheet and output data to a new worksheet.
NOTE: The ROWBUILDER sheet must remain as is. Only the resulting data must be copied to the OUTPUT sheet.
I have no idea how to do it and from where to start. Will appreciate any help, examples or links.
Many thanks in advance!
This moves all the data to one output sheet.
Sub MoveROWBUILDER()
Sheets("ROWBUILDER").Range("A1").CurrentRegion.Copy
Sheets("Output").Range("A1").PasteSpecial xlPasteValues
End Sub
If you are looking for one sheet per row, try this. You may run into a limit depending on memory and the amount of data.
Sub DataToSheets()
Dim sh As Worksheet
Dim rowCount As Integer, colCount As Integer
Dim i As Integer, j As Integer
Dim data() As Variant
Sheets("ROWBUILDER").Select
rowCount = Range("A1").CurrentRegion.Rows.Count
colCount = Range("A1").CurrentRegion.Columns.Count
data() = Sheets("ROWBUILDER").Range("A1").CurrentRegion.Value2
For i = 2 To rowCount
Set sh = Sheets.Add(After:=ActiveSheet)
sh.Name = "Data" & (i - 1)
For j = 1 To colCount
sh.Cells(1, j) = data(1, j)
sh.Cells(2, j) = data(i, j)
Next j
Next i
End Sub
If you can estimate the maximum number of rows in your data sheet then - instead of cycling through every row - you can use
rowbuilder.Range("A1:B" & lastrow).Copy
newworksheet.Range("A1").PasteSpecial xlValues
Let me try this again now that I have a better idea of what you need.
You can process all of the rows at once using dynamics arrays in ROWBUILDER. Set C1 in ROWBUILDER to
=COUNTA(DATA!A:A) - 1
Then set A2 to
=OFFSET(DATA!A2,0,0,C1,1) & " " & OFFSET(DATA!B2,0,0,C1,1)
This will spill all of the full names to column A. You can then set B2 to
=OFFSET(DATA!H2,0,0,C1,1) & " " & OFFSET(DATA!G2,0,0,C1,1) & " " & OFFSET(DATA!F2,0,0,C1,1) & OFFSET(DATA!E2,0,0,C1,1)
This will spill the Full Address to Column B. IF you still need to copy it to OUTPUT, then this code should do the job.
Range("A2#").Copy
offsetrows = Sheets("OUTPUT").Range("A1").CurrentRegion.Rows.Count
Sheets("OUTPUT").Select
Range("A1").Offset(offsetrows, 0).Select
Selection.PasteSpecial xlPasteValues
You can use loop like this:
numberRows = Range("A2").End(xlToDown).Row
Sheets.Add.Name = "OUTPUT"
For i = 2 To numberRows
Sheets("OUTPUT").Cells(i, 1).Value = Sheets("DATA").Cells(i, 1).Value
Sheets("OUTPUT").Cells(i, 2).Value = Sheets("DATA").Cells(i, 8).Value & " " & Sheets("DATA").Cells(i, 7).Value & " " & Sheets("DATA").Cells(i, 6).Value & " " & Sheets("DATA").Cells(i, 5).Value
Next i

Is it possible to compare two columns in excel and update the master column using VBA?

I have two sheets in my excel workbook.
Contained in these sheets are my primary key columns.
I want to compare the first column (which is the master) to the second column (source) using a VBA loop.
The reason is because the source usually contains new primary keys.
Please can anyone be kind enough to help me figure out a logic to compare these columns and add the unique values to the master column.
Thank you.
this image shows the sample master code
this image shows the sample source code
The code below shows what I have so far
Sub PullUniques()
Dim rngCell As Range
For Each rngCell In Sheet1.Range("W3:W40")
If WorksheetFunction.CountIf(Range("D3:D40"), rngCell) = 0 Then
Range("C" & Rows.Count).End(xlUp).Offset(1) = rngCell
End If
Next
For Each rngCell In Sheet6.Range("D3:D40")
If WorksheetFunction.CountIf(Range("W3:W40"), rngCell) = 0 Then
Range("W" & Rows.Count).End(xlUp).Offset(1) = rngCell
End If
Next
End Sub
Try this code, please. It is based on the assumption that in source sheet there could be keys not existing in your "Master" sheet, which will be add on the first empty row of the master sheet.
Sub testMasterUpdate()
Dim shM As Worksheet, shS As Worksheet, s As Long, boolF As Boolean
Dim lastRM As Long, lastRS As Long, m As Long
Dim arrM As Variant, arrS As Variant, arrDif As Variant, d As Long
Set shM = Worksheets("Master") 'please, use here your sheet name
Set shS = Worksheets("Source") 'please, use here your sheet name
lastRM = shM.Range("A" & Cells.Rows.Count).End(xlUp).Row
lastRS = shS.Range("A" & Cells.Rows.Count).End(xlUp).Row
arrM = shM.Range("A2:A" & lastRM).value
arrS = shS.Range("A2:A" & lastRS).value
ReDim arrDif(1 To 1, 1 To UBound(arrM) + UBound(arrS)): d = 1
For s = 1 To UBound(arrS)
For m = 1 To UBound(arrM)
If arrS(s, 1) = arrM(m, 1) Then
boolF = True
Exit For
End If
Next m
If Not boolF Then
arrDif(1, d) = arrS(s, 1)
d = d + 1
End If
boolF = False
Next s
If d > 1 Then
ReDim Preserve arrDif(1 To 1, 1 To d - 1)
'shM.Range("A" & lastRM + 1).Resize(UBound(arrDif, 2), 1).value = _
WorksheetFunction.Transpose(arrDif)
shM.Range("A" & lastRM).Resize(UBound(arrDif, 2), 1).value = _
WorksheetFunction.Transpose(arrDif)
lastRM = shM.Range("A" & Cells.Rows.Count).End(xlUp).Row
shM.Range("A" & lastRM + 1).Formula = "=CountA(A2:A" & lastRM & ")"
End If
End Sub
Please, replace generic sheet names with your real ones.

Resources