Excel VBA - Compare worksheet rows based off of value - vba

Similar issue to these:
Find the differences between 2 Excel worksheets?
Compare two excel sheets
My issue specifically, I have a monthly employee listing with a unique ID and around 30 columns of other data for around 900 employees.
I'm trying to accomplish two things:
Compare if employees were added or dropped between the lists.
Between the sheets for each employee compare what other data for that employee changed. i.e. Job Title changed.
Most compare add-ins/modules I find only compare the specific ranges in order, thus once once difference if found every subsequent row will be different.
First, I'm wondering if there are any existing tools that can do this. If not I was thinking of building my own. I was thinking of doing this by looping through each employee and using vlookup to verify matches. I'm concerned doing this many loops will make the macro difficult to use. Any guidance on how I should go about this? Thanks.

Untested, but will give you a place to start from...
This does not find ex-employees which are on the "old" sheet but not on the "current" sheet.
Sub CompareEmployeeInfo()
Const ID_COL As Integer = 1 ' ID is in the first column
Const NUM_COLS As Integer = 30 'how many columns are being compared?
Dim shtNew As Excel.Worksheet, shtOld As Excel.Worksheet
Dim rwNew As Range, rwOld As Range, f As Range
Dim x As Integer, Id
Dim valOld, valNew
Set shtNew = ActiveWorkbook.Sheets("Employees")
Set shtOld = ActiveWorkbook.Sheets("Employees")
Set rwNew = shtNew.Rows(2) 'first employee on "current" sheet
Do While rwNew.Cells(ID_COL).Value <> ""
Id = rwNew.Cells(ID_COL).Value
Set f = shtOld.UsedRange.Columns(ID_COL).Find(Id, , xlValues, xlWhole)
If Not f Is Nothing Then
Set rwOld = f.EntireRow
For x = 1 To NUM_COLS
If rwNew.Cells(x).Value <> rwOld.Cells(x).Value Then
rwNew.Cells.Interior.Color = vbYellow
Else
rwNew.Cells.Interior.ColorIndex = xlNone
End If
Next x
Else
rwNew.Cells(ID_COL).Interior.Color = vbGreen 'new employee
End If
Set rwNew = rwNew.Offset(1, 0) 'next row to compare
Loop
End Sub

Don't know if there is anything that does that for you or not. But, you can use the Dictionary Object to make this comparison task much easier. You can also take examples from this answer that uses Dictionaries which checked for uniques and is optimized for speed, change it to what you need. Then you can use this fast method to color the cells or whatever you want to do with it.
I know I'm not providing code for you but these pointers will get you started, and if you have more questions I can help you out.

Related

Excel VBA Function Lookover - Cooccurrences of two values

