Want to know The Truth About CPM?

21 July 2009

Fixing Planning's Filters

Full disclosure and source code and of course a disclaimer
Much of the content of this post comes from a ODTUG Kaleidoscope presentation I gave in June of this year. The presentation (280 – Master Essbase with MaxL automation) should be available 90 days after the conference, so I am guessing some time in September. I will update this post when it is available for those who want to see more.

This code was sort of the capstone of the presentation and used a variety of MaxL techniques. If you look at the source code you’re probably going to wonder why I used so many different approaches to, oh say, error checking, for example – you will intuit that I was trying to illustrate the different ways it can be handled. For your sanity I suggest you pick one (like %ERRORLEVEL%) and stick with it.

The source code is available here.

Take a look at the code, laugh at it, admire its genius, use it in anger – whatever. If it causes the end of the world (your Hyperion world, at least), gets you fired because it didn’t work, or any other less than optimal outcome, well, I make no guarantee, warranty, or anything else. Use it at your own risk. It is worth every penny you spent, which would be exactly zero.

Planning’s Filter Oddity
I seem to be going on and on about Planning, not Essbase, and of course the name of this blog isn’t Cameron’s Blog For Planning Hackers, so it’s fair to ask what’s going on.

Well, Planning is a wrapper application around Essbase – at the end of the day, whether it’s dimensions, data, or Business Rules (aka calc scripts on steroids) – Essbase is the foundation that Planning rests on. And Planning drives Essbase, but oddly.

Within Hyperion Planning, metadata is filtered. That is, if you're a Planning user, and security is applied to a dimension, you can only see the bits of the dimension that you have read or write access to. (If your read access is greater than your write access, you will see more members in, say, a dimension drop down, but you will only be able to write to the members you have write access to.)

This makes sense, right? Why would you want to deal with members you can't touch?

Makes sense
Here's the administrator's view of the Entity dimension from the Planning reference application:


The Planning administrator sees all – this is appropriate, as he is responsible for all data.
And here is a Planner's view of the world (literally):
See the difference? For a user that can only see Latin America, all is as it should be.

Stop making sense
And here’s that same user looking at the same dimension in SmartView:
The rows highlighted in yellow are not readable/writeable, but are visible. While the planner can select these members, he can’t retrieve their data values. This is inconsistent, and is colloquially known as Not Good.

Why does it do what it does?
Planning performs metadata filtering within the application, but when it writes the filters to the Essbase id used to query data, it uses a READ, not a METAREAD filter.

Why? This is a mystery. Okay, lots of things in life are mysteries, but the fix from the Planning side seems so simple (use METAREAD, not READ). I’m a fan of Raymond Chandler, but I am not seeing the plot to “The Big Sleep” here. Well, maybe someone was snoozing when this was implemented.

The fix (no, not the FIX..ENDFIX, the solution)
A few years ago at Solutions in the product lab I asked a Planning product manager (I think) about using MEATREAD and he said "It can't be done."

Curiouser and curiouser. Why not? Was there something in a METAREAD filter that would cause the world to stop spinning, the magnetic fields to fade, and result in a cockroach-only world? This did seem somewhat far-fetched.

There had to be a reason; I suspected I wasn’t hearing it. This was (and is) sort of the equivalent of waving a red cape in front of a bull and it made me curious -- why not? Essbase has had METAREAD since 6 (I think, it could be earlier). Why not write a METAREAD to Essbase when refreshing security?

There was an easy way to test this – roll my own refresh and force METAREAD filters as part of the Planning refresh.

Dare I? Briefly, I considered Robert Oppenheimer’s quote of the Bhagavad-Gita at Trinity: “I am become Death, the Shatterer of Worlds,” but in the true spirit of all mad scientists, I quickly dismissed such idle thoughts. Our future as cockroaches would bring many benefits, anyway.

Diagnosing the disease
What's the problem?

The filter in EAS as written by Planning:


Easy fix in EAS
If only it were this easy -- just copy the READ row and make it a METAREAD. Since METAREAD is more restrictive (both data AND metadata) it takes precedence, so the READ line doesn't have to be deleted:
And it looks just right in SmartView (or the Essbase-centric reporting tool of choice).

One tiny problem
But when Planning does a security refresh, it's going to go right back to the bad old ways of yore, and will strip the METAREAD off the Planner's filter. Bummer.

