Skip to Content
Author's profile photo Regan Macdonald

Analysis for Office and Excel VBA – Lessons learnt

After sharing a basic method of automating the updating and distribution of your Analysis for Office files with Excel, this blog covers some of my learnings since my initial posts.

I’ve continued to improve and modify the main template in the past few weeks, primarily related to my specific needs at work, but you may also find it useful.


General Notes

I few weeks ago I purchased SAP BusinessObjects Analysis Office – The Comprehensive Guide by Tobias Meyer and found it to be very useful.

As I progress further on my journey with Analysis I will certainly be drawing on many of the points mentioned in his book. Particularly for manipulating the visual display of data within the various queries.

His website also has a bunch of good blog posts about Analysis. Check it out.

A big thanks to Ivan Bondarenko as well. He pointed out a couple of things I was missing to help improve the template. Primarily the use of CDO to simplify the emailing side of things, and better use of one of the filter functions.

Ivan also has an excellent solution to BOA Automation himself, available on github. It’s certainly worth talking a look at.


 

So lets move on to the improvements.

Note all of these are incorporated in the new version of the template at the bottom of this blog post. Skip to the bottom if you just want to grab the latest version and work out how it was done yourself.

  • Technical Improvement #1 : For a cleaner list of filters, use SAPListOfDynamicFilters instead of SAPListOfEffectiveFilters
  • Technical Improvement #2 : Use CDO for emailing within Excel
  • Technical Improvement #3 : Correctly determining the number of dimensions in an array
  • Functional Improvement #1 : Distributing individual sheets within the AO file
  • Functional Improvement #2 : Refreshing any pivots in the file
  • Functional Improvement #3 : More dynamic date calculations

 


Technical Improvement #1 : For a cleaner list of filters, use SAPListOfDynamicFilters instead of SAPListOfEffectiveFilters

This one was courtesy of Ivan Bondarenko.

EffectiveFilters also shows the Variables again (since the variables are a form of filter). We don’t actually want that, as we are handling the variables seperately. There is no point double-handling everything.

So instead of this in our Get_Active_Variables_and_Filters subroutine :

tempFiltersArray = xlApp.Application.Run("SAPListOfEffectiveFilters", thedatasource, "INPUT_STRING")

We now use this :

tempFiltersArray = xlApp.Application.Run("SAPListOfDynamicFilters", thedatasource, "INPUT_STRING")

Technical Improvement #2 : Use CDO for emailing within Excel

This one was also courtesy of Ivan Bondarenko.

CDO has been a round for a while and is best summed up on this page : Ron de Bruin Excel Automation

All it really needs is to be pointed to a smtp server. It doesn’t need Outlook installed or any other 3rd party software like SwithMail.

It will even work happily with Gmail’s SSL (check out the example file on the Ron de Bruin Excel Automation page).

In our template we now allow the user to choose CDO and then call the following subroutine (note CDO_Server is a constant we have defined at the top of our module) :

Private Sub Send_Email_CDO(therecipient As String, thesubject As String, thebody As String, _
                           theattachment As String)

' This code is from http://www.rondebruin.nl/win/s1/cdo.htm

    Dim iMsg As Object
    Dim iConf As Object
    Dim Flds As Variant

    Set iMsg = CreateObject("CDO.Message")
    Set iConf = CreateObject("CDO.Configuration")

    iConf.Load -1    ' CDO Source Defaults
    Set Flds = iConf.Fields
    With Flds
        .Item("http://schemas.microsoft.com/cdo/configuration/sendusing") = 2
        .Item("http://schemas.microsoft.com/cdo/configuration/smtpserver") = CDO_Server
        .Item("http://schemas.microsoft.com/cdo/configuration/smtpserverport") = 25
        .Item("http://schemas.microsoft.com/cdo/configuration/smtpauthenticate") = 1
        .Update
    End With

    With iMsg
        Set .Configuration = iConf
        .to = therecipient
        .CC = ""
        .BCC = ""
        .From = "sendersaddress@example.com"
        .Subject = thesubject
        .TextBody = thebody
        .AddAttachment theattachment
        .Send
    End With

End Sub

