Blog Home  Home RSS 2.0 Atom 1.0 CDF  
Mark Mrachek - Business Intelligence Blog - Saturday, February 25, 2006
 
 Saturday, February 25, 2006

I've been working with a new User Interface Platform group in Microsoft Services IT for the last three months.  This group is using Panorama as their BI interface within their platform. 

Although I have a bias toward building custom web based applications for BI UI's, I must say that I'm impressed with Panorama.  The charting is great and the security models are flexible.  I would say the biggest drawback I've seen at this point is that Panorama does not handle dynamic security models in Analysis Services very well.

One thing I really like about Panorama is that you can customize slicer security by altering their base asp files which gives you maximum flexibility when dealing with data level security.

I'll try to post more information about Panorama in the future.

2/25/2006 12:02:35 PM (Pacific Standard Time, UTC-08:00)  #    Comments [0]   Web Reporting  | 
 Sunday, February 19, 2006

If you have ever used the NonEmptyCrossjoin function, you may have been surprised (and frustrated) with the results your queries returned.  This function was added to Analysis Services 2000 to help with performance of queries, but it commonly returned incorrect results.  Fortunately the NonEmptyCrossjoin function is being deprecated.

 

The NonEmpty() function is new in Analysis Services 2005 and should be used instead of the NonEmptyCrossjoin function.  The NonEmpty() function is powerful because it can be applied to MDX expressions whereas the NON EMPTY clause was only available at the top level in SELECT statements.

 

This syntax for the NonEmpty() function is NonEmpty({set},[filter set])

 

I met with a developer and PM from the Analysis Services team last week in Redmond and they provided me with more information on the NonEmpty() function. 

 

If you use the NonEmpty() function without a filterset, then the expression is evaluated using the default measure in your cube.  So, if you do not define a filterset and you did not specify a default measure in your cube, then you may get unexpected results with this function.  Because, even though you may not have explicity defined a default measure, all cubes have a default measure (Analysis Services will define a default measure even if you did not define one) and the NonEmpty() function will use this default measure as the context in which to evaluate the expression.

2/19/2006 6:21:11 PM (Pacific Standard Time, UTC-08:00)  #    Comments [0]   MDX  | 
 Tuesday, February 14, 2006

I have been developing reporting systems for over nine years.  For the last six years I’ve been working as an independent contractor and consultant for my own company, dymetrics, inc., which specializes in delivering Business Intelligence solutions.

 

In November of 2005, I joined a consulting company as a full-time employee to work on a contract at Microsoft. 

 

Although my areas of expertise include data warehousing, OLAP, and ASP.Net development, my primary focus at Microsoft has been working with Analysis Services 2005.  I have also been helping the new User Interface Platform group in Microsoft’s Services IT department implement Panorama as their Business Intelligence user interface.

 

Once my assignment is completed at Microsoft, I will most likely go back to independent contracting since I prefer working independently as opposed to being a full-time employee.

2/14/2006 1:05:40 AM (Pacific Standard Time, UTC-08:00)  #    Comments [0]   Consulting  | 
 Saturday, February 11, 2006

I’ve found myself working with UDF’s more and more lately and thought it would make a good blog topic. So, if you are interested in creating a UDF to use in your MDX, read on.

 

I’m using the Adventure Works database which is the sample database Analysis Services 2005.  If you have not yet created the Adventure Works database, you can get the source files and installation instructions here.

 

First, create a class library in Visual Studio.

 

Here is a very basic sample of a C# class library I created:

using System;
using System.Collections.Generic;
using System.Text;

namespace AWLib
{
public class Utilities
{
public string GetSomeSet(){
int sec = DateTime.Now.TimeOfDay.Milliseconds;
string set;

if (sec < 200) {
set = "[Date].[Fiscal].Children";
}
else if (sec < 400)
{
set = "[Reseller].[Reseller Type].Children";
}
else if (sec < 600)
{
set = "[Employee].[Employees].Children";
}
else if (sec < 800)
{
set = "[Geography].[Geography].Children";
}
else {
set = "[Sales Channel].[Sales Channel].Children";
}
return set;
}

}
}

Next, after you have successfully compiled your class library, you need to add it to the Adventure Works database.  You do this by:

1)      Opening SQL Server Management Studio

2)      Right click on the ‘Assemblies’ folder in the Adventure Works database, then click on ‘New Assembly…’

 

Note: You can also add your class library to the server assemblies so the UDF’s will be available in all databases on your Analysis Services instance.  Also, you can add your assembly to your ‘Business Intelligence Development Studio’ project and deploy your assembly along with your database.

 

To test the UDF (from above), open a ‘New Query’ window in SQL Server Management Studio, select the Adventure Works database and execute the following MDX.

 

SELECT Measures.Members ON 0,
STRTOSET(AWLib.GetSomeSet()) ON 1
FROM [Adventure Works]

The AWLib.GetSomeSet() function will return different sets based on the milliseconds from the timestamp on your server. 

 

UDF’s are very powerful and flexible and help to extend the functionality of MDX.  Here are just a few examples of how you can use UDF’s 1) implementing dynamic security in Analysis Services, 2) dynamically building sets and tuples, and 3) creating complex mathematical calculations.


2/11/2006 12:51:43 AM (Pacific Standard Time, UTC-08:00)  #    Comments [0]   Analysis Services  | 
 Saturday, February 04, 2006

I ran into this error the other day when attempting to design aggregations for a cube using the AS 2005 Aggregation Design Wizard.  The full error was: "Clr Assembly must have main file specified. To restart the process, resolve the problem and then click Start."

I was able to able to resolve this error by:   
    1) Stopping the services of AS where your database resides
    2) Renaming the data directory for Analysis Services Databases.
        Default location: C:\Program Files\Microsoft SQL Server\MSSQL.2\OLAP\Data  (rename the 'Data' folder to 'Data_OLD')
        Note: MSSQL.2 is the location for the AS files on *my* server.  You're AS folder may be named MSSQL.1 or MSSQL.3 or whatever.
                Also note, when you rename this folder, all existing databases that resided in your instance of AS are now unavailable.  You will need to re-deploy all of your databases to this server (if applicable).
    3) Create a new folder in the 'C:\Program Files\Microsoft SQL Server\MSSQL.2\OLAP\' directory called 'Data' (basically replacing the original 'Data' folder you renamed in Step 2 with a new "empty" folder)
    4) Start the AS Services on your server
    5) Deploy your Analysis Services Project to the server
    6) Create your aggregations.

This error has nothing to do with your database objects but everything to do with internal AS files.  Why or how this occurs is beyond me, but some AS files must be corrupt which causes this error.
2/4/2006 11:30:08 AM (Pacific Standard Time, UTC-08:00)  #    Comments [0]   Analysis Services  | 
 Wednesday, January 25, 2006

I’ve written thousands of lines of MDX code over the years to support web reporting applications and today I discovered something new.  Although it probably isn’t new to MDX, it was new to me because I never needed it until today.  I was targeting the ascendants of a member and based on the parent of the member, I needed to alter the slice of the cube.

Typically, when I use conditional statements, I create calculated member and handle the logic in the WITH statement.  Today’s challenge was different because I was not able to control what calculated members are passed from Panorama to Analysis Services.  Instead, I needed to work through the ‘WHERE’ clause to slice my data.  

Anyways, I don’t know if I’ll ever need to use it in the future, but it’s nice to know that this functionality exists.  Below is a sample MDX statement that uses a conditional statement in the ‘WHERE’ clause.  This sample provides little value except to illustrate that you can indeed use conditional statements in ‘WHERE’ clauses.

SELECT [Measures].[Internet Total Product Cost] ON 0,
[Sales Reason].[Sales Reasons].Members ON 1
FROM [Adventure Works]
WHERE (IIF([Sales Territory].[Sales Territory].[Sales Territory Country].[Australia].Parent.Name = “Pacific”, [Sales Territory].[Sales Territory], [Sales Territory].[Sales Territory].[Sales Territory Country].[Australia]))

1/25/2006 10:46:50 PM (Pacific Standard Time, UTC-08:00)  #    Comments [0]   Analysis Services | MDX  | 
 Sunday, January 15, 2006

I have a bias towards developing custom web-based front-ends for reporting applications.  This bias has come from working with clients who have purchased commercial front-ends only to get caught in a dependency trap.  This dependency trap has three components which are on-going software maintenance fees, large consultant fees, and application customization fees. 

