Identifier sa session

En PL/SQL

exec sys.dbms_session.set_identifier          ('identifiant de session')
exec sys.dbms_application_info.set_client_info('info client')
exec sys.dbms_application_info.set_module     ('module', 'action')

Eventuellement, si l’on veut ne changer que l’action

exec sys.dbms_application_info.set_action('action')

En PHP

<?php 
 oci_set_client_identifier($db , 'identifiant de session'); 
 oci_set_client_info      ($db , 'info client'); 
 oci_set_module_name      ($db , 'module'); 
 oci_set_action           ($db , 'action'); 
?>

Retrouver ces informations

select sys_context('USERENV', 'CLIENT_IDENTIFIER') as "client identifier"
     , sys_context('USERENV', 'CLIENT_INFO') as "client info"
     , sys_context('USERENV', 'MODULE') as module
     , sys_context('USERENV', 'ACTION') as action
from sys.dual ;

Avec un peu de mise en forme

SQL> col "client identifier" for a25
SQL> col "client info" for a17
SQL> col module for a17
SQL> col action for a17

SQL> /

client identifier         client info       MODULE            ACTION
------------------------- ----------------- ----------------- -----------------
identifiant de session    info client       module            action

C’était pourtant pas si compliqué

PS: On peut aussi le faire en java ou en .Net si tant est qu’on utilise encore des trucs aussi dépassés ou encore en C via la bibliothèque OCI1 si l’on est résolument moderne. Tout cela est détaillé dans l’excellent Troubleshooting Oracle Performance de Christian Antognini paru chez APress au chapitre 2 pages 45 à 48. Et de manière plus cryptique et éparpillée dans la documentation de l’éditeur.


1. OCI pour Oracle Call Interface, livré par Oracle avec tous les “clients”

Nombre de colonnes variable et PL/SQL

Ce que je veux faire

Dans mon post précédent, j’ai créé une fonction qui me permet d’afficher sous forme de tableau à deux dimensions diverses informations tirées d’une table. Nommée pompeusement transposition, le requête permet entre autres d’afficher en ligne ou en colonne, le topn sql_id consommateurs sur un wait event et leur évolution sur un certain nombre de clichés.

Maintenant, je voudrais exploiter ces données dans du code PL/SQL, j’ai donc besoin de créer du code qui saura s’adapter au nombre de colonnes remontées. Un premier code permet d’afficher les valeurs

declare
  requete  clob;
  nb_cols  number;
  tab_cols t_varchar;
  auto_plsql clob;
  ttext    varchar2(32767);
begin
  p_trsp('sql_id'                               
       , 'snap_id'
       , 'dba_hist_sqlstat'
       , 'EXECUTIONS_DELTA'
       , 'sys'
       , '834'
       , '840'
       , 18
       , 'yes'
       , requete
       , nb_cols
       , tab_cols);
  auto_plsql :='begin 
