vba how to select a set of rows based on multiple column - excel

Please help
I am trying to perform the following:
I have an excel file 'A' with 50000 rows.
I am creating another excel 'B' with 150 rows.
The 150 rows are picked from file 'A'.
The row selection criteria is based on values of 5 different columns as this set
First I want to make sure I select the rows with all different combination of these 5 columns
If I run out of combinations then I can pick combination which are repeated as have to reach the 150
What I have achieved till now is selecting 150 random rows from excel A and pasted in excel B
records = 150
With DataWs
SourceLastRow = .Cells(.Rows.count, "B").End(xlUp).Row
.Rows(1).Copy DestinationWs.Cells(DestLastRow, "A")
ar = RandomNumber(2, SourceLastRow, Records)
For r = 2 To UBound(ar)
DestLastRow = DestLastRow + 1
.Rows(ar(r)).Copy DestinationWs.Cells(DestLastRow, "A")
Next r
End With
Function RandomNumber(Bottom As Long, Top As Long, Amount As Long) As Variant
Dim i As Long, r As Long, temp As Long
ReDim iArr(Bottom To Top) As Long
For i = Bottom To Top: iArr(i) = i: Next i
For i = 1 To Amount
r = Int(Rnd() * (Top - Bottom + 1 - (i - 1))) _
+ (Bottom + (i - 1))
temp = iArr(r): iArr(r) = iArr(Bottom + i - 1): _
iArr(Bottom + i - 1) = temp
Next i
ReDim Preserve iArr(Bottom To Bottom + Amount - 1)
RandomNumber = iArr
End Function

This is maybe a bit complex but worked for me:
Sub PickRows()
Const COPY_ROWS As Long = 150
Dim dict As Object, data, DataWS As Worksheet, DestWS As Worksheet
Dim numCopied As Long, r As Long, k As String, destRow As Long
Dim combo As Long, keys, col As Collection, theRow As Long, t
Set DataWS = Sheet2 'for example
Set DestWS = Sheet3 'for example
'get the source data (at least the part with the key columns) in an array
data = DataWS.Range("A1:E" & DataWS.Cells(DataWS.Rows.Count, "B").End(xlUp).Row).Value
Set dict = CreateObject("scripting.dictionary")
'fill the dictionary - keys are combined 5 columns, values are collection
' containing the row number for each source row with that key
For r = 2 To UBound(data, 1)
k = RowKey(data, r, Array(1, 2, 3, 4, 5)) 'combination of the 5 columns
If Not dict.exists(k) Then
dict.Add k, New Collection 'new combination?
End If
dict(k).Add r
Next r
numCopied = 0
combo = 0
destRow = 2
'loop over the various key column combinations and pick a row from each
' keep looping until we've copied enough rows
Do While numCopied < COPY_ROWS
'see here for why the extra ()
'https://stackoverflow.com/questions/26585884/runtime-error-with-dictionary-when-using-late-binding-but-not-early-binding
Set col = dict.Items()(combo) 'a collection of all rows for this particular key
theRow = RemoveRandom(col)
'edit line below to copy more columns (eg change 5 to 10)
DataWS.Cells(theRow, 1).Resize(1, 5).Copy DestWS.Cells(destRow, 1)
destRow = destRow + 1 'next destination row
If col.Count = 0 Then dict.Remove dict.keys()(combo) 'remove if no more rows for this key
If dict.Count = 0 Then Exit Do 'run out of any rows to pick? (should not happen...)
combo = combo + 1
If combo >= dict.Count Then combo = 0 'start looping again
numCopied = numCopied + 1
Loop
End Sub
'Create a composite key from columns in arrKeyCols
Function RowKey(data, rowNum, arrKeyCols) As String
Dim rv, i, sep
For i = LBound(arrKeyCols) To UBound(arrKeyCols)
rv = rv & sep & data(rowNum, arrKeyCols(i))
sep = "~~"
Next i
RowKey = rv
End Function
'select a random item from a collection, remove it, and return the value
Function RemoveRandom(col As Collection)
Dim rv, num As Long
num = Application.RandBetween(1, col.Count)
RemoveRandom = col(num)
col.Remove num
End Function

Related

Visual basic copy and paste in excel is very slow

I have sheet which contains lots of column data, first column is date one in second column there are quantities and in the third one there are codes of item :
the data looks like this :
date qty code qty code
01.01.2022 0 4355 2 4356
02.01.2022 0 4355 2 4356
03.01.2022 0 4355 2 4356
....................................
and I want to have like this :
date qty code
01.01.2022 0 4355
02.01.2022 0 4355
03.01.2022 0 4355
01.01.2022 2 4356
02.01.2022 2 4356
03.01.2022 2 4356
I wrote the code in visual basic for macro which cuts fourth and fifth columns pasts at the end of second and third columns and then deletes empty columns and continuous until there are no empty columns my code works but it takes hours to execute on 1000+ columns and I want to know if there is any possible way to optimize it.
code:
Sub CutAndPasteColumnsUntilEmpty()
Dim lastRow As Long
Dim i As Integer
lastRow = ActiveSheet.UsedRange.Rows.Count
Do Until IsEmpty(Range("D2")) And IsEmpty(Range("E2"))
Range("D2:D" & lastRow).Cut Destination:=Range("B" & lastRow + 1)
Range("E2:E" & lastRow).Cut Destination:=Range("C" & lastRow + 1)
lastRow = ActiveSheet.UsedRange.Rows.Count
Columns("D:E").Delete
Loop
End Sub
This routine doesn't copy/paste any data.
It reads your table into an array, then creates a new array from that input array in the format you want. It then creates a new tab and writes the output to that tab. It should take seconds, not hours.
This will output by reading each row at a time.
Sub ReorganiseTable()
'Declarations
Dim LastRow As Long
Dim LastColumn As Long
Dim NoOfRows As Long
Dim NoOfColumnSets As Long
Dim o As Long, r As Long, c As Long
With ActiveSheet
'Find Last Row of table
LastRow = .Cells(.Rows.Count, "A").End(xlUp).Row
NoOfRows = LastRow - 1
'Find Last Column of table
LastColumn = .Cells(1, .Columns.Count).End(xlToLeft).Column
NoOfColumnSets = (LastColumn - 1) / 2
'Copy table to array
Dim ArrInput
ArrInput = ActiveSheet.Range(ActiveSheet.Cells(1, 1), ActiveSheet.Cells(LastRow, LastColumn)).Value
'Create output array for filling
ReDim ArrOutput(1 To NoOfColumnSets * NoOfRows + 1, 1 To 3)
'Copy headers across
ArrOutput(1, 1) = ArrInput(1, 1)
ArrOutput(1, 2) = ArrInput(1, 2)
ArrOutput(1, 3) = ArrInput(1, 3)
'Copy data across order by rows,columns
o = 2
For r = 2 To LastRow
For c = 2 To LastColumn Step 2
ArrOutput(o, 1) = ArrInput(r, 1)
ArrOutput(o, 2) = ArrInput(r, c)
ArrOutput(o, 3) = ArrInput(r, c + 1)
o = o + 1
Next
Next
End With
'Create a new tab
Worksheets.Add
'Write output array to tab
ActiveSheet.Cells(1, 1).Resize(UBound(ArrOutput), 3).Value = ArrOutput
End Sub
If you'd prefer it ordered by reading each column set at a time, invert the two For statements:
'Copy data across order by rows,columns
o = 2
For c = 2 To LastColumn Step 2
For r = 2 To LastRow
ArrOutput(o, 1) = ArrInput(r, 1)
ArrOutput(o, 2) = ArrInput(r, c)
ArrOutput(o, 3) = ArrInput(r, c + 1)
o = o + 1
Next
Next
This is one of those occasions when the OP may be reaching for VBA too soon. The task can also be achieved via spreadsheet functions (if the Excel version is recent enough):
The formula in cell G2 is:
=LET(n,COUNTA(A:A)-1,arr,OFFSET(A2:E2,0,0,n),r,SEQUENCE(n*2),c,SEQUENCE(,3),IF(r>n,INDEX(arr,r-n,IF(c>1,c+2,c)),INDEX(arr,r,c)))
The LET function allows you to do intermediate calculations and store the result in a variable.
n = number of rows in the input data
arr = input array (resized from first row in data, for n rows)
r = a vector of rows (1 .. 2n) in the output table
c = a vector of columns (1 .. 3) in the output table
The final parameter is the calculation, which takes columns (1,2,3) from the input for the first n rows of the output table, and thereafter takes columns (1,4,5) for rows n+1 to 2n.
This has the benefits that the sheet can remain a .xlsx file (and hence avoid security warnings) and the output columns will update automatically (with calc set to auto) as new data is added to the input. It will also be faster than VBA.
Stack Columns
It looks like it's written for any number of columns (Cols, Current) but it isn't (too lazy).
It only works for stacking column pairs to the first column.
The total number of columns is supposed to be odd (first + even).
It also includes the headers.
=LET(Data,A1:K7,Cols,2,
Current,TAKE(Data,,1+Cols),First,DROP(TAKE(Data,,1),1),Other,DROP(Data,1,1+Cols),
rCount,ROWS(First),cCount,COLUMNS(Other),cCountHalf,rCount*cCount/Cols,
SeqFirst,MOD(SEQUENCE(cCountHalf)-1,rCount)+1,
SeqOther,Cols*ROUNDUP(SEQUENCE(cCountHalf,,,Cols)/(Cols*rCount),0)-1,
VSTACK(Current,HSTACK(INDEX(First,SeqFirst),
INDEX(Other,SeqFirst,SeqOther),INDEX(Other,SeqFirst,SeqOther+1))))
Regarding to CLRs solution i would like to show my additional variant. With this one you can vary the columnsets as you wish.
Sub ReorganizeTable()
Dim arrA As Variant
Dim arrB As Variant
Dim c As Long
Dim r As Long
Dim isFirstRun As Boolean
Dim ColumnSet As Long
Dim RowSet As Long
Dim maxCols As Long
Dim maxRows As Long
Dim maxSets As Long
Dim ToggleCol As Integer
ColumnSet = 3 'set your columns here
With Cells(1, 1).CurrentRegion 'get dimension and array
maxRows = .Rows.Count - 1 '-1 = remove headers if available
maxCols = .Columns.Count
arrA = .Offset(1, 0).Resize(maxRows, maxCols)
End With
maxSets = maxCols / ColumnSet
ReDim arrB(1 To maxRows * maxSets, 1 To ColumnSet)
isFirstRun = True
For c = 0 To maxCols - 1 'must not start with 1, see ToggleCol below
ToggleCol = c Mod ColumnSet + 1 'switches between 1 and 2 but has to start with 1
If Not isFirstRun Then
If ToggleCol = 1 Then
RowSet = RowSet + maxRows
End If
End If
For r = 1 To maxRows
arrB(r + RowSet, ToggleCol) = arrA(r, c + 1)
Next
isFirstRun = False
Next
Range("...").Resize(UBound(arrB, 1), UBound(arrB, 2)) = arrB 'set your outputrange here
End Sub
Add one line: Application.ScreenUpdating = False
Sub CutAndPasteColumnsUntilEmpty()
Dim lastRow As Long
Dim i As Integer
lastRow = ActiveSheet.UsedRange.Rows.Count
Application.ScreenUpdating = False
Do Until IsEmpty(Range("D2")) And IsEmpty(Range("E2"))
Range("D2:D" & lastRow).Cut Destination:=Range("B" & lastRow + 1)
Range("E2:E" & lastRow).Cut Destination:=Range("C" & lastRow + 1)
lastRow = ActiveSheet.UsedRange.Rows.Count
Columns("D:E").Delete
Loop
End Sub

