Intersect not working if target range gets bigger - vba

I am relatively new to VBA and I need help with this please.
I have a private sub within a sheet and I want it to autofill formulas adjacent to a dynamic named range, if the size of the range changes.
(edit) I am pasting data from another worksheet into this one columns A-M. My dynamic range is defined as =OFFSET($A$1,1,0,COUNTA($A:$A)-1,13). The first If statement should exit the sub if there is no data in column M and I had the destination calculating the last row of column M because I want to fill the formulas in N:O so that they cover the same number of rows as column M.
This is my code and it works if the size of the range gets smaller (i.e. if I delete rows from the bottom), but not if it gets bigger and I can't work out why!
Private Sub Worksheet_Change(ByVal Target As Range)
If Me.Range("M2").Value = "" Then
MsgBox "No Data!"
Exit Sub
Else
If Intersect(Target, Me.Range("rngOracleInvoices")) Is Nothing Then
Application.EnableEvents = False
Dim Lrows As Long
Lrows = Me.Cells(Me.Rows.Count, "N").End(xlUp).Row
Me.Range(Me.Cells(3, 14), Me.Cells(Lrows, "O")).ClearContents
Me.Range("N2:O2").AutoFill Destination:=Me.Range("N2:O" & Me.Range("M" & Me.Rows.Count).End(xlUp).Row)
End If
End If
Application.EnableEvents = True
End Sub
I put the last bit into a separate macro to test if it works on its own and for some reason, when I run it, the autofill goes all the way up to row 1 and overwrites the formulas, which is weird because I use that code a lot and it's never done that before. What have I done??!!
Also, if there is a better way to do the autofill I'd appreciate if someone could let me know what it is because I just cobbled that together from bits I found on forums :)
Thanks,
Soph

In this line Me.Range("N2:O2").AutoFill Destination:=Me.Range("N2:O" & Me.Range("M" & Me.Rows.Count).End(xlUp).Row) you calculate your last row on the column M so if it is empty it'll give you 1 and autofill your formula on row 1.
So start by calculating it on the good column (my guess is O)
You can also simply define an Integer variable to test it and if it is inferior to 2, change it back to 2, 3, 4 or whatever you want.
For your dynamic range, we might need some precision.
And for the AutoFill, you could just select manually the range N2:02 and then double-click on the bottom right square (the one you drag to autofill) and it'll autofill as long as there data in adjacent cells! (give it a try ;) )

Related

Embedded "IF" formula breaks occasionally, VBA alternative?