And if you have 200 users, manually editing their filters in EAS is going to cause your fingers to fall off, or your eyeballs pop out of your head, or something else equally horrible. I understand that cockroaches can regenerate limbs, so there’s another plus towards the possible end of the world due to METAREAD.

Back to some kind of solution
By hacking (okay, I am stretching this, but hey, it's the name of the blog, so bear with me) Essbase, we can make the user experience the same between Essbase and Planning without . Never say "It can't be done" to a moderately curious geek.

What would this refresh need to do?
1) Refresh Planning dimensions and security
2) Apply METAREAD access to those filters

Pretty simple, right? It's always easy when the consultant writes it on the whiteboard...

A little more detail
We're going to combine Planning 11x's CubeRefresh.cmd, MaxL, NT Cmd scripts (I am showing my age), and VBScript (ditto) to put together a scripted approach to Planning that:
1. Refreshes Planning through CubeRefresh.cmd
2. Writes the filters to disk via MaxL
3. Reads them into memory in VBScript
4. Programmatically generates MaxL code to apply a METAREAD to each of those filters
5. Run that new MaxL script

BTW, I happen to be mildly proficient in VBScript (call me Fred) and NT Cmd scripts. You could use whatever tools you like, e.g., Powershell, or the Bourne shell, or Rexx (call yourself T. Rex), etc.

Putting it all together

This is where you’re going to want to download the source code and follow along. At least that’s what I would do, but I have been called odd (and a few other choice descriptions, but I digress).
Script components





Script name




Description


Refresh_Planning.cmd

Overall NT Cmd control script

ModCubeRefresh.cmd

Refresh Planning utility



Create_MetaRead_Filters.wsf


VBScript to read output from Write_Filters_To_Disc.mshs and write METAREAD filters

call_metaread_add_filters.mshs

MaxL shell to run METAREAD filter adds

Metaread_Add_Filters.msh

Programmatically generated METAREAD filters

A note about ModCubeRefresh.cmd
When I ran CubeRefresh from a command line (this was within the C:\Hyperion\products\Planning\bin directory) it worked just fine. However, when I called it from another batch file it stopped all execution. I tried every trick I could think of to make it work and finally just added an “EXIT” to the end of it and saved it as ModCubeRefresh.cmd.

Refresh_Planning.cmd
To get this all to work, I used the calling script to accept parameters as shown below:


Parameter


Value


Planning encrypted password file

c:\tempdir\odtug_2009\password.txt

Planning application

plansamp

Planning admin username

admin

Refresh or create

/R

Filter switches

/FS

Essbase server

%computername%

First half of private key

316108469

Second half of private key

694177571


How do I run it?
refresh_planning.cmd c:\tempdir\odtug_2009\password.txt plansamp admin /R /FS %computername% 316108469,694177571

ModCubeRefresh.cmd
The normal CubeRefresh.cmd script has seven parameters:




Parameter




Value


-f

Path to encrypted password file

/A

Planning application name

/U

Username

/C

RMI port to bind to (Extra points if you tell me who said “This is the sort of English up with which I will not put.” And why. And if he really did. Hint -- His name isn’t Gordon Broon.)

/R

Create or refresh outline

/D

Process database

/F or /FS or /FV or /FSV or /FVS

Filters, shared or not, or even validated

/L

Connect to the local bean (Is this like, "Splendid work, old bean."? Again, I digress.)

/RMIPORT

RMI port to bind to the remote Planning bean. Use with /-L option

/DEBUG

Print debug statements


The RMIPORT, Create/Refresh, Process database, Use security filters, Connect to the local bean, and Debug parameters are optional.

In my world (this is my code, you can follow or get just as explicit as you like/need/want), I chose to explicitly define: the password file, application name, username, refresh or create, filter switch (what would be the point otherwise?), and I also passed along a log file I use for error checking.

Write_Filters_To_Disc.mshs
This is simple code that writes all Essbase filters to disc. I don’t know why I did this, as the display filter command can be limited to only one database. It works in the demo world, but in a real environment it’s a distinctly lousy idea. Feel free to modify this with a MaxL parameter variable to pass only the Essbase database filters you want. Yes, this gets trickier with multiple Plan Types.

Create_Metaread_Filters.wsf
This is where the rubber meets the road – it reads the output from Write_Filters_To_Disc.mshs, converts the READ filter to METAREAD, and generates the MaxL script Metaread_Add_Filters.msh. This file will be used to set the Planner’s filters to METAREAD.

