Un outil de comparaison de schéma est une bonne idée. Le schéma de base de données est bien plus compliqué que ce que la plupart des gens pensent, et chaque différence entre deux schémas de base de données peut potentiellement provoquer des bogues.
Si vous souhaitez toujours le faire vous-même, la meilleure approche que j'ai trouvée consiste à extraire les définitions de schéma en texte, puis à exécuter une comparaison de texte. Tant que tout est trié par ordre alphabétique, vous pouvez ensuite utiliser la fonction de comparaison de documents dans Microsoft Word (ou FC.EXE, DIFF ou équivalent) pour mettre en évidence les différences.
Le script SQLPlus suivant génère la définition de schéma par ordre alphabétique, pour permettre la comparaison. Il y a deux sections. La première section répertorie chaque colonne, au format :
table_name.column_name: data_type = data_default <nullable>
La deuxième section répertorie les index et les contraintes, comme suit :
PK constraint_name on table_name (pk_column_list)
FK constraint_name on table_name (fk_column_list)
CHECK constraint_name on table_name (constraint_definition)
Le script sert de référence utile pour extraire certains des détails du schéma Oracle. Cela peut être une bonne connaissance à avoir lorsque vous êtes sur des sites clients et que vous ne disposez pas de vos outils habituels, ou lorsque les politiques de sécurité vous empêchent d'accéder à une base de données de site client directement depuis votre propre PC.
set serveroutput on;
set serveroutput on size 1000000;
declare
rowcnt pls_integer := 0;
cursor c_column is
select table_name, column_name, data_type,
data_precision, data_length, data_scale,
data_default, nullable,
decode(data_scale, null, null, ',') scale_comma,
decode(default_length, null, null, '= ') default_equals
from all_tab_columns where owner = 'BCC'
order by table_name, column_name;
cursor c_constraint is
select c.table_name, c.constraint_name,
decode(c.constraint_type,
'P','PK',
'R','FK',
'C','CHECK',
c.constraint_type) constraint_type,
c.search_condition,
cc.column_1||cc.comma_2||cc.column_2||cc.comma_3||cc.column_3||cc.comma_4||cc.column_4||
cc.comma_5||cc.column_5||cc.comma_6||cc.column_6||cc.comma_7||cc.column_7 r_columns
from all_constraints c,
( select owner, table_name, constraint_name, nvl(max(position),0) max_position,
max( decode( position, 1, column_name, null ) ) column_1,
max( decode( position, 2, decode(column_name, null, null, ',' ), null ) ) comma_2,
max( decode( position, 2, column_name, null ) ) column_2,
max( decode( position, 3, decode(column_name, null, null, ',' ), null ) ) comma_3,
max( decode( position, 3, column_name, null ) ) column_3,
max( decode( position, 4, decode(column_name, null, null, ',' ), null ) ) comma_4,
max( decode( position, 4, column_name, null ) ) column_4,
max( decode( position, 5, decode(column_name, null, null, ',' ), null ) ) comma_5,
max( decode( position, 5, column_name, null ) ) column_5,
max( decode( position, 6, decode(column_name, null, null, ',' ), null ) ) comma_6,
max( decode( position, 6, column_name, null ) ) column_6,
max( decode( position, 7, decode(column_name, null, null, ',' ), null ) ) comma_7,
max( decode( position, 7, column_name, null ) ) column_7
from all_cons_columns
group by owner, table_name, constraint_name ) cc
where c.owner = 'BCC'
and c.generated != 'GENERATED NAME'
and cc.owner = c.owner
and cc.table_name = c.table_name
and cc.constraint_name = c.constraint_name
order by c.table_name,
decode(c.constraint_type,
'P','PK',
'R','FK',
'C','CHECK',
c.constraint_type) desc,
c.constraint_name;
begin
for c_columnRow in c_column loop
dbms_output.put_line(substr(c_columnRow.table_name||'.'||c_columnRow.column_name||': '||
c_columnRow.data_type||'('||
nvl(c_columnRow.data_precision, c_columnRow.data_length)||
c_columnRow.scale_comma||c_columnRow.data_scale||') '||
c_columnRow.default_equals||c_columnRow.data_default||
' <'||c_columnRow.nullable||'>',1,255));
rowcnt := rowcnt + 1;
end loop;
for c_constraintRow in c_constraint loop
dbms_output.put_line(substr(c_constraintRow.constraint_type||' '||c_constraintRow.constraint_name||' on '||
c_constraintRow.table_name||' ('||
c_constraintRow.search_condition||
c_constraintRow.r_columns||') ',1,255));
if length(c_constraintRow.constraint_type||' '||c_constraintRow.constraint_name||' on '||
c_constraintRow.table_name||' ('||
c_constraintRow.search_condition||
c_constraintRow.r_columns||') ') > 255 then
dbms_output.put_line('... '||substr(c_constraintRow.constraint_type||' '||c_constraintRow.constraint_name||' on '||
c_constraintRow.table_name||' ('||
c_constraintRow.search_condition||
c_constraintRow.r_columns||') ',256,251));
end if;
rowcnt := rowcnt + 1;
end loop;
end;
/
Malheureusement, il y a quelques limitations :
- Les retours chariot et les espaces blancs intégrés dans data_defaults, ainsi que les définitions de contraintes de vérification, peuvent être mis en évidence comme des différences, même s'ils n'ont aucun effet sur le schéma.
- N'inclut pas les clés alternatives, les index uniques ou les index de performance. Cela nécessiterait une troisième instruction SELECT dans le script, faisant référence aux vues de catalogue all_ind_columns et all_indexes.
- N'inclut pas les détails de sécurité, les synonymes, les packages, les déclencheurs, etc. Les packages et les déclencheurs seraient mieux comparés en utilisant une approche similaire à celle que vous avez initialement proposée. D'autres aspects de la définition du schéma pourraient être ajoutés au script ci-dessus.
- Les définitions FK ci-dessus identifient les colonnes de clé étrangère de référence, mais pas la PK ou la table référencée. Juste un détail de plus que je n'ai jamais eu le temps de faire.
Même si vous n'utilisez pas le script. Il y a un certain plaisir technique à jouer avec ce truc.;-)
Matthieu