I have multi-tables in one sheet, how to collect my interested data from them.
for example, I just need the data of table1 column 3, and table2 column 2.
the size for both tables may be variate. I need collect the data into array for next processing.
Thanks.
You need to find a way to restrict the tables in VBA, i.e. know in which row they start and of how many rows they consist. Because the tables can appear anywhere in the sheet with variate dimensions, there is no straight-forward way of extracting their data.
What I would suggest is to loop from the top to the lastrow of the sheet and on every row check if the table started and then in an inner loop iterate through the table rows until the table ends (i.e. an empty row is encountered).
The code might look similar to this (not tested):
Dim LastRow as Long, i as Long, k as Long
Dim sht as Worksheet
LastRow = sht.Cells(sht.Rows.Count, "A").End(xlUp).Row 'Assuming the tables start in column A
For i=1 to LastRow
If (sht.Range("A" & i) <> "" Then 'table is encountered
k = i
Do While sht.Range("A" & k) <> ""
... 'Get data from specific column
k = k + 1
Loop
End if
i = k
Next i
Try this (necessary comments are in code):
Option Explicit
Sub CollectData()
Dim table1Address As String, table2Address As String
' here you specify cells that are at the start of a column
table1Address = "B2"
table2Address = "C7"
Dim firstCell As Range, lastCell As Range
Dim table1Data, table2Data As Variant
' determine last cell in column and read whole column at once to an array variable
Set firstCell = Range(table1Address)
Set lastCell = Range(table1Address).End(xlDown)
table1Data = Range(firstCell, lastCell).Value2
Set firstCell = Range(table2Address)
Set lastCell = Range(table2Address).End(xlDown)
table2Data = Range(firstCell, lastCell).Value2
End Sub
Related
I'm an electrical contractor and I made a worksheet to help me bid projects.
Say I'm bidding on wiring a new house. I have broken down each task "outlet"/"Switch" to materials and labor needed for each task. Those materials are then multiplied by the quantity needed and populate 3 different tables automatically.
Here is the process: (24 outlets are needed for this job)
"Bid Cut Sheet" Sheet where quantities of specific tasks are entered.
"Job List" Tasks are broken down into materials needed for that task, multiplied by the quantity entered in "Bid Cut Sheet"
"Material Sheet" Total of all material needed for the job in 3 different tables/stages of the project
What I am trying to do is populate rows in EACH table where materials are needed. Essentially consolidate the data in EACH table by eliminating with quantities of 0 and ADDING rows with quantities >0 and fill down rows with material needed: updating every time data is entered in the "Bid Cut Sheet"
This code eliminates values of 0 after I run the code, but does not update data entered in the "bid cut sheet" after I run the code. Also, I would like this to be imbedded in the workbook so I dont have to run the code each time I use the workbook.
Sub DeleteRowsBasedonCellValue()
'Declare Variables
Dim i As Long, LastRow As Long, Row As Variant
Dim listObj As ListObject
Dim tblNames As Variant, tblName As Variant
Dim colNames As Variant, colName As Variant
'Names of tables
tblNames = Array("Rough_Material", "Trim_Material", "Service_Material")
colNames = Array("Rough", "Trim", "Service")
'Loop Through Tables
For i = LBound(tblNames) To UBound(tblNames)
tblName = tblNames(i)
colName = colNames(i)
Set listObj = ThisWorkbook.Worksheets("MaterialSheet").ListObjects(tblName)
'Define First and Last Rows
LastRow = listObj.ListRows.Count
'Loop Through Rows (Bottom to Top)
For Row = LastRow To 1 Step -1
With listObj.ListRows(Row)
If Intersect(.Range, _
listObj.ListColumns(colName).Range).Value = 0 Then
.Delete
End If
End With
Next Row
Next i
End Sub
This is what it looks like after running the code, it works one time but does not update.
If I understand your question correctly, what you are looking for is something like this:
Sub DeleteRowsBasedonCellValue()
'Declare Variables
Dim LastRow As Long, FirstRow As Long
Dim Row As Long
Dim columns As Variant, column As Variant
columns = Array("A", "D", "G")
With ThisWorkbook.Worksheets("Sheet1") '<- type the name of the Worksheet here
'Define First and Last Rows
FirstRow = 1
LastRow = .UsedRange.Rows(.UsedRange.Rows.Count).Row
'Loop Through Columns
For Each column In columns
'Loop Through Rows (Bottom to Top)
For Row = LastRow To FirstRow Step -1
If .Range(column & Row).Value = 0 Then
.Range(column & Row).Resize(1, 2).Delete xlShiftUp
End If
Next Row
Next column
End With
End Sub
Test it out and see if this does what you want.
Alternatively, it might be wiser to be more explicit and make the code more flexible. If your tables are actually formatted as tables, you can also loop over these so-called ListObjects. That way, if you insert columns/rows in the future, the code won't break.
To do this, you could use code like this:
Sub DeleteRowsBasedonCellValue()
'Declare Variables
Dim i As Long, LastRow As Long, Row As Variant
Dim listObj As ListObject
Dim tblNames As Variant, tblName As Variant
Dim colNames As Variant, colName As Variant
'The names of your tables
tblNames = Array("Rough_Materials", "Trim_Materials", "Service_Materials")
colNames = Array("quantity_rough", "quantity_trim", "quantity_service")
'The name of the column the criterion is applied to inside each table
'Loop Through Tables
For i = LBound(tblNames) To UBound(tblNames)
tblName = tblNames(i)
colName = colNames(i)
Set listObj = ThisWorkbook.Worksheets("Sheet1").ListObjects(tblName)
'Define First and Last Rows '^- the name of the Worksheet
LastRow = listObj.ListRows.Count
'Loop Through Rows (Bottom to Top)
For Row = LastRow To 1 Step -1
With listObj.ListRows(Row)
If Intersect(.Range, _
listObj.ListColumns(colName).Range).Value = 0 Then
.Delete
End If
End With
Next Row
Next i
End Sub
Edit in response to your comment:
Make sure your table is actually formatted as a table and has been given the right name! You can also change the table names in your code to your liking in the line tblNames = Array("Rough_Materials", "Trim_Materials", "Service_Materials"). Also, the column names have to be correct/you should adapt them in the code: colNames = Array("quantity_rough", "quantity_trim", "quantity_service")
I am quite new to VBA so my question may sound silly to some of you.
I have a problem with my code. I am trying to check between 2 sheets(1st: test and 2nd: test data. In the code I am using the code name of the sheets) how may times a criteria/my product is met and based on that number to add that many rows beneath the SKU/product.
What I want to do is, if for my first product/SKU I have stock in 5 different locations, I want to add 5 rows in the first sheet and to see the qty in column D and the location in column E.
TestData tab
The reason why I need to list the quantity in different cells is that the stock is in different locations, so I can not just add in to one cell.
I am also adding screen shots of how my two sheets look like.
I have not add the loop to find the location yet, but I want to understand how to add the rows first.
Sub test()
Dim myrange As Range, testrange As Range, cell As Range, result As Range
Dim i As Long, testlastrow As Long, lastrow As Long
Dim helprng As Range
lastrow = TESTTAB.Range("a" & Rows.Count).End(xlUp).row
testlastrow = TDATA.Range("a" & Rows.Count).End(xlUp).row
Set testrange = TDATA.Range("a2:c" & testlastrow)
Set myrange = TESTTAB.Range("b2:b" & lastrow)
Set result = TESTTAB.Range("d2:e" & testlastrow)
Set helprng = TESTTAB.Range("f2:f" & lastrow)
For Each cell In myrange
For i = 1 To lastrow
If cell.Cells(i, 1) = testrange.Cells(i, 1) Then
result.Cells(i, 1) = testrange.Cells(i, 2)
End If
Next i
Next cell
End Sub
Here is the raw structure you were asking for.
Sub test()
' 011
Dim Rng As Range
Dim Tmp As Variant
Dim Radd As Long ' number of rows to add
Dim R As Long ' row counter
With Tdata
' Range, Cells and Rows Count, all in the same sheet
' exclude caption row(s)
Set Rng = .Range(.Cells(2, "A"), .Cells(.Rows.Count, "A").End(xlUp))
End With
Application.ScreenUpdating = False ' speed up the process
With TestTab
' end the loop in row 2 because row 1 might contain captions
' loop bottom to top because row numbers will change
' below the current row as you insert rwos
' column 1 = "A"
For R = .Cells(.Rows.Count, 1).End(xlUp).Row To 2 Step -1
Tmp = .Cells(R, 1).Value
Radd = Application.CountIf(Rng, Tmp)
If Radd > 1 Then
.Range(.Rows(R + 1), .Rows(R + Radd)).Insert
.Cells(R + 1, 1).Value = Radd ' for testing purposes
End If
Next R
End With
Application.ScreenUpdating = True ' now you look
End Sub
As our Evil Blue Monkey has suggested, inserting blank rows and then populating them may not be the most efficient way. You can copy a row and then click Insert to insert the copied data into a new row. Get the syntax from the Macro recorder if it's of interest. It works with the Range object the same way.
I am trying to create something that is capable of taking the value from one text box, searching a group of column headers to find the correct one, and then placing a new value from a second text box into the last row under that column. I adapted this code that I found on here, https://stackoverflow.com/a/37687346/13073514, but I need some help. This code posts the value from the second text box under every header, and I would like it to only post it under the header that is found in textbox 1. Can anyone help me and explain how I can make this work? I am new to vba, so any explanations would be greatly appreciated.
Public Sub FindAndConvert()
Dim i As Integer
Dim lastRow As Long
Dim myRng As Range
Dim mycell As Range
Dim MyColl As Collection
Dim myIterator As Variant
Set MyColl = New Collection
MyColl.Add "Craig"
MyColl.Add "Ed"
lastRow = ActiveSheet.Cells.Find("*", SearchOrder:=xlByRows, SearchDirection:=xlPrevious).Row
For i = 1 To 25
For Each myIterator In MyColl
If Cells(1, i) = myIterator Then
Set myRng = Range(Cells(2, i), Cells(lastRow, i))
For Each mycell In myRng
mycell.Value = Val(mycell.Value)
Next
End If
Next
Next
End Sub
Basic example:
Sub tester()
AddUnderHeader txtHeader.Text, txtContent.Text
End Sub
'Find header 'theHeader' in row1 and add value 'theValue' below it,
' in the first empty cell
Sub AddUnderHeader(theHeader, theValue)
Dim m
With ThisWorkbook.Sheets("Data")
m = Application.Match(theHeader, .Rows(1), 0)
If Not IsError(m) Then
'got a match: m = column number
.Cells(.Rows.Count, m).End(xlUp).Offset(1, 0).Value = theValue
Else
'no match - warn user
MsgBox "Header '" & theHeader & "' not found!", vbExclamation
End If
End With
End Sub
I have commented your code for your better understanding. Here it is.
Public Sub FindAndConvert()
Dim i As Integer
Dim lastRow As Long
Dim myRng As Range
Dim myCell As Range
Dim MyColl As Collection
Dim myIterator As Variant
Set MyColl = New Collection
MyColl.Add "Craig"
MyColl.Add "Ed"
Debug.Print MyColl(1), MyColl(2) ' see output in the Immediate Window
' your code starts in the top left corner of the sheet,
' moves backward (xlPrevious) from there by rows (xlByRows) until
' it finds the first non-empty cell and returns its row number.
' This cell is likely to be in column A.
lastRow = ActiveSheet.Cells.Find("*", SearchOrder:=xlByRows, SearchDirection:=xlPrevious).Row
For i = 1 To 25 ' do the following 25 times
' in Cells(1, i), i represents a column number.
' 1 is the row. It never changes.
' Therefore the code will look at A1, B1, C1 .. until Y1 = cells(1, 25)
For Each myIterator In MyColl ' take each item in MyColl in turn
If Cells(1, i) = myIterator Then
' set a range in the column defined by the current value of i
' extend it from row 2 to the lastRow
Set myRng = Range(Cells(2, i), Cells(lastRow, i))
' loop through all the cells in myRng
For Each myCell In myRng
' convert the value found in each cell to a number.
' in this process any non-numeric cells would become zero.
myCell.Value = Val(myCell.Value)
Next myCell
End If
Next myIterator
Next i
End Sub
As you see, there is no TextBox involved anywhere. Therefore your question can't be readily understood. However, my explanations may enable you to modify it nevertheless. It's all a question of identifying cells in the worksheet by their coordinates and assigning the correct value to them.
Edit/Preamble
Sorry, didn't read that you want to use TextBoxes and to collect data one by one instead of applying a procedure to a whole data range.
Nevertheless I don't remove the following code, as some readers might find my approach helpful or want to study a rather unknown use of the Application.Match() function :)
Find all header columns via single Match()
This (late) approach assumes a two-column data range (header-id and connected value).
It demonstrates a method how to find all existant header columns by executing a single Application.Match() in a â–ºone liner ~> see step [3].
Additional feature: If there are ids that can't be found in existant headers the ItemCols array receives an Error items; step [4] checks possible error items adding these values to the last column.
The other steps use help functions as listed below.
[1] getDataRange() gets range data assigning them to variant data array
[2] HeaderSheet() get headers as 1-based "flat" array and sets target sheet
[3] see explanation above
[4] nxtRow() gets next free row in target sheet before writing to found column
Example call
Sub AddDataToHeaderColumn()
'[1] get range data assigning them to variant data array
Dim rng As Range, data
Set rng = getDataRange(Sheet1, data) ' << change to data sheet's Code(Name)
'[2] get headers as 1-based "flat" array
Dim targetSheet As Worksheet, headers
Set targetSheet = HeaderSheet(Sheet2, headers)
'[3] match header column numbers (writing results to array ItemCols as one liner)
Dim ids: ids = Application.Transpose(Application.Index(data, 0, 1))
Dim ItemCols: ItemCols = Application.Match(ids, Array(headers), 0)
'[4] write data to found column number col
Dim i As Long, col As Long
For i = 1 To UBound(ItemCols)
'a) get column number (or get last header column if not found)
col = IIf(IsError(ItemCols(i)), UBound(headers), ItemCols(i))
'b) write to target cells in found columns
targetSheet.Cells(nxtRow(targetSheet, col), col) = data(i, 2)
Next i
End Sub
Help functions
I transferred parts of the main procedure to some function calls for better readibility and as possible help to users by demonstrating some implicit ByRef arguments such as [ByRef]mySheet or passing an empty array such as data or headers.
'[1]
Function getDataRange(mySheet As Worksheet, data) As Range
'Purpose: assign current column A:B values to referenced data array
'Note: edit/corrected assumed data range in columns A:B
With mySheet
Set getDataRange = .Range("A2:B" & .Cells(.Rows.Count, "B").End(xlUp).Row)
data = getDataRange ' assign range data to referenced data array
End With
End Function
'[2]
Function HeaderSheet(mySheet As Worksheet, headers) As Worksheet
'Purpose: assign titles to referenced headers array and return worksheet reference
'Note: assumes titles in row 1
With mySheet
Dim lastCol As Long: lastCol = .Cells(1, .Columns.Count).End(xlToLeft).Column
headers = Application.Transpose(Application.Transpose(.Range("A1").Resize(1, lastCol)))
End With
Set HeaderSheet = mySheet
End Function
'[4]
Function nxtRow(mySheet As Worksheet, ByVal currCol As Long) As Long
'Purpose: get next empty row in currently found header column
With mySheet
nxtRow = .Cells(.Rows.Count, currCol).End(xlUp).Row + 1
End With
End Function
I need some advice for my code. I really appreciate if some members can edit my code. Thanks
My code below is looking for the name on column B and copy the result on another sheet if 2 conditions met:
- The row.value on column G = "ongoing"
- The row.value on column C = "HP"
When I run this code, got an error-message box "Range of Object"_Worksheet failed.
I am trying to change the set "mytable to ShSReturn.ListObject ("Survey Return")" with mytable as Range, another message error "Subscription out of range"
Sub LOf()
Dim cell As Variant
Dim myrange As Long, lastrow As Long, finalrow As Long, resultrow As Long
Dim mytable As Range
lastrow = ShSReturn.Range("G" & ShSReturn.Rows.Count).End(xlUp).Row
finalrow = ShSReturn.Range("C" & ShSReturn.Rows.Count).End(xlUp).Row
resultrow = ShSReturn.Range("B" & ShSReturn.Rows.Count).End(xlUp).Row
Set mytable = ShSReturn.ListObjects("Survey Return")
cell = 7
For Each cell In mytable
If mytable.Cells(cell, lastrow).Value = "Ongoing" _
And mytable.Cells(cell, finalrow).Value = "HP" Then
mytable.Cells(cell, resultrow).Copy
ShPPT.Cells(cell, 17).PasteSpecial xlPasteValues
resultrow = resultrow + 1
End If
Next cell
End Sub
I think there's some confusion about the nature of your ListObject, as specified in your original code (see comments to the question). When you select a bunch of cells and go to Insert -> Table, then as well as the table object, Excel defines a Range with the name of that table: a named Range. This Range may be referenced directly in VBA as such:
Set mytable = Range("Table1")
Note that Range names may not contain spaces
On the assumption that you have a named Range, it might be something like this:
Sub LOf()
Dim myrange As Long, lastrow As Long, finalrow As Long, resultrow As Long
Dim mytable As Range
lastrow = ShSReturn.Range("G" & ShSReturn.Rows.Count).End(xlUp).Row
finalrow = ShSReturn.Range("C" & ShSReturn.Rows.Count).End(xlUp).Row
resultrow = ShSReturn.Range("B" & ShSReturn.Rows.Count).End(xlUp).Row
Set mytable = ActiveSheet.Range("SurveyReturn") ' It's best to specify which sheet your source data is on. Presumably "ShSReturn" is the CodeName of your results sheet
Dim x As Long
For x = 7 To mytable.Cells(mytable.Cells.Count).Row ' Start at Row 7, and finish at the row number of the last cell in that Range
If mytable.Cells(x, **lastrow**).Value = "Ongoing" And mytable.Cells(x, **finalrow**).Value = "HP" Then
mytable.Cells(x, **resultrow**).Copy
ShPPT.Cells(cell, 17).PasteSpecial xlPasteValues
resultrow = resultrow + 1
End If
Next x
End Sub
Note that the above code will not work in its present form. What I have done is an approximation of what I think you're looking for: however you're going to have to do a bit of work, because the code in your question has some fundamental issues. For example, in your code you have lines like this:
mytable.Cells(cell, resultrow).Copy
However addressed cells within Ranges are in the format Range.Cells(Row, Column) - where Row and Column are numbers. However in your code resultrow as defined at the top is a Row, not a Column. You need to work out what exactly you want to copy, in terms of which row/column and re-write your code accordingly.
If you want to provide clarity, I'll be happy to edit my answer to accommodate what you want.
Excel 2010. I am trying to write a macro that could copy a set of data multiple times based on criteria on another sheet, but I've been stuck for a long time. I very much appreciate any help that could be offered to help me solve this problem.
Step 1: In the "Criteria" worksheet, there are three columns in which each row contains a specific combination of data. The first set of combination is "USD, Car".
Criteria worksheet
Step 2: Then the macro will move to the Output worksheet (please refer to the below link for screenshots), and then filter column A and B with the first set of criteria "USD" and "Car" in the "Criteria" worksheet.
Step 3: Afterwards, the macro will copy the filtered data into the last blank row. But the tricky part here is that, the filtered data has to be copied two times (as the "Number of set" column in the "Criteria" tab is 3 in this combination, and it doesn't have to copy the data three times since the filtered data would be treated as the first set of data)
Step4: After the filtered data have been copied, the "Set" column D will need to fill in the corresponding number of set that the rows are in. Therefore, in this 1st example, cell D2 and D8 will have "1" value, cell D14-15 will have "2" value, and cell D16-17 will have "3" value.
Step5: The macro will then move back to the "Criteria" worksheet and continue to based on the 2nd set of combination "USD, Plane" to filter the data in the "Output" worksheet. Again, it will copy the filtered data based on the "Number of set" in the "Criteria" worksheet. This process will continue until all the different combinations in the "Criteria" worksheet have been processed.
Output worksheet
Ok sorry for delay, here is a working version
you just have to add a sheet called "BF" because the autofilter count wasn't working properly so I had to use another sheet
Sub testfct()
Dim ShC As Worksheet
Set ShC = ThisWorkbook.Sheets("Criteria")
Dim EndRow As Integer
EndRow = ShC.Cells(Rows.Count, 1).End(xlUp).Row
For i = 2 To EndRow
Get_Filtered ShC.Cells(i, 1), ShC.Cells(i, 2), ShC.Cells(i, 3)
Next i
End Sub
Sub Get_Filtered(ByVal FilterF1 As String, ByVal FilterF2 As String, ByVal NumberSetsDisered As Integer)
Dim NbSet As Integer
NbSet = 0
Dim ShF As Worksheet
Set ShF = ThisWorkbook.Sheets("Output")
Dim ColCr1 As Integer
Dim ColCr2 As Integer
Dim ColRef As Integer
ColCr1 = 1
ColCr2 = 2
ColRef = 4
If ShF.AutoFilterMode = True Then ShF.AutoFilterMode = False
Dim RgTotal As String
RgTotal = "$A$1:$" & ColLet(ShF.Cells(1, Columns.Count).End(xlToLeft).Column) & "$" & ShF.Cells(Rows.Count, 1).End(xlUp).Row
ShF.Range(RgTotal).AutoFilter field:=ColCr1, Criteria1:=FilterF1
ShF.Range(RgTotal).AutoFilter field:=ColCr2, Criteria1:=FilterF2
'Erase Header value, fix? or correct at the end?
ShF.AutoFilter.Range.Columns(ColRef).Value = 1
Sheets("BF").Cells.ClearContents
ShF.AutoFilter.Range.Copy Destination:=Sheets("BF").Cells(1, 1)
Dim RgFilt As String
RgFilt = "$A$2:$B" & Sheets("BF").Cells(Rows.Count, 1).End(xlUp).Row '+ 1
Dim VR As Integer
'Here was the main issue, the value I got with autofilter was not correct and I couldn't figure out why....
'ShF.AutoFilter.Range.SpecialCells(xlCellTypeVisible).Rows.Count
'Changed it to a buffer sheet to have correct value
VR = Sheets("BF").Cells(Rows.Count, 1).End(xlUp).Row - 1
Dim RgDest As String
ShF.AutoFilterMode = False
'Now we need to define Set's number and paste N times
For k = 1 To NumberSetsDisered - 1
'define number set
For j = 1 To VR
ShF.Cells(Rows.Count, 1).End(xlUp).Offset(j, 3) = k + 1
Next j
RgDest = "$A$" & ShF.Cells(Rows.Count, 1).End(xlUp).Row + 1 & ":$B$" & (ShF.Cells(Rows.Count, 1).End(xlUp).Row + VR)
Sheets("BF").Range(RgFilt).Copy Destination:=ShF.Range(RgDest)
Next k
ShF.Cells(1, 4) = "Set"
Sheets("BF").Cells.ClearContents
'ShF.AutoFilterMode = False
End Sub
And the function to get column letter using an integer input :
Function ColLet(x As Integer) As String
With ActiveSheet.Columns(x)
ColLet = Left(.Address(False, False), InStr(.Address(False, False), ":") - 1)
End With
End Function