Paste from list not found in current range to bottom of current range - excel

I have column A that has all existing categories, new categories are listed in column C. I'm trying to determine how to take these new categories, and add them to column "a" if they aren't already in column A. In the example the new categories in column C are added to column A even if there are already in column A. I would also need range("a1") in the if-then line to be a dynamic range since new categories will be added as the code runs. Some constructive criticism would be greatly appreciated as well to help me in the future.
Sub newcategory()
Dim newcatcount As Integer
Dim i As Integer
newcat = Range("c100000").End(xlUp).Row
For i = 1 To newcat
If Cells(i, 3).Value <> Range("a1") Then
Cells(i, 3).Select
Selection.copy
Range("a100000").End(xlUp).Offset(1, 0).Select
ActiveSheet.Paste
End If
Next
End Sub

Please give this a try...
Sub AddNewCategories()
Dim lrA As Long, lrC As Long, i As Long, j As Long
Dim x, y, z(), dict
lrA = Cells(Rows.Count, 1).End(xlUp).Row
lrC = Cells(Rows.Count, 3).End(xlUp).Row
'Array to hold the categories in column A starting from Row1, assuming the categories start from A1. If not, change it accordingly.
x = Range("A1:A" & lrA).Value
'Array to hold the new categories in column C starting from Row1, assuming the categories start from C1. If not, change it accordingly.
y = Range("C1:C" & lrC).Value
Set dict = CreateObject("Scripting.Dictionary")
For i = 1 To UBound(x, 1)
dict.Item(x(i, 1)) = ""
Next i
For i = 1 To UBound(y, 1)
If Not dict.exists(y(i, 1)) Then
dict.Item(y(i, 1)) = ""
j = j + 1
ReDim Preserve z(1 To j)
z(j) = y(i, 1)
End If
Next i
If j > 0 Then
Range("A" & lrA + 1).Resize(j).Value = Application.Transpose(z)
End If
Set dict = Nothing
End Sub

you could use excel built in RemoveDuplicates() function, as follows (mind the comments):
Option Explicit
Sub newcategory()
Dim newcat As Range
With Worksheets("Categories") ' change "Categories" to your actual sheeet name
Set newcat = .Range("C1", .Cells(.Rows.Count, 3).End(xlUp)) ' get the range of all nwe categories in reference sheet column C from row 1 down to last not empty one
.Cells(.Rows.Count, 1).End(xlUp).Resize(newcat.Rows.Count).Value = newcat.Value ' append new categories values below existing categories in column A
.Range("A1", .Cells(.Rows.Count, 1).End(xlUp)).RemoveDuplicates Columns:=Array(1), Header:=xlNo ' remove duplicates
End With
End Sub

Related

Need help to optimize the Excel VBA code that aggregates duplicates

