3

I'm trying to find a specific value in a specific column. For example the value 100000 in the column B. The following code only works if the column is wide enough to display the full number:

enter image description here

Dim rngSearchRange As Range
Set rngSearchRange = ThisWorkbook.Worksheets(1).Columns(2)
Dim searchTerm As Variant
searchTerm = 100000

Dim rngResultRange As Range       
Set rngResultRange = rngSearchRange.Find(What:=searchTerm, lookin:=xlValues, lookat:=xlWhole)

As soon as the column gets to narrow, so Excel only displays ##### instead of 100000 in the specific cell the find-method returns Nothing.

enter image description here

Is there a way to use the find-method based on the actual values and not on the display of the values? If not, are there any alternatives to For Each cell In rng.Cells? Eventually, I'm looing the method which usees up the least resources.

Note: the searchRange is only one column, the searchValue either doesn't exist or only exists once.

Note: there is a followup question on using match() Note: from time to time it seems to work although neither data nor code changes. Unfortunately, I can not reproduce the change. This whole thing might be a bug indeed

Albin
  • 1,000
  • 1
  • 11
  • 33
  • Try changing `lookat:=xlWhole` to `lookin:=xlValues` – Scott Craner Sep 16 '19 at 16:29
  • 1
    Can repro the `Find` failing. Is autofitting the column an option? – BigBen Sep 16 '19 at 17:02
  • Or is using `Application.Match` an option? – BigBen Sep 16 '19 at 17:20
  • @ScottCraner I can't get rid of `lookat:=xlWhole` (becuse it wont give me the correct results), but I tried adding `lookin:=xlValues` - no change. – Albin Sep 16 '19 at 17:27
  • @Albin - have you tried any of the answers that propose using `Match`? Btw - this is a great catch on a potential bug in `Find`. – BigBen Sep 16 '19 at 17:34
  • 4
    Having tested now, I find that `LookIn:=xlFormulas` works in all cases. If you don't specify this parameter it inherits the setting when last used. – SJR Sep 16 '19 at 17:38
  • @SJR Thanks, I'll try that as a workaround, although eventually, I have to use `lookin:=xlValues` since the value I'm looking for might be the result of a formula. I specified that in my question now as well. – Albin Sep 16 '19 at 18:21
  • That is a fairly fundamental thing. Is there a reason why the columns can't be expanded? – SJR Sep 18 '19 at 10:39
  • Do all the cells have the same Font settings? Could you do the `rngSearchRange.Font.Size=1: rngSearchRange.Calculate`, run the `Find`, and then set the size back to normal? – Chronocidal Sep 18 '19 at 10:59
  • @SJR yes, I don't want to use a search method, that relies on the view of the data and not on the data itself. With this requirement, there are better solutions like match etc. – Albin Sep 18 '19 at 13:57
  • @Chronocidal basically same issue as in my previous comment to SJR – Albin Sep 18 '19 at 13:58

5 Answers5

1

Can reproduce the Find failing if the column width is too narrow.

Match doesn't have this problem.

Sub dural()
    Dim rngSearchRange As Range
    Set rngSearchRange = ThisWorkbook.Worksheets(1).Columns(2)
    Dim searchTerm As Variant
    searchTerm = 100000
    Dim rngResultRange As Range

    Dim found As Variant
    found = Application.Match(searchTerm, rngSearchRange, 0)

    If Not IsError(found) Then
        Set rngResultRange = rngSearchRange.Cells(found)
        MsgBox rngResultRange.Address
    End If
End Sub

enter image description here

Depending on your use case, this may be an option, or if not, maybe Range.AutoFit? Though with "I'm trying to find a specific value in a specific column," it sounds like this could be an option.