Here is a prime example of the dependency trap.  I had a client who purchased an off-the-shelf application that provided about 85% of the functionality they needed.  A good question to ask would be why somebody would purchase an application that only delivers 85% of what you need?  The answer has three parts.  First, it was the best application available at the time.  Second, the VP in charge of the department did not want to build an in-house application.  And third, the salesman for the company told the VP that the application could be easily customized.

Anyway, this off-the-shelf application included 320 hours of consultant time (2 consultants @ 40 hours per week for 4 weeks) for application configuration and training.  After one month, the consultants had burned up the 320 hours on the installation of the application and on training.  However, the performance of the application was pitiful and not operating as promised.  So the consultants stayed for another four months (additional cost to my client) to do their original work correctly.

To make a lengthy story short, every time a change was requested (to customize the application) the minimal waiting period was two months the minimal cost was $8,000 per incident.  Granted, if the changes were significant, I could understand the cost and time….but one request was to have an existing column added to a report.  Since the column already existed, all the company that sold the application had to do was change the query the report was using…something that would require at most, one hour of work.  Instead, they told my client they would need to wait at least two months and pay the $8,000 to have the column added to the report.  How ridiculous.

When my clients ask me what I would use for a front-end, I always recommend developing an in-house web-based front-end because the costs are less and the in-house application will be more flexible and customizable in the future compared to the off-the-shelf products. 

A big concern for senior management is that in-house applications are dependent on the developers who build them.  I totally disagree.  If the in-house application is properly documented then it will not be dependent on individual developers.  So if attrition is a concern, you have documentation for your new developers to follow.

1/15/2006 7:07:32 PM (Pacific Standard Time, UTC-08:00)  #    Comments [0]   Web Reporting | Custom  | 
 Friday, January 13, 2006

Here is a bit of sample code for adding relationships between objects in Data Source Views in Analysis Services.

Dim strConn As String = "Provider=MSOLAP.3;Data Source=LOCAL;Initial Catalog=AS_DATABASE"
Dim objServer As New Server()
Dim objDatabase As Database
Dim objDSV As DataSourceView

objServer.Connect(strConn)
objDatabase = objServer.Databases.FindByName("AS_DATABASE_NAME")
objDSV = objDatabase.DataSourceViews.FindByName("DATA_SOURCE_VIEW_NAME")

'Delete existing relationships in dsv
If objDSV.Schema.Relations.Count > 0 Then
   objDSV.Schema.Relations.Clear()
   objDSV.Update()
End If

'Create Relationships in DSV
AddRelationship(objDSV, "dbo_PKTable1", "PKColumnName", "dbo_FKTable1", "FKColumnName")
AddRelationship(objDSV, "dbo_PKTable2", "PKColumnName", "dbo_FKTable2", "FKColumnName")
AddRelationship(objDSV, "dbo_PKTable3", "PKColumnName", "dbo_FKTable3", "FKColumnName")
AddRelationship(objDSV, "dbo_PKTable4", "PKColumnName", "dbo_FKTable4", "FKColumnName")

'Update DSV
objDSV.Update()


Sub AddRelationship(ByVal objDSV As DataSourceView, ByVal strPKTable As String, ByVal strPKCol As String, ByVal strFKTable As String, ByVal strFKCol As String)
   Dim objPKCol As Data.DataColumn = objDSV.Schema.Tables(strPKTable).Columns(strPKCol)
   Dim objFKCol As Data.DataColumn = objDSV.Schema.Tables(strFKTable).Columns(strFKCol)
   Dim strFKName As String = "FK_" & strFKTable & "_" & strFKCol

   objDSV.Schema.Relations.Add(strFKName, objPKCol, objFKCol, True)
End Sub

If you choose to use this sample code, please note that the underscore replaces the "." in the user.object naming standard used by SQL Server.  For instance, if your table owner is dbo and your table is called tbltable1, then the complete SQL Server name of the object would be dbo.tblTable1.  Analysis Services replaces the "." with and underscore.  The name that the table in the DSV would be dbo_tblTable1.

 

1/13/2006 8:09:21 PM (Pacific Standard Time, UTC-08:00)  #    Comments [0]   AMO  | 
Copyright © 2010 Mark Mrachek. All rights reserved.
DasBlog 'Portal' theme by Johnny Hughes.