0

Today I started studying Arrays in VBA.

After trying a few easy scripts I wanted to create one that is useful for my project.

In my excelsheet I have a datatable that needs to be transformed to new worksheets. Only for each column that has "Detail" in row 4.

The easiest way to imagine this would be by writing the values per relevant column to an array, reading and writing the results to a new sheet, and performing the action again.

But I think I'm using a wrong method to write the variables to my array. I looked through my code and all my declarions are not correct.

Could you help me out, how I can change the writing to the array correct?

Sub Import_data()

Dim LastCol As Integer
Dim LastRow As Long
Dim WS As Worksheet
Dim Arr() As Variant
Dim dim1 As Long, dim2 As Long

Set WS = Sheets("Budget to Table")



' Copy data from Budget to Table
    With WS
        LastCol = .Cells(1, .Columns.Count).End(xlToLeft).Column
        LastRow = .Cells(.Rows.Count, "B").End(xlUp).Row
        dim1 = .Cells(.Rows.Count, "B").End(xlUp).Row - 5
        dim2 = 4

    ' Copy information
        For i = 3 To LastCol
            If Cells(4, i).Value = "Detail" Then

                ReDim Arr(0 To dim1, 0 To dim2)

                    For dim1 = LBound(Arr, 1) To UBound(Arr, 1)
                        For dim2 = LBound(Arr, 2) To UBound(Arr, 2)
                            Arr(dim1, 0) = Range(Cells(dim1, 2)) 'Should have the variable length but always column B
                            Arr(dim1, 1) = Range(Cells(dim1, i)) 'Should have the variable length but always column i
                            Arr(dim1, 2) = Range(Cells(1, i)) 'Is always the same header info from row 1 of the chosen column
                            Arr(dim1, 3) = Range(Cells(2, i)) 'Is always the same header info from row 2 of the chosen column
                            Arr(dim1, 4) = Range(Cells(3, i)) 'Is always the same header info from row 3 of the chosen column
                        Next dim2
                    Next dim1

            End If

            'writing the contents in a new sheet
            Worksheet.Add
                For dim1 = LBound(Arr, 1) To UBound(Arr, 1)
                    For dim2 = LBound(Arr, 2) To UBound(Arr, 2)
                        ActiveCell.Offset(dim1, dim2).Value = Arr(dim1, dim2)
                    Next dim2
                Next dim1
            Erase Arr
        Next i
    End With

End Sub

If I need to provide any more guidance please let me know. I guess that the value of the dim1 and dim2 are never changing, so this doesn't create the loop i'm after.

edit: I uploaded the file here: https://dubblej15.stackstorage.com/s/C0DrKzFDxn4gY4U

I manually performed the action twice, what my result should look like. Maybe there is a better or easier way, but I thought that arrays could fit the job perfectly.

Thanks in advance!

6
  • I don't really understand, from your code and description, what you are trying to do. But the usual way to write a range to a VBA array would be something like: Dim arr as Variant: arr = myRange --> a 2D array. See Chip Pearsons web page on [Arrays and Ranges in VBA](VBA Arrays And Worksheet Ranges). And posting an annotated screenshot to show exactly what you are trying to do would be helpful. Commented Sep 16, 2017 at 20:02
  • And if the range you are transferring to an array is non-contiguous, then you might do something like Redim arr(1 to .Areas.Count): For each W in .Areas: I = I+1: arr(I) = W.Value2` Commented Sep 16, 2017 at 20:08
  • @RonRosenfeld I uploaded the file with the info (anomised it), i also manually performed the end result twice, so you can get an idea. If i need to provide anything else, please let me know and thnx for looking into it already! Commented Sep 16, 2017 at 21:00
  • 1
    You are likely referencing the wrong objects. You start off good by declaring WS, but later on start using the default properties on the omnipresent Application object. What I mean is in this statement: LastCol = .Cells(1, .Columns.Count).End(xlToLeft).Column, .Cells is from WS. However in this statement: If Cells(4, i).Value = "Detail" Then, Cells is from the Application object. Your use of Range is similarly suspect. What is Worksheet in Worksheet.Add? Normally, a Worksheet does not have an Add method. Commented Sep 16, 2017 at 21:58
  • To read an array use the vals = Range("A2").Resize(100,5).Value type of syntax, and to write one reverse it with Range("A2").Resize(100,5).Value=vals. Declare the array with Dim vals() as Variant and make sure the sizes are like ReDim vals(1 to 100, 1 to 5) with 1 based indexing. Commented Sep 17, 2017 at 3:15

2 Answers 2

3

There are a few issues with your code (do watch out for those unqualified Ranges), but the main one is that you're getting your array indexes mixed up with the cell row and column references, and as you point out, there are a few pieces of redundant code where you dimension your array. Redim Preserve is also limited when you use multi-dimensional arrays.

So, immediately below is a modified version of your code which shows the required adjustments.

However, if you want to work with arrays, then you can be much more efficient. For example, you can read ranges into arrays and write from arrays to ranges in just one line of code (which is way faster than using loops). The second piece of code shows you a more efficient way of handling the task - I wasn't sure if your sample rows will all have "Details' in column "A", because if they do without interruption, then the code could be even shorter.

Your modified code:

Dim dataWs As Worksheet, newWs As Worksheet
Dim lastRow As Long, lastCol As Long
Dim c As Long, r As Long, i As Long, j As Long
Dim arr() As Variant

'Read the data into an array
Set dataWs = ThisWorkbook.Worksheets("Budget to Table")
With dataWs
    lastCol = .Cells(1, .Columns.Count).End(xlToLeft).Column
    lastRow = .Cells(.Rows.Count, "B").End(xlUp).Row
End With

'Loop through each of the data columns.
For c = 3 To lastCol
    If Not IsEmpty(dataWs.Cells(3, c)) Then 'looks lik you only want the yellow columns.
        'Dimension the array for number of rows
        ReDim arr(1 To lastRow - 4, 1 To 5)
        'Loop through each row in data array and transfer it.
        With dataWs
            For r = 5 To lastRow
                arr(r - 4, 1) = .Cells(r, 2).Value
                arr(r - 4, 2) = .Cells(r, c).Value
                arr(r - 4, 3) = .Cells(1, c).Value
                arr(r - 4, 4) = .Cells(2, c).Value
                arr(r - 4, 5) = .Cells(3, c).Value
            Next
        End With
        'Create a new sheet.
        With ThisWorkbook.Worksheets
            Set newWs = .Add(After:=.Item(.Count))
            newWs.Name = arr(1, 5) 'name it for ease of use.
        End With
        'Write array onto the new sheet - the inefficient way
        For i = 1 To UBound(arr, 1)
            For j = 1 To UBound(arr, 2)
                newWs.Cells(i, j).Value = arr(i, j)
            Next
        Next
    End If
Next

A different way of handling arrays:

Dim ws As Worksheet
Dim data As Variant, output() As Variant
Dim rowList As Collection
Dim c As Long, i As Long
Dim r As Variant

'Read the data into an array
With ThisWorkbook.Worksheets("Budget to Table")
    data = .Range(.Range("A1"), _
           .Range(.Cells(1, .Columns.Count).End(xlToLeft), _
                  .Cells(.Rows.Count, "B").End(xlUp))) _
           .Value2
End With

'Find the first dimension indexes with "Detail" in column A.
'We'll create a collection of our target row numbers.
Set rowList = New Collection
For i = 1 To UBound(data, 1)
    If data(i, 1) = "Detail" Then rowList.Add i
Next

'Loop through each of the data columns.
For c = 3 To UBound(data, 2)
    If Not IsEmpty(data(3, c)) Then 'looks lik you only want the yellow columns.
        'Dimension the array for number of rows
        ReDim output(1 To rowList.Count, 1 To 5)
        i = 1 'row index for output array
        'Loop through each row in data array and transfer it.
        For Each r In rowList
            output(i, 1) = data(r, 2)
            output(i, 2) = data(r, c)
            output(i, 3) = data(1, c)
            output(i, 4) = data(2, c)
            output(i, 5) = data(3, c)
            i = i + 1
        Next
        'Create a new sheet.
        With ThisWorkbook.Worksheets
            Set ws = .Add(After:=.Item(.Count))
            ws.Name = output(1, 5) 'name it for ease of use.
        End With
        'Write array onto the new sheet.
        ws.Range("A1").Resize(UBound(output, 1), UBound(output, 2)).Value = output
    End If
Next
Sign up to request clarification or add additional context in comments.

1 Comment

thank you for the explanation, i see where i went wrong. thank you for describing it so clearly!
3

Using a Dynamic variant array is more simple.

Sub Import_data()

Dim LastCol As Integer
Dim LastRow As Long
Dim WS As Worksheet
Dim Arr() As Variant, vDB As Variant
Dim i As Integer, j As Long, n As Long

Set WS = Sheets("Budget to Table")

' Copy data from Budget to Table
    With WS
        LastCol = .Cells(1, .Columns.Count).End(xlToLeft).Column
        LastRow = .Cells(.Rows.Count, "B").End(xlUp).Row
        vDB = .Range("a1", .Cells(LastRow, LastCol)) '<~~ get data to vDB variant array from range

    ' Copy information
        For i = 3 To LastCol
            n = 0
            If vDB(4, i) = "Detail" Then
                For j = 5 To UBound(vDB, 1)
                    n = n + 1
                    ReDim Preserve Arr(1 To 5, 1 To n) '<~set dynamic variant array which is to be transposed.
                    Arr(1, n) = vDB(j, 2)
                    Arr(2, n) = vDB(j, i)
                    Arr(3, n) = vDB(1, i)
                    Arr(4, n) = vDB(2, i)
                    Arr(5, n) = vDB(3, i)
                Next j
                'writing the contents in a new sheet
                Worksheets.Add after:=Sheets(Sheets.Count)
                Range("a1").Resize(n, 5) = WorksheetFunction.Transpose(Arr)
                ReDim Arr(1 To 5, 1 To 1)
            End If


        Next i
    End With

End Sub

1 Comment

thank you, code looks so clean and i now understand with the guidance of TnTinMn

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.