martes, 10 de mayo de 2022

VBA: Cargando datos en Arrays

Repasaremos algunos conceptos fundamentales para trabajar con VBA como puede ser la carga de datos en una Array.
No vamos a descubrir ahora a nadie las bondades de trabajar con Arrays en nuestros códigos... la rapidez y agilidad a la hora de trasladar o sencillamente trabajar con datos cargados es simplemente 'brutal'.
Imagina una tabla de datos con 51 campos y 240.000 registros... son muchos datos... de hecho el fichero pesa alrededor de 60 Mb (mucho para un fichero solo con datos y cero cálculo).

Veamos algunos de los procesos habituales de carga de datos desde nuestra celdas a una Array.
Accedemos a un módulo estándar del editor de VBA y escribimos:
Sub OptimizandoCargaDatos()

Start = Timer   'inicia el contador de tiempo
Dim Tbl As ListObject
Set Tbl = ActiveSheet.ListObjects(1)

''A)  bien cargamos la matriz directamente
arrDatos1 = Tbl.DataBodyRange.Value

finA = Round(Timer - Start, 3)  'contabilizamos tiempo de carga
Start = Timer   'inicia el contador de tiempo
'
''B)) o alternativamente a través de una función...
'dentro de nuestro módulo estándar
arrDatos2 = CargaArrays

finB = Round(Timer - Start, 3)  'contabilizamos tiempo de carga
Start = Timer 'inicia el contador de tiempo
'
''C) o desde un módulo de clase
Dim arrClase As New clsCargaArrays
arrDatos3 = arrClase.CargaMatrices

finC = Round(Timer - Start, 3)  'contabilizamos tiempo de carga
Start = Timer   'inicia el contador de tiempo

''D) cargándolo usando Properties desde el módulo de clase
Dim arrMatriz As New Clase1
arrDatos5 = arrMatriz.TodaMatriz

Fin_D = Round(Timer - Start, 3) 'contabilizamos tiempo de carga
Start = Timer   'inicia el contador de tiempo

''Tip/bonus: Seleccionar ciertas columnas de la Matriz
Set arrTest = New Clase1
arrDatos1_3 = arrTest.SelectCols(Array(1, 3))

fin_E = Round(Timer - Start, 3) 'contabilizamos tiempo de carga

Debug.Print "A (carga directa clásica): " & finA & vbCrLf & _
"B (a través de UDF): " & finB & vbCrLf & _
"C (1-módulo clase - funcion): " & finC & vbCrLf & _
"D (2-modulo clase Property Get: " & Fin_D & vbCrLf & _
"E (Bonus: Seleccionar columnas de la Array): " & fin_E & vbCrLf & "___________________"
Set Tbl = Nothing
Set arrTest = Nothing
End Sub
''''''''''''''
Function CargaArrays() As Variant
    CargaArrays = ActiveSheet.ListObjects(1).DataBodyRange.Value
End Function


Por otra parte crearemos dos módulos de clase.
El primero llamado 'Clase1' que contiene los siguientos procesos:
Dim m_Array As Range
'--------------------------------------------
Private Sub Class_Initialize()
    Set m_Array = ActiveSheet.ListObjects(1).DataBodyRange
End Sub
'--------------------------------------------
Private Sub Class_Terminate()
    Set m_Array = Nothing
End Sub
'--------------------------------------------
Property Get ColVector(n As Long) As Variant
    'funciona y devuelve el vector completo
    ColVector = Empty
    
    On Error GoTo Err_Handler
    ColVector = m_Array.Columns(n)
    Exit Property
    
Err_Handler:
End Property
'--------------------------------------------
Property Get SelectCols(nCols() As Variant) As Variant
    SelectCols = Empty
    
    On Error GoTo Err_Handler
    SelectCols = Application.Index(m_Array, Evaluate("Row(1:" & m_Array.Rows.Count & ")"), nCols)
    Exit Property
    
Err_Handler:
End Property
'--------------------------------------------
Property Get TodaMatriz() As Variant
    TodaMatriz = Empty

    On Error GoTo Err_Handler
    TodaMatriz = m_Array
    Exit Property

Err_Handler:
End Property

En este módulo de clase creamos un 'objeto matriz' cargando datos de la hoja de cálculo con un procedimiento 'Sub Class_Initializa', el cual carga el objeto m_Array con los datos de la tabla cuando desde un módulo estandar, por ejemplo, se llame a ese 'objeto' Clase1.
Además he generado distintas propiedades del objeto que permiten obtener un vector, un conjunto de vectores o simplemente la totalidad de la matriz, a partir de la Array cargada.

Necesitamos un módulo de clase más... llamado 'clsCargaArrays', con un simple procedimiento Function:
Function CargaMatrices() As Variant
    CargaMatrices = ActiveSheet.ListObjects(1).DataBodyRange.Value
 End Function


Si lanzamos varias veces nuestro procedimiento, y vemos el tiempo requerido por cada forma descrita, veremos lo siguiente:
VBA: Cargando datos en Arrays

Observamos tiempos de carga prácticamente idénticos, por lo que la decisión de optar por uno u otro es decisión personal...
Nota que estamos simplemente cargando 12.240.000 de celdas con datos, sin ningún otro procesamiento... y que una carga cualquiera de ese volumen de información neutra consume alrededor de 2,5 segundos!!! (asombroso!).

Siempre práctico tener alternativas de trabajo... en este claso incluyendo el trabajo (en varias formas) con Módulos de clase.

No hay comentarios:

Publicar un comentario

Nota: solo los miembros de este blog pueden publicar comentarios.