Excel VBA find first value row in range - vba

I have this range it selects all values in that column:
Set c = ThisWorkbook.Worksheets("SplitSkillInterv").Range("C3:C" & Rows.Count)
Now I need to make it to find some value and then its row (it could be 1 or 10+, so I need to find first one) any ideas?
I need to find for example: in range c values are from 1 to 10 and randomly repeating till total 100 rows. I and lets say i need to find when first time will be number 3 in range and its row.

I'm also a bit unclear what you're asking, but using Find might do the trick for you:
With ThisWorkbook.Worksheets("SplitSkillInterv")
Set Rng = .Range("C3:C" & RowCount).Find(What:=".", LookIn:=xlValues, lookat:=xlPart, _
MatchCase:=False) 'find the first "." in the first row
While Not Rng Is Nothing
'Your stuff here
Set Rng = .Range("C3:C" & RowCount).Find(What:=".", LookIn:=xlValues, _
lookat:=xlPart, MatchCase:=False) 'find the next "."
Wend
End With
I would define RowCount prior to entering this code segment. Replace What:="." with whatever it is you're looking for.
Rng will then be the cell that contains whatever you're searching for. You can do your stuff to that cell at 'Your stuff here, then the next line will find the next occurrence of your search item. If you only need to do something with the first item, eliminate the rows of code after Set Rng = ...

The question is a bit unclear to me but they way I understand it this would get you the row number of the first row with the value you want to find, if you replace 'Something' with the value you want to find.
i = 3
ValueToFind = 'Something'
Do Until ValueToFind = ThisWorkbook.Worksheets("SplitSkillInterv").Cells(i, 3).Value
i = i + 1
Loop
FirstRowWithValue = i

Related

Count the number of cells in a found column using VBA

I am pretty new to VBA and I have been fighting with creating one simple report for many days so I decided to inquire for some help. I will be really grateful for any tips you have or could point to any errors I might've made in my code.
I have the below piece of code (extracted from my loop). What I want to do is to create a list based on around 20 excel files that will have below stats:
name of the current tab inside the workbook
count of nonblanks in a column which name contains word "Difference" (always in row 7 but can be in different columns)
count from the same column but where cells are not blank AND different than 0.
For the last stat I didn't even start so you won't see it in my code but I would appreciate if you have any tips for this one too (which method best to use).
Windows("PassRate.xlsm").Activate
b = ActiveSheet.Cells(Rows.count, 2).End(xlUp).Row + 1
Cells(b, 3) = xlWorkBook.Worksheets(i).Name
xlWorkBook.Worksheets(i).Activate
Set Myrng = Range("B7:M9999").Find(What:="Difference", LookAt:=xlPart, SearchOrder:=xlByColumns, MatchCase:=False)
If Not Myrng Is Nothing Then
RowQnt = xlWorkBook.Worksheets(i).Myrng.Offset(9999, 2).Cells.SpecialCells(xlCellTypeConstants).count
End If
Windows("PassRate.xlsm").Activate
Cells(b, 4) = RowQnt
My problem is that the macro runs and works, but the result I get is the list of tab names but all counts are 0 and I cannot overcome this issue. For the line number 7 I've also tried the piece of code below which yields the same result.
RowQnt = xlWorkBook.Cells(Rows.count, Myrng).End(xlUp)
Is it possible that my problem is due to the fact that in the source files the column containing word "Difference" is sometimes two merged columns? Unfortunately, I cannot change that as these are some automatically generated files from another program.
xlWorkBook.Worksheets(i).Myrng isn't a valid Range syntax while you can simply use MyRng which you already set to a not null Range reference and already has both parent worksheet and workbook references inside it
but even Myrng.Offset(9999, 2).Cells wouldn't do since it references one cell only and not a range of cells
you need a Range(Range1, Range2) syntax, where both Range1 and Range2 are valid Range references to the first and last cell of the range you actually want to count not blank cells of
furthermore you could use WorksheetFunction.CountA() function instead of SpecialCells(xlCellTypeConstants) range, since this latter errors out if no constant cells are found in the range it's being applied to (so you'd need a check) while the former simply returns zero if no not empty cells are found
for all what above you could write the following GetRowQnt() function:
Function GetRowQnt(sht As Worksheet) As Long
Dim Myrng As Range
With sht '<--| reference passed worksheet
Set Myrng = .Rows(7).Find(What:="Difference", LookAt:=xlPart, SearchOrder:=xlByColumns, MatchCase:=False) '<--| find "Difference" in its 7th row
If Not Myrng Is Nothing Then GetRowQnt = WorksheetFunction.CountA(.Range(.Cells(8, Myrng.Column), .Cells(WorksheetFunction.Max(.Cells(.Rows.count, Myrng.Column).End(xlUp).row, 8), Myrng.Column))) '<--| count not blank cells in column where "Difference" was found from row 8 down to its last not empty cell
End With
End Function
and use it in your main code as follows:
With Windows("PassRate.xlsm").ActiveSheet '<--| reference "PassRate.xlsm" workbook active sheet (or change 'ActiveSheet' with 'Worksheetes("yourSheetName")')
For i = 1 To xlWorkbook.Worksheets.count '<--| loop through 'xlWorkbook' workbook worksheets
b = .Cells(.Rows.count, 3).End(xlUp).row + 1 '<--| get "PassRate.xlsm" workbook active sheet current first empty cell in column "C"
.Cells(b, 3) = xlWorkbook.Worksheets(i).Name
.Cells(b, 4) = GetRowQnt(xlWorkbook.Worksheets(i))
Next
End With
please note that with
b = .Cells(.Rows.count, 3).End(xlUp).row + 1
I took column "C" as the leading one to get last not empty row from, since there was no code in your post that wrote something in column "B".
But if your real code has some
.Cells(b, 2) = somedata '<--| write something in column "B" current row
then you can go back to b = .Cells(.Rows.count, 2).End(xlUp).row + 1