I've been working on a user-defined function in VBA to find a certain count. For background, "raw" is a worksheet that refers a sheet that has genres in column B and an artist ID in column C. The sheet that I'm working in has combinations of two genres: first in column A, second in column B.
Anyway, the function that I'm trying to build should do the following:
Take two inputs as strings. Check the genre column in "raw" for matches with the first input. Then, take that ID and find a cell that matches the ID and the second input. If there is one found, add one to a running count. Whether or not it is found, move onto the next match. The function will return an integer that indicates the number of times the two genres had the same artist ID.
Now, my function is returning #VALUE.. no syntax errors, no compiling errors. Just the error in returning the value. I've looked it over, googled like crazy, and I just can't figure it out. I'm new to VBA, so maybe I'm just missing something really obvious or I've defined something wrong. Either way, I just need another set of eyes to look over it. Any suggestions for improvement are much appreciated, so thank in advance for your time and help!!
Here is the code. I know it isn't the prettiest, but it's short and the logic should make sense.
Public Function cocount(c1 As String, c2 As String) As Integer
Dim rng As Range
Dim rng2 As Range
Dim cell As Range
Dim cell1 As Range
Dim ID As Integer
Dim Count As Integer
rng = Worksheets("Raw").Range("B2:B183579")
rng2 = Worksheets("Raw").Range("C2:C183579")
Count = 0
For Each cell In rng
If cell.Value = c1 Then
ID = cell.Offset(0, 1).Value
For Each cell1 In rng2
If cell1.Value = ID And cell1.Offset(0, -1).Value = c2 Then
Count = Count + 1
End If
Next cell1
End If
Next cell
cocount = Count
End Function
EDIT: Thanks for viewing my question and being willing to help (And thanks Rdster for attempting a solution). I uploaded pictures of the raw data and the combination, although it won't let me embed the images. Raw Data Combination List
Anyway, I'll try and explain my problem again. In the combination list of genres, each row contains two genres. I want to find how many times those two genres share the same artist ID in the Raw Data sheet. There are 181,000+ combinations, and 183,000+ rows in the Raw Data. Thus, the function needs to be efficient--something that I'm not incredibly great at doing even in other languages.
This can be achieved using built-in several different Excel Worksheet functions.
Excel Formula using COUNTIFS
Define 2 dynamic named ranges that will resize themselves to fit the data. Gendre_2 is defined relative to Gendre_1 this ensures that the ranges are the same size.
Gendre_1 = OFFSET(Raw!$A$1,1,0,COUNTA(Raw!$A:$A)-1,1)
Gendre_2 = OFFSET(Raw!$A$1,1,1,COUNTA(Raw!$A:$A)-1,1)
Formula
=COUNTIFS(Gendre_1,A2,Gendre_2,B2)
Reference: ExcelJet - Excel COUNTIFS Function
COUNTIFS counts the number of cells in a range that match supplied criteria. Unlike the COUNTIF function, COUNTIFS can apply more than one set of criteria, with more than one range. Ranges and criteria are applied in pairs, and only the first pair is required. For each additional criteria, you must supply another range/criteria pairs. Up to 127 range/criteria pairs are allowed.
VBA
Public Function cocount(c1 As String, c2 As String) As Double
Dim rng As Range, rng2 As Range
With Worksheets("Raw")
Set rng = .Range("A2", .Range("A" & .Rows.Count).End(xlUp))
Set rng2 = rng.Offset(0, 1)
cocount = WorksheetFunction.CountIfs(rng, c1, rng2, c2)
End With
End Function
If I understand correctly, you are trying to count the number of times that c1 and c2 = B# and C# where # is the same row.
Public Function cocount(c1 As String, c2 As String) As Integer
Dim Count As Integer, iRow as Integer
Count = 0
For iRow = 2 to Sheets("Raw").Cells(Rows.Count, "B").End(xlUp).Row
If Cells(iRow, "B") = c1 And Cells(iRow,"C") = c2 Then
Count = Count + 1
End If
Next iRow
cocount = Count
End Function
Check the genre column in "raw" for
matches with the first input. Then, take that ID and find a cell that
matches the ID and the second input.
try this:
Public Function cocount(FirstKey$, SecondKey$, FirstRng As Range, SecondRng As Range) As Long
Dim FirstAccurance As Range, ID$
Set FirstAccurance = FirstRng.Find(FirstKey, , xlValues, xlWhole, xlByRows, xlNext, 0)
ID = Cells(FirstAccurance.Row, SecondRng.Column).Value2
cocount = WorksheetFunction.CountIfs(SecondRng, ID, FirstRng, SecondKey)
End Function
test:

Count Number of Rows based on Parameter. (Excel VBA)

