Global name meets Local, Global freaks out

So here’s something I came across today. Somehow I’d managed to set up both local AND global names in a sheet, and found that I pretty much couldn’t do anything with the Global name via VBA as a result.

Say I set up this global name:
 
SomeName - Global
 
 
Note that it says SomeName in the NameBox when I have the name selected. Nothing weird about that.

Now I add a local name with the same name, pointing to A2:
 
SomeName - Local
 
 
…and here you can see both names in the Name Manager. Note that it says SomeName in the NameBox when I have the local name selected. Nothing weird about that, either…local names take precedence over global.
 

SomeName - Both_Name Manager
 
 
If I type = then select A1 where the global name lives, note that no name comes up in the intellisense:
 
Equals Global
 
 
..but if I do the same with the local name, I get intellisense:
 
Equals Local
 
 
Now things get a little weirder. If I type =SomeName, I get two options in the intellisense:
 
Equals SomeName
 
 
If I select the SomeName (Workbook) option, Excel qualifies the reference with the workbook name:
 
Book2 SomeName
 
 
…but when I push Enter, it’s still the local name that’s being referenced:
 
Book2 SomeName Not
 
 
…and when I select it again, I see that Excel has bizarrely changed that Book1! qualifier to a Sheet1! qualifier. In other words, it’s said ‘I know you asked for workbook, but I’m giving you local':
 
Book2 SomeName Local Qualifier
 
 
So what the hell is the point of those two options in the intellisense, if no matter what you do, Excel gives you the local name? Beats me. What’s worse, if your workbook name has a space in it, then Excel forgets to wrap apostrophes around it, and the Update Values dialog comes up:
 
Update Values dialog
 
 
What a mess. Push cancel, and you’re left with an invalid name:
 
Book2 SomeName spaces
 
 

Names and Collections

It turns out that the local name gets added to both the ActiveWorkbook.Names collection AND the ActiveSheet.Names Collection:
? activeworkbook.Names.Count
2
? activesheet.names.count
1

You can reference both names by index number just fine:
? activeworkbook.Names(1).Name
Sheet1!SomeName
? activeworkbook.Names(2).Name
SomeName

…but any attempt to reference the globally scoped one by name gives you the local name. Here, I retrieve their names from their index numbers:
? activeworkbook.Names(1).Name
Sheet1!SomeName
? activeworkbook.Names(2).Name
SomeName

Here I retrieve their refersto ranges from those index numbers:
? activeworkbook.Names(1).RefersTo
=Sheet1!$A$2
? activeworkbook.Names(2).RefersTo
=Sheet1!$A$1

Here I try to retrieve their refersto ranges from their names:
? activeworkbook.Names(“Sheet1!SomeName”).RefersTo
=Sheet1!$A$2
? activeworkbook.Names(“SomeName”).RefersTo
=Sheet1!$A$2

And here I try to retrieve their names from their names:
? activeworkbook.Names(“Sheet1!SomeName”).Name
Sheet1!SomeName
? activeworkbook.Names(“SomeName”).Name
Sheet1!SomeName

As per this Stack Overflow thread, If you want to identify the global one, you have to loop through the collection of names and find the one that has an “!” in its name.

So there you have it: if you’re referencing names by name, don’t assume you’ll get the one you want. Tedious!

CF meets Paste Names, CF freaks out.

Here’s a funny little bug I came across today. So I’m adding a CF condition, and I push F3 to bring up Paste Names rather than type out a reference or name:
 
CF1
 
 
And clicking on that named range duly inserts it into my CF formula:
 
CF2
 
 
…but the only problem is, now I can’t seem to do anything else. I can’t type the closing bracket, because Excel won’t let me. I can’t backspace to remove the offending name, because Excel won’t let me. The cursor is still flashing, so Excel hasn’t hung.
CF2

If you type the name in manually rather than using F3, it works perfectly. It turns out its some kind of focus issue: if you push ALT + TAB the screen flickers, but you still have the Conditional Format dialog open and now it works perfectly.