How to delete a row that contains a certain code in the first column using excel VBA?

I run a report weekly and a step I must take is to delete a row that contains a certain phrase, in my case "CFS-GHOST-DJKT", in column A. Rows to read through start at 7 and have a variable end.
I have looked online and according to what I have found the following code should work but I get and error and it does not like the line With Cells(Lrow,"A")
I believe as far as the deleteing part goes it is ok the way it is written but the problem is the selection aspect.
Firstrow = 7
Lastrow = Cells(Rows.Count, "A").End(xlUp).Row
With Cells(Lrow, "A")
If Not IsError(.Value) Then
If .Value = "CFS-GHOST-DJKT" Then .EntireRow.Delete
End If
End With
You need to iterate from the last row to the first row when deleting else you will end up skipping rows.
Dim x As Long
For x = Cells(Rows.Count, "A").End(xlUp).Row To 7 Step -1
If Cells(x, "A") = "CFS-GHOST-DJKT" Then Rows(x).Delete
Next
as per you narrative ("I must ... delete a row") you seem to bother only one occurrence of the phrase "CFS-GHOST-DJKT"
in this case there's no need for iterating through cells but just try finding it and, if successful, delete its entire row
Dim f As Range
Set f = Range("A7", Cells(Rows.Count, "A").End(xlUp)).Find(what:="CFS-GHOST-DJKT", LookIn:=xlValues, lookat:=xlPart)
If Not f Is Nothing Then f.EntireRow.Delete

Excel UDF to find first and last cell in range with a given value - runs slowly

