Excel has a nice function : networkdays() -- it will show you the net amount of working days between two dates (excluding weekends).
Business Objects doesn't have this -- it only has "daysbetween()"
=(Truncate(DaysBetween([Start Date]; [End Date]) / 7 ; 0) * 5) + ToNumber(Substr("1234555123444512333451222345111234500123450123455"; ((DayNumberOfWeek([Start Date])-1)*7)+Mod(DaysBetween([Start Date];[End Date]);7)+1 ; 1))
This is the formula I found on
http://www.dagira.com/2009/10/23/calculating-business-days-between-two-days-via-report-functions/
I had worked out a procedure of my own, but this one is way shorter -- although not easier :D
let's break it down:
=(Truncate(DaysBetween([Start Date]; [End Date]) / 7 ; 0) * 5)
First we get the number before the comma, when dividing the number of days between two dates by 7. That gives us the number of entire weeks in the period.
Next, that number is multiplied by 5 and we have the basic number of weeks.
But because we did a Truncate, we lost a few days.
And that's part of the formula I'm taking my hat off for.. wow..
=ToNumber(Substr("1234555123444512333451222345111234500123450123455"; ((DayNumberOfWeek([Start Date])-1)*7)+Mod(DaysBetween([Start Date];[End Date]);7)+1 ; 1))
One day I'll be able to write constructions like these :)
Peter
External memory from my trainings. I used to teach Business Objects, Internet Development and Hardware at Xylos NV (http://www.xylos.com)
dinsdag, maart 24, 2015
vrijdag, maart 20, 2015
Verifying Bank account numbers in Business Objects
Today, I was asked to do a data-quality check on bank account numbers -- of course, since I live in Belgium, I will focus on the Belgian bank account numbers, but the system is pretty much the same elsewhere -- and I'll be focussing on the limitations of Business Objects and how to get around them.
The basic calculation function that is being used is Modulo 97.
We'll be considering the following fictious IBAN Bank account number:
BE74857221143209
(I used http://www.mobilefish.com/services/random_iban_generator/random_iban_generator.php to generate a random number)
The first part we need to do, is move the left 4 characters to the right.
[strInvertedBacc]=Substr("BE74857221143209";5;Length("BE74857221143209")-4) + Left("BE74857221143209";4)
Which gives us:
857221143209BE74
Next, we need to replace the letters with numbers.
A = 10
B = 11
C = 12
D = 13
E = 14
....
Z = 35
Replace(Replace([strInvertedBacc];"B";"11");"E";"14")
I know this is a shortcut -- but I'm being pragmatic here -- to take into account all possible countrycodes would make the code way too complex.
So now our number has become:
857221143209111474
Now we perform the function tonumber() on it and..
Here we have the first indication why I'm writing this article. Business Objects makes a mistake and the number displayed is :
857221143209111000
That would be bad.
The solution in Business Objects is to treat the number in two halves. Excel has an even lower treshhold for error on this kind of calculation. There you have to work in groups of 5.
Mod(ToNumber(Left("857221143209111474";9));97)
gives us our left numbers
[leftnumbers] = 36
We turn that number into a string and we stick the right 9 numbers to it
=formatnumber([leftnumbers];"0") + Right(857221143209111474;9)
[newnumber] = 36209111474
which we turn into a number
And we perform another Modulo 97 on that:
= mod(tonumber([newnumber]);97)
= 1
And if the result is 1 -- the number is correct.
The total formula is:
=Mod(ToNumber(FormatNumber(Mod(ToNumber(Left("857221143209111474";9));97);"0") + Right("857221143209111474";9));97)
That's how it's done.
Enjoy,
Peter
The basic calculation function that is being used is Modulo 97.
We'll be considering the following fictious IBAN Bank account number:
BE74857221143209
(I used http://www.mobilefish.com/services/random_iban_generator/random_iban_generator.php to generate a random number)
The first part we need to do, is move the left 4 characters to the right.
[strInvertedBacc]=Substr("BE74857221143209";5;Length("BE74857221143209")-4) + Left("BE74857221143209";4)
Which gives us:
857221143209BE74
Next, we need to replace the letters with numbers.
A = 10
B = 11
C = 12
D = 13
E = 14
....
Z = 35
Replace(Replace([strInvertedBacc];"B";"11");"E";"14")
I know this is a shortcut -- but I'm being pragmatic here -- to take into account all possible countrycodes would make the code way too complex.
So now our number has become:
857221143209111474
Now we perform the function tonumber() on it and..
Here we have the first indication why I'm writing this article. Business Objects makes a mistake and the number displayed is :
857221143209111000
That would be bad.
The solution in Business Objects is to treat the number in two halves. Excel has an even lower treshhold for error on this kind of calculation. There you have to work in groups of 5.
Mod(ToNumber(Left("857221143209111474";9));97)
gives us our left numbers
[leftnumbers] = 36
We turn that number into a string and we stick the right 9 numbers to it
=formatnumber([leftnumbers];"0") + Right(857221143209111474;9)
[newnumber] = 36209111474
which we turn into a number
And we perform another Modulo 97 on that:
= mod(tonumber([newnumber]);97)
= 1
And if the result is 1 -- the number is correct.
The total formula is:
=Mod(ToNumber(FormatNumber(Mod(ToNumber(Left("857221143209111474";9));97);"0") + Right("857221143209111474";9));97)
That's how it's done.
Enjoy,
Peter
Labels:
Bankaccount,
BusinessObjects,
Tips,
Trick,
Workaround
maandag, april 28, 2014
VBScript CSV to JSON
'CSVToJSON 'Purpose : Convert an existing CSV file, to a JSON file. 'Method: drag a file over this script. It will parse your textfile and convert it to a JSON file. set fso = wscript.createobject("scripting.filesystemobject") if wscript.arguments.count > 0 then for each x in wscript.arguments iLines = 0 strOutFile = x fso.movefile x, replace(x,".csv", "_input.csv") set objInFile = fso.opentextfile(replace(x,".csv", "_input.csv")) set objOutFile = fso.createtextfile(replace(x, ".csv", ".json")) 'read header objOutFile.writeline"var data={""users"":[" strHeader = objInFile.readline strHeader = replace(strHeader, """", "") arrTitles = split(strHeader, "|") do until objInFile.AtEndOfStream strLine = objInFile.readline strLine = replace(strLine, """","") arrLine = split(strLine, "|") iCount=0 iLines = iLines + 1 objOutFile.writeline "{" do while iCount <= ubound(arrTitles) if iCount < ubound(arrTitles) then objOutFile.writeline """" & replace(arrTitles(iCount), " ", "_") & """" & ":" & """" & arrLine(iCount) & """" & "," else objOutFile.writeline """" & replace(arrTitles(iCount), " ", "_") & """" & ":" & """" & arrLine(iCount) & """" end if iCount = iCount +1 loop objOutFile.writeline "}," loop objOutFile.writeline "{" objOutFile.writeline """O"":""O""}], ""count"":""" & iLines & """" objOutFile.writeline "}" next msgbox "Done." else msgbox "drag csv files over this script for it to work !" end if
zondag, februari 09, 2014
Business days between two dates (in Business Objects)
A colleague of mine asked me: is it possible to calculate the amount of working days between two dates. Immediately, I thought : of course, but as it turns out, it's not that easy.
A quick google and I found this formula:
A quick google and I found this formula:
=DaysBetween(RelativeDate([Begin];Floor(DayNumberOfWeek([Begin])/6)*(8-DayNumberOfWeek([Begin])));RelativeDate([End];Floor(DayNumberOfWeek([End])/6)*(8-DayNumberOfWeek([End])))) -Floor(DaysBetween(RelativeDate(RelativeDate([Begin];Floor(DayNumberOfWeek([Begin])/6)*(8-DayNumberOfWeek([Begin])));(DayNumberOfWeek(RelativeDate([Begin];Floor(DayNumberOfWeek([Begin])/6)*(8-DayNumberOfWeek([Begin]))))*-1)+1);RelativeDate([End];Floor(DayNumberOfWeek([End])/6)*(8-DayNumberOfWeek([End]))))/7)*2
I'm sure there's an easier way, and I'm working on it, but for the time being, it does the job just nicely. Thanks to this guy's post: http://blog.davidg.com.au/2012/11/workdays-between-two-dates-in-webi.html
Thanks mate. Loved the article.
Peter
vrijdag, november 08, 2013
Excel to SQL
Right. This is still a work in progress -- and if any of you super-savvy coders lend a hand, I'll be most appreciative -- until such time, here is MY very own script to upload data from Excel to SQL.
Be careful when you use this though, it's not finished by a LONG shot.
Some pre-requisites:
1) Column names better not contain too many special characters
2) the tab of your excel document should be equal to the name of the table
Just paste this code into notepad and save as excelToSQL.vbs
modify the settings for the connectionString and you're all set.
Drag an excel file on top of this script and watch..
Shortcomings
The script has a VERY rudementary detection of column-type and length -- it's something I'll be developing in more detail later.
Dates are uploaded as-is -- that's a really bad one, because month and day inversion are quite likely.
Decimal values are uploaded as-is -- if there is a discrepancy between your excel and the sql -- good luck to you, the data will, or create an error, or upload wrongly.
And yes, I AM aware that I use global variables inside my functions -- I had some problems passing objects to functions or I wouldn't have.
so, handle with care and enjoy.
'-----------------------------
Be careful when you use this though, it's not finished by a LONG shot.
Some pre-requisites:
1) Column names better not contain too many special characters
2) the tab of your excel document should be equal to the name of the table
Just paste this code into notepad and save as excelToSQL.vbs
modify the settings for the connectionString and you're all set.
Drag an excel file on top of this script and watch..
Shortcomings
The script has a VERY rudementary detection of column-type and length -- it's something I'll be developing in more detail later.
Dates are uploaded as-is -- that's a really bad one, because month and day inversion are quite likely.
Decimal values are uploaded as-is -- if there is a discrepancy between your excel and the sql -- good luck to you, the data will, or create an error, or upload wrongly.
And yes, I AM aware that I use global variables inside my functions -- I had some problems passing objects to functions or I wouldn't have.
so, handle with care and enjoy.
'-----------------------------
' excelToSQL.vbs
' Purpose : transfer an excel file to a SQL server.
' Use : drag 1 Excel file over the script and the data is transferred/
' Author: Peter De Rop
' Date: 2013/11/8
if WScript.Arguments.Count > 1 Then
WScript.Echo "Error! Please specify the source path and the destination. Usage: excelToSQL SourcePath.xls"
Wscript.Quit
else
' get timer before task begins:
starttime = Timer()
'define variables
dim objExcel, objWorkbook, objWorksheet, iXMax, objConn, objRS, bAppend, strTablename
dim arrColNames()
dim arrColTypes()
'set objects
set objExcel = createobject("Excel.Application")
set objWorkbook = objExcel.workbooks.open(wscript.arguments(0))
set objWorksheet = objWorkbook.worksheets(1)
strTablename = replace(objWorksheet.name, " ", "")
set objConn = createobject("ADODB.CONNECTION")
bAppend = false
objConn.connectionstring="DRIVER=SQL Server;Server=yoursqlserver;Database=database;User Id=userid;Password=password;"
objConn.open
'obtain the names, types and lengths of the columns to create
fnObtainColumnNames()
' check if the table exists or not -- ask to append, if the table already exists, if no, drop the table
set objRS = createobject("ADODB.Recordset")
objRS.activeconnection = objConn
q = "select count(table_name) as iTable from information_schema.tables where table_name='" & objWorksheet.name & "'"
objRS.open q,,1,1
if objRS.fields("iTable").value > 0 then
if msgbox("Drop the existing table?",vbQuestion+vbYesNo,"") = vbYes then
q = "drop table " & objWorksheet.name
objRS.close
objRS.open q,, 1,3
if msgbox("Store the data?",vbQuestion+vbYesNo,"") = vbYes then
fnObtainTypeAndLength()
q = fnAssembleQuery()
objRS.open q,, 1, 3
fnStoreData()
end if
else
fnStoreData()
end if
else
fnObtainTypeAndLength()
q = fnAssembleQuery()
objRS.close
file.writeline q
objRS.open q,, 1, 3
fnStoreData()
end if
' convert the data to the right type if needed
'close excel document
objWorkbook.close
objExcel.quit
set objWorkbook = nothing
set objExcel = nothing
' get timer after task completes:
endtime = Timer()
' display results:
msgbox "The task completed in " & endtime-starttime & " s"
end if
' fnObtainColumnNames(oWS)
' Purpose: this function will run through the first line of the excel worksheet and obtain each of the column names.
' it will clean them up and store them in an array.
function fnObtainColumnNames()
dim iX, iY
iX = 1
iY = 1
iXMax = 1
do until objWorksheet.cells(iY, iX) = ""
iXMax = iXMax + 1
iX = iX + 1
loop
redim arrColNames(iXMax)
iX = 1
do until objWorksheet.cells(iY, iX) = ""
arrColNames(iX) = replace(replace(replace(replace(objWorksheet.cells(iY, iX), " ", ""), "+", ""), "(", ""), ")", "")
iX = iX + 1
loop
end function
' fnObtainType
' Purpose: this function will run through all columns and determine what the type of each column needs to be.
' It will then store this information in an array of types
function fnObtainTypeAndLength
dim iX, iY, iMaxLen, iType
iX = 1
iY = 2
redim arrColTypes(iXMax)
for iX = 1 to iXMax
iMaxLen = 0
iY = 2
do until iY = 10
iType = vartype(objWorksheet.cells(iY, iX))
if len(objWorksheet.cells(iY, iX)) < iMaxLen then
iMaxLen = len(objWorksheet.cells(iY, iX))
end if
iY = iY + 1
loop
if iType = 8 or iType = 0 or iType = 1 or iType = 12 then
arrColTypes(iX) = fGetType(iType) & "(" & iMaxLen + 100 & ")"
else
arrColTypes(iX) = fGetType(iType)
end if
next
end function
Function fGetType(vType)
Select Case vType
Case 0 fGetType = "varchar"
Case 1 fGetType = "varchar"
Case 2 fGetType = "int"
Case 3 fGetType = "Float"
Case 4 fGetType = "Float"
Case 5 fGetType = "Float"
Case 6 fGetType = "Float"
Case 7 fGetType = "datetime"
Case 8 fGetType = "varchar"
Case 9 fGetType = "varchar"
Case 10 fGetType = "varchar"
Case 11 fGetType = "int"
Case 12 fGetType = "varchar"
Case 13 fGetType = "varchar"
Case 14 fGetType = "Float"
Case Else fGetType = "undetected"
End Select
End Function
' purpose : assemble the table creation query
' fnAssembleQuery
function fnAssembleQuery
strQuery = "create table " & strTablename & " ("
iCount = 1
do while iCount <= ubound(arrColNames)-1
if iCount < ubound(arrColNames)-1 then
strQuery = strQuery & "F_" & arrColNames(iCount) & " " & arrColTypes(iCount) & ","
else
strQuery = strQuery & "F_" & arrColNames(iCount) & " " & arrColTypes(iCount)
end if
iCount = iCount + 1
loop
strQuery = strQuery & ")"
fnAssembleQuery = strQuery
end function
' purpose: run through the excel file line by line, column by column
' fnStoreData
' once the table has been created, the data needs to be uploaded.
function fnStoreData
q = "select * from " & strTablename
set oStoreData = createobject("ADODB.recordset")
oStoreData.activeconnection = objConn
oStoreData.open q,,1,3
iXStore = 1
iYStore = 2
with oStoreData
do until objWorksheet.cells(iYStore, 1) = ""
.addNew
for iCol = 1 to ubound(arrColNames)-1
.fields("F_" & arrColNames(iCol)) = objWorksheet.cells(iYStore, iCol)
next
.update
iYStore = iYStore + 1
loop
end with
end function
dinsdag, september 17, 2013
Business Objects XI R3 Service Pack 6 Weirdness
Before you install Service Pack 6, you should be aware of a few things.
1) They corrected something that has to do with Context operators.
Especially if you're filtering on a field that has Context operators in it, your filter isn't going to work anymore.
Example = [Hire Date] = Max([Hire Date]) in([Person ID]) used to be the way it worked prior to this ServicePack. Now, this needs to be corrected to:
= [Hire Date] = Max([Hire Date]in([Person ID])) in([Person ID])
The way that function works, actually should make it clear that it used to be bugged before -- but the result can be quite annoying -- the bug is corrected -- very good, but now some of your reports will not return correct data or return no data at all.
2) Variables can no longer have a name equal to one of the Data providers.
This might not seem a big thing, but it happens more than you'd think. One of the dataproviders is called employee.. and you accidently make a variable called employee. With SP6, this report won't even display anymore.
The solution we found is to open the report in an older version Rich Client, change the name of the dataprovider and publish the report.
But, Service Pack 6 does deliver some useful bugfixes. Java 1.7 works flawlessly and dates from excel no longer show up as 1/1/1970.
Cheers
Peter
1) They corrected something that has to do with Context operators.
Especially if you're filtering on a field that has Context operators in it, your filter isn't going to work anymore.
Example = [Hire Date] = Max([Hire Date]) in([Person ID]) used to be the way it worked prior to this ServicePack. Now, this needs to be corrected to:
= [Hire Date] = Max([Hire Date]in([Person ID])) in([Person ID])
The way that function works, actually should make it clear that it used to be bugged before -- but the result can be quite annoying -- the bug is corrected -- very good, but now some of your reports will not return correct data or return no data at all.
2) Variables can no longer have a name equal to one of the Data providers.
This might not seem a big thing, but it happens more than you'd think. One of the dataproviders is called employee.. and you accidently make a variable called employee. With SP6, this report won't even display anymore.
The solution we found is to open the report in an older version Rich Client, change the name of the dataprovider and publish the report.
But, Service Pack 6 does deliver some useful bugfixes. Java 1.7 works flawlessly and dates from excel no longer show up as 1/1/1970.
Cheers
Peter
dinsdag, mei 07, 2013
Recovering SQL from old DeskI reports
Imagine the following situation:
You have some old BusinessObjects documents. The Universe is no longer available, you can't edit the query anymore, but you would like to obtain the SQL that's at the basis of it.
If this is the situation you are in, then here is your solution.
Go to Tools>Macro>Visual basic editor and create a new function:
Function test()
a = Application.ActiveDocument.DataProviders(1).SQL
Set fso = CreateObject("scripting.filesystemobject")
Set file = fso.createtextfile("c:\temp\sqlcode1.txt")
file.writeline a
End Function
next, press the triangle icon to run the function and you have query 1.. if more than 1 query exists, simply choose dataprovider(2).. etc and write them to separate textfiles.
Short .. and sweet :)
Peter
You have some old BusinessObjects documents. The Universe is no longer available, you can't edit the query anymore, but you would like to obtain the SQL that's at the basis of it.
If this is the situation you are in, then here is your solution.
Go to Tools>Macro>Visual basic editor and create a new function:
Function test()
a = Application.ActiveDocument.DataProviders(1).SQL
Set fso = CreateObject("scripting.filesystemobject")
Set file = fso.createtextfile("c:\temp\sqlcode1.txt")
file.writeline a
End Function
next, press the triangle icon to run the function and you have query 1.. if more than 1 query exists, simply choose dataprovider(2).. etc and write them to separate textfiles.
Short .. and sweet :)
Peter
dinsdag, april 02, 2013
Publication workaround (Business Objects)
Due to a bug in Business Objects XI R3, you might occasionally get the following error when you try to publish a report:
"Invalid Report, please choose a different report."
Very annoying when that happens, and the only solution that I know of is a restart of the server. But there is a simple and effective workaround:
While building your publication, you have to specify a Webi or DeskI document that will serve as a distribution list for your publication.
On the SAP website, you 'll find that you have to set a restriction on that distribution document. You have to limit the number of rows returned in the query (options).
That didn't work for me, but it might work for you.
My workaround? Use a DESKI report as distribution map. The document you'll be sending around can be a WEBI document of course, but the document you use to split it, can be DeskI and you won't be getting that annoying error.
Good luck folks
Peter
"Invalid Report, please choose a different report."
Very annoying when that happens, and the only solution that I know of is a restart of the server. But there is a simple and effective workaround:
While building your publication, you have to specify a Webi or DeskI document that will serve as a distribution list for your publication.
On the SAP website, you 'll find that you have to set a restriction on that distribution document. You have to limit the number of rows returned in the query (options).
That didn't work for me, but it might work for you.
My workaround? Use a DESKI report as distribution map. The document you'll be sending around can be a WEBI document of course, but the document you use to split it, can be DeskI and you won't be getting that annoying error.
Good luck folks
Peter
zondag, maart 10, 2013
The latest version of a Report (Business Objects)
Let's assume, you have this Dashboard. It runs every night and in the morning, when you open your browser, you want to see it pop up on your screen by default.
The issue you might run into, is that the Dashboard you scheduled, if you simply point to the report, you don't get the latest version, you get your original. Here's how you get around that:
First, you build your dashboard of course and you schedule it.
Go to Preferences and choose to view reports in an extra window:
Once the report ran at least once, you click right on the report.
From the menu, you should see
Cool huh :)
Enjoy,
Peter
The issue you might run into, is that the Dashboard you scheduled, if you simply point to the report, you don't get the latest version, you get your original. Here's how you get around that:
First, you build your dashboard of course and you schedule it.
Go to Preferences and choose to view reports in an extra window:
Once the report ran at least once, you click right on the report.
From the menu, you should see
From the menu, pick : View latest instance.
The latest instance of the report should now open in a new window.
When the report is open, press (Function key) F11. This should put the report in Full-screen. Now, when you move the mouse over the top part of the screen, it should be possible to see the URL of the Report. Select that URL and press Ctrl-C (copy).
Next, From the New menu, we create a new InfoView Page Layout.
Click: Define content
The dialog box that now appears has a box that says: Web Address.
Paste the URL in the Web-address box.
The fun part of the trick I showed you (F11) is that that specific URL will ALWAYS give you the most recent version of the report.
Cool huh :)
Enjoy,
Peter
zondag, februari 10, 2013
Ranking without duplicates in Business Objects
Here's a trick I got from James on the BOB-forum -- to a question from one of the visitors. Personally, I found the question a bit dodgy, but James worked out a nice two-step to get around the problem:
Lets asume we wanted to have a Top 10. But we wanted to disregard any duplicate values.
I'll get us a list of data to illustrate the idea:
Lets asume we wanted to have a Top 10. But we wanted to disregard any duplicate values.
I'll get us a list of data to illustrate the idea:
In the above list, a bunch of duplicates exist. K = 9, but L is also 9 (we want to lose L in our top 10), G is 6 but H is also 6, we want to lose H.. etc.
The first step is to create a ranking variable [Rank]:
=Rank([Value];[Code])
Great, now we want to find out what the rank is of the previous line:
=Previous([Rank])
Nice, so now, when Previous and Rank are the same, we want to lose the line. So we create a Flag-variable:
=if([Rank]=[Previous Rank];0;1)
Watch out though, don't insert the variable into your table or:
And if you get that error, you'll have to close the document and re-open it to get this to work.
Next, we apply a filter on the table:
Now, if we apply a top 5 on the table, it will only rank the unique values.
Works like a charm.
Thanks James,
Peter
woensdag, januari 30, 2013
The Tip Jar
Today, my wife asked me, how my Tip Jar was coming along. (The button you might have seen on the right)
I answered her truthfully:
I love you very much dear.
She said: So, how many people visit your blog these days?
I answered her truthfully:
About 2000 a month.
Wow, she replied, then that tip-jar idea I gave you must have really payed off right?
I answered truthfuly:
Not a penny dear.
Right, she replied, I'll cancel our holiday on the Bahama's then.
I agreed.
Can I write another article? I asked with a small voice.
Sure, she replied, are you going to leave the button on?
You never know, I responded. One day, a tip on this blog will be so helpful, a visitor will make a generous donation and we can have that holiday on the Bahama's.
I suppose one can dream, she replied-- a bit miffed.
I could bundle the blog and turn it into a book -- I suggested hopefully.
How are you going to turn the stuff you write about into an intriguing murder mystery that everyone wants to read? She asked jabbingly.
Hm.. I could change the name of my blog to "The Davinci code" I replied.
It's a start. She said, as she left the room.
Cheers folks,
Peter
I answered her truthfully:
I love you very much dear.
She said: So, how many people visit your blog these days?
I answered her truthfully:
About 2000 a month.
Wow, she replied, then that tip-jar idea I gave you must have really payed off right?
I answered truthfuly:
Not a penny dear.
Right, she replied, I'll cancel our holiday on the Bahama's then.
I agreed.
Can I write another article? I asked with a small voice.
Sure, she replied, are you going to leave the button on?
You never know, I responded. One day, a tip on this blog will be so helpful, a visitor will make a generous donation and we can have that holiday on the Bahama's.
I suppose one can dream, she replied-- a bit miffed.
I could bundle the blog and turn it into a book -- I suggested hopefully.
How are you going to turn the stuff you write about into an intriguing murder mystery that everyone wants to read? She asked jabbingly.
Hm.. I could change the name of my blog to "The Davinci code" I replied.
It's a start. She said, as she left the room.
Cheers folks,
Peter
zondag, januari 27, 2013
Compound Annual Growth rate in Business Objects
To calculate the Compound Annual Growth rate, Excel has a function called XIRR. BO doesn't have that, but that's easy to remedy. All you need to do, is work out the following formula:
So, now we need a bunch of numbers and see where we get:
Right -- this has everything to do with investments, something I know nothing about, but here's some numbers to get us started :
On Jan 1 2010, we invest 10000€
On Jan 1 2011, our investment has grown to 13000€
On Jan 1 2012, our investment has grown to 15000€
and
finally, On Jan 1 2013, our investment has now a value of 19500€
The table looks like this in BO:
So, our formula for that division becomes :
=[Value] where ([Date]=Max([Date]) In Report) / [Value] where ([Date]=Min([Date]) In Report)
I'll call this one basefigure
And we divide 1 by that figure:
=1/(Year(Max([Date]) In Report) - Year(Min([Date]) In Report))
this one, I'll call exponent
So, now we need a bunch of numbers and see where we get:
Right -- this has everything to do with investments, something I know nothing about, but here's some numbers to get us started :
On Jan 1 2010, we invest 10000€
On Jan 1 2011, our investment has grown to 13000€
On Jan 1 2012, our investment has grown to 15000€
and
finally, On Jan 1 2013, our investment has now a value of 19500€
The table looks like this in BO:
First, we'll divide our ending value by our beginning value -- interesting challenge.
Our last date would be : =Max([Date]) In Report = 1/1/2013
Our first date would be : =Min([Date]) In Report = 1/1/2010
So, our formula for that division becomes :
=[Value] where ([Date]=Max([Date]) In Report) / [Value] where ([Date]=Min([Date]) In Report)
I'll call this one basefigure
Now, we need to find out how many years it took:
The formula for that one is rather easy :
=Year(Max([Date]) In Report) - Year(Min([Date]) In Report)
And we divide 1 by that figure:
=1/(Year(Max([Date]) In Report) - Year(Min([Date]) In Report))
this one, I'll call exponent
There. Now, the next step would be to raise our first figure to the power of that second figure and subtract 1.
=Power([Basefigure];[Exponent])-1
Our Compound Annual Growth rate would be : 0.25 -- I guess those figures occurred in a parallel universe :)
Cool
Cheers folks
Peter
A running balance in Business Objects
I think it's quite clear by now, that it is my objective, never ever to need Excel -- well, almost. The charts in Excel do exceed BO's, but math -- naaaah.
Our next venture, is calculating a running balance.
In a running balance, we have a series of deposits and a series of withdrawals and we want to
keep track of the total of our balance at all times.
Here's how:
First, we need two columns. One has our deposits, the other has the withdrawals.
Now, we want to show a third column, with our balance -- on 1/1/2013, this should be : 180, on 2/1/2013, this should be -620 etc.
This is laughably easy really -- since BusinessObjects has a function Excel doesn't have: RunningSum
=runningsum([Deposit]) - runningsum([Withdrawal])
The result is :
Our next venture, is calculating a running balance.
In a running balance, we have a series of deposits and a series of withdrawals and we want to
keep track of the total of our balance at all times.
Here's how:
First, we need two columns. One has our deposits, the other has the withdrawals.
This is laughably easy really -- since BusinessObjects has a function Excel doesn't have: RunningSum
=runningsum([Deposit]) - runningsum([Withdrawal])
The result is :
In Excel this would be something like :
A | B | C |
---|---|---|
Deposits | Withdrawals | Balance |
$1,000 | $625 | =SUM(A2,-B2) |
$1000 | 740 | =SUM(C2,A3,-B3) |
Looks like our balance is in the red -- but with the economic times, that seems right :)
Cheers folks
Peter
zondag, januari 20, 2013
Trimmed mean in Business Objects
This is a challenging one.
This is what the formula looks like:
=NoFilter(Rank([Sold at (unit price)];Top))
This gives us the Top -- returning a value -- if V_ProductsToKeep is higher or equal than that value, we filter the value out.
Same with the lowest:
=NoFilter(Rank([Sold at (unit price)];Bottom))
This gives us the Bottom -- returning a value -- if V_ProductsToKeep is lower or equal, we filter that value out.
=If([V_Bottom] <= [V_ProductsToKeep]) Then (0) ElseIf ([V_Top]<=[V_ProductsToKeep]) Then 0 Else 1
=Sum([Sold at (unit price)])Where([V_Evaluate]=1) / ([V_NProducts] - ([V_ProductsToKeep]*2))
There. Not easy at all, but it works.
Our Trimmed mean with k = 33% is 166.6 -- higher than our regular mean, because we dropped the bottom and top 33% of the values.
If we change k to 0.1, the trimmed mean is:165.8
This is what the formula looks like:
But all this means is:
You pick a value k. And the % k number of values, is dropped while calculating the mean.
So, if k = 10% and you have 100 values, you'll be dropping the bottom 10 and the top 10 values and calculate the mean on the remaining 80.
I think it will be useful, to use ranking to calculate these.
First, lets calculate, from our list of products, with k = 30%, how many values we would need to drop.
We have 12 values. 30% of 12 = 4, so we'll be dropping the top 4 and the bottom 4 values.
Let's calculate this in Business Objects.
V_k = 0.3
=round(count([Lines]) * V_k,0)
which I will name V_ProductsToKeep
We need an integer of course, so round it is.
So, now we need to find out, what the 4th highest value is and filter everything out that is higher or equal. And we need to find out what the 4th lowest value is and filter everything out that is lower or equal.
This gives us the Top -- returning a value -- if V_ProductsToKeep is higher or equal than that value, we filter the value out.
Same with the lowest:
=NoFilter(Rank([Sold at (unit price)];Bottom))
This gives us the Bottom -- returning a value -- if V_ProductsToKeep is lower or equal, we filter that value out.
=If([V_Bottom] <= [V_ProductsToKeep]) Then (0) ElseIf ([V_Top]<=[V_ProductsToKeep]) Then 0 Else 1
Nice, now all we need is a formula, that determines what the sum is of [Sold at (unit price] where V_Evaluate = 1
=Sum([Sold at (unit price)])Where([V_Evaluate]=1) / ([V_NProducts] - ([V_ProductsToKeep]*2))
There. Not easy at all, but it works.
Our Trimmed mean with k = 33% is 166.6 -- higher than our regular mean, because we dropped the bottom and top 33% of the values.
If we change k to 0.1, the trimmed mean is:165.8
Good fun -- not that easy to do. I think that's going to be it for averages for now :)
Labels:
BusinessObjects,
Calculation,
Functions,
Math
zaterdag, januari 19, 2013
Quadratic average in Business Objects
Next up: the Quadratic mean.
As usual, first the function:
From the looks of it, we first need a count of the number of entries and divide 1 by that number. In our case, this gives us the following formula:
=1/count([Lines])
As usual, first the function:
This is quite a different animal. The application of it isn't really sales, on wikipedia it even says that this applies to electricity, but I don't have those kind of figures -- it'll work just as good with my numbers. It no longer takes into account the number of items sold, so our starting table is going to look a bit different:
=1/count([Lines])
In our case, this is 1/12 or : 0.08.
Next, we have to take the second power of each unit price.. easy peasy.
=power([Sold at (unit price)];2)
And next, we need to add those up:
=Sum(Power([Sold at (unit price)];2))
Next, we have to multiply those and take the square-root.
=sqrt(Sum(Power([Sold at (unit price)];2))*(1/Count([Lines])))
The Quadratic mean of the unit price is 167 -- so far, that's the highest mean we got.
More averages coming up.
Peter
Labels:
BusinessObjects,
Calculation,
Functions,
Tips
Weighted Harmonic Average in Business Objects
So, here's the second part of the Averages series. And this one might prove to be just a bit more challenging.
This time, we're going to calculate the Weighted Harmonic Average.
The formula I found on Wikipedia looks like this:
This time, we're going to calculate the Weighted Harmonic Average.
The formula I found on Wikipedia looks like this:
The table we're starting with, again, looks like this:
Our Average is 165.8, the median is 165.05 and the Weighted average is 163. Lets see what the Harmonic Weighted average gives us.
Lets add up our Unit Prices.
=sum([Quantity sold])
Now, we divide each quantity sold by the unit price and we add them up. Sounds weird to me, but, why not.
(Sum([Quantity sold]/[Sold at (unit price)])
I think I'm starting to like this particular average :)
Last step, we divide.
=Sum([Quantity sold]) / (Sum([Quantity sold]/[Sold at (unit price)]))
Tada, our Weighted Harmonic Average : 161.52
More averages to come :)
Peter
Calculating averages in Business Objects
Time for some averages.
Obviously, I'm not going to deal with the average function, that seems straightforward enough. This article and following articles will be about some other averages:
Weighted mean, Weighted harmonic mean, Quadratic mean, Winsor mean, Truncated mean, Mean of Angles, and so on and so forth. I didn't know there were that many means, so it's gonna be fun figuring some of them out. As I already pointed out in previous articles, I'm not a mathematician, so I'm learning this stuff as I write it.
Let's start with the Weighted mean.
The formula on Wikipedia looks like this:
Obviously, I'm not going to deal with the average function, that seems straightforward enough. This article and following articles will be about some other averages:
Weighted mean, Weighted harmonic mean, Quadratic mean, Winsor mean, Truncated mean, Mean of Angles, and so on and so forth. I didn't know there were that many means, so it's gonna be fun figuring some of them out. As I already pointed out in previous articles, I'm not a mathematician, so I'm learning this stuff as I write it.
Let's start with the Weighted mean.
The formula on Wikipedia looks like this:
Fortuntately, I had the chance of learning to read these syntaxes when I wrote the articles on trendlines. So, it's going to be just a little bit easier.
Basically, it's just going to be adding up a bunch of multiplications and dividing it again. Lets get a bunch of numbers:
Our starting table is this one -- as usual, I'm taking eFashion to start from:
We're interested in the weighted average of the Unit Price. The average is 165.80 and the median would be:165.05.
First, we multiply Quantity sold and unit price and we add them up.
In a column to the right, I'm adding =[Quantity sold]*[Sold at (unit price)]
Nice. Next, we divide that big number by the total of the quantities:
=sum([Quantity sold]*[Sold at (unit price)])/sum([Quantity sold])
This results in:
Our weighted mean is: 163
More means coming up.
Peter
Labels:
Average,
BusinessObjects,
Calculation,
Tips
dinsdag, januari 15, 2013
ForceMerge
Quite a useful function I should say. I think it just might be the solution to much of the Multivalue errors I've been having in my job as a Reporting Officer.
Here's how ForceMerge works :
First, you make two queries. One contains the dimension you don't have in your second universe and a common field,the other one contains that common field and some calculated data.
Here's how ForceMerge works :
First, you make two queries. One contains the dimension you don't have in your second universe and a common field,the other one contains that common field and some calculated data.
The left table contains states and cities, the right one contains cities and Sales figures. What we would really want, is State and Sales figures.
So, we move the sales figures in with the left table ( first we merge the city object of course).
There.. looking great. Just, we don't want the city. we want the state. But when we remove it:
We get ourselves a cartesian product. The solution to this issue is: Forcemerge().
When we change the formula for the Sales figures to : =ForceMerge([Sales Revenue]), the following table emerges:
Ain't that a beauty :)
That's all folks.
Enjoy,
Peter
zaterdag, januari 12, 2013
business objects operator "????"
I noticed that someone searched for these terms.
This is probably going to be short and sweet:
business objects operator "????"
Whenever you find ???? inside a variable, it simply means that the object that was used
to create this particular variable, is no longer in the query.
Let's say that you create a variable Year. And you use the following formula:
=year([Sales date])
and then, remove the object [Sales date] from your query.
The object Year will then contain =year(????)
Not good :)
Cheers
This is probably going to be short and sweet:
business objects operator "????"
Whenever you find ???? inside a variable, it simply means that the object that was used
to create this particular variable, is no longer in the query.
Let's say that you create a variable Year. And you use the following formula:
=year([Sales date])
and then, remove the object [Sales date] from your query.
The object Year will then contain =year(????)
Not good :)
Cheers
maandag, december 24, 2012
Bubbeling data
Here's an interesting problem I ran into last week:
Date Code Emplid Name of the Employee
2012/12/01 -- DTA -- 123 -- John
2010/12/10 -- XFR -- 123 -- John
2012/12/05 -- DTA -- 456 -- Bert
2012/05/08 -- DTA -- 567 -- Pascal
2000/06/04 -- DTA -- 567 -- Pascal
2000/05/01 -- XFR -- 567 -- Pascal
Right. My boss asked me, in the above list, to show him the transfers for people that got transferred, and for everyone else, he needed their most recent situation.
The tricky part is : you can't filter on a code and you can't filter on a date.
Part of the solution is of course to be found in the article where I showed you how to get the two most recent items. That is, we're going to turn our date into a number :
=(year([Date])*10000)+(monthnumberinyear([Date])*100) + daynumberinmonth([Date])
First, we need to create a break on Emplid of course.
CalcDate Date Code Emplid Name of the Employee
20121201 -- 2012/12/01 -- DTA -- 123 -- John
20101210 -- 2010/12/10 -- XFR -- 123 -- John
20121205 -- 2012/12/05 -- DTA -- 456 -- Bert
20120508 -- 2012/05/08 -- DTA -- 567 -- Pascal
20000604 -- 2000/06/04 -- DTA -- 567 -- Pascal
20000501 -- 2000/05/01 -- XFR -- 567 -- Pascal
And then, we can create a rank on CalcDate and get our most recent items. Easy. BUT. There was an additional requirement. If the employee had a transfer, we need that person's most recent transfer.
Easy as pie :
=CalcDate * if([Code]="XFR") then (1000) else (1)
Of course, this messes up the date-look of Calcdate a bit, but it gets the job done. When you rank on it, you get :
CalcDate Date Code Emplid Name of the Employee
20101210000 -- 2010/12/10 -- XFR -- 123 -- John
20121205 -- 2012/12/05 -- DTA -- 456 -- Bert
20000501000 -- 2000/05/01 -- XFR -- 567 -- Pascal
Mission accomplished :)
Cheers
Peter
Date Code Emplid Name of the Employee
2012/12/01 -- DTA -- 123 -- John
2010/12/10 -- XFR -- 123 -- John
2012/12/05 -- DTA -- 456 -- Bert
2012/05/08 -- DTA -- 567 -- Pascal
2000/06/04 -- DTA -- 567 -- Pascal
2000/05/01 -- XFR -- 567 -- Pascal
Right. My boss asked me, in the above list, to show him the transfers for people that got transferred, and for everyone else, he needed their most recent situation.
The tricky part is : you can't filter on a code and you can't filter on a date.
Part of the solution is of course to be found in the article where I showed you how to get the two most recent items. That is, we're going to turn our date into a number :
=(year([Date])*10000)+(monthnumberinyear([Date])*100) + daynumberinmonth([Date])
First, we need to create a break on Emplid of course.
CalcDate Date Code Emplid Name of the Employee
20121201 -- 2012/12/01 -- DTA -- 123 -- John
20101210 -- 2010/12/10 -- XFR -- 123 -- John
20121205 -- 2012/12/05 -- DTA -- 456 -- Bert
20120508 -- 2012/05/08 -- DTA -- 567 -- Pascal
20000604 -- 2000/06/04 -- DTA -- 567 -- Pascal
20000501 -- 2000/05/01 -- XFR -- 567 -- Pascal
And then, we can create a rank on CalcDate and get our most recent items. Easy. BUT. There was an additional requirement. If the employee had a transfer, we need that person's most recent transfer.
Easy as pie :
=CalcDate * if([Code]="XFR") then (1000) else (1)
Of course, this messes up the date-look of Calcdate a bit, but it gets the job done. When you rank on it, you get :
CalcDate Date Code Emplid Name of the Employee
20101210000 -- 2010/12/10 -- XFR -- 123 -- John
20121205 -- 2012/12/05 -- DTA -- 456 -- Bert
20000501000 -- 2000/05/01 -- XFR -- 567 -- Pascal
Mission accomplished :)
Cheers
Peter
Abonneren op:
Posts (Atom)