L’affichage d’un plan d’exécution de manière graphique est toujours appréciable et le web propose pléthores d’API pour le faire. Parmis elles google visualization qui permet entr’autres choses de faire de jolis graphiques et d’afficher de manière simple des organigrammes ou pourquoi pas des plans d’exécution de requêtes SQL.
On peut trouver les plans d’exécution dans diverses tables de la base de donnée. Ici, j’ai choisi d’afficher un plan d’exécution pioché dans la table dba_hist_sql_plan et donc sort d’un historique AWR. Je pré suppose que je connais le SQLID et la valeur de hashage du plan à afficher (plan hash value).
Si on s’intéresse à la brique orgchart de google visualization API on s’apperçoit qu’il suffit de lui donner un tableau javascript contenant au minimum des identifiants uniques dans la première colonne et l’identifiant du pêre dans une deuxième colonne pour que l’arbre s’affiche : de jolies cases bleues contant les identifiants uniques … Ok, ce n’est pas transcendant, mais c’est déjà un premier pas : pas besoin de requête hyérarchique pour afficher une hyérarchie et comble de bonheur le travail est partagé entre la base de donnée et le client
Orgchart la brique que l’on utilise
Comme je le disais plus haut, cette brique s’appuie sur un tableau, les deux premières colonnes décrivent affichage et hyérachie, la troisième peut être utilisée pour renseigner le contenu de bulles contextuelles qui s’afficheront lors du passage de la souris sur une case de notre organigramme / plan d’exécution. Si l’outil n’utilise que ces trois colonnes d’autres peuvent être renseignées pour une utilisation ultérieure. Mais avant de remplir notre tableau, préparons déjà notre page html / javascript
Appeler google vsualization dans une page web
Rien de plus simple, l’appel se situe dans l’en-tête (non affichée) d’une page html :
<script type="text/javascript" src="http://www.google.com/jsapi"></script> <script type="text/javascript">google.load('visualization', '1', {packages: ['orgchart']})</script>
Et c’est tout l’interface de programmation n’a plus qu’à être utilisée.
Afficher le tableau
La commande d’affichage du tableau est une fonction javascript définie dans google visualization
new google.visualization.OrgChart(document.getElementById('visualization')).draw(<b>data</b>, <b>{allowHtml: true, size: 'small'}</b>)
On note ici en gras data, qui est le tableau que nous n’avons pas défini encore et un petit objet json qui liste les options que je souhaite appliquer à mon organigramme / plan d’exécution. La liste complète des options est disponible dans la documentation du module orgchart, seul bémol, c’est en anglais.
Alimenter notre tableau pour afficher un plan d’exécution.
La fameuse documentation décrite quelques citée quelques lignes plus haut nous indique que la première colonne (celle qui pilote l’affichage, souvenons-nous) peut contenir un objet json contenant deux champs :
- L’identifiant unique sous la forme v: ‘identifiant unique’
- Le texte à affiche sous la forme f: ‘texte à afficher’
En outre, la première ligne du tableau doit contenir les en-têtes de colonnes et j’ai pu constater que la première ligne de valeurs du tableau ne peut pas comporter d’objet json. On crée donc le taleau suivant :
[[ 'id','parent','bloc'] ,['1316798873',null,null] ,[{v: '0', f: 'SELECT STATEMENT cvhk2j2gymzhd'},'1316798873',''] ,[{v: '1', f: 'NESTED LOOPS '},'0','SEL$1'] ,[{v: '2', f: 'NESTED LOOPS '},'1',''] ,[{v: '3', f: 'HASH JOIN '},'2',''] ,[{v: '4', f: 'TABLE ACCESS BY INDEX ROWID&lt;br /&gt;(&lt;b&gt;SYS.OBJ$&lt;/b&gt;)'},'3','SEL$1'] ,[{v: '5', f: 'NESTED LOOPS '},'4',''] ,[{v: '6', f: 'NESTED LOOPS '},'5',''] ,[{v: '7', f: 'NESTED LOOPS '},'6',''] ,[{v: '8', f: 'NESTED LOOPS '},'7',''] ,[{v: '9', f: 'TABLE ACCESS BY INDEX ROWID&lt;br /&gt;(&lt;b&gt;SYS.USER$&lt;/b&gt;)'},'8','SEL$1'] ,[{v: '10', f: 'INDEX UNIQUE SCAN&lt;br /&gt;(&lt;b&gt;SYS.I_USER1&lt;/b&gt;)'},'9','SEL$1'] ,[{v: '11', f: 'TABLE ACCESS BY INDEX ROWID&lt;br /&gt;(&lt;b&gt;SYS.OBJ$&lt;/b&gt;)'},'8','SEL$1'] ,[{v: '12', f: 'INDEX RANGE SCAN&lt;br /&gt;(&lt;b&gt;SYS.I_OBJ2&lt;/b&gt;)'},'11','SEL$1'] ,[{v: '13', f: 'TABLE ACCESS BY INDEX ROWID&lt;br /&gt;(&lt;b&gt;SYS.COL$&lt;/b&gt;)'},'7','SEL$1'] ,[{v: '14', f: 'INDEX UNIQUE SCAN&lt;br /&gt;(&lt;b&gt;SYS.I_COL1&lt;/b&gt;)'},'13','SEL$1'] ,[{v: '15', f: 'TABLE ACCESS BY INDEX ROWID&lt;br /&gt;(&lt;b&gt;SYS.COLTYPE$&lt;/b&gt;)'},'6','SEL$1'] ,[{v: '16', f: 'INDEX UNIQUE SCAN&lt;br /&gt;(&lt;b&gt;SYS.I_COLTYPE2&lt;/b&gt;)'},'15','SEL$1'] ,[{v: '17', f: 'INDEX RANGE SCAN&lt;br /&gt;(&lt;b&gt;SYS.I_OBJ3&lt;/b&gt;)'},'5','SEL$1'] ,[{v: '18', f: 'TABLE ACCESS FULL&lt;br /&gt;(&lt;b&gt;SYS.ASSOCIATION$&lt;/b&gt;)'},'3','SEL$1'] ,[{v: '19', f: 'TABLE ACCESS BY INDEX ROWID&lt;br /&gt;(&lt;b&gt;SYS.OBJ$&lt;/b&gt;)'},'2','SEL$1'] ,[{v: '20', f: 'INDEX UNIQUE SCAN&lt;br /&gt;(&lt;b&gt;SYS.I_OBJ1&lt;/b&gt;)'},'19','SEL$1'] ,[{v: '21', f: 'TABLE ACCESS CLUSTER&lt;br /&gt;(&lt;b&gt;SYS.USER$&lt;/b&gt;)'},'1','SEL$1'] ,[{v: '22', f: 'INDEX UNIQUE SCAN&lt;br /&gt;(&lt;b&gt;SYS.I_USER#&lt;/b&gt;)'},'21','SEL$1']]
Tableau que l’on affecte à la variable data de la manière suivante
var data = google.visualization.arrayToDataTable(<b>&lt;TABLEAU&gt;</b>);
Et on empaquette tout ça dans une fonction pour que l’affichage se fasse après le chargement de la page (comme il est d’usage en javascript). Fonction que l’on appelle donc qu’au bon moment.
function drawVisualization() { var data = google.visualization.arrayToDataTable(<b>&lt;TABLEAU&gt;</b>); new google.visualization.OrgChart(document.getElementById('visualization')).draw(<b>data</b>, <b>{allowHtml: true, size: 'small'}</b>) } google.setOnLoadCallback(drawVisualization)
Simplissime …
Générer tout ça en SQL ou plus exactement en PL/SQL
Le script utilisé pour générer le tableau est le suivant :
variable sqlid varchar2(13) variable phv number exec :sqlid:='&amp;1' exec :phv:=&amp;2 spool plan_&amp;&amp;1._&amp;&amp;2..html begin dbms_output.put_line('[[ ''id'',''parent'',''bloc''] ,['''||:PHV||''',null,null]'); for c in ( select id , operation||' '||options|| case when OBJECT_NAME is not null then '<br />(<b>'||OBJECT_OWNER||'.'||OBJECT_NAME||'</b>)' when parent_id is null then :sqlid else '' end txt , parent_id , QBLOCK_NAME qbl , rownum from dba_hist_sql_plan where sql_id=:sqlid and plan_hash_value=:phv ) loop dbms_output.put(' ,[{v: '''||to_char(c.id)||''''); dbms_output.put(', f: '''||c.txt||'''}'); dbms_output.put(','''||nvl(to_char(c.parent_id),:PHV)||''''); dbms_output.put_line(','''||c.qbl||''']'); end loop; dbms_output.put_line(']);'); end; /
Reste à gérer l’enveloppe html la sortie de SQL*Plus et la zone d’affichage, ce qui donne le script complet suivant
-- -- olivier joly 16/06/2013 v1.0.1 -- sql_hist_plan.sql -- -- ce script prend 2 parametres -- 1 =&gt; le sqlid de la requête -- 2 =&gt; le phv de la requête que l'on veut tracer -- store set settings_sqlplus replace set serveroutput on set pages 0 set lines 32767 set trimspool on set echo off set head off set feed off set verify off set timi off set autot off set termout off set markup html off ttitle off btitle off variable sqlid varchar2(13) variable phv number exec :sqlid:='&amp;&amp;1' exec :phv:=&amp;&amp;2 col dirsep new value dirsep ho mkdir awr&amp;&amp;1.&amp;&amp;2 ho mkdir -p awr/&amp;&amp;1./&amp;&amp;2 spool awr/&amp;&amp;1./&amp;&amp;2./plan.html prompt &lt;!DOCTYPE html&gt; prompt &lt;html xmlns="http://www.w3.org/1999/xhtml"&gt; prompt &lt;head&gt; prompt &lt;meta http-equiv="content-type" content="text/html; charset=utf-8"/&gt; prompt &lt;title&gt;Plan::&amp;&amp;1.::&amp;&amp;2.&lt;/title&gt; prompt &lt;script type="text/javascript" src="http://www.google.com/jsapi"&gt;&lt;/script&gt; prompt &lt;script type="text/javascript"&gt; prompt google.load('visualization', '1', {packages: ['orgchart', 'table']}); prompt &lt;/script&gt; prompt &lt;script type="text/javascript"&gt; prompt var map; prompt var table; prompt var data; prompt prompt function drawOrgChartAndTable() { prompt var data = google.visualization.arrayToDataTable([ begin sys.dbms_output.put_line('[[ ''id'',''parent'',''bloc''] ,['''||:phv||''',null,null]'); for c in ( select id , operation||' '||options|| case when object_name is not null then ' ('||object_owner||'.'||object_name||')' when parent_id is null then :sqlid else '' end txt , parent_id , qblock_name qbl , rownum from sys.dba_hist_sql_plan where sql_id=:sqlid and plan_hash_value=:phv ) loop sys.dbms_output.put(' ,[{v: '''||to_char(c.id)||''''); sys.dbms_output.put(', f: '''||c.txt||'''}'); sys.dbms_output.put(','''||nvl(to_char(c.parent_id),:phv)||''''); sys.dbms_output.put_line(','''||c.qbl||''']'); end loop; sys.dbms_output.put_line(']);'); end; / prompt new google.visualization.OrgChart(document.getElementById('V&amp;&amp;1.&amp;&amp;2')). prompt draw(data, {allowHtml: true}); prompt } prompt google.setOnLoadCallback(drawVisualization); prompt &lt;/script&gt; prompt &lt;/head&gt; prompt &lt;body style="font-family: Arial;border: 0 none;"&gt; prompt &lt;div id="V&amp;&amp;1.&amp;&amp;2"&gt;&lt;/div&gt; prompt &lt;/body&gt; prompt &lt;/html&gt; spool off undef 1 undef 2 @settings_sqlplus
Ce qui donne :
Plan plus classique
Depuis la version 10g plus besoin de requête hyérarchique pour effectuer un affichage sympathiqe du plan d’exécution. Les tables qui peuvent stocker des plan d’exécution (sys.dba_hist_sql_plan certes mais aussi plan_table ou v$sql_plan) contiennent la colonne DEPTH qui permet l’indentation pour créer vos propres requêtes de lecture en mode texte. L’utilisation du package dbms_xplan restant encore la manière le plus simple d’exploiter ces tables.