VBA IE automation - finding the last row of dynamic table - vba

I am working on some code that would take information from Excel and transfer the information to a table on a website in IE. All has been well until now.
I'm currently stuck at a point and I can't seem to find any information to help.
What happens is there is a dynamic table per employee on the website. After clicking an "Add" button, a blank row is created, which is the one I want to transfer the information to. The problem is the row that is created ends up being the last row in the table. So when adding the row, it could be number 1, number 35, or whatever.
Is there a way I can set a reference to the last row of a dynamic IE table without having to know the number so the code would work for any employee?

I think you are talking about converting the elements in a column into an array. Is that what you want? If so, try one of these two scripts.
Sub MakeArray()
Dim arr As Variant
With ActiveSheet
arr = WorksheetFunction.Transpose(.Range(.[A1], .Cells(Rows.Count, "A").End(xlUp)))
End With
End Sub
Sub MakeString()
Dim s As String
Const DELIMITER = ","
With ActiveSheet
s = Join(WorksheetFunction.Transpose(.Range(.[A1], .Cells(Rows.Count, "A").End(xlUp))), DELIMITER)
End With
End Sub

Related

Excel VBA Get Count of Non-Empty Rows in Range

I know this has been asked at least a dozen times, but of all those questions the solutions seemed to be tailored or simply created an error in my code. For some reason I get an error when I try and reference a range from a different worksheet. Below is my code. I have two worksheets (tabs). One has a button to launch my code MENU, the other is the data I am trying to read and write to RAW. All I am trying to do is find out how many rows of data the sheet has so I can loop through the rows and make changes. For some reason I can't wrap my head around it or something.
Private Sub CommandButton21_Click()
Dim wbCurrent As Workbook
Dim wsCurrent As Worksheet
Set wbCurrent = ThisWorkbook
Set wsCurrent = wbCurrent.Worksheets("RAW")
Dim i As Long
Dim siteCol As Long
siteCol = Range("I" & Rows.Count).End(xlUp).Row
For i = 1 To siteCol
wsCurrent.Range("I" & i) = "MARKED"
Next i
Range("I1") = siteCol
End Sub
1- Always use Long for your variables, not Integer unless you have a specific reason. The maximum value of an Integer is 32767, which might be not enough to hold your count of rows.
2- Dont get the number of rows with COUNTA, this has many drawbacks. use this instead:
siteCol = wsCurrent.Range("I" & wsCurrent.Rows.count).End(xlUp).Row
This finds the Row number of the last occupied cell in wsCurrent, assuming that wsCurrent is at the top of the document (it starts on Row 1). Please note that if wsCurrent is completely empty, this will find the row number of the first occupied cell above wsCurrent, or the first row of the document.
3- When you want to assign a whole range to the same value, you can do it at once, which is much faster and simpler, like this (no loop needed):
wsCurrent.Range("I1:I" & siteCol) = "MARKED"
4- No need to Activate a worksheet to work with it. Drop the statement wsCurrent.Activate and try to always work with qualified ranges.

Outputting rows into another sheet

I have two sets of data stored in two different sheets. I need to run an analysis which prints out the non-duplicate rows (i.e. row is present in one and not the other) found in the sheets and print them in a new sheet.
I can do the comparison fine - it is relatively simple with ranges and the For Next method. I currently store the non-duplicates in two different collections, each representing the non-duplicates in each sheet. However I am having trouble deciding how to proceed with pasting the duplicate rows on the new sheet.
I thought about storing the entire row into a collection but printing the row out of the collection in the new sheet seems non-trivial: I would have to determine the size of the collection, set the appropriate range and then iterate through the collection and print them out. I would also like to truncate this data which would add another layer of complexity.
The other method I thought was simply storing the row number and using Range.Select.Copy and PasteSpecial. The advantage of this is that I can truncate however much I wish, however this seems incredibly hacky to me (essentially using VBA to simulate user input) and I am not sure on performance hits.
What are the relative merits or is there a better way?
I have been tackling a similar problem at work this week. I have come up with two methods:
First you could simply iterate through each collection one row at a time, and copy the values to the new sheet:
Function PasteRows1(ByRef srcRows As Collection, ByRef dst As Worksheet)
Dim row As Range
Dim curRow As Integer
curRow = 1
For Each row In srcRows
dst.rows(curRow).Value = row.Value
curRow = curRow + 1
Next
End Function
This has the benefit of not using the Range.Copy method and so the user's clipboard is preserved. If you are not copying an entire row then you will have to create a range that starts at the first cell of the row and then resize it using Range.Resize. So the code inside the for loop would roughly be:
Dim firstCellInRow as Range
Set firstCellInRow = dst.Cells(curRow,1)
firstCellInRow.Resize(1,Row.columns.Count).Value = row.Value
curRow = curRow + 1
The second method I thought of uses the Range.Copy. Like so:
Function PasteRows2(ByRef srcRows As Collection, ByRef dst As Worksheet)
Dim row As Range
Dim disjointRange As Range
For Each row In srcRows
If disjointRange is Nothing Then
Set disjointRange = row
Else
Set disjointRange = Union(disjointRange, row)
End If
Next
disjointRange.Copy
dst.Paste
End Function
While this does use the .Copy method it also will allow you to copy all of the rows in one shot which is nice because you will avoid partial copies if excel ever crashes in the middle of your macro.
Let me know if either of these methods satisfy your needs :)