for c in (';
  dbms_lob.append(auto_plsql,requete);
  ttext:=')
loop 
dbms_output.put(to_char(c.snap_id)||'' '');
';
  dbms_lob.writeappend(auto_plsql,length(ttext), ttext);
  for i in 1..nb_cols
  loop
    ttext:='dbms_output.put(to_char(c.c_'||trim(to_char(i))||',''9999'')||'' '');
'; 
    dbms_lob.writeappend(auto_plsql,length(ttext), ttext);
  end loop;
  ttext:='dbms_output.put_line('' '');
end loop;
end;'; 
  dbms_lob.writeappend(auto_plsql,length(ttext), ttext);
  execute immediate auto_plsql;
end;
/

Ce dui donne :

834     0  1803  1730  1430  1024     0   414     0     0   339   312   298   269     0   318   213   209   256  
835  2085  1493  1316   905   952   723   291   714     0   229   234   233   232   241   184   187   181   161   
836  2068  1490  1308   862   944   720   291   708   708   229   228   228   228   241   175   184   180   134   
837  1962  1466  1265   870   892   712   292     0     0   228   228   228   228   237   170   181   176   133   
838  1644  1490  1096   700   732   718   291     0     0   229   231   231   231   241   175   184   172   132   
839  1909  1494  1275  1064   864   721   328   648   648   229   235   233   231   241   193   184   178   154   
840  2028  1490  1288   858   924   723   291     0   693   230   231   231   231   241   175   184   180   133   

Et si on limite topn à 4 (donc 4 colonnes + snap id) on obtient :

834     0  1803  1730  1430
835  2085  1493  1316   905
836  2068  1490  1308   862
837  1962  1466  1265   870
838  1644  1490  1096   700
839  1909  1494  1275  1064
840  2028  1490  1288   858

Et si on va un poil plus loin pour traiter le résultat des colonnes dans du PL/SQL …

CREATE OR REPLACE
PACKAGE OJO_RPT
AS
PROCEDURE report(
    debut IN varchar2,
    fin   IN varchar2,
    topn  IN NUMBER);
END OJO_RPT;
/

CREATE OR REPLACE
PACKAGE body OJO_RPT
AS
-- private proc
type t_varchar is table of varchar2(1000);

procedure p_trsp   ( lig   in varchar2         -- Colonne qui sera utilisee en tete de ligne 
         , col    in varchar2         -- Colonne qui sera utilisee en tete de colonne
         , ltab   in varchar2         -- Table source 
         , pivot  in varchar2         -- Colonne qui sera utilisee pour remplir le tabeau
         , owner  in varchar2         -- Proprietaire de la table
         , mini   in varchar2         -- Valeur minimale pour col
         , maxi   in varchar2         -- Valeur maximale pour col
         , topn   in number           -- Nombre de lig affichees ordonnes par cumul de pivot descendant
         , trsp  in varchar2 := 'No'  -- Transposition de la table (lig en tete de colonne et col en tete de ligne)
                  , req   out clob
                  , ncol  out number
                  , t_ncl out t_varchar ) is
 v_col    varchar2(32767);  
 v_where  varchar2(32767);  -- clause where pour mini et maxi
 v_wtopn  varchar2(1000);   -- clause where pour topn
 v_owner  varchar2(255);    
 c1  sys_refcursor;    
 v_poreq  varchar2(32767);  
  i               number;
begin
        t_ncl:=t_varchar();
 -- Gestion de la variable topn, si null on prend tout
 if topn is null
 then
   v_wtopn:=' ';
 else
   v_wtopn:=' where rownum <= '||topn;
 end if;

 -- Gestion de la variable owner, si null on prend ls schema appelant 
 if owner is null
 then 
  select sys_context('USERENV','CURRENT_SCHEMA') into v_owner from sys.dual;
 else
  v_owner:=owner;
 end if;
 
 -- Gestion des variables mini et maxi, si null on prend tout
 if ( mini is not null or maxi is not null )
 then
  v_where:=' where ';
  if mini is not null 
  then
   v_where:=v_where||col||' >
= '||mini ;
   if maxi is not null
   then
       v_where:=v_where||' and '||col||' <= '||maxi ;
   end if;
  else
      v_where:=v_where||col||' <= '||maxi ;
  end if;
 end if;
 
    -- Si on ne transpose pas lig et col  
 if upper(trsp) = 'NO'
 then
   req:='select * from (Select '||lig||' c_0
, sum('||pivot||') total ';
          i:=1;
   open c1 for 'select distinct to_char('||col||') from '||v_owner||'.'||ltab||v_where||' order by 1' ;
   loop
    fetch c1 into v_col;
    exit when c1%notfound;
    v_poreq:='
   , sum(decode(to_char('||col||'),'''||v_col||''','||pivot||',0)) c_'||trim(to_char(i)) ;
    dbms_lob.writeappend(req,length(v_poreq),v_poreq);
                  t_ncl.extend();
                  t_ncl(i):=trim(to_char(v_col));
                  i:=i+1;
   end loop;
          ncol:=c1%rowcount;
          close c1;
   v_poreq:='
from '||v_owner||'.'||ltab||v_where||'
group by '||lig||'
order by 2 desc)'||v_wtopn ;
   dbms_lob.writeappend(req,length(v_poreq),v_poreq);

    -- Si on transpose lig et col  
 else
   req:='Select '||col||' c_0';
          i:=1;
   open c1 for 'select ent from (select distinct to_char('||lig||') ent, sum('||pivot||') tot from '||v_owner||'.'||ltab||v_where||' group by to_char('||lig||') order by 2 desc)'||v_wtopn ;
   loop
     fetch c1 into v_col;
     exit when c1%notfound;
     v_poreq:='
   , sum(decode(to_char('||lig||'),'''||v_col||''','||pivot||',0)) c_'||trim(to_char(i)) ;
     dbms_lob.writeappend(req,length(v_poreq),v_poreq);
                          t_ncl.extend();
                          t_ncl(i):=trim(to_char(v_col));
                          i:=i+1;
   end loop;
          ncol:=c1%rowcount;
   close c1;
   v_poreq:='
