I am trying to pass a range of columns as a parameter for a function.
For sr = 1 To srCount
If IsEmpty(Data(sr, 4)) Then
dr = dr + 1
Data(dr, 4) = Data(sr, 1) 'instead of just the first column, a range of columns
End If
Next sr
I thought I could define a range ("A:C") and pass its reference as a parameter, but VBA doesn't seem to accept anything but (references to) long variables/constants as parameters for the Data() function. What is the correct syntax for such a thing?
Edited 01/26 for clarification: Data is an array. The goal is to copy a row of a range of columns based on the condition that another cell in that row is empty (If IsEmpty(Data(sr, 4))). E.g. if cell(7,4) is empty, then row 7 of columns A-C should be copied to another area of the worksheet (K2:M2). If (13,4) is empty, then row 13, columns A-C to K3-M3, and so on.
As per #Cameron's tip I used Collections to store the ranges instead.
Dim users As New Collection
Dim cell As Range
With Worksheets(2)
users.Add .Range("A:C"), "Users"
users.Add .Range("K:M"), "FinalList"
End With
For Each cell In users.Item("Users")
For sr = 1 to srCount
If IsEmpty(Data(sr, 4)) Then
dr = dr + 1
FinalList = Users
End If
Next sr
Next
Despite the research I can't find how I can manipulate Collections for this objective. Once I have all the necessary values stored in FinalList, how can I copy them to the goal Range ("K:M")?
Using a Single Array to Extract Matching Data
Option Explicit
Sub Test()
Const SRC_CRITERIA_COLUMN As Long = 4
Const DST_WORKSHEET_ID As Variant = 2 ' using the tab name is preferable!
Const DST_FIRST_CELL As String = "K2"
' The following could be calculated from the source variables.
Const DST_COLUMNS_COUNT As Long = 3
Dim wb As Workbook: Set wb = ThisWorkbook ' workbook containing this code
' whatever...
Dim Data() ' ?
Dim srCount As Long ' ?
' whatever...
Dim sr As Long, dr As Long, c As Long
' Write the matching values to the top of the array
' When using a single array, the result needs to be in the top-left
' of the array. The data of interest is already left-most
' so there is no column offset.
For sr = 1 To srCount
If IsEmpty(Data(sr, SRC_CRITERIA_COLUMN)) Then
dr = dr + 1
For c = 1 To DST_COLUMNS_COUNT
Data(dr, c) = Data(sr, c)
Next c
End If
Next sr
' Reference the destination range.
Dim dws As Worksheet: Set dws = wb.Worksheets(DST_WORKSHEET_ID)
Dim dfCell As Range: Set dfCell = dws.Range(DST_FIRST_CELL)
' Note how you're using just the 'dr' number of rows
' and 'DST_COLUMNS_COUNT' number of columns.
Dim drg As Range: Set drg = dfCell.Resize(dr, DST_COLUMNS_COUNT)
' Write the result from the top-left of the array to the destination range.
drg.Value = Data
' Clear below.
drg.Resize(dws.Rows.Count - drg.Row - dr + 1).Offset(dr).ClearContents
MsgBox "Data copied.", vbInformation
End Sub
Related
Problem: Our company receives a data set that summarizes invoices to be paid.
For each outstanding invoice, there is a single row of data.
Each invoice has a variable number of items to be paid and are listed on the same row.
Each item has four columns listed on the invoice row.
As a result, the number of columns per invoice can become unwieldy.
We need to upload this data with one row per item and it currently requires an accounting clerk to manually copy/paste each item to a new row.
Request: Please help me find a way to copy every item (four columns) and paste to a new row with the invoice listed first.
Attachments:
"RAW" Worksheet is the original data.
Columns A-D, highlighted in Gray are the invoice detail.
Columns J-M highlighted in Orange are the first item, Columns N-Q highlighted in Blue are the second item, etc.
"RAW" Screenshot
"Output" Worksheet is the desired outcome (currently done by manually copy/paste)
"Output" Screenshot
Link to Google Doc for data
Attempts:
I am a fairly inexperienced Excel user, but I tried a series of if/then, transpositions, pivots, and Offsets with no success.
I think that this problem requires a VBA that reviews each row and identifies
if there is a non-zero four column item. For each non-zero four column item, it will paste the invoice summary (columns A-D) and the non-zero item (ex. columns J-M) on a new row.
If there is a zero-value four column item, the VBA will move to the next row (invoice).
That is my best guess, and I haven't a clue how to script this VBA.
Thanks for any insight here!!
Transform Data (VBA)
Option Explicit
Sub TransformData()
' Define constants.
Const SRC_NAME As String = "RAW"
Const SRC_FIRST_CELL As String = "A3"
Const SRC_REPEAT_COLUMNS As Long = 9
Const SRC_CHANGE_COLUMNS As Long = 4
Const DST_NAME As String = "Output"
Const DST_FIRST_CELL As String = "A2"
Dim wb As Workbook: Set wb = ThisWorkbook ' workbook containing this code
' Reference the Source range.
Dim sws As Worksheet: Set sws = wb.Worksheets(SRC_NAME)
Dim sfCell As Range: Set sfCell = sws.Range(SRC_FIRST_CELL)
Dim srg As Range, srOffset As Long, srCount As Long, scCount As Long
With sws.UsedRange
scCount = .Columns.Count
srOffset = sfCell.Row - 1
srCount = .Rows.Count - srOffset
If srCount < 1 Then
MsgBox "No data in the Source worksheet.", vbExclamation
Exit Sub
End If
Set srg = .Resize(srCount).Offset(srOffset)
End With
' Write the values from the Source range to the Source array.
Dim sData() As Variant: sData = srg.Value
' Define the Destination array.
Dim scaCount As Long
scaCount = (scCount - SRC_REPEAT_COLUMNS) / SRC_CHANGE_COLUMNS
Dim drCount As Long: drCount = scaCount * scCount ' could be to many
Dim dcCount As Long: dcCount = SRC_REPEAT_COLUMNS + SRC_CHANGE_COLUMNS
Dim dData() As Variant: ReDim dData(1 To drCount, 1 To dcCount)
' Transform the data from the Source array
' into the Destination array.
Dim sr As Long, sc As Long, scFirst As Long, scLast As Long, sca As Long
Dim dr As Long, dc As Long
For sr = 1 To srCount
For sca = 1 To scaCount
' Determine the Source Change columns.
scFirst = 1 + SRC_REPEAT_COLUMNS + (sca - 1) * SRC_CHANGE_COLUMNS
scLast = scFirst + SRC_CHANGE_COLUMNS - 1
' Check if the Source Area is not blank.
For sc = scFirst To scLast
If Len(CStr(sData(sr, sc))) > 0 Then Exit For
Next sc
' Write the Source data.
If sc <= scLast Then ' Source Area is not blank
dr = dr + 1
For sc = 1 To SRC_REPEAT_COLUMNS
dData(dr, sc) = sData(sr, sc)
Next sc
dc = SRC_REPEAT_COLUMNS
For sc = scFirst To scLast
dc = dc + 1
dData(dr, dc) = sData(sr, sc)
Next sc
'Else ' Source Area is blank; do nothing
End If
Next sca
Next sr
If dr = 0 Then
MsgBox "No data found.", vbExclamation
Exit Sub
End If
Erase sData
' Reference the Destination range.
Dim dws As Worksheet: Set dws = wb.Worksheets(DST_NAME)
Dim dfCell As Range: Set dfCell = dws.Range(DST_FIRST_CELL)
Dim drg As Range: Set drg = dfCell.Resize(dr, dcCount)
' Write the values from the Destination array to the Destination range.
drg.Value = dData
drg.Resize(dws.Rows.Count - drg.Row - dr + 1).Offset(dr).Clear
' Inform.
MsgBox "Data transformed.", vbInformation
End Sub
Yes it can be done with formulas alone:
Starting Data:
Output Data:
...
First I start with 3 helper columns:
A) #s (this could be substituted for just ROW() but I found this easier. 1 to 1000 but feel free to continue at least 5 times larger than your largest expected data set.
B) Counts how may cells are not empty on the RAW sheet to the right of "Posting Status" Column
C) This is a bit less clear. the first cell (C2) must be just the number one, then each following cell, down to row 1000, has this formula:
=IF(COUNTIF($C$1:C2,C2)=INDEX(B:B,MATCH(C2,A:A,0)),C2+1,C2)
Next we start with repeating the General Dataset:
=IF($C2<INDEX($A:$A,MATCH(0,$B:$B,0)),INDEX(RAW!A:A,$C2+1),"")
(this formula is exactly the same through the entire blue section: D2:K1000 )
Now! the really fun part:
In the invoice column:
=IF($C3<INDEX($A:$A,MATCH(0,$B:$B,0)),OFFSET(RAW!$I$1,$C3,((COUNTIF($C$1:$C3,$C3)-1)*4),1,4),"")
Make sure everything is filled all the way down to row 1000 (or whatever your row of choice is) and bob's your Aunty!
To Note:
I'm assuming your column A (count) on the RAW sheet was added by you. If not you will either need to note copy it over, or adjust all the formulas to pull from one cell to the right.
Let me know if you have any troubles with it.
I have 3 named table : Table11, Table34, Table41.
The idea is that, any time I am inserting/adding a new table(lets say Table56), in the new table I need to add the data from previous inserted added table data: the values from columns Tax1, Tax2, Fees.
Sometimes the table might have additional columns and rows, which it shouldn't affect retrieving data from a previous table into a new table. Also, it might happen that the name of tables to be different, because the file is accessed by different users
The name of the table is added as a list in another worksheet when the table is created, for this I was able to create a small code using a template table (copy and paste).
So, is it possible to retrieve data in last named table from previous inserted named table?
Anyone can give me a helpful hand?
Update New Table With Data From Previously Added Table
Sub UpdateNewTable()
' Define constants.
Const wsName As String = "Report"
Const CriteriaHeader As String = "ID"
Const sTableHeadersList As String _
= "Wage,Tax1,Tax2,Fees"
Const dTableHeadersList As String _
= "Wage Previous,Tax1 Previous,Tax2 Previous,Fees Previous"
' Reference the objects.
Dim wb As Workbook: Set wb = ThisWorkbook
Dim ws As Worksheet: Set ws = wb.Worksheets(wsName)
Dim tblCount As Long: tblCount = ws.ListObjects.Count
If tblCount < 2 Then Exit Sub
Dim stbl As ListObject: Set stbl = ws.ListObjects(tblCount - 1)
Dim dtbl As ListObject: Set dtbl = ws.ListObjects(tblCount)
Dim srg As Range: Set srg = stbl.ListColumns(CriteriaHeader).DataBodyRange
Dim drg As Range: Set drg = dtbl.ListColumns(CriteriaHeader).DataBodyRange
' Read data.
' A 2D one-based one-column array with the same number of rows
' as the source (one-column) range, holding the destination (table)
' row indexes where the source values were found.
' There will be error values for not found values ('IsNumeric').
Dim drIndexes As Variant: drIndexes = Application.Match(srg, drg, 0)
Dim sHeaders() As String: sHeaders = Split(sTableHeadersList, ",")
Dim dHeaders() As String: dHeaders = Split(dTableHeadersList, ",")
Dim hUpper As Long: hUpper = UBound(sHeaders)
' Write (copy) data.
Dim sr As Long
Dim dr As Long
Dim hc As Long
For sr = 1 To UBound(drIndexes) ' or '1 To srg.Rows.Count'
If IsNumeric(drIndexes(sr, 1)) Then
dr = drIndexes(sr, 1)
For hc = 0 To hUpper
dtbl.ListColumns(dHeaders(hc)).DataBodyRange.Rows(dr).Value _
= stbl.ListColumns(sHeaders(hc)) _
.DataBodyRange.Rows(sr).Value
Next hc
End If
Next sr
' Inform.
MsgBox "New table updated.", vbInformation
End Sub
I have a set of data with Product name in rows and Customer names in columns. In input table (as attached snip), range B6:F20 will contain the order volume. In order to generate order in ERP, I need to build the output table (as attached snip) as shown. In output table range H5:J5 will remain constant and the table will be auto generated based on order volume of products for each customers in Input table.
I am trying to build any excel formula or VBA to build the output table.
Unpivot Data (No Duplicates)
This is a simplified version since there are no duplicates in the first column or the first row.
It will still work if you add more columns.
Option Explicit
Sub UnpivotDataCRV()
' 1.) Define constants.
Const wsName As String = "Sheet1"
Const sFirstCellAddress As String = "A5"
Const dcCount As Long = 3 ' fixed!
Const cGap As Long = 1 ' empty columns in-between (>0)
' 2.) Reference the worksheet ('ws').
Dim wb As Workbook: Set wb = ThisWorkbook ' workbook containing this code
Dim ws As Worksheet: Set ws = wb.Worksheets(wsName)
' 3.) Reference the source range ('srg')
' (and the destination first row range ('dfrrg')).
Dim srCount As Long
Dim scCount As Long
Dim srg As Range
Dim dfrrg As Range
With ws.Range(sFirstCellAddress)
' Assuming the range has at least two rows and its first column
' has no empty cells.
Dim slRow As Long: slRow = .End(xlDown).Row
' Assuming the range has at least two columns and its first (header) row
' has no empty cells.
Dim slCol As Long: slCol = .End(xlToRight).Column
srCount = slRow - .Row + 1
scCount = slCol - .Column + 1
Set srg = .Resize(srCount, scCount)
Set dfrrg = .Offset(1, scCount + cGap).Resize(, dcCount)
End With
' 4.) Write the values from the source range to the source array ('sData'),
' a 2D one-based array.
Dim sData() As Variant: sData = srg.Value
' 5.) Count the number of destination rows ('drCount').
Dim drCount As Long
With srg.Resize(srCount - 1, scCount - 1).Offset(1, 1)
' a) Count the cells containing a number.
drCount = Application.Count(.Cells)
' Or:
' b) Count the non-blank cells.
'drCount = srCount * scCount - Application.CountBlank(.Cells)
End With
If drCount = 0 Then Exit Sub ' no cell's meeting the criteria found
' 6.) Define the destination array ('dData'),
' a 2D one-based three-column array.
Dim dData() As Variant: ReDim dData(1 To drCount, 1 To dcCount)
' 7.) Write the values from the source array to the destination array.
Dim sr As Long
Dim sc As Long
Dim dr As Long
' Loop through the columns of the source array (skip row labels).
For sc = 2 To scCount
' Loop through the columns of the source array (skip column labels).
For sr = 2 To srCount
' a) Check if the current value in the source array is a number.
If VarType(sData(sr, sc)) = vbDouble Then ' is a number
' Or:
' b) Check if the current value in the source array is not blank.
'If Len(CStr(sData(sr, sc))) > 0 Then ' is not blank
dr = dr + 1
dData(dr, 1) = sData(1, sc) ' write column label (first row)
dData(dr, 2) = sData(sr, 1) ' write row label (first column)
dData(dr, 3) = sData(sr, sc) ' write value
'Else ' value doesn't meet the criteria; do nothing
End If
Next sr
Next sc
' 8.) Write the values from the destination array to the destination range.
With dfrrg
' Write.
.Resize(drCount).Value = dData
' Clear below.
.Resize(ws.Rows.Count - .Row - drCount + 1).Offset(drCount).Clear
End With
' 9.) Inform.
MsgBox "Data unpivoted.", vbInformation
End Sub
F2 has =INDEX($B$1:$D$1,MOD(ROW()-ROW(F$1)-1,COUNTA($B$1:$D$1))+1)
G2 has =INDEX($A$2:$A$5,MOD(ROW()-ROW(G$1)-1,COUNTA($A$2:$A$5))+1)
H2 has =INDEX($B$2:$D$5,MATCH(G2,$A$2:$A$5,0),MATCH(F2,$B$1:$D$1,0))
Replace $B$1:$D$1 with your column heading range for Customer columns,
Replace $A$1:$A$5 with your row heading range for Product columns,
Replace F$1 with the address of your Customer heading on the result range ( H$4?)
Replace G$1 with the address of your Product heading on the result range ( I$4?)
Replace $B$2:$D$5 with your data range (where the volumes are)
You also need to adjust G2 to and F2 in the last formula in H2 (in my example). If this to go to J5 (in yours), change G2 to I5, and F2 to H5.
I hope this helps; if you still have difficulties tell me what your H5:J5 refers to: the first row of results, or the heading (Customer Name,Product Name, Volume)
You still calculate rows*columns to copy the formulae down. Also it does not remove blank cells (your Customer1 having no Product volume.
I have figured it out using power query. This is really so easy. All I need to to is-
Step 1: Open the table in power query,
Step 2: Select the column Product name,
Step 3: Right click and Unpivot the other columns. And got the desired result.
does anyone know why this range(c) doesnt work? i am looping through a row and filtering a table with c as a criteria, after that i need to paste everything this filter give me under the c cell.
Sub exercicio1()
Dim table As Range
For Each c In Range("i5", Range("i5").End(xlToRight))
Range("B5").Select
Selection.AutoFilter
ActiveSheet.Range("$B$5:$C$5570").AutoFilter Field:=1, Criteria1:=c
Range("C6").Select
Range(Selection, Selection.End(xlDown)).Select
Selection.Copy
ActiveSheet.Range("$B$5:$C$5570").AutoFilter Field:=1
Range(c).Offset(1).Select
ActiveSheet.Paste
Application.CutCopyMode = False
Next c
End Sub
i want to select the cell under c
As the earlier answer "c" is already a range...
SO:
c.Offset(1).Select
Return Lookup in Columns Using Application.Match
Adjust the values in the constant section.
Option Explicit
Sub ReturnLookupInColumns()
' Define constants.
' Source (lookup and read)
Const slCol As String = "B"
Const svCol As String = "C"
Const sfRow As Long = 6
' Destination (lookup and write)
Const dfCol As String = "I"
Const dlRow As Long = 5
' Reference the worksheet ('ws')...
Dim ws As Worksheet: Set ws = ActiveSheet ' improve!
' ... and write its number of rows to a variable ('wsrCount').
Dim wsrCount As Long: wsrCount = ws.Rows.Count
' Using the source lookup column ('slCol'), calculate the last row
' ('slRow'), the row of the last non-empty cell in the column.
Dim slRow As Long: slRow = ws.Cells(wsrCount, slCol).End(xlUp).Row
' Reference the (one-column) source lookup range ('slrg').
Dim slrg As Range
Set slrg = ws.Range(ws.Cells(sfRow, slCol), ws.Cells(slRow, slCol))
' Using 'EntireRow' and 'Resize' on the source lookup range,
' reference the (one-column) source value range ('svrg').
Dim svrg As Range: Set svrg = slrg.EntireRow.Columns(svCol)
' Using the destination lookup row ('dlRow'), calculate the last column
' ('dlCol'), the column of the last non-empty cell in the row.
Dim dlCol As Long
dlCol = ws.Cells(dlRow, ws.Columns.Count).End(xlToLeft).Column
' Reference the (one-row) destination lookup range ('dlrg').
Dim dlrg As Range
Set dlrg = ws.Range(ws.Cells(dlRow, dfCol), ws.Cells(dlRow, dlCol))
' Clear the contents below the destination lookup range.
dlrg.Offset(1).Resize(wsrCount - dlrg.Row).ClearContents
' To avoid an inner loop (increase efficiency), use this little known
' feature of 'Application.Match' that will return the destination indexes
' (in this case the destination column indexes) of the matching values
' of the source lookup range, in a 2D one-based (one-column) array
' ('dIndexes'). If a source value is not found, the element
' at the same position in the array will contain an error value
' ('Error 2042').
Dim dIndexes As Variant: dIndexes = Application.Match(slrg, dlrg, 0)
' Declare additional variables used in the loop.
Dim svCell As Range ' Current Source Value Cell
Dim sr As Long ' Current Row of the Source Ranges
Dim dvCell As Range ' Current Destination Value Cell
Dim dIndex As Variant ' Current Index in the Destination Indexes Array
' Firstly, 'dIndex' needs to be declared as variant because it will
' be assigned a number or an error value.
' Secondly, 'dIndex' needs to be declared as variant because it will
' be used as a so-called 'For Each control variable' which needs
' to be declared as variant (or as object) no matter what.
' Loop through the elements in the destination indexes array.
For Each dIndex In dIndexes
' Since the destination indexes array has the same number of elements
' as the source ranges have rows and the array is one-based,
' this number ('sr') also represents the current element's index,
' the row position in the array (see 'dIndexes(sr, 1)' the line after).
sr = sr + 1
' Check if the current source lookup value was found.
If IsNumeric(dIndexes(sr, 1)) Then ' source lookup value was found
' Reference the current source value cell.
Set svCell = svrg.Cells(sr)
' Reference the current destination value cell.
Set dvCell = ws.Cells(wsrCount, dlrg.Columns(dIndex).Column) _
.End(xlUp).Offset(1)
' Write the value from the current source value cell
' to the current destination value cell.
dvCell.Value = svCell.Value
'Else ' source lookup value was not found; do nothing
End If
Next dIndex
' Inform to not wonder if the code has run or not.
MsgBox "Lookup has finished.", vbInformation
End Sub
Here's the process I have in mind:
Count all cells containing data in column A (I'm thinking "xlUp"?) and set that number as a variable.
Select first 400 cells in Column A (starting at A2), and fill them color Yellow.
Copy that data (data is pasted to another prog ((Let's call the shortcut key "!*Z")).
Subtract 400 from the total cell count variable.
Select second 400 cells in Column A (starting at A402)
Fill them color Yellow
Copy that data (!*Z).
Subtract 400 from updated cell count variable.
Repeat until the last cell of data is copied and color filled.
Any help would be great.
I feel like I've been going in circles on this for too long. It's time to ask the experts. Thank you!
Batch Range
Here is my quick (short) vision of the suggested post applied to your case. Please, do share if you find an improvement.
Option Explicit
Sub BatchRange()
Const sfRow As Long = 2
Const sCol As String = "A"
Const Batch As Long = 400
' Create a reference to the First Cell.
Dim sfCell As Range: Set sfCell = Sheet1.Cells(sfRow, sCol)
' Calculate the last row.
Dim slRow As Long
slRow = Sheet1.Cells(Sheet1.Rows.Count, sCol).End(xlUp).Row
Dim srCount As Long: srCount = slRow - sfRow + 1
Dim bCount As Long: bCount = Int(srCount / Batch) + 1
Dim srg As Range
Dim b As Long
Dim r As Long
For b = 1 To bCount
If b = bCount Then
r = srCount Mod Batch
Else
r = Batch
End If
If r > 0 Then
' Create a reference to the current Column Range.
Set srg = sfCell.Resize(r)
' Do your stuff
' e.g.:
Debug.Print sfCell.Address(0, 0), srg.Address(0, 0)
' or:
srg.Interior.Color = vbYellow
' Create a reference to the next First Cell.
Set sfCell = sfCell.Offset(r)
End If
Next b
End Sub