I need to count the number of rows depending on the week and type of the data. I have the excel formula but I want to make it as a VB code yet I don't have that much idea and it is not working.
=IF(AND($N$4="All",$N$5="All"),SUM(('SD'!$I$2:$I$99538='Source'!$B6)*('SD'!$A$2:$A$99538='Source'!C$5)),IF(AND($N$4="All",$N$5<>"All"),SUM(('SD'!$I$2:$I$99538='Source'!$B6)*('SD'!$A$2:$A$99538='Source'!C$5)*('SD'!$B$2:$B$99538='Source'!$N$5)),IF(AND($N$4<>"All",$N$5="All"),SUM(('SD'!$I$2:$I$99538='Source'!$B6)*('SD'!$A$2:$A$99538='Source'!C$5)*('SD'!$K$2:$K$99538='Source'!$N$4)),IF(AND($N$4<>"All",$N$5<>"All"),SUM(('SD'!$I$2:$I$99538='Source'!$B6)*('SD'!$A$2:$A$99538='Source'!C$5)*('SD Raised'!$B$2:$B$99538='Source'!$N$5)*('SD'!$K$2:$K$1048576='Source'!$N$4))))))
I have a sheet where in all datas are captured (SD) and the second one will be the sheet(Source) where i need to count the number of rows available based on the parameter as follow; The week where data belongs and the category of the data.
Edit:
This formula* does not count the data i needed to count. And if possible I want to make it as a VBA code.
This is where the counted data should go. "Weeks are changing depending on the dropdown iput (Max of 4 weeks below from the selected week)"
This image shows the data where i need to capture and count the number of category based on the weeks and category. (Sample only)
I guess, if it's the right point you're hitting DoktorOSwaldo, better use the Range().Rows.Count property rather than scrolling through allRows.
Hope this helps.
Hadi
so i have to guess a bit what you want, but if you want to Count specific rows in Excel vba you can use something like this:
Dim allRows As Variant
Dim i As Long
Dim count as Long
count = 0
allRows = Tabelle5.Range("A" & start_row, end_column & last_row)
For i = 1 To UBound(allRows)
If allRows(i, 1) = *category* and allRows(i,2) = *week* Then
count = count + 1
End If
Next
To find right range, there are multiple possible solution. I use this, maybe it is not the best, but works fine for me:
Private Function last_row() As Integer
Dim rangeObj As Range
Set rangeObj = Tabelle5.Cells.Find("*", SearchOrder:=xlByRows, SearchDirection:=xlPrevious)
If rangeObj Is Nothing Then
last_row = start_row
Else
last_row = rangeObj.row
End If
End Function
Public Function start_row() As Integer
start_row = 2
End Function

Insert 10000+ rows in an Excel sheet

I am creating an Excel macro while pulls data from an Excel sheet (containing more than 10 thousand rows) and populates another Excel sheet.
I have to insert data row-wise since for every row I need to fetch data from 2-3 different sheets based on a column value (say, EMP_ID).
e.g. Excel file - 1 has
Emp_ID | Emp_Name | Age
Now based on each employee ID I need to fetch employee related data from 3 other Excel sheets. So I have to loop through 10k records.
When I execute the code the Excel application just hangs. I think this is because I try to insert data row-wise.
Can someone suggest a faster way to insert/update large number of rows.
I have already tried using Variant/Array to store data and then populate the sheets. But it still doesn't seem to work.
NOTE: I am reading records from the Source file into a RecordSet.
I have already added:
Application.DisplayAlerts = False,
Application.ScreenUpdating = False,
Application.Calculation = xlCalculationManual
And then set it back to default.
I know this is not a direct answer, but sometimes it's better to teach how the work has to be done, intead of simply reply.
Your work needs to be done with Access (or any other dbms). You have to define three tables, each indexed by Emp_ID, and all the staff become simple.
I tend to agree with Sergio.
If using a database is totally not an option, using an array is the way to go.
I have already tried using Variant/Array to store data and then populate the sheets. But it still doesn't seem to work.
Can you show the code you tried?
This works for me:
Dim arData() As Variant
' ... calculate number of rows and columns ...
ReDim arData(1 To numRows, 1 To numCols)
' ... populate arData ...
' Define range with identical dimensions as arData, e.g. insert in second row
Set rng = sh.Range(sh.Cells(2, 1), sh.Cells(numRows + 1, numCols))
' Transfer array to range (this is fast!)
rng.Value = arData
Also agree, Excel isn't really the tool for this.
If you're stuck with it then try the following:
Read all lookup sheets just once into collections using class objects as your data structures. For example, create a class called Employee and add the appropriate properties.
Public ID As Long
Public Age As Integer
Public Name As String
To read them, you'd code it like this...
Private mEmployeeCol As Collection
Dim ws As Worksheet
Dim empData As Employee
Dim v As Variant
Dim r As Long
Set ws = ThisWorkbook.Worksheets("employee stuff")
v = ws.UsedRange.Value2
Set mEmployeeCol = New Collection
For r = LBound(v, 1) To UBound(v, 1)
Set empData = New Employee
empData.ID = v(r, 1)
empData.Name = v(r, 2)
empData.Age = v(r, 3)
mEmployeeCol.Add empData, Key:=CStr(empData.ID)
Next
To look up the values, do it so ...
Set empData = mEmployeeCol(CStr(ID))
v(r, [your col]) = empData.ID
Then, definitely, DEFINITELY populate the final sheet with an array of variants. It's pretty straight forward ...
Dim v(1 To 10000, 1 To 50) As Variant
ws.Range("A1").Resize(UBound(v, 1), UBound(v, 2)).Value = v