Call_metaread_add_filters.mshs
Encrypted (username and password) MaxL shell to modify filters to use METAREAD.
Metaread_Add_Filters.msh
This is the code that adds the METAREAD. It’s kind of silly to have both a READ and METAREAD in the same filter, although it works because of METAREAD’s more restrictive nature. I tried using MaxL’s replace filter command instead of alter filter but I found that it just gave the Planner read access to the entire dimension in question. It must be (I think) something to do with Shared Services and alter filter works, so I went with that. I welcome a better approach – hint – Blogspot has a comment section and I read them. I will modify this post as required.

What does it look like?
Magic – Essbase filters that come from Planning are now METAREAD filters.

Conculsion
I believe that the Earth still rotates on its axis. All is well. I do not scuttle sideways across the floor, and my trusty prismatic compass still works, so apparently TEOTWAWKI did not occur.

NB – You will have to perform all filter refreshes through this utility. Unfortunately, for ModCubeRefresh.cmd to work, this will require Planning server access, or at least remote access. There are a number of ways to do this – Google is your friend.

The future
At the conclusion of the presentation, I heard that METAREAD functionality is now in the pipeline for Planning.

However, if you’re on Planning 9x (the Planning refresh utility is a little different), or up to 11.1.2, you may want to use this code base to give your Planning users the same metadata filtering in Essbase as they get in Planning.

Another chapter in hacking Essbase under the belt.

17 June 2009

Why I hate (and love) Business Rules, part 2

The Two Minutes' Hate is over – the below is a paean of love to HBR.

Prequel to the cool stuff
So in part 1, I reviewed how to create a HBR, substitute in local variables to read a Planning form’s Run Time Prompts (RTP), and suggested that it might be worthwhile to read this post.

The code sample I used doesn’t do any fancy allocation or calculation. It just aggregates the database using form POV members.

You may be scratching your head, thinking, “Why on earth is he banging on and on about this? Just issue an AGG(“Entity”, “Segments”) statement and be done with it.”

Zoiks! If I wrote one line of code to consolidate a database I would be out of a blog post topic – This Is A Bad Thing.

Secondly, Why Would I Want To Do That? (Ex-Comshare employees/customers/partners know of whom I speak when I write that.)

Because thirdly, and most importantly to you, I can make that database aggregate much, much more quickly.

The Trouble with AGG
Other than sounding like a misquote from a sketch in “Monty Python and the Holy Grail”, what is wrong with an “AGG” when applied to the Entity and Segments dimensions?

Think about it in the context of the form as shown below:

If I change any of the data values on this form, I am only changing them for DVD Recorders in Pennsylvania. New Hampshire isn’t in the POV. Neither are eight-track cassettes.

But if I wrote:
AGG(“Entity”, “Segments”) ;
I would be also be aggregating a New England state and a totally obsolete electronic media, if there was any Gross Profit data for those two members. After all, AGG is kind of a blunt axe.




(For you Intelligent Calc lovers out there, yes, I could use the blocks’ clean or dirty status to not calculate upper level data that already exists, but good luck doing that in a real world Planning application. There will be clean/dirty blocks all over the place and it is very difficult to keep this approach from going pear shaped.)

Having disposed of the Intelligent Calc option, while an AGG statement will certainly come up with the right result, Why Would You Want To Do That when you only changed data at the PA and DVD Recorders intersection? Why aggregate data combinations for all of the other level zero Entity and Segment combinations that haven’t even changed?

(These questions do have a point, so bear with me.) You might answer, what else am I to do? How do I consolidate my dimensions if I don’t use AGG or CALC DIM?


Tricky, innit?
Here’s the trick – you don’t need to calculate the dimension, you only need to calculate the relevant hierarchies. That’s what the calc script above shows and what your HBR can do for your forms.


@IANCESTOR is your friend
@IANCESTOR is your BFF? That I couldn’t say. But it’s going to be your HBR pal from now on because, if you think like Essbase, you can make Essbase only calculate what you want. *This* is hacking Essbase.

Let’s review the way Essbase aggregates a database, per our dear friend, the Database Administrators Guide (DBAG). For the below section we’re going to ignore the Consol database to go along with the DBAG. Just substitute Entity for Product and Segments for Market; they are the first and second consolidating sparse dimensions.