I have a very large embedded IF formula that appears to occasionally break for no reason. Opening and closing the page a few times eventually gets it working again. I am wondering if there is a VBA alternative for it. Here is the IF formula I am running.
=IF(ISNUMBER(SEARCH("76210",E125)),"_012_00762_10",IF(ISNUMBER(SEARCH("76220",E125)),"_012_00762_20",IF(ISNUMBER(SEARCH("76900",E125)),"_012_00769_00",IF(ISNUMBER(SEARCH("76901",E125)),"_012_00769_01",IF(ISNUMBER(SEARCH("85702",E125)),"_012_00857_02",IF(ISNUMBER(SEARCH("85710",E125)),"_012_00857_10",IF(ISNUMBER(SEARCH("100800",E125)),"_012_01008_00",IF(ISNUMBER(SEARCH("100900",E125)),"_012_01009_00",IF(ISNUMBER(SEARCH("123100",E125)),"_012_01231_00",IF(ISNUMBER(SEARCH("124600",E125)),"_012_01246_00",IF(ISNUMBER(SEARCH("124601",E125)),"_012_01246_01",IF(ISNUMBER(SEARCH("124640",E125)),"_012_01246_40",IF(ISNUMBER(SEARCH("124641",E125)),"_012_01246_41",IF(ISNUMBER(SEARCH("142301",E125)),"_012_01423_01",IF(ISNUMBER(SEARCH("158801",E125)),"_012_01588_01",IF(ISNUMBER(SEARCH("158900",E125)),"_012_01589_00",IF(ISNUMBER(SEARCH("159203",E125)),"_012_01592_03",IF(ISNUMBER(SEARCH("159303",E125)),"_012_01593_03",IF(ISNUMBER(SEARCH("159401",E125)),"_012_01594_01",IF(ISNUMBER(SEARCH("159410",E125)),"_012_01594_10",IF(ISNUMBER(SEARCH("159420",E125)),"_012_01594_20",IF(ISNUMBER(SEARCH("159501",E125)),"_012_01595_01",IF(ISNUMBER(SEARCH("169000",E125)),"_012_01690_00",IF(ISNUMBER(SEARCH("186900",E125)),"_012_01869_00",IF(ISNUMBER(SEARCH("213200",E125)),"_012_02132_00",IF(ISNUMBER(SEARCH("213300",E125)),"_012_02133_00",IF(ISNUMBER(SEARCH("215400",E125)),"_012_02154_00",IF(ISNUMBER(SEARCH("220100",E125)),"_012_02201_00",IF(ISNUMBER(SEARCH("223800",E125)),"_012_02238_00",IF(ISNUMBER(SEARCH("225600",E125)),"_012_02256_00",IF(ISNUMBER(SEARCH("230700",E125)),"_012_02307_00",IF(ISNUMBER(SEARCH("230701",E125)),"_012_02307_01",IF(ISNUMBER(SEARCH("231800",E125)),"_012_02318_00",IF(ISNUMBER(SEARCH("235000",E125)),"_012_02350_00",IF(ISNUMBER(SEARCH("235020",E125)),"_012_02350_20",IF(ISNUMBER(SEARCH("242000",E125)),"_012_02420_00",IF(ISNUMBER(SEARCH("246400",E125)),"_012_02464_00",IF(ISNUMBER(SEARCH("292900",E125)),"_012_02929_00",""))))))))))))))))))))))))))))))))))))))
Basically it is built so a serial number is scanned and it populates a cell for the users who use this sheet with its results from the search. I am already running one macro in this sheet as well. Here is that...
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
Dim rng As Range
Set rng = Intersect(Range("A2:A500, J2:J500"), Target) ' define range of interest
If Not rng Is Nothing Then ' check it's not "nothing"
If WorksheetFunction.CountA(rng) = rng.Count Then 'check for all of its cells being not empty
On Error GoTo safe_exit 'add error control
Application.EnableEvents = False 'don't do anything until you know something has to be done
rng.Offset(, 1).Value = Date 'write Date next to all relevant changed cells
End If
End If
safe_exit:
Application.EnableEvents = True
End Sub
Maybe there is a better way to build this search using a formula that isn't using embedded IF statements, but i couldn't think of another way to do it. Thanks in advance.
This may be what you're looking for:
=IF(ISNA(MATCH(1,IF(ISERR(SEARCH($A$5:$A$42,$E$125)),0,1),0)),"",INDEX($B$5:$B$42,MATCH(1,IF(ISERR(SEARCH($A$5:$A$42,$E$125)),0,1),0)))
entered as an array formula (CTRL-SHIFT-ENTER).
Here $A$5:$A$42 contains 76210, 76220, ... , 292900 (entered as text, not numbers); and $B$5:$B$42 contains _012_00762_10, _012_00762_20, ... , _012_02929_00.
Hope that helps.
Any time you have to go more than 2 deep on an IF you may want to rethink the usage.
What you can do is build a table from your values. Then reference that table as part of your lookup. Assuming your list of value is in range D8:E45 you could use the formula =VLOOKUP(E125,$D$8:$E$45,2).
The beginning of your table would look like what's seen below. The input result cell is referencing your input value and pulling the match of the second column.
To get your table you can take your source formula and replace (Find and Replace - Ctrl+H) some characters with unique delimiting characters. Then use Text To Columns Alt+D+E and delimit and Copy>Paste special>Transpose to quickly have it close to the format you need.

Excel VBA Dynamic Range / Loop issue

