My file has two identical Worksheets for users to input two different sets of assumption variables, called "InputA" and "InputB". I want to quickly switch which Input sheet is feeding into the other sheets of the model.
Using Find and Replace took over 5 minutes, and there were over 350,000 references to "InputA".
I tried the following macro, which takes an instant to run, but unfortunately also changes all references in the workbook, effectively keeping everything referenced to the original input sheet.
Sheets("InputA").Name = "temp"
Sheets("InputB").Name = "InputA"
Sheets("temp").Name = "InputB"
Is there a way to execute the macro but prevent any references to worksheets from changing to the new sheet name, basically freezing everything except the sheet name change? Or perhaps any other solution that will work quickly? I don't want to go through 350,000 instances and rewrite using INDIRECT(), as that is the only other solution I've seen, because my references are complex and nested and that will take an age.
Thanks.
Assuming that your 2 Input-Sheets have the same structure, I would suggest the following:
Create a named range on Workbook-level, name it for example InputData. This range should contain all data from InputA.
Create a helper-sheet and name it Input - you can later set it to hidden.
Mark the range in the new sheet that is exactly the size of the Input-Data-Range and enter the formula =InputData as Array-formula. You can do so by entering Ctrl+Shift+Enter. The formula should have curly brackets and the sheet should now display the data of InputA.
Change all you formulas to refer to the helper Sheet Input instead of InputA.
Create a macro:
Sub switchInput()
Const sheetAName = "InputA"
Const sheetBName = "InputB"
With ThisWorkbook.Names("inputData")
If InStr(.RefersTo, sheetAName) > 0 Then
.RefersTo = Replace(.RefersTo, sheetAName, sheetBName)
Else
.RefersTo = Replace(.RefersTo, sheetBName, sheetAName)
End If
End With
End Sub
This routine will just change the definition of the named range to point either to the first or second input sheet. As a consequence, the helper sheet will show the data of the selected Input-Sheet. All your formulas itself stays unchanged.
As stated in the comments, you could take the approach recommended by Damian and use a conditional in all relevant cells. The generic conditional would be
=IF(A1="InputA",FORMULA INPUTA,FORMULA INPUTB)
This formula makes A1 the cell that decides which input to pull. This will make changing the around 350.000 output formulas in your workbook the bottleneck, the following macro takes care of changing all the formulas to conatin the conditional:
Sub changeFormulas()
Dim rng As Range, cll As Range
Set rng = shOutput.Range("A2:C10") 'Your relevant worksheet and range here
Dim aStr As String, bStr As String, formulaStr As String
aStr = "InputA"
bStr = "InputB"
For Each cll In rng
If cll.HasFormula And InStr(1, cll.Formula, aStr, 1) Then
formulaStr = Right(cll.Formula, Len(cll.Formula) - 1)
cll.Formula = "=IF(A1=" & Chr(34) & aStr & Chr(34) & "," & formulaStr & "," & Replace(formulaStr, aStr, bStr) & ")" 'Change A1 to the reference most suited for your case
End If
Next cll
End Sub
This might take a bit of time, since it has to access all the relevant cells one by one, but it will only have to run once.
To explain: This macro will go through all the cells in your range rng specified at the top. If a cell has a formula in it and the formula contains "InputA" it will change that formula to fit the generic conditional (with the cells own formula of course). Chr(34) is the quotation mark ", I find using Chr(34) more readable than multiple quotation marks """, but that is just preference.
Related
I am trying to define a range starting at B2 (constant) to the last cell with data which will change month to month. I want to take the same range length and define another range for column A which will also start at A2 (constant) but will extend only down as far as column B goes. I'm trying to identify them as range and use the dimmed range in a formula in vba but it doesn't like it...any ideas?
Dim Data As range
Dim Time As range
range("b2").Select
'Select Range
Set Data = range("B2", range("B2").End(xlDown))
Set Time("A2", range("A2").End(xlDown))
ActiveCell.Offset(1, 1).Select
ActiveCell.FormulaR1C1 = _
"=FORECAST.ETS([#Timeline],.address(data),.address(time):R[-1]C[-2],1,0)"
You need to close the formula string, add the address, and then continue:
"=FORECAST.ETS([#Timeline],.address(" & data.address & "),.address(" &
time.address & "):R[-1]C[-2],1,0)"
Note since you're using R1C1 style, you might have to do this on both .address parts,
time.address(ReferenceStyle:=xlR1C1)
so:
"=FORECAST.ETS([#Timeline],.address(" & data.address(ReferenceStyle:=xlR1C1) & "),.address(" &
time.address(ReferenceStyle:=xlR1C1) & "):R[-1]C[-2],1,0)"
Edit: Also, I would change the keyword Time, as I think that's a reserved word. Perhaps Dim timeRng as Range?
In addition to #BruceWayne's answer, to address the first part of your question:
If I have a range B2:B50, and I want the corresponding A column, then I can use the Offset function:
Set time = data.Offset(columnOffset:=-1)
Alternatively you can construct the column like this:
Set time = Sheet1.Range("A2").Resize(Rows(data), 1) 'nrows, 1 column
Then you could put A2 anywhere
FWIW:
range("b2").Select is unnecessary and will really slow down your code if you get into this habit (it's just because the macro recorder doesn't know what you want exactly). You could use Range("B2").Offset(1,1).FormulaR1C1 with no selecting
You can name cells in excel and refer to the names: Range("myNamedCell")
Always best practice to prepend the sheet name and fully qualify references (e.g. Sheet1.Range("A1")) since that will always refer to the same cell, whereas Range("A1") refers to A1 on whichever sheet happens to be selected when you run the macro
I have a formula in A1 that is fed with data from a different workbook. It's only a reference to this other workbook, there aren't any calculations.
This second workbook is updated on a monthly basis so the cell I'm interested in referring to is offset one cell to the right each month.
How can I write a macro that tells my current formula in A1 to use the same formula but moving it one place to the right? It'd be something like: [Book1]Sheet1!C15 to [Book1]Sheet1!D15. Thanks!
Use Range.Precedents to get the cells a particular Range depends on.
'get the cell:
Dim theCell As Range
Set theCell = ActiveSheet.Range("A1")
'get its first "precedent" Range:
Dim precedent As Range
Set precedent = theCell.Precedents(1)
'rewrite the formula, offsetting the precedent by 1 column:
theCell.Formula = "=" & precedent.Offset(ColumnOffset:=1).Address(External:=True)
Obviously this makes a lot of assumptions and will need to be adjusted to your specific needs, but you don't need to parse any formulas to offset its precedent cells when you're looking at a formula that's simply =SomeCellAddress.
First put this small UDF in a standard module:
Public Function NextCellOver(s As String) As String
arr = Split(s, "!")
addy = Range(arr(1)).Offset(0, 1).Address
NextCellOver = arr(0) & "!" & addy
End Function
It will accept a string that ends with something like !Z99 and return a string ending with !AA99. (Basically one column to the right.)
Then enter:
Sub marine()
With Range("A1")
.Formula = NextCellOver(.Formula)
End With
Application.CalculateFullRebuild
End Sub
To apply this to the cell in question.
For example. I want my intro sheet "Main Sheet" to have an option to switch the workbook between currencies. USD and AED at the rate of 3.68. Some cells are referencing other cells in different sheets, so I don't want to change the cell references, I only need to calculate the rate in specific cells within each sheet.
How can I accomplish this preferably using a check box or button for easy converting from the start. I'm using excel for Mac. Thank you
Create a cell with a validation drop-down allowing to choose between AED and USD. Convert that cell to a named range for easy referencing throughout the workbook. You might call it "Curr", short for "Currency" (short because it will be used often).
I recommend that you create a similar cell somewhere where you enter the rate, currently 3.68 but plan on changing the rate in that cell only and have it applied to all the workbook. Name that cell as "Rate".
Now all cells containing values which you may want switched would be subject to the following formula. =[CellValue] * IF(Curr = "AED", Rate, 1). This formula presumes that the values are all entered in USD. If they are entered in AED the formula should look as follows. = ROUND([CellValue] / IF(Curr = "AED", 1, Rate), 2)
As you see, this solution would require the original cell values to be recorded somewhere, meaning, the cells used for data capture can't be the same as the ones used for data display. If you wish to insist on capture and display being in the same cell you would need code to do the conversion.
On the face of it this seems simple: When the Curr selection is changed, all cells with affected values are re-calculated. In practise this would end in disaster because there are 1001 ways in which something might go wrong and then you would lose all your data, not knowing whether the values are USD or AED at that moment.
Therefore the starting point needs to be to separate data capture and data display. Once that is done workheet functions might well be not only the easiest but also the most efficient way of achieving what you want.
I'm going to assume that you want to have the conversion on the input cell and not all of your cells are formulas and that a lot of the cells you want to convert are values. You should seriously consider the answer to split out input vs display, it will be much more foolproof and protected from any logic that may break your workbook.
If you're keen on this pathe then do the following, but, before you do ... BACKUP YOUR WORKBOOK. Any tests I've done with the below code are not breaking but I don't have your workbook, therefore, I make no guarantees.
Firstly, you need a cell that gives you the current exchange rate. You need to give that cell a named range of ExchangeRate.
In my workbook, that cell contains a formula ...
=IF(B1="USD",1,3.68)
It looks like this ...
... and cell B1 has a validation attached to it that allows you to select from 2 currencies, AED or USD.
You said you want to be able to ensure that only a selection of cells will be converted. To make sure we ring fence just those cells, you need to create a named range ON EACH SHEET that includes all of those cells.
The name of that range needs to be called CellsToConvert and you can do that through the Name Manager. When creating the named range, make sure you specify the worksheet you're creating it for, do not selected the "Workbook" option.
... the below shows the sporadic range I used on the first sheet. All coloured cells a part of that range. The green cells contain values and the yellow cells contain formulas.
At the end of the day, that range can be huge and across different sheets but it should work.
Now, add the following code into the ThisWorkbook object within the VBA editor ...
Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range)
Dim objCell As Range, dblExRate As Double, strFormula As String, objSheet As Worksheet
Dim strNewFormula As String, strOpeningChar As String, bIsFormula As Boolean
Dim objCells As Range, strError As String, strExRateRangeName As String
strExRateRangeName = "ExchangeRate"
dblExRate = Range(strExRateRangeName)
Application.EnableEvents = False
For Each objSheet In Worksheets
On Error Resume Next
strError = ""
Err.Clear
Set objCells = objSheet.Range("CellsToConvert")
strError = Err.Description
On Error GoTo 0
If strError = "" Then
For Each objCell In objCells
strFormula = objCell.FormulaR1C1
bIsFormula = False
' Check to make sure this field contains a formula.
If Left(strFormula, 1) = "=" And objCell.NumberFormat <> "#" Then
bIsFormula = True
End If
If dblExRate = 1 Then
' Base currency selected.
' Check to see if the cell contains a formula, if it does,
' convert it back to a value
If bIsFormula Then
' It's a formula and the cell is not set to text, proces it back
' to its original value, that could still be a formula.
' Remove all of the exchange rate components we would've added as
' a part of this routine.
strNewFormula = Replace(strFormula, ") * " & strExRateRangeName, "")
' Check to see if the formula has changed against the previous statement,
' if it has, then it contained the custom additions, otherwise, it didn't.
If strFormula <> strNewFormula Then
strNewFormula = Mid(strNewFormula, 3)
' Check to see if the new value is numeric, if it is, remove the leading
' equals sign as it wasn't originally a formula, or, at least it doesn't
' need to be a formula.
If IsNumeric(strNewFormula) Then
objCell.Value = strNewFormula
Else
objCell.FormulaR1C1 = "=" & strNewFormula
End If
End If
End If
Else
' Something other than the base currency has been selected.
strNewFormula = objCell.FormulaR1C1
If InStr(1, strNewFormula, strExRateRangeName, vbTextCompare) = 0 Then
If bIsFormula Then strNewFormula = Mid(objCell.FormulaR1C1, 2)
objCell.FormulaR1C1 = "=(" & strNewFormula & ") * " & strExRateRangeName
End If
End If
Next
End If
Next
Application.EnableEvents = True
End Sub
... once you've done all of the above, it should work for you. Performance could be tested if the workbook is large but that's something you'll need to check for yourself.
If you change a cell and it's within one of those ranges AND the currency of USD is not selected, you'll see the input value changed to a formula after you hit enter. That's pretty neat when you think about it but may not be for you.
One last thing to note, if your range contains broken links, the calculation for that sheet will fail and my code will not notify you of that.
This adds another option for you but is riskier than the first answer. There's nothing like options. :-)
I have a single worksheet with sheets Sheet1 and Sheet2 and I am trying to reference a range of cells from Sheet2 to Sheet1
I know how to reference worksheet cells such as =Sheet2!A1 but how can I do the same for a cell range such as A1:F1 I tried =Sheet2!A1:F1 but it does not like the syntax.
I need to use Excel Formulas for this if possible.
Simple ---
I have created a Sheet 2 with 4 cells and Sheet 1 with a single Cell with a Formula:
=SUM(Sheet2!B3:E3)
Note, trying as you stated, it does not make sense to assign a Single Cell a value from a range. Send it to a Formula that uses a range to do something with it.
The formula that you have is fine. But, after entering it, you need to hit Control + Shift + Enter in order to apply it to the range of values. Specifically:
Select the range of values in the destination sheet.
Enter into the formula panel your desired formula, e.g. =Sheet2!A1:F1
Hit Control + Shift + Enter to apply the formula to the range.
Ok Got it, I downloaded a custom concatenation function and then just referenced its cells
Code
Function concat(useThis As Range, Optional delim As String) As String
' this function will concatenate a range of cells and return one string
' useful when you have a rather large range of cells that you need to add up
Dim retVal, dlm As String
retVal = ""
If delim = Null Then
dlm = ""
Else
dlm = delim
End If
For Each cell In useThis
if cstr(cell.value)<>"" and cstr(cell.value)<>" " then
retVal = retVal & cstr(cell.Value) & dlm
end if
Next
If dlm <> "" Then
retVal = Left(retVal, Len(retVal) - Len(dlm))
End If
concat = retVal
End Function
If you wish to concatenate multiple cells from different sheets, and you also want to add a delimiter between the content of each cell, the most straightforward way to do it is:
=CONCATENATE(Sheet1!A4, ", ", Sheet2!A5)
This works only for a limited number of referenced cells, but it is fast if you have only of few of these cells that you want to map.
You can put an equal formula, then copy it so reference the whole range (one cell goes into one cell)
=Sheet2!A1
If you need to concatenate the results, you'll need a longer formula, or a user-defined function (i.e. macro).
=Sheet2!A1&Sheet2!B1&Sheet2!C1&Sheet2!D1&Sheet2!E1&Sheet2!F1
Its quite simple but not easy to discover --- Go here to read more. its from the official microsoft website
Step 1 -
Click the cell or range of the source sheet (that contains the data you want to link to)
Step 2
Press Ctrl+C, or go to the Home tab, and in the Clipboard group, click Copy Button image .
Step 3
Clipboard group on the Home tab
Step 4
Press Ctrl+V, or go to the Home tab, in the Clipboard group, click Paste Link Button. By default, the Paste Options Button image button appears when you paste copied data.
Step 5
Click the Paste Options button, and then click Paste Link .
I rewrote the code provided by Ninja2k because I didn't like that it looped through cells. For future reference here's a version using arrays instead which works noticeably faster over lots of ranges but has the same result:
Function concat2(useThis As Range, Optional delim As String) As String
Dim tempValues
Dim tempString
Dim numValues As Long
Dim i As Long, j As Long
tempValues = useThis
numValues = UBound(tempValues) * UBound(tempValues, 2)
ReDim values(1 To numValues)
For i = UBound(tempValues) To LBound(tempValues) Step -1
For j = UBound(tempValues, 2) To LBound(tempValues, 2) Step -1
values(numValues) = tempValues(i, j)
numValues = numValues - 1
Next j
Next i
concat2 = Join(values, delim)
End Function
I can't help but think there's definitely a better way...
Here are steps to do it manually without VBA which only works with 1d arrays and makes static values instead of retaining the references:
Update cell formula to something like =Sheet2!A1:A15
Hit F9
Remove the curly braces { and }
Place CONCATENATE( at the front of the formula after the = sign and ) at the end of the formula.
Hit enter.
If these worksheets reside in the same workbook, a simple solution would be to name the range, and have the formula refer to the named range. To name a range, select it, right click, and provide it with a meaningful name with Workbook scope.
For example =Sheet1!$A$1:$F$1 could be named: theNamedRange. Then your formula on Sheet2! could refer to it in your formula like this: =SUM(theNamedRange).
Incidentally, it is not clear from your question how you meant to use the range. If you put what you had in a formula (e.g., =SUM(Sheet1!A1:F1)) it will work, you simply need to insert that range argument in a formula. Excel does not resolve the range reference without a related formula because it does not know what you want to do with it.
Of the two methods, I find the named range convention is easier to work with.
I am tasked with creating A map of our warehouse.
In the data I have to have model, description and location.
What I am having trouble with is, I am using data from a second sheet to populate the "map"
i.e. ='1'!F2
when I try to drag and use it to fill an entire line it changes to ='1'!g2. I would like it to go to ='1'!F3
I see the logic in what it is doing...but I dont want it to use that logic..I want it to use the next cell below it to populate that cell.
The simplest thing might be to Copy and then Paste Special > Transpose the data on "1" to a new sheet. Then you could drag formulas that refer to the new sheet and they'd behave as expected.
EDIT: Based on your original question, this will fill in the results of columns to the right as you drag it down and vice-versa. This literally does what your original question asked:
=INDEX(Sheet1!$F$2:$Z$8000,COLUMN(),ROW())
Start in A1 and drag in either direction. To add a header line or rows to left just insert rows or columns to top or left (to keep the formula sound).
EDIT: Here's the Transpose function, per #brettdj's suggestion. I find it difficult to work with, but it certainly makes it clearer what's going on:
In cells F2:8000 of your target sheet enter:
=TRANSPOSE(Sheet1!$F2:$Z8000)
Then, with all those cells selected, go into edit mode in one of the cells and do Ctrl Shft Enter to array-enter it. If you have to resize the source range I believe you have to repeat these steps with the correct ranges. I'm an Index fan myself, so would stick with that. Offset is volatile, so I'd avoid it. If I've got any of this last edit wrong, #brettdj will help us.
Since what you want is non-native behaviour, it might be worth writing a small VBA macro to do the copy, and assign it to a keyboard shortcut.
Here's a simple example to copy a formula one cell to right, updating reference one cell down (preserves Absolute/Relative settings in formula).
It assumes A1 style address, work only if the active cell contains a formula referencing a single cell (ends silently if not). Will silently overwrite anything in the destination cell.
Sub CopyToRight()
Dim clFrom As Range
Dim clAddr As Range
Dim addr As String
On Error GoTo EH
Set clFrom = ActiveCell
If clFrom.Formula Like "=*!*" Then
Set clAddr = Range(Mid(clFrom.Formula, 2))
If clAddr.Count = 1 Then
If clFrom.Formula Like "=*!$*$*" Then
addr = clAddr.Offset(1, 0).Address(True, True)
ElseIf clFrom.Formula Like "=*!$**" Then
addr = clAddr.Offset(1, 0).Address(False, True)
ElseIf clFrom.Formula Like "=*!*$*" Then
addr = clAddr.Offset(1, 0).Address(True, False)
Else
addr = clAddr.Offset(1, 0).Address(False, False)
End If
clFrom.Offset(0, 1).Formula = "='" & clAddr.Worksheet.Name & "'!" & addr
End If
End If
clFrom.Offset(0, 1).Select
EH:
End Sub