The Planning Repository: HSP_MEMBER (Part 3 – UDA’s)

Not to sound like a broken record, but…a few disclaimers:

  • This is the fun stuff…that voids your warranty.  Not that you have to tell Oracle that you poke around here, but if you do, they will blame anything and everything that’s wrong on you for being in there.
  • The content here has been tested and validated against 11.1.2.3.500 and 11.1.2.4.
  • The content here has been tested and validated against Microsoft SQL Server and Oracle 11g.
  • The content here is based on the Vision sample application.
  • The content here is totally unsupported by Oracle (in case you missed the first bullet).

Eventually we will find all of tables required to get all of the important information about members out of the repository.  Our next step is to go find where UDA’s are hiding.  Much like aliases, Planning allows for an undefined number of UDA’s.  So, of course we need another table…or two.  The good news is that we actually have tables outside of the HSP_OBJECT table.

There are two tables we will be using to identify the UDA’s associated with members.  First, let’s take a look at the HSP_UDA table:

Field NameSQL Server TypeOracle TypeDescription
UDA_IDintNUMBER(38,0)The unique ID of the UDA.
DIM_IDintNUMBER(38,0)The OBJECT_ID of the dimension that the UDA belongs to.
UDA_VALUEvarchar(80)VARCHAR2(80)The actual UDA itself.

As far as Planning tables go, few are more straight forward than this one.  Contrary to aliases, UDA’s do not even merit a row in the HSP_OBJECT table.  HSP_UDA is it.  So how do members get associated with these UDA’s?  Another table of course!  Let’s take a look at the HSP_MEMBER_TO_UDA table:

Field NameSQL Server TypeOracle TypeDescription
MEMBER_IDintNUMBER(38,0)The OBJECT_ID of the member associated with the UDA.
UDA_IDintNUMBER(38,0)The unique ID of the UDA.

And I just got done saying that few are more straight forward than HSP_UDA…well, this one is even more straight forward.  Given that this one is entirely self-explanatory, let’s go straight to combining this with the the HSP_MEMBER table.  This will require a little bit more complexity.  The methods are entirely different between Microsoft and Oracle.

SQL Server:

SELECT
    o.OBJECT_NAME AS MEMBER_NAME
	,op.OBJECT_NAME AS PARENT_NAME
	,REPLACE(REPLACE(REPLACE((
		SELECT
			ou.UDA_VALUE AS UDA
		FROM 
			HSP_MEMBER_TO_UDA u
		INNER JOIN
			HSP_UDA ou ON ou.UDA_ID = u.UDA_ID
		WHERE
			u.MEMBER_ID = m.MEMBER_ID FOR XML Raw)
			, '"/>', '' )
	 AS MemberUDA
	,od.OBJECT_NAME AS DIMENSION_NAME
FROM
	HSP_MEMBER m
INNER JOIN 
	HSP_OBJECT o ON m.MEMBER_ID = o.OBJECT_ID
INNER JOIN
	HSP_OBJECT op ON o.PARENT_ID = op.OBJECT_ID
INNER JOIN
	HSP_OBJECT od ON m.DIM_ID = od.OBJECT_ID
WHERE
	od.OBJECT_NAME = 'Account'

Instead of a table join, we have to do a sub-query.  Basically, we want to get a list of all of our UDA’s associated with each member without each member having multiple rows.  So our sub-query gets the list of members first and then we use the For XML Raw command to put the results of the sub-query into XML format.  From there, we replace the XML parts with commas to give us a nice clean result:

HSP_MEMBER_TO_UDA_MOracle:

SELECT
    o.OBJECT_NAME AS MEMBER_NAME
	,op.OBJECT_NAME AS PARENT_NAME
	,od.OBJECT_NAME AS DIMENSION_NAME
  ,LISTAGG(UDA_VALUE,', ') WITHIN GROUP (ORDER BY UDA_VALUE) "UDA LIST"
FROM
	HSP_MEMBER m
INNER JOIN 
	HSP_OBJECT o ON m.MEMBER_ID = o.OBJECT_ID
INNER JOIN
	HSP_OBJECT op ON o.PARENT_ID = op.OBJECT_ID
INNER JOIN
	HSP_OBJECT od ON m.DIM_ID = od.OBJECT_ID
LEFT JOIN
  ( SELECT 
      UDA_VALUE
      ,MEMBER_ID
    FROM
      HSP_MEMBER_TO_UDA mu
    INNER JOIN
      HSP_UDA u ON mu.UDA_ID = u.UDA_ID
    ) mu ON m.MEMBER_ID = mu.MEMBER_ID
WHERE
	od.OBJECT_NAME = 'Account'
GROUP BY
  o.OBJECT_NAME
  ,op.OBJECT_NAME
  ,od.OBJECT_NAME

In Oracle, we do a little of both.  First we do a LEFT JOIN to a sub-query that returns all of our associated UDA’s for each member.  If we left it at that, when a member has three UDA’s associated, it would have three rows in the result set.  But, we made use of an excellent function to aggregate our list and separate it with commas.  The LISTAGG function let’s you choose a value and a separator and then how you want to order the list.  The downside to this function is that we have to GROUP BY all of our other columns.  So be sure to include anything you add to the query to the GROUP BY as well.  Here’s the result:

HSP_MEMBER_TO_UDA_OThis is our first major difference in the queries that we execute against the repository.  As luck would have it, we can get the same result regardless of our back end technology, we just need the right functions.  That’s it for UDA’s!  In our next look at the Planning Repository, we’ll dive into member formulas.


Veeam Backup & Replication Free Edition Experience and Sample

This post is a bit outside of what I normally write about, but I had some trouble getting it working and all of the web searching didn’t really help.  So just in case someone else is having the same problems I was having, Google should provide this for you!

First a quick introduction.  I have a server in my house (I’d like to thank my wife for allowing this) and it runs VMWare ESXi 5.5.  I have a couple of dozen VM’s that I run 24/7 and I’ve given access to a variety of these VM’s to about a dozen colleagues and clients.  My problem has always been backing up the VM’s with this many people in the system.  There is no production data on this system, but I’d prefer that we have a good backup of everyone’s work in case a RAID array in the system fails or in case someone breaks something on accident.

My current environment consists of a single server with a variety of RAID arrays.  All of the VM’s live on SSD storage, most of which is in RAID 0.  So given that I’m running RAID 0, I have even more reason to make sure I have up to date backups.  In addition to the SSD’s, I also have a traditional platter-based RAID 6 array.  This is where my backups will be stored.  Up until now, I have done occasional manual backups using the Veeam Backup & Replication product.  They have a free version that has worked well for one-off backups.  But, until now, it did not support a way of doing automated backups.

That all changed recently with the release of Update 2 to the Version 8 product.  Once I heard this, I of course had to get it running.  So I went to Veeam’s very own blog post on the topic:

Veeam’s Blog Post

So what trouble did I run into?  First, with one VM getting it all setup went perfectly.  It worked just fine on the first attempt and I was pretty excited.  But when I decided to add VM’s to the list, I hit a snag.  On the blog post, it clearly says to separate the list of VM’s with commas, but the sample file they have available for download says to use semi-colons.  So I finally figured that problem out and I was off and running.

The other cool thing that the backup script supports is an e-mail notification at the end.  So not only do I get free backups on an automated schedule, but I don’t have to check in to verify that they ran.  But this is where I hit my second snag.  The provided samples did not take into account that many SMTP servers require authentication to actually send mail.  So, I finally figured out how to get it working with authentication and while I was at it, I went ahead and added a logging section.  Now Veeam has a log of all of the backups, so this may be extraneous, but I like having it.  Too many MaxL scripts over the years…

So here’s my code.  I hope it helps!

# Orignal Author: Vladimir Eremin
# Created Date: 3/24/2015
# http://forums.veeam.com/member31097.html
# Modified By: Brian Marshall
# Modified Date: 7/26/2015

##################################################################
#                   User Defined Variables
##################################################################

# Names of VMs to backup separated by commas (Mandatory)
$VMNames = "Hyperion SS","Hyperion ES","Hyperion PL","Hyperion FM","Hyperion SQL","Hyperion BI","Hyperion DR","Hyperion ET"

# Name of vCenter or standalone host VMs to backup reside on (Mandatory)
$HostName = "HyperionESXI"

# Directory that VM backups should go to (Mandatory; for instance, C:\Backup)
$Directory = "G:\Backup\ESXi"

# Directory that log files should go to
$LogDirectory = "G:\Backup\ESXi\Logs"

# Description of what is being backed up
$BackupDescription = "Hyperion11123500"

# Desired compression level (Optional; Possible values: 0 - None, 4 - Dedupe-friendly, 5 - Optimal, 6 - High, 9 - Extreme) 
$CompressionLevel = "0"

# Quiesce VM when taking snapshot (Optional; VMware Tools are required; Possible values: $True/$False)
$EnableQuiescence = $True

# Protect resulting backup with encryption key (Optional; $True/$False)
$EnableEncryption = $False

# Encryption Key (Optional; path to a secure string)
$EncryptionKey = ""

# Retention settings (Optional; By default, VeeamZIP files are not removed and kept in the specified location for an indefinite period of time. 
# Possible values: Never , Tonight, TomorrowNight, In3days, In1Week, In2Weeks, In1Month)
$Retention = "In2Weeks"

##################################################################
#                   Notification Settings
##################################################################

# Enable notification (Optional)
$EnableNotification = $True

# Email SMTP server
$SMTPServer = "smtp.gmail.com"

# Email SMTP server port
$SMTPPort = "587"

# Email SMTP username
$SMTPUser="yourgmailaccount@gmail.com"

# Email SMTP password
$SMTPPass="youcanthavemypassword"

# Email FROM
$EmailFrom = "Brian Marshall " 

# Email TO
$EmailTo = "yourgmailaccount@gmail.com"

# Email subject
$EmailSubject = "ESXi 11.1.2.3.500 Backup Complete"

##################################################################
#                   Email formatting 
##################################################################

$style = ""

##################################################################
#                   End User Defined Variables
##################################################################

#################### DO NOT MODIFY PAST THIS LINE ################
Asnp VeeamPSSnapin

$Server = Get-VBRServer -name $HostName
$MesssagyBody = @()

foreach ($VMName in $VMNames)
{
  $VM = Find-VBRViEntity -Name $VMName -Server $Server
  
  If ($EnableEncryption)
  {
    $EncryptionKey = Add-VBREncryptionKey -Password (cat $EncryptionKey | ConvertTo-SecureString)
    $ZIPSession = Start-VBRZip -Entity $VM -Folder $Directory -Compression $CompressionLevel -DisableQuiesce:(!$EnableQuiescence) -AutoDelete $Retention -EncryptionKey $EncryptionKey
  }
  
  Else 
  {
    $ZIPSession = Start-VBRZip -Entity $VM -Folder $Directory -Compression $CompressionLevel -DisableQuiesce:(!$EnableQuiescence) -AutoDelete $Retention
  }
  
  If ($EnableNotification) 
  {
    $TaskSessions = $ZIPSession.GetTaskSessions().logger.getlog().updatedrecords
    $FailedSessions =  $TaskSessions | where {$_.status -eq "EWarning" -or $_.Status -eq "EFailed"}
  
  if ($FailedSessions -ne $Null)
  {
    $MesssagyBody = $MesssagyBody + ($ZIPSession | Select-Object @{n="Name";e={($_.name).Substring(0, $_.name.LastIndexOf("("))}} ,@{n="Start Time";e={$_.CreationTime}},@{n="End Time";e={$_.EndTime}},Result,@{n="Details";e={$FailedSessions.Title}})
  }
   
  Else
  {
    $MesssagyBody = $MesssagyBody + ($ZIPSession | Select-Object @{n="Name";e={($_.name).Substring(0, $_.name.LastIndexOf("("))}} ,@{n="Start Time";e={$_.CreationTime}},@{n="End Time";e={$_.EndTime}},Result,@{n="Details";e={($TaskSessions | sort creationtime -Descending | select -first 1).Title}})
  }
  
  }   
}

If ($EnableNotification)
{
$Message = New-Object System.Net.Mail.MailMessage $EmailFrom, $EmailTo
$Message.Subject = $EmailSubject
$Message.IsBodyHTML = $True
$message.Body = $MesssagyBody | ConvertTo-Html -head $style | Out-String
$SMTP = New-Object Net.Mail.SmtpClient($SMTPServer, $SMTPPort)
$SMTP.EnableSsl = $true 
$SMTP.Credentials = New-Object System.Net.NetworkCredential($SMTPUser, $SMTPPass); 
$SMTP.Send($Message)
}

$LogMessage = $MesssagyBody | ConvertTo-Html -head $style | Out-String
$LogMessage | Out-File "$LogDirectory\$BackupDescription-$(get-date -f yyyy-MM-dd-hh-mm).html"

You can find the rest of the setup information at the official blog post by Veeam, so I won’t try to re-invent the wheel here.  I’m no PowerShell expert, but the modifications served my purpose!


The Planning Repository: HSP_MEMBER (Part 2 – HSP_ALIAS)

Not to sound like a broken record, but…a few disclaimers:

  • This is the fun stuff…that voids your warranty.  Not that you have to tell Oracle that you poke around here, but if you do, they will blame anything and everything that’s wrong on you for being in there.
  • The content here has been tested and validated against 11.1.2.3.500 and 11.1.2.4.
  • The content here has been tested and validated against Microsoft SQL Server and Oracle 11g.
  • The content here is based on the Vision sample application.
  • The content here is totally unsupported by Oracle (in case you missed the first bullet).

Now that we have the annoying complex pieces of the HSP_MEMBER table figured out (bitmasks), let’s move on to the easier to obtain and far more gratifying information about our meta-data in Hyperion Planning.  This is where we leave the HSP_MEMBER table and go looking for everything else.  The focus of this post will be the HSP_ALIAS table.

So why aren’t aliases in the HSP_MEMBER table?  The answer is simply that Planning allows for an undefined number of alias tables.  So why add a large number of columns to the main table?  Instead we have an HSP_ALIAS table.  Before we get into that table, let’s first determine how many alias tables we are dealing with.  For that, we just go to the HSP_ALIAS_TABLE table…oh wait.  That doesn’t exist.  

Instead, we have to query the HSP_OBJECT table to determine our alias tables.  But at least if we have to use the HSP_OBJECT table, we should be able to filter that easily using something like OBJECT_TYPE, right?  Of course not…if we do that, aliases have an OBJECT_TYPE of 1 which equates to a folder.  So because there are a lot of folders in Planning, that’s not terribly helpful.  But…at least there is a parent for us to query.  So here goes:

SELECT
	o.OBJECT_NAME AS ALIAS_TABLE
FROM
	HSP_OBJECT o
INNER JOIN
	HSP_OBJECT op ON o.PARENT_ID = op.OBJECT_ID
WHERE
	op.OBJECT_NAME = 'Aliases'

Pretty simple…though it still seems like a round-about way to get our results:

HSP_MEMBER_ALIAS_TABLE

So we can see that we have two alias tables.  Now we should be ready to go take a look at the HSP_ALIAS table.  Let’s start by looking at the structure:

Field NameSQL Server TypeOracle TypeDescription
MEMBER_IDintNUMBER(38,0)The OBJECT_ID of the member.
ALIASTBL_IDintNUMBER(38,0)The OBJECT_ID of the alias table.
ALIAS_IDintNUMBER(38,0)The OBJECT_ID of the alias.

So basically, there isn’t much going on here.  Probably the most interesting thing about this table is that it just provides a reference back to the HSP_OBJECT table.  So each alias also exists in the Planning repository as an object.  So now let’s add alias information to our HSP_MEMBER query:

SELECT
    o.OBJECT_NAME AS MEMBER_NAME
	,op.OBJECT_NAME AS PARENT_NAME
	,a1.ALIAS_NAME AS ALIAS_DEFAULT
	,a2.ALIAS_NAME AS ALIAS_ENGLISH
	,od.OBJECT_NAME AS DIMENSION_NAME
FROM
	HSP_MEMBER m
INNER JOIN 
	HSP_OBJECT o ON m.MEMBER_ID = o.OBJECT_ID
INNER JOIN
	HSP_OBJECT op ON o.PARENT_ID = op.OBJECT_ID
INNER JOIN
	HSP_OBJECT od ON m.DIM_ID = od.OBJECT_ID
LEFT JOIN
	(	SELECT
			oa.OBJECT_NAME AS ALIAS_NAME
			,a.MEMBER_ID
		FROM
			HSP_ALIAS a
		INNER JOIN
			HSP_OBJECT oa ON a.ALIAS_ID = oa.OBJECT_ID
		INNER JOIN
			HSP_OBJECT oat ON a.ALIASTBL_ID = oat.OBJECT_ID
		WHERE
			oat.OBJECT_NAME = 'Default')
	 a1 ON m.MEMBER_ID = a1.MEMBER_ID
LEFT JOIN
	(	SELECT
			oa.OBJECT_NAME AS ALIAS_NAME
			,a.MEMBER_ID
		FROM
			HSP_ALIAS a
		INNER JOIN
			HSP_OBJECT oa ON a.ALIAS_ID = oa.OBJECT_ID
		INNER JOIN
			HSP_OBJECT oat ON a.ALIASTBL_ID = oat.OBJECT_ID
		WHERE
			oat.OBJECT_NAME = 'English')
	 a2 ON m.MEMBER_ID = a2.MEMBER_ID
WHERE
	od.OBJECT_NAME = 'Product'

I know what you’re thinking.  I cheated.  And its true, I used a sub-query join for each of the aliases that I knew existed.  Not very dynamic, but it gets the job done.  So let’s look at the results:

HSP_ALIAS

That’s it for aliases.  If you plan to use this type of query for other downstream targets, like say an Essbase cube, you may want to consider putting in some code to compare the two aliases.  Essbase will not allow non-unique aliases even for the same member.  Next up in our series will cover UDA’s.  Stay tuned…


It’s a Boy!

Our son was born this past Friday, July 31, 2015 at 12:06 PM.  He weighed 8 pounds and 6 ounces and was 20 inches long.

And now for some pictures:

image

His sister Hillary pushes him around:

image


Working with Strings in ASO

I needed a break from the Planning repository and recently had a colleague inquire about getting the name of a member as a string value in ASO.  This is a relatively simple process, but it occurred to me that when working with strings in Essbase, sometimes it helps to actually see what you are working with.  So how do we display text in Essbase based on an MDX formula?

First, we have to turn on typed measures.  In Essbase, the idea of typed measures are things like dates and text lists.  Typed measures also give us the ability to display formula-based text from Essbase in Smart View.  I specify Smart View because this functionality does not work in the Excel Add-In.  So let’s start by enabling typed measures:

  1. Make sure you really want to do this, because there is no undo.
  2. Open your outline (I’m using ASOsamp in this example) and go to the properties tab.
  3. Change Typed measures enabled to True.MemberName1
  4. Click OK when prompted.MemberName2

Now that we have typed measures enabled, we can try to display some text.  First let’s open the Measures dimension and add a member.  I chose ProductName as my member.  Once we have that member added, we can follow a few more steps and be ready for a retrieve:

  1. Open the Properties of the newly created member (in my case, ProductName).
  2. On the information tab, modify the Associate Format String to the following: MdxFormat([Products].CurrentMember.Member_Name)MemberName3
  3. Next we need to associate a formula for the member.  We do this because Essbase will not display a format string for a missing member.  I just used a literal zero to ensure we always have a value.MemberName4
  4. Save and restructure your cube.

We should be ready to see some text in Smart View:MemberName5

Now that we have some text, we can start to manipulate that text and see things.  First let’s do something simple, like get the length of our member name:

Set our format string: MdxFormat(NumToStr(Len([Products].CurrentMember.Member_Name)))

And in Smart View:MemberName6

Quick side-note…notice that we use the NumToStr function because the MdxFormat function requires a string parameter.

As one last sample, here’s how we use a text function like Left:

Set our format string:

MdxFormat(Left([Products].CurrentMember.Member_Name,5))

And in Smart View:MemberName7

There we have it.  Text manipulation that you can see.  Once you get your formula just right, you can use the strings in other formulas.  You can use all of your regular functions and go nuts with strings.


The Planning Repository: HSP_MEMBER (Part 1 – The Table)

Not to sound like a broken record, but…a few disclaimers:

  • This is the fun stuff…that voids your warranty.  Not that you have to tell Oracle that you poke around here, but if you do, they will blame anything and everything that’s wrong on you for being in there.
  • The content here has been tested and validated against 11.1.2.3.500 and 11.1.2.4.
  • The content here has been tested and validated against Microsoft SQL Server and Oracle 11g.
  • The content here is based on the Vision sample application.
  • The content here is totally unsupported by Oracle (in case you missed the first bullet).

In our last post, we covered the HSP_DIMENSION table.  HSP_DIMENSION is the starting point for all things meta-data, but no actual members live there.  Enter the HSP_MEMBER table.  Any member added to your application will be added to this table, regardless of the dimension.  This table contains all of the properties common to all dimensions whether they are built-in (Account, Entity, Etc.) or custom.  This will be part one of a two-part series related to members.  There are too many other tables to get into for just one post.  Let’s take a look at the table structure:

Field NameSQL Server TypeOracle TypeDescription
MEMBER_IDintNUMBER(38,0)The OBJECT_ID of the member.
DIM_IDintNUMBER(38,0)The OBJECT_ID of the dimension to which the member belongs.
DATA_STORAGEsmallintNUMBER(38,0)The data storage type to be used in Essbase for members without formulas (members with formulas have another table). See the reference table below for details.
TWOPASS_CALCsmallintNUMBER(38,0)The Essbase two-pass calculation property.
0 = not two-pass
1 = two-pass
CONSOL_OPbigintNUMBERThe consolidation operator for the member. This is actually a bitmask that allows for the storage of multiple operators across plan types in a single integer.
0 = Add
1 = Subtract
2 = Multiply
3 = Divide
4 = Percentage
5 = Ignore
6 = Never
USED_FOR_CONSOLsmallintNUMBER(38,0)No longer used.
HAS_MBR_FXsmallintNUMBER(38,0)Tells us if the member has a formula. The formula itself does not exist in this table as members can have more than one formula.
0 = no formula
1 = formula
BASE_MBRIDintNUMBER(38,0)If the member is shared, then this contains the prototype member ID from the HSP_OBJECT table.
ENABLED_FOR_PMsmallintNUMBER(38,0)Tells us if the member has been enabled for process management (workflow).
0 = not process management enabled
1 = process management enabled (table default)
PS_MEMBER_IDintNUMBER(38,0)Planning specific member ID that identifies predefined members used for custom components such as workforce (direct from OracleÉstill not exactly sure what this is for).
DATA_TYPEintNUMBER(38,0)The data type of the members.
0 = unspecified
1 = currency
2 = non-currency
3 = percentage
4 = smart list
5 = date
6 = text
ENUMERATION_IDintNUMBER(38,0)The ID of the smart list for the members. This links back to HSP_OBJECT and HSP_ENUMERATION.
USED_INintNUMBER(38,0)The plan type usage for the member. This is also a bitmask that tells us which plan types are used in a single integer.
HIERARCHY_TYPEintNUMBER(38,0)This is a new column that is supposed to pertain to ASO plan types. I'm working on understanding what it means.

If you have ever looked at this table in past releases, you will notice a pretty big difference.  The new field CONSOL_OP has replaced all of the CONSOL_OP# fields.  We’ll touch on that in a bit.  The other change you will see is that they added a new field named HIERARCHY_TYPE.  This field was added to support ASO plan types as the hierarchies in ASO can be either Stored or Dynamic.  What you won’t see in this table are some important items: the actual member name, the member name of the parent, aliases, attributes, UDA’s, formulas, and properties related to accounts, entities, scenarios, and versions.  Given that almost everything I just listed is in another table of its very own…this is a multi-part post.

Let’s start by querying just our basic member information that every member in Planning will have.  To do this we just need two tables: HSP_MEMBER (for the member information) and HSP_OBJECT (for the member name and the member name of the parent).  So let’s start with an easy query to get the member name, parent name, and a few of the simple to decode properties:

SELECT
    o.OBJECT_NAME AS MEMBER_NAME
	,op.OBJECT_NAME AS PARENT_NAME
	,od.OBJECT_NAME AS DIMENSION_NAME
    ,CASE m.DATA_STORAGE
		WHEN 0 THEN 'Store Data'
		WHEN 1 THEN 'Never Share'
		WHEN 2 THEN 'Label Only'
		WHEN 3 THEN 'Shared Member'
		WHEN 4 THEN 'Dynamic Calc and Store'
		WHEN 5 THEN 'Dynamic'
	 END AS DATA_STORAGE
	 ,CASE m.DATA_TYPE
		WHEN 0 THEN 'Unspecified'
		WHEN 1 THEN 'Currency'
		WHEN 2 THEN 'Non-currency'
		WHEN 3 THEN 'Percentage'
		WHEN 4 THEN 'Enum'
		WHEN 5 THEN 'Date'
		WHEN 6 THEN 'Text'
		ELSE 'Unspecified'
	 END AS DATA_TYPE
FROM
	HSP_MEMBER m
INNER JOIN 
	HSP_OBJECT o ON m.MEMBER_ID = o.OBJECT_ID
INNER JOIN
	HSP_OBJECT op ON o.PARENT_ID = op.OBJECT_ID
INNER JOIN
	HSP_OBJECT od ON m.DIM_ID = od.OBJECT_ID
WHERE
	od.OBJECT_NAME = 'Product'

So if we look at the query, there are several things of note.  First, we have three joins, all of which are the HSP_OBJECT table.  The first join is on MEMBER_ID.  This join provides us with access to the member name and the OBJECT_ID of the parent member.  The second joins the parents OBJECT_ID (PARENT_ID) from the first join to get the actual member name of the parent.  And the third joins the dimension’s OBJECT_ID (DIM_ID) from the HSP_MEMBER table to give us the actual name of the dimension.

After we get everything joined, we just need to decode a few of the simple fields.  We use simple case statements for DATA_STORAGE and DATA_TYPE.  It should also be noted that the DATA_STORAGE here is not necessarily that useful.  Because Planning allows us to have a different data storage for each plan type, we will have another table to look at this later.  This is the main setting from the member properties screen only.

So let’s look at the results.

SQL Server (Management Studio):

HSP_MEMBER_SIMPLE_M

Oracle (SQL Developer):

HSP_MEMBER_SIMPLE_O

That was easy…what’s the big deal?  Oh wait…we still don’t have consolidation operators.  In 11.1.2.2 and earlier, this was a simple decode.  There were separate columns for each plan type.  Starting in 11.1.2.3, they changed the structure of HSP_MEMBER.  We now have just one CONSOL_OP field and it uses a bitmask (again).  Even worse, this bitmask contains multiple values, because figuring out one value wasn’t painful enough!

So how do we store multiple values in one integer?  In this instance, they are again using the bitmask using three bits per value.  So to store a decimal 1, it becomes 001.  As we combine multiple values, we do so going from right to left.  So if I want to store decimal 1 and decimal 2 in that order, the value becomes 010001.  This value is then stored as an integer.  I’ve noticed that the integer stored is actually quite a bit bigger than just the boxes I’ve checked, but for our purposes today, we don’t care.  So how do we actually determine the consolidation operator for each of our plan types?

For the first plan type, this is an easy operation.  We can just use our bitwise operator or BITAND function to check the value without any trouble.  The real difficulty comes in when we try to do this for the second position and beyond.  The good news is we know that each value is stored in 3 bits.  This means we just need to shift over three bits to check the next value.  To do that, we just multiply the number by 2 to the power of the number of positions we want to shift.  So for the second plan type, we shift 2 to the power of 3.  For our third plan type we shift 2 to the power of 6.  But one last thing.  Before we do this, we should check the USED_IN field to verify that we even need to check the operator.  So here goes:

SQL Server:

SELECT
    o.OBJECT_NAME AS MEMBER_NAME
	,op.OBJECT_NAME AS PARENT_NAME
	,od.OBJECT_NAME AS DIMENSION_NAME
    ,CASE m.DATA_STORAGE
		WHEN 0 THEN 'Store Data'
		WHEN 1 THEN 'Never Share'
		WHEN 2 THEN 'Label Only'
		WHEN 3 THEN 'Shared Member'
		WHEN 4 THEN 'Dynamic Calc and Store'
		WHEN 5 THEN 'Dynamic'
	 END AS DATA_STORAGE
	 ,CASE m.DATA_TYPE
		WHEN 0 THEN 'Unspecified'
		WHEN 1 THEN 'Currency'
		WHEN 2 THEN 'Non-currency'
		WHEN 3 THEN 'Percentage'
		WHEN 4 THEN 'Enum'
		WHEN 5 THEN 'Date'
		WHEN 6 THEN 'Text'
		ELSE 'Unspecified'
	 END AS DATA_TYPE
	 ,CONSOL_OP
	,CASE WHEN m.USED_IN & 1 = 1 THEN 
		CASE 
			WHEN m.CONSOL_OP & 6 = 6 THEN '^' 
			WHEN m.CONSOL_OP & 5 = 5 THEN '~' 
			WHEN m.CONSOL_OP & 4 = 4 THEN '%' 
			WHEN m.CONSOL_OP & 3 = 3 THEN '/' 
			WHEN m.CONSOL_OP & 2 = 2 THEN '*' 
			WHEN m.CONSOL_OP & 1 = 1 THEN '-' 
			ELSE '+' END
		ELSE
			NULL
		END AS CONSOL1_OP
	,CASE WHEN m.USED_IN & 2 = 2 THEN 
		CASE 
			WHEN m.CONSOL_OP & POWER(2,3)*6 = POWER(2,3)*6 THEN '^' 
			WHEN m.CONSOL_OP & POWER(2,3)*5 = POWER(2,3)*5 THEN '~' 
			WHEN m.CONSOL_OP & POWER(2,3)*4 = POWER(2,3)*4 THEN '%' 
			WHEN m.CONSOL_OP & POWER(2,3)*3 = POWER(2,3)*3 THEN '/' 
			WHEN m.CONSOL_OP & POWER(2,3)*2 = POWER(2,3)*2 THEN '*' 
			WHEN m.CONSOL_OP & POWER(2,3)*1 = POWER(2,3)*1 THEN '-' 
			ELSE '+' END
		ELSE
			NULL
		END AS CONSOL2_OP
	,CASE WHEN m.USED_IN & 4 = 4 THEN 
		CASE 
			WHEN m.CONSOL_OP & POWER(2,6)*6 = POWER(2,6)*6 THEN '^' 
			WHEN m.CONSOL_OP & POWER(2,6)*5 = POWER(2,6)*5 THEN '~' 
			WHEN m.CONSOL_OP & POWER(2,6)*4 = POWER(2,6)*4 THEN '%' 
			WHEN m.CONSOL_OP & POWER(2,6)*3 = POWER(2,6)*3 THEN '/'
			WHEN m.CONSOL_OP & POWER(2,6)*2 = POWER(2,6)*2 THEN '*' 
			WHEN m.CONSOL_OP & POWER(2,6)*1 = POWER(2,6)*1 THEN '-' 
			ELSE '+' END
		ELSE
			NULL
		END AS CONSOL3_OP
FROM
	HSP_MEMBER m
INNER JOIN 
	HSP_OBJECT o ON m.MEMBER_ID = o.OBJECT_ID
INNER JOIN
	HSP_OBJECT op ON o.PARENT_ID = op.OBJECT_ID
INNER JOIN
	HSP_OBJECT od ON m.DIM_ID = od.OBJECT_ID
WHERE
	od.OBJECT_NAME = 'Account'

Oracle:

SELECT
  o.OBJECT_NAME AS MEMBER_NAME
  ,op.OBJECT_NAME AS PARENT_NAME
  ,od.OBJECT_NAME AS DIMENSION_NAME
  ,CASE m.DATA_STORAGE
    WHEN 0 THEN 'Store Data'
    WHEN 1 THEN 'Never Share'
    WHEN 2 THEN 'Label Only'
    WHEN 3 THEN 'Shared Member'
    WHEN 4 THEN 'Dynamic Calc and Store'
    WHEN 5 THEN 'Dynamic'
    END AS DATA_STORAGE
  ,CASE m.DATA_TYPE
    WHEN 0 THEN 'Unspecified'
    WHEN 1 THEN 'Currency'
    WHEN 2 THEN 'Non-currency'
    WHEN 3 THEN 'Percentage'
    WHEN 4 THEN 'Enum'
    WHEN 5 THEN 'Date'
    WHEN 6 THEN 'Text'
    ELSE 'Unspecified'
    END AS DATA_TYPE
  ,CONSOL_OP
  ,CASE WHEN BITAND(m.USED_IN,1) = 1 THEN 
    CASE 
      WHEN BITAND(m.CONSOL_OP,6) = 6 THEN '^' 
      WHEN BITAND(m.CONSOL_OP,5) = 5 THEN '~' 
      WHEN BITAND(m.CONSOL_OP,4) = 4 THEN '%' 
      WHEN BITAND(m.CONSOL_OP,3) = 3 THEN '/' 
      WHEN BITAND(m.CONSOL_OP,2) = 2 THEN '*' 
      WHEN BITAND(m.CONSOL_OP,1) = 1 THEN '-' 
      ELSE '+' END
    ELSE
      NULL
    END AS CONSOL1_OP
  ,CASE WHEN BITAND(m.USED_IN,2) = 2 THEN 
    CASE 
      WHEN BITAND(m.CONSOL_OP,POWER(2,3)*6) = POWER(2,3)*6 THEN '^' 
      WHEN BITAND(m.CONSOL_OP,POWER(2,3)*5) = POWER(2,3)*5 THEN '~' 
      WHEN BITAND(m.CONSOL_OP,POWER(2,3)*4) = POWER(2,3)*4 THEN '%' 
      WHEN BITAND(m.CONSOL_OP,POWER(2,3)*3) = POWER(2,3)*3 THEN '/' 
      WHEN BITAND(m.CONSOL_OP,POWER(2,3)*2) = POWER(2,3)*2 THEN '*' 
      WHEN BITAND(m.CONSOL_OP,POWER(2,3)*1) = POWER(2,3)*1 THEN '-' 
      ELSE '+' END
    ELSE
      NULL
    END AS CONSOL2_OP
  ,CASE WHEN BITAND(m.USED_IN,4) = 4 THEN 
  CASE 
    WHEN BITAND(m.CONSOL_OP,POWER(2,6)*6) = POWER(2,6)*6 THEN '^' 
    WHEN BITAND(m.CONSOL_OP,POWER(2,6)*5) = POWER(2,6)*5 THEN '~' 
    WHEN BITAND(m.CONSOL_OP,POWER(2,6)*4) = POWER(2,6)*4 THEN '%' 
    WHEN BITAND(m.CONSOL_OP,POWER(2,6)*3) = POWER(2,6)*3 THEN '/' 
    WHEN BITAND(m.CONSOL_OP,POWER(2,6)*2) = POWER(2,6)*2 THEN '*' 
    WHEN BITAND(m.CONSOL_OP,POWER(2,6)*1) = POWER(2,6)*1 THEN '-' 
    ELSE '+' END
  ELSE
    NULL
  END AS CONSOL3_OP
FROM
	HSP_MEMBER m
INNER JOIN 
	HSP_OBJECT o ON m.MEMBER_ID = o.OBJECT_ID
INNER JOIN
	HSP_OBJECT op ON o.PARENT_ID = op.OBJECT_ID
INNER JOIN
	HSP_OBJECT od ON m.DIM_ID = od.OBJECT_ID
WHERE
	od.OBJECT_NAME = 'Account'

As you can see, for the first plan type, we just use a regular & or BITAND to determine the consolidation operator.  For the second and third plan types we use the power function with our base number of 2 and the number of positions we want to move to the left (if we wanted to go to the right, we would divide by the power function). 

Before we look at the results, we have just a few other important notes.  Because a negative test by both the & and BITAND function returns a 0, we can’t test for the value of 0, which is of course our most common operator (+).  Instead we have to exclude all other options that we can test for.  Assuming all of our other options fail, it must be 0 (+). 

Another similar issue results in our reverse order case statement.  Because case statement stop evaluating at the first successful case, we have to look at our bigger numbers first.  For instance, if 3 (011) or 5 (101) are compared to 1 (001) first, it would always evaluate to true.  Technically, 001 will fit into 011 and 101.  Of course that’s not what we’re looking for, so I just chose to test from biggest to smallest.  I haven’t been a DBA in a long…long time.  So for those of you out there smarter than me, let me know if there’s a better way!

And finally…let’s look at the results:

SQL Server (Management Studio):

HSP_MEMBER_COMPLEX_MOracle (SQL Developer):

HSP_MEMBER_COMPLEX_OWorks great!  You can add as many plan types as you want to this query with the examples above.  And that’s enough for one post.  In part two, we’ll go find all of those other important things that are missing from the HSP_MEMBER table.


The Planning Repository: HSP_DIMENSION

Not to sound like a broken record, but…a few disclaimers:

  • This is the fun stuff…that voids your warranty.  Not that you have to tell Oracle that you poke around here, but if you do, they will blame anything and everything that’s wrong on you for being in there.
  • The content here has been tested and validated against 11.1.2.3.500 and 11.1.2.4.
  • The content here has been tested and validated against Microsoft SQL Server and Oracle 11g.
  • The content here is based on the Vision sample application.
  • The content here is totally unsupported by Oracle (in case you missed the first bullet).

In our last post, we covered the HSP_OBJECT table.  Now that we’ve covered the basics, let’s dig a little deeper and start to look at how Planning manages meta-data inside of our applications.  The starting point for this is the HSP_DIMENSION table.  This table will have a pretty light set of data (just a few more rows than the number of dimensions in your app) but has quite a few properties.  Each of the properties for the dimensions that you set in the GUI will be found here.  Let’s take a look at the table structure:

Field NameSQL Server TypeOracle TypeDescription
DIM_IDintNUMBER(38,0)The OBJECT_ID of the dimension.
ENFORCE_SECURITYintNUMBER(38,0)Determines if security is being enforced on this dimension.
0 = Don't Enforce Security
1 = Enforce Security
DIM_TYPEsmallintNUMBER(38,0)The type of dimension.
0 = None
1 = Account
2 = Time
3 = Entity
7 = Entity Hierarchy
DENSITYintNUMBER(38,0)No longer used.
USED_INintNUMBER(38,0)The plan type usage for the dimension This is a bitmask that tells us which plan types are used in a single integer.
DIM_EDITORsmallintNUMBER(38,0)Determines if the standard dimension editor can be used.
0 = Non-standard dimension, like period, scenario, etc.
1 = Standard dimension that can be modified with the standard editor , like account, entity, and custom dimensions
DENSITY1smallintNUMBER(38,0)The Essbase setting for density of the dimension.
0 = Dense
1 = Sparse
DENSITY2smallintNUMBER(38,0)See above
DENSITY3smallintNUMBER(38,0)See above
DENSITY4smallintNUMBER(38,0)See above
DENSITY5smallintNUMBER(38,0)See above
DENSITY6smallintNUMBER(38,0)See above
DENSITY7smallintNUMBER(38,0)See above
POSITION1smallintNUMBER(38,0)The Essbase setting for the position of the dimension. The position hear should line up with the number in the performance section of the GUI.
POSITION2smallintNUMBER(38,0)See above
POSITION3smallintNUMBER(38,0)See above
POSITION4smallintNUMBER(38,0)See above
POSITION5smallintNUMBER(38,0)See above
POSITION6smallintNUMBER(38,0)See above
POSITION7smallintNUMBER(38,0)See above
POSITION8smallintNUMBER(38,0)See above
POSITION9smallintNUMBER(38,0)See above
POSITION10smallintNUMBER(38,0)See above
POSITION11smallintNUMBER(38,0)See above
POSITION12smallintNUMBER(38,0)See above
POSITION13smallintNUMBER(38,0)See above
POSITION14smallintNUMBER(38,0)See above
POSITION15smallintNUMBER(38,0)See above
ENUM_ORDER1smallintNUMBER(38,0)The evaluation order of the dimension. The enum name comes Smart Lists which are stored in the HSP_ENUMERATION and HSP_ENUMERATION_ENTRY tables.
ENUM_ORDER2smallintNUMBER(38,0)See above
ENUM_ORDER3smallintNUMBER(38,0)See above
ENUM_ORDER4smallintNUMBER(38,0)See above
ENUM_ORDER5smallintNUMBER(38,0)See above
ENUM_ORDER6smallintNUMBER(38,0)See above
ENUM_ORDER7smallintNUMBER(38,0)See above
ENUM_ORDER8smallintNUMBER(38,0)See above
ENUM_ORDER9smallintNUMBER(38,0)See above
ENUM_ORDER10smallintNUMBER(38,0)See above
ENUM_ORDER11smallintNUMBER(38,0)See above
ENUM_ORDER12smallintNUMBER(38,0)See above
ENUM_ORDER13smallintNUMBER(38,0)See above
ENUM_ORDER14smallintNUMBER(38,0)See above
ENUM_ORDER15smallintNUMBER(38,0)See above
DRIVER_DIM_IDintNUMBER(38,0)The OBJECT_ID of the dimension configured for data loads in Planning.
DISPLAY_OPTIONintNUMBER(38,0)The display option selected for the dimension.
0 or null = Member name
1 = Alias
2 = Member name and Alias
3 = Alias and Member name
4 = Member count (I think this legacy)
5 = Member name and Description (I think this is legacy)

If you’ve looked at this table in the past, not too much has changed.  There are a few interesting things to mention.  First let’s look at the USED_IN field.  This field is a bitmask.  So what does that mean?  It means that Oracle is the devil.  That’s what it means.  Bitmasks are among the most annoying things you can run across when decoding someone else’s database.  But here we are, so let’s take a look.  The developers of Planning have chosen to use a single integer field to tell us all of the plan types that a specific dimension can be part of.  This means that we have up to 13 different pieces of information that are stored in a single integer.  So how do they do this?  The easiest way to illustrate this is with a table.  Here we see how we would represent all 13 plan types numerically:

Plan TypeIntegerExample
Plan Type 11Plan1
Plan Type 22Plan2
Plan Type 34Plan3
Plan Type 48Wrkforce
Plan Type 516Capex
Plan Type 632Project
Plan Type 764PlanASO1
Plan Type 8128PlanASO2
Plan Type 9256PlanASO3
Plan Type 10512PlanASO4
Plan Type 111024PlanASO5
Plan Type 122048PlanASO6
Plan Type 134096PlanASO7

So how do all of these become a single integer?  Simple  math.  Add up the ones that the dimension should be used in.  So let’s run through a few examples:

Plan Types 1 and 3:  1 + 4 = 5

Plan Types 1, 2, and 3:  1 + 2 + 4 = 7

Plan Types 1 – 13: 1 + 2 + 4 + 8 + 16 + 32 + 64 + 128 + 256 + 1048 + 2048 + 4096 = 8191

So that’s all great, but how do I determine if the plan type is used without having to build a giant set of logic?  The bitwise operator!  Basically we ask if the number is included in our sum.  For instance, is if my sum is 5, is Plan Type 2 included:

SELECT 5 & 2

This will return a 0 because 5 is a combination of 1 and 4 and does not include 2.

Let’s try a different example that returns a different an affirmative response.  If my sum is 7, is Plan Type 2 included:

SELECT 7 & 2

This will return 2.  It returns 2 because 7 does include 2.  So we can basically assume if I use the bitwise & operator and the result is not 0, then its included.

Wow…this post is getting long.  But hey, let’s keep going anyway, if you are still awake now, you must really want or need to know about how this bitwise thing works.

So how do we use this in a practical way?  Let’s take a look at the HSP_PLAN_TYPE table.  I won’t waste any space showing the structure of this table because it’s basically the same as my sample above.  The plan types by name and their numerical equivalent.  So here’s a practical example of how we can determine all of the plan types that a dimension belongs to.

SQL Server:

SELECT 
	TYPE_NAME
	,PLAN_TYPE
	,CASE WHEN (SELECT USED_IN FROM HSP_DIMENSION WHERE DIM_ID = 31) & PLAN_TYPE = 0 THEN 'Not Used' ELSE 'Used' END AS UsedDecode
FROM
	HSP_PLAN_TYPE

Oracle:

SELECT 
	TYPE_NAME
	,PLAN_TYPE
	,CASE WHEN BITAND((SELECT USED_IN FROM HSP_DIMENSION WHERE DIM_ID = 53702),PLAN_TYPE) = 0 THEN 'Not Used' ELSE 'Used' END AS UsedDecode
FROM
	HSP_PLAN_TYPE

Both queries should return a list of all of our Plan Types and whether or not the dimension in our where clause is used:

SQL Server (Management Studio):

HSP_PLAN_TYPE_M

Oracle (SQL Developer):

HSP_PLAN_TYPE_O

So now that we have made it past that diversion, let’s keep looking at our table.  The rest of the dimension table is pretty well described by the table at the very beginning of this post (does anyone remember that far back?).  But I do want to note a few interesting things.  First, you will notice that we have only 7 density fields but for position and enumeration we have 15.  Why?  I scratched my head for a minute on this one, but then it hit me…ASO doesn’t care.  So basically the repository support 7 BSO plan types and and additional 8 ASO plan types.  Now if we look at the actual limitations in Planning, we can max out Planning with 13 plan types (which you can see in our sample table above).  So we basically have an extra BSO plan type and an extra ASO plan type.  It’s also possible that they use these extra fields for some other system purpose that I’m not aware of.  If you know, drop me a comment!

So there you have it…an unexpected dive into some pretty technical workings of SQL code just to look at our dimensions.  Now that we’ve had a look at the dimensions themselves, we can start looking at the members…in our next marathon post!


Kscope15 Presentations and Queries

I’m back in Texas after my quick in-and-out trip to present.  As promised, I’m posting both my presentations and more importantly, the queries that we reviewed during my Planning repository presentation.  Have a look at the files here:

Cash Forecasting Kscope15

Planning Repository Kscope15

Planning Repository Kscope15 Queries for SQL Server

I’d love to hear any questions, comments, or suggestions anyone around any of this content.


Brian @ Kscope15

It’s been a long time since my last post.  Moving and life seem to have slowed me down a bit, but I’m still here!  Right now I’m in Hollywood, Florida getting ready for my presentations at Kscope15.  I’m here for a quick trip in and out, so if you are at the conference, you can find me Tuesday only until around 2pm.  I have two presentations:

Cash Rich, Forecasting Poor

Jun 23, 2015, Session 5, 8:30 am – 9:30 am

In many Hyperion Planning applications, cash flow is overlooked or left out of the forecast. But many well-run companies manage their Treasury operations strategically. They need to manage their cash flow and forecasting on a daily basis, and the absence of a strong cash management approach to Hyperion is a financial risk. This session will focus on one client’s approach, with a demo of an application where cash is king.

Voiding Your Warranty: The Planning Repository Exposed

Jun 23, 2015, Session 7, 11:15 am – 12:15 pm

Hyperion Planning provides an excellent platform for deploying enterprise-scale budgeting and forecasting solutions. Let’s take that world-class solution and peel the warranty label off so we can have some real fun. Learn how to delete years from the past, remove dimensions you no longer use or need, and report on secured objects and who is doing what on your applications. This session will also provide a primer to the Planning Repository and a variety of queries for attendees to take home and leave Oracle with an uneasy feeling that more support tickets could result from this session.

And here:

http://kscope15.com/presenters/presenterlist?last_id=205

I hope everyone enjoys the content!


The Planning Repository: HSP_OBJECT and HSP_OBJECT_TYPE

Welcome to the first of a long series of posts describing the Planning Repository.  The point of this series is to describe many of the tables and their purpose inside of the relational repository that supports your Hyperion Planning applications.  Additionally, we’ll take a look at things like deleting a dimension or years from your application.

First…a few disclaimers:

  • This is the fun stuff…that voids your warranty.  Not that you have to tell Oracle that you poke around here, but if you do, they will blame anything and everything that’s wrong on you for being in there.
  • The content here has been tested and validated against 11.1.2.3.500 and 11.1.2.4.
  • The content here has been tested and validated against Microsoft SQL Server and Oracle 11g.
  • The content here is based on the Vision sample application.
  • The content here is totally unsupported by Oracle (in case you missed the first bullet).

We’ll start the series with the HSP_OBJECT table.  The HSP_OBJECT table is essentially the foundation for the repository.  Every object in Planning (forms, dimension members, task lists, etc.) will have a record in this table.  Let’s start by taking a look at the columns that make up the HSP_OBJECT table:

Field NameSQL Server TypeOracle TypeDescription
OBJECT_IDintNUMBER(38,0)The unique ID of the object. ÊThis is not an identity or auto-number field, rather it is actually generated internally by Hyperion Planning. ÊThe ID will be referenced throughout the repository in virtually every other table.
OBJECT_NAMEvarchar(80)VARCHAR2(80 CHAR)The name of the object in Planning.
OBJECT_DESCRIPTIONintNUMBER(38,0)The key value from the HSP_STRINGS table that references the description of the object.
OBJECT_TYPEsmallintNUMBER(38,0)We'll cover this a little later when we get to the HSP_OBJECT_TYPE table. But in short, this is an integer value representing the type of object for the record (form, member, etc.). Sometimes this integer can be found in the HSP_OBJECT_TYPE table, sometimes it can't. More on this later.
PARENT_IDintNUMBER(38,0)The OBJECT_ID of the parent for object.
OWNER_IDintNUMBER(38,0)The OBJECT_ID of the owner of the object.
GENERATIONintNUMBER(38,0)The generation of the object if applicable and 0 if not. Objects like members and Planning Units have generations while things like forms do not.
POSITIONfloatFLOATThe sort-order for the object in relation to other objects in the hierarchy.
HAS_CHILDRENsmallintNUMBER(38,0)The flag that determines if a member is a leaf or not. If the member has children and is therefore not a leaf, the value is 1, otherwise it is 0. So if you are looking for leaves, look for 0.
MARKED_FOR_DELETEsmallintNUMBER(38,0)Members that have been deleted from Planning but not refreshed to Essbase will be marked for deletion with a value of 1. Otherwise this value should be 0.
CREATEDdatetimeDATEThe date and time that the object was created.
MODIFIEDdatetimeDATEThe date and time that the object was last modified.
MOVEDdatetimeDATEThe date and time that the object was last moved.
OLD_NAMEvarchar(80)VARCHAR2(80 CHAR)This field is used to maintain data in Essbase when a member is renamed. Prior to refreshing Essbase, the old name of the member will be here. Once the refresh is complete, the OBJECT_NAME and OLD_NAME will be the same.
SECCLASS_IDintNUMBER(38,0)No longer used.
REMOVABLEsmallintNUMBER(38,0)The flag that determins if a member can be deleted. If an object can be deleted, the value is 1, otherwise it is 0.
MODIFIED_BYvarchar(80)VARCHAR2(80 CHAR)The user ID of the last user to modify the member. If you want any additional auditing of modifications to members, you have to hit the audit tables assuming you have them turned on.

This table, for all of its importance to Planning isn’t all that interesting.  There are really only two fields that require a little more information than the table above.  The first is OBJECT_TYPE and the second is DESCRIPTION.  The OBJECT_TYPE field is of particular interest if you really look at the data.  For instance, if we were to check the OBJECT_TYPE contents against the HSP_OBJECT_TYPE table, we’ll find that not all types have made it into the table.  I’ve gone through and identified all of the types that I have in my sample application.  Here’s the full list:

OBJECT_TYPETYPE_NAME from HSP_OBJECT_TYPETYPE_NAME Made Up By Brian
1Folder
2Dimension
4Calendar
5User
6Group
7Form
9Currency
10Alias
11Cube
12Planning Unit
23NULLType for the parent of all task lists
24NULLTask List
25NULLTask List Item
31Scenario
32Account
33Entity
34Time Period
35Version
38Year
39NULLPlanning Unit Hierarchy
45Shared Member
50User Defined Dimension Member
51NULLPlanning Unit Selection
103NULLMenu
107NULLComposite Form
115NULLCalculation Manager Rule
116NULLCalculation Manager Rule Set
117NULLCalculation Manager Variable
118NULLType for the parent of Calculation Manager Rules
119NULLType for the parent of Calculation Manager Rule Sets
120NULLType for the parent of Calculation Manager Variables
143NULLGood questionÉsomething related to Business Metrics

Now let’s talk about DESCRIPTION.  This is the first occurence of the HSP_STRINGS table.  This table itself is pretty simple.  It’s just a list of strings used in various places inside of Planning.  I believe the idea here was to save space in the HSP_OBJECT table.  Descriptions could be quite (up to 255 characters) so why allocate space in a table for long strings when you can separate them out?  So how do we actually see the description?  A simple join:

SQL Server and Oracle Code: 

SELECT 
	OBJECT_ID
	,OBJECT_NAME
	--,DESCRIPTION
	,THE_STRING
	,OBJECT_TYPE
	,PARENT_ID
	,OWNER_ID
	,GENERATION
	,POSITION
	,HAS_CHILDREN
	,MARKED_FOR_DELETE
	,CREATED
	,MODIFIED
	,MOVED
	,OLD_NAME
	,SECCLASS_ID
	,REMOVABLE
	,MODIFIED_BY
FROM 
	HSP_OBJECT o
LEFT JOIN
	HSP_STRINGS s ON o.DESCRIPTION = s.STRING_SEQ

 The results of the query should render all of our objects along with their description (and every other field in the HSP_OBJECT table):

SQL Server (Management Studio):

HSP_OBJECT_QUERY_M

Oracle (SQL Developer):

HSP_OBJECT_QUERY_O

That’s it for the HSP_OBJECT table!  Next we’ll start looking at the way Planning stores meta-data (the HSP_DIMENSION table first and then HSP_MEMBER).