It’s incredible what you can do with CF if you’ve got a good memory. The good memory bit is required because CF doesn’t let you write notes about what each condition does like Names do, or even assign those CF conditions meaningful names. Let alone (heaven forbid) resize the dialog box so you can see all your rules at once, to help you work out which does what:
 
CF dialog
 
 
With a good memory, it’s an incredibly strong and useful tool. With my memory, it’s downright mysterious.

Shame, because I’m using it to really good effect to steer users through a pretty complicated self-assessment tool. I pity the poor sap that comes after me that has to amend these rules. On the other hand, that poor sap will probably end up hiring me back at my urgent pickle rate. Thanks, Microsoft.

Office 2016 Preview

From the Office blog: Office 2016 Public Preview Now Available

Intuitive data connecting and shaping capabilities. With integrated Power Query, use Excel as your personal analysis workspace by connecting to and viewing all the data around you. Take advantage of a broad range of data sources, including tables from websites, corporate data like SAP Business Objects, unstructured sources like Hadoop, and services like Salesforce. After bringing all your data together in one place, quickly shape and combine to fit your unique business needs and get to analysis in seconds

Go here for instructions on how to get the preview.

I use Oracle Virtualbox to install previews. So far, the 2016 preview has been remarkably stable, but I’m not one for taking chances.

Building an Excel Add-in

Hi there!

Only recently I read this quote somewhere: “If you want something done, ask a busy person”. I found two entirely different people as the originator of this quote: Benjamin Franklin and Lucille Ball. I wonder which it is…

Well, turns out I’ve been quite busy as of late. So I decided it was time to dust off some old stuff I prepared to add to my site but never came round to finishing (I must have become less busy when I was almost done :-) ).

If you’re about to embark on the journey to create an add-in out of a set of macro’s you have been using for some time now, this article is a nice read as it takes you through most of the steps needed when building an add-in for Excel.

Enjoy!

 

Jan Karel Pieterse

www.jkp-ads.com

 

CellAbove trick no longer needed?

I was writing up the CellAbove trick for the book. You know, the trick outlined at Ken Puls’ place.

First I was going to show how SUM doesn’t handle row insertions immediately above the SUM function itself. So I duly made up a crappy example (if you’ll pardon the crappy pun):
 
Underwear 1
 
 
…and then did the screenshot of the row insert, that showed how the formula didn’t adjust:
 
Underwear 2
 
 
…and then went to add some ridiculously large number of underwear, for readers who need some point laboring:
 
Underwear 3
 
 
…and then pushed enter, and to my amazement saw that my example of “broken” ain’t, no more:
 
Underwear 4
 
 
How utterly inconsiderate of Microsoft to fix this. Ruined a perfectly good example that I figure I could have dragged out for another half a chapter. Bugger! Now I have to write something else. God I hope they don’t fix the CONCATENATE function before I finish the book, or I’m screwed.
 
 

Edit

I just heard from Bill Jelen, who mentions when this gets autocorrected in this really great podcast of his:

He says “Going back to Excel 2003, provided you have a series of three or more numbers, Excel’s going to rewrite the formula to handle the new rows”.

But do check out that link: It’s hilarious. Also check out the follow-up podcasts.

There’s nothing like an Excel nerd with too much time on his hands…

Timing Formulas

In my last post, I looked at how much faster the Double VLOOKUP trick was on sorted data compared with the usual linear VLOOKUP on unsorted data.

Below is the code for the timing routine I use. I stole the guts of it from joeu2004 who made some incredibly insightful comments at this great thread at MrExcel

If you’re going to time formulas, then that thread is required reading, because it makes this important point: we cannot always accurately measure the performance of a formula simply by measuring one instance of it. (But that does depend on the nature of the formula and the situation that we are trying to measure. Sometimes we need to measure one instance of a formula, but increase the size of ranges that it references in order to overcome the effects of overhead.)

I’ve assigned my code to a custom button in the ribbon. You just select the cells you want the formula to time, and click the button. (By the way, my upcoming book not only shows you how to do this, but also gives you routines for every one of the icons shown above and a lot more besides.)
 