Condense largely(Unpractical) loop based VBA code; nested For...Next loops

Hello everyone alright let start by giving some brief background on my project then I will follow up with my specific issue and code.
Currently I am building a program to automate the process of filling a template. This template exceeds 60,000 rows of data quite often and I've built the large majority of it to work month to month by plugging in new data sheets and running it. Currently all of the work is based off of one data sheet which I import into excel manually. This data sheet does not contain all the data I need to populate the template so now I am beginning to bring in additional data to supplement this. The problem herein lies with data association. When I was originally pulling from one data sheet I didn't have to worry if the data I pulled for each row coincided with the other rows because it all came from the same sheet. Now I have to cross check data across two sheets to confirm it is pulling the correct information.
Now for what you need to know. I am trying to fill a column that will be referred to as Haircut, but before I do that I need to confirm that I am pulling the correct haircut number in correlation to a Trade ID which was already populated into the template in a previous line of code.
Using similar logic that I have been using throughout my entire project this is a snippet of code I have to perform this task.
Dim anvil as Worksheet
Dim ALLCs as worksheet
Dim DS as worksheet
'''''''''''''''''''''''''''''code above this line is irrelevant to answer this question
ElseIf InStr(1, DS.Cells(x, 2), "Haircut") Then
Anvil.Select
For y = 1 To 80
If Anvil.Cells(1, y) = "Haircut" Then
For Z = 1 To 80
If Anvil.Cells(1, Z) = "Trade ID" Then
For t = 2 To 70000
For u = 16 To 70000
If Anvil.Cells(t, Z) = ALLCs.Cells(u, 34) Then
ALLCs.Cells(u, 27) = Anvil.Cells(t, y)
End If
Next
Next
End If
Next
End If
Next
This code coupled with my other code I assume will in theory work, but I can only imagine that it will take an unbelievable amount of time(this program already takes 7 and a half minutes to run). Any suggestions on how to rewrite this code with better functionality, following this general logic?
Any help is appreciated, whether you completely revamp the code, or if you offer suggestions on how to cut down loops. I am also looking for suggestions to speed up the code in general aside from screen updating and calculation suggestions.
If I understand the logic correctly then you can replace all but one of the loops with a .Find() method like so:
'// Dimension range objects for use
Dim hdHaricut As Excel.Range
Dim hdTradeID As Excel.Range
Dim foundRng As Excel.Range
With Anvil
With .Range("A1:A80") '// Range containing headers
'// Find the cell within the above range that contains a certain string, if it exists set the Range variable to be that cell.
Set hdHaircut = .Find(What:="Haircut", LookAt:=xlWhole)
Set hdTradeID = .Find(What:="Trade ID", LookAt:=xlWhole)
End With
'// Only if BOTH of the above range objects were found, will the following block be executed.
If Not hdHaricut Is Nothing And Not hdTradeID Is Nothing Then
For t = 2 To 70000
'// Using the .Column property of the hdTradeID range, we can see if the value of Cells(t, hdTradeColumn) exists
'// in the other sheet by using another .Find() method.
Set foundRng = ALLCs.Range(ALLCs.Cells(16, 34), ALLCs.Cells(70000, 34)).Find(What:=.Cells(t, hdTradeID.Column).Value, LookAt:=xlWhole)
'// If it exists, then pass that value to another cell on the same row
If Not foundRng Is Nothing Then ALLCs.Cells(foundRng.Row, 27).Value = .Cells(t, hdHaircut.Column).Value
'// Clear the foundRng variable from memory to ensure it isn't mistaken for a match in the next iteration.
Set foundRng = Nothing
Next
End If
End With

Check if unique identifier is present in "new data", if not, add entries to "historical data" sheet?

I've got data with a unique ID number in one column. This is pasted into a "Raw Data" sheet by the user.
Then, I've got macros that manipulate the "Raw Data", including archiving some of it on a "archival" worksheet which includes the ID numbers.
I want to run a check to see if there are any "new" ID numbers in the "Raw Data", and if so add a new row with some of the data including the ID number to the "archival" sheet.
I've googled and checked here. It looks like I want to use a Collection? Never encountered this so far, not sure where to start.
Sorry that this isn't the most well structured question, and that it doesn't include any code. Not sure how to get started.
It should be noted that this reconciliation only needs to go one way -- I don't need to delete "old" unique IDs from the "archival" sheet.
Thanks!
If you specifically want to use collections, over Range.Find, you can do so like this.
Although handling errors this way in VBA is best to avoid most times, for iterating over collections, it can result in faster and often less verbose code.
I have used named ranges for a Rawdata and Archive worksheet tabs, so you will need to adapt this to your own situation. What this does is use collections to store unique items already in Archive, and compare these against items in Rawdata - when a new (unique) item is found in Rawdata that is not already in Archive, it is added to the sheet (and collection).
Sub IDcollection()
Dim IDcoll As New Collection
Dim cells, Rng, Rng_a As Range
Set Rng = Worksheets("Rawdata").Range("IDRANGE")
Set Rng_a = Worksheets("Archive").Range("IDRANGE_A")
'IDRANGE looks like string IDs 'abc', 'ab1',etc
'get unique IDs already in ARCH sheet via named range
On Error Resume Next 'includes only unique items
For Each cells In Rng_a.cells
IDcoll.Add cells.Text, cells.Text
'use the IDs as the KEY in the VBA Collection
Next cells
'check for unique items not in Archive, but in Rawdata (i.e. new items)
On Error Resume Next
For Each cells In Rng
IDcoll.Item cells.Text
If Err.Number <> 0 Then
IDcoll.Add cells.Text, cells.Text
LastRow = Rng_a.Rows(Rows.Count).End(xlUp).Row + 1
Rng_a(LastRow).Value = cells.Text
Err.Clear
End If
Next
End Sub
Just stick this in a module in your VBA for your worksheet, set up some named ranges as above, populated with dummy data.

Selecting all data from a default table size VBA Excel

I have a spread sheet with a default table size and layout that is populated by information from another spread sheet. This table will always have the same number of columns, but the number of entries in the rows can vary. I want to select all the data from the table, and paste it into another sheet, without copying any empty rows.
My initial attempt involved the following code:
Set rightcell = Range("B9").End(x1Right)
Set bottomcell = Range(rightcell).End(x1Down)
To define what the bottom right corner should be, so I can reference the entire table like so:
Range("B9", bottomcell).Select
Or copy or whatever. When I run this, it gives me a "user-defined or object-defined error" and I don't know why. I have the code entered as part of a larger sub, and I have defined my variables as both ranges and variants to try and get this to work. I have spent quite a bit of time scouring the internet for a solution, but so far the information I've found has not explicitly related to my problem, and none of the similar solutions work.
Does anyone know what the appropriate coding for this is, or if I am making some minor error that is throwing everything else off? I remember encountering the same issue during a project in college, but for the life of me, I can't recall the solution. It's quite frustrating.
Also, if I am too vague or you need more clarification on the task, don't hesitate to ask. Thanks in advance for the help!
EDIT: An important note that I left out is that the the table I want to extract data from is in the middle of a page with multiple other tables that I am not trying to interact with.
If the table will always be in the same location on the sheet, you can do something like this to copy the entire table:
'Modify this to any cell in your table (like the top left hand cell):
Range("B9").CurrentRegion.Copy Sheets("TheSheetYouWantToPasteTo").Range("A1")
Even if the table's location on the sheet changes, you can still use the above code to copy the table as long as you know one of the cells in the table.
If you want to keep the same method as you're trying, try this instead:
Dim rightcell As Long
Dim bottomcell As Long
'Finds the furthest column to the right:
rightcell = Cells(5, Columns.Count).End(xlToLeft).Column
'Finds the bottom most row in the table (will stop at the first non-blank cell it finds.)
bottomcell = Range("B:B").Find("*", Range("B9"), searchdirection:=xlPrevious).Row
'Reference the variables like this:
Range(Cells(9, 2), Cells(bottomcell, rightcell)).copy _
Sheets("TheSheetYouWantToPasteTo").Range("A1")
this is what I use
Public Function last_row() As Long
Dim i As Integer
Dim l_row As Long
'my sheet has 35 columns change this number to fit your
For i = 1 To 35
If Sheet1.Cells(Rows.Count, i).End(xlUp).Row > l_row Then
l_row = Sheet1.Cells(Rows.Count, i).End(xlUp).Row
End If
Next i
last_row = l_row
End Function
Then Use
Dim l_row As Long
l_row = last_row
'Again since you know the last column change 35 here to your value
'or use the String i.e. "AI"
Range("B9", Cells(l_row,35)).Select
This will look at every column to determine the the last row that contains data