from '||v_owner||'.'||ltab||v_where||'
group by '||col||'
order by 1 asc';
   dbms_lob.writeappend(req,length(v_poreq),v_poreq);
 end if;        
end p_trsp;

procedure trt( t in t_varchar ) is
  vt varchar2(32767);
  c  sys_refcursor;
begin
  open c for 'select * from table(t)';
  loop
    fetch c into vt;
    exit when c%notfound;
    dbms_output.put_line(vt);
  end loop;
end trt;


-- public proc
PROCEDURE report(
    debut IN varchar2,
    fin   IN varchar2,
    topn  IN NUMBER) is
  requete  clob;
  nb_cols  number;
  tab_cols t_varchar;
  auto_plsql clob;
  ttext varchar2(32767);
begin
  for c0 in (select column_name from dba_tab_columns where table_name='DBA_HIST_SQLSTAT' and owner='SYS' and column_name like '%DELTA')
  loop
  p_trsp('sql_id'                               
       , 'snap_id'
       , 'dba_hist_sqlstat'
       , c0.column_name
       , 'sys'
       , debut
       , fin
       , topn
       , 'no'
       , requete
       , nb_cols
       , tab_cols);
  auto_plsql :='begin 
for c in (';
  dbms_lob.append(auto_plsql,requete);
  ttext:=')
loop 
dbms_output.put(to_char(c.c_0)||'' '');
';
  dbms_lob.writeappend(auto_plsql,length(ttext), ttext);
  for i in 1..nb_cols
  loop
    ttext:='dbms_output.put(to_char(c.c_'||trim(to_char(i))||',''99999999'')||'' '');
'; 
    dbms_lob.writeappend(auto_plsql,length(ttext), ttext);
  end loop;
  ttext:='dbms_output.put_line('' '');
end loop;
end;'; 
  dbms_lob.writeappend(auto_plsql,length(ttext), ttext);
  dbms_output.put_line(c0.column_name);
  execute immediate auto_plsql;
  end loop;
end report;

END OJO_RPT;
/

Le résultat de cette requête étant une succession de tableaux, dont je donne ici les trois premiers :

FETCHES_DELTA
cm5vu20fhtnq1      6549      5494      5490      5401      5490      5497      5490
8t43xdhf4d9x2         0      2085      2068      1962      1644      1909      2028
0k8522rmdzg4k      1976      1604      1600      1575      1600      1606      1600
089dbukv1aanh      1730      1316      1308      1265      1096      1275      1288
aykvshm7zsabd         0         0      2640      2618         0         0         0
5ms6rbzdnq16t         0       723       720       712       718       721       723
83taa7kaw59c1      3587         0         0         0         0         0         0
5kyb5bvnu3s04       414       291       291       292       291       328       291
7qqnad1j615m7         0       714       708         0         0       648         0
5hfunyv38vwfp         0         0       708         0         0       648       693
END_OF_FETCH_COUNT_DELTA
8t43xdhf4d9x2         0      2085      2068      1962      1644      1909      2028
cm5vu20fhtnq1      1803      1493      1490      1466      1490      1494      1490
089dbukv1aanh      1730      1316      1308      1265      1096      1275      1288
8vwv6hx92ymmm      1430       905       862       870       700      1064       858
6v7n0y2bq89n8      1024       952       944       892       732       864       924
5ms6rbzdnq16t         0       723       720       712       718       721       723
5kyb5bvnu3s04       414       291       291       292       291       328       291
7qqnad1j615m7         0       714       708         0         0       648         0
5hfunyv38vwfp         0         0       708         0         0       648       693
43c5ykm1mcp2a       312       234       228       228       231       235       231
SORTS_DELTA
5ms6rbzdnq16t         0       723       720       712       718       721       723
fsbqktj5vw6n9       213       187       184       181       184       184       184
4jrfrtx4u6zcx         0       122       122       128       122       150       122
aykvshm7zsabd         0         0       360       357         0         0         0
db78fxqxwxt7r       447         0         0         0         0         0         0
btwkwwx56w4z6         0        60        60        59        60        60        60
75u022kdra1fz         0        60        60        59        60        60        60
7ng34ruy5awxq       338         0         0         0         0         0         0
grb1cf30q5r7m       314         0         0         0         0         0         0
gvt8zu1k1tsff         0       122         0         0         0       150         0

Rendre dbms_metadata lisible

DBMS_METADATA lisible, comment faire ?

Le package dbms_metadata existe depuis la version 9i et permet de regénerer les commandes DDL relatives aux objets ORACLE … Maintenant, pour l’utilisation et la liste des focntion au choix : RTFM ou RTFDB (DB pour Donald B le bien connu copiste en chef de la documentation Oracle) ce qui, au final revient à peu près au même. Cependant, souvent sa sortie contient des informations dont on se passerait bien, comme la gestion des extents ou autres alors qu’on utilise la plupart du temps les paramètres par défaut du tablespace.

Deux trois petites astuces bien sympa à retenir

  • Ne jamais faire un dbms_metadata sur une liste d’objets (si un objet plante, les suivants ne sont pas traités)
  • Utiliser long 9999999 et longc 9999999
  • Utiliser des lignes larges (300 à 400 caractères, on peut aller jusqu’à 32768 soit 32k) et un “trimspool on” s’il on choisit de rediriger la sortie dans un fichier.
  • Demander la mise en forme avec les terminaisons SQL ( / ou ; )
    exec dbms_metadata.set_transform_param( DBMS_METADATA.SESSION_TRANSFORM, 'SQLTERMINATOR', TRUE ); 
  • Eviter les paramètres de stockage afin d’utiliser ceux du tablespace :
    exec dbms_metadata.set_transform_param( DBMS_METADATA.SESSION_TRANSFORM, 'STORAGE', false); 

L’exemple suivant donne le DDL de la table WRH$_SQL_PLAN dans ses deux versions

Comportement par défaut de DBMS_METADATA

select dbms_metadata.get_ddl('TABLE','WRH$_SQL_PLAN',USER) from sys.dual ;
DBMS_METADATA.GET_DDL('TABLE','WRH$_SQL_PLAN',USER)
--------------------------------------------------------------------------

  CREATE TABLE "SYS"."WRH$_SQL_PLAN"
   (    "SNAP_ID" NUMBER,
        "DBID" NUMBER NOT NULL ENABLE,

        [...]
        
        "OTHER_XML" CLOB,
         CONSTRAINT "WRH$_SQL_PLAN_PK" PRIMARY KEY ("DBID", "SQL_ID", "PLAN_HASH_VALUE", "ID")
  USING INDEX PCTFREE 10 INITRANS 2 MAXTRANS 255 COMPUTE STATISTICS
  STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
  PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1
  BUFFER_POOL DEFAULT FLASH_CACHE DEFAULT CELL_FLASH_CACHE DEFAULT)
  TABLESPACE "SYSAUX"  ENABLE
   ) PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255
 NOCOMPRESS LOGGING
  STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
  PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1
  BUFFER_POOL DEFAULT FLASH_CACHE DEFAULT CELL_FLASH_CACHE DEFAULT)
  TABLESPACE "SYSAUX"
 LOB ("OTHER_XML") STORE AS BASICFILE (
  TABLESPACE "SYSAUX" ENABLE STORAGE IN ROW CHUNK 8192 RETENTION
  NOCACHE LOGGING
  STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
  PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1
  BUFFER_POOL DEFAULT FLASH_CACHE DEFAULT CELL_FLASH_CACHE DEFAULT))