TimeFormulas 1
 
 
In the above case, I’ve selected a 1 row by 3 column range. If your selection is only one row deep – and there’s more rows with data below – then the app assumes you want to time everything below too, up to the first blank cell it encounters. I may want to rethink that, but it works good for now.

And after I click the magic button, here’s the result:
 
Timing message
 
 
As you can see from the output screenshot above, it puts a new table in a new sheet, populates it with your timings and relevant parameters, and then displays a message with joeu2004’s warning in it. Plus – and I think this is the genius part – it lets you push OK to return back to the sheet where the original formulas are, or push Cancel if you want to stay in the output sheet.

Note the ‘Formula’ column in the output table. For now, it just lists the formula that was in the top-left cell in the user’s selection. I think listing multiple formulas would be overkill. If there’s no formula in the top left cell, it sees if there’s a formula in the cell below. That way you can select headers in a Table, and you’ll still get the formula in the output.

It also can time multiple areas in one pass. So if I select that same range as a non-contiguous selection by holding Ctrl down and clicking each cell as I’ve done here (not that it’s easy to tell the difference visually from the previous screenshot):
 
noncontiguous
 
 
…then here’s what I get:
 
Output 2
 
 
Man, that is sooo much better than having to do each formula separately and then having to manually copy the results from a messagebox into a Table, like I used to do.

As you can see, I put a a databar on the important column, so you can visually eyeball results instead of conceptually juggling scientific notation in your head. But I’m damned if I can get this to display exactly as I want via VBA. For instance, here’s the databars I want if I add them manually:
Nice Databars

Notice that those have nice borders, and sensible setting for the minimum values, meaning that first result also gets a databar. The code that VBA spits out when you add this default databar is pretty ugly:

Selection.FormatConditions.AddDatabar
    Selection.FormatConditions(Selection.FormatConditions.Count).ShowValue = True
    Selection.FormatConditions(Selection.FormatConditions.Count).SetFirstPriority
    With Selection.FormatConditions(1)
        .MinPoint.Modify newtype:=xlConditionValueAutomaticMin
        .MaxPoint.Modify newtype:=xlConditionValueAutomaticMax
    End With
    With Selection.FormatConditions(1).BarColor
        .Color = 13012579
        .TintAndShade = 0
    End With
    Selection.FormatConditions(1).BarFillType = xlDataBarFillGradient
    Selection.FormatConditions(1).Direction = xlContext
    Selection.FormatConditions(1).NegativeBarFormat.ColorType = xlDataBarColor
    Selection.FormatConditions(1).BarBorder.Type = xlDataBarBorderSolid
    Selection.FormatConditions(1).NegativeBarFormat.BorderColorType = _
        xlDataBarColor
    With Selection.FormatConditions(1).BarBorder.Color
        .Color = 13012579
        .TintAndShade = 0
    End With
    Selection.FormatConditions(1).AxisPosition = xlDataBarAxisAutomatic
    With Selection.FormatConditions(1).AxisColor
        .Color = 0
        .TintAndShade = 0
    End With
    With Selection.FormatConditions(1).NegativeBarFormat.Color
        .Color = 255
        .TintAndShade = 0
    End With
    With Selection.FormatConditions(1).NegativeBarFormat.BorderColor
        .Color = 255
        .TintAndShade = 0
    End With

My code is just doing the first line:

'Add DataBars to the Each Cell column for easier comparison
        lo.ListColumns("Each Cell (sec)").Range.FormatConditions.AddDatabar

…because no matter what I try, doing anything beyond merely adding the bars causes the code to error out. For instance, even if I just try and set the minpoint and maxpoint I get an error:
 
error 1
 
 
…and this successfully adds the min setting I want, but errors out when I try to add a listrow:
 
error 2
 
 
Even worse, when I end the routine, the screen no longer updates no matter what I do. So I have to close out of Excel entirely.