Manipulating Excel spreadsheet, removing rows based on values in a column and then removing more rows based on values in another column

I have a rather complicated problem.
I have a log file that when put into excel the column "I" contains event IDs, and the column J contains a custom key that keeps a particular even grouped.
All i want to do is remove any rows that do not contain the value of say 102 in the event id column.
And THEN i need to check the custom key (column J) and remove rows that are duplicates since any duplicates will falsely show other statistics i want.
I have gotten as far as being able to retrieve the values from the columns using com objects and .entirecolumn cell value etc, but I am completely stumped as to how i can piece together a solid way to remove rows. I could not figure out how to get the row for each value.
To give a bit more clarity this is my thought process on what i need to do:
If cell value in Column I does not = 102 Then delete the row that cell contains.
Repeat for all rows in spreadsheet.
And THEN-
Read every cell in column J and remove all rows containing duplicates based on the values in column J.
Save spreadsheet.
Can any kind persons help me?
Additional Info:
Column I holds a string that is an event id number e.g = 1029
Column J holds a string that is a mix of numbers and letters = 1ASER0X3NEX0S
Ellz, I do agree with Macro Man in that your tags are misleading and, more importantly, I did indeed need to know the details of Column J.
However, I got so sick of rude posts today and yours was polite and respectful so I've pasted some code below that will do the trick ... provided Column J can be a string (the details of which you haven't given us ... see what Macro Man's getting at?).
There are many ways to test for duplicates. One is to try and add a unique key to a collection and see if it throws an error. Many wouldn't like that philosophy but it seemed to be okay for you because it also gives you a collection of all the unique (ie remaining) keys in Column J.
Sub Delete102sAndDuplicates()
Dim ws As Worksheet
Dim uniques As Collection
Dim rng As Range
Dim rowPair As Range
Dim iCell As Range
Dim jCell As Range
Dim delRows As Range
Set ws = ThisWorkbook.Worksheets("Sheet1")
Set rng = Intersect(ws.UsedRange, ws.Range("I:J"))
Set uniques = New Collection
For Each rowPair In rng.Rows
Set iCell = rowPair.Cells(, 1)
Set jCell = rowPair.Cells(, 2)
On Error Resume Next
uniques.Add jCell.Value2, jCell.Text
If Err = 457 Or iCell.Value2 = 102 Then
On Error GoTo 0
If delRows Is Nothing Then
Set delRows = rowPair.EntireRow
Else
Set delRows = Union(delRows, rowPair.EntireRow)
End If
End If
Next
If Not delRows is Nothing then
MsgBox delRows.Address(False, False) & " deleted."
delRows.Delete
End If
End Sub
There are a number of ways in which this can be done, and which is best will depend on how frequently you perform this task and whether you want to have it fully automated. Since you've tagged your question with VBA I assume you'll be happy with a VBA-based answer:
Sub removeValues()
Range("I1").Select 'Start at the top of the I column
'We are going to go down the column until we hit an empty row
Do Until IsEmpty(ActiveCell.Value) = True
If ActiveCell.Value <> 102 Then
ActiveCell.EntireRow.Delete 'Then delete the row
Else
ActiveCell.Offset(1).Select 'Select the cell below
End If
Loop
'Now we have removed all non-102 values from the column, let`s remove the duplicates from the J column
Range("A:J").RemoveDuplicates Columns:=10, Header:=xlNo
End Sub
The key line there is Range("A:J").RemoveDuplicates. It will remove rows from the range you specify according to duplicates it finds in the column you specify. In that case, it will remove items from the A-J columns based on duplicates in column 10 (which is J). If your data extends beyond the J column, then you'll need to replace "A:J" with the appropriate range. Note that the Columns value is relative to the index of the first column, so while the J column is 10 when that range starts at A (1), it would be 2 for example if the range were only I:J. Does that make sense?
(Note: Using ActiveCell is not really best practice, but it's the method that most obviously translates to what you were trying to do and as it seems you're new to VBA I thought it would be the easiest to understand).