How Essbase aggregates a database
Per the ever-scintillating DBAG, Block Storage Option (BSO) databases calculate dimensions in the following order (we are only going to concern ourselves with sparse, aggregating dimensions), “Sparse dimensions (in the order they display in the database outline)”. See http://download.oracle.com/docs/cd/E10530_01/doc/epm.931/html_esb_dbag/dcacaord.htm and the “Member Calculation Order” section if you need to cite chapter and verse.

Okay, we know the order that Essbase is going to calculate the dimensions. What about the order of the members within the dimensions? This is answered by the “Block Calculation Order” section :
“Essbase calculates blocks in the order in which the blocks are numbered. Essbase takes the first sparse dimension in a database outline as a starting point. It defines the sparse member combinations from this first dimension.”

Using My Very Favorite Essbase Database In The Whole Wide World (MVFEDITWWW), i.e., Sample.Basic, this means that:
“In the Sample Basic database, Product is the first sparse dimension…Product has 19 members…Therefore, the first 19 data blocks in the database are numbered according to the calculation order of members in the Product dimension.”

The DBAG goes on to say:
“The other sparse dimension is Market. The first 19 data blocks contain the first member to be calculated in the Market dimension, which is New York…The next member in the Market dimension is Massachusetts. Essbase creates the next 19 data blocks for sparse combinations of each Product member and Massachusetts.”

This is the important bit:
Essbase continues until blocks have been created for all combinations of sparse dimension members for which at least one data value exists.

Guess what, we have just reviewed how Essbase calculates sparse dimensions from the first sparse dimension to the last one, by dimension and within each dimension. In essence, block by block.

In plain English:
1) Product is aggregated for every level 0 Market member (where data exists in Market – Essbase is smart enough not to calculate combinations that don’t exist).
2) Then Market gets aggregated by every Product (that exists, there’s Essbase being smart again).

Okay, but so what?
If we jump back to the Consol database, you may recall that I claimed you don’t need to calculate all of Entity, do you? Nope. Just the member in the form POV and its ancestors.

You also don’t need to calculate all of Segments. No, sir. Just the Segment that is in the POV and its ancestors.

This is the trick/gimmick/optimization/clever bit/thing you maybe already knew long ago and are now totally disappointed by. Sorry if you’re in the last category – I swear the next post will be better.

How do you do this?
Simple. Let’s assume that the Entity is PA and the Segment is DVD Recorder. Remember, you only need to calculate the relevant branches of the hierarchy. The other level zero/upper level members haven’t changed, so there’s no profit in recalculating them.

Use @IANCESTORS in combination with FIX statements to make Essbase aggregate:
1) The PA ancestor tree for DVD Recorders.
2) The DVD Recorder ancestor tree for the PA ancestor tree.


The results of the above versus that AGG/CALC DIM is exactly the same, only the focused aggregation does it in less than a quarter of the time.

Correction
My good Hyperion buddy Joe Aultman pointed out an error, or at least a redundancy with the above code.

Basically, the @IANCESTORS within the FIX statement isn't needed as the members themselves don't need to be aggregated, just their ancestors.

The code as posted will result in the right value, but will be that fraction of a second slower as it is addressing two more blocks. We don't want that, right?

I don't know why I wrote it in this blog with the @IANCESTOR within the FIX as that isn't how I do it at my clients. Overthinking it, I guess.

I have inserted a snippet with the correct, @IANCESTOR-in-the-FIX-only approach below. This code also shows HBR local variables for the form's run time prompts.

Back to our regular programming

That’s it. Skeptical, are you? Can’t be that easy? The proof of the pudding is in the eating. Let’s trace the data by only changing Operating Revenue for January through March.
1) Here’s the form with the original data:

2) Let’s round the numbers up and send to Essbase:

3) Switching to Excel, the blue cells show the data that should be updated. This sheet is easy peasy, as it just shows aggregated dynamic Accounts.

4) And here it is where we expect to see it aggregated one level up, by Entity and Segment.

5) And now let’s look at the when the focused aggregation HBR is run.

Notice that it doesn’t matter if we do or don't aggregate MA, NY, DVD Player, Portable DVD, and DVD/VCR combo. Only PA and DVD Recorders changed, so only their parents need be aggregated. In other words, only aggregate parents whose children's values change, and leave the rest be.
6) Here it is rolled up by Entity.

7) And rolled up by Segment.

The Payoff
What does this mean from a performance perspective (faster=better)?