I am developing a financial model for a bank and come across the below issue which I am not able to resolve in Excel VBA, and would appreciate your help.
I have built a simple macro which essentially does two things: (i) it clears contents in a given range, (ii) it populates the same range with a formula. In a very abbreviated way it looks like the following:
Sub AutoCalculateCashFlows()
Range(D208:L208).ClearContents
Range("L208").FormulaR1C1 = "=+R[-34]C-R[-34]C[-1]"
Range("L208").AutoFill Destination:=Range("E208:L208"), Type:=xlFillDefault
End Sub
My problem is that the range that should be auto populated is dependent on how many cells did the user fill in within the range of E10:L10. Users will start populating this range from right to left, but I don't know how far they will go from column L to the left. The formula that my macro auto populates needs at least two data, ie. at least L10 and K10 should be populated and if the latter is the case then the macro only needs to auto populate L208 with formula, in case J10:L10 is filled out then the macro needs to auto populate the range L208:K208 and so on to the point that in case the full D10:L10 range is filled out then E208:L208 should be populated with formula.
I have thought to resolve this issue via two routes: (i) approaching it as a dynamic range problem in which case I need a vba code to determine the previous to the last cell populated by the user in the range D10:L10 and use the column code of that cell in "Destination:=Range("E208:L208")", or (ii) run a loop which will populate range E208:L208 with formula until the cell in the previous column within range D10:L10 is filled in by the user and stop when it is not.
Hope this makes sense and thanks in advance for the help.
When you need a dynamic range in VBA, you should simply build one. This is probably the easiest method:
Sub TestMe()
Dim colRange As Long
Dim rowRange As Long
Dim rngMain As Range
rowRange = 10
With Worksheets(1)
colRange = .Cells(1, .Columns.Count).End(xlToLeft).Column
Set rngMain = .Range(.Cells(rowRange, colRange), .Cells(100, 200))
MsgBox rngMain.Address
End With
End Sub
It is dynamic, based on the last used column in row 1 of the first Worksheet.
Concerning the second used column in Row 1, one of these 3 would probably do it for you, depending on what exactly do you want:
.Cells(1, 1).End(xlToRight).End(xlToRight).Column
.Cells(1, 1).End(xlToRight).Column
.Cells(1, 1).End(xlToRight).Column + 1

copy row to next free row on another spreadsheet on change

First off, I'm a noob when it comes to Macros and VBA, so please forgive me if I don't make sense.
I've got an Excel spreadsheet which is basically a list of users and their mobile phone numbers and some other bits (columns A-K are currently used) and it's ordered by rows.
What I need is a way of copying the whole row if I change a cell. So if I change the username, it copies the whole row of that user to the next blank row on a second sheet.
The purpose of this is to keep an audit trail allowing us to see who's previously used a number etc.
I found this: Copy row to another sheet in excel using VBA which is working as intended, but I can't for the life of me get it to a, copy the cells to the next free row, or b, not overwrite the existing entry.
This is the code I'm using:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim a As Range, rw As Range
For Each a In Selection.Areas
For Each rw In a.Rows
If rw.Row >= 2 Then
rw.EntireRow.Copy Sheet2.Cells(2 + (rw.Row - 2) * 3, 1)
End If
Next rw
Next a
End Sub
I'd really appreciate it if someone could help me customise it.
I'm using Excel 2010 on Win7.
Many thank in advance.
Typically the Intersect method is used to determine if the cell or cells receiving a change involve one or more columns that you are concerned with. You can add additional parameters; in this case, I've .Offset the Worksheet.UsedRange property down one row to make sure that row 1 is not involved.
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
If Not Intersect(Target, Columns(1), Me.UsedRange.Offset(1, 0)) Is Nothing Then
On Error GoTo bm_Safe_Exit
Application.EnableEvents = False 'not really necessary in this case but never a bad idea within a Worksheet_Change
Dim a As Range
For Each a In Intersect(Target, Columns(1), Me.UsedRange.Offset(1, 0))
If CBool(Len(a.Value2)) Then _
a.EntireRow.Copy _
Destination:=Sheet2.Cells(Rows.Count, 1).End(xlUp).Offset(1, 0) 'not really sure this is the correct destination
Next a
End If
bm_Safe_Exit:
Application.EnableEvents = True
End Sub
I've included a call to disable event handling for the duration of the Worksheet_Change event macro. While this is a critical step when the Worksheet_Change modifies values, it is not really important to incorporate here. However, it does not harm and is already in place in case you want to augment the Worksheet_Change to include something like a timestamp that would change the values on the worksheet.

