Excel vba for each loop with if statement and structured references - excel

I want to change the value in a column of a table based on the value of another column in the same table using structured references. So far I have something like:
Sub Test
For Each cell In Worksheets("FNM_DB").Range("DB_SS[Resource_Loc_Type]")
If cell.Value = "TH" Then
Worksheets("FNM_DB").Range("DB_SS[Resource]") = Worksheets("FNM_DB").Range("DB_SS[SOURCE_AND_SINK_NAMES]")
End If
Next cell
End Sub
I know I could do this without the structured reference, but I want the code to be more readable. Is there a way to loop through all the values? I know the # refers to a specific row, but I don't know how to use it in this instance.

The above statement
The # sign refers to the current row, but that syntax only works inside a worksheet cell, not within a VBA.
is false.
This works:
[tblActualsWithFacts[#1c]].Select
these work too:
Range("tblActualsWithFacts[#1c]").Select
For i = 1 to 12
Range("tblActualsWithFacts[" & i & " c]").Select
Next i
however, the combination of the above techniques i.e. concatenating a structured reference which makes use of "#" does not work, therefore this does NOT work
For i = 1 to 12
Range("tblActualsWithFacts[#" & i & " c]").Select
Next i

While it is possible to use structured references in VBA, addressing the current row is different. The # sign refers to the current row, but that syntax only works inside a worksheet cell, not within a VBA.
If you are concerned about readability, I'm not sure the long syntax is helpful at all. My suggestion is to create variables that represent the offset of the table column name from the current column. Something like this:
Sub test()
Dim Height As Integer, Width As Integer, Result As Integer
Result = 3
Height = 1
Width = 2
For Each cel In Worksheets("Sheet1").Range("SizingTable[Colour]")
If cel.Value = "red" Then
cel.Offset(0, Result) = cel.Offset(0, Height) * cel.Offset(0, Width)
End If
Next cel
End Sub

Related

Excel Table - Convert Range-Based Formula to Field-Based

I have inherited a very large spreadsheet and am trying to migrate it to a database. The table has over 300 columns, many of which reference other columns.
By converting it to a table (ListObject) in Excel, I thought it would be easier to deconstruct the logic... basically turn the formula:
=CJ6-CY6
into
=[#[Sale Price]]-[#[Standard Cost]]
Converting it to a table worked great... unfortunately it didn't change any of the embedded formulas. They still reference the ranges.
I think I may notionally understand why -- if a formula references a value in another row, then it's no longer a primitive calculation. But for formulas that are all on the same row, I'm wondering if there is any way to convert them without manually going into each of these 300+ columns and re-writing them. Some of them are beastly. No joke, this is an example:
=IF(IF(IF(HD6="",0,IF(HD6=24,0,IF(HD6="U",((FI6-(ES6*12))*$I6),($I6*FI6)*HS6)))<0,0,IF(HD6="",0,IF(HD6=24,0,IF(HD6="U",((FI6-(ES6*12))*$I6),($I6*FI6)*HS6))))>GO6,GO6,IF(IF(HD6="",0,IF(HD6=24,0,IF(HD6="U",((FI6-(ES6*12))*$I6),($I6*FI6)*HS6)))<0,0,IF(HD6="",0,IF(HD6=24,0,IF(HD6="U",((FI6-(ES6*12))*$I6),($I6*FI6)*HS6)))))
And it's not the worst one.
If anyone has ideas, I'd welcome them. I'm open to anything. VBA included.
I would never use this to teach computer science, but this is the hack that did the trick. To keep things simple, I transposed header names and the corresponding column into A17:
And then this VBA code successfully transformed each range into the corresponding column property.
Sub FooBomb()
Dim ws As Worksheet
Dim r, rw, translate As Range
Dim col, row As Integer
Dim find, anchored, repl As String
Set ws = ActiveWorkbook.ActiveSheet
Set rw = ws.Rows(6)
Set translate = ws.Range("A17:B363")
For col = 12 To 347
Set r = rw.Cells(1, col)
For row = 363 To 17 Step -1
find = ws.Cells(row, 1).Value2 & "6"
anchored = "$" & find
repl = "[#[" & ws.Cells(row, 2).Value2 & "]]"
r.Formula = VBA.Replace(r.Formula, anchored, repl)
r.Formula = VBA.Replace(r.Formula, find, repl)
Next row
Next col
End Sub
Hard-coded and not scalable, but I'm not looking to repeat this ever again.
-- EDIT --
Word to the wise to help performance, especially with as many columns and formulas are in this spreadsheet.
Set Formula calculation to manual before
Check before the field exists before doing a replacement -- skipping happens more often than not
Program ran in a few seconds (minutes prior) before these changes:
If InStr(r.Formula, anchored) > 0 Then
r.Formula = VBA.Replace(r.Formula, anchored, repl)
End If
If InStr(r.Formula, find) > 0 Then
r.Formula = VBA.Replace(r.Formula, find, repl)
End If

Excel VBA: How to swap two selected cell ranges (not only two values) within the same column?

I would like to swap selected cell ranges within the same column without having automatically adjusted attached formulas in other columns. Those cell ranges will almost always be of unequal size.
I found a VBA code which does it for two selected cells, but im afraid that this wont help me much.
Sub SwapCells()
Dim sHolder As String
If Selection.Cells.Count = 2 Then
With Selection
sHolder = .Cells(1).Formula
If .Areas.Count = 2 Then ' Cells selected using Ctrl key
.Areas(1).Formula = .Areas(2).Formula
.Areas(2).Formula = sHolder
Else ' Adjacent cells are selected
.Cells(1).Formula = .Cells(2).Formula
.Cells(2).Formula = sHolder
End If
End With
Else
MsgBox "Select only TWO cells to swap", vbCritical
End If
End Sub
I know that another option would be to hold 'shift' when moving the cell ranges (works perfectly fine), but then all the attached formulas will change their reference which I dont want (e.g. if I have a formula referring to cell A1, and im swapping A1 somewhere, the formula will refer to A1's new position, but I want the formula to still refer to A1).
I think another option would be to use INDIRECT("G" & ROW()) to fix it, but since its a quite resource-intensive formula, Id love to see an alternative.
On top of that, the latter two options would not allow me to use tables (which Id prefer for other reasons) because you cant swap cells in tables. This is why Id strongly prefer a VBA option.
I hope you can help me, thank you! Maybe it is only necessary to adjust the VBA code a little.
Kind regards,
Marco
EDIT: If it is significantly easier to swap two equal cell ranges (e.g. encompassing 5 cells each), then it would also be a good solution.
Sub SwapTwoSelectedRanges()
Dim initialRng As Range
Set initialRng = Selection
If initialRng.Areas.Count <> 2 Then
Debug.Print "Select 2 areas!"
Exit Sub
End If
If initialRng.Areas(1).Cells.Count <> initialRng.Areas(2).Cells.Count Then
Debug.Print "The cells should be the same number!"
Exit Sub
End If
Dim intermediateRng As Variant
intermediateRng = initialRng.Areas(1).Cells.Value2
initialRng.Areas(1).Cells.Value2 = initialRng.Areas(2).Cells.Value2
initialRng.Areas(2).Cells.Value2 = intermediateRng
End Sub
Swaping two values is considered an easy task, if you are using an intermediate value. With the ranges, there are two important checks to perform, before swapping them:
Are the selected areas exactly 2;
Is the number of cells equal in every area;
Then with an intermediateRng as a 3. variable, the swap is made;
This would only work, if the Areas are per column. If the selection is made per row, then the results would not be as expected;
Concerning the swaping of the colors, if the colors of all the cells per area are exactly the same, this would work:
Dim intermediateRng As Variant
Dim intermediateClr As Variant
intermediateRng = initialRng.Areas(1).Cells.Value2
intermediateClr = initialRng.Areas(1).Cells.Interior.Color
With initialRng
.Areas(1).Cells.Value2 = .Areas(2).Cells.Value2
.Areas(1).Cells.Interior.Color = .Areas(2).Cells.Interior.Color
.Areas(2).Cells.Value2 = intermediateRng
.Areas(2).Cells.Interior.Color = intermediateClr
End With
However, if the colors of the cells per Area are not the same, then the easiest way is to copy+paste the first range to a separate range and work from there.

Excel User Defined Function: change the cell's color [duplicate]

This question already has answers here:
Excel VBA: Answer gets "stuck"
(1 answer)
Using a UDF in Excel to update the worksheet
(3 answers)
Closed 1 year ago.
I have a user defined function in Excel. It is called as a formula function from spreadsheet cells and works fine.
I'd like the function to be able to change the cell's color depending on the value that it returns. Essentially, changing the cell's color is a side effect of the function.
I tried
Application.ThisCell.Interior.ColorIndex = 2
But it fails.
Here's a demonstration of how a VBA UDF can change the colouring of a sheets contents rather than using conditional formatting.
As long as both sheets have rows and columns sorted in the same order then this will compare for differences in every cell between two seperate Excel sheets.
You can add this into as many cells as you need to on a third sheet to detect differences between the same two cells on the two sheets with data on: =DifferenceTest(Sheet1!A1,Sheet2!A1)
And the function to be stored in the VBA editor as follows:
Function DifferenceTest(str1 As String, str2 As String) As String
If str1 = str2 Then
Application.Caller.Font.ColorIndex = 2
Else
Application.Caller.Font.ColorIndex = 3
DifferenceTest = str1 & " vs " & str2
End If
End Function
This cannot be done. User defined functions cannot change the state of the workbook/worksheet etc.
Use Conditional Formatting to achieve what you are trying.
EDIT: This is more of a suggestion, not a real answer.
No, you cannot alter a cell's color using a Function(). You can, however, alter it in a Sub() routine.
Simply write a Sub() that will run your function on the cells you wish it to be run on, then after each is run, put an If-statement to see if you want to color it based on the value it returns.
You could create a vba code that runs automatically after there is a change in your sheet.
Instead of hving the code in a seperate module you have to embed it in the sheet itself.
Right click on the sheet tab, choose View Code, and create the following code:
Private Sub Worksheet_Change(ByVal Target As Range)
For Each cell In Range("A1:B8") 'change cell range as needed
Select Case cell.Value
Case 8
cell.Interior.ColorIndex = 4 'cell color becomes green when cell value is 8
Case ""
cell.Interior.ColorIndex = 1 'cell color becomes black when cell is empty
Case Is < 6
cell.Interior.ColorIndex = 7 'cell color becomes pink when cell value is smaller than 6
Case Else
cell.Interior.ColorIndex = 0 'all other cells get no color
End Select
Next cell
End Sub
Function HexToLongRGB(sHexVal As String) As Long
Dim lRed As Long
Dim lGreen As Long
Dim lBlue As Long
lRed = CLng("&H" & Left$(sHexVal, 2))
lGreen = CLng("&H" & Mid$(sHexVal, 3, 2))
lBlue = CLng("&H" & Right$(sHexVal, 2))
HexToLongRGB = RGB(lRed, lGreen, lBlue)
End Function
Function setBgColor(ByVal stringHex As String)
Evaluate "setColor(" & Application.Caller.Offset(0, 0).Address(False, False) & ",""" & stringHex & """)"
setBgColor = ""
End Function
Sub setColor(vCell As Range, vHex As String)
vCell.Interior.Color = HexToLongRGB(vHex)
End Sub
I tried the Evaluate method, which worked but immediately crashed (2007). The help mentions caching the address, so that's my approach - store the cell and color in a collection, then change the color after the calculation.
Dim colorCells As New Collection
Function UDF...
UDF = <whatever>
color = <color for whatever>
colorCells.Add (Application.Caller)
colorCells.Add (color)
End Function
Sub SetColor()
While colorCells.Count <> 0
colorCells(1).Interior.Color = colorCells(2)
colorCells.Remove (1)
colorCells.Remove (1)
Wend
End Sub
Private Sub Workbook_SheetCalculate(ByVal Sh As Object)
SetColor
End Sub

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

Sumifs with Array conditional

I'm surprised I can't find this anywhere which makes me think I must be going about this incorrectly. I want to be able to include a series of values within sumifs so it performs like a loop for each value in the conditional (without having to write a " +sumifs(....) for each value. Here's an example of what I have so far that is not working
`=SUMIFS(Sum,Range1,Criteria1, '[Stores.xlsx]Sheet1'!$H:$H, "Store #"&Regions!$T:$T&"*")`
So I'm trying to pass every value in Regions!T:T as a criteria.
For example "Store #150 Los Angeles" and "Store #155 San Diego" would both need to be passed through the argument. Currently the formula just returns the first item it matches and doesn't continue to the next item.
I hope that makes sense. Please ask if you need more clarity.
I think the easiest is to start an "intermediate" column next to the T column, do a sumifs for each of the rows of that column, and then sum that column into another cell. Tables or even just array sums may also be helpful here.
I came up with the following in VBA, but I cannot test it completely:
Option Explicit
Function SumSumIfs(ByVal inp As Range) As Integer
Dim i As Integer
Dim QBData As Worksheet
Dim Stores As Worksheet
Set QBData = Workbooks.Open("QBData.xlsx").Sheets("Sheet1")
Set Stores = Workbooks.Open("Stores.xlsx").Sheets("Sheet1")
Dim QBRange1, QBRange2, SalesRange As Range
Set QBRange1 = QBData.Range("H1:H" & Range("H1").End(xlDown).Row)
Set QBRange2 = QBData.Range("I1:I" & Range("I1").End(xlDown).Row)
Set SalesRange = QBData.Range("H1:H" & QBData.Range("H1").End(xlDown).Row)
For i = 1 To inp.End(xlDown).Row
SumSumIfs = SumSumIfs + Application.WorksheetFunction.SumIfs(QBRange1, QBRange2, _
"=" & Stores.Cells(16, 5), StoreRange3, "=" & inp.Cells(i, 19))
Next i
End Function
Again, I'm certain there's a way to do this looping with the formula, but searching around, it was not evident to me.

Resources