DBMS_METADATA lisible, ou du moins beaucoup moins inutilement chargé

Paramétrage

exec dbms_metadata.set_transform_param( DBMS_METADATA.SESSION_TRANSFORM, 'SQLTERMINATOR', TRUE );

PL/SQL procedure successfully completed.

exec dbms_metadata.set_transform_param( DBMS_METADATA.SESSION_TRANSFORM, 'STORAGE', false);

PL/SQL procedure successfully completed.

sortie DBMS_METADATA lisible

select dbms_metadata.get_ddl('TABLE','WRH$_SQL_PLAN',USER) from sys.dual ;
DBMS_METADATA.GET_DDL('TABLE','WRH$_SQL_PLAN',USER)
--------------------------------------------------------------------------

  CREATE TABLE "SYS"."WRH$_SQL_PLAN"
   (    "SNAP_ID" NUMBER,
        "DBID" NUMBER NOT NULL ENABLE,

        [...]

        "OTHER_XML" CLOB,
         CONSTRAINT "WRH$_SQL_PLAN_PK" PRIMARY KEY ("DBID", "SQL_ID", "PLAN_HASH_VALUE", "ID")
  USING INDEX PCTFREE 10 INITRANS 2 MAXTRANS 255 COMPUTE STATISTICS
  TABLESPACE "SYSAUX"  ENABLE
   ) PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255
 NOCOMPRESS LOGGING
  TABLESPACE "SYSAUX"
 LOB ("OTHER_XML") STORE AS BASICFILE (
  TABLESPACE "SYSAUX" ENABLE STORAGE IN ROW CHUNK 8192 RETENTION
  NOCACHE LOGGING ) ;