Excel macro select two ranges and compare

This is a question that was asked to me in an interview. I have a excel list. It is copied to another location and then by mistake a row in the new location gets deleted.
Now I need to write a macro to compare the old and new ranges and then provide the missing data as result.
I can perhaps perform the comparison part. But the problem is I don't know how to get the selected range as input in a macro.
For eg. as soon as I select a range, it should be sent as input to the macro, then the macro should wait for another selection. As soon as I select the new range, the macro should compare and find the missing lines in new range.
Regarding the selection per mouse click you could look at the link I sent in the comments of the other answer. Selection_Change is an event which gets triggered when you change the selection of a worksheet (not only mouseclick but move-by-keys as well). The target coming in is the cell which you have selected. You can pass this as a range on to a function.
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
showMsg Target
End Sub
Private Function showMsg(r As Range)
MsgBox r.Address
End Function
You can just as well use another event like BeforeDoubleClick or BeforeRightClick. Check out the events of Excel and choose the one you feel fits best.
If you only want the function to be triggered for a certain range you can filter it.
If target.column <> 1 then exit function
If you don't want the event to trigger your function each time you change a selection you can choose one cell to be the switch which gets triggered by the same event.
If target.address = "$A$1" Then Call toggleSearch()
with toggleSearch being the switching function.
This is a classical diff (and a simple one at that), you shouldn't select by hand or anything. Just sort the two lists in an identical way, then run a Sub which loops over the number of rows in the source sheet comparing each row with the same row in the target sheet. The first mismatch you get is the missing line.
This example assumes both sheets are in the same workbook but you can easily adapt it
Public Sub diffThem()
Dim src as Worksheet, trg as Worksheet
Dim r as Range, i as Integer
Set src = ThisWorkbook.Sheets("Source")
Set trg = ThisWorkbook.Sheets("Destination")
Set r = src.Range("A1")
For i = 1 to ThisWorkbook.Sheets("Source").UsedRange.Rows.Count
If r.EntireRow <> trg.Range("A" & r.Row).EntireRow Then
MsgBox("The missing row is " & r.Row)
Exit Sub
End if
Set r = r.Offset(1,0)
Next i
End Sub
If EntireRow cannot be run due to different layouts or whatever then loop the columns at that point.

Get the cell reference of the value found by Excel INDEX function