VBA. Comparing values of columns names in two separate worksheets, copy nonmatching columns to third worksheet

So, I've explored a few answered VBA Questions, but I'm still stuck. I have three sheets "By_Oppt_ID", "Top_Bottom" and "Non_Top_Bottom". The first two have a large amount of columns each with a unique name. Now there are some columns in By_Oppt_ID that aren't in "Top_Bottom". So I want to compare each column name in By_Oppt_ID to every column name in "Top_Bottom", and if the column name isn't found, copy that column name and all the rows beneath it, to a third worksheet "Non_Top_Bottom".
So Here's what I have:
Sub Copy_Rows_If()
Dim Range_1 As Worksheet, Range_2 As Worksheet
Dim c As Range
Set Range_1 = Workbooks("Complete_Last_Six_Months_Q_Results.xlsx").Sheets("Top_Bottom")
Set Range_2 = Workbooks("Complete_Last_Six_Months_Q_Results.xlsx").Sheets("By_Oppt_ID")
Application.ScreenUpdating = False ' Stays on the same screen even if referencing different worksheets
For Each c In Range_2.Range("A2:LX2")
' Checks for values not in Range_1
If Application.WorksheetFunction.CountIf(Range_1.Range("A1:CR1"), c.Value) = 0 Then
' If not, copies rows to new worksheet
' LR = .Cells(Row.Count, c).End(xUp).Row
c = ActiveCell
Sheets("By_Oppt_ID").Range("Activecell", "ActiveCell.End(xlDown)").Copy Destination:=Workbooks("Complete_Last_Six_Months_Q_Results.xlsx").Sheets("Non_Top_Bottom").Range("A1:A6745")
Set rgPaste = rgPaste.Offset(0, 1) 'Moves to the next col, but starts at the same row position
End If
Next c
End Sub
I've compiled this many ways and keep getting a series of errors: Subscript Out of Range/ Method "Global_Range" Failure. What am I doing wrong?
If you are going to have this code within the same workbook every time, try using
ThisWorkbook.Sheets("Top_Bottom")
instead of
Workbooks("Complete_Last_Six_Months_Q_Results.xlsx").Sheets("Top_Bottom")
replicate that through your code and see if that fixes the problem.
What do you mean by c = Activecell? Do you mean to say c.activate?
You might then also want to change the next line to
Sheets("By_Oppt_ID").Range(Activecell, ActiveCell.End(xlDown)).Copy Workbooks("Complete_Last_Six_Months_Q_Results.xlsx").Sheets("Non_Top_Bottom").Range("A1")