First, I would like to know if the letter "A" is included in Column A, and secondly if at least one of the occurences has a 1 in Column B.
Column A | Column B
A | 0
B | 1
A | 1
C | 0
A | 0
With my poor skills I can barely know if there is such value in the column.
Set Obj = Sheets("Sheet 1").Range("Column A")
If Not IsError(Application.Match("A", ObjColumn, 0)) Then MsgBox("There is at least one occurrence")
If Application.Vlookup("A", ObjTable, 2, False) = 1 Then MsgBox("At least one A has 1 as value")
Unfortunately, with Application.Vlookup I can only explore first appearance's value.
I have done some research but I have just found excesively complicated codes for such a simple issue.
Thank you in advance!
you could use WorksheetFunction.CountIf() and WorksheetFunction.CountIfs()
Sub main()
With Sheets("Sheet 1") '<--| reference your sheet
If Application.WorksheetFunction.CountIf(.Columns(1), "A") > 0 Then
MsgBox ("There is at least one occurrence")
If Application.WorksheetFunction.CountIfs(.Columns(1), "C", .Columns(2), 1) > 0 Then MsgBox ("At least one A has 1 as value")
End If
End With
End Sub
or, if you have headers on first row, you could use AutoFilter() and Find() methods:
Option Explicit
Sub main()
With Sheets("Sheet 1") '<--| reference your sheet
With Intersect(.Range("A:B"), .UsedRange) '<--| reference its columns A and B used cells
.AutoFilter Field:=1, Criteria1:="A" '<--| filter referenced cells on its 1st column (i.e. column "A") with value "A"
If Application.WorksheetFunction.Subtotal(103, .Resize(, 1)) > 1 Then '<--| if any cell filtered other than header
MsgBox ("There is at least one occurrence")
If Not .Resize(.Rows.count - 1, 1).Offset(1, 1).SpecialCells(xlCellTypeVisible).Find(what:=2, LookIn:=xlValues, lookat:=xlWhole) Is Nothing Then MsgBox ("At least one A has 1 as value") '<--|search 2nd column filtered cells for "1")
End If
End With
End With
End Sub
Thank you #user3598756
Your suggestions helped me to figure out a solution to my needs, as I have a third column which empty would also activate the code.
Column A | Column B | Column C
A | 0 | ""
B | 1 | 0
A | 0 | 1
C | 1 | ""
A | 0 | ""
Below is part of the code:
Set Obj1 = Sheets("Sheet 1").Range("Table[Column A]")
Set Obj2 = Sheets("Sheet 2").Range("Table[Column B]")
Set Obj3 = Sheets("Sheet 3").Range("Table[Column C]")
If Not IsError(Application.Match("A", Obj1, 0)) Then
If Application.CountIfs(Obj1, "A", Obj2, "1") Or Application.CountIfs(Obj1, "A", Obj3, "<>") > 0 Then MsgBox ("At least one occurrence has either an 1 in B or an empty field in C.")
End If
Thanks a lot!
Related
I have 3 worksheets (user1, user2, result). Each sheet has three columns (A: System_ID, B: Comment, C: Last Modified Time).
The code does this:
Gets maximum last modified time between user1 and user2 in column c.
The result is to get that comment in column b (adjacent to max time found in col c)
put the result (comment) in column b in resut sheet
Simply the comment with last modified time wins and gets pasted in result WS.
Anyways, my problem is that I only can index-match if both indexes in column A have the same sorting/order.
I need to match all records in column A even if they have different row.number or row index.
How to index-match no matter the order in column A
Sub Get_LastModified_Here()
Application.EnableEvents = False
Dim Location1 As Workbook
Set Location1 = GetWorkbook("C:\Users\HP\Desktop\User_1.xlsb")
Dim Location2 As Workbook
Set Location2 = GetWorkbook("C:\Users\HP\Desktop\User_2.xlsb")
Dim SourceCell As Range, SourceRange As Range, CurrentRange As Range
Dim rngTarget As Range
Dim strAdr As String
Dim vSource As Variant, vTarget As Variant, vCurrent As Variant
Dim i As Long
Set SourceRange = Workbooks("User_2.xlsb").Sheets("Data").Range("A2:" & "A1607")
With SourceRange
Set SourceRange = .Resize(.Rows.Count, .Columns.Count + 3)
End With
strAdr = SourceRange.Address
Set rngTarget = Workbooks("User_1.xlsb").Worksheets("Data").Range(strAdr)
Set CurrentRange = ThisWorkbook.Worksheets("Data").Range(strAdr).Offset(0, 1)
vSource = SourceRange
vTarget = rngTarget
vCurrent = CurrentRange
For i = 1 To UBound(vSource, 1)
'Match Column A
If vSource(i, 1) = vTarget(i, 1) Then
'Check max time in Column C (user1 vs user2)
If vSource(i, 3) > vTarget(i, 3) Then
'Get max comment from ((user max)) in column B (result ws)
vCurrent(i, 1) = vSource(i, 2)
ElseIf vSource(i, 3) < vTarget(i, 3) Then
vCurrent(i, 1) = vTarget(i, 2)
ElseIf vSource(i, 3) = vTarget(i, 3) Then
vCurrent(i, 1) = vSource(i, 2)
End If
End If
Next i
SourceRange = vSource
rngTarget = vTarget
CurrentRange = vCurrent
Application.EnableEvents = True
End Sub
Here is a detailed explanation of the issue (I apologize for CAPS letters):
User1 Sheet
I have the SYSTEM_ID in ## Row 1 ##
System_ID
Comment
LastModTime
ID_1
User1 notes
09/12/2020 10:00:01 PM
User2 Sheet
I have the SAME SYSTEM_ID in ## Row 2 ##
System_ID
Comment
LastModTime
ID_1
User2 notes
09/12/2020 10:00:02 PM
This is what I GET in Result Sheet
I have the SAME SYSTEM_ID but in ## Row 3 ##
System_ID
Comment
LastModTime
ID_1
This is what I Want in Result Sheet
I have the SAME SYSTEM_ID but in ## Row 3 ##
System_ID
Comment
LastModTime
ID_1
User2 notes
09/12/2020 10:00:02 PM
What our codes CAN do
Get the comment based on the last modified time, ONLY IF "ID_1" is on THE SAME ROW #. i have tried it (didn't work)
What our codes CAN'T do
Get the comment based on the last modified time, EVEN IF "ID_1" is on A DIFFERENT ROW #. this is where I need help?
EDIT to confirm that the assumptions match:
Sheet User1:
| Id | Comment | LastMod |
| --- | ------- | --------|
| 3 | S1 Comm3| 2 |
| 2 | S1 Comm2| 8 |
| 1 | S1 Comm1| 6 |
Sheet User2:
| Id | Comment | LastMod |
| --- | ------- | --------|
| 1 | S2 Comm1| 3 |
| 2 | S2 Comm2| 4 |
| 3 | S2 Comm3| 8 |
Expected Output:
Id
Comment
NOTES
1
S1 Comm1
Id 1 highest mod is on sheet 1
2
S1 Comm2
Id 2 highest mod is on sheet 2
3
S2 Comm3
Id 3 highest mod is on sheet 3
One option is to build up the result set into a separate collection, and then populate your result set when finished. Since this is an operation that involved multiple lookups (checking to see if a system Id has already been visited), I like to use dictionary objects. These offer highly performant lookup operations.
I'm going to post a much simplified example below that you can hopefully use for your purposes. The code below assumes that the SystemId column is a unique key that maps an entry in sheet1 to an entry in sheet2. It also assumes that each systemId appears once per sheet. If not, it can be tweaked to support that.
The code basically loops through the range and checks if the rows in both sheets have matching system ID. If so, it adds that row to the dictionary, using the ID as a key, and a two element array containing the comment and the last mod time.
If they don't match, it checks each entry against the dictionary to see if that systemID was already visited (earlier on the other sheet). If so, it compares the entries and keeps the most recent mod time, otherwise, it leaves it as is.
Try to work through it and let us know if you need additional help.
Sub Tester()
Dim oDict As Object
Dim a(0 To 1)
Dim sUser1 As Worksheet
Dim sUser2 As Worksheet
Set oDict = CreateObject("Scripting.Dictionary")
Set sUser1 = Sheets("User1")
Set sUser2 = Sheets("User2")
'Here I will assume that both ranges will always
'be the same length. I'm also hardcoding in the
'needed rows. You can use whichever logic
'works best for you to determine how to capture
'all rows in both sheets
For i = 2 To 8
'Two possibilities here:
' 1. The SystemId in both sheets match and
' can be directly compared
' 2. They differ and will each be checked
' to see if they already exist in the dict.
'You can bypass this and just treat each of the
'ranges individually, but I think it would be
'slightly more performant the way I'm doing it.
'
'Also, this assumes that each SystemId will only
'appear once in each sheet, and is a true Primary Key
If sUser1.Cells(i, 1).Value = sUser2.Cells(i, 1) Then
If sUser1.Cells(i, 3).Value > sUser2.Cells(i, 3).Value Then
MergeEntryToDictionary oDict, sUser1.Cells(i, 1).Value, _
sUser1.Cells(i, 2).Value, sUser1.Cells(i, 3).Value
Else
MergeEntryToDictionary oDict, sUser2.Cells(i, 1).Value, _
sUser2.Cells(i, 2).Value, sUser2.Cells(i, 3).Value
End If
Else
'In case they don't match, check each entry against the
'dictionary to see if the systemId has already been added.
'If not, then add it. Otherwise, compare the last mod date
'of the entry to the current, and update if needed.
MergeEntryToDictionary oDict, sUser1.Cells(i, 1).Value, _
sUser1.Cells(i, 2).Value, sUser1.Cells(i, 3).Value
MergeEntryToDictionary oDict, sUser2.Cells(i, 1).Value, _
sUser2.Cells(i, 2).Value, sUser2.Cells(i, 3).Value
End If
Next i
'Below prints back to sheet
Dim k As Variant
Dim n As Long
n = 2
For Each k In oDict.keys
Sheets("result").Cells(n, 1).Value = k
Sheets("result").Cells(n, 2).Value = oDict(k)(0)
Sheets("result").Cells(n, 3).Value = oDict(k)(1)
n = n + 1
Next k
End Sub
Function MergeEntryToDictionary(ByRef oDict As Object, _
SystemId As String, _
sComment As String, _
LastModTime As Double) As Boolean
Dim a(0 To 2)
If oDict.exists(SystemId) Then
If LastModTime > oDict(SystemId)(1) Then
a(0) = sComment
a(1) = LastModTime
oDict(SystemId) = a
End If
Else
a(0) = sComment
a(1) = LastModTime
oDict.Add SystemId, a
End If
MergeEntryToDictionary = True
End Function
Column A contains a list of identical values which are unique among the column. The length of this list is not known. What is the most effienct way to determine the upper and lower bound of the list?
A | B | C | ...
--------------------------
... |
AAA |
AAA |
AAA |
AAA |
AAA |
AAA |
... |
Of course this can be solved by iterating down and up the list from the start postion until you hit a different value. But with larger lists I doubt this is a good solution. Is there any built-in excel function usable in this scenario which would give me a performance advantage?
Other than built in Excel functions as pointed by Scott Craner in comments, you could consider this little VBA function
Function GetArea(rng As Range) As String
With rng.EntireColumn
.AutoFilter field:=1, Criteria1:=rng.Value 'ActiveCell.Value
GetArea= .Resize(.Rows.count - 1).Offset(1).SpecialCells(xlCellTypeVisible).Areas(1).address
.Parent.AutoFilterMode = False
End With
End Function
to be exploited in your "Main" code as follows:
Sub Main()
MsgBox getarea(Range("A12")) '<--| get the bound of the list one element of which is cell A12
End Sub
Here is some sample code you can adapt:
Sub TheOuterLimits()
Dim r As Long, v As Variant
Dim a1 As String, a2 As String
Dim i As Long, c As Long
r = ActiveCell.Row
c = ActiveCell.Column
v = ActiveCell.Value
a1 = ""
a2 = ""
For i = r To 1 Step -1
If Cells(i, c).Value <> v Then
a1 = Cells(i, c).Address(0, 0)
Exit For
End If
Next i
For i = r To Rows.Count
If Cells(i, c).Value <> v Then
a2 = Cells(i, c).Address(0, 0)
Exit For
End If
Next i
MsgBox a1 & vbCrLf & a2
End Sub
The code tells you where the pattern began and where it ends.
I have a text data set that I need to reformat before I can use it. It's currently a text file that I've imported into Excel. Each record currently spans three rows but is in one column. I need to transform it so it's one row with three columns.
The sample below is how my data is currently structured. It shows three records out of 2,000+. The 'Row' column is just for reference and not actually in my data.
Row | Column
1 | File Number: 001
2 | File Code: ABC
3 | File Description: Text file
4 | File Number: 002
5 | File Code: DEF
6 | File Description: Text file
7 | File Number: 003
8 | File Code: GHI
9 | File Description: Text file
Just to clarify, row 1 to 3 would be one record. Row 4 to 6 would be the second record. The third record is from row 7 to 9. Every record in my data is currently split into three rows.
I want to reformat it so it looks something like this:
Row | File Number | File Code | File Description
1 | 001 | ABC | Text
2 | 002 | DEF | Text
3 | 003 | GHI | Text
Again, the row column is just for reference and I don't need it in my reformatted data. Copy and pasting does not appear to be a good option.
Is there a quick way to transform this?
You can use VBA to do this. Code like this might help you for this particular situation.
Option Explicit
Sub Test()
' Let's make the tabular structure in column C, D and E
' C D E
' File Number Code Description
Dim CurrentRow As Integer
CurrentRow = 2 ' Read from A2
Dim WriteRow As Integer
WriteRow = 2 ' Write to C2
Do
' if we see empty data in column A, then we are done with our work
If Len(Trim(Range("A" & CurrentRow))) = 0 Then Exit Do
' make 3 rows of data into 3 columns in a single row
Range("C" & WriteRow).Value = Trim(Replace(Range("A" & CurrentRow).Text, "File Number:", ""))
Range("D" & WriteRow).Value = Trim(Replace(Range("A" & CurrentRow + 1).Text, "File Code:", ""))
Range("E" & WriteRow).Value = Trim(Replace(Range("A" & CurrentRow + 2).Text, "File Description:", ""))
' increment our reading and writing markers
CurrentRow = CurrentRow + 3
WriteRow = WriteRow + 1
Loop
End Sub
Feel free to test out.
As a reference: this uses TextToColumns, AutoFilter, and places results on a new sheet
Option Explicit
Sub mergeRows()
Dim ws As Worksheet, fld As Variant, i As Long, cel As Range
fld = Split("File Number,File Code,File Description", ",")
Worksheets.Add After:=Worksheets(Worksheets.Count)
Set ws = Worksheets(Worksheets.Count)
Application.ScreenUpdating = False
With Worksheets(1)
Set cel = .Range("A1")
.UsedRange.Columns(1).TextToColumns Destination:=cel.Cells(1, 2), _
DataType:=xlDelimited, _
Other:=True, OtherChar:=":"
.Rows(1).Insert Shift:=xlDown, CopyOrigin:=xlFormatFromLeftOrAbove
cel.Cells(0, 2) = "Col 1": cel.Cells(0, 3) = "Col 2"
.UsedRange.AutoFilter
For i = 0 To 2
.UsedRange.AutoFilter Field:=cel.Cells(1, 2).Column, Criteria1:=fld(i)
.UsedRange.Columns(cel.Cells(1, 3).Column).Copy ws.Cells(1, i + 1)
ws.Cells(1, i + 1) = fld(i)
Next
.UsedRange.AutoFilter
.UsedRange.Offset(, 1).EntireColumn.Delete
cel.Cells(0, 2).EntireRow.Delete
End With
ws.UsedRange.Columns.AutoFit
Application.ScreenUpdating = True
End Sub
I need to compare the value in a single column in a single table. Here is a sample table:
Duplicates values id want print in column C.
In the above example ID 1 and ID 6 are duplicates, So cell c2 want to print ID 6, like that c7 want print ID 1.
How to get ans for this ?
Can try below,
Sub test()
i = 2
With ActiveSheet
For Each Cell In .Range("B2:" & .Range("B2").End(xlDown).Address)
If Cell.Row + 1 < .Range("B2").End(xlDown).Row Then
For Each C In .Range(.Cells(Cell.Row + 1, 2).Address, .Range("B2").End(xlDown).Address)
If C.Value = Cell.Value Then
.Cells(i, 3).Value = C.Offset(0, -1).Value
i = i + 1
End If
Next
End If
Next
End With
End Sub
I need a VBA code to loop through only those rows in range1(AA:AB) whose value in cell AA is not found in Col A.
eg
A
1
2
4
AA AB
1 Jon
3 Bob
4 Frank
5 Hank
In this example I need to loop through rows containing Bob and Hank only (2 iterations)
Sub cycle()
On Error GoTo Unmatched
Set used_range = ActiveSheet.UsedRange
For Each Line In used_range.Rows
Key = Cells(Line.Row, 2).Value
If (Key <> "") Then
x = Application.WorksheetFunction.VLookup(Key, used_range, 1, False)
End If
Next Line
Exit Sub
Unmatched:
MsgBox (Cells(Line.Row, 2).Value & " " & Cells(Line.Row, 3).Value)
Resume Next
End Sub
Some notes - I used columns B and C (index 2 and 3) for my test - substitute the appropriate column index numbers for your spreadsheet
Put the thin gs you need to do in the code after Unmatched: