Loop with two table and last row - excel

I'm trying to build a formula:
=BDS(Bonds!J2& " ISIN","ISSUE_UNDERWRITER","Headers","Y")
In one sheet that takes a unique identifier from another table.
These formula builds me a table. After it builds me the table, I need to take the next row in the other sheet:
=BDS(Bonds!J3& " ISIN","ISSUE_UNDERWRITER","Headers","Y")
Then insert that formula a the end of the previous table built by the previous formula.
What I tried was getting the last row and then offsetting it by one, but I'm trying to figure out how to loop through it.
This is what i have tried:
Sub Formula2()Formula2 Macro
Range("A1").Select
ActiveCell.FormulaR1C1 = _
"=BDS(Bonds!R[1]C[9]& "" ISIN"",""ISSUE_UNDERWRITER"",""Headers"",""Y"")"
lRow = Cells(Rows.Count, 1).End(xlUp).Select
Selection.Offset(1, 0).Select
ActiveCell.FormulaR1C1 = _
"=BDS(Bonds!R[-53]C10& "" ISIN"",""ISSUE_UNDERWRITER"",""Headers"",""Y"")"
Range("A57").Select
End Sub
Image of Table, Im trying to iterate through the ISIN Column. It is column "J"

Although selection and .select are used by the macro recorder, they cause big problems when developing code. It's worth your time to learn how to replace them with range objects. So, while I'm not directly answering your question, I'm trying to give you the tools to do so.
I've shown an example below to illustrate (although I do not work with the BDS() function so I'm undoubtedly getting the details wrong). The main point is that if you learn to move around using the range object you'll be much better off.
Sub formula()
Dim r As Range, sh As Worksheet, bondR As Range, bondSh as Worksheet
set sh = ActiveSheet
set r = sh.range("A1")
Set bondSh = Worksheets("Bonds")
Set bondR = bondSh.Range("J1")
For i = 1 To 10
r.formula = "=BDS(bondR.offset(i,0) & "" ISIN"",""ISSUE_UNDERWRITER"",""Headers"",""Y"")"
Set r = r.Offset(i, 0)
Next i
End Sub
Here I'm defining one range object, r, to track the location on the active sheet, and another, bondR, for the location on the "Bonds" sheet. Once the initial locations of these ranges are defined, you can manipulate them using the .offset(row,col) function as I've done with the simple for-loop, moving down 1 row (but 0 columns) in each loop.
Feel free to ask questions.

Related

How to loop through columns in VBA given that columns are referred by a letter? [duplicate]