I'm writing a function which takes a column range and finds the first and last cell in that column which have a certain value. This gives a first row number and a last row number that are then used to return the corresponding subrange in another column.
The idea is that with this function I can apply Excel functions to a (continuous) subsection of a range. E.g. suppose I have a table with various prices of Apples and Bananas, grouped so that all prices of Apples come first, then Bananas. I want to find the minimum price of Apples and the minimum of Bananas, but selecting the whole range and without changing the range over which to minimise. I would use my desired function to feed a range to Excel's MIN function which included just Apples, or just Bananas, without having to manually select these subranges. A MINIF, if you will - like a weak version of SUMIF but for MIN (and potentially many other functions).
I've found a way of doing it but it's running really quite slow. I think it may have to do with the for loop, but I don't understand enough about efficiency in Excel/VBA to know how to improve it. I'm using this code on an Excel table, so the columns I pass are named columns of a table object. I'm using Excel 2010 on Windows 7 Enterprise.
Grateful for any help. Even solutions on how to conditionally apply functions to ranges that deviate radically from this are well received.
Code:
' ParentRange and CriterionRange are columns of the same table.
'I want to extract a reference to the part of ParentRange which corresponds
'by rows to the part of CriterionRange that contains cells with a certain value.
Function CorrespondingSubrange(CriterionRange As Range, Criterion As _
String, ParentRange As Range) As Range
Application.ScreenUpdating = False
Dim RowCounter As Integer
Dim SubRangeFirstRow As Integer
Dim SubRangeFirstCell As Range
Dim SubRangeLastRow As Integer
Dim SubRangeLastCell As Range
Dim RangeCountStarted As Boolean
RangeCountStarted = False
Set SubRangeFirstCell = CriterionRange.Find(What:=Criterion, LookIn:=xlValues, _
LookAt:=xlPart, SearchOrder:=xlByColumns, SearchDirection:=xlNext, _
MatchCase:=False, SearchFormat:=False)
If Not (SubRangeFirstCell Is Nothing) Then
RangeCountStarted = True
SubRangeFirstRow = SubRangeFirstCell.Row - CriterionRange.Range("A1").Row + 1
For RowCounter = SubRangeFirstRow To CriterionRange.Cells.Count
If Not (CriterionRange.Cells(RowCounter, 1).Value = Criterion) Then
SubRangeLastRow = RowCounter - 1
Exit For
End If
Next
End If
If RangeCountStarted = True And SubRangeLastRow = 0 Then SubRangeLastRow = RowCounter
Set CorrespondingSubrange = ParentRange.Range("A" & SubRangeFirstRow & ":A" & SubRangeLastRow)
Application.ScreenUpdating = True
End Function
I don't like using VBA when an Excel formula can be used efficiently.
First of all, you can get a minimum or maximum according to conditions using a simple IF in an array formula (enter the formula using Ctrl + Shift + Enter. This will add the surrounding {} that indicate an array formula):
=MIN(IF($A$1:$A$10=D1,$B$1:$B$10))
This formula checks in A for the condition in D1 and returns the corresponding value from B. Notice that your data doesn't even need to be ordered for this formula to work:
Second, if you want to keep getting the first and last row numbers, you can use this very formula with a minor addition. However, I suspect that one would use the INDIRECT or OFFSET functions with these values, which is unnecessary and inefficient, as this functions are volatile. Regardless, the addition to the formula is the ROW function. (This formula will need the data to be ordered of course). Array formula for row numbers:
=MAX(IF($A$1:$A$10=D1,ROW($A$1:$A$10)))
This will return the last row number for Bananas.
By settiing Find SearchDirection to xlPrevious you can easily Find the last occurrence in a range.
Toggling Application.ScreenUpdating has little effect when you are just reading values. I prefer shorter variable names. Longer names tend to clutter the screen and make it harder to see what's going on. But's that's just my opinion.
Function CorrespondingSubrange(rCriterion As Range, Criterion As _
String, rParent As Range) As Range
Dim FirstCell As Range
Dim LastCell As Range
Set FirstCell = rCriterion.Find(What:=Criterion, LookIn:=xlValues, _
LookAt:=xlPart, SearchOrder:=xlByColumns, SearchDirection:=xlNext, _
MatchCase:=False, SearchFormat:=False)
Set LastCell = rCriterion.Find(What:=Criterion, LookIn:=xlValues, _
LookAt:=xlPart, SearchOrder:=xlByColumns, SearchDirection:=xlPrevious, _
MatchCase:=False, SearchFormat:=False)
If Not (FirstCell Is Nothing) Then
Set CorrespondingSubrange = rParent.Range("A" & FirstCell.Row & ":A" & LastCell.Row)
End If
End Function
My answer is similar to the VBA UDF solution posted earlier by Thomas Inzina with a couple of differences.
The After:= parameter is used to ensure that the first match found is the first match in the range. The Range.Find method uses a 'tin-can' approach where it loops through the cells of hte range and restarts at the beginning once it reaches the end. By starting After:=.Cells(.Cells.Count) and moving in a forward direction, you will find hte first cell in the range that matches. Similarly, by starting at After:=.Cells(1) and moving SearchDirection:=xlPrevious you will quickly find the last without looping.
I've also used the Intersect method to a) cut down full column references to the Worksheet.UsedRange property and b) to quickly return the working range from the determined criteria range.
Function CorrespondingSubrange(rngCriterion As Range, Criterion As String, _
rngWorking As Range) As Variant
Dim SubRangeFirstCell As Range
Dim SubRangeLastCell As Range
'set the return value to an #N/A error (success will overwrite this)
CorrespondingSubrange = CVErr(xlErrNA)
'chop any full column references down to manageable ranges
Set rngCriterion = Intersect(rngCriterion, rngCriterion.Parent.UsedRange)
With rngCriterion
'look forwards for the first occurance
Set SubRangeFirstCell = .Find(What:=Criterion, After:=.Cells(.Cells.Count), _
LookIn:=xlValues, LookAt:=xlPart, SearchOrder:=xlByColumns, _
SearchDirection:=xlNext, MatchCase:=False, SearchFormat:=False)
If Not SubRangeFirstCell Is Nothing Then
'there is at least one of the criteria - now look backwards
Set SubRangeLastCell = .Find(What:=Criterion, After:=.Cells(1), _
LookIn:=xlValues, LookAt:=xlPart, SearchOrder:=xlByColumns, _
SearchDirection:=xlPrevious, MatchCase:=False, SearchFormat:=False)
Set CorrespondingSubrange = Intersect(rngWorking, Range(SubRangeFirstCell, SubRangeLastCell).EntireRow)
Debug.Print CorrespondingSubrange.Address(0, 0, external:=True)
End If
End With
End Function