How to Copy Data into an Array/Manipulate It/Paste to new sheet

SETUP
I have an Excel file with my source data in Columns in A to J.
In Column K there's a 'Send Type' value which can either be "Many" or "Single".
In Column L there's a 'Send Count' value, which is numeric ("N").
OBJECTIVE
copy the source data
insert N-1 rows
paste that data N-1 times into those rows.
I'd like the result to be the data pasted N times.
If N is 1, there's no copy/insert/paste required
if N is greater than 1, the result should be the data displayed N times.
Example Excel Sheet
CURRENT VBA
Sub Copy_PROD_Paste_Send_Count()
Dim Copy_Row As Integer
Dim Send_Count As Variant
Dim TargetMapCount As Integer
Dim ProgressCount As Integer
Dim Send_Type As String
Dim ProgressTarget As Integer
Copy_Row = 1
TargetMapCount = Application.WorksheetFunction.SumIf(Range("K:K"), "Many", Range("L:L"))
Send_Type = Cells(Copy_Row, "K")
ProgressTarget = Application.WorksheetFunction.Count(Range("A:A")) + Application.WorksheetFunction.SumIf(Range("K:K"), "Many", Range("L:L")) - Application.WorksheetFunction.CountIf(Range("K:K"), "Many")
Application.ScreenUpdating = False
Do While (Cells(Copy_Row, "A") <> "")
Send_Count = Cells(Copy_Row, "L")
Send_Type = Cells(Copy_Row, "K")
If (Send_Type = "Many" And (Send_Count > 1) And IsNumeric(Send_Count)) Then
Range(Cells(Copy_Row, "A"), Cells(Copy_Row, "L")).Copy
Range(Cells(Copy_Row + 1, "A"), Cells(Copy_Row + Send_Count - 1, "L")).Select
Selection.Insert Shift:=xlDown
Copy_Row = Copy_Row + Send_Count - 1
ProgressCount = Range("A" & Rows.Count).End(xlUp).Row
Application.StatusBar = "Updating :" & ProgressCount - 1 & " of " & ProgressTarget & ": " & Format((ProgressCount - 1) / ProgressTarget, "0%")
End If
Copy_Row = Copy_Row + 1
Loop
End Sub
PROBLEM STATEMENT
The macro executes up to about 2-3k rows before crashing. I need to run up to 15k.
I understand I should try to copy the data into an array, manipulate it within that array and then paste the results back to a new sheet. I can't work out how.
Here is a routine that copies the input table to an array and then builds an output array (in memory) from the inputs, before dumping the output array back to the worksheet.
Note: It ignores the Single/Many column and instead just uses the value in the last column to determine how many times to repeat the output.
Sub RepeatData()
'Declarations
Dim LastRow As Long
Dim LastColumn As Long
Dim m As Long, n As Long, o As Long, r As Long, c As Long
With ActiveSheet
'Find Last Row of table
LastRow = .Cells(.Rows.Count, "B").End(xlUp).Row
'Find Last Column of table
LastColumn = .Cells(2, .Columns.Count).End(xlToLeft).Column
'Copy table to array
Dim ArrInput
ArrInput = ActiveSheet.Range(ActiveSheet.Cells(1, 1), ActiveSheet.Cells(LastRow, LastColumn)).Value
'sum up multiples (total of last column in table) so we know how big the output array needs to be
OutputRows = 1 + Application.WorksheetFunction.Sum(Cells(2, LastColumn).Resize(UBound(ArrInput) - 1, 1))
'Create output array for filling
ReDim ArrOutput(1 To OutputRows, 1 To LastColumn)
'Copy data across
o = 1 ' start outputting to index 1 of ArrOutput
For r = 1 To LastRow
If r = 1 Then ' if on header row
m = 1 ' set repeat to once (set m to 1)
Else ' if not on header row
m = ArrInput(r, LastColumn) ' set repeat to value in last column
End If
For n = 1 To m ' loop to repeat
For c = 1 To LastColumn ' cycle across columns
ArrOutput(o, c) = ArrInput(r, c) 'copy value
Next
o = o + 1 ' increment output index
Next
Next
End With
'Write output array to sheet, 5 rows below the end of the input table
ActiveSheet.Cells(LastRow + 5, 1).Resize(UBound(ArrOutput), UBound(ArrOutput, 2)).Value = ArrOutput
End Sub