If anyone can tell me where I’m going wrong, I’d be much obliged. Might be another peculiarity of Excel Tables. Meanwhile, I’ll just run with those simple bars.

Here’s my draft code:

Option Explicit

Public Declare Function QueryPerformanceFrequency Lib "kernel32" _
    (ByRef freq As Currency) As Long
Public Declare Function QueryPerformanceCounter Lib "kernel32" _
    (ByRef cnt As Currency) As Long
   
   
'Code adapted from http://www.mrexcel.com/forum/excel-questions/762910-speed-performance-measure-visual-basic-applications-function.html
'   Description:    Determines formusa execution time
'   Programmer:     Jeff Weir
'   Contact:        excelforsuperheroes@gmail.com

'   Name/Version:       Date:       Ini:   Modification:
'   TimeFormula         20150426    JSW    Added in ability to record times to ListObject


Sub TimeFormula()
    Dim sc As Currency
    Dim ec As Currency
    Dim dt As Double
    Dim sMsg As String
    Dim sResults As String
    Dim i As Long
    Dim N As Long
    Dim oldCalc  As Variant
    Dim myRng As Range
    Dim lo As ListObject
    Dim lr As ListRow
    Dim bUnique As Boolean
    Dim strFormula As String
    Dim myArea As Range
    Dim lngArea As Long
    Dim ws As Worksheet
    Dim wsOriginal As Worksheet
    Dim bNewListObject As Boolean
    Dim lngAreas As Long
    Dim varResults As Variant
    Dim varMsg As Variant
    Dim fc As FormatCondition
   
   
    Const passes As Long = 10
   
    With Application
        .ScreenUpdating = False
        .EnableEvents = False
        oldCalc = .Calculation
        .Calculation = xlCalculationManual
    End With

   
    Set myArea = Selection
    lngAreas = myArea.Areas.Count
    Set wsOriginal = myArea.Worksheet
    ReDim varResults(1 To lngAreas, 1 To 6)
    For Each myArea In Selection.Areas
        lngArea = lngArea + 1
        dt = 0
        Set myRng = myArea
        If myRng.Rows.Count = 1 Then
            If Not IsEmpty(myRng.Cells(1).Offset(1)) Then Set myRng = Range(myRng, myRng.End(xlDown))
        End If
        N = myRng.Count
        If myRng.Cells.Count > 1 Then
            'Get formula from 2nd row in case we're dealing with multiple cells and happen to be on a header
            If myRng.Cells(2).HasFormula Then strFormula = myRng.Cells(1).Formula
            If myRng.Cells(2).HasArray Then strFormula = myRng.Cells(1).HasArray
        End If
   
        If myRng.Cells(1).HasFormula Then strFormula = myRng.Cells(1).Formula
        If myRng.Cells(1).HasArray Then strFormula = myRng.Cells(1).HasArray
       
        With myRng
            For i = 1 To passes
                sc = myTimer
                .Calculate
                ec = myTimer
                dt = dt + myElapsedTime(ec - sc)
            Next
           
            'Record results for this pass
            varResults(lngArea, 1) = myRng.Address
            If strFormula <> "" Then varResults(lngArea, 2) = "'" & strFormula
            varResults(lngArea, 3) = N
            varResults(lngArea, 4) = dt / passes
            varResults(lngArea, 5) = dt / N / passes
            varResults(lngArea, 6) = Now
        End With
       
         
    Next
   
    bNewListObject = True
    For Each ws In ActiveWorkbook.Worksheets
        For Each lo In ws.ListObjects
            If lo.Name = "appTimeFormulas" Then
                bNewListObject = False
                ws.Activate
                Exit For
            End If
        Next
    Next

   
    If bNewListObject Then
        Set ws = ActiveWorkbook.Worksheets.Add
        On Error Resume Next
        ws.Name = "TimeFormula"
        On Error GoTo 0
        Range("A1").Value = "Range"
        Range("B1").Value = "Formula"
        Range("C1").Value = "Count"
        Range("D1").Value = "Entire Range (sec)"
        Range("E1").Value = "Each Cell (sec)"
        Range("F1").Value = "TimeStamp"
        Set lo = ws.ListObjects.Add(xlSrcRange, Range("A1").CurrentRegion, , xlYes)
        lo.Name = "appTimeFormulas"
       
        'Add DataBars to the Each Cell column for easier comparison
        lo.ListColumns("Each Cell (sec)").Range.FormatConditions.AddDatabar
       
    Else: Set lo = ActiveSheet.ListObjects("appTimeFormulas")
    End If
   
    Set lr = lo.ListRows.Add
    lr.Range.Resize(lngAreas).Value = varResults
    lo.Range.EntireColumn.AutoFit
    With lo.ListColumns("Formula").Range
        If .ColumnWidth > 30 Then
            .ColumnWidth = 30
            .WrapText = True
        End If
    End With
   
    With Application
        .EnableEvents = True
        .Calculation = oldCalc
        .ScreenUpdating = True
    End With
   
    sMsg = "Here are the average timings for the selected range over " & passes & " passes "
    sMsg = sMsg & vbNewLine & vbNewLine
    sMsg = sMsg & "Note that timings include some overhead incurred during the actual measurement process itself. "
    sMsg = sMsg & "So if the functions you are tring to time are really really fast, then it's possible that "
    sMsg = sMsg & "the measurement time included in the above result dwarfs the "
    sMsg = sMsg & "actual recalculation time of the formulas themselves."
    sMsg = sMsg & vbNewLine & vbNewLine
    sMsg = sMsg & "For best results, either time the functions over a really big range (hundreds "
    sMsg = sMsg & "of rows or more) or increase the size of the ranges that the formulas "
    sMsg = sMsg & "refer to. Furthermore, pay more heed to the average per-formula time than the overall time when "
    sMsg = sMsg & "making comparisons with other formulas."
    sMsg = sMsg & vbNewLine & vbNewLine
    sMsg = sMsg & "Do you want to return to the formulas, or stay in the result sheet?"
    sMsg = sMsg & vbNewLine & vbNewLine
    sMsg = sMsg & "(Press YES to return to formulas, and NO to stay in this results sheet.)"

    varMsg = MsgBox(Prompt:=sMsg, Title:="Recalculation time for selection:", Buttons:=vbYesNo)
    If varMsg = vbYes Then wsOriginal.Activate
   