Looking at the Plansamp.log file, we can see that plain old AGG took almost 7 seconds:

Where the focused aggregation didn’t even make it to 2.5 seconds.

In the Real World

Just yesterday I benchmarked a real Planning HBR with the AGG versus focused aggregation approach. How about 180 seconds versus 24 seconds? Now we’re talking 1/8 of the time. This is powerful medicine.

YMMV; these are the results I got for my client’s Essbase database. Every database is different, so my performance improvement won't necessarily map to your application. Regardless, the above technique can make “big” Planning applications fast and give you the run on save performance your users demand.

See, I love Hyperion Business Rules.

See you next time.

12 June 2009

Why I hate (and love) Business Rules, part 1

Just a quick note -- this is a two part (hence the title) post as the content was just too long. Trust me, the next post is worth waiting around for.

The trouble with Business Rules
Hyperion Business Rules (HBR) really has a number of petty annoyances for those of us forced to use its editor:

1) It loses its connection with Planning/Essbase on a whim.

2) The editor converts all of those lovely tabs you used to delimit your FIX and IF statements in the calc script editor (any editor, actually) and turns them into spaces, which then makes everything hard to read. Ah, but it's a tease, and HBR doesn't do that when you paste it, but when you save the rule.

3) It uses strange colors to identify keywords, and yes, they’re different than the calc script editor. And they’re unreadable. Yes, you can modify them, but still…

4) In HBR’s Bizarro World, it makes sense that while you can create a rule, you can’t run it until you’ve granted yourself the ability to do so, although you couldn’t grant yourself the ability to do so unless you could create it in the first place. Or can you? HBR has all sorts of byzantine roles that hardly anyone bothers with (has there ever been a real HBR Interactive User?) – in the real world (usually), you either write ‘em or run ‘em.

5) Oh, I forgot to mention the above whine was about the decent HBR editor – this is not the well intentioned (?), yet utterly misguided graphical business rule editor. I’m complaining about the “Enhanced Calc Script editor”. The other one…well, it’s been financially rewarding fixing the code generated from that beast. :)


Learning to love Business Rules
Despite all of the above whinging, HBR has one saving grace – it can read a Planning form Point of View (POV) through hidden Run Time Prompts (RTPs). And that means the calc script/business rule you wrote can be focused on the members in the POV. Everyone who does Planning knows this, right?

Actually, I lie, that isn’t really the reason I love business rules, although there’s a kernel of Business Rule Love there.

Boring stuff that you (probably) already know
You can’t build a house without setting a foundation. The below steps are what just about everyone does and is the basis for the cool stuff. Read it to refresh your memory, but the hack occurs in the next post – this delay is either going to intrigue or annoy you. I am hoping for the former, not the latter, but Que Sera, Sera.

The below example aggregates the Planning Reference Application’s Plan Type/Essbase database “Consol”.

Don’t pay too much attention to the logic of the calc, just follow the technique. The logic will be explained below.

Do pay attention to the below form. The two dimensions we will concentrate on are Entity and Segments which you can see in the form dropdown controls.


The basic steps I follow:

1) Write, test, and validate the calc script in a real editor (TextPad – my favorite, UltraEdit, EAS calc script editor or whatever) with POV replacement in mind, i.e., the Planning application has 100 products, you know that they will be in a dropdown control in the form POV, with a single product in your FIX. You’ll be replacing that hardcoded member name with a variable later on, but you’re trying to get the logic right first.


2) Create a new business rule in EAS.


3) Select the Plan Type that this HBR references. NB – It will have to be a Planning data source as you are going to point this Business Rule against Planning sooner or later.

4) Before you do much of anything else, give yourself rights to Validate or Launch the rule. Otherwise you’ll just do all of the following steps and find out that you can’t run it. Which can be fixed at the end, but is still a pain.

DOH! Caught you out, didn’t it? Does it to me every time.

5) First you assign the location, then you assign Validate or Launch rights. Why does it have to be this order? I think understanding this is to dream the impossible dream, but I digress.
6) Make it an Enhanced Business Rule by clicking on the Source tab, and then typing a single letter (your choice, actually any character on the keyboard). Business Rules will ask if you want to take the daring step of abandoning your beloved graphical business rule. You do.
7) Paste your calc script into the HBR editor. I’ll explain in the next post (there’s that tease, again) why a plain old AGG(“Entity”, “Segments”) statement wasn’t used.
8) Create HBR variables to map your POV dimension members (Entity and Segments) if hardcoding (yes, sometimes that is perfectly acceptable) a member doesn’t make sense. Make them Run Time Prompts for a single member. Only varEntity is shown below -- it would be the same for varSegment.