Below is my source table
Name Sales
---------------------------------
Thomas 100
Jay 200
Thomas 100
Mathew 50
Output I need is as below
Name Sales
---------------------------------
Thomas 200
Jay 200
Mathew 50
Basically, I have 2 columns that can have duplicates and I need to aggregate the second column based on first column.
Current code I have is as below. Its working perfectly fine. It takes around 45 seconds to run for 4500 records. I was wondering if there is a more efficient way to do this... as it seems to be a trivial requirement.
'Combine duplicate rows and sum values
Dim Rng As Range
Dim LngRow As Long, i As Long
LngLastRow = lRow 'The last row is calculated somewhere above...
'Initializing the first row
i = 1
'Looping until blank cell is encountered in first column
While Not Cells(i, 1).Value = ""
'Initializing range object
Set Rng = Cells(i, 1)
'Looping from last row to specified first row
For LngRow = LngLastRow To (i + 1) Step -1
'Checking whether value in the cell is equal to specified cell
If Cells(LngRow, 1).Value = Rng.Value Then
Rng.Offset(0, 1).Value = Rng.Offset(0, 1).Value + Cells(LngRow, 2).Value
Rows(LngRow).Delete
End If
Next LngRow
i = i + 1
Wend
Note that this is part of a larger excel app and hence I definitely need the solution to be in Excel VBA.
Here you go:
Option Explicit
Sub Consolidate()
Dim arrData As Variant
Dim i As Long
Dim Sales As New Scripting.Dictionary 'You will need the library Microsoft Scripting Runtime
Application.ScreenUpdating = False 'speed up the code since excel won't show you what is happening
'First of all, working on arrays always speeds up a lot the code because you are working on memory
'instead of working with the sheets
With ThisWorkbook.Sheets("YourSheet") 'change this
i = .Cells(.Rows.Count, 1).End(xlUp).Row 'last row on column A
arrData = .Range("A2", .Cells(i, 2)).Value 'here im assuming your row 1 has headers and we are storing the data into an array
End With
'Then we create a dictionary with the data
For i = 1 To UBound(arrData) 'from row 2 to the last on Q1 (the highest)
If Not Sales.Exists(arrData(i, 1)) Then
Sales.Add arrData(i, 1), arrData(i, 2) 'We add the worker(Key) with his sales(Item)
Else
Sales(arrData(i, 1)) = Sales(arrData(i, 1)) + arrData(i, 2) 'if the worker already exists, sum his sales
End If
Next i
'Now you have all the workers just once
'If you want to delete column A and B and just leave the consolidate data:
With ThisWorkbook.Sheets("YourSheet") 'change this
i = .Cells(.Rows.Count, 1).End(xlUp).Row 'last row on column A
.Range("A2:B" & i).ClearContents
.Cells(2, 1).Resize(Sales.Count) = Application.Transpose(Sales.Keys) 'workers
.Cells(2, 2).Resize(Sales.Count) = Application.Transpose(Sales.Items) 'Their sales
End With
Application.ScreenUpdating = True 'return excel to normal
End Sub
To learn everything about dictionaries (and more) check this
With data in cols A and B like:
Running this short macro:
Sub KopyII()
Dim cell As Range, N As Long
Columns("A:A").Copy Range("C1")
ActiveSheet.Range("C:C").RemoveDuplicates Columns:=1, Header:=xlNo
N = Cells(Rows.Count, "C").End(xlUp).Row
Range("B1").Copy Range("D1")
Range("D2:D" & N).Formula = "=SUMPRODUCT(--(A:A= C2),(B:B))"
End Sub
will produce this in cols C and D:
NOTE:
This relies on Excel's builtin RemoveDuplicates feature.
EDIT#1:
As chris neilsen points out, this function should be a bit quicker to evaluate:
Sub KopyIII()
Dim cell As Range, N As Long, A As Range, C As Range
Set A = Range("A:A")
Set C = Range("C:C")
A.Copy C
C.RemoveDuplicates Columns:=1, Header:=xlNo
N = Cells(Rows.Count, "C").End(xlUp).Row
Range("B1").Copy Range("D1") ' the header
Range("D2:D" & N).Formula = "=SUMIFS(B:B,A:A,C2)"
End Sub

Countif on sheet with 700k rows freezes program