This question already has answers here:
Excel VBA, getting range from an inactive sheet
(3 answers)
Closed 1 year ago.
I am a newbie to the VBA world and need your help.
I want to copy data from columns A, B and C in Sheet2 and Sheet3 and paste it in Columns A, B and C of Sheet1 but stacked. Meaning, data from Sheet2 should be pasted in "A1:A4", then data from Sheet3 should be pasted in "A5:A9".
I am using the following code and getting an error:
j = 1
For i = 1 to 2
For k = 1 to 3
Sheets(i+1).range(cells(1,k),cells(4,k).copy
Sheet(1).range(cells(j,k),cells(j+3,k).PasteSpecial xlPasteValues
Next k
j = j + 4
next i
If there is a better way to do it, it would be helpful too.
Please help!
The difference between your code and the one submitted below is in the effort expanded on preparation. Preparation led to recognition of problems where lack of preparation led to confusion and, ultimately, this question. What I'm saying is that I don't think you would have needed to ask had you known to prepare adequately.
Private Sub Snippet()
' 218
Dim Ws(1 To 2) As Worksheet ' "Source 1" and "Source 2"
Dim i As Integer ' loop counter: Ws index
Dim Rs As Long ' loop counter: rows (source)
Dim WsOut As Worksheet ' "Output"
Dim Rt As Long ' target row
With ThisWorkbook
Set WsOut = .Worksheets("Output")
Set Ws(1) = .Worksheets("Source 1")
Set Ws(2) = .Worksheets("Source 2")
End With
With WsOut ' = last used row in WsOut
Rt = .Cells(.Rows.Count, "A").End(xlUp).Row
End With
Application.ScreenUpdating = False
For i = 1 To 2 ' loop through Ws(1 to 2)
With Ws(i) ' find last used row in Ws(1)
' start copying from row 2 because row 1 probably holds captions
' end with last used row in column A
For Rs = 2 To .Cells(.Rows.Count, "A").End(xlUp).Row
Rt = Rt + 1
.Range(.Cells(Rs, 1), .Cells(Rs, 3)).Copy
WsOut.Cells(Rt, 1).PasteSpecial xlPasteValues
Next Rs
End With
Next i
With Application
.CutCopyMode = False
.ScreenUpdating = True
End With
End Sub
The procedure starts off by naming all variables. The first result was to replace references like "Sheet(1)" even at this early stage because "Sheet(1)" is the first sheet from the left in the tab bar. If you add a sheet unwittingly your code won't work anymore or, rather, it will destroy your workbook. Your variables "k" and "j" were replaced with "Rs" and "Rt" marking them as row numbers (source and target).
Next, the code makes sure that the worksheets are properly defined as being within ThisWorkbook. They are also properly linked to their real life names, executing a decision made at the beginning. Actually, the variable declarations are modified many times while the rest of the code is developed. It isn't hewn in stone at the beginning but everything is built on it nevertheless.
Then the Target Row is set, and a system for it's maintenance designed. The system is to find the last used row first and then increment that number every time before a new row is written.
The decision is made to turn off ScreenUpdating while the code runs. It will run faster that way but you must make provision to turn the feature back on at the end. That part of the code is written at this time.
And only now I arrive at the point which you had started out with. My code is remarkably like yours. Note that Copy/PasteSpecial allows you to choose what to paste, in this case "Values". You might use Copy with Destination instead which would also include formats. Or you might specify to copy formulas instead of values. To copy values you could simply use syntax like WsOut.Cells(Rt, 1).Value = .Cells(Rs, 1).Value` in a little loop. Because of the solid fundament on which this code is built it's very easy to modify these two lines using the established components.
The code ends on setting Application.CutCopyMode = False and presenting the result of the action in the display by restoring ScreenUpdating.

Adding “Zebra Stripes” to every second visible row Automatically when AutoFilter changes

I have a report of around 250 rows (varies +/- 15 rows, columns A – J). To make the report easier-on-the-eye for end users, I format every second row with Zebra Stripes. The problem is that whenever a filter is applied to the data, the zebra stripes (rows) sometimes bunch together which defeats the purpose of having them at all.
What I would like to do is have the zebra stripes appear on every second visible row after the filter is changed - and I know such code already exists on this site – but I furthermore want to have this happen automatically without the need to trigger any code.
My current macro works fine as far as adding the formatting – but doesn’t achieve the ideal objective stated above. Any guidance is appreciated.
Sub AddZebraStripes()
Dim c As Range, LastRow As Long
LastRow = Sheet1.Cells(Rows.Count, "A").End(xlUp).Row
Sheet1.Range("A2:J" & LastRow).Interior.ColorIndex = xlNone
For Each c In Sheet1.Range("A2:J" & LastRow)
If c.Row Mod 2 = 0 Then
c.Interior.ThemeColor = xlThemeColorDark1: c.Interior.TintAndShade = -0.15
End If
Next c
End Sub
You can also easily use conditional formatting if you have a column which will always have data in it.
The references are in the formula are pointed to the first cell in the data column of choice
Please note the absolute vs non-absolute cell reference in the formula. Set the format for true, then depending how you want the banding you can reverse "TRUE" and "FALSE" in the formula
=IF(MOD(SUBTOTAL(103,$A$2:$A2),2)=0,"TRUE","FALSE")
UPDATE
To allow for blank values in data column:
=IF(MOD(SUBTOTAL(103,$A$2:$A2)+COUNTBLANK($A$2:$A2),2)=0,"TRUE","FALSE")
I agree with Rory and Siddharth that using a table as the simplest option. However, if you’re stuck on using a VBA approach then this is how I would do it:
You can trigger the code automatically by using a Worksheet_Calculate() event handler – based on a calculation of a formula on the sheet.
First step is to add the following formula to cell K1 (for example based on your description of your data) on your sheet: =SUBTOTAL(3,$A:$A). This particular SUBTOTAL (3,) = COUNTA(). Whenever the filter changes, it will recalculate – even if the same number of rows are visible in Col A. You can hide Col K if you like – it won’t affect the code execution.
Step Two – add the following Worksheet_Calculate() event handler to the Sheet1 module
The following code will run the macro ZebraStripes whenever it detects a calculation in cell K1 on Sheet1.
Private Sub Worksheet_Calculate()
Set Target = Me.Range("K1")
If Not Intersect(Target, Range("K1")) Is Nothing Then
ZebraStripes
End If
End Sub
Step Three – add the following code to a standard module
Option Explicit
Sub ZebraStripes()
Dim LastRow As Long, i As Long
Dim c As Range, r As Range
Dim ws As Worksheet
Set ws = Sheet1
LastRow = ws.Cells(Rows.Count, 1).End(xlUp).Row
i = 1
ws.Range("A2:J" & LastRow).Interior.ColorIndex = xlNone
For Each c In ws.Range("A2:A" & LastRow)
If c.EntireRow.Hidden = False Then
i = i + 1
If i Mod 2 = 0 Then
Set r = Range(c, c.Offset(0, 9))
r.Interior.ThemeColor = xlThemeColorDark1: r.Interior.TintAndShade = -0.15
End If
End If
Next c
End Sub
If you follow the 3 steps above precisely, you should achieve your ideal objective of every second row shaded – automatically whenever there’s a change to the filter on the sheet. Looking at all the steps/code, I think you might be better off with a Table :)

Selecting dynamic, filtered range in VBA

I am attempting to select a dynamic range of filtered data that spans from col. A: col. J without selecting the header (in row 1). From there I need to copy and paste it into a new sheet where I will manipulate it further, but I cannot come up with an efficient or functional way to do this. Based on some code I found on another forum I was able to select all of the "visable cells" in a single column, but I am running into issues trying to select the whole range. I am still very new to vba so forgive my syntax, but my code posted below was an attempt to itterate through Rows.Count and i which was an integer 1-10. If you have any advice on how to do this better and more efficiently I would really appreciate it.
Sub SelectVisibleInColD()
Dim lRow As Long, i As Integer
Set i = 1
Do While i <= 10
With ActiveSheet
lRow = .Cells(.Rows.Count, i).End(xlUp).Row
If lRow < 3 Then Exit Sub
.Cells(1, 1).Offset(1, 0).Resize(lRow - 1).SpecialCells(xlCellTypeVisible).Select
End With
i = i + 1
Loop
End Sub
You can select a range by using Range property of ActiveSheet. You already have the last row and you know that the header is in the first row, so your range starts from position A2 and goes to the last row of column J
ActiveSheet.Range("A2:J"&lRow).SpecialCells(xlCellTypeVisible)
If you want to copy this range, use Copy function like
yourRangeAsAbove.Copy
This function only moves the selection to memory, to paste it, build your destination range and call PasteSpecial function.
I came across this answer googling my issue for: deleting of filtered selection in vba.
However trying your answer &lRow gives me an runtime error 1004, application-defineed or object-defined error
I got around it with this
ActiveSheet.Range("A2:G" & Range("A" & Rows.Count).End(xlUp).Row).SpecialCells(xlCellTypeVisible).Delete
For those that may also get the same issue.

adding new rows in excel without breaking a vba macro that uses Range.Value

I've written a macro in VBA that simply fills in a given cell's value from another cell in that sheet. I do this for lots of cells in the sheet, and I'm doing it like so:
Range("B3").Value = Range("B200")
Range("B4").Value = Range("B201")
'etc.
Now, I am often adding values by inserting new rows, so I might insert a new row
between B200 and B201, which will break the macro because it doesn't autoupdate when
I insert the new row.
How can I code the macro so it autoupdates the cell references when I insert new rows or columns?
My suggestion would be to make sure the ROW you want to retrieve values from has a unique value in it that you can .FIND anytime you want, then grab your values from column B of that found cell's row. So right now you want to get a value in B200 and A200 always has the text in it: "Final Total" and that is unique.
Dim MyRNG As Range
Set MyRNG = Range("A:A").Find("Final Total", LookIn:=xlValues, LookAt:=xlWhole)
Range("B3").Value = Range("B" & MyRNG.Row)
Range("B4").Value = Range("B" & MyRNG.Row + 1)
This is not an answer but an alternative.
Naming your range is the way to go as Shiin suggested but then if you have 500 cells then like I mentioned earlier, naming 500 cells and using them in your code can be very painful. The alternative is to use smart code. Let's take an example
Let's say you have a code like this
Sub Sample()
Range("B3").Value = Range("B200")
Range("B4").Value = Range("B201")
Range("B5").Value = Range("B201")
' And
' So On
' till
Range("B500").Value = Range("B697")
End Sub
The best way to write this code is like this
Sub Sample()
Dim i As Long
For i = 200 To 697
Range("B" & i - 197).Value = Range("B" & i)
Next i
End Sub
and say if you insert a line at say row 300 then simply break the above code in two parts
Sub Sample()
Dim i As Long
For i = 200 To 299
Range("B" & i - 197).Value = Range("B" & i)
Next i
For i = 301 To 698
Range("B" & i - 197).Value = Range("B" & i)
Next i
End Sub
So every time you insert a row, simply break the for loop into an extra part. This looks tedious but is much better than naming 500 cells and using them in your code.
If you are planning to use the macro only once (i.e for 1 time use) then read ahead.
If you are worried that when the user inserts the row then the cells are not updated then you can instead of assigning a value, assign a formula.
For example
Range("B3").Formula = "=B200"
This will put a formula =B200 in cell B3. So next time when you insert a row so that the 200th row moves it's position, you will notice that the formula automatically gets updated in cell B3
HTH
Try giving a name to the range. If you refer to the range by name Excel searches for it and retrieves the rows that defines it. Range names update their definition when new rows are added.
Adding to the above, i think this tutorial illustrates my point:
http://www.homeandlearn.co.uk/excel2007/excel2007s7p6.html this is how to define the name of the range.
This tutorial explains how to use it on macros and vba:
http://excel.tips.net/T003106_Using_Named_Ranges_in_a_Macro.html
I hope this helps :D

Excel Macro: conditional cut and paste between workbooks

So I want a macro running in an Excel file ("input.xls") that is searching a column in another Excel file ("data.xls") for the value "1" (the only values in that columns are 1s and 0s). When it finds a "1," it should copy and paste the entire row from that file into "input.xls".
Here is the code that I have
Sub NonErrorDataParse()
Dim intEnd As Integer
Workbooks("data.xls").Sheets("Raw").Activate
intEnd = 65000
Range("F").Select
Do Until ActiveCell.Row = intEnd
If Int(ActiveCell.Value) = 1 Then
Range(ActiveCell.Row & ":" & ActiveCell.Row).Cut
intEnd = intEnd - 1
Workbooks("input.xls").Sheets("Non-errors").Activate
Range("A1").Select
ActiveSheet.Paste
Else
ActiveCell.Offset(1, 0).Select
End If
Loop
End Sub
However, when I run it, it gives me a "subscript out of range" error on "data.xls." No matter how I fiddle with the code I can't seem to get past that error (even though I have OTHER macros that are accessing that sheet that work fine).
Any ideas as to how to fix it? Or better code that will do the same thing?
Thanks in advance
You don't have to Select or Activate each time you do a command.
You can also find the last used cell with Range("A65536").End(xlup) instead of parsing every cell (that probably caused your error).
The code would then look like:
Sub NonErrorDataParse()
Dim intEnd As Integer
Dim c As Range
intEnd = Workbooks("data.xls").Sheets("Raw").Range("A65536").End(xlUp).Row
For Each c In Workbooks("data.xls").Sheets("Raw").Range("F1:F" & intEnd)
If CStr(c.Value) = "1" Then
c.EntireRow.Cut
Workbooks("input.xls").Sheets("Non-errors").Rows("1:1").Insert Shift:=xlDown
End If
Next c
End Sub
Yet, if you have many rows, it would be faster to use the autofilter method or use a dictionary.

Resources