Générer des rapports statspack

Petite procédure pour générer un nombre important de rapports statspack.

SET SERVEROUTPUT OFF
set feedback off

var delta number;
var date_fin varchar2(20);
begin
:delta:=&1;
--  :date_fin:=&2;
end;
/

create or replace procedure dba_all_reports( delta in number
                                         , fin in varchar default to_char(sysdate, 'YYYYMMDDHH24MI') ) as
begin
for i in (select snap_id
        , snap_next
        , to_char(snap_time, 'MMDD_HH24MI') rpt_name
        , instance_name
      from (select s.SNAP_ID
              , lead(s.SNAP_ID,1) over (order by s.SNAP_ID) SNAP_NEXT
              , s.STARTUP_TIME
              , lead(s.STARTUP_TIME, 1) over (order by s.SNAP_ID) STARTUP_NEXT
              , s.SNAP_TIME
              , i.INSTANCE_NAME
            from stats$snapshot s, v$instance i)
      where SNAP_NEXT=SNAP_ID+1
      and STARTUP_TIME=STARTUP_NEXT
      and snap_time between to_date(fin,'YYYYMMDDHH24MI')-delta and to_date(fin,'YYYYMMDDHH24MI') )
loop
DBMS_OUTPUT.ENABLE (32000);
dbms_output.put_line ('define begin_snap='||i.snap_id);
dbms_output.put_line ('define end_snap='||i.snap_next);
dbms_output.put_line ('define report_name='||i.instance_name||'_'||i.rpt_name);
dbms_output.put_line ('@?/rdbms/admin/spreport.sql');
end loop;
end;
/

SET SERVEROUTPUT ON
spool reports.sql
-- execute dba_all_reports(:delta, :date_fin)
execute dba_all_reports(:delta)
spool off

@reports.sql

drop procedure dba_all_reports;

Les rapports ainsi générés sont nommés <ORACLE_SID>_<MOIS><JOUR>_<HEURE><MINUTES>.lst

On peut décommenter les lignes

--  :date_fin:=&2;

et

-- execute dba_all_reports(:delta, :date_fin)

et supprimer la ligne

execute

dba_all_reports(:delta)

de manière à générer des rapports anciens.