Converting weekly data in a table to monthly data using VBA

I have a table of hours against weeks (start of the week is a Sunday). The weekly data goes up to 12-16 months dependent on user input. I want to create a VBA macro which will iterate through this table of weekly hours data and convert the columns into monthly data.
Example:
All October 2021 related columns will collapse into 1 column called Oct-21. This will also combine the hours. 2nd row in the image below would equal 4+3+4+0= therefore value would be 11 in the new combined column's 2nd row.
My current thinking was calculating the Sundays between the start date and the last date which is below:
Dim d As Date, format As String, w As Long, FirstSunday As String
format = format(lastMonth, "Medium Date")
d = DateSerial(Year(format), Month(format), 1)
w = Weekday(d, vbSunday)
FirstSunday = d + IIf(w <> 1, 8 - w, 0)
Any ideas on how to do this?
Not sure how you want to group the weeks into months as some months will have 5 weeks. This code inserts a column when the month changes and then fills it with a sum formula for the relevant week columns. It assumes the dates are on row 1 , the task numbers in column 1 and the first week is in column 2.
Option Explicit
Sub ByMonth()
Dim wb As Workbook, ws As Worksheet
Dim LastCol As Long, LastRow As Long, c As Long, n As Long
Dim dt As Date
Set wb = ThisWorkbook
Set ws = wb.Sheets("Sheet1")
LastCol = ws.Cells(1, Columns.Count).End(xlToLeft).Column
LastRow = ws.Cells(Rows.Count, 1).End(xlUp).Row
' scan cols from right to left insert new columns
Application.ScreenUpdating = False
For c = LastCol + 1 To 3 Step -1
' add columns on month change
If Month(ws.Cells(1, c)) <> Month(ws.Cells(1, c - 1)) Then
ws.Columns(c).Insert
With ws.Columns(c)
.HorizontalAlignment = xlCenter
'.Interior.Color = RGB(255, 255, 200)
.Font.Bold = True
.Cells(1).NumberFormat = "#"
End With
End If
Next
' scan left to right filling new cols with sum() formula
' hide weekly columns
LastCol = ws.Cells(1, Columns.Count).End(xlToLeft).Column
n = 0
For c = 2 To LastCol + 1
If ws.Cells(1, c) = "" Then
dt = ws.Cells(1, c - 1)
ws.Cells(1, c) = MonthName(Month(dt), True) & " " & Year(dt)
ws.Cells(2, c).Resize(LastRow - 1).FormulaR1C1 = "=SUM(RC[-" & n & "]:RC[-1])"
n = 0
Else
ws.Columns(c).EntireColumn.Hidden = True
n = n + 1
End If
Next
' copy visible month columns to sheet2
ws.Cells.SpecialCells(xlCellTypeVisible).Copy
With wb.Sheets("Sheet2")
.Activate
.Range("A1").PasteSpecial xlPasteValues
.Range("A1").Select
End With
' end
ws.Columns.Hidden = False
Application.ScreenUpdating = True
Application.CutCopyMode = False
MsgBox "Done"
End Sub
Please, try the next code. It assumes that in column A:A, starting from the 6th row, there are (not sorted) tasks. If they are sorted, the code will run without problem, too. It uses arrays and a dictionary and mostly working in memory, should be very fast for big ranges:
Sub SumWeeksMonths()
Dim sh As Worksheet, sh1 As Worksheet, lastR As Long, arrWk, arrMonths, arrTasks
Dim i As Long, k As Long, j As Long, El, arr, arrFin, dict As New Scripting.Dictionary
Set sh = ActiveSheet 'use there the sheet to be processed
Set sh1 = sh.Next 'use here the sheet where the processed result to be returned
lastR = sh.Range("A" & sh.rows.count).End(xlUp).row 'last row (in column A:A)
arrWk = sh.Range(sh.Range("B5"), sh.cells(5, sh.Columns.count).End(xlToLeft)).Value 'place the Week headers in a 2D array
ReDim arrMonths(UBound(arrWk, 2) - 1)'redim the 1D array to keep the unique munths, at a maximum size
For i = 1 To UBound(arrWk, 2) - 1 'create the array of (only) months:
If month(DateValue(arrWk(1, i))) <> month(DateValue(arrWk(1, i + 1))) Then
k = k + 1: arrMonths(k) = Format(DateValue(arrWk(1, i + 1)), "mmm-yyyy")
Else
arrMonths(k) = Format(DateValue(arrWk(1, i)), "mmm-yyyy")
End If
Next i
ReDim Preserve arrMonths(k) 'preserve only the existing Date elements
For Each El In sh.Range("A4:A" & lastR).Value
dict(El) = 1 'extract the unique tasks (only to count them for ReDim the necessary array)
Next El
'place all the range to be processed in an array (for faster iteration):
arr = sh.Range("A5", sh.cells(lastR, sh.cells(5, sh.Columns.count).End(xlToLeft).Column)).Value
ReDim arrFin(1 To UBound(dict.Keys) + 1, 1 To UBound(arrMonths) + 2) 'reDim the final array to keep processed data
ReDim arrTasks(UBound(arrMonths)) 'redim the array to temporarily keep the array of each task summ
dict.RemoveAll: k = 0 'clear the dictionary and reitinialize the K variable
For i = 2 To UBound(arr) 'iterate between the main array elements:
If Not dict.Exists(arr(i, 1)) Then 'if the Task key does not exist:
For Each El In arrMonths 'iterate between each month in arrMonths:
For j = 2 To UBound(arr, 2) 'iterate between all arr columns for the i row:
If month(DateValue(arr(1, j))) = month(El) Then 'if column months is a specific arrMonths column:
arrTasks(k) = arrTasks(k) + arr(i, j) 'sumarize everything in the arrTask each element
End If
Next j
k = k + 1 'increment k, for the next month
Next El
dict.Add arr(i, 1), arrTasks 'create the dictionary key with the tasks array as item
ReDim arrTasks(UBound(arrMonths)): k = 0 'reinitialize arrTasks and k variable
Else 'if dictionary (task) key exists:
For Each El In arrMonths
For j = 2 To UBound(arr, 2)
If month(DateValue(arr(1, j))) = month(El) Then
arrTasks(k) = dict(arr(i, 1))(k) + arr(i, j) 'add the sum to the allready existing elements
End If
Next j
k = k + 1
Next El
dict(arr(i, 1)) = arrTasks 'make the item the updaded array
ReDim arrTasks(UBound(arrMonths)): k = 0 'reinitialize arrTasks and k variable
End If
Next i
'place the processed values in final array (arrFin):
For i = 0 To UBound(arrMonths) 'firstly the headers:
arrFin(1, i + 2) = arrMonths(i)
Next i
'Extract the tasks value for each month and place in the final array appropriate columns:
For i = 0 To dict.count - 1 'iterate between the dictionary elements:
arrFin(i + 2, 1) = dict.Keys(i) 'place the task in the array first column, starting from the second row
For j = 0 To UBound(dict.items(i)) 'iterate between the dictionary item array elements
arrFin(i + 2, j + 2) = dict.items(i)(j) 'place the appropriate array elements in the final array (arrFin)
Next j
Next i
'drop the final array at once and make some formatting:
With sh1.Range("A1").Resize(UBound(arrFin), UBound(arrFin, 2))
.Value = arrFin
With .rows(1)
.Font.Bold = True
.Interior.ColorIndex = 20
.BorderAround 1
End With
.EntireColumn.AutoFit
.BorderAround 1
End With
sh1.Activate 'to see the processing result...
MsgBox "Ready..."
End Sub
Please, test it and send some feedback.

How can i use VBA Code to Copy and Paste Specific Cells if Condition is Met in two or more worksheets to different areas of another worksheet

Looking for a little more help please. I was here a month ago a RiskyPenguin gave me a great bit of code. I would like to add to this.
This is the part that works:
So if the "invoice" spreadsheet (sheet 5), if cell G4 (for example is I111) matches any of the data in the first column of the "income" spreadsheet (sheet 1) (starting at row 6) then the corresponding data in columns 2 3, 8 & 9 will copy over to the "invoice" spreadsheet in columns 2, 3, 4 & 5 (starting at row 13).
Sub FindAndCopyData2()
Dim shData As Worksheet, shReport As Worksheet
Set shData = Sheet1
Set shReport = Sheet6
Dim strInvoceNumber As String
strInvoceNumber = shReport.Cells(4, "E").Value
Dim intLastRow As Integer
intLastRow = shData.Cells(Rows.Count, 1).End(xlUp).Row
Dim intReportRow As Integer
intReportRow = 13
shReport.Range("B13:E20").ClearContents
Dim i As Integer
For i = 1 To intLastRow
If shData.Cells(i, 1).Value2 = strInvoceNumber Then
shReport.Cells(intReportRow, 2).Value2 = shData.Cells(i, 3).Value2
shReport.Cells(intReportRow, 3).Value2 = shData.Cells(i, 4).Value2
shReport.Cells(intReportRow, 4).Value2 = shData.Cells(i, 8).Value2
shReport.Cells(intReportRow, 5).Value2 = shData.Cells(i, 9).Value2
intReportRow = intReportRow + 1
End If
Next i
End Sub
I would then like to (hopefully using the same search)
Take the "invoice" spreadsheet (sheet 5), if cell G4 (for example is I111) matches any of the data in the second column of the "expenses" spreadsheet (sheet 2) (starting at row 11) then the corresponding data in columns 3, 5, & 7 will copy over to the "invoice" spreadsheet in columns 2, 4 & 6 (starting at row 13).
Is this possible or does it have to be a separate piece of programming?
Many Thanks for any advise.
Assuming this could be useful for others I made a function out of it and refactored the initial code to handle the copy in memory. I setup your first lookup so you just need to edit the variables to get your second lookup:
Option Explicit
''''''''''''''''''''''''''''''''''''''
''Main Sub
''''''''''''''''''''''''''''''''''''''''''''''''''
Sub main()
'Set some vars
Dim sourceArr, targetArr, sourceCls, targetCls, sourceStartRw As Long, targetStartRw As Long, dict As Object, j As Long, sourceLookupCl As Long, Matchkey As Long
''''''''''''''''''''''''''''''''''''''
''Lookup 1
''''''''''''''''''''''''''''''''''''''''''''''''''
Matchkey = Sheet5.Range("G4").Value2 'lookupKey
sourceCls = Split("2,3,8,9 ", ",") 'Columns to copy from
targetCls = Split("2,3,4,5", ",") 'Columns to copy to
sourceStartRw = 6
targetStartRw = 13
sourceLookupCl = 1 'matching column
'get data in memory = array
sourceArr = Sheet1.Range("A1").CurrentRegion.Value2
'call our function
targetArr = reorder(sourceArr, sourceCls, targetCls, sourceStartRw, sourceLookupCl, Matchkey)
'dump to sheet
With Sheet5
.Range(.Cells(targetStartRw, 1), .Cells(UBound(targetArr) + targetStartRw - 1, UBound(targetArr, 2))).Value2 = targetArr
End With
''''''''''''''''''''''''''''''''''''''
''Lookup 2 => change source and target cols to your need
''''''''''''''''''''''''''''''''''''''''''''''''''
Matchkey = Sheet5.Range("G4").Value2
sourceCls = Split("2,3,8,9 ", ",")
targetCls = Split("2,3,4,5", ",")
sourceStartRw = 6
targetStartRw = 13 'must be the same as previous lookup if you want to keep the targetArr from previous lookups
sourceLookupCl = 1
'get data in memory = array
sourceArr = Sheet1.Range("A1").CurrentRegion.Value2
'call our function keeping the data from the first lookup
targetArr = reorder(sourceArr, sourceCls, targetCls, sourceStartRw, sourceLookupCl, Matchkey, targetArr)
'dump to sheet
With Sheet5
.Range(.Cells(targetStartRw, 1), .Cells(UBound(targetArr) + targetStartRw - 1, UBound(targetArr, 2))).Value2 = targetArr
End With
End Sub
''''''''''''''''''''''''''''''''''''''
''Supporting function
''''''''''''''''''''''''''''''''''''''''''''''''''
Private Function reorder(sourceArr, sourceCls, targetCls, sourceStartRw As Long, sourceLookupCl As Long, Matchkey As Long, Optional targetArr) As Variant
Dim dict As Object, j As Long
'if the target array overlaps the previous lookups pass it to the function
If IsMissing(targetArr) Then
ReDim targetArr(1 To UBound(sourceArr), 1 To UBound(sourceArr, 2))
End If
'build a dict to compare quickly
Set dict = CreateObject("Scripting.Dictionary") 'create dictionary lateB
For j = 1 To UBound(sourceArr) 'traverse source
dict(sourceArr(j, sourceLookupCl)) = Empty
Next j
'check if key exists in dict and copy data
Dim i As Long, ii As Long ': ii = 1
If dict.Exists(Matchkey) Then
For j = sourceStartRw To UBound(sourceArr)
For i = 1 To UBound(sourceArr, 2)
If i = sourceCls(ii) Then
targetArr(j - sourceStartRw + 1, targetCls(ii)) = sourceArr(j, i)
ii = IIf(ii < UBound(sourceCls), ii + 1, ii)
End If
Next i
ii = 0
Next j
End If
reorder = targetArr
End Function