BigBen
  • 46,229
  • 7
  • 24
  • 40
  • Thanks, I'll give it a try. I guess I could also try to fit the column using VBA before I use range.find() (and put it back to its original width afterwards) but it doesn't seem like a good solution since it depends on the "presentation" of the values, which is out of my control. At least I have to make sure that after fitting the column, all the values are fully visible. Is there a way to do this? – Albin Sep 16 '19 at 18:29
  • That's what `Range.AutoFit` does. – BigBen Sep 16 '19 at 18:29
  • I know, but if I do this workaround I would prefer to validate the correct results separately. Not sure if this can be done. Or does AutoFit at least return any results after execution (especially if it failed but doesn't produce an error)? – Albin Sep 16 '19 at 18:35
  • I'm not sure what you mean. I'm not sure how AutoFit would fail. – BigBen Sep 16 '19 at 18:38
  • (+1 anyway) I can not give you a specific scenario because this is in itself would be the reason for the validation. But let us say, it did not execute for some unknown reason. A possible validation would be to check that there is no ##### value in the search range. Basically, it's just like [design by contract](https://de.wikipedia.org/wiki/Design_by_contract) only that I'm just checking the postconditions. – Albin Sep 17 '19 at 09:04
  • You could do a `Find` for `#` but I'm not sure that is robust. Also, FYI it looks like Autofit will unhide a hidden column. – BigBen Sep 17 '19 at 11:10
  • I'll try that, do you know if there is anything else then "#" which can be displayed instead of the actual value? – Albin Sep 17 '19 at 16:00
  • Scientific notation, so continuing with the example, `1E+05`. That's why I'm not sure this is a good idea. – BigBen Sep 17 '19 at 16:02
  • In my case I could look for "+" and "#", since the range in question only holds numeric values. I'll give it a try. – Albin Sep 17 '19 at 16:04
1

You could either get the range into an array and loop the array, or just use MATCH:

    Sub test()
        Dim rngSearchRange, rngResultRange As Range
        Dim searchTerm As Variant
        Dim vRow As Variant

        Set rngSearchRange = ThisWorkbook.Worksheets(1).Columns(2)
        searchTerm = 10000
        vRow = Application.Match(searchTerm, rngSearchRange, 0)
        If Not IsError(vRow) Then
            Set rngResultRange = rngSearchRange.Resize(1, 1).Offset(vRow - 1, 0)
        Else
            MsgBox "Not Found"
        End If
End Sub
Charles Williams
  • 23,121
  • 5
  • 38
  • 38
  • Thanks, but a loop takes too much time, as I mentioned in my question. Right now I don't see any differences to BigBen's answer (using match), or am I missing anything? – Albin Sep 16 '19 at 18:30
  • @Albin - looping an array is a lot quicker than `For Each cel In rng.Cells`. – BigBen Sep 16 '19 at 18:42
  • @BigBen yes, but if I'm not mistaken, to fill the array would require to loop through each cell anyway, so in the end I have to loop twice (through the cells and the array)!? Or is there a quicker way? Charles's match example is essentially the same as yours, right? – Albin Sep 17 '19 at 07:51
  • No you can read a range into an array with one line. See [here](http://www.cpearson.com/excel/ArraysAndRanges.aspx) for a good explanation. Looping through an array is very fast. Hitting every individual cell is what is slow. – BigBen Sep 17 '19 at 09:58
  • @Albin Is it possible to just loop `rngSearchRange .Columns` (or `rngSearchRange .Rows` - whichever is smaller!), and do `Match` on each until you find a result? – Chronocidal Sep 18 '19 at 11:09
  • @Chronocidal yes, that's what I'm going for, BigBen already suggested it in his answer. Anyway, I don't need to loop through any columns since I'm only searching within one column. – Albin Sep 18 '19 at 13:54
  • @Charles Williams: why does your example resize? Once I have the search result / cell, I don't need to resize anymore (the example would be easier to understand without it). – Albin Sep 19 '19 at 12:56
  • @Albin - it is just a habit (I don't use .Cells(number) because its difficult with 2D ranges) – Charles Williams Sep 19 '19 at 13:38
0

Try this:

Sub test()
   Dim rngSearchRange, rngResultRange As Range
   Dim searchTerm As Variant

   Set rngSearchRange = ThisWorkbook.Worksheets(1).Columns(2)
   searchTerm = 10000
   Set rngResultRange = rngSearchRange.Find(what:=searchTerm, LookIn:=xlValues)
End Sub
CADV
  • 13
  • 3
  • 2
    I don't see any difference to my code (except that you define rngSearchRange as a Variant)?! And it doesn't help to define it as Variant, why should it help? – Albin Sep 16 '19 at 17:22
  • Compared to your original question I used "set" for both ranges, and changed the parameters for find. I tested it on some data, and it worked fine, so I expect it would for your data as well. Also, if you read the code, rngSearchRange is declared as a Range, not a Variant. – CADV Sep 17 '19 at 05:42
  • Thanks!! the missing set was just missing from my question, not from my original code, so that wasn't the problem (I corrected my question). I overlooked the parameters, but actually, I can not use your parameters, since it doesn't contain lookat=xlWhole. I need that for my search (please see my question). No rngSearchRange will be declared as a Variant see [here](https://stackoverflow.com/questions/27403834/declare-and-use-range-in-vba), or did they change the syntax in VBA? – Albin Sep 17 '19 at 07:43
  • @CADV Albin is right, you have to explicitly declare the variable type, otherwise it is a `Variant`: `Dim rngSearchRange as Range, rngResultRange as Range`. – BigBen Sep 17 '19 at 10:08
0

The issue with find is that it only looks for displayed values for some reason, identical to the behaviour of the search box you get pressing crtl+F or clicking the "Find & Select" option on your "Home" ribbon. There is currently no known way to fix this (looking in xlValues and the like as the comments pointed out) As there are various ways to get around this, the (slowest) but most reliable one would be to use a foreach loop as so:

For Each cel In rngSearchRange
If cel.Value = searchTerm Then
Set rngResultRange = cel
exit for '<-If you want the first result, leave this. If you want the last result, omit. Using the first result could be significantly quicker as it will stop looping right away.
End If
Next cel

Just make sure you set your range as definite value like Range("A1:B87") instead of Columns(2) as this will throw a type mismatch error. If you want to search column B, use Range("B:B") instead.

Plutian
  • 2,276
  • 3
  • 14
  • 23
  • I think, reading the range into an array and using a loop on the array (as it has been already suggested in a different answer) should be faster. I still have to test it though. – Albin Sep 18 '19 at 13:50
  • That was indeed one of my ideas, and it might indeed be slightly faster. But I don't have a large dataset to test it with, so I went with something I'm sure of. If you do try both though can you let me know which is faster? – Plutian Sep 18 '19 at 13:57
  • sure thing, I will do so – Albin Sep 18 '19 at 14:00
0

This is a cheating-version: It will copy the range to a temporary Worksheet, converting Formulas to Values, and do the lookup there.

Public Function FindValueInRange(ByVal RangeToSearch As Range, ByVal ValueToFind As Variant) As Range
    Dim WasActive As Worksheet, ScreenUpdating As Boolean, Calculation As XlCalculation
    'Store current position
    Set WasActive = ActiveSheet
    ScreenUpdating = Application.ScreenUpdating
    Application.ScreenUpdating = False
    Calculation = Application.Calculation
    Application.Calculation = xlCalculationManual

    'Let's get to work!
    Set FindValueInRange = Nothing 'Default to Nothing
    On Error GoTo FunctionError

    Dim TempSheet As Worksheet, FoundCell As Range, DisplayAlerts As Boolean

    'Create Temp Sheet
    Set TempSheet = Worksheets.Add

    'Copy data to Temp Sheet, in the same location
    TempSheet.Range(RangeToSearch.Address(True, True, xlA1, False)).Value = RangeToSearch.Value

    'Column Width to Maximum!
    TempSheet.Range(RangeToSearch.Address(True, True, xlA1, False)).EntireColumn.ColumnWidth = 255

    'Search the cells in the Temp Sheet
    Set FoundCell = TempSheet.Range(RangeToSearch.Address(True, True, xlA1, False)).Find(ValueToFind, LookIn:=xlFormulas, LookAt:=xlWhole)

    'Return the found cell, but on the original Worksheet
    If Not (FoundCell Is Nothing) Then Set FindValueInRange = RangeToSearch.Worksheet.Range(FoundCell.Address(True, True, xlA1, False))

    'Remove the Temp Sheet
    DisplayAlerts = Application.DisplayAlerts
    Application.DisplayAlerts = False
    TempSheet.Delete
    Application.DisplayAlerts = DisplayAlerts
    Set TempSheet = Nothing

FunctionError:
    On Error GoTo -1 'Reset the error buffer
    'Restore previous position
    WasActive.Activate
    Application.Calculation = Calculation
    Application.ScreenUpdating = ScreenUpdating
End Function

This would then be used like so:

Set rngResultRange = FindValueInRange(rngSearchRange, searchTerm)
Chronocidal
  • 6,827
  • 1
  • 12
  • 26
  • thanks, although this is a good way to get rid of formulas, it does not solve the initial problem. – Albin Sep 18 '19 at 13:46
  • @Albin Because it gets rid of the Formulas, it can then use `LookAt:=xlFormulas` (which should work regardless of display width). Alternatively, you could just set `EntireColumn.ColumnWidth=255` to make the columns *really* wide? – Chronocidal Sep 18 '19 at 13:55
  • I don't think LookAt:=xlFormulas solved the problem, but I'll give it another try. But I think I prefer a solution with match since it's easier to implement and doesn't require as much processing time / memory (at least for this case/question) – Albin Sep 18 '19 at 14:06