Wednesday, September 9, 2009

Simple logging for oracle procedures


When PL*SQL is used as an ETL tool (instead of OWB or another off the shelf tool) there is a need to audit the results of the ETL procedures to monitor the loading of the data warehouse.
In this blog a simple logging procedure is created and it is shown how to use it to log a test procedure.

Create the log table:

CREATE TABLE MAP_LOG
(ID NUMBER NOT NULL ENABLE
,MAP_NAME VARCHAR2(30)
,MAP_START_DATE DATE
,MAP_END_DATE DATE
,ELAPSED NUMBER
,NR_RECORDS NUMBER
,ERR_MSG VARCHAR2(2000)
,CONSTRAINT MAP_LOG_PK PRIMARY KEY (ID) ENABLE);

The log table contains an id as primary key, because the map name combined with the start date is in general not enough to make a primary key.
Further columns as the map (or procedure) name, start date, end date, elapsed time in seconds, the number of records deleted, inserted, merged or updated and the eventual error message.

Create the sequence:

CREATE SEQUENCE SEQ_MAP_LOG MINVALUE 1 MAXVALUE 999999999999999999999999999 INCREMENT BY 1 START WITH 1 NOCACHE NOORDER NOCYCLE;

Create the logging procedure:

CREATE OR REPLACE PROCEDURE P_LOG
(I_ID IN NUMBER
,I_MAP_NAME IN VARCHAR2
,I_BE_IN IN VARCHAR2
,I_NR_RECORDS IN NUMBER
,I_ERR_MSG IN VARCHAR2)
IS
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
IF I_BE_IN = 'B'
THEN
INSERT INTO MAP_LOG(ID,MAP_NAME,MAP_START_DATE)
VALUES (I_ID,I_MAP_NAME,SYSDATE);
ELSE
UPDATE MAP_LOG
SET
MAP_END_DATE = SYSDATE
,ELAPSED = ROUND((SYSDATE - MAP_START_DATE)*86400)
,NR_RECORDS = I_NR_RECORDS
,ERR_MSG = I_ERR_MSG
WHERE ID = I_ID;
END IF;
COMMIT;
END;

By using the pragma autonomous_transaction the insert and update in the logging table can be committed without influencing the transaction that is logged. The explicit commit at the end of the logging procedure is necessary because of the autonomous transaction.

Create a test table to load in the test procedure :
Note: 'LT' is used here instead of the less then sign.

CREATE TABLE TEST AS
SELECT * FROM USER_OBJECTS
CROSS JOIN (SELECT LEVEL FROM DUAL CONNECT BY LEVEL LT 2)
WHERE 1 = 0;

With the join with level the number or records in the selection is easily varied.

Create a test procedure:

CREATE OR REPLACE PROCEDURE MAP_TEST
IS
LOG_ID NUMBER;
BEGIN
SELECT SEQ_MAP_LOG.NEXTVAL INTO LOG_ID FROM DUAL;
P_LOG(LOG_ID,$$PLSQL_UNIT,'B',NULL,NULL);
EXECUTE IMMEDIATE 'TRUNCATE TABLE TEST';
INSERT INTO TEST
SELECT * FROM USER_OBJECTS
CROSS JOIN (SELECT LEVEL FROM DUAL CONNECT BY LEVEL LT 10001);
P_LOG(LOG_ID,NULL,'E',SQL%ROWCOUNT,NULL);
EXCEPTION WHEN OTHERS THEN
P_LOG(LOG_ID,NULL,'E',SQL%ROWCOUNT,SQLERRM);
END;

This test procedure selects the contents of user_objects 10.000 times (to make the procedure run for a few seconds and test the elapsed column) and inserts it into the test table.
Before the insert into test the sequence number is selected and an entry in the log table is made.
After the insert into the test table the logrecord is updated.
In case of an error the sql error message is logged in the exception handler.












Wednesday, August 12, 2009

Automatic conversion of Excel to CSV for Oracle external table using an auto open macro


In Oracle Data Warehouses data from Excel sheets is often used for reference tables.
In order to read this data into an external table the Excel spreadsheet has to be converted to csv format.
Users administer data in Excel rather in the xlsx format than the csv format, because they can use multiple sheets in a workbook.
In this example the sheets 'first_sheet' and 'second_sheet' of the workbook 'excel_to_convert.xlsx' are automatically converted to 'first_sheet.csv' and 'second_sheet.csv' by opening the macro enabled workbook 'convert_xlsx_to_csv.xlsm'.

After opening 'convert_xlsx_to_csv.xlsm' the macro Auto_open executes:

Sub Auto_open()
'
' Auto_open Macro
'

Application.Run "convert_xlsx_to_csv.xlsm!Save_as_csv"
Application.Quit

End Sub

The Auto_open macro calls macro Save_as_csv.
This macro opens the workbook C:\excel_to_convert.
The sheet 'first_sheet' is saved as 'first_sheet.csv', the second sheet likewise:

Sub Save_as_csv()
'
' Save_as_csv Macro
'
Application.DisplayAlerts = False
Workbooks.Open Filename:="C:\excel_to_convert.xlsx"
Sheets("first_sheet").Select
ActiveWorkbook.SaveAs Filename:="C:\first_sheet.csv", FileFormat:= _
xlCSV, CreateBackup:=False
ActiveWindow.Close

Workbooks.Open Filename:="C:\excel_to_convert.xlsx"
Sheets("second_sheet").Select
ActiveWorkbook.SaveAs Filename:="C:\second_sheet.csv", FileFormat:= _
xlCSV, CreateBackup:=False
ActiveWindow.Close

Application.DisplayAlerts = True

End Sub


Now the load of the external tables can be completely automated:
  1. In a scheduled task we call a bat file.
  2. In the bat file we open the macro enabled excel.
  3. In the bat file we ftp the resulting csv files to an oracle directory on the database server.
  4. The data is available as an external table in the oracle database.
When we reverse this sequence it can be used for instance to automatically mail formatted excel files to end users:
  1. We write a csv file to an oracle directory on the database server using the utl_file package.
  2. In a scheduled task we call a bat file.
  3. In the bat file we ftp the csv files to the windows server.
  4. In the bat file we convert the csv file to a formatted excel, using another macro enabled excel.
  5. In the bat file we mail the formatted Excel to the end user.