End Sub

Function myTimer() As Currency
    ' defer conversion to seconds until myElapsedTime
    QueryPerformanceCounter myTimer
End Function

Function myElapsedTime(dc As Currency) As Double  ' return seconds
    Static df As Double
    Dim freq As Currency
    If df = 0 Then QueryPerformanceFrequency freq: df = freq
    myElapsedTime = dc / df
End Function

Emailing Access Report in Email Body

I’ve been doing a fair bit of work in Access lately, which is why I’m so irritable. I like Access for it’s rapid development characteristics, but I have specific user interface requirements that don’t happen in Access out of the box. By the time I implement those quirks, I feel like I should have just written the damn thing from scratch in C#.

The quirk du jure is sending an Access report via email not as an attachment, but rather in the body of the email. I thought this would be pretty darn easy. I would display the report and the user would use some built-in send function under the File menu to send it off. Oddly, to send an Access object via email, you have to look on the External Data tab, not the File menu. The Export Email function does not have the option of putting the report in the body of the email, only as an attachment. At least as far as I can see. Off to VBA, I guess.

Private Sub cmdEmailBlended_Click()
   
    Dim sFile As String, lFile As Long, sHtml As String
    Dim olApp As Outlook.Application
    Dim olMail As Outlook.MailItem
       
    UpdateBlendedQuery
   
    'Output the report to HTML in the temp directory
    sFile = Environ$("TEMP") & "\Blended" & Format(Date, "yyyymmdd") & ".html"
    DoCmd.OutputTo acOutputReport, "BlendedRackReport", acFormatHTML, sFile
   
    'Read in the HTML File
    lFile = FreeFile
   
    Open sFile For Input As lFile
        sHtml = Input$(LOF(lFile) - 1, lFile)
    Close lFile
   
    'Put the file contents in the email body
    Set olApp = New Outlook.Application
    Set olMail = olApp.CreateItem(olMailItem)
    olMail.To = "nobody@example.com"
    olMail.Subject = "Special Pricing"
    olMail.HTMLBody = sHtml
    olMail.Display
   