A third technical improvement is in the handling of Arrays, particularly the part where we work out how many dimensions it has so we can treat it accordingly.

Technical Improvement #3 : Correctly determining the number of dimensions in an array

The previous code was a hack. It was basic and worked initially, but has since been shown to be unreliable and produces a ‘Runtime error 13 Type Mismatch’ in certain instances (thanks go to Carolyne for showing that it was happening to her too).

The following function returns the number of Dimensions in an array (from http://stackoverflow.com/questions/24613101/vba-check-if-array-is-one-dimensional) :

Public Function NumberOfArrayDimensions(arr As Variant) As Integer
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' NumberOfArrayDimensions
' This function returns the number of dimensions of an array. An unallocated dynamic array
' has 0 dimensions. This condition can also be tested with IsArrayEmpty.
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Dim Ndx As Integer
Dim Res As Integer
On Error Resume Next
' Loop, increasing the dimension index Ndx, until an error occurs.
' An error will occur when Ndx exceeds the number of dimension
' in the array. Return Ndx - 1.

Do
    Ndx = Ndx + 1
    Res = UBound(arr, Ndx)
Loop Until Err.Number <> 0

NumberOfArrayDimensions = Ndx - 1

End Function

Add that function in to the bottom of your module.

Then in the Get_Active_Variables_and_Filters subroutine make the following changes.

Add to the top of the routine :

Dim NumberOfDimensions as Integer

The instead of :

array_start = 1
array_end = Application.CountA(tempVariablesArray) / 2

If array_end = 1 Then

    ' We have only 1 real record, so we know VariablesArray is ONE dimensional

    techname = xlApp.Application.Run("SAPGetVariable", thedatasource, tempVariablesArray(1), "TECHNICALNAME")
    VariablesArray(UBound(VariablesArray)) = techname

Else

    ' We have more than 1 record, so we know VariablesArray is TWO dimensional

    For arrayloop = array_start To array_end

        techname = xlApp.Application.Run("SAPGetVariable", thedatasource, tempVariablesArray(arrayloop, 1), "TECHNICALNAME")
        VariablesArray(UBound(VariablesArray)) = techname

    Next arrayloop

End If

Replace it all with :

NumberOfDimensions = NumberOfArrayDimensions(tempVariablesArray)

If NumberOfDimensions = 1 Then

    ' We have only 1 real record, so we know VariablesArray is ONE dimensional

    techname = xlApp.Application.Run("SAPGetVariable", thedatasource, tempVariablesArray(1), "TECHNICALNAME")
    VariablesArray(UBound(VariablesArray)) = techname

Else

    ' We have more than 1 record, so we know VariablesArray is TWO dimensional

    For arrayloop = LBound(tempVariablesArray) To UBound(tempVariablesArray)

        techname = xlApp.Application.Run("SAPGetVariable", thedatasource, tempVariablesArray(arrayloop, 1), "TECHNICALNAME")
        VariablesArray(UBound(VariablesArray)) = techname

    Next arrayloop

End If

Do the same for the filters section in the same subroutine. It will be more reliable, and is now making proper use of the LBound and UBound functionality for looping through the arrays.


Functional Improvement #1 : Distributing individual sheets within the AO file

A real-world scenario for me was a single AO file that had seperate sheets for different areas of the business.

The seperate sheets were all drawing their data from the same shared BW query on another sheet in that file.

I needed to refresh the file from BW, update the pivots on all of the respective sheets then email out each individual sheet in PDF form to different email addresses.

To email each sheet, I’ve added a new column to the parameters file.

When saving or emailing, if the Sheet column is populated it will do the following :

1. Save the whole file to a temporary filename.

2. Load the new file and delete any sheet on it that isn’t the sheet we want.

3. Save and/or email the new file.

Why do we save the whole thing to a temp file first? Because we still want to do more with the original file.

If I have 10 sheets to work through, I want the original refreshed file to still be available to me after I am done with the current sheet.

 

Functional Improvement #2 : Refreshing any pivots in the file

A new action has been added, “RefreshAllPivots”.

If thats been chosen in the parameter file, it does the following in the VBA code :

xlApp.ActiveWorkbook.RefreshAll

All it does is refresh any pivots in the AO file.

 

Functional Improvement #3 : More dynamic date calculations

The current list of dynamic date calculations is now as follows.

TODAY : Today date. Format DD.MM.YYYY

YESTERDAY : Yesterdays date. Format DD.MM.YYYY

CURRENTDAY : Todays date, but just the day. Format DD

CURRENTMONTH : Current Calendar Month. Format MM.YYYY

CURRENTYEAR : Current Calendar Year. Format YYYY

LASTMONTH : Previous Calendar Month when compared to today. Format MM.YYYY. Same as PREVIOUSCALENDARMONTH.

ENDOFLASTMONTH : The date of the final day of the previous calendar month. Format DD.MM.YYYY

PREVIOUSCALENDARWEEK : The calendar week number for last week. Based on a week starting on Mondays. Format WW.YYYY

PREVIOUSCALENDARWEEKLASTYEAR : Same as PREVIOUSCALENDARWEEK but for the previous year. Format WW.YYYY

PREVIOUSCALENDARMONTH : Same as LASTMONTH. Format MM.YYYY

PREVIOUSCALENDARMONTHLASTYEAR : Same as PREVIOUSCALENDARMONTH but for the previous year. Format MM.YYYY

LASTSUNDAY : The date of the most recent sunday. Format DD.MM.YYYY

LASTSUNDAYLASTYEAR : Same as LASTSUNDAY but for the previous year. Format DD.MM.YYYY

FINANCIALYEAR : The current Fiscal year. Format YYYY.

FIRSTDAYOFFINANCIALYEAR : First day of the current financial year (assuming a July to June financial year). Format DD.MM.YYYY

FIRSTDAYOFFINANCIALYEARLASTYEAR : Same as FIRSTDAYOFFINANCIALYEAR but for the previous year. Format DD.MM.YYYY

You are probably looking at some of them wondering when they would ever be used. Here is an example.

I needed to get current Year to Date figures, but to the end of the most recent week (week finishing on a Sunday). Then needed to also compare that with the same for the previous financial year.

I can’t use Calendar weeks for this as one of the weeks begins before the start of the financial year.

Instead I used the variables in the parameter file as follows :

FIRSTDAYOFFINANCIALYEAR – LASTSUNDAY; FIRSTDAYOFFINANCIALYEARLASTYEAR – LASTSUNDAYLASTYEAR

The VBA code converts this to :

01.07.2016 – 19.02.2017; 01.07.2015 – 19.02.2016


 

There are still things to learn and improve on, but the changes above help round things out a bit more and improve the stability of it.

Below is version 0.7 of the template along with an example parameter file. I’ve zipped both up for a single download.

Assigned Tags

      17 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Tobias Meyer
      Tobias Meyer

      Hi Regan,

      thanks for mentioning. A really cool thing are the dynamic date calculations. I build for me the ActualCalendarWeek and the ActualCalendarWeekLastYear to compare revenue of stores in the same week a year before.

      Best regards,

      Tobias

       

       

      Author's profile photo Former Member
      Former Member

      Thanks for sharing. This is very useful information!

      Author's profile photo Former Member
      Former Member

      Hi Regan,
      thanks for sharing your learnings! Could you reupload your file? It seems it had been removed.

      Best regards,

      Jan

       

      Author's profile photo Regan Macdonald
      Regan Macdonald
      Blog Post Author

      Link now updated and should be working again.

      Author's profile photo Former Member
      Former Member

      I had use MarketXLS. It works great for me.
      http://marketxls.com/stock-charts-in-excel/

      Author's profile photo Regan Macdonald
      Regan Macdonald
      Blog Post Author

      Hi Harrison,

      I can't see how a commercial graphing solution is related to auto-updating of AO files?

      Did you post this on the wrong blog?

      Regards,

      Regan

      Author's profile photo Andreea Diaconu
      Andreea Diaconu

      Hi!

      I have the following crosstab based on Bex Query (and Composite in the backend):

      in the Delta% field I need to calculate the difference between Value B of the current date and the Value B of the previous date (the dates are not consecutive), divided by Value B of previous date. In short words (F-J)/J.

      The above formula returns correct values only for the first column. The second column formula is automatically filled with (J4-J4)/J4, the next one is (N4-J4)/J4 and so on.

      Can you please give me a hint on how I can solve this?

       

      Thanks in advance,

      Best regards,

      Andreea D.

      Author's profile photo Regan Macdonald
      Regan Macdonald
      Blog Post Author

      Hi Andreea,

      This question might be better posted in the main question area. This blog is primarily about the automation of Analysis for Office files in Excel rather than specific formula questions.

      Regards,

      Regan

      Author's profile photo John Brady
      John Brady

      Great work! I do have an issue that perhaps you have seen and can shed some light on.

      Excel has been throwing an automation error 429. I think this may be due to patches that my org puts onto my machine which are beyond my control.

      Have you encountered this, and, if so, do you have any tips on how to deal with it?

      Again, great work!

      Author's profile photo Regan Macdonald
      Regan Macdonald
      Blog Post Author

      Hi John,

      I haven't come across automation error 429 myself.

      If you are using AO, make sure you are on the latest version/patch level.

      I found there was a big difference in the stability and reliability between the older and newer versions (now using 2.4.4 but not yet 2.5).

      Cheers,

      Regan

      Author's profile photo Former Member
      Former Member

      Hi Regan,

      Thanks to your post from series 1 I was able to proof the auto refresh is working within my company (with the vbs file). I also used your "Refresh and Distribute" worksheet successfully - but it works only for one report. If I add a second line, I always get the following Excel error after the refresh of the first workbook: "Microsoft Excel is waiting for another application to complete an OLE action."

      It looks to me, that Excel is still busy closing the first workbook in the background, while it tries to open another session for the 2nd report to resfresh.

      Any suggestion how to overcome this issue?

      Thanks in advance!

      Frank

      Author's profile photo Regan Macdonald
      Regan Macdonald
      Blog Post Author

      Hi Frank,

      Normally the routine will complete the first line before attempting any steps on the subsequent lines.

      I've come across the 'waiting to complete an OLE action' message myself when the data it is trying to refresh takes too long to come back from the server. Excel seems to have a specific period it waits before it thinks the other end isn't working any more (even if the second instance of Excel is actually working but just waiting for the data from the BW server).

      The only way I was able to get around this (lots of recommendations on the internet didn't work for me) was to restructure the file I was refreshing to be more efficient in its prompts/filters.

      Grabbing 'Year to Date' figures across a wide range of the business was one area that took longer than Excel OLE was comfortable with (even though nothing was really wrong with the file itself).

      The second file you are trying to refresh, put that in as the first line and see if it still gives you the error. That may then confirm what I've written above.

      Cheers,

      Regan

      Author's profile photo Regan Macdonald
      Regan Macdonald
      Blog Post Author

      March 2018 : Note any download link in the blog post points to Dropbox which unfortunately keeps changing the link URL.

      The final version of this blog series is 0.7 and is also available at the more permanent link on gt.tt : http://ge.tt/92Kodvo2

      Author's profile photo Felippe Santos
      Felippe Santos

      Great job!! this automation will help me on BW update tasks.

      Could you send it by email or an updated link?

      Author's profile photo Parichay Gupta
      Parichay Gupta

      Hi Regan,

      I have a requirement. I have a analysis for office query. There is a dimension, It can be based on Row or column. I want to write a VBA to hide the column/row via lookup the member of dimension.

      Let assume there is a dimension “Posting period”. Its member is “April”. If the member is April that April’s Row/Column should hide automatically. And the thing is to remember this action should execute on the start up of the query.

      Please help !

      Thank you.

      Regards

      Parichay Gupta

       

      Author's profile photo mouad mennani
      mouad mennani

      Hello Regan,

      Very interesting article thank you !

      while implementing your code I have the following error : error 438

      in the following code line, can you please help me.

       Call pAddin.ExcelInterface.SetConnectionFromHandle(p_connectionObj)

      Thanks,

       

      Mouad.

      Author's profile photo B. Struijk
      B. Struijk

      Very nice blog.
      Very helpful for SAP consultants with just basic VBA knowledge.

      Thank you very much for sharing.