Finding Overlap counts

Hi I have 2 columns (cities and members) with 5 unique cities and 100,000 members. Some members may be assigned to multiple cities. I would like a graph that has 6 columns and the 6 rows of the cities (with additional No City Assigned Column). The values in the table would be the member counts. So I'm basically trying to count overlap. How can I accomplish this?
I wish it would be as simple as dragging my cities field into both columns and rows in the pivot table but I can't.
I would like it to look like this:
Assuming you want conditional formatting:
Select the full range you want to apply the formatting to. Then apply a rule (formula option) so that =B$1=$A2 (assuming your have selected from B2 to some end). The use of the fixed ("$") condition on column or row helps determine that this applies to each cell in the selected range individually.
Here's a VBA solution. Set up a working array with one row for each distinct member, one column for each distinct city, and fill with 1's where member and city coincide. Then transfer into output array where pairs of columns in working array have been set to 1:
Option Explicit
Sub MultResponse()
Dim LastRow, LastColumn As Long
Dim sht1, sht2 As Worksheet
Dim workArray() As Integer
Dim cityDict As New Scripting.Dictionary
Dim memberDict As New Scripting.Dictionary
Dim city, member As String
Dim item As Integer
Dim i, j As Long
Dim memberNo As Long
Dim cityNo As Integer
Dim outputArray() As Integer
Dim outrow, outcol, rowTotal As Integer
Set sht1 = ThisWorkbook.Worksheets("Sheet1")
Set sht2 = ThisWorkbook.Worksheets("Sheet2")
LastRow = sht1.Cells(sht1.Rows.Count, "A").End(xlUp).Row
' Make list of distinct cities
j = 0
For i = 2 To LastRow
city = sht1.Cells(i, 2)
If city <> "" And Not cityDict.Exists(city) Then
j = j + 1
cityDict.Add Key:=city, item:=j
End If
Next i
' Make list of distinct members
j = 0
For i = 2 To LastRow
member = sht1.Cells(i, 1)
If member <> "" And Not memberDict.Exists(member) Then
j = j + 1
memberDict.Add Key:=member, item:=j
End If
Next i
' Set up and fill array with one row for each distinct member, one column for each distinct city
ReDim workArray(1 To memberDict.Count, 0 To cityDict.Count)
For i = 2 To LastRow
member = sht1.Cells(i, 1)
city = sht1.Cells(i, 2)
If city <> "" And member <> "" Then
memberNo = memberDict(member)
cityNo = cityDict(city)
workArray(memberNo, cityNo) = 1
End If
Next i
' Fill output array where pairs of columns in work array are set to 1
' outputArray(0,0) is used for members with missing city
ReDim outputArray(0 To cityDict.Count, 0 To cityDict.Count)
'First do ones with no affiliation
For i = 1 To memberDict.Count
rowTotal = 0
For j = 1 To cityDict.Count
rowTotal = rowTotal + workArray(i, j)
Next j
If rowTotal = 0 Then outputArray(0, 0) = outputArray(0, 0) + 1
Next i
' Then do ones with affiliation
For outrow = 1 To cityDict.Count
For outcol = 1 To cityDict.Count
For i = 1 To memberDict.Count
If workArray(i, outrow) = 1 And workArray(i, outcol) = 1 _
Then outputArray(outrow, outcol) = outputArray(outrow, outcol) + 1
Next i
Next outcol
Next outrow
' Transfer output array into sheet
For i = 0 To cityDict.Count
For j = 0 To cityDict.Count
sht2.Cells(i + 2, j + 2) = outputArray(i, j)
Next j
Next i
'Insert row and column headers
sht2.Cells(1, 2) = "N/C"
sht2.Cells(2, 1) = "N/C"
For i = 0 To cityDict.Count - 1
sht2.Cells(i + 3, 1) = cityDict.Keys(i)
Next i
For j = 0 To cityDict.Count - 1
sht2.Cells(1, j + 3) = cityDict.Keys(j)
Next j
End Sub
Test data
Results

Resources