martes, 12 de febrero de 2019

El método FINDNEXT para realizar búsquedas contínuas.

Siguiendo con el tema iniciado en video 16 de mi canal (entrada actualizada en mes de Febrero 2019) para una búsqueda con el método FIND, vamos a tratar ahora de realizar una búsqueda continua, con más de un criterio.
Para ello haremos uso del método FINDNEXT.

Opción 1:
Partiremos de una tabla de varias columnas donde una de ellas será la del primer criterio (mes) y otra contendrá datos del segundo criterio (art.). La tarea será calcular el acumulado de registros que coincidan en MES y ART.

Si bien esto puede ser resuelto con un filtro y la función Subtotales, vamos a ver cómo sería la programación en VBA utilizando los métodos FIND para encontrar el primer elemento y FINDNEXT para continuar la búsqueda hasta cubrir toda la tabla.
En el Editor de macros insertaremos un botón (que lo asociaremos al botón 'Actualizar' de la hoja) y allí copiaremos el siguiente código.

     Sub busquedaRepetidos()
     'x Elsamatilde
     'busca el valor de F1 en todas las apariciones en hoja activa
     dato = Range("F1")
     'se inicia la búsqueda en col A de la hoja activa
     Set busco = Range("A:A").Find(dato, LookIn:=xlValues, lookat:=xlWhole)
     'si no se encuentra ninguna coincidencia se notifica y finaliza el proceso
     If busco Is Nothing Then MsgBox "Dato no encontrado": Exit Sub
     'guarda la 1er fila encontrada
     filx = busco.Row
     'inicia un bucle
     Do
         'compara la col B con criterio en G1
         If busco.Offset(0, 1) = [G1] Then
             totx = totx + busco.Offset(0, 2)
         End If
         'repite la búsqueda
         Set busco = Range("A:A").FindNext(busco)
     Loop While Not busco Is Nothing And busco.Row <> filx
     'terminó la búsqueda. Se coloca el acumulado en H1
     [H1] = totx
     End Sub

Opción 2:
También podríamos utilizar método FINDNEXT para obtener el acumulado de todo enero, sin uso del segundo criterio. Lo único que se quitaría en este caso es la comparación con la celda G1.
La macro en la parte del bucle DO....LOOP WHILE nos quedará del siguiente modo:

     'inicia un bucle
     Do
         totx = totx + busco.Offset(0, 2)
         'repite la búsqueda
         Set busco = Range("A:A").FindNext(busco)
     Loop While Not busco Is Nothing And busco.Row <> filx


Descargar libro de ejemplo desde aquí.

VIDEO N° 23

Ver más macros con bucles en nuevo manual Bucles en Excel.

Búsquedas con VBA - Método FIND

Entre las funciones Excel quizás la estrella de todas es BUSCARV, la que nos permite buscar un dato y devolver el contenido de otra columna de la fila encontrada.

En VBA podría decir que la estrella es FIND , que nos permite buscar un dato y obtener su ubicación para luego movernos o devolver otros valores en relación a la referencia encontrada.

La instrucción con todos sus argumentos es:
Find (What, After, LookIn, LookAt, SearchOrder, SearchDirection, MatchCase, MatchByte, SearchFormat)

What: dato a buscar
After: a partir de dónde se realiza la búsqueda
Lookin: búsqueda entre valores (xlValues) o fórmulas (xlFomulas)
LookAt: coincidir con el contenido total (xlWhole) o parcial (xlPart) de la celda.
SearchOrder: realizar la búsqueda por filas (xlByRows) o por columnas (xlByColumns)
SearchDirection: búsqueda siguiente (xlNext) o anterior (xlPrevious).

Pero como hay varias sintaxis posibles vamos a ver aquellas que pueden llegar a fallar (ejemplos 1 a 4) y aquellas más recomendables (ejemplos 5 a 7)

Ejemplo 1: sin control de error en caso de dato no encontrado
       
      Cells.Find (qué se busca, a partir de dónde se busca, cómo se busca).Activate

Con Cells  le estamos indicando la hoja completa, no acotada a un rango en especial.
Con Activate o Select le estamos diciendo que cuando lo encuentre ya lo deje seleccionado.

Ejemplo 2:

       If j = 1 Then Set busca = ha.Find(numero)
      If j > 1 Then Set busca = ha.FindNext(busca)
      celda = busca.Address


