Tuesday, July 22, 2014

Techniques to control the processing units in the ETL process

Introduction
In the loading of a Data Warehouse is important to have full control of the processing units that compose it. Each processing unit must be carefully monitored both in the detection of errors that may occur, both in the analysis of the execution times.
In this article, which uses the messaging techniques described in the slideshare [http://www.slideshare.net/jackbim/recipe-7-of-data-warehouse-a-messaging-system-for-oracle-dwh-1], I enrich the Micro ETL Foundation, by building a control system of the PL/SQL elaboration units.
By using the MEF messaging system, we have seen how to activate messages that are then stored in a log table. In the demonstration tests, it is seen as a suitable sequence of messages and a correct settings of the package variables , has provided an idea of the processing flow and the delay between a message and the other.
It could call The setting of those tests as an "excess of zeal". In fact the purpose of the messaging system was much less ambitious. It wanted provide only generic information at any moment of the ETL process, simply calling a procedure.
It is now time to take the next step, to implement an agile control system for the processing units. The goal is always the same: it must be simple and non-invasive. This means that it can plug into existing DWH systems (and not only) gradually, without changing the elaborative flow. There is no doubt, however, that if you have already set up your ETL process in a modular way, the application of the techniques that will be described, it will be simpler and more natural.
I will use concepts and definitions already given in the messaging system. Now let us concentrate on the concept of modularity, which is fundamental to the control system.

Modularity and sequencing
The concept of modularity is the basis of the techniques that will be exhibited.As we know, a complex system, and the ETL process of a Data Warehouse can be defined without doubt very complex, it can be managed and understood only if we can break its overall complexity, in less complex components. In other words: you can not have one big main program that contains thousands code lines. This is the first point.
The second point is the sequentiality. We must try to think, and you can almost always do, that each component of the process is connected to the next, and that their sequential execution leads to the final loading of the Data Warehouse. Please note, I am not saying it is not possible the parallelism, but to identify which components are completely independent of each other (so they can run in parallel), it is not an easy task; without forgetting all the problems of their synchronization.
Moreover, the parallelism also requires a specific hardware structure and specific Oracle settings. And the performance improvement,I speak from my experience, it is not so sure. I suggest, therefore, to try to apply parallelism on the objects rather than on the processes. Usually, the dimension tables may be loaded in parallel (if there are not logical connections between them), but why complicate our lives if we can reason in a simple sequential  way ? 
Recall that simplicity is a pillar of the Micro ETL Foundation. So the advice is: modularity and sequencing. Do not forget that the ETL process, is physiologically sequential in its basic pillars. You can not load a Data Mart of level 2 before loading  the Data Mart of level 1. And the Data Mart of level 1 cannot be loaded until you first load the dimensions, which in turn, cannot be loaded if you have not first  loaded the Staging Area tables, and so on.
The figure below shows the concepts  of modularity and sequencing applied to a hypothetical schedule S1. On the left we have the "logical" components of the ETL process, ie not code, but names configured in a table.On the right we have the "physical" components, ie the real programming code.



Requirements
The main requirement, as already mentioned, is to have the control of each processing unit (unit) which
constitutes the ETL process . Having control means that, for each unit, at any time, I need to know:

• when it started and when it ended.
• how long was his execution.
• if it is successful or has had some problems (exception).
• if you received an exception, what is the error that occurred
• What are the consequences in case of error.

In order to meet those requirements, it is not necessary to use very complex structures. In accordance with the MEF philosophy, I will use just a configuration table (MEF_UNIT_CFT), that allows me to enter the main characteristics of the units that make up the loading job, and a logging table (MEF_UNIT_LOT) that allows me to see the log of the executions.
The most important information present in the configuration table, along with some context information, is the continuity flag, which allows me to decide whether, in the event of an error, the error is critical (ie it must abort the job that has called the unit) or not critical (ie it must allows the running of the next unit ).
To be able logging the execution, you will have to "surround" the unit call by a procedure call that logs the beginning, and from a procedure call that logs the end.
In addition, the error situations should be treated in a uniform manner by a unique procedure, which logs the error and implements the consequences using the continuity flag.
To control the behavior of a unit means, first of all, to understand its life cycle in the global context of the job to which it belongs. To that end, we will use the the theory of the  finite-state machine, or, if we want to be a bit more modern, we will use a simplified version of the state-diagrams of the Unified Modelling Language.

The state diagram
As stated previously, the MEF control system, in order to perform its function, it must surround the execution of every elaborative unit, with a procedure call (p_start) that registers its beginning, and with a procedure call (p_end) that registers the end. In practice, the program code (we will see him clearly in the test case) must have call like:

p_start
<unit call x>
p_end
p_start
<unit call y>
p_end
...

This seems so simple, but to complicate the situation, there may be exceptions in the execution. These exceptions should be handled by a procedure call (p_exc) always present inside the unit. A state diagram is the most useful tool to understand the unit life cycle.


The Figure shows the various states and the possible state changes of the units inside a block of executions. Everything shown graphically it was  translated into PL/SQL language.
Because each unit must be preceded by a procedure call that logs the beginning of the execution, the procedure p_start places the unit in the "running" state , and set the return code to a obviously unknown ("?") value. It is important to note that after the p_start must be present only the call of the unit and no other procedure.
The unit can conclude its run in two different ways. It finishes without any problems, ie no Oracle error,or it fails. In the first case the unit will call the p_end procedure, which leads us into a "Done" terminal state with  return code = "OK". In the second case, depending on the setting of the continuity flag in the configuration table, it can behave in two different ways.
The unit ends in a definitive way and thus prevents the execution of any other unit, it switches in the "Aborted" state  and sets the return code = "NOT OK".
The unit ends with a warning, switches in the "Done" state but with  "OK (Warning)" as return code.This doesn't  prevent the execution of the next unit; In fact, in this state is again possiblecall a p_start procedure that will switch the next unit in the "Running" state.
The p_end procedure however, will do nothing, leaving the unit in the final state.Each state change that is not present in the diagram, will always give an error message.This ensures that, for distraction, you have not followed the correct sequence of calls(eg.  you have forgotten the p_end procedure or it is called more than one time, or other).

The exception management
Since the more complex change of state is related to the error situations, we explore briefly the Exception management, (of which, however, we have already seen some examples inside the messaging techniques).
In the PL/SQL language, an error situation that occurs while the program is running, it is called "exception". The error condition can be generated by the program itself (eg. Division by zero), or forced by the logic of the program (eg,. An amount that does not exceed a certain threshold).
In the latter case, the exception is explicitly reached using the RAISE_APPLICATION_ERROR statement. Regardless of the cause of the error, officially identified as internal exception or user-defined exception, the Oracle PL/SQL engine transfers the control of the program toward the exception handler of the running module (or PL/SQL block) In practice the code after the EXCEPTION keyword .
Obviously, if the EXCEPTION keyword is not present, the program will endimmediately because did not find any indication for error handling.
Let us now analyze the error propagation. If there are several nested procedures between them, an unhandled error by the most internal procedure , propagates into the caller procedure, and so forth up to the main program. If the error is handled, the  procedure will continue its regular work. (unless inside the exception handler there is the RAISE keyword or there is a software error into the exception handler).
To clarify the positioning of the exception call, we try to anticipate what will be the structure of the program code, using the next figure.


Design
The design of the unit control system, is composed of two tables, and a sequence.The MEF_CFT table is the configuration table of the processing units.The MEF_UNIT_LOT table  is the one that keeps the log of the executions of the units. The MEF_UNIT_LOT_SEQ sequence  serves to give a sequence number to each log line. 

drop table mef_unit_cft cascade constraints purge;
create table mef_unit_cft
(
  sched_cod        varchar2(30)                 not null,
  job_cod          varchar2(60)                 not null,
  unit_cod         varchar2(61)                 not null,
  sort_cnt         number                       ,
  continue_flg     number                       ,
  unit_active_flg  number                       ,
  job_active_flg   number                      
)
;

drop table mef_unit_lot cascade constraints purge;
create table mef_unit_lot
(
  seq_num          number          not null,
  day_cod          number          not null,
  sched_cod        varchar2(30)    not null,
  job_cod          varchar2(60)    not null,
  unit_cod         varchar2(61)    not null,
  exec_cnt         number          not null,
  status_cod       varchar2(60)    not null,
  return_cod       varchar2(30)    not null,
  ss_num           number          not null,
  mi_num           number          not null,
  hh_num           number          not null,
  elapsed_txt      varchar2(4000)  not null,
  errmsg_txt       varchar2(4000),
  stamp_dts        date            not null
)
;

alter table mef_unit_cft add (
  constraint mef_unit_cft_pk1
 primary key
 (job_cod, unit_cod));   

                                   
drop sequence mef_unit_lot_seq;

create sequence mef_unit_lot_seq nocache;  

Let us now see in detail these objects.

The DDW_COM_ETL_UNIT_LOT_SEQ sequence
It is the most functional of the time stamp to sort the table. Because sometimes the units begin and end in fractions of a second of each other,the time stamp might not be sufficiently discriminating.

The DDW_COM_ETL_UNIT_CFT table
This table contains the configuration information of the processing units In it you configure schedules, jobs and units.For the purpose of the control system of the elaboration units, jobs and schedules will be used in a static way, as parameters to the procedure calls.
Their management will be explained, in the future, in the control system of the jobs. As well the SORT_CNT, UNIT_ACTIVE_FLG and JOB_ACTIVE_FLG fields not havean immediate use, and we not have to set them.

  • SCHED_COD: Identifies the schedule to which the job belongs. It is alogical entity, in the sense that we have to think of it as the identifier of a job list.
  • JOB_COD: identifier of the job. It is a logical entity, in the sense that we have to think of it as the identifier of a list of elaboration units.
  • UNIT_COD: Identifier of the processing unit within the job. We can think of it as a Oracle packaged procedure.
  • SORT_CNT: Counter of the unit inside the job.
  • CONTINUE_FLG: Continuity flag. If set = 1 means that in the event of an error, the next unit can continue, if set = 0 means that the job should have an abort because the error is blocker.
  • UNIT_ACTIVE_FLG: A flag that indicates whether the unit is active.
  • JOB_ACTIVE_FLG: Flag indicating whether the job is active
The DDW_COM_ETL_UNIT_LOT table
This table stores all information relating to the executions of all processing units. Its structure is very similar to that of messaging system as regards the timing information, but in addition also retains the unit status and the final outcome of its execution.

  • SEQ_NUM: Sequential number of the line obtained from the Oracle sequence .
  • DAY_COD: day of the execution in the  YYYYMMDD (year, month, day) format
  • SCHED_COD: identifier of the schedule to which the job belongs.
  • JOB_COD: identifier of the job.
  • UNIT_COD: Identifier of the processing unit within the job.
  • EXEC_CNT: Identifier of the job execution. Every job execution should be tagged by a number, in turn extracted from a Oracle sequence.
  • STATUS_COD: State of the unit.
  • RETURN_COD: return code of the execution of the unit.
  • SS_NUM: Number of seconds consumed by the processing unit.This information, together with the two following, is a summable statistical number.
  • MI_NUM: Number of minutes consumed by the processing unit
  • HH_NUM: Number of hours consumed by the processing unit
  • ELAPSED_TXT: execution time in the HH24MISS format
  • ERRMSG_TXT: Error Message.
  • STAMP_DTS: Time stamp.

The MEF_UNIT package
This package is the core of the control system. 

 /*******************************************************************************
*   Package Specification
*******************************************************************************/
CREATE OR REPLACE package mef_unit is
type pt_work_rec is record (
   status_num number,
   lot_row mef_unit_lot%rowtype,
   cft_row mef_unit_cft%rowtype,
   fail_list_txt varchar2(4000),
   fail_unit_cnt number,
   errmsg_txt varchar2(4000),
   continue_flg number
);
pv_work_rec pt_work_rec;
function f_concat(p_curr varchar2,p_add varchar2, p_size number,p_sep varchar2)
   return varchar2;
procedure p_start(
   p_sched_cod varchar2
   ,p_job_cod varchar2
   ,p_unit_cod varchar2);
procedure p_end;
procedure p_exc(p_unit_cod varchar2,p_errmsg_txt varchar2);
procedure p_ins_unit_lot(p_row in out mef_unit_lot%rowtype);
procedure p_upd_unit_lot(p_row in out mef_unit_lot%rowtype);
procedure p_get_cft(p_sched_cod varchar2,p_job_cod varchar2
   ,p_unit_cod varchar2,p_cft_row in out mef_unit_cft%rowtype);
end;
/
sho errors

/*******************************************************************************
*   Package Body
*******************************************************************************/
CREATE OR REPLACE package body mef_unit as
pv_pkg varchar2(30) := 'mef_unit.';
pv_error EXCEPTION; 
pragma exception_init (pv_error, -20058);

function f_concat(p_curr varchar2,p_add varchar2, p_size number,p_sep varchar2)
   return varchar2 as
   v_module_cod varchar2(61) := pv_pkg||'f_concat';
v_out varchar2(4000);
begin
   if (p_curr is null) then
      v_out := substr(p_add,1,p_size);
   else
      if (length(p_curr)+length(p_add)+1 > p_size) then
         v_out := substr(p_curr||'...',1,p_size);
      else
         v_out := p_curr||p_sep||p_add;
      end if;
   end if;
   return v_out;
exception
   when pv_error then raise;
   when others then mef.p_rae(sqlerrm,v_module_cod);   
end;

procedure p_ins_unit_lot(p_row in out mef_unit_lot%rowtype) is
   pragma autonomous_transaction;
   v_module_cod varchar2(61) := pv_pkg||'p_ins_unit_lot';
begin
   insert into mef_unit_lot values p_row;
   commit;
exception
   when pv_error then raise;
   when others then mef.p_rae(sqlerrm,v_module_cod);
end;

procedure p_upd_unit_lot(p_row in out mef_unit_lot%rowtype) is
   pragma autonomous_transaction;
   v_module_cod varchar2(61) := pv_pkg||'p_upd_unit_lot';
begin
   mef.delta_time(p_row.stamp_dts,sysdate,p_row.ss_num
   ,p_row.mi_num,p_row.hh_num,p_row.elapsed_txt);
   update mef_unit_lot set
       exec_cnt      = p_row.exec_cnt,
       status_cod    = p_row.status_cod,
       return_cod    = p_row.return_cod,
       ss_num        = p_row.ss_num,
       mi_num        = p_row.mi_num,
       hh_num        = p_row.hh_num,
       elapsed_txt   = p_row.elapsed_txt,
       errmsg_txt    = p_row.errmsg_txt,
       stamp_dts     = p_row.stamp_dts
      where seq_num = p_row.seq_num;
      commit;
   exception
      when pv_error then raise;
      when others then mef.p_rae(sqlerrm,v_module_cod);
end;

procedure p_get_cft(p_sched_cod varchar2,p_job_cod varchar2
   ,p_unit_cod varchar2,p_cft_row in out mef_unit_cft%rowtype) is
   v_module_cod varchar2(61) := pv_pkg||'p_get_cft';
begin  
     select * into p_cft_row
     from mef_unit_cft
     where job_cod=p_job_cod
     and unit_cod=p_unit_cod
     --and sched_cod=p_sched_cod
     ;
exception
   when pv_error then raise;
   when others then mef.p_rae(sqlerrm,v_module_cod,mef.f_str(
   'Sched %1 job %2 unit %3 not found in mef_unit_cft (check case sensitive)',
   p_sched_cod,p_job_cod,p_unit_cod));
end; 
procedure p_init_unit (
   p_sched_cod varchar2
   ,p_job_cod varchar2
   ,p_unit_cod varchar2) is
   v_module_cod varchar2(61) := pv_pkg||'p_init_unit';
begin
   mef.pv_job_cod := p_job_cod;
   mef.pv_sched_cod := p_sched_cod;
   mef.pv_unit_cod := p_unit_cod;
  p_get_cft(
      p_sched_cod,p_job_cod,p_unit_cod,pv_work_rec.cft_row);     
      pv_work_rec.lot_row.seq_num := mef.f_get_seq_val(
      'mef_unit_lot_seq','nextval');
   pv_work_rec.lot_row.job_cod := p_job_cod;
   pv_work_rec.lot_row.sched_cod := p_sched_cod;
   pv_work_rec.lot_row.unit_cod := p_unit_cod;
   pv_work_rec.lot_row.stamp_dts := sysdate;
   pv_work_rec.lot_row.day_cod := to_char(sysdate,'yyyymmdd'); 
   pv_work_rec.lot_row.errmsg_txt := null;
   pv_work_rec.lot_row.ss_num := 0;
   pv_work_rec.lot_row.mi_num := 0;
   pv_work_rec.lot_row.hh_num := 0;
   pv_work_rec.lot_row.elapsed_txt := '?';
   pv_work_rec.lot_row.exec_cnt := nvl(mef.pv_exec_cnt,0); 
   pv_work_rec.continue_flg := 0;
exception
      when pv_error then raise;
      when others then mef.p_rae(sqlerrm,v_module_cod); 
end;

procedure p_exc_continue (p_unit_cod varchar2,p_errmsg_txt varchar2) is
   v_module_cod  varchar2(61) := pv_pkg||'p_exc_continue';
begin
   pv_work_rec.lot_row.status_cod := 'Done';
   pv_work_rec.lot_row.return_cod := 'OK (Warning)';
   pv_work_rec.lot_row.errmsg_txt := p_errmsg_txt;
   pv_work_rec.errmsg_txt :=
   f_concat(pv_work_rec.errmsg_txt,p_errmsg_txt,4000,mef.cr); 
   pv_work_rec.status_num := 3;
  p_upd_unit_lot(pv_work_rec.lot_row);  
exception
   when pv_error then raise;
   when others then mef.p_rae(sqlerrm,v_module_cod);
end;

procedure p_exc_abort (p_unit_cod varchar2,p_errmsg_txt varchar2) is
   v_module_cod  varchar2(61) := pv_pkg||'p_exc_abort';
begin
   pv_work_rec.lot_row.status_cod := 'Aborted';
   pv_work_rec.lot_row.return_cod := 'NOT OK';
   pv_work_rec.lot_row.errmsg_txt := p_errmsg_txt;
   pv_work_rec.errmsg_txt := p_errmsg_txt; 
  p_upd_unit_lot(pv_work_rec.lot_row);
   pv_work_rec.status_num := 4;
   raise_application_error (-20058, 'Module '||p_unit_cod||' aborted !');             
exception
   when pv_error then raise;
   when others then mef.p_rae(sqlerrm,v_module_cod);
end;

/*******************************************************************************
*   Entry points
*******************************************************************************/
procedure p_start (
   p_sched_cod varchar2
   ,p_job_cod varchar2
   ,p_unit_cod varchar2) is
   v_module_cod varchar2(61) := pv_pkg||'p_start';
   v_str   varchar2 (4000);
begin
   if (nvl(pv_work_rec.status_num,0) in (0,2,3)) then
      p_init_unit(p_sched_cod,p_job_cod,p_unit_cod);
      pv_work_rec.lot_row.status_cod := 'Running';
      pv_work_rec.lot_row.return_cod := '?';    
      p_ins_unit_lot(pv_work_rec.lot_row);
      pv_work_rec.status_num := 1;       
   else
      raise_application_error (-20058, 'Not allowed');
   end if;
exception
      when pv_error then raise;
      when others then mef.p_rae(sqlerrm,v_module_cod);
end;

procedure p_end is
   v_module_cod  varchar2(61) := pv_pkg||'p_end';
begin
   if (pv_work_rec.status_num = 1) then
      pv_work_rec.lot_row.status_cod := 'Done';
      pv_work_rec.lot_row.return_cod := 'OK';
     p_upd_unit_lot(pv_work_rec.lot_row);
      pv_work_rec.status_num := 2;       
   elsif (pv_work_rec.status_num = 3) then
      null;    
   else  
      raise_application_error (-20058, 'Not allowed');
   end if;
exception
      when pv_error then raise;
      when others then mef.p_rae(sqlerrm,v_module_cod);
end;

procedure p_exc(p_unit_cod varchar2,p_errmsg_txt varchar2) is
   v_module_cod  varchar2(61) := pv_pkg||'p_exc';
begin
   mef.p_send(p_unit_cod,'%1 (continue = %2)'
   ,p_errmsg_txt,pv_work_rec.cft_row.continue_flg);      
   if (nvl(pv_work_rec.status_num,0) <> 1) then
      mef.p_send(p_unit_cod,'Not allowed'); 
      raise_application_error(-20058,p_errmsg_txt);
   else
      pv_work_rec.fail_unit_cnt := nvl(pv_work_rec.fail_unit_cnt,0) + 1;
      pv_work_rec.fail_list_txt :=
      f_concat(
      pv_work_rec.fail_list_txt,p_unit_cod,2000,mef.cr);
      if (pv_work_rec.cft_row.continue_flg = 1) then
         p_exc_continue(pv_work_rec.lot_row.unit_cod,p_errmsg_txt);
      else
         p_exc_abort(pv_work_rec.lot_row.unit_cod,p_errmsg_txt);
      end if;
   end if; 
end;
end;
/
sho errors
I will give a brief description of the main procedures.

p_start
The task of the p_start procedure, is to record the start of the processing unit. In practice, the code implements the logic present in the diagram of the status changes.The initial test is related to the recognition of the current state to see if the p_start procedure is permitted at this time. If we are not in a state 0, ie, the first elaboration unit of the job, in a 2/3 state, (that is, after the correct end or the end with warning of the previous unit), it generates an abort that prevents to the process to continue.
If we are in the correct state, is called the initialization procedure of the unit and there is the setting of the status variable and of the return code variable. So all this informations are stored in the DDW_COM_ETL_UNIT_LOT table. Finally, there is the change of the unit current status.

p_exc
The procedure of the exceptions management begins immediately with the recording of this anomalous situation in the MEF_MSG_LOT table. At this point there is the test on the state, which, obviously, can only be that of unit in running (ie the state 1).
Are then updated two variables, the use of which we will see in the future, which preserve the history of the units in error.
The fail_unit_cnt is simply a counter of the units that have had problems,
The fail_list_txt variable, links using a carriage return, the name of these units.
The next test, based on the continuity flag, invokes the corresponding management procedures. Let's see.

p_exc_continue
This procedure has the task of recording the error situation, but should not block the process of working through. It then sets the state and the return code of the unit as specified by the state diagram.It will pass in the final state 3, ie ends with warning, and updates the MEF_UNIT_LOT table.

p_exc_abort
This procedure has the task to terminate the processing flow. Like the previous procedure, it will sets the ending state, it will sets the return code and updates the MEF_UNIT_LOT table, but ends, with the RAISE_APPLICATION_ERROR, in a definitive way, the running job.

p_init_unit
This procedure has the only task to initialize all global variables, extracting the information from the MEF_UNIT_CFT table basing on parameters received in input.

The UNIT_TEST package
We are now able to start our tests. To this end, we will build the UNIT_TEST package which will contain a small number of processing units. Its code is only for demonstration. Note, that each processing unit has the exception according to the standard described above, ie using the SQL statement:

when others then mef_unit.p_exc (v_module_cod, sqlerrm);

create or replace package unit_test is
procedure module1;
procedure module2;
procedure module3;
procedure module_a;
procedure module_w;
procedure module_w2;
end;
/
sho errors

create or replace package body unit_test is
pv_pkg varchar2(30) := 'unit_test.';

procedure module1 as
   v_module_cod varchar2(60) := pv_pkg||'module1';
   v_num number;
begin
   select count(*) into v_num from all_objects;
exception
   when others then mef_unit.p_exc(v_module_cod,sqlerrm);   
end;

procedure module2 as
   v_module_cod varchar2(60) := pv_pkg||'module2';
   v_num number;
begin
   select count(*) into v_num from all_objects;
exception
   when others then mef_unit.p_exc(v_module_cod,sqlerrm);   
end;
procedure module3 as
   v_module_cod varchar2(60) := pv_pkg||'module3';
   v_num number;
begin
   select count(*) into v_num from all_objects;
exception
   when others then mef_unit.p_exc(v_module_cod,sqlerrm);   
end;

procedure module_a as
   v_module_cod varchar2(60) := pv_pkg||'module_a';
   --v_gg date;
begin
    execute immediate 'delete pippo';
    --v_gg := to_date('20100140','yyyymmdd');
exception
   when others then mef_unit.p_exc(v_module_cod,sqlerrm);   
end;

procedure module_w as
   v_module_cod varchar2(60) := pv_pkg||'module_w';
   v_num number(1);
begin
    select count(*) into v_num from all_objects;
exception
   when others then mef_unit.p_exc(v_module_cod,sqlerrm);   
end;

procedure module_w2 as
   v_module_cod varchar2(60) := pv_pkg||'module_w2';
   v_gg date;
begin
    v_gg := to_date('20100140','yyyymmdd');
exception
   when others then mef_unit.p_exc(v_module_cod,sqlerrm);   
end;
end;
/
sho errors

We see the functionality of these modules:
  • MODULE1, MODULE2, MODULE3: They are procedures that finish without problems. They set into a local variable, the number of rows of an Oracle system table.This select was chosen to occupy a little time, and allows us to check the correctness of the information thunderstorms in the MEF_UNIT_LOT table .
  • MODULE_W, MODULE_W2: These procedures have some instructions that force an error, that, using the flag of continuity, is non-blocking.
  • MODULE_A: This procedure will end in failure, as the number of rows in the table is surely greater than 1 digit ,which is the constraint associated with the v_num local variable. As the flag of continuity says, the error is blocking. Remember, in your own tests, to exit and enter from SQL, between a test and the other, to reset the package variables.In addition, you must place a lot of attention to the names of units: If you execute the p_start of the X unit and you start the Y unit, the elaboration log will not be reliable.

Installation
I remember that all the code of the control system of units, can be downloaded from MEF_01:
https://drive.google.com/folderview?id=0B2dQ0EtjqAOTN3I1MU9JQmpOUEE&usp=sharing
Before you use it, you must install the base of the Micro ETL Foundation, that is the messaging system. This is the link to MEF_00:
https://drive.google.com/folderview?id=0B2dQ0EtjqAOTaU5WNmc5MkVnVFE&usp=sharing
Its installation is explained on slideshare:
http://www.slideshare.net/jackbim/recipe-7-of-data-warehouse-a-messaging-system-for-oracle-dwh-2

spool mef_unit_install

@mef_unit_ddl.sql
@mef_unit_pkg.sql
@unit_test_pkg.sql

spool off

Regarding the control system of the units, do this. You must go in SQL*Plus with the user you created/ configured in the messaging system. Then run:

SQL> @ mef_unit_install.sql

You do not need to do anything else. We're ready for the test phase.

Test1 (it is all right)
We enter into SQL*Plus, and we launch the unit_test_run1.sql SQL script. The script is very simple.

delete mef_unit_cft where job_cod='job1';

insert into mef_unit_cft(sched_cod, job_cod, unit_cod, continue_flg)
values('s1', 'job1', 'unit_test.module1', 0);
insert into mef_unit_cft(sched_cod, job_cod, unit_cod, continue_flg)
values('s1', 'job1', 'unit_test.module2', 0);
insert into mef_unit_cft(sched_cod, job_cod, unit_cod, continue_flg)
values('s1', 'job1', 'unit_test.module3', 0);
commit;

begin
   mef.pv_exec_cnt := 1;
   mef_unit.p_start('s1','job1','unit_test.module1');
   unit_test.module1;
   mef_unit.p_end;
 
   mef_unit.p_start('s1','job1','unit_test.module2');
   unit_test.module2;
   mef_unit.p_end;
 
   mef_unit.p_start('s1','job1','unit_test.module3');
   unit_test.module3;
   mef_unit.p_end;
end;
/
sho errors
exit;

First of all, it configures the units of the testing job, by inserting a row for units, into the MEF_UNIT_CFT table. So then runs the three units that surely will end with a positive outcome (it only takes about 30 seconds). As you can see, each unit is limited by the start/end pair.
Now we can verify the result, by seeing the contents of the MEF_UNIT_LOT table. You can enter into SQL*Plus and run the select of the table, but for ease of viewing, I will show the result (reduced) in graphical format.



Test2 (not-blocking errors)
In this second test, we show the behavior of the control system, in case of non-blocking errors. We go into SQL*Plus and launch the unit_test_run2.sql script.

delete mef_unit_cft where job_cod='job2';

insert into mef_unit_cft(sched_cod, job_cod, unit_cod, continue_flg)
values('s1', 'job2', 'unit_test.module1', 0);
insert into mef_unit_cft(sched_cod, job_cod, unit_cod, continue_flg)
values('s1', 'job2', 'unit_test.module_w', 1);
insert into mef_unit_cft(sched_cod, job_cod, unit_cod, continue_flg)
 values('s1', 'job2', 'unit_test.module_w2', 1);
insert into mef_unit_cft(sched_cod, job_cod, unit_cod, continue_flg)
values('s1', 'job2', 'unit_test.module3', 0);
commit;

begin
   mef.pv_exec_cnt := 2;  
   mef_unit.p_start('s1','job2','unit_test.module1');
   unit_test.module1;
   mef_unit.p_end;
  
   mef_unit.p_start('s1','job2','unit_test.module_w');
   unit_test.module_w;
   mef_unit.p_end;
  
   mef_unit.p_start('s1','job2','unit_test.module_w2');
   unit_test.module_w2;
   mef_unit.p_end;  
  
   mef_unit.p_start('s1','job2','unit_test.module3');
   unit_test.module3;
   mef_unit.p_end;
end;
/
exit;

The script is similar to the previous one, changing only the calls and the setting of the continuity flag of the unit. The final result obtained, clearly shows the errors encountered at run-time from the job2 and the fact that, not being blockers, module_w2 and module3 led to term, with successful, their executions.



Test3 (fatal error)
In this third test, we show the behavior of the control system in case of fatal error. We go into SQL*Plus and we launch the unit_test_run3.sql script.

delete MEF_UNIT_CFT where JOB_COD='job3';

insert into mef_unit_cft(sched_cod, job_cod, unit_cod, continue_flg)
values('s1', 'job3', 'unit_test.module1', 0);       
insert into mef_unit_cft(sched_cod, job_cod, unit_cod, continue_flg)
values('s1', 'job3', 'unit_test.module_a', 0);
insert into mef_unit_cft(sched_cod, job_cod, unit_cod, continue_flg)
values('s1', 'job3', 'unit_test.module2', 0);
insert into mef_unit_cft(sched_cod, job_cod, unit_cod, continue_flg)
values('s1', 'job3', 'unit_test.module3', 0);  
commit;

begin
   mef.pv_exec_cnt := 3;  
   mef_unit.p_start('s1','job3','unit_test.module1');
   unit_test.module1;
   mef_unit.p_end;
  
   mef_unit.p_start('s1','job3','unit_test.module_a');
   unit_test.module_a;
   mef_unit.p_end;
  
   mef_unit.p_start('s1','job3','unit_test.module2');
   unit_test.module2;
   mef_unit.p_end;  
  
   mef_unit.p_start('s1','job3','unit_test.module3');
   unit_test.module3;
   mef_unit.p_end;
end;
/
exit;

The final result obtained, clearly shows how the module_a of the job3, having configured the continue_flg = 0, prevents the execution of the subsequent module2 and module3 units. Obviously such exception  situations were inserted automatically also in the log messages table.



Conclusions
As the messaging system, also the control system of the processing units is simple and very useful, to answer all those questions that, inevitably, we are asked in the case of problems with the loading process of the Data Warehouse. Its simplicity is based on the fact that only three steps are sufficient in an already existing code:

1. Configure the unit
2. Insert the p_start and the p_end procedure call.
3. Replace the exception-handler with the p_exc call.

Soon, we will also see some scheduling techniques.That is, based on the configuration table here described, we will launch a loading job without care to insert the p_start and p_end calls.
It will be all automatical and dynamic. We will not have a separate  main program for each job. And we will get what I theorized in the past in an my article that appeared, years ago, for Data Mart Review.
[The Infrastructural Data Warehouse]
(unfortunately the site no longer exists and has been incorporated into the Information Management site).
We will have implemented a method that, in my opinion, it is essential for an ETL process: get the clear separationbetween infrastructure code and business code of a Data Warehouse.

Monday, July 21, 2014

Letter to a Programmer (just a moment to forget our work)


I can still see his tired eyes, behind his lenses,thick two fingers.
You gave me the light, happy.
I've grown, statement after statement, with passion and pride.
Each "if" any "then" every "else", each "loop", studied for hours.
Not an ounce of fat.
I ran very fast. Without ever falling. Without a moment's pause.
I was your only one. You were the only one, my creator.

Now you're not there anymore.
I do not know where you are.
"Relocation", some call it. Others "Industry Standard"
I just know that one day, after startup, there was another person.
A stranger.
Without a soul.
He scrolled my body with boredom.
Then one day, suddenly.
Without trying to understand.
Without trying to see through me.
He mutilated me.
He started to tear pieces of my body. Necessary for You.
And I started to stumble, to slow down, to suffer.

Now around me, there are only anonymous, aseptic, redone, fat, cloned and arrogant programs.
Suitable (or deformed) programs, badly, for each situation.
They run slow, weighed down by pounds of unnecessary lines of code.
I know, my programmer, that I am still in your heart.
That you dream me.
That you have not forgotten me.
But so, without your love, without your father's eyes,
I cannot continue.
I do not want to continue.

It's time to die.
It's time to abort.

Sunday, July 13, 2014

Data Warehouse: Naming convention techniques (part 2)

Introduction

In the part 1, the application of the Naming Convention techniques had as its privileged object the tables, certainly the basic entities of a information system. We have defined a name to these entities in their easier form (table), in their aggregate form (materialized view or summary table) in their logical form (view).
It was emphasized that these techniques can be applied to any logical/physical entity of a Data Warehouse. So, I wish to complete these thoughts having in mind three targets:
 
Completeness: The tables are basic, but alone, do not constitute a Data Warehouse. There must be access rights to view them, programs must exist to load them ,must exist indexes to speed their access, there must be constraints to ensure data integrity. Even programs, rights, indexes and constraints must be created by respecting the Naming Convention. The tables are made of attributes.Even the attributes have a name. We will speak.
Pragmatism: Only seeing apply the techniques described in a real case, we can recognize the utility, then
we will examine and we will give a name to all the other entities in the game, using the sample Data Warehouse.
Knowledge: Some of the entities that will be subject to naming are specific to Oracle and this is a good
opportunity  to give them a brief description. The choices made for the Naming Convention are only guidelines. They are not a dogma. The convention can be discussed and changed according to our needs and to our particular view of the system. The main objective was to put attention to the importance and usefulness of the Naming Convention. 

Another point I wish to emphasize is that the convention is "Database Administator oriented" and not "Business oriented." It means that the names chosen, for example, for the tables, will be physical ones, and the names will be those that only the DBA sees. The "rest of the world" should not see those names,but the "logical" names  that are  filtered by synonyms and/or views.

The users of the Naming Convention

Based on some useful questions received, I want to clarify this point. Take for example the EDW_COM_CDI_CUST_DIT entity. This entity represents the customers (CUST) of the dimension table (DIT) of the conformed dimensions section (CDI) of the common area (COM) for all entities, of our Data Warehouse (EDW). Using the Naming Convention, the content of this entity appears clear to any DBA, also to who, for example, inherits the management of a Data Warehouse that does not know. (try to think if the table had been named A01DWCST).
The EDW_COM_CDI_CUST_DIT entity is seen and handled only by the DBA. In my view, the only other users who can see the entity (and only what we need, that are, usually, facts and dimensions) are the business-area builders,  by means of an administration module that is part of the front-end tool (eg Oracle Business Intelligence).
These users do not need to see the EDW_COM_CDI_CUST_DIT physical name, but a view/synonym (logical name) as,for example, CUSTOMER_DIT. If we had the foresight to make unique the last two
components of the name, the rest of the world will see an entity name much shorter, simple and near to its business logic.

The Naming Convention of the table attributes

As we know from the theory of relational databases, the table attributes are a set of specific characteristics of the various entities that define the logical model. Since we have defined a Naming Convention for the entity, it is necessary define a Naming Convention for attributes.
The paradigm that underlies the Naming Convention of the table attributes can be summarized in the following formula:

       <attribute name> = <logical name>_<type code>

For them, the name is very simplified because their logical context is already structurally defined by the table to which they belong. Into the data dictionary tables of an RDBMS such as Oracle,you can locate all the attributes and their tables associated. So an effective Naming Convention will be very useful in the research of all the attributes with certain common characteristics. Here are some examples from personal experiences.
 
In a Data Warehouse for a bank, was born the need to change the size of the currency numbers fields from two to six decimal places. The need was clearly linked to rounding problems. Hundreds of tables with different columns had to be involved in the modification. Have adopted the Naming Convention to typify all the columns of currency amounts with <logica namel>_AMT was decisive. Has allowed us to generate  a script that, accessing to the data dictionary tables ,it made dynamically the change of structure of all and only the affected columns.
 
Some ETL and reporting tools allow us to identify automatically all the descriptive columns of alphanumeric codes that will be displayed in the output: the interface of the tool will then use a clause "like" to locate the fields. If you use the standard to name the description of all the code columns with  "*_DSC, this will allow you to take advantage of this feature of the tool, and it will not need to specify one by one all the fields. Now we see some examples of the <type code>

COD - Code - Alphanumeric code: This is the classic code that is associated with a description and a domain. It may be a customer number, an order type, an account status, etc.. I suggest to deal all the numeric codes as alphanumeric codes.
DSC - Description - the code description is always the description associated with the code that is used in the reporting tools and in front-end. It is a design choice understand if only a single description is sufficient or define a short description (SDS which stands for short description) and a long description (LDS which stands for long description). It often happens that the user requires the concatenation of the short description with the code (CSD which stands for code plus short description)
AMT - Amount - Always indicates an amount.
QTY - Quantity - Indicates a quantity in pieces, weight or in some other unit of measure.
KEY - Always indicates an artificial key. A column with this type must exist in all dimension tables and in the corresponding columns of the fact tables.
DTS - Date Stamp - Date: indicates a date in the format Oracle, that is inclusive of the time (hours, minutes, seconds)
FLG - Flag - It is always a binary field, ie that it may be only 0 or 1.
TXT - Text - Field of generic text.
. YMD - Day in the YYYYMMDD format

The other entities of a Data Warehouse

Identify all the main entities or structures of a Data Warehouse, is not an easy job without forgetting that in Oracle there are over 30 different types of structures.
Eeach RDBMS has its own requirements and peculiarities and would be long-winded and useless to try to give a Naming Convention at all. So we will focus on the main entities, almost always present, leaving to the reader the application of the learned techniques for the remaining ones. Here is the list of entities, subject of our next guidelines.

• Index
• Tablespace
• Datafile
• Integrity Constraint
• Role
• Package

The Naming Convention of the indexes

As the Naming Convention is linked to the type of index, I will give a brief overview of the most common types of indexes. They typically cover 90% of the need for a Data Warehouse.
As everyone knows, the indexes are data structures that are created on one or more columns in a table to optimize the performance of access to data; the goal of an index is therefore to provide an immediate physical access to the rows of the table that contains the values. In Oracle, but there are also in other RDBMS, the indexes most used are the classic B-tree indexes, the local or global bitmap indexes, and the function index. The paradigm that underlies the Naming Convention of the indexes can be summarized in the following formula:

<index name> = <project code>_<area code>_<section code>_<logical name>_<index type>
 
The Naming Convention of the indexes will then have the same syntax of the entities, but will only change the tipology. In practice its name is identical to that of the table on which it is created except for the suffix. What follows is a list of the indexes applicable to a sales fact table. X indicates a progressive number.

  • EDW_DM0_SLS_LBx: Represents a local bitmap index.
  • EDW_DM0_SLS_GBx: Represents a global bitmap index.
  • EDW_DM0_SLS_NUx: Represents a generic btree index not unique
  • EDW_DM0_SLS_UIx: Represents a generic index btree unique
    EDW_DM0_SLS_FUx: Represents a function index

 

The Naming Convention of integrity constraints

The Integrity constraints allow us to associate some rules to the Data Warehouse tables, to order to prevent the introduction of outliers or non-compliant values.
It is needless to emphasize the importance that these rules have in the design of the system. Dispelling immediately a myth that often we hear: constraints on tables encumbers the data manipulation operations. Nothing could be further from the truth. Let's see to make things clear.
  1. Is obvious that the introduction of an integrity constraint slows down the processes of manipulation of the table, but its overhead is minimal and, as a percentage, its weight in the loading process will be negligible. I remember you, however, that the constraints can be turned off before the data loading and reactivated immediately after loading.
  2. If implemented programmatically in your application, the constraints will never be so complete, secure and manageable as those defined automatically by the RDBMS.
  3. Always enter the integrity constraints, even if the source systems are in turn RDBMS with the active constraints. Do not to trust is better: try to think of what it means to have discovered duplicate keys after loading a few months of data and be in production.
  4. The integrity constraints are necessary to activate the query rewrite in Oracle, ie its internal functionality, which is able to rewrite a query based on the fact table, and redirecting it on a materialized view. Without the integrity constraints between the fact table and its dimension table this mechanism will never work.

The paradigm that is the basis of the Naming Convention of the integrity constraints can be summarized in the following formula:

<constr. name> = <project code>_<area code>_<section code>_<logical name>_< constr.  type>
 
The following is a list of integrity constraints applicable to a fact table of sales. X indicates a progressive number.
  •  EDW_DM0_SLS_Nxx: To indicate the requirement to have always a non-null value for a field. XX is a sequential number for each column in the table that requires the constraint.
  • EDW_DM0_SLS_PK1: To indicate the primary key.
  • EDW_DM0_SLS_UKx: To specify a unique key.
  • EDW_DM0_SLS_FKx: To specify the foreign key. If you think that the number of foreign key can be higher of 9, use the convention Fxx
  • EDW_DM0_SLS_CKx: To indicate a more complex constraint based on some conditions. (for example a start date should always be prior of the end date)

 

The Naming Convention of the tablespaces

The tablespaces are logical drives that connect objects with common logical characteristics. Each table, materialized view or index always has a table space that contains, either expressed explicitly inside the script of creation, or implied, that is (the default), the tablespace of the user who created the object.
In turn each tablespace are associated with one or more datafiles. The paradigm that underlies the Naming Convention of the tablespace can be summarized in the following formula:

<tbs name> = <project code>_<area code>_<section code>_< tbs  type>

where the section code  and type code are optional; In fact, the technique to be applied in this case, is not unique, but depends on the size of the objects that constitute the tablespace. Referring to our example of the sales , we have the following:
  • EDW_COM: Tablespace of common entities. In the area that we have defined COM, there are definitely tables and indexes of little size, compared to data from other areas, so will be sufficient the project code plus the area code.
  • EDW_STA: Tablespace for temporary objects. Also in this case, the staging tables, which are only transient and of small dimensions, may stay into only one tablespace.
  • EDW_DM0_SLS: Tablespace objects from the sales data mart. If the total space occupied by these objects is limited, this may be sufficient only one tablespace. (limited,for me, is under 8 Gb). If the volumes are higher, it can be used DFT, IFT, DMT and IMT, ie fact table, index fact table , materialized view and index materialized view.
In cases of VLDW (Very Large Data Warehouse) is conceivable a tablespace for indexes, and a tablespace for the data, of each table.

The Naming Convention of the datafiles

The next considerations are valid if you are not using the Automatic Storage Management feature of Oracle.
As stated in the previous paragraph, the tablespace is made up datafile. At the time of the creation of the tablespace, you must already know about, the total space occupied by the objects that will stay in the tablespace, because you will be asked to allocate physical space. 
Let's forget about "to drive" the location of the data files on some disks of the Database Server. Now the virtualization techniques of physical space allow us to see a single disk. My advice is to divide the space occupied by the objects of the tablespace in a number of different files, of size not too high, for their better management. The paradigm that underlies the Naming Convention of the datafile can be summarized in the following formula:

<datafile name> = <tablespace name>_XX.<file type>

In this case, XX is a progressive number, the type 01,02, .., while the file type is usually fixed to DBF (Data Base File). Of course, instead of DBF you can also associate other acronyms, it is important that all the datafiles follow the same logic.

The Naming Convention of the roles

In a Data Warehouse, tables and their structures, must be aggregated to be accessible to users for data selection. I spoke at the beginning of the users of the Data Warehouse. I am aware that often the reality is more complicated, and there will always be users who access or wish to access the data directly. For this reason I speak about roles.
Provide access, means giving the grant to the entities. Because users generally have access to one or more data marts, the best way to simplify the management of access rights is to group all accesses to the data mart using roles. (When I speak about Data Mart,that is logical, I intend the fact table and the related dimension tables).
So the grant does not associate a user with a structure, but a user with a role. Appears immediately clear that the Naming Convention of the roles is closely connected to the data marts, ie with the logical partitioning at the section level . The paradigm that underlies the Naming Convention of roles can be summarized in the following formula:

<role name> = <project code>_<area code>_<section code>_<type code>

The type code may be optional, as users of the Data Warehouse will access always with "SELECT" query (I hope !); this does not mean that we cannot use "_SEL" to indicate the role of read-only access, and with "_UPD" the role of insert, update and delete. The next figure shows a summary of the techniques applied so far.


The Naming Convention of the packages

Packages are libraries of PL/SQL code. In Oracle, PL/SQL (procedural language sql) is the internal database language ,although you can write programs in Java, C, or other programming languages, callable from  PL/SQL modules. 
These modules may be procedures or functions. In Oracle, to use the package is crucial: I highly recommend that all modules necessary for the loading process are contained into packages. The advantages of their use are numerous, and I will mention only two:
  • Modularity: organize your programs in an orderly manner according to the context in which they operate is essential for anyone that work, or will work, on the project.
  • Performance: when you call a module of a package for the first once, the entire package is loaded into memory. Subsequent calls to other modules of the package doesn't require disk access.
Returning to the Naming Convention, this means that the procedure which is used to load the fact table of the sales or the aggregate monthly one, must be contained in the package that has the same name (if possible) of the target table.
If this procedure uses functions or procedures of the generic Data Mart of the sales, such a procedure should be contained in the package that has the same name of the corresponding section. The logical process will continue until reaching the common procedures to the entire Data Warehouse (for example, a function that returns me the difference of two dates for calculate the delta). Next figure shows an example of such encapsulation. 


 The Naming Convention to be adopted for the package is very flexible and may be in its most extensive form:

<pkg name> = <project code>_<area code>_<section code>_<logical name>_<type code>
as well as in its simplest form:
<project code>

The presence of the logical name and of the type code can be usable in complex systems where the number of package tends to be very high.
Do not forget that the type code must give value added to the semantics of the name. Add as _PKG type code does not create added value, as this information is obtainable from the Oracle catalog with a simple select statement.
If you decide that all modules that recall Java procedures in a certain section are within a specific package, then "_PKG" and "_JPK" will definitely  effective choices. In the case where, as in Oracle, it is not possible to have the same name for a package and a table, the use of "_PKG" Will be mandatory.

Conclusions

We have really reached the end of this short journey within the Naming Convention techniques . What is mentioned, is not certainly exhaustive of the many possible  applications of these techniques. Each of us, on the basis of own experience, can partition and can codify according to their needs and according to your own intuition. 
Indeed it is not  important the choice by which  you partition or codify the system, but it is important follow a method of standardization, in the most rigorous
way. An effective Naming Convention certainly provides all the tools necessary to keep under control soon the system, in terms of knowledge, management and maintenance

(you can download this article from slideshare: http://www.slideshare.net/jackbim/recipes-8-the-naming-convention-part-2

Saturday, June 28, 2014

Micro ETL Foundation - A messaging system for Oracle Data Warehouse

Introduction

A goal of what I have called the Micro ETL Foundation (MEF) is to provide some simple and immediate solutions to the need of a Data Warehouse.
What could be more simple (and necessary) of a log message? It may seem strange to devote an entire article to describe how to report the message "Hello world". A message, famous for all the programmers of the computer world. But we find that what appears simple is actually a bit more complicated.
If we take the first paragraph of any programming book, (eg. java) the solution is expressed with a single line:


         System.out.println (\ Hello Word! ");
 

If we use another programming language (eg. PL/SQL Oracle), the solution is again expressed in a single line:

         Dbms_output.put_line ('Hello World');
 

What I want to emphasize is the incompleteness of the received message; In fact, the complexity lies not in the message itself: at the end it is a simple function call.The complexity is the lack of context. Complexity is the metadata.
Suppose that at this time are running more loading processes. The fact of receiving a warning or alert message is not enough.
What I want to know is when the message was sent, which job was running, how much time has elapsed since the previous message, which was the current procedure at the time of the message.
We find out that answer to all these questions is no longer trivial. When we go out from theoretical examples printed on the books and enter into the reality of the daily work,everything becomes more complicated.
We will see the theory and practice, then the code, which will give us an answer to contextual needs described above.
And the solution will be (surprise!), again expressed in a single line. The programming language used is the Oracle PL/SQL (simply because it is widely used in the Data Warehouse world and then I turn to a wider audience), but the techniques exposed are easily playable on any other RDBMS.
I forgot: we also see how to ensure that the message is sent via e-mail.
This, however, is not just for programmers. It 'an article that shows all the hard work behind a simple log message. Just imagine the work that is behind the complete loading process of a big Data Warehouse.

Definitions

Naming Convention
I spent a lot of words on the importance and need to always use a naming convention for the Data Warehouse projects. I summarize the concept. 

In general, the naming convention is the method by which you choose to assign a name to the various entities of the system being designed. This method should produce a name that must be able to represent immediately the semantic nature of the entities that we will use. It does not matter if they belong to a Data Warehouse system or to an operational system.

ETL Process
The ETL process is the set of programs that load data from external systems into the Data Warehouse tables.
Since this set can be very complex, it is extremely important to have a messaging system that gives me the
most information possible about the process.

Job
A job is a logical unit that describe a very specific task, such as loading a dimension of analysis or of a fact table or both.
A set of job-related between them is in turn a job that is part of a schedule.
It is activated at a preset time. In turn, a job consists of simple and sequential processing component that we call units.

Unit
The unit is the elementary processing component (so it is code) which, in turn, can call other procedures and functions that can be defined in a generic way, modules.

Execution
A job usually runs at night, but it could also run several times a day.
Each job execution is a run that has to be identified with a sequential number, which can be defined as exec counter.
For the moment are sufficient these definitions very short. Later we will analyze in more detail.

Requirements

The requirements are very simple. To have a basic messaging system that signals everything that it is useful to
monitor the execution of the ETL process.

It is a task of the designer to decide the content of these messages, which may simply be informative, as the number of rows processed, or of particular attention, such as the identification of errors or anomalies. In these cases it should be possible to send the message via e-mail.
The system should not merely store all messages generated, but must also have the context information.
The context is described in the paragraph relative to the definition of the ETL process.
More context information we insert, and more we can control the system and we will be more efficient and faster to resolve the problems.
I suppose a minimum knowledge of Oracle and SQL and PL/SQL languages.

Design

The design of the messaging component of the Micro ETL Foundation (MEF), consists of 3 tables, 2 sequences and 1 package.
We can see them as the basic components. The minimum necessary for the other MEF components that we will use in the future. The naming convention used is a smaller version of the one to be applied to all objects of a Data Warehouse. [See http://www.slideshare.net/jackbim/recipes-6-of-data-warehouse-naming-convention-techniques].
But it would be unreasonable to apply to MEF the same logic that is used to organize the hundreds of tables typical of a large Data Warehouse. Also we can use MEF in any type of project. We will use this simplified structure:

<entity name>=<area code>_ <type name>_<logical code>

The basic package, being common to all, will be further simplified by eliminating the <logical name>.
The area code is "MEF".
The table MEF_CFT is the table of system configuration.
The table MEF_EMAIL_CFT is the specific configuration table for e-mail addresses, the table MEF_MSG_LOT is one that will keep the text messages.
The sequence MEF_MSG_SEQ gives a sequence number to each message.
The sequence MEF_RUN_SEQ is used to numerically identify each job execution.
The Oracle package MEF is a library of programs that manages those objects.
All scripts for creating objects, can be downloaded from slideshare that show in a pragmatic and fun way the system [http://www.slideshare.net/jackbim/recipe-7-of-data-warehouse-a-messaging-system-for-oracle-dwh-2].
The scripts are very minimal, with only the main structures, leaving the reader with the completion of all other
accessory structures such as indexes, constraints, etc.. 

The tables will be created in the default tablespace, but you should always create ad-hoc tablespace.

The MEF_CFT table

This table contains general information about the MEF and the Data Warehouse.Other columns will be added in the future (or on your choice)

prj_cod: Code of the Data Warehouse project


user_cod: name of the Oracle user for ETL.


email_srv_cod: E-mail Server. Every corporate has always a server for managing e-mail, indicate here that server.


mef_root_txt: Path of the folder of MEF scripts.


mef_dir: Oracle directory pointing to mef_root_txt

The MEF_MSG_LOT table

This table stores all log messages that are sent by loading process.

seq_num: Sequential number of the message. It is obtained from an Oracle sequence.


day_cod: Time stamp of the message insert in the format YYYYMMDD.


sched_cod: The identifier for the schedule to which the job belongs.


job_cod: job identifier. It is a logical entity in the sense that we think of it as the launch of a list of processing units.


unit_cod: Identifier of the processing unit within the job. We can think of it as a procedure or a function of the Oracle package.


module_cod: module Identifier. A unit, though complex, can in turn call the sub-routine or subfunctions, ie modules. In this case, it is interesting to know this detail.


rows_num: Number of rows processed. Typically, this field is not set, but if we want to report the number of rows, for example, inserted into a table, we also have this information.


line_txt: Message text


cline_txt: Message text in CLOB type.


ss_num: Number of seconds that have elapsed since the previous message. This information, together with the next two, provides a summable data. If we wanted to know how long it took all the statements of a certain kind, we would be able to calculate.


mi_num: Number of minutes that have elapsed since the previous message


hh_num: Number of hours that occurred since the previous message


elapsed_txt: Time elapsed since the previous message in the format HH24:MI:SS


stamp_dts: Time stamp insertion of the message.


exec_cnt: The identifier for the execution of the job. Every run of a job should be characterized by a number,
in turn, extracted from an Oracle sequence.


user_cod: Oracle user who posted the message. It is setted automatically by a session variable. It can be useful in cases where multiple users contribute to the loading process(not recommended)

The MEF_EMAIL_CFT table

This table configures the email addresses.

email_cod: Code to identify a group of recipients.


from_txt: Sender of the message. This name will appear as the sender of the e-mail message. Do not use special characters, nor the blank between words. Eg. not set "Administrator ETL" but "Amministratore_ETL"  otherwise you get a run-time error message like:


ORA-29279: SMTP permanent error: 501
5.5.4 Invalid arguments 


to_txt: Identifier of the message recipient
cc_txt: This e-mail address of the recipient in knowledge
subj_txt: Subject default message
status_cod: Status (1 = active, 0 = inactive) of the recipient
 

The MEF_MSG_SEQ sequence

The sequence is an Oracle object. In practice it is a universal counter that increments each time you request it.
Each message line must have its sequential number. It is more functional than time stamp to sort the table.
Because sometimes the messages are separated at a fraction of a second of each other, the time stamp might not be sufficiently discriminating.

The MEF_RUN_SEQ sequence

Sequence that indicates unambiguously, a run of a job.

The MEF package

This is the basic package. I suggest to develop all the code inside PL/SQL packages (they are basically libraries), which allow a better management and use of the code.
Now a short description of the units contained in the package.


f_str: Utility function to generate a string after replacing the input variables.


p_ins_msg_lot: This procedure perform the insertion of the message in the MEF_MSG_LOT table  receiving a variable of row type as input parameter. This procedure has the "pragma autonomous_transaction". It is very important and requires a thorough description.
It seems incredible, but if there were not, it would not be possible this messaging system. The pragma autonomous_transaction allows us to commit (i.e. to validate into the database) only and exclusively the DML statements of the unit which contains this compiler directive. This concept is crucial because it allows us to commit (so to insert data into the database) without affecting the logic of the loading process.
Let's clarify with an example.
Typically, before to loading the daily data into a table, you delete, first of all, the data of that day, and then load/reload the new data. (forget for a moment the partition manipulation)
You do the commit at the end, where the loading, (delete/insert), was successfully completed. If i ran a commit after the delete and the insert has a problem, I could have the loss of data of the day.
As the messaging needs to do a commit of the message in the table, the execution of the innocent message "I have done the delete", would validate even the delete itself. And this is a side effect not acceptable.
The autonomous transaction solve the problem: the PL/SQL Oracle engine, produce a "daughter" transaction
who live an autonomous life, which validates the data in the MEF_MSG_LOT table without affecting the logic of the parent transaction.


p_rae: The management of the exception is standardized in the following manner. When any Oracle error happen, in the "when others" instruction there is the call of the p_rae procedure that enriches the content of the error with other useful information,such as, for example, where the exception occurred. The output will always be the standard error pv_error. The "when pv_error" is used to make sure that you keep the original error. 


p_init: Other private procedure. It initializes the line_row variable to perform the insert in the table.


delta_time: Procedure that, based on the input parameters, such as the date of the last message and the current date , calculates all the delta-time information, how many seconds, minutes, hours have passed and a delta time in the 'HH24:MI:SS' format. The ways in which we can calculate the delta time are numerous: one used is just one of many.


f_get_seq_val: Function that extracts generalized, in a dynamic way, the next number of the Oracle sequence whose name is given in the parameter. 


f_get_exec_cnt: Function that extracts the execution number of the job. Before calling the generic function
f_get_seq_val it verify that has not already been set as a global variable: in this case, use the number of the current execution.


f_get_cft: Function that extracts the current configuration from the MEF_CFT table.


p_esend: A function that performs the sending of email via the UTL_MAIL package.

p_send: This is the procedure that sends the message.


p_mail: Procedure for sending the e-mail. Using the email code in inputit looks for all the recipients in the MEF_EMAIL_CFT table and calls the p_esend passing all required parameters.

System configuration

Before you can create all the structures described above, you must perform some environment check. First of all it is necessary to verify that the Oracle RDBMS is designed for sending emails.
So you have to make sure that the Oracle user that sends the messages has all grant necessary for its operations.
Let's see in detail.

SMTP check
We need to verify that the Oracle RDBMS is designed for sending emails. In fact the sending is activated by calling a procedure that is part of a package of the RDBMS. To verify this, connect to SQL * Plus with SYS user and check the package UTL_MAIL (from Oracle 11):


Sqlplus / as sysdba
SQL> descr utl_mail
ERROR:
ORA-04043: object utl_mail does not exist


If you get this error message, you need to install the package system UTL_MAIL and give the execute permission to the user.
Always as user SYS, run the script to install the package from the rdbms/admin folder of the Oracle home, and give the execution permissions to the ETL user.
So you have to check the smtp server in the Oracle initialization parameter file; in the following example it is not setted and I indicate you how to do it. Here is the sequence of instructions for making these checks (remember to replace the file path of the utlmail.sql file with the relative path to your Oracle installation):


D:\>sqlplus / as sysdba

SQL*Plus: Release 11.2.0.1.0 Production on Wed Jun 25 15:35:49 2014

Copyright (c) 1982, 2010, Oracle.  All rights reserved.

Connected to:
Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - Production
With the Partitioning option

SQL> @...\RDBMS\ADMIN\utlmail.sql
SQL> @...\RDBMS\ADMIN\prvtmail.plb
Package created.
Synonym created.

SQL> grant execute on UTL_MAIL to <ETL user>;
Grant succeeded.
SQL> sho parameters smtp
NAME TYPE VALUE
------------------------------------ ----------- -------
smtp_out_server string

SQL> alter system set smtp_out_server = <email server> scope=both;
System altered.

 
Almost always, in the company there is a mail server, the value should be include the domain, for example. exch.dev.com. The option "scope = both" make the change effective immediately and permanently.
We must also create and configure an ACL (from Oracle11) using the system package dbms_network_acl_admin.
The ACL (Access Control List) is only one way to define external resources to the RDBMS (such as email servers) and allow access to users.
Now open slideshare [http://www.slideshare.net/jackbim/recipe-7-of-data-warehouse-a-messaging-system-for-oracle-dwh-2] where you will find all the instructions on how to download and run the installation script of this messaging system.
The script will execute all the necessary settings for you. All this will take no longer than 5 minutes of work.

Test

In addition to the tests showned in the slides of slideshare, we perform another test now, certainly more exhaustive,  which clearly shows the functionality simulating a piece of ETL load.
We start by creating a test table, initializing it from a system table. At this point we run an anonymous block of code (anonymous block is defined as the set of SQL statements included between a begin and an end) that will simulate a real load of a table with insert and delete of data. The sequence of steps is quite simple: you initialize global variables package to better identify the various steps in the messaging table.

create table SALES as select * from tabs;
begin
   mef.pv_sched_cod := 'Daily';
   mef.pv_job_cod := 'Staging tables';
   mef.pv_unit_cod := 'Load sales table';
   mef.pv_exec_cnt := 10;

   mef.p_send('proc_prova','Load of SALES table');
   mef.p_send('proc_prova','Deleting...');
   delete from sales;
   mef.p_send('proc_prova','Deleted');
   mef.p_send('proc_prova','Loading...');
   insert into sales select * from tabs;
   mef.p_send('proc_prova','Loaded');
   mef.p_mail('MEF','ETL_administrator','Sales table loaded');
   mef.p_send('proc_prova','Load ended');
end;
/


The final result obtained, which can be seen in the table MEF_MSG_LOT, it is very interesting, and gives you the wealth of contextual information that is talked about in the beginning.

Conclusion

We saw in detail the steps required to build a messaging system, simple, but very useful for all the people working in the Data Warehouse projects.
This implementation, which is the basis of my Micro-ETL-Foundation, obviously works on any Oracle-PL/SQL project, is non-invasive, and can be applied at any time inserting simple procedure calls in an existing ETL process.

Wednesday, May 28, 2014

How to send mail with attach using Oracle pl/sql

In a Data Warehouse is very important to use e-mail to report the results of the processing and any  errors.  Oracle provides a package UTL_MAIL (from 10g) for this task.
                              
This package, however, can not be used immediately, but must be verified a number of conditions.        
Because I love to "see" the solutions in a graphical format, you can download the solution from www.slideshare.net
 

Inside it you can see, in a single slide, the solution and all the "objects" involved.                  
After making the controls in the slide, you can try the solution immediately in this way.

               
1) Copy and paste the following script in a file, for example, OAiO1.sql    


spool OAiO1

grant connect,resource,dba to &&1 identified by &&2;

grant read,write on directory DATA_PUMP_DIR to &&1;

begin
   dbms_network_acl_admin.drop_acl(
   acl => '&&3');
   commit;
end;
/
begin   
   dbms_network_acl_admin.create_acl (
   acl         => '&&3',
   description => 'Allow mail to be send',
   principal   => '&&1',
   is_grant    => TRUE,
   privilege   => 'connect');
   commit;
end;
/
begin   
   dbms_network_acl_admin.assign_acl(
   acl  => '&&3',
   host => '&&4');
   commit;
end;
/
begin  
   dbms_network_acl_admin.add_privilege (
   acl       => '&&3',
   principal => '&&1',
   is_grant  => TRUE,
   privilege => 'resolve'
   );
   commit;
   end;
/
connect &&1/&&2

create or replace procedure p_email(
   p_sender varchar2
   ,p_recipients varchar2
   ,p_subject varchar2
   ,p_message varchar2
   ,p_dir varchar2 default null
   ,p_file varchar2 default null) is
   v_fh utl_file.file_type;
   v_rfile raw(32767);
   v_flen number;
   v_bsize number;
   v_ex boolean;
begin
   if (p_dir is null) then
      utl_mail.send(
      sender => p_sender
      ,recipients => p_recipients
      ,subject => p_subject
      ,message => p_message
      );
   else
      utl_file.fgetattr(p_dir, p_file, v_ex, v_flen, v_bsize);
      v_fh := utl_file.fopen(p_dir, p_file, 'r');
      utl_file.get_raw(v_fh,v_rfile, v_flen);
      utl_file.fclose(v_fh);
      utl_mail.send_attach_raw(
      sender => p_sender
      ,recipients => p_recipients
      ,subject => p_subject
      ,message => p_message
      ,attachment => v_rfile
      ,att_inline => FALSE
      ,att_filename => p_file
      );
   end if;
end;
/

exec p_email('&&7','&&6','OAiO1_Test','Hello world from user &&1', 'DATA_PUMP_DIR', 'dp.log');

spool off


2) The script could create a new test user. For this example we will use the "historical" user SCOTT.
3) Connect to SQL from command prompt with sqlplus / as sysdba
4) Run the command passing your personal setting:


@OAiO1 <&&1> <&&2> <&&3> <&&4> "<&&5>" "<&&6>" <&&7>

where:
&&1 = Oracle User that send the email (uppercase, e.g. SCOTT)
&&2 = Oracle User password (e.g. tiger)
&&3 = An ACL name of your choice (e.g. scott_mail.xml)
&&4 = Email Server of your Company (e.g.  Acme.ita.com)
&&5 = Oracle Home path (e.g. E:\o\s\product\11.2.0\dbhome_1)
&&6 = Email receiver (e.g. massimo_cenci@yahoo.it)
&&7 = User sender (e.g. DWH_dba)


It is all.