Select a row to search in for a string, return whole column and copy it to other sheet

Im facing a problem makeing a loop through row7 for example and returning the Row(which i know, its 7) + Column and copy entire column.
Lets say i need to find "DG" on row 7, i used to search like that:
Dim Found As Range, LastRow As Long
Set Found = Rows(7).Find(What:=value1, LookIn:=xlValues, LookAt:=xlWhole)
If Not Found Is Nothing Then
firstAddress = Found.Address
MsgBox "found" & firstAddress
Do
LastRow = Cells(Rows.Count, Found.Column).End(xlUp).Row
Then hardcoded with SwitchCase :| to return the column to to copy...
I cant post the whole code coz its switch cased from A to Z. The code works, but it is not clear :( i need it dynamic.
Goal:
Specify the row then search on each column ON THAT row only for a value. If found, copy the entire column to another sheet, then loop again (something like .nextRight or something, i know its a parameter) from the column that value was found (like row 7, column 5 or E) to right for the value im looking for untill the value isnt found on that row anymore. The Columns copyed should be placed 1 after another.
I used to code it like so :
Set Found = .FindNext(Found)
Loop While Not Found Is Nothing And Found.Address <> firstAddress
// for the loop
Set destination = Sheets("Sheet1")
emptyColumn = destination.Cells(7, destination.Columns.Count).End(xlToLeft).Column + 1
MsgBox "empty coloana" & emptyColumn
If emptyColumn > 1 Then
emptyColumn = emptyColumn
End If
// for the path that entire column found is placed on another sheet. This code aint working well ... it start with column B all the time, then C, D and so on... But why aint start with column A?
I'm not sure why it is starting at the beginning, but if you are looking at every cell in a sheet, why iterate through every row when you can just iterate through every cell? This runs pretty quick and should work how you want it to.
Sub main()
For Each cell In ActiveSheet.UsedRange.Cells
If cell <> "" Then
MsgBox (cell.Row & ":" & cell.Column)
End If
Next
End Sub
I provided the MsgBox to show the row/column details you can pull from ActiveSheet.UsedRange.Cells object.

Check which is the last cell in a specific column which has a value and select the cell below it in excel vba

Hello I am trying to write some code to get the last cell in a specific column (e.g Column A) which has a value. I only know this:
Cells(3, 2).Value = quantity
To determine the last row use below formula
lastRow = Cells(rows.count,1).End(xlUp).row
rows.count = total number of rows in that version of Excel (65k in
2003 etc) , i.e the last row in that verison of Excel
1 on the right of rows.count = Column A, 2 would be coulmn B and so on
xlUp roughly means it will search bottom to up
You can search more if you would like on this command.
Now once you know the last row, then to fetch the value of it use
Variable = Cells(lastRow,1).Value
something like this which checks:
there is at least one value in column A (else no selection)
that the last value is not in the last row (avoiding an error when trying to offset a row)
code
Sub LastRow()
Dim rng1 As Range
Set rng1 = Columns("A").Find("*", [a1], xlValues, , xlByRows, xlPrevious)
If Not rng1 Is Nothing Then
If rng1.Row <> Rows.Count Then rng1.Offset(1, 0).Activate
End If
End Sub
If you want to find the row for the last cell in a range it is better to use the find function.
As an example: For a specific column n; use the code below to set it to quantity
Dim n As Long, quantity As Long
n = 3
quantity = 5000
Cells(Columns(n).Find("*", SearchOrder:=xlByRows, LookIn:=xlValues, _
SearchDirection:=xlPrevious).Row, n).Value = quantity