Pero en cualquiera de los 2 casos, el código fallará si no encuentra el dato y por lo tanto no lo puede seleccionar o no puede obtener la dirección de la celda ya que no se encontró el dato a buscar.

Entonces habrá que colocar previamente una instrucción como la siguiente:
      On Error goto NoEncontrado
       Cells.Find (qué se busca, a partir de dónde se busca, cómo se busca).Activate

Y una entrada donde se instruya qué hacer en caso de no encontrar el dato.
      NoEncontrado:
           MsgBox "El dato buscado no se encuentra en la hoja activa."
           Exit sub

NOTA: también es posible utilizar On Error Resume Next  pero esto hará que siga con el resto de las instrucciones lo que en algún momento dará un resultado equivocado.

Ejemplo 3: acotando el rango de búsqueda pero a partir de celda seleccionada
   
      Range("A1:H1").Find(What:="marzo", After:=ActiveCell, SearchOrder:=xlByRows).Select

La misma instrucción puede escribirse del siguiente modo. En este caso habrá que colocar una coma por cada argumento que se omite.
     Range("A1:H1").Find("marzo", ActiveCell, , xlByRows).Select

Aquí le indicamos el rango y con Select le instruímos que seleccione la celda encontrada.
Pero con After le estamos indicando que lo busque a partir de la celda activa.
Y esto nos dará error si la celda activa se encuentra a continuación del dato buscado o en otras filas.

Ejemplo 4: no indicando orden o dirección de búsqueda cuando el dato puede encontrarse en distintas celdas.
En la siguiente imagen se observa que el texto 'marzo' se encuentra en 2 lugares.

Para que no nos devuelva un dato erróneo debemos instruirle a Excel en qué dirección buscarlo con el argumento SearchOrder. En este ejemplo le indicamos que lo busque por columnas por lo que devolverá el de la celda A3:
        Cells.Find("marzo", , , SearchOrder:=xlByColumns).Select

Ejemplo 5: 
El método de búsqueda guarda los argumentos LookIn, LookAt y SearchOrder de la última búsqueda efectuada. Por lo tanto si no recordamos cuáles fueron esos argumentos utilizados debemos declararlos en nuestra instrucción para no correr riesgos de obtener resultados erróneos.
Una instrucción segura debiera incluir entonces:

     Range("A1:D8").Find("ma", Lookin:=xlvalues, lookAt:=xlPart, SearchOrder:=xlbyRows)



LookAt:=xlPart indica que el texto buscado debiera estar incluido en el contenido de la celda.
LookAt:=xlWhole indica que el texto debe coincidir con el contenido de la celda


Ejemplo 6: 
Al realizar búsquedas dentro de fórmulas, los argumentos que debemos incluir son:
LookIn:=xlFormulas
LookAt:=xlPart


Ejemplo 7: utilizando una variable declarada con SET
Este método puede ser el más preciso considerando la declaración de argumentos según cada caso.
Aquí buscamos en un rango de otra hoja, por columnas, un texto parcial y luego ejecutamos varias acciones con la celda encontrada. Si no se encuentra coincidencias se envía un mensaje.
     Sub buscando()
     Set ho2 = Sheets("Hoja2")
     Set busco = ho2.Range("A1:D8").Find("ma", LookIn:=xlValues, lookAt:=xlPart,     SearchOrder:=xlByColumns)
     If Not busco Is Nothing Then
         'podemos obtener todas las referencias del dato encontrado sin necesidad de seleccionarlo
         filx = busco.Row
         colx = busco.Column
        'traer un rango a partir de la celda encontrada
         rgo = Range(Cells(filx, colx), Cells(filx, colx + 2)).Address
         ho2.Range(rgo).Copy Destination:=ActiveCell
         'colorear celda encontrada
         busco.Interior.ColorIndex = 3
     Else
         MsgBox "No se encontró el dato buscado en la Hoja 2"
     End If
     End Sub

NOTA: Este método no requiere que se seleccione la hoja para obtener las referencias de la celda encontrada ni para realizar alguna tarea allí, como copiarla, darle color, obtener un rango a partir de la celda encontrada y tantas otras tareas.

Descargar ejemplo desde aquí.

ver VIDEO N° 16.