9) Flip back into the HBR source code, select the member name in whatever FIX statement makes sense (usually a single member; although more than one member can be selected that isn’t going to work with the single member coming off of a dimension in the POV) and right click to insert the variable.

10) Save the HBR.
11) Validate it – you will be prompted for whatever dimensions have prompts. When you saved the variable/RTP, you specified that the sample value you enter is not the default, right? It is highly likely that what works for you as a default is nonsensical to almost everyone else in the Planning application. If the code doesn’t work, revise till it does (you knew this, too). I often run the HBR at this point to ensure I haven’t inadvertently introduced an error during the copy, paste, and HBR variable substitute process.

12) Edit the Planning form in question, go to the Business Rules tab, assign your freshly baked HBR over to the righthand pane, and then click on Properties. Select the “Run on Save,” “Use Members on Data Form,” and “Hide Prompt” checkboxes and then save the rule. This is the magic that moves RTPs across from the form POV to the business rule. Save that rule.

The above twelve steps (not substance abuse) are nothing special you say, every Planning developer does this. All you’ve seen is the simple substitution of hardcoded member names for Planning form POV member names. This is actually (for those of us who remember a time before the above was possible, pre Planning 4) pretty big stuff, although nothing new, for veteran System 9/11x developers.

Spot the Clever Bit?
Here’s a test for you – do you see the Essbase hack in the code above? All will be revealed in the next post.

Stringing you along
Hopefully I’ve piqued your interest; the second installment of this post will explain why I wrote the above HBR the way I did. As I wrote before, I decided to split this subject in two because the length was getting ridiculous.

10 May 2009

This blog's introduction

History
I’ve worked with (and enjoyed, usually) Essbase since it was running on OS/2 on a Pentium 66 MHz Compaq underneath my desk with 256 MB RAM (the memory cost $25K, the same as the server) and connecting to it via my Macintosh Quadra 950 (yes, there was an Essbase add-in for the Mac which ran, amusingly enough, System 9 for Macintosh).

Essbase crashed a lot in those days if you looked at it sideways and I can painfully recall reinstalling OS/2 from floppies many, many times. When IBM released 2.1 on CD ROM I was thrilled – now it was only a three hour install instead of the whole day.

But I was hooked – this tool was amazing – it could do anything you could think of with a number and a few things I think surprised even the developers. The thing that was (and still is) so cool about a true OLAP database was that you didn’t have to know the question before you asked. That sounds odd, but think about a relational database -- it can only answer questions its creators anticipate. Essbase was so powerful it let the data speak. And, that power meant I didn’t have to write any more stupid canned reports. :)

As an IT developer I replaced a mainframe system with it, and as a consultant built multiple Excel add-ins (I cannot seem to rid myself of Excel VBA), built budgeting apps with ActiveOLAP (Essbase on the web – hot stuff in 1999), and made the transition to Planning, which is kind of a wet squib with that Essbase goodness behind the covers.

In short, Essbase has been my constant work companion since 1993, and in that time it has amazed me (ASO) and frustrated me beyond belief (Essbase 6.0.0, anyone?). And yes, I was on the beta for Essbase 6. Sorry about that.

That’s how I’ve managed to support myself since I was first told to by my then boss to “look at this client/server database.” It’s been quite a ride.

Explanation
What about the “hacking” in the name of this blog? Hacking can mean all sorts of bad things and that’s what villans do. Good hackers are more interested in taking an ordinary tool (but so cool) and doing out of the ordinary things in a geek chic way.

To that end, I’m going to try to share with you some of the dumb things I’ve done and how you don’t have to do them, how to make Essbase do things it “can’t” do, and generally make Essbase dance.

Lastly and most importantly, I’ll also share code/techniques/approaches. I welcome your comments (constructive please, I have an average ego and it is bruised when pummeled) and most of all your suggestions for improvements. I’ve never written a piece of code that hasn’t been improved through examination by a fresh set of eyes and as a consultant if I can’t fix where I wrote it, I’ll make it better next time.

And, despite the title of this web site, I won’t limit the scope of my postings to Essbase. I’ll include anything else that touches Essbase, from Planning to Dodeca, to who knows what.