I currently have two lists. A list of "Grantors" in column A and the same list with duplicates removed in column B. I am trying to get a count of how many times a given Grantor is in Column A using countif however my list in Column A is over 700k rows. I am using 64bit excel but every time I run code to do this excel freezes and crashes.
Is there a way to do this in excel or do I need to take another approach like using a pivot table or creating tables in access?
I have written a few sub routines but this is the latest, got from another post on this forum.
Sub Countif()
Dim lastrow As Long
Dim rRange As Range
Dim B As Long '< dummy variable to represent column B
B = 2
With Application
.ScreenUpdating = False 'speed up processing by turning off screen updating
.DisplayAlerts = False
End With
'set up a range to have formulas applied
With Sheets(2)
lastrow = Cells(Rows.Count, "A").End(xlUp).Row
Set rRange = .Range(.Cells(2, B), .Cells(lastrow, B))
End With
'apply the formula to the range
rRange.Formula = "=COUNTIF($A$2:$A$777363,C2)"
'write back just the value to the range
rRange.Value = rRange.Value
With Application
.ScreenUpdating = True
.DisplayAlerts = True
End With
End Sub
Something like this:
Sub Countif()
Dim allVals, uniqueVals, i As Long, dict, v, dOut(), r As Long
''creating dummy data
' With Sheet2.Range("A2:A700000")
' .Formula = "=""VAL_"" & round(RAND()*340000,0)"
' .Value = .Value
' End With
'
'get the raw data and unique values
With Sheet2
allVals = .Range("A2:A" & .Cells(.Rows.Count, "A").End(xlUp).Row).Value
uniqueVals = .Range("B2:B" & .Cells(.Rows.Count, "B").End(xlUp).Row).Value
End With
ReDim dOut(1 To UBound(uniqueVals, 1), 1 To 1) 'for counts...
Set dict = CreateObject("scripting.dictionary")
'map unique value to index
For i = 1 To UBound(uniqueVals, 1)
v = uniqueVals(i, 1)
If Len(v) > 0 Then dict(v) = i
Next i
'loop over the main list and count each unique value in colB
For i = 1 To UBound(allVals, 1)
v = allVals(i, 1)
If Len(v) > 0 Then
If dict.exists(v) Then
r = dict(v)
dOut(r, 1) = dOut(r, 1) + 1
End If
End If
Next i
'output the counts
Sheet2.Range("C2").Resize(UBound(dOut, 1), 1).Value = dOut
End Sub
Runs in ~30sec with 700k values in A and 300k uniques in B
... or maybe this.
Caution: this overwrites the de-duplicated values in column A of the target worksheet.
Option Explicit
Sub countUnique()
Dim arr As Variant, i As Long, dict As Object
Debug.Print Timer
Set dict = CreateObject("scripting.dictionary")
dict.comparemode = vbTextCompare
With Worksheets("sheet2")
arr = .Range(.Cells(2, "A"), .Cells(.Rows.Count, "A").End(xlUp)).Value2
End With
For i = LBound(arr, 1) To UBound(arr, 1)
dict.Item(arr(i, 1)) = dict.Item(arr(i, 1)) + 1
Next i
With Worksheets("sheet3")
.Cells(2, "A").Resize(dict.Count, 1) = bigTranspose(dict.keys)
.Cells(2, "B").Resize(dict.Count, 1) = bigTranspose(dict.items)
End With
Debug.Print Timer
End Sub
Function bigTranspose(arr1 As Variant)
Dim t As Long
ReDim arr2(LBound(arr1) To UBound(arr1), 1 To 1)
For t = LBound(arr1) To UBound(arr1)
arr2(t, 1) = arr1(t)
Next t
bigTranspose = arr2
End Function
42.64 seconds for 700K originals and 327K uniques on a Surface Pro tablet. This might be improved by turning off calculation and enableevents. Screenupdating really shouldn't be an issue.

compare column with unique identifier in vba excel

I need to compare the data with unique identifier which is the concatenation of (Column A, "~" Column B) and store it in Column F. Find all the duplicate values in ColumnF, which will used as a basis for comparing to the other Columns (Column C, Column D and Column E). For example,
In my example, I have a duplicate value of 5*2018~OPS$CABUCKLE, in this case I will compare each column using the identifier. In my 1st entry, Column C have the same value in 2nd entry which is 222, but in Column D the value of 1st entry is N and it was changed to Y in 2nd entry. Same case in Column E. I need to highlight the changes happened between the entries.
I only did the concatenation in VBA, but I don't know how will I find the duplicate value and compare the other column?
Sub split1()
Dim ws As Worksheet, lRow As Long
Dim x As Long
Set ws = ThisWorkbook.ActiveSheet
lRow = ws.Cells(Rows.Count, 1).End(xlUp).Row
With ws
For x = 1 To lRow
For Each wrd In .Cells(x, 1)
d = wrd
For Each nm In .Cells(x, 2)
.Cells(x, 6).Value = d & "*" & nm
Next nm
Next
Next x
End With
End Sub
This could achieve what you're looking for, let me know if it misses anything
Just don't forget to go to Tools > References and check 'Microsoft Scripting Runtime'
Sub highlight()
' need to include Microsoft Scripting Runtime in Tools > References
Dim prevIDs As Scripting.Dictionary: Set prevIDs = New Scripting.Dictionary
Dim ws As Worksheet: Set ws = ThisWorkbook.ActiveSheet
Dim lastRow As Long
Dim oldRow As Long
Dim row As Long
Dim id As String
With ws
lastRow = .Cells(.Rows.Count, 1).End(xlUp).row
For row = 2 To lastRow
' set lookup value
.Cells(row, "F").Value = Trim(CStr(.Cells(row, "A").Value)) & "~" & Trim(CStr(.Cells(row, "B").Value))
id = .Cells(row, "F").Value
If prevIDs.Exists(id) Then
' get previously found row
oldRow = prevIDs(id)
If .Cells(row, "C").Value = .Cells(oldRow, "C").Value Then
' only checks if col D doesn't match -- can change to check both
If .Cells(row, "D").Value <> .Cells(oldRow, "D").Value Then
.Range("D" & row & ":E" & row).Interior.Color = RGB(100, 200, 100)
.Range("D" & oldRow & ":E" & oldRow).Interior.Color = RGB(100, 200, 100)
End If
End If
' reset last found row
prevIDs(id) = row
Else
prevIDs.Add id, row
End If
Next
End With
End Sub
Here's my test:

Creat VBA Dictionary Entry When Criteria Is Met

I am new to VBA Dictionaries. What I am trying to do is evaluate each row of a sheet. If the value in Column J is "100", then I want to create a key/item entry into the dictionary. If any other value is in Column J, I do not want the entry to be created and for the macro to look at the next row of data.
I currently have the code below:
Dim x, x2, y, y2()
Dim i As Long
Dim dict As Object
Dim LastRowTwo As Long, shtOrders As Worksheet, shtReport As Worksheet
Set shtOrders = Worksheets("Orders")
Set shtReport = Worksheets("Mapping")
Set dict = CreateObject("Scripting.Dictionary")
With shtReport
LastRow = .Range("G" & Rows.Count).End(xlUp).Row
x = .Range("G2:G" & LastRow).Value
x2 = .Range("I2:I" & LastRow).Value
Set SelectionRNG = Worksheets("Mapping").Range("G2:J" & LastRow)
For Each rngrow In SelectionRNG.Rows
If rngrow.Cells(1, 4) = "100" Then
dict.Item(x(i, 1)) = x2(i, 1)
End If
Next
End With
I know that this line:
dict.Item(x(i, 1)) = x2(i, 1)
Is where my problem is. I have used that syntax in the code below and it works fine:
For i = 1 To UBound(x, 1)
dict.Item(x(i, 1)) = x2(i, 1)
Next i
I am stuck on how to change the syntax of either my "rngrow code" to add a dictionary entry without using the "i" or to adjust the code directly above to include an IF statement to check the value in column J (and only create an entry if J = 100 for the row currently being assessed.
If the code above was not clear, the key would be in Column G and the item would be in column I. (I later lookup Column G and retrieve column I's value.)
As I said, I am new to this, so I appreciate any help!
Cheers!
I would get rid of rngrow and just loop through rows 2 to LastRow:
Dim i As Long
Dim dict As Object
Dim LastRow As Long, shtReport As Worksheet
Set shtReport = Worksheets("Mapping")
Set dict = CreateObject("Scripting.Dictionary")
With shtReport
LastRow = .Range("G" & .Rows.Count).End(xlUp).Row
For i = 2 To LastRow
If .Cells(i, "J") = "100" Then
dict.Item(.Cells(i, "G").Value) = .Cells(i, "I").Value
End If
Next
End With

Remove duplicates from column A based on existing values in column B using VBA

I need to input data in column A and column B and get the data that's in column A but not in column B written to column C.
Examples of what I need:
A slightly different and faster approach without looping through cells on the sheet would be this...
Private Sub CommandButton1_Click()
Dim x, y(), dict
Dim i As Long, j As Long
x = Range("A1").CurrentRegion
Set dict = CreateObject("Scripting.Dictionary")
Columns("C").ClearContents
For i = 1 To UBound(x, 1)
dict.Item(x(i, 2)) = ""
Next i
j = 1
For i = 1 To UBound(x, 1)
If Not dict.exists(x(i, 1)) Then
ReDim Preserve y(1 To j)
y(j) = x(i, 1)
j = j + 1
End If
Next i
Range("C1").Resize(UBound(y), 1) = Application.Transpose(y)
End Sub
Place this in the code file behind your sheet and change CommandButton1 to the name of your button.
Option Explicit
Private Sub CommandButton1_Click()
Dim r As Range, matched_ As Variant, counter_ As Long
'Loop in each cell in Column A
For Each r In Range("A1:A" & Cells(Rows.Count, 1).End(xlUp).Row)
If Not IsEmpty(r) Then
'Loop for a matching value in Column B
matched_ = Application.Match(r.Value, Columns(2), 0)
'If match not found, write the value in Column C
If IsError(matched_) Then
counter_ = counter_ + 1
Range("C" & counter_) = r.Value
End If
End If
Next r
End Sub

Resources