End Sub

Ah, the ubiquitous DoCmd object. I hate DoCmd (I told you I was irritable). If I was building the Access Object Model, that line would read ActiveReport.Export sFile, acFormatHTML.

In Excel, I like to write my own HTML converters, but not so in Access. Taking an Excel Range and turning into an HTML table is one thing. But taking a Recordset and creating an HTML report out of it is nuts. There’s calculated fields, headers and footers, and a whole bunch of other considerations. So I have no problem using the built-in HTML converter, even though my 18 record report produces 199 lines of HTML (actually less than I expected).

On the first go ’round, I encountered a Input past end of file error. You might notice on my Input$ statement that I take the length of the file minus 1. That’s not how I normally do it. In fact, I have an AutoHotKey for when I type Input$

:*:Input$(::
    sendinput Input$(lof(lfile),lfile)
    Return

I couldn’t figure out why I was getting this error. I’m using LOF to get the length of the file, what more could you possibly want. I subtracted one just to see what would happen and it worked. Then I opened the file in Notepad++ to see if I could see what was happening.

Look at that little guy at the end. What’s that about?

How much faster is the double-VLOOKUP trick?

Quick post. I was writing up the Double-VLOOKUP trick I learnt from Charles Williams for the book.

Standard VLOOKUP on unsorted data:
=VLOOKUP([@ID],Table2,2,FALSE)

Double VLOOKUP trick on sorted data:
=IF(VLOOKUP([@ID],Table2,1,TRUE)=[@ID],VLOOKUP([@ID],Table2,2,TRUE),NA())

The point of the double VLOOKUP trick is this:

  • Standard VLOOKUPS on unsorted data are slow, because your VLOOKUP has to look at each item in turn until it finds a match. So on average, it looks at – and discounts – half the things in your lookup list before it finds that match.
  • Binary searches are lightning fast. Because your data is sorted, they can start half way through the lookup list, and check if the item at that point is bigger or smaller than what they’re looking for. Meaning they can ditch half the list immediately, then look halfway through the remainder. And over and over, ditching half the list each time until they either find the item they are looking for, or rule out all items.
  • VLOOKUP and MATCH will quite happily do a binary search for you. But for reasons known only to Microsoft, they offer an exciting plot-twist: if they don’t find what you’re looking for in the list, they return the closest match they can find to it, which happens to be the only thing left in the list when they’d divided it in half enough times. Only they don’t tell you its not an exact match. Phooey.
  • Charles’ brilliant trick is to do two lightning-fast Binary searches – the first one simply looks for the closest match to your input term among the Lookup terms. If it’s an exact match with what you fed it, you know your lookup term is in the list. So now that you KNOW it’s there for sure, you kick off a second approximate match VLOOKUP, which will grab the corresponding value you want from the lookup table. Go read his blog for the specifics.

I’ve known about this for a while, but it’s only as I’m writing this up for the book that I’ve gained an appeciation of just how much faster Binary Searches – and the Double VLOOKUP trick that gets around Microsoft’s crap implementation of them – are, compared to unsorted/linear VLOOKUPs.

See for yourself:
 
Double VLOOKUPv2
 
 
That says that:

  • At one extreme, if your lookup table has 10,000 things in it, the double VLOOKUP trick on sorted data is 28 times faster than the standard VLOOKUP on unsorted data
  • At the other, if your lookup table has 1,000,000 things in it, the double VLOOKUP trick on sorted data is 3,600 times faster than the standard VLOOKUP on unsorted data

Kinda puts the ‘hassle’ of sorting your lookup table ascending into perspective, don’t it!