I had 2 tables. The 1st table contains the department name and number of people, the other contains department name and some other information. I'm trying to copy the number of people from the 1st table to the 2nd table based on the department name.
However, the size of the table are not equal as for the 2nd table, the department name can appear more than once or even not at all.
Tables are from different worksheets.
Example of Table 1
Example of Table 2
I had successfully get the data from the column using dynamic array and pass between subs, but unable to compare when copy the value when match.
My codes structure
Sub getTable1()
Dim dept, getNum As Variant
Dim i,x As Long
x = 0
ReDim dept(1 To 1)
ReDim getNum(1 To 1)
With ThisWorkbook.Sheets("Table1")
For i= 1 To .Cells(Rows.Count, "A").End(xlUp).Row
x = x + 1
ReDim Preserve dept(1 To x)
ReDim Preserve getNum(1 To x)
dept(x) = .Cells(i, "A").Value
getNum(x) = .Cells(i, "B").Value
Next x
End With
For i = 1 to x
Call passValue(dept(i), getNum(i))
Next
End Sub
Sub passValue(ByVal dept, getNum As Variant)
Dim target As Variant
ReDim target(1 To 1)
Dim i, cnt, rowCnt As Long
cnt = 0
With ThisWorkbook.Sheets("Table2")
For i = 2 To .Cells(Rows.Count, "D").End(xlUp).Row
cnt = cnt + 1
ReDim Preserve target(1 To cnt)
target(cnt) = .Cells(i, "D").Value
Next i
End With
For i = 1 To cnt
If target(i) = dept Then ' If match print result
With ThisWorkbook.Sheets("Table2")
For rowCnt = 2 To .Cells(Rows.Count, "D").End(xlUp).Row
.Cells(rowCnt, "E").Value = getNum ' Only print the last result
Next
End With
End If
Next
End Sub
Here is an example of what I was describing, you probably want some error handling in case a dept appears in sheet Table2 that is not in Table1:
Public Sub getTable1()
Dim wb As Workbook
Dim ws1 As Worksheet
Dim ws2 As Worksheet
Set wb = ThisWorkbook
Set ws1 = wb.Worksheets("Table1")
Set ws2 = wb.Worksheets("Table2")
Dim lastRowT1 As Long
lastRowT1 = ws1.Cells(Rows.Count, "A").End(xlUp).Row 'find last row in column A of first sheet
Dim lastRowT2 As Long
lastRowT2 = ws2.Cells(Rows.Count, "A").End(xlUp).Row 'find last row in column A of second sheet
Dim table1Arr()
table1Arr = ws1.Range("A2:B" & lastRowT1).Value '1 to 4, 1 to 2 'read the range from A2:B to last used row in A e.g. A2:B6 into array creating a 2D array that starts at index 1. The table is now held in the array.
Dim table2Arr()
table2Arr = ws2.Range("A2:B" & lastRowT2).Value '1 to 3, 1 to 2 'read used range containing table 2 into an array.
Dim table1Dict As New Scripting.Dictionary 'required reference to MS Scripting Runtime
Dim i As Long
For i = LBound(table1Arr, 1) To UBound(table1Arr, 1) 'loop the first dimension of array 1 i.e. the depts.
If table1Dict.Exists(table1Arr(i, 1)) Then
table1Dict(table1Arr(i, 1)) = table1Dict(table1Arr(i, 1)) + table1Arr(i, 2) 'if dept exists as a key in the dict then add the number of people from array 1 (i.e. from table 1) to the existing value. This handles potentially repeating depts in table1.
Else
table1Dict.Add table1Arr(i, 1), table1Arr(i, 2) 'if dept not already in dict, add the dept as a key to the dict and the number of people as the value.
End If
Next i
For i = LBound(table2Arr, 1) To UBound(table2Arr, 1) 'next loop your table 2 array depts
table2Arr(i, 2) = table1Dict(table2Arr(i, 1)) 'as department names are spelt the same across both tables you can use the table2 dept names as the key to retrieve the dictionary values for that dept in the dictionary i.e. from table1. Then simply assign that to the Others column i.e. table2Arr(i, 2)
Next i
End Sub
See Chip Pearson's article on working with arrays. From that article you can see how to write back out to Table2 sheet the second array:
Writing A Two Dimensional VBA Array To The Worksheet
If you have a 2 dimensional array, you need to use Resize to resize
the destination range to the proper size. The first dimension is the
number of rows and the second dimension is the number of columns. The
code below illustrates writing an array (..table2Arr..) out to the worksheet
starting at cell (..A2..).
Dim Destination As Range
Set Destination = ws2.Range("A2")
Destination.Resize(UBound(table2Arr, 1), UBound(table2Arr, 2)).Value = table2Arr
Related
I'm trying to create an associative table on a sheet that is pulling in data from a different sheet. By associative I mean, if the data is changed in the source data sheet, it would be reflected on the new sheet. I also want to only have the new sheet's table to be contingent on having a certain unique value. In my case, I want to pull up information related to a part number. The original source data will have many rows that contain the same part number, but I only care to display one of them.
This is what I have so far:
Function IsInArray(stringToBeFound As String, arr As Variant) As Boolean
IsInArray = (UBound(Filter(arr, stringToBeFound)) > -1)
End Function
Dim ref() As Variant
Dim row As Integer
row = 92
Worksheets("Part Tracking Scorecard").Activate
While Cells(row, 6).Value:
If IsInArray(Cells(row, 6).Value, ref) Then
row = row + 1
ElseIf Not IsInArray(Cells(row, 6).Value, ref) Then
ReDim Preserve ref(1 To UBound(ref) + 1) As Variant
ref(UBound(ref)) = Cells(row, 6).Value
Worksheets("Unique Parts").Activate
?????
row = row + 1
To satisfy my condition to only showcase the unique part numbers, I initialized an empty array called "ref". Then, as I iterate through the source sheet, I would check if the part number was in ref with the function "IsInArray". If it was in it, it would move onto the next row, if it wasn't add the part number into the empty array and move to the next row.
The portion with the "????" is where I'm having most of my issue trying to figure out. That part is supposed to be where I make the new table with the date from the unique part number. The very simple and tedious thing I could do is make some loop to run through the columns of the rows and put in a vlookup function. I was wondering if there may be a more robust or more elegant way in doing this.
You've had the right reflex tyring to define an array to stock your values. Here are a few tips of how I would get around to doing it (not perfect, but it should help you out):
Function IsInArray(stringToBeFound As String, arr As Variant) As Boolean
IsInArray = (UBound(Filter(arr, stringToBeFound)) > -1)
End Function
Dim Source as Worksheets
Set Source = Worksheets("Part Tracking Scoreboard")
Dim ref1(), ref2() As Variant
Dim row, index, index2 As Integer
row = 92
ref1 = Source.Range(Worksheets(Source.Cells(row,1), Source.Cells(lastrow, last column))
'Start by placing your ENTIRE source sheet in ref1, if your source sheet is big, this will help you win A LOT of time during the looping phase. Notice how I start from row 92 seeing as this is where you started your loop
'lastrow and lastcolumn represent the position of the last cell in your source file
For index = row to lastrow
If Not IsInArray(ref1(row, 6).Value, ref2) Then
ref2(index) = ref1(index) 'copy the entire row from source to ref2
Next index
Dim NewFile as Worksheet
Set Newfile = Sheets("NewSheetName")
Dim ref2dimension_x, ref2dimension_y as Integer 'find dimensions of ref2 array
ref2dimension_x= UBound(ref2, 1) - LBound(ref2, 1) + 1
ref2dimension_y = UBound(ref2, 2) - LBound(ref2, 2) + 1
For index = 2 to ref2dimension_x 'go through entire new sheet and set values
For index2 = 1 to ref2dimension_y
NewFile.Cells(index, index2).Value = ref2(index - 1, index2)
Next index2
Next index
ref1() = nothing
ref2() = nothing 'free up the space occupied by these arrays
I was not sure about what you were trying to do exactly during the else loop. If you intention is to copy the entire row, this should work. If you want to copy only specific data from the source sheet, you will need to find the indexes of the corresponding columns (hardcode them if they are not going to budge, or use a loop to find them through string comparison otherwise).
This solution combines some macros that I use frequently (so even if you don't use them now, they might be helpful in the future). It won't work if the data in the unique table needs to be "live", but if it'd be sufficient for it to be updated whenever the workbook is opened/closed (or on demand), this is a lot less complicated than the array version.
Basically you just:
Copy the main/unduplicated table to a new sheet
Remove duplicates by part number
Remove unnecessary columns from unduplicated table (if applicable)
I'm assuming that your source data is in a formal Excel Table (ListObject). Just swap out "PartTable" for whatever your actual table is called.
Sub makeUniqueTable()
Application.ScreenUpdating = False
Dim MainWS As Worksheet
Set MainWS = ThisWorkbook.Sheets("Part Tracking Scorecard")
Dim UniqueWS As Worksheet
Set UniqueWS = ThisWorkbook.Sheets("Unique Parts")
UniqueWS.Cells.Clear
Call cloneTable(MainWS.ListObjects("PartTable"), "UniquePartTable", UniqueWS)
Dim UniquePartTable As ListObject
Set UniquePartTable = UniqueWS.ListObjects("UniquePartTable")
Call removeDuplicates(UniquePartTable, "Part Number")
'Optional: remove unnecessary columns by listing columns to be deleted...
'Call deleteColumns(UniquePartTable, Array("Unnecessary Column 1", "Unnecessary Column 2"))
'...or kept:
'Call deleteColumns(UniquePartTable, Array("Part Number", "Manufacturer", "Product Description"), True)
Application.ScreenUpdating = True
End Sub
Sub cloneTable(tbl As ListObject, newName As String, Optional newWS As Worksheet = Nothing)
'Copies a table (tbl) to a new worksheet (newWS) and gives it a name (newName)
'If there is any data in newWS, the new table will be added to the right of the used range
'If newWS is omitted, new table will be added to same worksheet as original table
Dim ws As Worksheet
Dim lastColumn As Long
Dim newRng As Range
Dim newTbl As ListObject
If newWS Is Nothing Then
Set ws = tbl.Parent
lastColumn = ws.Cells(1, ws.Columns.Count).End(xlToLeft).Column
Set newRng = ws.Range(ws.Cells(1, lastColumn + 2), ws.Cells(1 + tbl.ListRows.Count, lastColumn + tbl.ListColumns.Count + 1))
Else
Set ws = newWS
If ws.ListObjects.Count > 0 Then
lastColumn = ws.Cells(1, ws.Columns.Count).End(xlToLeft).Column
Set newRng = ws.Range(ws.Cells(1, lastColumn + 2), ws.Cells(1 + tbl.ListRows.Count, lastColumn + tbl.ListColumns.Count + 1))
Else
Set newRng = ws.Range(ws.Cells(1, 1), ws.Cells(1 + tbl.ListRows.Count, tbl.ListColumns.Count))
End If
End If
tbl.Range.Copy
newRng.PasteSpecial Paste:=xlPasteValuesAndNumberFormats
Application.CutCopyMode = False
Set newTbl = ws.ListObjects.Add(xlSrcRange, newRng, , xlYes)
newTbl.Name = newName
End Sub
Sub removeDuplicates(tbl As ListObject, Optional colName As Variant = "")
'Removes duplicates from a table (tbl) based on column header names (colName()) provided by user
'If no column names are provided, duplicates will be removed based on all columns in table
Dim i As Long
Dim j As Long
If Not IsArray(colName) Then
If colName = "" Then
ReDim colNumArr(0 To tbl.ListColumns.Count - 1) As Variant
For i = 0 To tbl.ListColumns.Count - 1
colNumArr(i) = tbl.ListColumns(i + 1).Range.Column
Next
Else
ReDim colNumArr(0 To 0) As Variant
colNumArr(0) = tbl.ListColumns(colName).Range.Column
End If
Else
ReDim colNumArr(0 To UBound(colName) - LBound(colName)) As Variant
j = 0
For i = LBound(colName) To UBound(colName)
colNumArr(j) = tbl.ListColumns(colName(i)).Range.Column
j = j + 1
Next
End If
tbl.Range.removeDuplicates Columns:=(colNumArr), Header:=xlYes
End Sub
Sub deleteColumns(tbl As ListObject, ByVal colName As Variant, Optional invert As Boolean = False, Optional sheetCol As Boolean = True)
'Deletes column(s) from sheet based on header names (colName) from a table (tbl)
'Will result in error if provided column contains multiple tables
'colName can be a String or an array of Strings
'Inverted mode deletes all columns *except* those in colName
Dim i As Long
Dim j As Long
Dim x As Boolean
If Not IsArray(colName) Then
tempStr = colName
ReDim colName(1 To 1) As String
colName(1) = tempStr
End If
If invert = False Then
For i = LBound(colName) To UBound(colName)
If sheetCol = True Then
tbl.Parent.Columns(tbl.ListColumns(colName(i)).Range.Column).Delete
Else
tbl.ListColumns(colName(i)).Delete
End If
Next
Else
For i = tbl.ListColumns.Count To 1 Step -1
x = False
For j = LBound(colName) To UBound(colName)
If tbl.HeaderRowRange(i).Value = colName(j) Then
x = True
Exit For
End If
Next
If x = False Then
If sheetCol = True Then
tbl.Parent.Columns(tbl.ListColumns(i).Range.Column).Delete
Else
tbl.ListColumns(i).Delete
End If
End If
Next
End If
End Sub
What the case is:
So I got a "results sample" in excel format that needs filtering and reshaping to look nice. It is a result that will be not identical all the time but it follows similar rules. I have to filter it further and make it a little more tidy. I have figured out the filtering part, but I am not sure how to sort the remaining data, in a tidy way.
What the situation is:
There are six columns involved.
Notice: Real deal is not THAT simple, but what I need can be demonstrated using such a simple example and then I can manage more complex stuff myself I suppose.
For our example we use columns from B to G
The data are set as pairs of a "title" and a value.
For instance, if you look the first example picture I provide, The first detais the pair B3 and C3.
As you can see, looking at the same picture, D3 and E3 is an empty pair.
Same goes for D4 - E4 and F4 - G4 and so on until a last one at B11 - C11.
Starting data example:
[
What I want to achieve:
I would like, using Visual Basic for Applications, to sort the data, starting from let's say for our example B3 (see second picture) and fill three SETS of two columns, (BC, DE, FG) if there are no data inside those cells.
Notice: If a cell like D3 is null then SURELY E3 will be null too so there can be just only one check. I mean we can check either value columns or title columns.
Notice2: The B,D,F or C,E,G columns DON'T have to be sorted. I just want all the not-null values of B,D,F and their respective values from C,E,G gathered together neat so printing will not need 30 pages but just a few (too many spaces between is causing it and I try to automate the cleanup)
Here's something to start with. The first double loop populates a VBA Collection with Range variables that refer to the Cells that contain the titles.
The associated values are obtained by using an offset. The middle double loop performs a bubble sort on the latter (highly inefficient - you might want to replace it with something else). The next if statement creates a 2nd sheet if it doesn't exist on which to write out the results (last loop).
Option Explicit
Sub GatherData()
Dim lastRow As Integer, lastCol As Integer
Dim r As Integer, c As Integer
Dim vals As Collection
Set vals = New Collection
With Sheets(1)
lastCol = .UsedRange.Columns(.UsedRange.Columns.Count).Column
lastRow = .UsedRange.Rows(.UsedRange.Rows.Count).row
For c = 1 To lastCol Step 2
For r = 1 To lastRow
If (Trim(Cells(r, c).Value) <> "") Then
vals.Add .Cells(r, c)
End If
Next
Next
End With
' Bubble Sort
Dim i As Integer, j As Integer
Dim vTemp As Range
For i = 1 To vals.Count - 1
For j = i + 1 To vals.Count
If vals(i).Value > vals(j).Value Then
Set vTemp = vals(j)
vals.Remove j
vals.Add vTemp, vTemp, i
End If
Next j
Next i
Dim sht2 As Worksheet
If ThisWorkbook.Worksheets.Count = 1 Then
Set sht2 = ThisWorkbook.Sheets.Add(After:=ThisWorkbook.Sheets(1))
Else
Set sht2 = Worksheets(2)
End If
With sht2
r = 3
c = 2
For i = 1 To vals.Count
.Cells(r, c).Value = vals(i).Value
.Cells(r, c + 1).Value = vals(i).Offset(, 1).Value
c = c + 2
If c = 8 Then
r = r + 1
c = 2
End If
Next
End With
End Sub
Here is a method using the Dictionary object. I use early binding which requires setting a reference to Microsoft Scripting Runtime. If you are going to be distributing this, you might want to convert this to late-binding.
We assume that your data is properly formed as you show it above. In other words, all the titles are in even numbered columns; and the results are in the adjacent cell.
We create the dictionary using the Title as the Key, and the adjacent cell value for the Dictionary item.
We collect the information
Transfer the Keys to a VBA array and sort alphabetically
create a "Results Array" and populate it in order
write the results to a worksheet.
I will leave formatting and header generation to you.
By the way, there is a constant in the code for the number of Title/Value pair columns. I have set it to 3, but you can vary that.
Enjoy
Option Explicit
Option Compare Text 'If you want the sorting to be case INsensitive
'set reference to Microsoft Scripting Runtime
Sub TidyData()
'Assume Titles are in even numbered columns
'Assume want ColPairs pairs of columns for output
'Use dictionary with Title as key, and Value as the item
Dim dctTidy As Dictionary
Dim arrKeys As Variant
Dim wsSrc As Worksheet, wsRes As Worksheet, rRes As Range
Dim vSrc As Variant, vRes As Variant
Dim LastRow As Long, LastCol As Long
Dim I As Long, J As Long, K As Long, L As Long
Dim V As Variant
'in Results
Const ColPairs As Long = 3
'Set Source and results worksheet and range
Set wsSrc = Worksheets("sheet1")
Set wsRes = Worksheets("sheet2")
Set rRes = wsRes.Cells(1, 2)
'Read source data into variant array
With wsSrc.Cells
LastRow = .Find(what:="*", after:=.Item(1, 1), _
LookIn:=xlValues, searchorder:=xlByRows, searchdirection:=xlPrevious).Row
LastCol = .Find(what:="*", after:=.Item(1, 1), _
LookIn:=xlValues, searchorder:=xlByColumns, searchdirection:=xlPrevious).Column
vSrc = .Range(.Cells(1, 1), .Cells(LastRow, LastCol))
End With
'Collect the data into a dictionary
Set dctTidy = New Dictionary
For I = 1 To UBound(vSrc, 1)
For J = 2 To UBound(vSrc, 2) Step 2
If vSrc(I, J) <> "" Then _
dctTidy.Add Key:=vSrc(I, J), Item:=vSrc(I, J + 1)
Next J
Next I
'For this purpose, we can do a simple sort on the dictionary keys,
' and then create our results array in the sorted order.
arrKeys = dctTidy.Keys
Quick_Sort arrKeys, LBound(arrKeys), UBound(arrKeys)
'Create results array
ReDim vRes(1 To WorksheetFunction.RoundUp(dctTidy.Count / ColPairs, 0), 1 To ColPairs * 2)
I = 0
J = 0
For Each V In arrKeys
K = Int(I / ColPairs) + 1
L = (J Mod ColPairs) * 2 + 1
vRes(K, L) = V
vRes(K, L + 1) = dctTidy(V)
I = I + 1
J = J + 1
Next V
'write the results
Set rRes = rRes.Resize(UBound(vRes, 1), UBound(vRes, 2))
With rRes
.Worksheet.Cells.Clear
.Value = vRes
.HorizontalAlignment = xlCenter
End With
End Sub
Sub Quick_Sort(ByRef SortArray As Variant, ByVal first As Long, ByVal last As Long)
Dim Low As Long, High As Long
Dim Temp As Variant, List_Separator As Variant
Low = first
High = last
List_Separator = SortArray((first + last) / 2)
Do
Do While (SortArray(Low) < List_Separator)
Low = Low + 1
Loop
Do While (SortArray(High) > List_Separator)
High = High - 1
Loop
If (Low <= High) Then
Temp = SortArray(Low)
SortArray(Low) = SortArray(High)
SortArray(High) = Temp
Low = Low + 1
High = High - 1
End If
Loop While (Low <= High)
If (first < High) Then Quick_Sort SortArray, first, High
If (Low < last) Then Quick_Sort SortArray, Low, last
End Sub
Assuming we got all variables set and initialized properly, in this example:
Sheets("sheetname").Select ' because stupid things can happen...
For i = 3 To 13
Let newrangeT = "B" & i '
Let newrangeV = "C" & i '
If Sheets("sheetname").Range(newrangeV) <> "" Then
values(Position) = Sheets("sheetname").Range(newrangeV)
titles(Position) = Sheets("sheetname").Range(newrangeT)
Position = Position + 1
Else
' Don't do anything if the fields are null
End If
Next i
Sheets("sheetname").Range("B1:G13").Clear
' We then get each data from the arrays with a For loop.
' We set a columnset variable to 1.
' We set a currentrow variable to 3.
' If columnset is 1 data will enter in B and C and columnset = columnset +1
' Then if columnset is 2 we set data to DE and columnset = columnset +1
' But if columnset is 2we set data to FG and columnset = 1 and currentrow = currentrow +1
' Iterating the arrays will result in a neat setting of the data, but it will add zeros for all the nulls. Thus we need an If statement that will exclude that values checking the TITLE array (that should contain a title instead). if the value is not 0 then... we run what I describe, otherwise we do nothing.
Putting the data in the array is half of the trick.
Then we clear the area.
We set two string variables to declare ranges (actually cell reference) for every cell iterated in the loop. Here I demonstrated only for column set B,C
but we have to do the same for the rest of the columns.
The If statement here checks for null. You might have different needs, so changing the if statement changes the filtering. Here I check if the cells are not null. If the cells of column C contain data, put those data in values array and the respective B data on titles array but where? Position starts as 1 and we then iterate it +1 each time it adds something.
You can set data from an array using this command:
' current_row is set to the first row of the spreadsheet we wanna fill.
Sheets("sheetname").Select ' because stupid things can happen...
newrangeV = "C" & current_row
Sheets("sheetname").Range(newrangeV) = values(j)
The rest is just putting things together.
In any case, I wanna thank both of the people involved in this question, because I might didn't got the solution, but I got an idea of how to do other stuff, like accidentally learning something new. Cheers.
So, I'm a bit baffled on how to move forward, and would like some collaboration to get me started. I'm not asking for someone to code this, but to verify my theoretical path forward.
Background:
I have a single worksheet with 30 activities. Each activity is 75 rows, with the first of the 75 rows having a cell with the description of the activity. Assuming the # of columns is irrelevent to this, the activities A, B, and C, would appear such as:
A1
A...
A75
B1
B...
B75
C1
C...
C75
Theoretical path forward:
Since I have a known row which starts each activity, I was thinking that I could:
.1) Copy the known cell from each row that I intend to sort by to another sheet (this isn't preferred, but is how I can think to do it).
.2) Once in the other sheet, Sort the activity descriptions.
.3) Once sorted, I want to copy each of the activities 75 rows, in order, to the sorted sheet, via Match or Find.
.4) Once completed, I would Copy the Activities from the new sheet, paste back into the original sheet, then delete the new sheet.
Question:
Does this sound appropriate? Is there possibly a better way to do this that immediately comes to mind?
I think you could save a lot of time copying and pasting if you used a 2 dimensional array. This program stops at each 75th row and loads up the 2d array "Activities" from column A. The array gets passed to bubblesort where it is sorted on the first value. Then it is returned and all output to column B. You might have to adjust the constants to match and if there is a blank row between activities there will be some other minor adjustments to the two main loops.
Option Compare Text
Const RowsPerActivity As Integer = 75 'setting
Const NumActivities As Integer = 30 'setting
Const RowtoSortOn As Integer = 1 'setting
Sub SortGroups()
Dim MySheet As Worksheet
Set MySheet = Worksheets("sheet1")
Dim Activities(1 To NumActivities, 1 To RowsPerActivity) As Variant
Dim CurActivity, CurDataRow As Integer
Dim LastRow As Integer
LastRow = MySheet.Cells(MySheet.Rows.Count, "A").End(xlUp).Row
For CurActivity = 1 To LastRow Step RowsPerActivity 'maybe +1 if there is a blank row between activities
For CurDataRow = 1 To RowsPerActivity Step 1
Activities((CurActivity \ 75) + 1, CurDataRow) = MySheet.Cells(CurActivity + CurDataRow - 1, 1).Value
Next CurDataRow
Next CurActivity
Call BubbleSort(Activities)
For CurActivity = 1 To LastRow Step RowsPerActivity 'maybe +1 if there is a blank row between activities
For CurDataRow = 1 To RowsPerActivity Step 1
MySheet.Cells(CurActivity + CurDataRow - 1, 2).Value = Activities((CurActivity \ 75) + 1, CurDataRow)
Next CurDataRow
Next CurActivity
End Sub
Sub BubbleSort(ByRef list() As Variant)
' Sorts an array using bubble sort algorithm
Dim First As Integer, Last As Long
Dim i As Long, j As Long, k As Long
Dim Temp As Variant
First = LBound(list, 1)
Last = UBound(list, 1)
For i = First To Last - 1
For j = i + 1 To Last
If list(i, RowtoSortOn) > list(j, RowtoSortOn) Then
For k = 1 To RowsPerActivity
Temp = list(j, k)
list(j, k) = list(i, k)
list(i, k) = Temp
Next k
End If
Next j
Next i
End Sub
I am trying to make an excel code that will compare the used range of Rows 1 and 2 of the same worksheet and delete any similar cells and move the remaining (unique values) cells to Row 1 beginning at A1.
eg) If row 1 contains these values (commas inidicate diff cells): a, b, c
and row 2 contains: a, b, c, d, e
I want the code to compare the two rows and end up with row 1 being: d, e (in columns A and B), after the code is complete. Any help would be appreciated.
Im new to VBA so im having trouble on some syntax that I would appreciate if some pros could help me out.
Get the used number of columns for rows 1 and 2 as integers. eg) maxCol1 = 3, maxCol2 = 5
Create a for loop that goes from i = 1 To maxCol2 and compares row 1 to row 2. if they are equal, make them both "", if there is something in row 2 but not in row 1, set that value to cell A1.
basically just need help on setting step 1 up.
With the help of the link posted in the comment, I figured it out! Thanks to those who helped. The code compares row 2 from row 1 and deletes any similar cell values and posts the unique values into row 1 and also into a new worksheet.
Sub CompareAndDelete()
'This code will compare the rows of each sheet and delete any old alerts that have already been emailed out
' it will then call SaveFile IF new alerts have been found
Dim row1() As Variant, row2() As Variant, newRow As Variant
Dim coll As Collection
Dim i As Long
Dim maxCol1 As Integer
Dim maxCol2 As Integer
'Find max number of columns for old and new alert
With ActiveSheet
maxCol1 = .Cells(1, .Columns.Count).End(xlToLeft).Column
maxCol2 = .Cells(2, .Columns.Count).End(xlToLeft).Column
End With
'Redimensionalize arrays
ReDim row1(0 To (maxCol1 - 1))
ReDim row2(0 To (maxCol2 - 1))
'Assign row1/row2 string values into arrays
For r = 0 To (maxCol1 - 1)
row1(r) = Cells(1, r + 1).Value
Next
For s = 0 To (maxCol2 - 1)
row2(s) = Cells(2, s + 1).Value
Next
ReDim newRow(LBound(row1) To Abs(UBound(row2) - UBound(row1)) - 1)
'Create a collection to load all row1/row2 values into
Set coll = New Collection
'Empty Collection for each run through
Set coll = Nothing
'Set collection to New before using
Set coll = New Collection
For i = LBound(row1) To (UBound(row1))
coll.Add row1(i), row1(i)
Next i
For i = LBound(row2) To (UBound(row2))
On Error Resume Next
coll.Add row2(i), row2(i)
If Err.Number <> 0 Then
coll.Remove row2(i)
End If
On Error GoTo 0
Next i
'Copy Row 2 and Paste it to Row 1
ActiveWorkbook.ActiveSheet.Rows(2).Copy
Range("A1").Select
ActiveSheet.Paste
'Now values are stored in collection, delete row 2
'Rows(2).EntireRow.ClearContents
'Paste only the new alerts onto a new worksheet that is designated for new alerts
For i = LBound(newRow) To UBound(newRow)
newRow(i) = coll(i + 1) 'Collections are 1-based
'Debug.Print newRow(i)
ActiveWorkbook.Sheets("Sheet" & index + 4).Select
ActiveWorkbook.Sheets("Sheet" & index + 4).Cells(1, i + 1).Value = newRow(i)
Next i
'if NEW alerts have been found, call SaveFile
If IsEmpty(ActiveWorkbook.Sheets("Sheet" & index + 4).Cells(1, 1)) = False Then
Call SaveFile
End If
End Sub
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
I wrote the below macro in Excel (2010) VBA to add markers to contracts with various issues to a master tracker. While doing some size testing I am getting error 400 when I attempt to run with an input of 50,000 contracts (array Contracts), but it runs fine with 40,000 (took about 14 minutes). Any ideas at why I am getting the error? Commented in the code where it is stopping at 50,000. Thank you!
Sub UploadNew()
''''''''''''''''''''''''Add All Contracts to End of Master'''''''''''''''''''''''''''''''
'Set up the array Contracts which will house the new contracts to be uploaded
Dim Contracts() As String
Dim size As Long
Dim R As Integer
Dim N As Long
'This sets up the value for N as the end of the current master list
N = Worksheets("Master").Cells(Rows.Count, "A").End(xlUp).Row + 1
'Determine size of array and store it into variable size
size = Worksheets("Update").Cells(Rows.Count, "A").End(xlUp).Row - 1
'Identifies which Remediation column to add the marker to
R = Application.WorksheetFunction.VLookup(Worksheets("Update").Range("F2"), Range("E14:G263"), 3, False)
'Having counted size we can redimension the array
ReDim Contracts(size)
'Insert the values in column A into the array
Dim i As Long
For i = 1 To size
Contracts(i) = Range("A1").Offset(i)
Next i
'Takes each value in the array and adds it to the end of the master list using N
For i = 1 To size
Worksheets("Master").Range("A" & N).Value = Contracts(i)
N = N + 1
Next i
'Remove the duplicates from the master tab based on the first column
Worksheets("Master").Range("A:ZZ").RemoveDuplicates Columns:=Array(1)
'Remove blank rows from Master
Dim rng As Range
Set rng = Worksheets("Master").Range("A2:A" & N).SpecialCells(xlCellTypeBlanks)
rng.EntireRow.Delete
''''''''''''''''''''''''Add All Contracts to End of Master'''''''''''''''''''''''''''''''
'''''''''''''''''''''Place New Contract Marker for Each Contract'''''''''''''''''''''''''
'This searches all the contracts in the master and places a 1 R columns to the right of
'the found contract
For i = 1 To size
Dim rgFound As Range
Set rgFound = Worksheets("Master").Range("A2:A" & N).Find(Contracts(i))
'! Code is stopping about here with 50,000 contracts, doesn't add a single marker !'
With rgFound.Offset(, R)
.Value = "1"
.NumberFormat = "General"
End With
Next i
'''''''''''''''''''''Place New Contract Marker for Each Contract'''''''''''''''''''''''''
End Sub
This rewrite bulk loads and bulk unloads the array. I've swapped out a worksheet MATCH function for the Range.Find method since there should be guaranteed matches.
Sub UploadNew()
''''''''''''''''''''''''Add All Contracts to End of Master'''''''''''''''''''''''''''''''
'Set up the array Contracts which will house the new contracts to be uploaded
Dim Contracts As Variant
Dim i As Long, N As Long, R As Integer
With Worksheets("Update")
'Identifies which Remediation column to add the marker to
'I have no idea why you are looking up F2 in column E (and returning value from column G) on the Updates worksheet
R = Application.WorksheetFunction.VLookup(.Range("F2"), .Range("E14:G263"), 3, False)
'AT THIS POINT R SHOULD BE AN INTEGER BETWEEN 2 and 16384
'NOT LARGER OR SMALLER OR TEXT
'CHECK WITH A WATCH WINDOW!!!!!!!!!!!
'Insert the values in column A into the array (SKIP HEADER ROW)
Contracts = .Range(.Cells(2, "A"), .Cells(Rows.Count, "A").End(xlUp)).Value2
End With
With Worksheets("Master")
'This sets up the value for N as the end of the current master list
N = .Cells(Rows.Count, "A").End(xlUp).Row + 1
'Takes each value in the array and adds it to the end of the master list using N
.Range("A" & N).Resize(UBound(Contracts, 1), UBound(Contracts, 2)) = Contracts
'Remove the duplicates from the master tab based on the first column
.Range("A:ZZ").RemoveDuplicates Columns:=Array(1)
'Remove blank rows from Master
If CBool(Application.CountBlank(.Range("A2:A" & N))) Then _
.Range("A2:A" & N).SpecialCells(xlCellTypeBlanks).EntireRow.Delete
''''''''''''''''''''''''Add All Contracts to End of Master'''''''''''''''''''''''''''''''
'''''''''''''''''''''Place New Contract Marker for Each Contract'''''''''''''''''''''''''
'This searches all the contracts in the master and places a 1 R columns to the right of
'the found contract
For i = LBound(Contracts, 1) To UBound(Contracts, 1)
With .Cells(Application.Match(Contracts(i, 1), .Columns(1), 0), R)
.Value = "1"
.NumberFormat = "General"
End With
Next i
End With
'''''''''''''''''''''Place New Contract Marker for Each Contract'''''''''''''''''''''''''
End Sub
btw, regarding Dim rgFound As Range ; do not declare a variable in a loop. Declare it outside the loop and assign it new values inside the loop.