The Problem
Assume that the active cell contains a formula based on the INDEX function:
=INDEX(myrange, x,y)
I would like to build a macro that locates the value found value by INDEX and moves the focus there, that is a macro changing the active cell to:
Range("myrange").Cells(x,y)
Doing the job without macros (slow but it works)
Apart from trivially moving the selection to myrange and manually counting x rows y and columns, one can:
Copy and paste the formula in another cell as follows:
=CELL("address", INDEX(myrange, x,y))
(that shows the address of the cell matched by INDEX).
Copy the result of the formula above.
Hit F5, Ctrl-V, Enter (paste the copied address in the GoTo dialog).
You are now located on the very cell found by the INDEX function.
Now the challenge is to automate these steps (or similar ones) with a macro.
Tentative macros (not working)
Tentative 1
WorksheetFunction.CELL("address", ActiveCell.Formula)
It doesn't work since CELL for some reason is not part of the members of WorksheetFunction.
Tentative 2
This method involves parsing the INDEX-formula.
Sub GoToIndex()
Dim form As String, rng As String, row As String, col As String
form = ActiveCell.Formula
form = Split(form, "(")(1)
rng = Split(form, ",")(0)
row = Split(form, ",")(1)
col = Split(Split(form, ",")(2), ")")(0)
Range(rng).Cells(row, CInt(col)).Select
End Sub
This method actually works, but only for a simple case, where the main INDEX-formula has no nested subformulas.
Note
Obviously in a real case myrange, x and ycan be both simple values, such as =INDEX(A1:D10, 1,1), or values returned from complex expressions. Typically x, y are the results of a MATCH function.
EDIT
It was discovered that some solutions do not work when myrange is located on a sheet different from that hosting =INDEX(myrange ...).
They are common practice in financial reporting, where some sheets have the main statements whose entries are recalled from others via an INDEX+MATCH formula.
Unfortunately it is just when the found value is located on a "far" report out of sight that you need more the jump-to-the-cell function.
The task could be done in one line much simpler than any other method:
Sub GoToIndex()
Application.Evaluate(ActiveCell.Formula).Select
End Sub
Application.Evaluate(ActiveCell.Formula) returns a range object from which the CELL function gets properties when called from sheets.
EDIT
For navigating from another sheet you should first activate the target sheet:
Option Explicit
Sub GoToIndex()
Dim r As Range
Set r = Application.Evaluate(ActiveCell.Formula)
r.Worksheet.Activate
r.Select
End Sub
Add error handling for a general case:
Option Explicit
Sub GoToIndex()
Dim r As Range
On Error Resume Next ' errors off
Set r = Application.Evaluate(ActiveCell.Formula) ' will work only if the result is a range
On Error GoTo 0 ' errors on
If Not (r Is Nothing) Then
r.Worksheet.Activate
r.Select
End If
End Sub
There are several approaches to select the cell that a formula refers to...
Assume the active cell contains: =INDEX(myrange,x,y).
From the Worksheet, you could try any of these:
Copy the formula from the formula bar and paste into the name box (to the left of the formula bar)
Define the formula as a name, say A. Then type A into the Goto box or (name box)
Insert hyperlink > Existing File or Web page > Address: #INDEX(myrange,x,y)
Adapt the formula to make it a hyperlink: =HYPERLINK("#INDEX(myrange,x,y)")
Or from the VBA editor, either of these should do the trick:
Application.Goto Activecell.FormulaR1C1
Range(Activecell.Formula).Select
Additional Note:
If the cell contains a formula that refers to relative references such as =INDEX(A:A,ROW(),1) the last of these would need some tweaking. (Also see: Excel Evaluate formula error). To allow for this you could try:
Range(Evaluate("cell(""address""," & Mid(ActiveCell.Formula, 2) & ")")).Select
This problem doesn't seem to occur with R1C1 references used in Application.Goto or:
ThisWorkbook.FollowHyperlink "#" & mid(ActiveCell.FormulaR1C1,2)
You could use the MATCH() worksheet function or the VBA FIND() method.
EDIT#1
As you correctly pointed out, INDEX will return a value that may appear many times within the range, but INDEX will always return a value from some fixed spot, say
=INDEX(A1:K100,3,7)
will always give the value in cell G3 so the address is "builtin" to the formula
If, however, we have something like:
=INDEX(A1:K100,Z100,Z101)
Then we would require a macro to parse the formula and evaluate the arguments.
Both #lori_m and #V.B. gave brilliant solutions in their own way almost in parallel.
Very difficult for me to choose the closing answer, but V.B. even created Dropbox test file, so...
Here I just steal the best from parts from them.
'Move to cell found by Index()
Sub GoToIndex()
On Error GoTo ErrorHandler
Application.Goto ActiveCell.FormulaR1C1 ' will work only if the result is a range
Exit Sub
ErrorHandler:
MsgBox ("Active cell does not evaluate to a range")
End Sub
I associated this "jump" macro with CTRL-j and it works like a charm.
If you use balance sheet like worksheets (where INDEX-formulas, selecting entries from other sheets, are very common), I really suggest you to try it.