My macro goes through a range, looping by columns, finds where the numeric data starts in each column and stores the ranges in a jagged array (the "matrix" variant in the code).
After that, I would like to return the entire matrix to a range in another worksheet. If I try to assign "matrix(1)" to the range where I want it to be put, it works fine, but if I try to assign the entire "matrix" to a range, I get blank cells.
How could I return all of the values in "matrix" to a range at once, without using loops?
This is the source data, through which the code loops:
I would like that all of the rows of "matrix" would be returned as this:
Here is my code:
Sub MyMatrix()
Dim wb1 As Workbook
Set wb1 = ActiveWorkbook
Dim wsNSA As Worksheet
Set wsNSA = wb1.Worksheets("NSA")
Dim wsSA As Worksheet
Set wsSA = wb1.Worksheets("SA")
Dim col As Range
Dim matrix() As Variant
'LR is the Last row and LC is the last column with data
LR = wsNSA.Cells(1, 1).End(xlDown).Row
LC = wsNSA.Cells(LR, 1).End(xlToRight).Column
'Loops through columns and finds the row where numeric data begins
For Each col In wsNSA.Range(wsNSA.Cells(1, 2), wsNSA.Cells(LR, LC)).Columns
wsNSA.Activate
nsa = wsNSA.Range(wsNSA.Cells(1, col.Column), wsNSA.Cells(LR, col.Column))
num_linha = Application.Match(True, Application.Index(Application.IsNumber(nsa), 0), 0)
nsa = wsNSA.Range(wsNSA.Cells(num_linha, col.Column), wsNSA.Cells(LR, col.Column))
'The range starts in the column B in the worksheet, so the matrix ubound is 'col.column -1
ReDim Preserve matrix(1 To col.Column - 1)
matrix(col.Column - 1) = nsa
Next
wsSA.Range(wsSA.Cells(3, 2), wsSA.Cells(LR, LC)) = matrix
End Sub
You can just copy all and delete the blank cells after:
Sheet1.Range("A3").CurrentRegion.Copy Destination:= Sheet2.Range("A3")
Sheet2.Range("A3").CurrentRegion.SpecialCells(xlCellTypeBlanks).Delete xlShiftUp
If you are you willing to forget the requirement that the output should not be written inside a loop, the following code would probably do what you are trying to do:
Sub MyMatrix()
Dim wb1 As Workbook
Set wb1 = ActiveWorkbook
Dim wsNSA As Worksheet
Set wsNSA = wb1.Worksheets("NSA")
Dim wsSA As Worksheet
Set wsSA = wb1.Worksheets("SA")
Dim c As Long
Dim LC As Long
Dim LR As Long
Dim num_linha As Long
Dim nsa As Variant
With wsNSA
'LR is the Last row and LC is the last column with data
'???? Is data1_linha declared anywhere and assigned a value? ????
LR = .Cells(data1_linha, 1).End(xlDown).Row
LC = .Cells(LR, 1).End(xlToRight).Column
'Loops through columns and finds the row where numeric data begins
For c = 2 To LC
nsa = .Range(.Cells(1, c), .Cells(LR, c))
num_linha = Application.Match(True, Application.Index(Application.IsNumber(nsa), 0), 0)
wsSA.Cells(3, c).Resize(LR - num_linha + 1, 1).Value = .Range(.Cells(num_linha, c), .Cells(LR, c)).Value
Next
End With
End Sub
Related
Hope you you can help me here. I have a repetitive task every week, which I could do the same way every single time through Excel formulas, but I am looking for a more automated way of going about this.
What I want to achieve is to set-up a dynamic range that will look for multiple key words such as in this case "OA" & "SNC" and if it matches it will return the value in the column G & H respectively. At the same time it has to skip blank rows. What is the best way to go about this?
I figured it shouldn't be too hard, but I cannot figure it out.
As per image above, I want to consolidate the charges per category (OA & SNC) in the designated columns ("G" & "H") on row level.
My approach to the task
Procedure finds data range, loops through it's values, adding unique values to the dictionary with sum for specific row and then loads all these values along with sums per row.
Option Explicit
Sub CountStuff()
Dim wb As Workbook, ws As Worksheet
Dim lColumn As Long, lRow As Long, lColTotal As Long
Dim i As Long, j As Long
Dim rngData As Range, iCell As Range
Dim dictVal As Object
Dim vArr(), vArrSub(), vArrEmpt()
'Your workbook
Set wb = ThisWorkbook
'Set wb = Workbooks("Workbook1")
'Your worksheet
Set ws = ActiveSheet
'Set ws = wb.Worksheets("Sheet1")
'Number of the first data range column
lColumn = ws.Rows(1).Find("1", , xlValues, xlWhole).Column
'Number of the last row of data range
lRow = ws.Cells(ws.Rows.Count, lColumn).End(xlUp).Row
'Total number of data range columns
lColTotal = ws.Cells(1, lColumn).End(xlToRight).Column - lColumn + 1
'Data range itself
Set rngData = ws.Cells(1, lColumn).Resize(lRow, lColTotal)
'Creating a dictionary
Set dictVal = CreateObject("Scripting.Dictionary")
'Data values -> array
vArr = rngData.Offset(1, 0).Resize(rngData.Rows.Count - 1, _
rngData.Columns.Count).Value
'Empty array
ReDim vArrEmpt(1 To UBound(vArr, 1))
'Loop through all values
For i = LBound(vArr, 1) To UBound(vArr, 1)
For j = LBound(vArr, 2) To UBound(vArr, 2)
'Value is not numeric and is not in dictionary
If Not IsNumeric(vArr(i, j)) And _
Not dictVal.Exists(vArr(i, j)) Then
'Add value to dictionary
dictVal.Add vArr(i, j), vArrEmpt
vArrSub = dictVal(vArr(i, j))
vArrSub(i) = vArr(i, j - 1)
dictVal(vArr(i, j)) = vArrSub
'Value is not numeric but already exists
ElseIf dictVal.Exists(vArr(i, j)) Then
vArrSub = dictVal(vArr(i, j))
vArrSub(i) = vArrSub(i) + vArr(i, j - 1)
dictVal(vArr(i, j)) = vArrSub
End If
Next j
Next i
'Define new range for results
Set rngData = ws.Cells(1, lColumn + lColTotal - 1). _
Offset(0, 2).Resize(1, dictVal.Count)
'Load results
rngData.Value = dictVal.Keys
For Each iCell In rngData.Cells
iCell.Offset(1, 0).Resize(lRow - 1).Value _
= Application.Transpose(dictVal(iCell.Value))
Next
End Sub
I've used a simple custom function, possibly overkill as this could be done with worksheet formulae, but given that your ranges can vary in either direction...
Function altsum(r As Range, v As Variant) As Variant
Dim c As Long
For c = 2 To r.Columns.Count Step 2
If r.Cells(c) = v Then altsum = altsum + r.Cells(c - 1)
Next c
If altsum = 0 Then altsum = vbNullString
End Function
Example below, copy and formula in F2 across and down (or apply it one go with another bit of code).
I have 10 records in excel of which i have edited 3rd and 7th records and placing a flag/string "modified" in certain column belongs to same rows to filter while processing
Below is the code that i am working with which is fetching only the first record(3rd) and not the 7th record into array using VBA
Dim RecordsArray() As Variant
Set sht = ThisWorkbook.Sheets("RMData")
sht.Range("M1:M100").AutoFilter Field:=1, Criteria1:="Modified"
sht.Range("A2:A100").Rows.SpecialCells (xlCellTypeVisible)
col = [a2].CurrentRegion.Columns.count
lw = [a2].End(xlDown).Row
RecordsArray = Range(Cells(2, 1), Cells(lw,col)).SpecialCells(xlCellTypeVisible)
Idea is I want to get those two records without looping and searching for
"Modified" string for the edited row
When reading a Filtered Range, most likely there will be splits ranges, the rows will not be continuous, so you need to loop through the Areas of the Filtered Range.
Also, you might have a few Rows in each Area, so you should loop through the Area.Rows.
More detailed comments in my code below.
Code
Option Explicit
Sub Populated2DArrayfromFilterRange()
Dim RecordsArray() As Variant
Dim sht As Worksheet
Dim col As Long, lw As Long, i As Long
Dim FiltRng As Range, myArea As Range, myRow As Range
ReDim RecordsArray(0 To 1000) ' redim size of array to high number >> will optimize later
' set the worksheet object
Set sht = ThisWorkbook.Sheets("RMData")
i = 0 ' reset array element index
' use With statement to fully qualify all Range and Cells objects nested inside
With sht
.Range("M1:M100").AutoFilter Field:=1, Criteria1:="Modified"
.Range("A2:A100").Rows.SpecialCells (xlCellTypeVisible)
col = .Range("A2").CurrentRegion.Columns.Count
lw = .Range("A2").End(xlDown).Row
' set the filtered range
Set FiltRng = .Range(.Cells(2, 1), .Cells(lw, col)).SpecialCells(xlCellTypeVisible)
' Debug.Print FiltRng.Address(0, 0)
For Each myArea In FiltRng.Areas ' <-- loop through areas
For Each myRow In myArea.Rows ' <-- loop through rows in area
RecordsArray(i) = Application.Transpose(Application.Transpose(myRow))
i = i + 1 ' raise array index by 1
Next myRow
Next myArea
ReDim Preserve RecordsArray(0 To i - 1) ' optimize array size to actual populated size
End With
End Sub
If you have a hidden row in the middle, then .SpecialCells(xlCellTypeVisible) will return multiple Areas. Assigning a range to an Array only assigns the first Area. (At also always makes the array 2D)
Instead of looping & searching for "Modified", you could just loop For Each cell in the SpecialCells range and assign that to the array instead - if you plan was "no loops at all" then this is not what you want. (But, I would then have to ask you "why not?"!)
Dim RecordsArray() As Variant, rFiltered As Range, rCell As Range, lCount As Long
Set sht = ThisWorkbook.Sheets("RMData")
sht.Range("M1:M100").AutoFilter Field:=1, Criteria1:="Modified"
sht.Range("A2:A100").Rows.SpecialCells (xlCellTypeVisible)
col = [a2].CurrentRegion.Columns.Count 'This will act on ActiveSheet, not sht - is that intended?
lw = [a2].End(xlDown).Row 'In case of gaps, would "lw=sht.Cells(sht.Rows.Count,1).End(xlUp).Row" be better?
'RecordsArray = Range(Cells(2, 1), Cells(lw, col)).SpecialCells(xlCellTypeVisible)
Set rFiltered = Range(Cells(2, 1), Cells(lw, col)).SpecialCells(xlCellTypeVisible)
ReDim RecordsArray(1 To rFiltered.Cells.Count, 1) 'Mimic default assignment
lCount = 1
For Each rCell In rFiltered
RecordsArray(lCount, 1) = rCell.Value
lCount = lCount + 1
Next rTMP
Set rCell = Nothing
Set rFiltered = Nothing
If you want to avoid dealing with the visible areas mentioned already, you can try something like this
Option Explicit
Public Sub CopyVisibleToArray()
Dim recordsArray As Variant, ws As Worksheet, nextAvailable As Range
Set ws = ThisWorkbook.Worksheets("RMData")
Set nextAvailable = ws.Cells(ws.Rows.Count, "A").End(xlUp).Offset(2)
With ws.Range("M1:M100")
Application.ScreenUpdating = False
.AutoFilter Field:=1, Criteria1:="Modified"
If .Rows.SpecialCells(xlCellTypeVisible).Cells.Count > 1 Then
'copy - paste visibles in col A, under all data
ws.UsedRange.Columns("A:M").SpecialCells(xlCellTypeVisible).Copy nextAvailable
Set nextAvailable = nextAvailable.Offset(1)
nextAvailable.Offset(-1).EntireRow.Delete 'Delete the (visible) header
recordsArray = nextAvailable.CurrentRegion 'Get the cells as array
nextAvailable.CurrentRegion.EntireRow.Delete 'Delete the temporary range
End If
.AutoFilter
Application.ScreenUpdating = True
End With
End Sub
To copy just column A to array use this: ws.UsedRange.Columns("A")
To copy columns A to M use this: ws.UsedRange.Columns("A:M")
I would like to apply filter on a table based the values presented in cells A1:AG1. But the problem is when my data gets updated, sometimes i have values in other cells like AH,AI etc., The values available only on first row.
So i tried to add a loop for every cell, but it is not working. How to change my code to loop through every column in a single row. Help me
Dim ws As Worksheet
Dim str2 As Variant
Dim arr2() As String
Dim j As Long
Dim rng As Range
Set ws = Sheets("Main")
Set Tbl = Sheet2.ListObjects("DataTable")
Set rng = Range("A1:AG1") 'Need to change
j = 1
For Each cell In rng
str2 = cell.Value
ReDim Preserve arr2(j)
arr2(j) = str2
j = j + 1
Next cell
Tbl.Range.AutoFilter Field:=12, Criteria1:=arr2, Operator:=xlFilterValues
End sub
How about something like below:
Dim ws As Worksheet
Dim ws2 As Worksheet
Dim arr2() As String
Dim i As Long
Set ws = Sheets("Main")
Set ws2 = Sheets("Sheet2")
Set Tbl = ws2.ListObjects("DataTable")
LastCol = ws2.Cells(1, ws2.Columns.Count).End(xlToLeft).Column
'above will give you the last column number in row one of sheet ws2
ReDim Preserve arr2(1 To LastCol) 're-size the array
'Set rng = Range("A1:A" & LastCol) 'set your range from column 1 to last
For i = 1 To LastCol 'loop through columns
arr2(i) = ws2.Cells(1, i).Value 'add value to array
'above number 1 represents Row 1, and i will loop through columns
Next i
Tbl.Range.AutoFilter Field:=12, Criteria1:=arr2, Operator:=xlFilterValues
'above will filter table column 12 with array values?
I do not know if I understood your question well, but just replace:
Set rng = Range("A1:AG1")
With:
Set rng = ws.range(ws.cells(1 , 1) , ws.cells(1 , ws.Cells.Columns.Count).End(xlToLeft).Column
I am working on a macro to copy a varied number of cells to a row, transpose and paste into a different sheet, in the next empty cell in a column. Then the idea is to match each transposed item with the ID from the row it originated from. The number of rows in the ID column will vary as well.
Looking at the example below, ID 1 is associated with Co D and Co R. Transposing would create the need for ID 1 to be copied into the two cells adjacent to the destination. This example I created has them on the same sheet, but for the code itself it will be on a different sheet.
The problem appears in copying the range to be transposed. I can't seem to figure out how to grab the whole row. The macro correctly pastes the value in the next available cell in the destination, but the version of the code I have now only copies the last result in the row, and not the whole row which is my intent. I haven't even gotten to the part of matching the ID to the Co in the Destination column, but I am dreading it already. The code I have is as follows;
Sub Testing()
Dim TearS As Worksheet: Set TearS = Worksheets(1)
Dim FeeS As Worksheet: Set FeeS = Worksheets(2)
Dim EntryS As Worksheet: Set EntryS = Worksheets(3)
Dim Stage2 As Worksheet: Set Stage2 = Worksheets(4)
Dim Stage3 As Worksheet: Set Stage3 = Worksheets(5)
Dim Bbg As Range: Set Bbg = EntryS.Range("F4:T199")
Dim TDest As Range: Set TDest = Stage2.Range("F5:T200")
Dim DateA As Range: Set DateA = Stage2.Range("G5:G200")
Dim DateB As Range: Set DateB = TearS.Range("E5:E200")
Dim DesA As Range: Set DesA = Stage2.Range("J5:J200")
Dim DesB As Range: Set DesB = TearS.Range("O5:O200")
Dim DesC As Range: Set DesC = Stage3.Range("C5:C200")
Dim CpnMatA As Range: Set CpnMatA = Stage2.Range("Y5:Y200")
Dim CpnMatB As Range: Set CpnMatB = TearS.Range("P5:P500")
Dim SettA As Range: Set SettA = Stage2.Range("I5:I200")
Dim SettB As Range: Set SettB = TearS.Range("Q5:Q200")
Dim MinA As Range: Set MinA = Stage2.Range("AA5:AA200")
Dim MinB As Range: Set MinB = Stage3.Range("D5:D200")
Dim MWOB As Range: Set MWOB = TearS.Range("N5:N200")
Dim Cel As Range
For Each Cel In DesC
If IsEmpty(Cel) = False Then
Cel.Offset(0, 1).End(xlToRight).Copy
TearS.Range("N3").End(xlDown).Offset(1).PasteSpecial Paste:=xlPasteAll, _
Operation:=xlNone, SkipBlanks:=False, Transpose:=True
End If
Next Cel
End Sub
Edit: Jeeped's solution that you can see in the answer below works swimmingly. Make sure that there are no errors in the source data, or you may get a run-time error 13.
Try transposing within a 2-D array before passing the values back to the worksheet.
Sub rewrite()
Dim lr As Long, a As Long, b As Long, val As Variant, vals As Variant
With Worksheets("sheet6")
.Range("F:G").Clear
lr = Application.Max(.Cells(.Rows.Count, "B").End(xlUp).Row, _
.Cells(.Rows.Count, "C").End(xlUp).Row, _
.Cells(.Rows.Count, "D").End(xlUp).Row, _
.Cells(.Rows.Count, "E").End(xlUp).Row)
vals = .Range(.Cells(2, "A"), .Cells(lr, "E")).Value2
For a = LBound(vals, 1) To UBound(vals, 1)
ReDim val(1 To UBound(vals, 2), 1 To 2)
For b = LBound(val, 1) To UBound(val, 1) - 1
If CBool(Len(vals(a, b + 1))) Then
val(b, 1) = vals(a, 1)
val(b, 2) = vals(a, b + 1)
End If
Next b
.Cells(.Rows.Count, "F").End(xlUp).Offset(1, 0).Resize(UBound(val, 1), UBound(val, 2)) = val
Next a
End With
End Sub
I have got a macro that needs to loop according to the number of columns that exist in the Worksheet "NSA" (not counting the dates), as in the image below:
Looping through the columns, the macro needs to fill the corresponding range in the worksheet "SA" with a random number, one column at a time.
I want to fill one column of "SA" for each time the loop occurs in "NSA", as to keep different numbers in B:B and C:C.
Thus, in the first time the code runs, I would like to insert data only in column B and, in the second time, fill only the column C.
That's where my code fails. It always fills both columns B and C in the worksheet "SA" at the same time, each time it runs. This is what I get (for a random value):
How could I change the loop so the columns in "SA" change only one at a time, according to the loop in "NSA"?
Thanks for the help.
Here is my code:
Sub Dessaz2()
Dim wb1 As Workbook
Set wb1 = ActiveWorkbook
Dim wsNSA As Worksheet
Set wsNSA = wb1.Worksheets("NSA")
Dim wsSA As Worksheet
Set wsSA = wb1.Worksheets("SA")
Dim col1 As Range
Dim col2 As Range
LR = wsNSA.Cells(3, 1).End(xlDown).Row
LC = wsNSA.Cells(3, 1).End(xlToRight).Column
For Each col1 In wsNSA.Range(Cells(3, 2), Cells(LR, LC)).Columns
wsNSA.Activate
wsSA.Activate
x = WorksheetFunction.RandBetween(0, 100)
wsSA.Range(Cells(3, 2), Cells(LR, LC)) = x
Next
End Sub
Fill col1.column each time :
Sub Dessaz2()
Dim wb1 As Workbook
Set wb1 = ActiveWorkbook
Dim wsNSA As Worksheet
Set wsNSA = wb1.Worksheets("NSA")
Dim wsSA As Worksheet
Set wsSA = wb1.Worksheets("SA")
Dim col1 As Range
Dim col2 As Range
LR = wsNSA.Cells(3, 1).End(xlDown).Row
LC = wsNSA.Cells(3, 1).End(xlToRight).Column
For Each col1 In wsNSA.Range(wsNSA.Cells(3, 2), wsNSA.Cells(LR, LC)).Columns
wsNSA.Activate
wsSA.Activate
x = WorksheetFunction.RandBetween(0, 100)
wsSA.Range(wsSA.Cells(3, col1.Column), wsSA.Cells(LR, col1.Column)) = x
Next
End Sub
Sub Main()
Dim rng as Range, cl as Range
Set rng = worksheets("NSA").Range("B1:C100") // update for your Range
For each cl in rng
Worksheets("SA").Range(cl.Address) = WorksheetFunction.RandBetween(0, 100)
Next cl
End Sub