This blog is recording things I think will be useful. Generally these are IT-solutions but I also touch on other issues as well as-and-when they occur to me.
Wednesday, May 13, 2009
Oracle EBS: Viewing General Ledger (GL) Daily Currency Conversion Rates
NOTE: This SQL works in version 11.5.10.2, your version might be different!
Many companies like to have "one version of the truth" (I say "like to" as we all know how hard this is to actually achieve!). One of the ways this can be achieved is a standardised set of currency conversion rates across an organisation.
The SQL in this blog post gives you a quick and easy report showing the currency conversion rates currently being used in the GL.
If your company is using Oracle Finance software (i.e. General Ledger) and the currency conversion rates are being loaded into the system (either automatically or manually) then it makes sense to publish this information out so that other parts of the company can use it.
The SQL below will show currency conversion rates (or a specified type) between two currencies and between two dates with the latest date conversion rate entered at the top.
SELECT FROM_CURRENCY,
TO_CURRENCY,
TO_CHAR(CONVERSION_DATE, 'DD-MON-YYYY') COVERSION_DATE,
SHOW_CONVERSION_RATE,
SHOW_INVERSE_CON_RATE
FROM GL_DAILY_RATES_V
WHERE status_code != 'D'
and (FROM_CURRENCY = :FROM_CURRENCY)
and (TO_CURRENCY = :TO_CURRENCY)
and (CONVERSION_DATE >=
to_date(:START_DATE, 'DD-MON-YYYY') AND
CONVERSION_DATE < to_date(:END_DATE, 'DD-MON-YYYY')+1)
and (USER_CONVERSION_TYPE = :USER_CONVERSION_TYPE)
order by from_currency,
to_currency,
conversion_date desc,
user_conversion_type
In order to run this SQL you need to specify five parameters;
FROM_CURRENCY: The currency you wish to convert from (i.e. EUR)
TO_CURRENCY: The currency code you wish to convert to (i.e. GBP)
START_DATE: This is the first date you want to see in the report in the format DD-MON-YYYY
END_DATE: This is the last date you want to see in the report in the format DD-MON-YYYY
USER_CONVERSION_TYPE: This will be dependant on your system, I'd recommend you look in the GL_DAILY_RATES_V view and find out the values used at your site and then plug one of those in.
Hopefully this will prove useful to people!
Tuesday, May 12, 2009
Oracle EBS: Updating Vendor and Vendor Site (Supplier) Information in Oracle e-Business Suite (11i)
This blog post covers how you update Vendor and Vendor Site records in Apps 11i. The included example shows how you clear the Tax Code associated with both types of record (this is particularly useful in the UK where our Sales Tax has changed).
The Problem
Changes quite often need to be made to the records in the PO_VENDORS and PO_VENDOR_SITES tables that involve changes to a large number of records . For example when the Sales Tax (VAT) rate changed from 17.5 to 15% in the UK each Vendor and Vendor Site had the old rate stored against it (this is the default rate that populates the dialog when you create an invoice for a vendor).
The problem with making these changes directly into the tables themselves is that Oracle a) does not support this, and b) doesn't publish the necessary information for us to work out exactly what doing something as simple as this actually entails.
The Solution
Summary/ Description
Oracle has provided two packages which contain routines that enable developers to make these changes themselves;
AP_VENDORS_PKG
AP_VENDOR_SITES_PKG
The first thing to note is that there is no "delete" routine; if you want to delete a record you need to update it and set the "INACTIVE_DATE" field to the current date.
The two routines we are interested in are called INSERT_ROW and UPDATE_ROW. There are lots of other routines but they are basically checking/validation or display routines and the Lock routine which we don't want to touch.
Inserting a New Record using INSERT_ROW
Basically you need to pass in ALL the parameters listed (for Vendor Sites there is around forty) these are the direct values you want to end up in the columns in the table. The only field you don't need to worry about is the VENDOR_SITE_ID which is populated from the PO_VENDOR_SITES_S sequence (NEXTVAL).
The two pieces of validation (as of 12th May 2009) that are carried out are a check for duplicate records (based on Vendor ID, and Site Code) and if you have specified the new site as a tax-reporting site (i.e. x_tax_reporting_site_flag = 'Y') a check is also made to make sure there aren't multiple tax sites for a single vendor (I guess you'll need to use UPDATE_ROW to turn off the tax reporting site flag on another record before inserting your new record).
Other than that the insert relies on Oracles validation (i.e. you won't be able to squeeze 12 characters into 10 character field, put a floating point number into an integer, etc) to stop things if you are inserting invalid data, but there really isn't anything to stop you doing something stupid like creating a new vendor who has only been active in the past or who has an inactive date before their active date.
If you pass in a Shipping Location then a new record will be created in the PO_LOCATION_ASSOCIATIONS table. This appears to be optional.
Updating a Record using UPDATE_ROW
This is the best example of how not to write an API you will ever come across. Basically you call it an pass in the ROWID and then new values for all the existing fields (fantastic potential to delete other peoples work here!).
The two pieces of validation (as of 12th May 2009) that are carried out are a check for duplicate records (based on Vendor ID, and Site Code) and if you have specified the new site as a tax-reporting site (i.e. x_tax_reporting_site_flag = 'Y') a check is also made to make sure there aren't multiple tax sites for a single vendor (I guess you'll need to use UPDATE_ROW to turn off the tax reporting site flag on another record before inserting your new record).
Obviously if the ROWID you specify doesn't exist you will get an error, equally if you try and insert invalid data you will get an error as well.
If you pass in a Shipping Location then a call will be made to the ap_po_locn_association_pkg.update_row package to update the location ID you've specified. This appears to be optional.
Example;
There is a "sample" script here which performs an update on the VAT Code attached to suppliers.
The Problem
Changes quite often need to be made to the records in the PO_VENDORS and PO_VENDOR_SITES tables that involve changes to a large number of records . For example when the Sales Tax (VAT) rate changed from 17.5 to 15% in the UK each Vendor and Vendor Site had the old rate stored against it (this is the default rate that populates the dialog when you create an invoice for a vendor).
The problem with making these changes directly into the tables themselves is that Oracle a) does not support this, and b) doesn't publish the necessary information for us to work out exactly what doing something as simple as this actually entails.
The Solution
Summary/ Description
Oracle has provided two packages which contain routines that enable developers to make these changes themselves;
AP_VENDORS_PKG
AP_VENDOR_SITES_PKG
The first thing to note is that there is no "delete" routine; if you want to delete a record you need to update it and set the "INACTIVE_DATE" field to the current date.
The two routines we are interested in are called INSERT_ROW and UPDATE_ROW. There are lots of other routines but they are basically checking/validation or display routines and the Lock routine which we don't want to touch.
Inserting a New Record using INSERT_ROW
Basically you need to pass in ALL the parameters listed (for Vendor Sites there is around forty) these are the direct values you want to end up in the columns in the table. The only field you don't need to worry about is the VENDOR_SITE_ID which is populated from the PO_VENDOR_SITES_S sequence (NEXTVAL).
The two pieces of validation (as of 12th May 2009) that are carried out are a check for duplicate records (based on Vendor ID, and Site Code) and if you have specified the new site as a tax-reporting site (i.e. x_tax_reporting_site_flag = 'Y') a check is also made to make sure there aren't multiple tax sites for a single vendor (I guess you'll need to use UPDATE_ROW to turn off the tax reporting site flag on another record before inserting your new record).
Other than that the insert relies on Oracles validation (i.e. you won't be able to squeeze 12 characters into 10 character field, put a floating point number into an integer, etc) to stop things if you are inserting invalid data, but there really isn't anything to stop you doing something stupid like creating a new vendor who has only been active in the past or who has an inactive date before their active date.
If you pass in a Shipping Location then a new record will be created in the PO_LOCATION_ASSOCIATIONS table. This appears to be optional.
Updating a Record using UPDATE_ROW
This is the best example of how not to write an API you will ever come across. Basically you call it an pass in the ROWID and then new values for all the existing fields (fantastic potential to delete other peoples work here!).
The two pieces of validation (as of 12th May 2009) that are carried out are a check for duplicate records (based on Vendor ID, and Site Code) and if you have specified the new site as a tax-reporting site (i.e. x_tax_reporting_site_flag = 'Y') a check is also made to make sure there aren't multiple tax sites for a single vendor (I guess you'll need to use UPDATE_ROW to turn off the tax reporting site flag on another record before inserting your new record).
Obviously if the ROWID you specify doesn't exist you will get an error, equally if you try and insert invalid data you will get an error as well.
If you pass in a Shipping Location then a call will be made to the ap_po_locn_association_pkg.update_row package to update the location ID you've specified. This appears to be optional.
Example;
There is a "sample" script here which performs an update on the VAT Code attached to suppliers.
Friday, March 6, 2009
Installing Systems Centre Operation Manager (SCOM) 2007 with SP1
This guide is intended as a simple checklist of things you need to do in order to get a "working" SCOM test environment up and running under some form of "virtualising" technology (we use Microsoft's Hyper-V, but exactly the same instructions should work in VMware Workstation, VirtualBox etc).
In order to put this guide together I've made the following assumptions (well, OK, there weren't really any assumptions - this is how the environment I had to use was configured);
Installing the Prerequisites
When you try and install SCOM it will perform a series of checks against your system. If you're using a vanilla 2003 server than a great many of them will fail. Each piece of software listed below needs to be installed in order for SCOM to even begin to install.
Installing Systems Centre Operation Manager (SCOM)After installing the prerequisites this is actually quite easy. The steps below are "guidance" and aren't a screen-by-screen description of what the installer is doing. Just accept the defaults until you are asked one of the questions below;
Optional Extras: The Oracle ClientIf you're going to be using SCOM to access an Oracle database you will also need to install the Oracle client. For the purposes of this example I'm going to suggest the 10g client but that won't work if you need to monitor 8i and earlier systems. I'm not sure what subset of companies are running older versions of Oracle AND want to use something as "new" as SCOM to monitor them!
In order to put this guide together I've made the following assumptions (well, OK, there weren't really any assumptions - this is how the environment I had to use was configured);
- Your server operating system is Windows 2003R2 SP2 32-bit. The links below will install the required software onto this version of Windows. If you are running a 64-bit version , or a different version of the server operating system (i.e. Windows 2008) then follow the links and see if you can find a x64 version - once again Google is your friend.
- Your server, users, etc are all part of an Active Directory domain. There is a group in Active Directory called "Domain Admins" that includes all the users who are Domain Administrators (i.e. your IT Department). The person running this installation has a "normal" (i.e. Standard User) domain account in addition to their Domain Admin account.
- The software is being installed by a Domain Administrator.
- You have either the installation CD's provided by your company or a MSDN account from which you can download the software.
Installing the Prerequisites
When you try and install SCOM it will perform a series of checks against your system. If you're using a vanilla 2003 server than a great many of them will fail. Each piece of software listed below needs to be installed in order for SCOM to even begin to install.
- Install the base operating systems (i.e. Windows Server 2003R2) and add it to the Active Directory domain.
- Install the "Application Server" role (you need to go into "Manage your server" under the start menu). When asked make sure you include the FrontPage Server Extensions and ASP.NET.
- Run the SCOM Installer and it will tell you that MSXML6.0 is missing and will offer to install it for you. This is the only useful thing the installer seems to offer, if you try it again it will just start telling you things are missing and you have to fix them all yourself!
- Download and Install Microsoft .NET Framework 2.0 Service Pack 1 (x86).
- Bring up a command prompt (you need this to turn on ASP.NET 2.0).The instructions for doing this are also available if you get the details for the pre-requisite check that fails.
- Type "cd %WINDIR%\Microsoft.NET\Framework\v2.0.50727". As you can see the version of the framework is included in the directory path (so if you're using a different version you'll need to modify the path accordingly).
- Type "aspnet_regiis.exe –i –enable" (this performs the switch-on).
- Download and Install Microsoft .NET Framework 3.0 Service Pack 1
- Install SQL Server 2005 Standard Edition (use your Domain Admin account as the Service Account, choose "Windows Authentication", otherwise accept the defaults).
- Download and install Microsoft SQL Server 2005 Service Pack 2 (you can do this before you reboot from the previous step if you want to). You should definitely reboot the machine before continuing.
- Download and install Windows PowerShell 1.0 English-Language Installation Package for Windows Server 2003 (KB926139)
Installing Systems Centre Operation Manager (SCOM)After installing the prerequisites this is actually quite easy. The steps below are "guidance" and aren't a screen-by-screen description of what the installer is doing. Just accept the defaults until you are asked one of the questions below;
- Enter the group name as "DEVELOPERS" - technically you can pick anything but it's always good to make sure that your group is a good description of what you are using the system for and who set it up!
- Give the AD Group "Domain Admins" permission to administer the box.
- Select the locally installed copy of SQL Server (should be the only one available)
- When prompted again for a User/Password enter your STANDARD user account, not your Domain Administrator (if you enter the Domain Administrator account you will get a "warning" that it's not good practice - it's unlikely your production box will be setup this way so you should avoid doing it for testing).
- When prompted to enter account details for the SDK account enter your Domain Administrator details.
- Choose to use "Windows Authentication" for the Console.
- I would select "Don't use Windows Update" just because it's usually the simplest option, but I'd go with whatever your production system uses.
- Click "Next"/ accept the defaults until you click "Install" to install the software.
Optional Extras: The Oracle ClientIf you're going to be using SCOM to access an Oracle database you will also need to install the Oracle client. For the purposes of this example I'm going to suggest the 10g client but that won't work if you need to monitor 8i and earlier systems. I'm not sure what subset of companies are running older versions of Oracle AND want to use something as "new" as SCOM to monitor them!
- Go to the URL Oracle 10.2 Client Download (this link is for Windows)
- Download "Oracle Database 10g Client Release 2 (10.2.0.1.0)", this is around 470MB.
- Uncompress the zip file into a local directory
- Run the setup.exe and select the "Administrator Client"
- Accept all the defaults and then click though to the "Install" button and then click that.
- Setup the client to work with your network (i.e. if you have a TNS Names Server then configure the client to use that, if you have a "shared TNS" on a server somewhere then set it up to use that, if you have a copy of TNS under Source Control then get that and copy it into the NETWORK/ADMIN directory, whatever works for you).
- Test the client.
Wednesday, February 11, 2009
Oracle PL/SQL: How to Track Which Users Are Running a Report
As anyone managing a large system will tell you it's often not getting the data in that's a problem; it's getting it back out. A Report that is suitable for Finance is probably not exactly what they're looking for in the Warehouse. Pretty soon, especially if you have an end-user Reporting tool (like Business Objects), you're going to have hundreds of reports all solving individual problems out in the business.
Over time peoples needs change, some reports become obsolete and some new ones appear. Of course your users will not actually delete anything "just in case" it is needed in the future.
Now it's upgrade time and you've got 600+ reports and no idea who users what.
The point of the blog post is to suggest a way of writing reports that will build logging into the report so that whenever the report is run a record is kept (and these can then be archived off monthly, or moved into the Data Warehouse, or whatever).
Setting Up The Recording Package
Before we can make any changes to the reports to track their usage we need to define somewhere to store this data. For the purposes of this Knol I'm going to be using the Oracle e-Business Suite (11.5.10.2 to be exact) as the source for my reporting data AND as the storage location for the usage data.
To this end I'm going to create three tables in the APPS schema (yes, I know, it's far from ideal but this is only intended as an example giving the objects their own schema is a lot easier BUT then you have to sort out permissions and things start to get complicated - too complicated for this demo!).
The three tables I'm going to create are called; REPORTLOG, REPORTPARAMLOG, and REPORTRUNLOG. To reduce the volume of data produced I've split the necessary information up into three tables so, for example, if the user runs the same report 20 times with the same parameters I only need to store one copy of them. I hope this makes sense.
Anyway, that table structure is;
![]() |
| Figure 1: Log Information Storage Structure |
REPORTRUNLOG
CREATE TABLE "APPS"."REPORTRUNLOG"
( "ID" NUMBER,
"REPORTLOG_ID" NUMBER,
"REPORTPARAMLOG_ID" NUMBER,
"RUNDATE" DATE,
"USERNAME" VARCHAR2(40 BYTE)
);
REPORTPARAMLOG
CREATE TABLE "APPS"."REPORTPARAMLOG"
( "ID" NUMBER,
"PARAM01" VARCHAR2(80 BYTE),
"PARAM02" VARCHAR2(80 BYTE),
"PARAM03" VARCHAR2(80 BYTE),
"PARAM04" VARCHAR2(80 BYTE),
"PARAM05" VARCHAR2(80 BYTE),
"PARAM06" VARCHAR2(80 BYTE),
"PARAM07" VARCHAR2(80 BYTE),
"PARAM08" VARCHAR2(80 BYTE),
"PARAM09" VARCHAR2(80 BYTE),
"PARAM10" VARCHAR2(80 BYTE)
);
REPORTLOG
CREATE TABLE "APPS"."REPORTLOG"
( "REPORTNAME" VARCHAR2(40 BYTE),
"ID" NUMBER,
"VERSION" VARCHAR2(20 BYTE)
);
These scripts are taken from Oracle SQL Developer tool (selecting the objects and choosing "Export DDL" from the right-click menu).
I've not chosen to enforce the relationship between the tables in Oracle itself (foreign keys). I'm going to use a package to write the information into the tables and I'm happy that the validation I put into the package will enforce the links. This is really a design decision; you could go either way you probably should use foreign keys but it will depend on your environment.
Next comes the creation of the Oracle package;
CREATE OR REPLACE
PACKAGE REPORTLOGGER
AS
FUNCTION LogReport
(
p_ReportName IN VARCHAR2,
p_Version IN VARCHAR2,
p_User IN VARCHAR2,
p_Param01 IN VARCHAR2 DEFAULT NULL,
p_Param02 IN VARCHAR2 DEFAULT NULL,
p_Param03 IN VARCHAR2 DEFAULT NULL,
p_Param04 IN VARCHAR2 DEFAULT NULL,
p_Param05 IN VARCHAR2 DEFAULT NULL,
p_Param06 IN VARCHAR2 DEFAULT NULL,
p_Param07 IN VARCHAR2 DEFAULT NULL,
p_Param08 IN VARCHAR2 DEFAULT NULL,
p_Param09 IN VARCHAR2 DEFAULT NULL,
p_Param10 IN VARCHAR2 DEFAULT NULL)
RETURN VARCHAR2;
END REPORTLOGGER;
This created the package header, the defaults for each parameter will mean that if (say) we only have 3 parameters we don't have to pass in 10 and give them null values in order to make the call. Also if this ever needs to be increased to 20 parameters all the existing modified reports should carry on working!
The next (and larger) part is the package body;
CREATE OR REPLACE
PACKAGE BODY REPORTLOGGER
AS
FUNCTION LogReport
(
p_ReportName IN VARCHAR2,
p_Version IN VARCHAR2,
p_User IN VARCHAR2,
p_Param01 IN VARCHAR2 DEFAULT NULL,
p_Param02 IN VARCHAR2 DEFAULT NULL,
p_Param03 IN VARCHAR2 DEFAULT NULL,
p_Param04 IN VARCHAR2 DEFAULT NULL,
p_Param05 IN VARCHAR2 DEFAULT NULL,
p_Param06 IN VARCHAR2 DEFAULT NULL,
p_Param07 IN VARCHAR2 DEFAULT NULL,
p_Param08 IN VARCHAR2 DEFAULT NULL,
p_Param09 IN VARCHAR2 DEFAULT NULL,
p_Param10 IN VARCHAR2 DEFAULT NULL)
RETURN VARCHAR2
AS
PRAGMA autonomous_transaction;
v_ExistsCount NUMBER;
v_ReportName VARCHAR2(40);
v_Version VARCHAR2(20);
v_User VARCHAR2(40);
v_Param01 VARCHAR2(80);
v_Param02 VARCHAR2(80);
v_Param03 VARCHAR2(80);
v_Param04 VARCHAR2(80);
v_Param05 VARCHAR2(80);
v_Param06 VARCHAR2(80);
v_Param07 VARCHAR2(80);
v_Param08 VARCHAR2(80);
v_Param09 VARCHAR2(80);
v_Param10 VARCHAR2(80);
v_RowCount NUMBER;
v_ReportRunLogId NUMBER;
v_ReportLogId NUMBER;
v_ReportParamlogId NUMBER;
BEGIN
-- This section converts the passed in paramters (which could be of any length) to values that can
-- be safely stored in hte table without worrying about "too big"-type errors!
v_ReportName := upper(SUBSTR(p_reportname, 1, 40));
v_Version := upper(SUBSTR(p_Version, 1, 20));
v_User := upper(SUBSTR(p_User, 1, 40));
v_Param01 := upper(SUBSTR(NVL(p_Param01, '
v_Param02 := upper(SUBSTR(NVL(p_Param02, '
v_Param03 := upper(SUBSTR(NVL(p_Param03, '
v_Param04 := upper(SUBSTR(NVL(p_Param04, '
v_Param05 := upper(SUBSTR(NVL(p_Param05, '
v_Param06 := upper(SUBSTR(NVL(p_Param06, '
v_Param07 := upper(SUBSTR(NVL(p_Param07, '
v_Param08 := upper(SUBSTR(NVL(p_Param08, '
v_Param09 := upper(SUBSTR(NVL(p_Param09, '
v_Param10 := upper(SUBSTR(NVL(p_Param10, '
-- Get the ID for the Report (with Version)
SELECT COUNT(*)
INTO v_RowCount
FROM reportlog nrl
WHERE nrl.reportname = v_ReportName
AND nrl.version = v_Version;
IF v_RowCount = 0 THEN
SELECT NVL(MAX(id), 0)+1 INTO v_ReportLogId FROM reportlog;
INSERT
INTO reportlog
(
id ,
reportname,
version
)
VALUES
(
v_ReportLogId,
v_ReportName ,
v_Version
);
ELSE
SELECT id
INTO v_ReportLogId
FROM reportlog nrl
WHERE nrl.reportname = v_ReportName
AND nrl.version = v_Version;
END IF;
-- Get the ID for the Report parameters
SELECT COUNT(*)
INTO v_RowCount
FROM reportparamlog
WHERE param01 = v_Param01
AND param02 = v_Param02
AND param03 = v_Param03
AND param04 = v_Param04
AND param05 = v_Param05
AND param06 = v_Param06
AND param07 = v_Param07
AND param08 = v_Param08
AND param09 = v_Param09
AND param10 = v_Param10;
IF v_RowCount = 0 THEN
SELECT NVL(MAX(id), 0)+1 INTO v_ReportParamlogId FROM reportparamlog;
INSERT
INTO reportparamlog
(
id ,
param01,
param02,
param03,
param04,
param05,
param06,
param07,
param08,
param09,
param10
)
VALUES
(
v_ReportParamlogId,
v_Param01 ,
v_Param02 ,
v_Param03 ,
v_Param04 ,
v_Param05 ,
v_Param06 ,
v_Param07 ,
v_Param08 ,
v_Param09 ,
v_Param10
);
ELSE
SELECT id
INTO v_ReportParamlogId
FROM reportparamlog
WHERE param01 = v_Param01
AND param02 = v_Param02
AND param03 = v_Param03
AND param04 = v_Param04
AND param05 = v_Param05
AND param06 = v_Param06
AND param07 = v_Param07
AND param08 = v_Param08
AND param09 = v_Param09
AND param10 = v_Param10;
END IF;
-- Insert a record into the REPORTRUNLOG table
SELECT NVL(MAX(id), 0)+1
INTO v_ReportRunLogId
FROM reportrunlog;
INSERT
INTO reportrunlog
(
id ,
reportlog_id ,
reportparamlog_id,
rundate ,
username
)
VALUES
(
v_ReportRunLogId ,
v_ReportLogId ,
v_ReportParamLogId,
SYSDATE ,
v_User
);
COMMIT;
RETURN 'OK';
EXCEPTION
WHEN OTHERS THEN
RETURN SUBSTR
(
'ERROR:' || SQLERRM || '(' || SQLCODE || ')', 1, 255
)
;
END LogReport;
END REPORTLOGGER;
Now a quick test will show if the package has been setup correctly;
SELECT reportlogger.LOGREPORT('test', '1', user)
FROM DUAL;
This should return "OK" (anything else and we need to look into the error).
The following sections look at the changes necessary to reports for the individual platforms. Because of the nature of the business I work in I'm only going to be listing reporting tools we actually use!
Adding Logging to a Microsoft SQL Reporting Services (SRS) Report
The first thing I should probably mention is that this reporting tool comes with some pretty nice "off the shelf" reports when you're tracking usage. It's very new though and if your company is anything like mine only a tiny fraction of reporting is currently done with it. As things stand at the moment I'm trying to get the reporting data in one place and so I'm going to add logging. If you want, when you sit down to look at your data, multiple reports to look at and reconcile and that's your choice.
Open the Reporting Services Project (in Visual Studio).
Open the Individual Report to be logged.
Click on the "Data" tab and then click on the Dataset drop down and select "
![]() |
| Figure 2: Creating a New Dataset |
![]() |
| Figure 3: Editing a dataset |
![]() |
| Figure 4: Editing the Report Parameters |
Version can't be populated automatically so I've decided to give this report a version of "1.0" as a static value. I'll need to remember to change that each time I do an update (but that's what pre-Go-Live review processes are there to check!).
Click "OK" to save the parameter settings and then click on the Preview tab and see if it works. you should be able to check in the database and see the fact the report has run is being logged.
Adding Logging to a Business Objects 6.5 Report
Unfortunately this isn't quite as "clean" as adding the logging to Reporting Services; it will add a new Variable to the users list which actually executes the logging. It's not necessary to add this to the report for it to work, but it is visible to the users (i.e. if they add it to the report they will see "OK" displayed). To encourage them not to use the report I've called the field "ZZDONOTUSE".
Open the Report in Business Objects.
Under the "Data" menu item select "New Data Provider" to bring up the "New Data Wizard".
![]() |
| Figure 5: Business Objects 6.5 New Data Wizard |
![]() |
| Figure 6: Specify Data Access |
![]() |
| Figure 7: Free-Hand SQL |
SELECT
reportlogger.LOGREPORT('Off-Site Storage', '1', @Variable('BOUSER')) as zzDONOTUSE
FROM DUAL
Business Objects 6.5 does not appear to have a variable for the report name (feel free to comment and correct me if that's wrong!) so it's necessary to enter the name and version number for each report and to make sure you update it when the report is changed (again the importance of a rigorous Development > Production process cannot be underestimated).
If you now run the report and check the logging tables you will see a new record for this report execution.
Wednesday, November 26, 2008
Oracle PL/SQL: Pivoting a SQL Query within SQL
This blog post includes a rather simple script that will allow a developer to quickly pivot a single-table query so that rather than returning a single row will the data it returns multiple rows representing Column Name/Value combinations.
Let's assume we have a fairly simple table called PIVOTTEST this table, apart from displaying a distinct lack of imagination as far as naming goes, contains four columns ID (number), NAME (varchar2), DATE_CREATED (date) and DATE_LAST_UPDATED (date). It's created using the SQL:
create table PIVOTTEST
(
ID number,
NAME varchar2(60),
DATE_CREATED date,
DATE_LAST_UPDATED date
);
In order to do our test let's put a few records into the table;
insert into pivottest(id, name, date_created, date_last_updated) values (1, 'ANDY', sysdate-200, sysdate - 5);
insert into pivottest(id, name, date_created, date_last_updated) values (2, 'BRETT', sysdate-190, sysdate - 4);
insert into pivottest(id, name, date_created, date_last_updated) values (3, 'COLIN', sysdate-180, sysdate - 3);
insert into pivottest(id, name, date_created, date_last_updated) values (4, 'IAN', sysdate-170, sysdate - 2);
insert into pivottest(id, name, date_created, date_last_updated) values (5, 'ADAM', sysdate-160, sysdate - 1);
commit;
If we do a SELECT * FROM PIVOTTEST WHERE ID = 1 we get a single record back:
ID NAME DATE_CREATED DATE_LAST_UPDATED
1 ANDY 10-MAY-2008 21-NOV-2008
Yours dates will be different, and the format will be determined by your system settings but you get the point.
Assuming we'd prefer to have the results as multiple columns we would be aiming at something looking like:
Column Name Column Value
ID 1
NAME ANDY
DATE_CREATED 10-MAY-2008
DATE_LAST_UPDATED 21-NOV-2008
The easiest way to do this is to use multiple UNIONS and select each field we're interested in in turn:
select 1 ID, 'ID' Name, to_char(ID) Value from APPS.PIVOTTEST where ID = 1
union
select 2 ID, 'NAME' Name, NAME Value from APPS.PIVOTTEST where ID = 1
union
select 3 ID, 'DATE_CREATED' Name,to_char(DATE_CREATED) Value from APPS.PIVOTTEST where ID = 1
union
select 4 ID, 'DATE_LAST_UPDATED' Name, to_char(DATE_LAST_UPDATED) Value from APPS.PIVOTTEST where ID = 1
First thing; in order to allow the UNION to work the columns have to be of the same type. I've go for "character" just because it's the one practically every type has in common. In theory it will depend on the data you're working with but in practice you'll almost certainly want to use characters!
You'll notice that I've included the "WHERE ID =1" clause at the end to just give me the record I'm interested in and I've numbered the select statements so that when they are all joined together with the UNION the columns still come out in the order I'm expecting (if you remove the 1, 2, 3, 4 from the SELECT ... ID then the records come back in alphabetical column name order ... do you want that?).
Because the table details are held in Oracle you can actually do the same thing using a script:
-- Created on 25-Nov-2008 by APE06
declare
-- Local variables here
cursor c_Columns is
select atc.COLUMN_ID,
atc.owner,
atc.table_name,
atc.COLUMN_NAME,
atc.DATA_TYPE
from all_tab_columns atc
where atc.owner = 'APPS'
and atc.TABLE_NAME = upper('PIVOTTEST')
order by atc.COLUMN_ID;
v_Where varchar2(255) := 'ID = 1';
begin
for v_Column in c_Columns loop
dbms_output.put_line('select ' || v_Column.column_id ||
' ID, ''' || v_Column.column_name ||
''' Name, nvl(' ||
case
when v_Column.Data_Type = 'DATE' then 'to_char(' || v_Column.column_name || ',''DD-MON-YYYY'')'
when v_Column.Data_Type = 'NUMBER' then 'to_char(' || v_Column.column_name || ')'
else v_Column.column_name
end ||
', '''') Value from ' || v_Column.Owner || '.' ||
v_Column.Table_name || ' where ' || v_Where);
dbms_output.put_line('union');
end loop;
end;
You need to change the OWNER (from APPS), the TABLE_NAME (from PIVOTTEST), and your where clause condition to return a single row (from ID = 1) and then you're ready to go.
You should also watch out for the "union" that gets tacked on the end ... you'll need to delete that (I could have added a "select '','' from dual where 3=1" to get rid of it but ... well you can do that yourselves now can't you? (I'm also using PL/SQL Developer a a test window which makes copy/pasting very easy so I don't really need 100% accuracy).
This script will generate the SQL to query the table as a Column Name/ Value combination - it also does a few other "nice" things like specifying the format for the date and displaying when the column has a null value.
I hope this helps!
Let's assume we have a fairly simple table called PIVOTTEST this table, apart from displaying a distinct lack of imagination as far as naming goes, contains four columns ID (number), NAME (varchar2), DATE_CREATED (date) and DATE_LAST_UPDATED (date). It's created using the SQL:
create table PIVOTTEST
(
ID number,
NAME varchar2(60),
DATE_CREATED date,
DATE_LAST_UPDATED date
);
In order to do our test let's put a few records into the table;
insert into pivottest(id, name, date_created, date_last_updated) values (1, 'ANDY', sysdate-200, sysdate - 5);
insert into pivottest(id, name, date_created, date_last_updated) values (2, 'BRETT', sysdate-190, sysdate - 4);
insert into pivottest(id, name, date_created, date_last_updated) values (3, 'COLIN', sysdate-180, sysdate - 3);
insert into pivottest(id, name, date_created, date_last_updated) values (4, 'IAN', sysdate-170, sysdate - 2);
insert into pivottest(id, name, date_created, date_last_updated) values (5, 'ADAM', sysdate-160, sysdate - 1);
commit;
If we do a SELECT * FROM PIVOTTEST WHERE ID = 1 we get a single record back:
ID NAME DATE_CREATED DATE_LAST_UPDATED
1 ANDY 10-MAY-2008 21-NOV-2008
Yours dates will be different, and the format will be determined by your system settings but you get the point.
Assuming we'd prefer to have the results as multiple columns we would be aiming at something looking like:
Column Name Column Value
ID 1
NAME ANDY
DATE_CREATED 10-MAY-2008
DATE_LAST_UPDATED 21-NOV-2008
The easiest way to do this is to use multiple UNIONS and select each field we're interested in in turn:
select 1 ID, 'ID' Name, to_char(ID) Value from APPS.PIVOTTEST where ID = 1
union
select 2 ID, 'NAME' Name, NAME Value from APPS.PIVOTTEST where ID = 1
union
select 3 ID, 'DATE_CREATED' Name,to_char(DATE_CREATED) Value from APPS.PIVOTTEST where ID = 1
union
select 4 ID, 'DATE_LAST_UPDATED' Name, to_char(DATE_LAST_UPDATED) Value from APPS.PIVOTTEST where ID = 1
First thing; in order to allow the UNION to work the columns have to be of the same type. I've go for "character" just because it's the one practically every type has in common. In theory it will depend on the data you're working with but in practice you'll almost certainly want to use characters!
You'll notice that I've included the "WHERE ID =1" clause at the end to just give me the record I'm interested in and I've numbered the select statements so that when they are all joined together with the UNION the columns still come out in the order I'm expecting (if you remove the 1, 2, 3, 4 from the SELECT ... ID then the records come back in alphabetical column name order ... do you want that?).
Because the table details are held in Oracle you can actually do the same thing using a script:
-- Created on 25-Nov-2008 by APE06
declare
-- Local variables here
cursor c_Columns is
select atc.COLUMN_ID,
atc.owner,
atc.table_name,
atc.COLUMN_NAME,
atc.DATA_TYPE
from all_tab_columns atc
where atc.owner = 'APPS'
and atc.TABLE_NAME = upper('PIVOTTEST')
order by atc.COLUMN_ID;
v_Where varchar2(255) := 'ID = 1';
begin
for v_Column in c_Columns loop
dbms_output.put_line('select ' || v_Column.column_id ||
' ID, ''' || v_Column.column_name ||
''' Name, nvl(' ||
case
when v_Column.Data_Type = 'DATE' then 'to_char(' || v_Column.column_name || ',''DD-MON-YYYY'')'
when v_Column.Data_Type = 'NUMBER' then 'to_char(' || v_Column.column_name || ')'
else v_Column.column_name
end ||
', ''
v_Column.Table_name || ' where ' || v_Where);
dbms_output.put_line('union');
end loop;
end;
You need to change the OWNER (from APPS), the TABLE_NAME (from PIVOTTEST), and your where clause condition to return a single row (from ID = 1) and then you're ready to go.
You should also watch out for the "union" that gets tacked on the end ... you'll need to delete that (I could have added a "select '','' from dual where 3=1" to get rid of it but ... well you can do that yourselves now can't you? (I'm also using PL/SQL Developer a a test window which makes copy/pasting very easy so I don't really need 100% accuracy).
This script will generate the SQL to query the table as a Column Name/ Value combination - it also does a few other "nice" things like specifying the format for the date and displaying
I hope this helps!
Building an MSI to Deploy Fonts using wItem Installer (formerly Installer2Go)
This blog post gives you detailed instructions on how to build a simple MSI installer that will deploy fonts to Windows users. It is envisaged that this will be used in conjunction with Active Directory (Group Policy) to control the deployment.
Why Use an MSI?
The first thing to understand is that there are many ways of doing the deployment of fonts in Windows. Each machine you are seeking to deploy to has a "Fonts" directory under it's Windows installation folder. In a default Windows installation (XP, Vista, 7, or even 2003 Server) this installation folder is called C:\WINDOWS and the directory used for Fonts is C:\WINDOWS\Fonts.
It would be possible to write a script to copy the files into this directory, you could even run that script from a GPO, but using an MSI is much neater, creates a nice error trail if things go wrong (and they will in any large environment), and is much more controllable using Active Directory and Group Policy Objects (GPOs) for deployment. Unfortunately every company will have a few machines where the installation hasbeen done into a different directory (C:\WINNT for instance!), maybe even a couple where the installation is actually on the D: drive. Putting scripts in place to test all of this is a pain. Not just the writing of the script; it's the testing and proving that it works that consumes all the time.
In short; use an MSI. It really will save you time in the long run. And if you're going to use an MSI why not use one that's free? Hence my recommendation for wItem Installer (which was formerly called Installer2Go).
Getting The Software
Of course the obvious prerequisite is going to be obtaining the MSI-building software. Fairly simple; just visit this URL;
http://www.witemsoft.com/togo/ (wItem Software)
The software is also available from other sources such as CNET if that link doesn't work.
Make sure you read the software licensing information when prompted by the installation!
The software is provided by wItem Software as "Freeware".
Using The Software
Once you've got the software installed start it up;
For size reasons I've colour-compressed the images, but hopefully they will still be good enough to give you some idea of the process.
As you can see I'm using version 4.2.5 of the software, as the MSI standard has changed very little (especially when all you need to do is install fonts!) I would expect future versions to be pretty much the same.
Step 1: Click on the "New" icon at the top left. You'll now see a whole heap of tabs;
You should now fill in all the fields on the General Info tab. You don't have to, but it's tidy and in IT we like tidy. Just show anyone in IT your windows desktop with it's gazillion icons and watch them flinch ... As the installer is going to be for internal use only you don't have to spend quite as much time on this bit ... Oh. You didn't. You've already scrolled past this bit to;
Step 2: Click on the "Files" tab and expand the "Windows" folder;
Select "Fonts" and then drag-and-drop the fonts you wish to install into this Window (they will usually have the extension .TTF). Once you've done that you're ready for the next step.
Step 3: Click the "Create Setup" (right-most) tab enter an Output Folder and a Filename and make sure that "Create Self-Extracting Executable that will contain your MSI file" is not checked.
Next click "Build" and your MSI will magically appear in your installation directory.
NOTE: At the end of an attended installation a dialog will be displayed promoting SDS Software. When you're doing an unattended installation no dialog is displayed and if you're deploying via Group Policy then it's the unattended installation that you'll be interested in!
Why Use an MSI?
The first thing to understand is that there are many ways of doing the deployment of fonts in Windows. Each machine you are seeking to deploy to has a "Fonts" directory under it's Windows installation folder. In a default Windows installation (XP, Vista, 7, or even 2003 Server) this installation folder is called C:\WINDOWS and the directory used for Fonts is C:\WINDOWS\Fonts.
It would be possible to write a script to copy the files into this directory, you could even run that script from a GPO, but using an MSI is much neater, creates a nice error trail if things go wrong (and they will in any large environment), and is much more controllable using Active Directory and Group Policy Objects (GPOs) for deployment. Unfortunately every company will have a few machines where the installation hasbeen done into a different directory (C:\WINNT for instance!), maybe even a couple where the installation is actually on the D: drive. Putting scripts in place to test all of this is a pain. Not just the writing of the script; it's the testing and proving that it works that consumes all the time.
In short; use an MSI. It really will save you time in the long run. And if you're going to use an MSI why not use one that's free? Hence my recommendation for wItem Installer (which was formerly called Installer2Go).
Getting The Software
Of course the obvious prerequisite is going to be obtaining the MSI-building software. Fairly simple; just visit this URL;
http://www.witemsoft.com/togo/ (wItem Software)
The software is also available from other sources such as CNET if that link doesn't work.
Make sure you read the software licensing information when prompted by the installation!
The software is provided by wItem Software as "Freeware".
Using The Software
Once you've got the software installed start it up;
![]() |
| Installer2Go Version 4.2 (Freeware Version) |
As you can see I'm using version 4.2.5 of the software, as the MSI standard has changed very little (especially when all you need to do is install fonts!) I would expect future versions to be pretty much the same.
Step 1: Click on the "New" icon at the top left. You'll now see a whole heap of tabs;
![]() |
| New Project Multi-Tabbed Dialog |
Step 2: Click on the "Files" tab and expand the "Windows" folder;
![]() |
| "File" Tab |
Step 3: Click the "Create Setup" (right-most) tab enter an Output Folder and a Filename and make sure that "Create Self-Extracting Executable that will contain your MSI file" is not checked.
Next click "Build" and your MSI will magically appear in your installation directory.
NOTE: At the end of an attended installation a dialog will be displayed promoting SDS Software. When you're doing an unattended installation no dialog is displayed and if you're deploying via Group Policy then it's the unattended installation that you'll be interested in!
Thursday, October 30, 2008
IBM Maximo: Re-Opening a Work Order in Maximo 4.1.1
This article gives you the instructions and Oracle PL/SQL Source code in order to change an existing Maximo 4 Work Order (WO) from a CLOSE state (where no changes are possible) back to HANDBACK state (where the record can be updated).
This code has been tested in an Oracle 8i database environment with Maximo 4.1.1 (Service pack 3) as the front end. When you run this code and look at the audit trace of a record a new entry will show that the Work Order has changed state. You will lose the records in the Equipment Hierarchy that show the Work Order has been previously closed. If you are in a tightly regulated (i.e. Pharmaceutical) environment you should carefully study this code, run it on a test system, and study the impact it has on your audit records.
NOTE: This code was written and tested using a Product called PL/SQL Developer (by AllRoundAutomations). This allows you to have output variables in blocks of PL/SQL code. If you are not running PL/SQL Developer then you will almost certainly need to modify this block of code. It will definitely not work in SQL * Plus.
This code is in two parts, the first is a simple Oracle PL/SQL script that takes two parameters; p_WorkOrder (the Work Order number) and :p_User (the user who the change should be audited against). When executed the procedure will return a message in the :p_Result variable. This will usually be "OK" meaning everything worked or an error message if it didn't.
It should be noted for validation purposes the User specified must exist as a record in the LABOR tables.
The second part of the code is the LIB_LABOR package. I created this simply to save myself some time validating labor records. There is no reason why the routines below couldn't just be copied and pasted into the script above and executed from within that (except, of course, the it's a terribly way to do ongoing development - but sometimes the terrible way to do something long term is also the way to get soemthing done quickly).
This package should be installed as your MAXIMO user (and should be accessible to the script running above):
This code has been tested in an Oracle 8i database environment with Maximo 4.1.1 (Service pack 3) as the front end. When you run this code and look at the audit trace of a record a new entry will show that the Work Order has changed state. You will lose the records in the Equipment Hierarchy that show the Work Order has been previously closed. If you are in a tightly regulated (i.e. Pharmaceutical) environment you should carefully study this code, run it on a test system, and study the impact it has on your audit records.
NOTE: This code was written and tested using a Product called PL/SQL Developer (by AllRoundAutomations). This allows you to have output variables in blocks of PL/SQL code. If you are not running PL/SQL Developer then you will almost certainly need to modify this block of code. It will definitely not work in SQL * Plus.
This code is in two parts, the first is a simple Oracle PL/SQL script that takes two parameters; p_WorkOrder (the Work Order number) and :p_User (the user who the change should be audited against). When executed the procedure will return a message in the :p_Result variable. This will usually be "OK" meaning everything worked or an error message if it didn't.
It should be noted for validation purposes the User specified must exist as a record in the LABOR tables.
declare
-- Purpose: This procedure rolls back a Work Order from CLOSED state back into
-- HANDBACK (this allows a normal maximo user to edit it).
cursor c_getWOStatus is
select wo.wonum,
wo.glaccount
from workorder wo
where wo.wonum = :p_WorkOrder
and wo.status = 'CLOSE';
v_ChangeDate date;
v_User labor.laborcode%Type; -- lib_labor routines require a writable string
v_RecordCount number;
begin
v_ChangeDate := SYSDATE; -- This ensures all changes have the same date/time stamp
v_User := :p_User;
if not lib_labor.validateLaborCode(v_User) then
:p_Result := 'ERROR: Labour code "' || v_User || '" does not exist';
else
v_RecordCount := 0; -- keep track of the number of records updated
for v_WorkOrder in c_getWOStatus loop
v_RecordCount := v_RecordCount + 1;
-- Insert an audit record to make sure that this change is "historied"
insert into WOSTATUS (WONUM, STATUS, CHANGEDATE, CHANGEBY, GLACCOUNT)
values (:p_WorkOrder, 'HANDBACK', v_ChangeDate, v_User, v_WorkOrder.Glaccount);
-- Remove the existing records in EQHIERARCHY
delete from eqhierarchy
where wonum = :p_WorkOrder;
-- Update the Work Order itself
update workorder wo
set wo.status = 'HANDBACK',
wo.statusdate = v_ChangeDate,
wo.changedate = v_ChangeDate
where wo.wonum = :p_WorkOrder
and wo.status = 'CLOSE';
end loop;
-- Ensure that we return something as the result.
if v_RecordCount = 1 then
:p_Result := 'OK';
elsif v_RecordCount > 1 then
:p_Result := 'ERROR: Multiple workorders found for WO ' || :p_WorkOrder; -- *should* be impossible
rollback; -- do not commit any changes!
else
:p_Result := 'ERROR: Work Order ' || :p_WorkOrder || ' does not exist/ is not in CLOSED state';
end if;
end if;
-- Commit changes (if any) to the database
commit;
exception
when others then
:p_Result := 'ERROR: PL/SQL error' || SQLERRM || ' (' || SQLERRM || ')';
rollback;
end;
The second part of the code is the LIB_LABOR package. I created this simply to save myself some time validating labor records. There is no reason why the routines below couldn't just be copied and pasted into the script above and executed from within that (except, of course, the it's a terribly way to do ongoing development - but sometimes the terrible way to do something long term is also the way to get soemthing done quickly).
This package should be installed as your MAXIMO user (and should be accessible to the script running above):
create or replace package lib_labor is
function getEMail(
p_LaborCode in varchar2) return varchar2;
function getFormattedContactDetails(
p_LaborCode in varchar2,
p_Format in varchar2) return varchar2;
function validateLaborCode(
p_LaborCode in out varchar2) return Boolean;
end lib_labor;
create or replace package body lib_labor is
function getEMail(
p_LaborCode in varchar2) return varchar2 as
begin
return getFormattedContactDetails(
p_LaborCode => p_LaborCode,
p_Format => '%EMAIL%');
end getEMail;
function getFormattedContactDetails(
p_LaborCode in varchar2,
p_Format in varchar2) return varchar2 as
pragma autonomous_transaction;
cursor c_Labor is
select l.Name, l.CallId extension, l.pagepin email
from labor l
where l.laborcode = upper(p_LaborCode)
and rownum = 1;
v_Result varchar2(255);
begin
for v_Labor in c_Labor loop
v_Result := Replace(p_Format, '%NAME%', v_Labor.Name);
v_Result := Replace(v_Result, '%EXT%', v_Labor.extension);
v_Result := Replace(v_Result, '%EMAIL%', v_Labor.email);
v_Result := Replace(v_Result, '%CODE%', p_LaborCode);
end loop;
return v_Result;
end getFormattedContactDetails;
function validateLaborCode(
p_LaborCode in out varchar2) return Boolean as
begin
if p_LaborCode <> Upper(p_LaborCode) then
p_LaborCode := Upper(p_LaborCode);
end if;
return (getFormattedContactDetails(p_LaborCode, '%CODE%') is not null);
end validateLaborCode;
end lib_labor;
Subscribe to:
Posts (Atom)











