How to compare and show differences on multiple columns in Microsoft Excel - excel

I have a sheet with data in 2 columns, A and B:
--A-- --B--
Apple 57
Orange 62
Lime 45
Orange 58
Apple 57
What I want is, I need to search column A for duplicates, then if there are any, look for their value in column B. If they are different, I want to color the cell in column A to red, show the other value of that entry in column C, and show a message on how many indifferences there are. Something like this:
--A-- --B-- --C--
Apple 57
Orange 62 58
Lime 45
Orange 58 62
Apple 57
Please help me with this, I know how to compare the value in one column, but then don't know how to search for additional value for them in the other column.

Since i am still at learning process it may not be the best solution but it seems it is working
```
' inoG
Sub Solution()
Dim rows As Integer
rows = Range("a1").End(xlDown).Row 'Getting total row number
Dim dt As Variant
dt = Range("a1:c" & rows) 'data into array '
'forward search
For i = 1 To rows
For j = i + 1 To rows
If dt(i, 1) = dt(j, 1) And dt(i, 2) <> dt(j, 2) Then
dt(i, 3) = dt(j, 2)
GoTo Continue1
End If
Next j
Continue1:
Next i
'backward search
For i = rows To 1 Step -1
For j = i - 1 To 1 Step -1
If dt(i, 1) = dt(j, 1) And dt(i, 2) <> dt(j, 2) Then
dt(i, 3) = dt(j, 2)
GoTo Continue2
End If
Next j
Continue2:
Next i
'filling row C and Highlighting
For i = 1 To rows
If Not IsEmpty(dt(i, 3)) Then
Cells(i, 3) = dt(i, 3)
Range("A" & i).Interior.ColorIndex = 3
End If
Next i
'Final Message
Dim totdif As Integer
totdif = WorksheetFunction.CountA(Range("C1:C1" & rows))
MsgBox totdif
End Sub

My following solution used a helper column to rank the values in Column B per item in Column A using COUNTIFS function. Then I used a pivot table to show the average value of each rank for each item.
Presume you have following named ranges:
ListItem being your data in Column A;
ListValue being your data in Column B.
The formula in Cell C2 is:
=IF(COUNTIFS(ListItem,A2,ListValue,">"&B2)+1>1,"2nd Value","1st Value")
Change the cell references used to suit your case.
This solution will create an output table laying out all the unique items and then populate the two different values (if there are two) in two consecutive columns next to each item. For comparison purpose I think a pivot table is sufficient and quite efficient.
P.s. to create a pivot table, you just need to highlight the source table, go to Insert tab, and click the Pivot Table button to generate a pivot table. Set up the fields in the following way and you will have something similar to my example:
EDIT #2
If you want to show the second value in Column C for each item, here is a formula based approach.
In Cell C2 enter the following formula and drag it down:
=IFERROR(AGGREGATE(14,6,AGGREGATE(14,6,ListValue/(ListItem=A2),ROW($Z$1:INDEX($Z:$Z,COUNTIF(ListItem,A2))))/((AGGREGATE(14,6,ListValue/(ListItem=A2),ROW($Z$1:INDEX($Z:$Z,COUNTIF(ListItem,A2))))<>B2)),1),"")
The logic is to use ListValue/(ListItem=A2) to return a range of values for each item, then use AGGREGATE function to filter out all the errors, then use
AGGREGATE(14,6,ListValue/(ListItem=A2),ROW($Z$1:INDEX($Z:$Z,COUNTIF(ListItem,A2))))<>B2
to further filter the range to show the second value (which is different to the first value), then use AGGREGATE function again to return that value.
Let me know if you have any questions. Cheers :)

I think you can do this with formulas.
If you are concerned about users changing the formula, use a Table (and perhaps even protect the formula column, although this would require VBA code to allow expanding the table). That way the ranges will dynamically adjust to additions and deletions of data, and the users will not need to edit the formula:
With the table renamed Fruits, and the columns named as in the screenshot:
=IFERROR(AGGREGATE(14,6,1/(([#Fruit]=[Fruit])*([#Value]<>[Value]))*[Value],1),"")
Use Conditional Formatting to format the cells
EDIT:
I think the table approach would give you a better solution, but for a VBA approach I would use a Dictionary and a collection of the different values associated with the fruits.
Assuming your first column is named "Fruit" (or something you can use in Find, or even a known address), you can use the following to create a column of the alternate values for each item.
'Add reference to Microsoft Scripting Runtime
' or use late binding
Option Explicit
Sub diffs()
Dim myD As Dictionary
Dim vData As Variant
Dim rData As Range, C As Range
Dim wsSrc As Worksheet
Dim I As Long, V As Variant
Dim colVals As Collection
'Find the table
Set wsSrc = Worksheets("sheet2") 'or wherever
With wsSrc.Cells
Set C = .Find(what:="Fruit", after:=.Item(1, 1), LookIn:=xlValues, lookat:=xlWhole, MatchCase:=True)
If Not C Is Nothing Then
With wsSrc
Set rData = .Range(C, .Cells(.Rows.Count, C.Column).End(xlUp)).Resize(columnsize:=3)
vData = rData
End With
Else
MsgBox "No data table"
Exit Sub
End If
End With
'Collect the data into a dictionary
'Max 2 different values per fruit
Set myD = New Dictionary
myD.CompareMode = TextCompare
For I = 2 To UBound(vData)
If Not myD.Exists(vData(I, 1)) Then
Set colVals = New Collection
colVals.Add Item:=vData(I, 2), Key:=CStr(vData(I, 2))
myD.Add Key:=vData(I, 1), Item:=colVals
Else
On Error Resume Next 'omit duplicate values
myD(vData(I, 1)).Add Item:=vData(I, 2), Key:=CStr(vData(I, 2))
On Error GoTo 0
End If
Next I
'Populate column 3
For I = 2 To UBound(vData, 1)
Set colVals = myD(vData(I, 1))
vData(I, 3) = ""
If colVals.Count > 1 Then
For Each V In colVals
If V <> vData(I, 2) Then vData(I, 3) = V
Next V
End If
Next I
Application.ScreenUpdating = False
With rData
.Clear
.Value = vData
For I = 2 To UBound(vData)
If vData(I, 3) <> "" Then
With rData.Cells(I, 1)
.Font.Color = vbWhite
.Font.Bold = True
.Interior.Color = vbRed
End With
End If
Next I
End With
End Sub

Related

Excel formula or VBA required to resolve this case complicated

Dear Team Could you please help me on below case
In the excel file we have name and department with available resources
First table we have details and second table need to fill with number or just comments YES or NO.
I have tried with IF formula it will not be helpful because cells keep moving based on second table which changes daily
Formula which I have tried no useful
If(A2&b1=a12&b11,if(b2<0,"No","Yes"),"Match not found")
Could you please help me. VBA am new no idea how this case can be helpful
You may want to transform the first table from the cross-table layout to tabular (aka unpivot):
e.g. 1st column=name, 2nd column=department
then add 3rd column as combo: “name/department” (or any other delimiter in between)
| a1 | 1011 | a1/1011 | 1 |
| a1 | 1033 | a1/1033 | 3 |
etc.
In the second crosstable you could use vlookup/xlookup:
match criteria is the respective combo of the name to the left and the department on the column header (e.g. A12&”/“&”B11)
Match (vlookup) this against 3rd column from first table (in tabular layout) to get back then value (or “yes”) - this should work dynamically based on the value in the respective column and row headers (and not dependent on the position of the cells)
Use PowerQuery to unpivot and add 3rd column and replace the numbers with “yes” to create tabular version of first table
I asked a clarification question, but you were not interested in answering it.
Anyhow, I prepared an answer which should be fast enough, using arrays and a dictionary. It uses the ranges you show us in the picture. I wanted to configure it for using two sheets and automatically calculating the last row of each.
It assumes that in the first table there are unique names. In the second one may be as many names as you want, in any sorting order.
Please, test the next code and send some feedback:
Sub matchNames()
Dim sh As Worksheet, lastR As Long, dict As Object
Dim rngGlob As Range, rngRow As Range, arrGlob, arrSrc, i As Long, j As Long, arrYes, arrRet
Set sh = ActiveSheet
lastR = 7 ' if can be calculated, if two sheets will be used: sh.Range("A" & sh.rows.count).End(xlUp).row
Set rngGlob = sh.Range("A1:G" & lastR): arrGlob = rngGlob.Value2
arrSrc = sh.Range("B11:D11").Value2 'the array of numbers to be matched in the global array
arrRet = sh.Range("A12:D17").Value2 'the array of the range to return (Yes...)
'place the "Yes" string where the numbers exist in an array and load the dictinary:
Set dict = CreateObject("Scripting.Dictionary")
For i = 2 To UBound(arrGlob)
On Error Resume Next 'for the case of no any value on the processed row:
Set rngRow = rngGlob.rows(i).Offset(0, 1).Resize(1, rngGlob.Columns.count - 1).SpecialCells(xlCellTypeConstants)
On Error GoTo 0
If Not rngRow Is Nothing Then arrYes = getYes(rngGlob, rngRow, arrSrc)
dict(arrGlob(i, 1)) = IIf(IsArray(arrYes), arrYes, vbNullString) 'place the array containing Yes as Item
Erase arrYes
Next i
'place the dictionary arrays value in the array to be returned:
For i = 1 To UBound(arrRet)
arrYes = dict(arrRet(i, 1))
If UBound(arrYes) = UBound(arrSrc, 2) - 1 Then
For j = 0 To UBound(arrYes)
arrRet(i, j + 2) = arrYes(j)
Next j
Else
'place empty strings, to clean eventually older values whchid does not correspond, anymore
For j = 0 To UBound(arrSrc, 2) - 1: arrRet(i, j + 2) = "": Next j
End If
Next i
sh.Range("A12").Resize(UBound(arrRet), UBound(arrRet, 2)).Value2 = arrRet
End Sub
Function getYes(rngGlob As Range, rng As Range, arr) As Variant 'it returns the "Yes" array per name
Dim rngH As Range, arrY, i As Long, cel As Range, mtch
ReDim arrY(UBound(arr, 2) - 1)
Set rngH = rng.Offset(-(rng.row - 1))
For Each cel In rngH.cells
mtch = Application.match(cel.value, arr, 0)
If IsNumeric(mtch) Then
arrY(mtch - 1) = "Yes"
End If
Next cel
getYes = arrY
End Function

Excel VBA AutoFilter select row which is filter equal to key1 and minimum of key2

Spoiler Alert: I dont want a formula based approach i want to use the worksheet as a data store not a calculator
The question here is
Is there a smart way I can do step 4 with autofilter?
I have data in an excel range which is
Key
Time
A
1
B
3
C
3
C
3
A
3
B
2
So can have duplicate rows.
I want to filter the rows where key in (A, C) which has the smallest time for each key, so that would yield
Key
Time
A
1
C
3
I can do this "brute force" by
define range which is data
define array which is filter
filter and delete rows with unwanted keys
(brute force bit) sort by time step through each row and delete not first rows
Is there a smart way I can do step 4 with autofilter? I need to filter on a secondary key and take the first value. I kind of feel I should be able to do this with xlFilterTop10 but cant figure it out. Long day
ps
I know I can do this by setting up the sheet as an ADO source and performing sql but would prefer not to ...
Option Explicit
Sub help_me_please()
Dim arr(1) As Variant
arr(0) = "A"
arr(1) = "C"
Dim rIn As Range
Dim rOut As Range
Set rIn = ThisWorkbook.Worksheets("sheet1").Range("a1")
Set rOut = ThisWorkbook.Worksheets("sheet2").Range("a1")
rIn.CurrentRegion.AutoFilter field:=1, Criteria1:=arr, Operator:=xlFilterValues
'copy filtered in to output sheet
rIn.CurrentRegion.SpecialCells(xlCellTypeVisible).Copy Destination:=rOut
'remove filter
rIn.CurrentRegion.AutoFilter
rOut.CurrentRegion.Sort key1:=rOut.Range("a1"), order1:=xlAscending, _
key2:=rOut.Range("b1"), order2:=xlAscending, Header:=xlYes
Dim r As Range
Dim i As Long
Set r = rOut.Offset(1, 0)
i = 0
While Not IsEmpty(r.Offset(i, 0).Value)
If r.Offset(i, 0).Value = r.Offset(i - 1, 0).Value Then
'remove the not-first row
r.Offset(i, 0).EntireRow.Delete
Else
' move to next item to check
i = i + 1
End If
Wend
End Sub

Ignore row if all 4 cells are 0; get name if any of the 4 cells is greater than 0

i have a list of names(Column A), the numbers in columns B to F are result of a formula. I'm trying to create a FOR LOOP code that will check columns B to F, if all cells in B to F are zero then the code should ignore the current row and skip to the next row; if any of the cells in columns B to F is greater than 0, then the code should get the corresponding name in Column A.
Example: If any of the cells in B2, C2, D2, and E2 is greater than 1, then i should get the name/value of A2. if all cells in B2, C2. D2, and E2 are all zeros, then proceed to check next row and do the same thing.
here's the code i used to try to get the names that has any of the 4 column cell values greater than 1
For i = 2 To LastCalcAnalystRowIndex '//wsCalculations.Cells(Rows.Count, "CP").End(xlUp).Row
'//Get Component from cell in column "BP"
Analyst = wsCalculations.Cells(i, "CP").Value
Component = wsCalculations.Cells(i, "CN").Value
weeknumber = wsCalculations.Range("BR2").Value + 3
If wsCalculations.Cells(i, "B").Value = 0 And wsCalculations.Cells(, "C").Value = 0 _
And wsCalculations.Cells(i, "D").Value = 0 And wsCalculations.Cells(i, "E").Value = 0 _
And wsCalculations.Cells(i, "F").Value = 0 Then
Exit For
Else
wsCalculations.Cells(i, "A").Value = wsCalculations.Cells(i, "CP").Value
End If
Next
using the code above, i tried to get the names which all 4 column values are not equal to zero, but the result i get is just a copy of the original list. i highlighted the rows i want my code to skip. i also included the result i get and the result i want to get.
Below is a sample data. My original data has 54 rows. .
can anyone please tell me what im getting wrong?
There's no real need for VBA.
Note that I have used a Table with structured references. You can change it to a range with normal references if you prefer.
If you have O365, you can use a helper column and a formula.
Add a helper column which SUM's the cells in each row (and you can hide that column if necessary).
eg: G2: =SUM(Table3[#[Column2]:[Column6]])
Then, assuming the data is in a Table named Table3 use the formula:
=INDEX(FILTER(Table3,Table3[sumRow]>0),0,1)
If you have an earlier version of Excel, you can use:
I2: =IFERROR(INDEX(Table3[Column1],AGGREGATE(15,6,1/(Table3[sumRow]>0)*ROW(Table3)-ROW(Table3[#Headers]),ROWS($1:1))),"")
and fill down the length of the table.
Not the solution but could shorten your initial code
Why not create a hidden column* that does an =SUM of the entire row
Then get the value from that
instead of using code to get the value of each 5 cells then adding it up.
edit: changed the 'hidden cell' to 'hidden column' :P
Try
Sub test()
Dim rngDB As Range
Dim rng As Range, rngSum As Range
Dim wsCalculations As Worksheet
Dim vR() As Variant
Dim n As Long
Set wsCalculations = ActiveSheet
With wsCalculations
Set rngDB = .Range("a2", .Range("a" & Rows.Count).End(xlUp))
End With
For Each rng In rngDB
Set rngSum = rng.Offset(, 1).Resize(1, 5)
If WorksheetFunction.Sum(rngSum) > 0 Then
n = n + 1
ReDim Preserve vR(1 To n)
vR(n) = rng
End If
Next rng
With wsCalculations
If n Then
.Range("h2").Resize(n) = WorksheetFunction.Transpose(vR)
End If
End With
End Sub
can anyone please tell me what im getting wrong?
actually your shown code isn't consistent with your wording, so it's impossibile to tell all that's wrong
but for sure that Exit For is a logical error, since it actually gets you out of the loop when it first meets five zeros
so as far as this logical error is concerned, you should change that piece fo code to the following:
With wsCalculations
For i = 2 To .Cells(.Rows.Count, "A").End(xlUp).Row
If WorksheetFunction.CountIf(.Cells(i, 2).Resize(, 5), 0) < 5 Then ' when a row is eligible for math
' do your math
End If
Next
End With
where I used WorksheetFunction.CountIf() function that enables you to deal with different conditions since your wording wasn't clear about this item, too ("greater than 0", "all cells...are zero", "greater than 1")

Macro to extract min/max/average data from large data set

I have 2 columns in my excel file, and I want to get the MIN/Max/Average of the price in the second row based of the information in the first column. i cannot use the normal function as there is 200,000 rows in my workbook.
I have done this before with different data that used the date in the first column now I wish to change it as i am not using date. I am getting errors in the fist if statement.
Sub Button1_Click()
Dim Rng As Range, Dn As Range, n As Long, c As Long, K As Variant
Set Rng = Range(Range("A2"), Range("A" & Rows.Count).End(xlUp))
With CreateObject("scripting.dictionary")
.CompareMode = vbTextCompare
Application.ScreenUpdating = False
For Each Dn In Rng
If Not .Exists(DateValue(Dn.Value)) Then
.Add DateValue(Dn.Value), Dn.Offset(, 1)
Else
Set .Item(DateValue(Dn.Value)) = Union(.Item(DateValue(Dn.Value)), Dn.Offset(, 1))
End If
Next
Range("E1:H1") = Array("Date", "Max", "Min", "Average")
c = 1
For Each K In .keys
c = c + 1
Cells(c, "E") = K
Cells(c, "F") = Application.Max(.Item(K))
Cells(c, "G") = Application.Min(.Item(K))
Cells(c, "H") = Application.Average(.Item(K))
Next K
End With
Application.ScreenUpdating = True
End Sub
MIN/Max/Average of the values in column 2 that relate to the values in column 1
The "normal" functions should be working properly regardless of how many rows of data you have.
For example, I just double-checked worksheet functions MIN, MAX, AVERAGE, MINIFS, MAXIFS and AVERAGEIFS calculated on a column of 200k rows and dependant on the value of another column, and I didn't have any problem (using Excel for Office 365).
Example:
"Average of Column B where Column A equals 2"
Worksheet function:
=AVERAGEIFS(B:B, A:A, 2)
VBA WorksheetFunction:
MsgBox Application.WorksheetFunction.AverageIfs(Range("B:B"), Range("A:A"), 2)
Perhaps you're using an older version of Excel?
As far as I know, all of Excel's functions/formulas will work properly up to the maximum number of rows/columns of that the version can handle (which is 1,048,576 rows by 16,384 columns since at least Excel 2007).

VBA Excel: Non-Active Sheet and Unknown Number of Rows. If cell contains value, perfom calculation and insert new value into cell on same row

I have tried my best to search for the answer but can't get what I'm looking for. I'm very new to VBA so may be going wrong in several places here . . .
I'm creating a data formatter that processes data with a different numbers of records (rows) each time it is used. Data will be on non-active sheet. First row has headings. I've successfully used similar code to the code below to identify rows with certain data on it and clear the contents of other cells on that row.
The reason I refer to column E is because it is the only column that has data in every record. I then have to find the rows that have a value in column BU, then multiply that value by 20 and insert the result in column BX of the same row.
I keep getting Run-time Error 13 but don't understand as it's simply a number with 2 decimal places in cell BU, and currently there is nothing in BX.
Sub CalcTotalLTA()
Dim i As Variant
'counts the no. of rows in E and loops through all
For i = 1 To Sheets("Input").Range("E2", Sheets("Input").Range("E2").End(xlDown)).Rows.Count
'Identifies rows where columns BU has a value
If Sheets("Input").Cells(i, 73).Value <> "" Then
'calculate Total LTA
Sheets("Input").Cells(i, 76).Value = Sheets("Input").Cells(i, 73).Value * 20
End If
Next i
End Sub
You're most likely having an issue because Application.Sheets holds both sheet types, which are Charts and Worksheets. Application.Sheets does not have a .Range() property.
Replace all instances of Sheets() with Worksheets().
Worksheets("Input").Cells(i, 76).Value = Worksheets("Input").Cells(i, 73).Value * 20
Even better:
Dim ws as Worksheet
Set ws = Worksheets("Input")
..
ws.Cells(i,76).Value = ws.Cells(i,73).Value * 20
Exclude Header Row From Range
Public Function rngExcludeHeaders(rng As Range) As Range
Set rng = rng.Offset(1, 0).Resize(rng.rows.count - 1, rng.Columns.count)
Set rngExcludeHeaders = rng
End Function
usage:
Dim MyRange as Range
Set MyRange = rngExcludeHeaders(ws.UsedRange)
Thanks to input from #Adam Vincent and #Vityata, and some other research (the reason why I'm solving this myself, hope that's not bad etiquette) I've found the solution. Starting the index 'i' at 2 and adding 1 at the end avoids the header row text and includes the last row too:
Option Explicit
Sub CalcTotalLTA()
Dim i As Variant
Dim ws As Worksheet
Set ws = Worksheets("Input")
'counts the no. of rows in E and loops through all
For i = 2 To ws.Range("E2", ws.Range("E2").End(xlDown)).Rows.Count + 1
'Identifies rows where columns BU has a value
If ws.Cells(i, 73).Value <> "" Then
'calculate Total LTA
ws.Cells(i, 76).NumberFormat = "0.00"
ws.Cells(i, 76).Value = ws.Cells(i, 73).Value * 20
End If
Next i
End Sub
Try it like this:
Option Explicit
Sub CalcTotalLTA()
Dim i As Long
With Worksheets("Input")
For i = 1 To .Range("E2", .Range("E2").End(xlDown)).Row
If .Cells(i, 3) <> "" Then
.Cells(i, 6) = .Cells(i, 3) * 20
End If
Next i
End With
End Sub
This is what I have changed:
Adding Option Explicit on top
I have used With Worksheets("Input") to make your code more understandable.
Furthermore, I suppose you do not need Rows.Count but .Row
I have changed 76 and 73 to 3 and 6 to avoid some scrolling to the right, thus be careful when you use it over your workbook.
Removed .Value as far as it is the default one.

Resources