Dba compare schemas
| Fiche express | |
|---|---|
| Type | Script SQL*Plus de diagnostic |
| Domaine | Administration (comparaison de schémas) |
| Voir aussi | Dba column constraints · Dba errors |
Dba_compare_schemas compare intégralement deux schémas Oracle — le schéma local (celui utilisé pour se connecter) et un schéma distant accessible via un database link temporaire — et produit un rapport détaillé de tous les écarts constatés : objets présents d'un seul côté, objets invalides, privilèges (rôles, système, objets), colonnes de table (présence, type, nullabilité...), index, contraintes, séquences, synonymes privés, code source PL/SQL (packages, procédures, fonctions), triggers, vues, jobs planifiés et database links.
Le script a été conçu et testé sur des versions Oracle allant de 7.3 à 11g. Certains éléments ne sont volontairement pas comparés en détail (commentaires, partitions, types objets, tables imbriquées, dimensions, clusters, métadonnées d'audit, IOT, tables temporaires, snapshots/vues matérialisées) : ils apparaissent toutefois dans la section « Résumé des objets » en début de rapport.
Fonctionnement
- Se connecter à la base en tant que l'un des deux schémas à comparer (le schéma « local »).
- Exécuter le script : il demande confirmation, puis les informations de connexion du schéma « distant » (utilisateur, mot de passe, nom de service Oracle Net) et enfin le nom du fichier de rapport (un nom par défaut est proposé).
- Le script crée temporairement un database link (
remote_schema_link), une table de travail (schema_compare_temp) et deux fonctions PL/SQL utilitaires (getLongText/getLongText2, utilisées pour comparer le texte des vues au-delà de la limite de 4000 caractères d'unLONGclassique). - Il génère le rapport section par section (spoolé dans le fichier choisi), puis supprime tous les objets temporaires créés en début d'exécution.
Prérequis : le compte utilisé doit disposer des privilèges nécessaires pour créer un database link, une table et des fonctions dans son propre schéma, ainsi que d'un accès en lecture aux vues USER_*/ALL_OBJECTS des deux côtés de la liaison.
Script
SET PAGESIZE 50000
SET LINESIZE 256
PROMPT
PROMPT +------------------------------------------------------------------------+
PROMPT | SCRIPT DE COMPARAISON DE SCHEMAS |
PROMPT |------------------------------------------------------------------------|
PROMPT | |
PROMPT | UTILISATION |
PROMPT | -----------------------------------------------------------------------|
PROMPT | Ce script doit être exécuté en étant connecté à la base Oracle en tant |
PROMPT | que l'un des deux schémas à comparer. Il demandera ensuite le nom |
PROMPT | utilisateur, le mot de passe et le nom du service Oracle Net (TNS) du |
PROMPT | second schéma (distant) à comparer. Enfin, il demandera le nom du |
PROMPT | fichier de rapport à générer pour toutes les différences détectées. |
PROMPT | (Appuyez sur [ENTREE] pour accepter le nom de fichier par défaut.) |
PROMPT | |
PROMPT | REMARQUE |
PROMPT | -----------------------------------------------------------------------|
PROMPT | Les objets suivants seront créés temporairement pour les besoins du |
PROMPT | script. |
PROMPT | |
PROMPT | [*] Database Link (remote_schema_link) |
PROMPT | [*] Table (schema_compare_temp) |
PROMPT | [*] Procédure PL/SQL (getLongText) |
PROMPT | [*] Procédure PL/SQL (getLongText2) |
PROMPT | |
PROMPT | Ces objets sont supprimés à la fin du script. |
PROMPT +------------------------------------------------------------------------+
PROMPT
SET TERMOUT OFF
COLUMN local_conn_info NEW_VALUE local_conn_info NOPRINT
SELECT 'Vous êtes actuellement connecté à l''instance [' ||
SYS_CONTEXT('USERENV', 'INSTANCE_NAME') || '] en tant qu''utilisateur [' ||
SYS_CONTEXT('USERENV', 'SESSION_USER') || '].' local_conn_info
FROM dual;
SET TERMOUT ON
PROMPT +------------------------------------------------------------------------+
PROMPT | CONNEXION LOCALE |
PROMPT |------------------------------------------------------------------------|
PROMPT | &local_conn_info
PROMPT +------------------------------------------------------------------------+
PROMPT
ACCEPT a1 CHAR PROMPT "Appuyez sur <ENTREE> pour continuer ou CTRL-C pour quitter... "
PROMPT
REM +---------------------------------------------------------------------------+
REM | DEMANDE DU NOM UTILISATEUR, MOT DE PASSE ET NOM DE SERVICE ORACLE NET. |
REM +---------------------------------------------------------------------------+
ACCEPT schema CHAR PROMPT "Nom utilisateur du schéma distant : "
ACCEPT password CHAR PROMPT "Mot de passe du schéma distant : " HIDE
ACCEPT tns_name CHAR PROMPT "Service Oracle Net du schéma distant : "
REM +---------------------------------------------------------------------------+
REM | CREATION D'UN DATABASE LINK TEMPORAIRE. |
REM +---------------------------------------------------------------------------+
SET FEEDBACK OFF
SET VERIFY OFF
SET TRIMSPOOL ON
CREATE DATABASE LINK remote_schema_link
CONNECT TO &schema IDENTIFIED BY &password
USING '&tns_name'
/
REM +---------------------------------------------------------------------------+
REM | NOM DE FICHIER DE RAPPORT PAR DEFAUT (MODIFIABLE PAR L'UTILISATEUR). |
REM +---------------------------------------------------------------------------+
SET TERMOUT OFF
COLUMN dflt_name NEW_VALUE dflt_name NOPRINT
SELECT 'compare_' ||
LOWER(user) || '_' ||
LOWER('&schema') || '_' ||
LOWER('&tns_name') dflt_name
FROM dual;
SET TERMOUT ON
PROMPT +------------------------------------------------------------------------+
PROMPT | NOM DU FICHIER DE RAPPORT |
PROMPT |------------------------------------------------------------------------|
PROMPT | Le nom de fichier par défaut est &dflt_name..lst
PROMPT | |
PROMPT | Pour l'utiliser, appuyez sur [ENTREE], sinon saisissez un autre nom. |
PROMPT +------------------------------------------------------------------------+
PROMPT
SET HEADING OFF
COLUMN report_name NEW_VALUE report_name NOPRINT
SELECT
'Nom de rapport utilisé : ' || NVL('&&report_name', '&dflt_name')
, NVL('&&report_name', '&dflt_name') || '.lst' report_name
FROM sys.dual;
SPOOL &report_name
SET HEADING ON
REM +---------------------------------------------------------------------------+
REM | EN-TETE DU RAPPORT : DATE, HEURE, INFOS DE CONNEXION. |
REM +---------------------------------------------------------------------------+
SELECT SUBSTR(RPAD(TO_CHAR(sysdate, 'DD-MON-YYYY HH24:MI:SS'), 25), 1, 25) "Date et heure du rapport"
FROM dual;
COLUMN local_schema FORMAT a45 HEADING "Schéma local" TRUNC
COLUMN remote_schema FORMAT a45 HEADING "Schéma distant" TRUNC
SELECT
user || '@' || c.global_name local_schema
, a.username || '@' || b.global_name remote_schema
FROM
user_users@remote_schema_link a
, global_name@remote_schema_link b
, global_name c
WHERE
rownum = 1;
SET FEEDBACK OFF
SET TERMOUT OFF
COLUMN object_name FORMAT a40 HEADING 'Nom objet'
COLUMN object_type FORMAT a40 HEADING 'Type objet'
COLUMN obj_count FORMAT 999,999,999 HEADING 'Nombre objets'
PROMPT
SET HEADING OFF
SET FEEDBACK OFF
SELECT '+----------------------------------------------------------------------+' || chr(10) ||
'| RESUME DES OBJETS |' || chr(10) ||
'+----------------------------------------------------------------------+'
FROM dual;
SET HEADING ON
SET FEEDBACK ON
PROMPT
PROMPT ========================================================
PROMPT Objets absents du schéma local - (Résumé)
PROMPT ========================================================
SELECT
object_type
, count(*) obj_count
FROM
(SELECT
object_type
, DECODE( object_type
, 'INDEX', DECODE(SUBSTR(object_name, 1, 5), 'SYS_C', 'SYS_C', object_name)
, 'LOB' , DECODE(SUBSTR(object_name, 1, 7), 'SYS_LOB', 'SYS_LOB', object_name)
, object_name)
FROM user_objects@remote_schema_link
MINUS
SELECT
object_type
, DECODE( object_type
, 'INDEX', DECODE(SUBSTR(object_name, 1, 5), 'SYS_C', 'SYS_C', object_name)
, 'LOB', DECODE(SUBSTR(object_name, 1, 7), 'SYS_LOB', 'SYS_LOB', object_name),
object_name)
FROM user_objects
)
GROUP BY object_type
ORDER BY object_type;
PROMPT
PROMPT
PROMPT ========================================================
PROMPT Objets en trop dans le schéma local - (Résumé)
PROMPT ========================================================
SELECT
object_type
, count(*) obj_count
FROM
(SELECT
object_type
, DECODE( object_type
, 'INDEX', DECODE (SUBSTR (object_name, 1, 5), 'SYS_C', 'SYS_C', object_name)
, 'LOB', DECODE (SUBSTR (object_name, 1, 7), 'SYS_LOB', 'SYS_LOB', object_name)
, object_name)
FROM user_objects
WHERE object_type != 'DATABASE LINK'
OR object_name NOT LIKE 'REMOTE_SCHEMA_LINK.%'
MINUS
SELECT
object_type
, DECODE( object_type
, 'INDEX', DECODE (SUBSTR (object_name, 1, 5), 'SYS_C', 'SYS_C', object_name)
, 'LOB', DECODE (SUBSTR (object_name, 1, 7), 'SYS_LOB', 'SYS_LOB', object_name)
, object_name)
FROM user_objects@remote_schema_link
)
GROUP BY object_type
ORDER BY object_type;
PROMPT
SET HEADING OFF
SET FEEDBACK OFF
SELECT '+----------------------------------------------------------------------+' || chr(10) ||
'| DIFFERENCES DE PRIVILEGES |' || chr(10) ||
'+----------------------------------------------------------------------+'
FROM dual;
SET HEADING ON
SET FEEDBACK ON
COLUMN granted_role FORMAT a30 HEADING 'Rôle accordé'
COLUMN default_role FORMAT a22 HEADING 'Rôle par défaut'
COLUMN os_granted FORMAT a11 HEADING 'Accordé OS'
COLUMN owner FORMAT a30 HEADING 'Propriétaire'
COLUMN table_name FORMAT a30 HEADING 'Table'
COLUMN schema FORMAT a7 HEADING 'Schéma'
COLUMN grantee FORMAT a30 HEADING 'Bénéficiaire'
COLUMN privilege FORMAT a40 HEADING 'Privilège'
COLUMN grantable FORMAT a10 HEADING 'Transmissible'
COLUMN admin_option FORMAT a13 HEADING 'Option Admin'
PROMPT
PROMPT ========================================================
PROMPT Différences de privilèges sur les rôles
PROMPT ========================================================
(
SELECT
granted_role
, 'Remote' schema
, admin_option
, default_role
, os_granted
FROM
user_role_privs@remote_schema_link
MINUS
SELECT
granted_role
, 'Remote' schema
, admin_option
, default_role
, os_granted
FROM
user_role_privs
)
UNION ALL
(
SELECT
granted_role
, 'Local' schema
, admin_option
, default_role
, os_granted
FROM
user_role_privs
MINUS
SELECT
granted_role
, 'Local' schema
, admin_option
, default_role
, os_granted
FROM
user_role_privs@remote_schema_link
)
ORDER BY 1, 2;
PROMPT
PROMPT ========================================================
PROMPT Différences de privilèges système
PROMPT ========================================================
(
SELECT
privilege
, 'Remote' schema
, admin_option
FROM
user_sys_privs@remote_schema_link
MINUS
SELECT
privilege
, 'Remote' schema
, admin_option
FROM
user_sys_privs
)
UNION ALL
(
SELECT
privilege
, 'Local' schema
, admin_option
FROM
user_sys_privs
MINUS
SELECT
privilege
, 'Local' schema
, admin_option
FROM
user_sys_privs@remote_schema_link
)
ORDER BY 1, 2;
PROMPT
PROMPT ========================================================
PROMPT Différences de privilèges au niveau objet
PROMPT ========================================================
(
SELECT
owner
, table_name
, 'Remote' schema
, grantee
, privilege
, grantable
FROM
user_tab_privs@remote_schema_link
WHERE
(owner, table_name) IN (
SELECT owner, object_name
FROM all_objects
)
MINUS
SELECT
owner
, table_name
, 'Remote' schema
, grantee
, privilege
, grantable
FROM user_tab_privs
)
UNION ALL
(
SELECT
owner
, table_name
, 'Local' schema
, grantee
, privilege
, grantable
FROM
user_tab_privs
WHERE
(owner, table_name) IN (
SELECT owner, object_name
FROM all_objects@remote_schema_link
)
MINUS
SELECT
owner
, table_name
, 'Local' schema
, grantee
, privilege
, grantable
FROM
user_tab_privs@remote_schema_link
)
ORDER BY 1, 2, 3;
PROMPT
SET HEADING OFF
SET FEEDBACK OFF
SELECT '+----------------------------------------------------------------------+' || chr(10) ||
'| DIFFERENCES D''OBJETS |' || chr(10) ||
'+----------------------------------------------------------------------+'
FROM dual;
SET HEADING ON
SET FEEDBACK ON
PROMPT
PROMPT ========================================================
PROMPT Objets absents du schéma local
PROMPT ========================================================
SELECT
DECODE( object_type
, 'INDEX', DECODE(SUBSTR(object_name, 1, 5), 'SYS_C', 'SYS_C', object_name)
, 'LOB', DECODE(SUBSTR(object_name, 1, 7), 'SYS_LOB', 'SYS_LOB', object_name)
, object_name) object_name
, object_type
FROM user_objects@remote_schema_link
MINUS
SELECT
DECODE( object_type
, 'INDEX', DECODE(SUBSTR(object_name, 1, 5), 'SYS_C', 'SYS_C', object_name)
, 'LOB', DECODE(SUBSTR(object_name, 1, 7), 'SYS_LOB', 'SYS_LOB', object_name)
, object_name) object_name
, object_type
FROM user_objects
ORDER BY object_type, object_name;
PROMPT
PROMPT
PROMPT ========================================================
PROMPT Objets en trop dans le schéma local
PROMPT ========================================================
SELECT
DECODE( object_type
, 'INDEX', DECODE(SUBSTR(object_name, 1, 5), 'SYS_C', 'SYS_C', object_name)
, 'LOB', DECODE(SUBSTR(object_name, 1, 7), 'SYS_LOB', 'SYS_LOB', object_name)
, object_name) object_name
, object_type
FROM
user_objects
WHERE
object_type != 'DATABASE LINK'
OR object_name NOT LIKE 'REMOTE_SCHEMA_LINK.%'
MINUS
SELECT
DECODE( object_type
, 'INDEX', DECODE(SUBSTR(object_name, 1, 5), 'SYS_C', 'SYS_C', object_name)
, 'LOB', DECODE(SUBSTR(object_name, 1, 7), 'SYS_LOB', 'SYS_LOB', object_name)
, object_name) object_name
, object_type
FROM
user_objects@remote_schema_link
ORDER BY object_type, object_name;
PROMPT
PROMPT
PROMPT ========================================================
PROMPT Objets invalides dans le schéma local
PROMPT ========================================================
SELECT object_name, object_type, status
FROM user_objects
WHERE status != 'VALID'
ORDER BY object_name, object_type;
PROMPT
PROMPT
PROMPT ========================================================
PROMPT Objets invalides dans le schéma distant
PROMPT ========================================================
SELECT object_name, object_type, status
FROM user_objects@remote_schema_link
WHERE status != 'VALID'
ORDER BY object_name, object_type;
PROMPT
SET HEADING OFF
SET FEEDBACK OFF
SELECT '+----------------------------------------------------------------------+' || chr(10) ||
'| DIFFERENCES DE COLONNES DE TABLE |' || chr(10) ||
'+----------------------------------------------------------------------+'
FROM dual;
SET HEADING ON
SET FEEDBACK ON
PROMPT
PROMPT ========================================================
PROMPT Colonnes de table absentes d'un des deux schémas
PROMPT (les écarts ne sont pas listés dans l'ordre des colonnes)
PROMPT ========================================================
COLUMN table_name FORMAT a30 HEADING 'Table'
COLUMN column_name FORMAT a30 HEADING 'Colonne'
COLUMN mis FORMAT a17 HEADING 'Absente du schéma'
COLUMN schema FORMAT a7 HEADING 'Schéma'
COLUMN nullable FORMAT a8 HEADING 'Nullable ?'
COLUMN data_type FORMAT a9 HEADING 'Type'
COLUMN data_length FORMAT 9999 HEADING 'Longueur'
COLUMN data_precision FORMAT 9999 HEADING 'Précision'
COLUMN data_scale FORMAT 9999 HEADING 'Échelle'
COLUMN default_length FORMAT 9999 HEADING 'Long. valeur défaut'
(
SELECT
table_name
, column_name
, 'Local' mis
FROM user_tab_columns@remote_schema_link
WHERE table_name IN (
SELECT table_name
FROM user_tables
)
MINUS
SELECT
table_name
, column_name
, 'Local' mis
FROM user_tab_columns
)
UNION ALL
(
SELECT
table_name
, column_name
, 'Remote' mis
FROM user_tab_columns
WHERE table_name IN (
SELECT table_name
FROM user_tables@remote_schema_link
)
MINUS
SELECT
table_name
, column_name
, 'Remote' mis
FROM user_tab_columns@remote_schema_link
)
ORDER BY 1, 2;
PROMPT
PROMPT ========================================================
PROMPT Différences de type pour les colonnes présentes dans les
PROMPT deux schémas
PROMPT ========================================================
(
SELECT
table_name
, column_name
, 'Remote' schema
, nullable
, data_type
, data_length
, data_precision
, data_scale
, default_length
FROM user_tab_columns@remote_schema_link
WHERE (table_name, column_name) IN (
SELECT table_name, column_name
FROM user_tab_columns
)
MINUS
SELECT
table_name
, column_name
, 'Remote' schema
, nullable
, data_type
, data_length
, data_precision
, data_scale
, default_length
FROM user_tab_columns
)
UNION ALL
(
SELECT
table_name
, column_name
, 'Local' schema
, nullable
, data_type
, data_length
, data_precision
, data_scale
, default_length
FROM user_tab_columns
WHERE (table_name, column_name) IN (
SELECT table_name, column_name
FROM user_tab_columns@remote_schema_link
)
MINUS
SELECT
table_name
, column_name
, 'Local' schema
, nullable
, data_type
, data_length
, data_precision
, data_scale
, default_length
FROM user_tab_columns@remote_schema_link
)
ORDER BY 1, 2, 3;
PROMPT
SET HEADING OFF
SET FEEDBACK OFF
SELECT '+----------------------------------------------------------------------+' || chr(10) ||
'| DIFFERENCES D''INDEX |' || chr(10) ||
'+----------------------------------------------------------------------+'
FROM dual;
SET HEADING ON
SET FEEDBACK ON
COLUMN index_name FORMAT a30 HEADING 'Index'
COLUMN schema FORMAT a7 HEADING 'Schéma'
COLUMN uniquenes HEADING 'Unicité'
COLUMN table_name FORMAT a30 HEADING 'Table'
COLUMN column_name FORMAT a30 HEADING 'Colonne'
COLUMN column_position FORMAT 999 HEADING 'Ordre'
PROMPT
PROMPT ========================================================
PROMPT Différences pour les index présents dans les deux schémas
PROMPT ========================================================
(
SELECT
a.index_name
, 'Remote' schema
, a.uniqueness
, a.table_name
, b.column_name
, b.column_position
FROM
user_indexes@remote_schema_link a
, user_ind_columns@remote_schema_link b
WHERE
a.index_name IN (
SELECT index_name
FROM user_indexes
)
AND b.index_name = a.index_name
AND b.table_name = a.table_name
MINUS
SELECT
a.index_name
, 'Remote' schema
, a.uniqueness
, a.table_name
, b.column_name
, b.column_position
FROM
user_indexes a
, user_ind_columns b
WHERE
b.index_name = a.index_name
AND b.table_name = a.table_name
)
UNION ALL
(
SELECT
a.index_name
, 'Local' schema
, a.uniqueness
, a.table_name
, b.column_name
, b.column_position
FROM
user_indexes a
, user_ind_columns b
WHERE
a.index_name IN (
SELECT index_name
FROM user_indexes@remote_schema_link
)
AND b.index_name = a.index_name
AND b.table_name = a.table_name
MINUS
SELECT
a.index_name
, 'Local' schema
, a.uniqueness
, a.table_name
, b.column_name
, b.column_position
FROM
user_indexes@remote_schema_link a
, user_ind_columns@remote_schema_link b
WHERE
b.index_name = a.index_name
AND b.table_name = a.table_name
)
ORDER BY 1, 2, 6;
PROMPT
SET HEADING OFF
SET FEEDBACK OFF
SELECT '+----------------------------------------------------------------------+' || chr(10) ||
'| DIFFERENCES DE CONTRAINTES |' || chr(10) ||
'+----------------------------------------------------------------------+'
FROM dual;
SET HEADING ON
SET FEEDBACK ON
PROMPT
PROMPT ========================================================
PROMPT Différences de contraintes pour les tables présentes dans
PROMPT les deux schémas
PROMPT ========================================================
SET FEEDBACK OFF
CREATE TABLE schema_compare_temp (
database NUMBER(1)
, object_name VARCHAR2(30)
, object_text VARCHAR2(2000)
, hash_value NUMBER
)
/
DECLARE
CURSOR c1 IS
SELECT constraint_name, search_condition
FROM user_constraints
WHERE search_condition IS NOT NULL;
CURSOR c2 IS
SELECT constraint_name, search_condition
FROM user_constraints@remote_schema_link
WHERE search_condition IS NOT NULL;
v_constraint_name VARCHAR2(30);
v_search_condition VARCHAR2(32767);
BEGIN
OPEN c1;
LOOP
FETCH c1 INTO v_constraint_name, v_search_condition;
EXIT WHEN c1%NOTFOUND;
v_search_condition := SUBSTR (v_search_condition, 1, 2000);
INSERT INTO schema_compare_temp (
database, object_name, object_text
) VALUES (
1, v_constraint_name, v_search_condition
);
END LOOP;
CLOSE c1;
OPEN c2;
LOOP
FETCH c2 INTO v_constraint_name, v_search_condition;
EXIT WHEN c2%NOTFOUND;
v_search_condition := SUBSTR (v_search_condition, 1, 2000);
INSERT INTO schema_compare_temp (
database, object_name, object_text
) VALUES (
2, v_constraint_name, v_search_condition
);
END LOOP;
CLOSE c2;
COMMIT;
END;
/
SET FEEDBACK ON
COLUMN constraint_name FORMAT a30 HEADING 'Contrainte|Nom'
COLUMN schema FORMAT a7 HEADING 'Schéma'
COLUMN constraint_type FORMAT a10 HEADING 'Contrainte|Type'
COLUMN table_name FORMAT a30 HEADING 'Table|Nom'
COLUMN r_constraint_name FORMAT a30 HEADING 'Contrainte réf.|Nom'
COLUMN delete_rule FORMAT a10 HEADING 'Règle|Suppr.'
COLUMN status FORMAT a9 HEADING 'Statut'
COLUMN object_text FORMAT a20 HEADING 'Texte|Objet'
(
SELECT
REPLACE(TRANSLATE(a.constraint_name,'012345678','999999999'), '9', NULL) constraint_name
, 'Remote' schema
, a.constraint_type
, a.table_name
, a.r_constraint_name
, a.delete_rule
, a.status
, b.object_text
FROM
user_constraints@remote_schema_link a
, schema_compare_temp b
WHERE
a.table_name IN (
SELECT table_name
FROM user_tables
)
AND b.database(+) = 2
AND b.object_name(+) = a.constraint_name
MINUS
SELECT
REPLACE(TRANSLATE(a.constraint_name,'012345678','999999999'), '9', NULL) constraint_name
, 'Remote' schema
, a.constraint_type
, a.table_name
, a.r_constraint_name
, a.delete_rule
, a.status
, b.object_text
FROM
user_constraints a
, schema_compare_temp b
WHERE
b.database(+) = 1
AND b.object_name(+) = a.constraint_name
)
UNION ALL
(
SELECT
REPLACE(TRANSLATE(a.constraint_name,'012345678','999999999'), '9', NULL) constraint_name
, 'Local' schema
, a.constraint_type
, a.table_name
, a.r_constraint_name
, a.delete_rule
, a.status
, b.object_text
FROM
user_constraints a
, schema_compare_temp b
WHERE
a.table_name IN (
SELECT table_name
FROM user_tables@remote_schema_link
)
AND b.database(+) = 1
AND b.object_name(+) = a.constraint_name
MINUS
SELECT
REPLACE(TRANSLATE(a.constraint_name,'012345678','999999999'), '9', NULL) constraint_name
, 'Local' schema
, a.constraint_type
, a.table_name
, a.r_constraint_name
, a.delete_rule
, a.status
, b.object_text
FROM
user_constraints@remote_schema_link a
, schema_compare_temp b
WHERE
b.database(+) = 2
AND b.object_name(+) = a.constraint_name
)
ORDER BY 1, 4, 2;
PROMPT
SET HEADING OFF
SET FEEDBACK OFF
SELECT '+----------------------------------------------------------------------+' || chr(10) ||
'| DIFFERENCES DE SEQUENCES |' || chr(10) ||
'+----------------------------------------------------------------------+'
FROM dual;
SET HEADING ON
SET FEEDBACK ON
PROMPT
PROMPT ========================================================
PROMPT Différences de séquences
PROMPT ========================================================
COLUMN sequence_name FORMAT a30 HEADING 'Séquence|Nom'
COLUMN schema FORMAT a7 HEADING 'Schéma'
COLUMN min_value HEADING 'Val.|Min'
COLUMN max_value HEADING 'Val.|Max'
COLUMN increment_by HEADING 'Pas'
COLUMN cycle_flag FORMAT a5 HEADING 'Cycle'
COLUMN order_flag FORMAT a5 HEADING 'Ordre'
COLUMN cache_size HEADING 'Cache'
(
SELECT
sequence_name
, 'Remote' schema
, min_value
, max_value
, increment_by
, cycle_flag
, order_flag
, cache_size
FROM
user_sequences@remote_schema_link
MINUS
SELECT
sequence_name
, 'Remote' schema
, min_value
, max_value
, increment_by
, cycle_flag
, order_flag
, cache_size
FROM
user_sequences
)
UNION ALL
(
SELECT
sequence_name
, 'Local' schema
, min_value
, max_value
, increment_by
, cycle_flag
, order_flag
, cache_size
FROM
user_sequences
MINUS
SELECT
sequence_name
, 'Local' schema
, min_value
, max_value
, increment_by
, cycle_flag
, order_flag
, cache_size
FROM
user_sequences@remote_schema_link
)
ORDER BY 1, 2;
PROMPT
SET HEADING OFF
SET FEEDBACK OFF
SELECT '+----------------------------------------------------------------------+' || chr(10) ||
'| DIFFERENCES DE SYNONYMES PRIVES |' || chr(10) ||
'+----------------------------------------------------------------------+'
FROM dual;
SET HEADING ON
SET FEEDBACK ON
PROMPT
PROMPT ========================================================
PROMPT Différences de synonymes privés
PROMPT ========================================================
COLUMN synonym_name FORMAT a30 HEADING 'Synonyme|Nom'
COLUMN schema FORMAT a7 HEADING 'Schéma'
COLUMN table_owner FORMAT a20 HEADING 'Table|Propriétaire'
COLUMN table_name FORMAT a30 HEADING 'Table|Nom'
COLUMN db_link FORMAT a25 HEADING 'DB|Link'
(
SELECT
synonym_name
, 'Remote' schema
, table_owner
, table_name
, db_link
FROM
user_synonyms@remote_schema_link
MINUS
SELECT
synonym_name
, 'Remote' schema
, table_owner
, table_name
, db_link
FROM user_synonyms
)
UNION ALL
(
SELECT
synonym_name
, 'Local' schema
, table_owner
, table_name
, db_link
FROM
user_synonyms
MINUS
SELECT
synonym_name
, 'Local' schema
, table_owner
, table_name
, db_link
FROM
user_synonyms@remote_schema_link
)
ORDER BY 1, 2;
PROMPT
SET HEADING OFF
SET FEEDBACK OFF
SELECT '+----------------------------------------------------------------------+' || chr(10) ||
'| DIFFERENCES PL/SQL |' || chr(10) ||
'+----------------------------------------------------------------------+'
FROM dual;
SET HEADING ON
SET FEEDBACK ON
PROMPT
PROMPT ========================================================
PROMPT Différences de code source pour tous les packages,
PROMPT procédures et fonctions présents dans les deux schémas
PROMPT (COMPARAISON SENSIBLE A LA CASSE)
PROMPT ========================================================
COLUMN name FORMAT a30 HEADING 'Source|Nom'
COLUMN type FORMAT a20 HEADING 'Source|Type'
COLUMN discrepancies FORMAT 999,999,999 HEADING 'Nombre|Écarts'
SELECT
name
, type
, COUNT(*) discrepancies
FROM
( ( SELECT name, type, line, text
FROM user_source@remote_schema_link
WHERE (name, type) IN (
SELECT object_name, object_type
FROM user_objects
)
MINUS
SELECT name, type, line, text
FROM user_source
)
UNION ALL
( SELECT name, type, line, text
FROM user_source
WHERE (name, type) IN (
SELECT object_name, object_type
FROM user_objects@remote_schema_link
)
MINUS
SELECT name, type, line, text
FROM user_source@remote_schema_link
)
)
GROUP BY name, type
ORDER BY name, type;
PROMPT
PROMPT ========================================================
PROMPT Différences de code source pour tous les packages,
PROMPT procédures et fonctions présents dans les deux schémas
PROMPT (COMPARAISON INSENSIBLE A LA CASSE)
PROMPT ========================================================
COLUMN name FORMAT a30 HEADING 'Source|Nom'
COLUMN type FORMAT a20 HEADING 'Source|Type'
COLUMN discrepancies FORMAT 999,999,999 HEADING 'Nombre|Écarts'
SELECT
name
, type
, COUNT (*) discrepancies
FROM
( ( SELECT name, type, line, UPPER(text)
FROM user_source@remote_schema_link
WHERE (name, type) IN (
SELECT object_name, object_type
FROM user_objects
)
MINUS
SELECT name, type, line, UPPER(text)
FROM user_source
)
UNION ALL
( SELECT name, type, line, UPPER(text)
FROM user_source
WHERE (name, type) IN (
SELECT object_name, object_type
FROM user_objects@remote_schema_link
)
MINUS
SELECT name, type, line, UPPER(text)
FROM user_source@remote_schema_link
)
)
GROUP BY name, type
ORDER BY name, type;
PROMPT
SET HEADING OFF
SET FEEDBACK OFF
SELECT '+----------------------------------------------------------------------+' || chr(10) ||
'| DIFFERENCES DE TRIGGERS |' || chr(10) ||
'+----------------------------------------------------------------------+'
FROM dual;
SET HEADING ON
SET FEEDBACK ON
PROMPT
PROMPT ========================================================
PROMPT Différences de triggers
PROMPT ========================================================
SET FEEDBACK OFF
TRUNCATE TABLE schema_compare_temp
/
DECLARE
CURSOR c1 IS
SELECT trigger_name, trigger_body
FROM user_triggers;
CURSOR c2 IS
SELECT trigger_name, trigger_body
FROM user_triggers@remote_schema_link;
v_trigger_name VARCHAR2(30);
v_trigger_body VARCHAR2(32767);
v_hash_value NUMBER;
BEGIN
OPEN c1;
LOOP
FETCH c1 INTO v_trigger_name, v_trigger_body;
EXIT WHEN c1%NOTFOUND;
v_trigger_body := REPLACE(v_trigger_body, ' ', NULL);
v_trigger_body := REPLACE(v_trigger_body, CHR(9), NULL);
v_trigger_body := REPLACE(v_trigger_body, CHR(10), NULL);
v_trigger_body := REPLACE(v_trigger_body, CHR(13), NULL);
v_trigger_body := UPPER(v_trigger_body);
v_hash_value := dbms_utility.get_hash_value(v_trigger_body, 1, 65536);
INSERT INTO schema_compare_temp (
database, object_name, hash_value
) VALUES (
1, v_trigger_name, v_hash_value
);
END LOOP;
CLOSE c1;
OPEN c2;
LOOP
FETCH c2 INTO v_trigger_name, v_trigger_body;
EXIT WHEN c2%NOTFOUND;
v_trigger_body := REPLACE(v_trigger_body, ' ', NULL);
v_trigger_body := REPLACE(v_trigger_body, CHR(9), NULL);
v_trigger_body := REPLACE(v_trigger_body, CHR(10), NULL);
v_trigger_body := REPLACE(v_trigger_body, CHR(13), NULL);
v_trigger_body := UPPER(v_trigger_body);
v_hash_value := dbms_utility.get_hash_value(v_trigger_body, 1, 65536);
INSERT INTO schema_compare_temp (
database, object_name, hash_value
) VALUES (
2, v_trigger_name, v_hash_value
);
END LOOP;
CLOSE c2;
END;
/
SET FEEDBACK ON
COLUMN trigger_name FORMAT a20 HEADING 'Trigger|Nom'
COLUMN schema FORMAT a7 HEADING 'Schéma'
COLUMN trigger_type FORMAT a16 HEADING 'Trigger|Type'
COLUMN triggering_event FORMAT a20 HEADING 'Événement|déclencheur'
COLUMN table_name FORMAT a15 HEADING 'Table|Nom'
COLUMN referencing_names FORMAT a20 HEADING 'Noms de|référence'
COLUMN when_clause FORMAT a20 HEADING 'Clause|WHEN'
COLUMN status FORMAT a9 HEADING 'Statut'
COLUMN hash_value HEADING 'Empreinte'
( SELECT
a.trigger_name
, 'Local' schema
, a.trigger_type
, SUBSTR(a.triggering_event, 1, 20) triggering_event
, a.table_name
, SUBSTR(a.referencing_names, 1, 20) referencing_names
, SUBSTR(a.when_clause, 1, 20) when_clause
, a.status
, b.hash_value
FROM
user_triggers a
, schema_compare_temp b
WHERE
b.object_name(+) = a.trigger_name
AND b.database(+) = 1
AND a.table_name IN (
SELECT table_name
FROM user_tables@remote_schema_link
)
MINUS
SELECT
a.trigger_name
, 'Local' schema
, a.trigger_type
, SUBSTR(a.triggering_event, 1, 20) triggering_event
, a.table_name
, SUBSTR(a.referencing_names, 1, 20) referencing_names
, SUBSTR(a.when_clause, 1, 20) when_clause
, a.status
, b.hash_value
FROM
user_triggers@remote_schema_link a
, schema_compare_temp b
WHERE
b.object_name(+) = a.trigger_name
AND b.database(+) = 2
)
UNION ALL
(
SELECT
a.trigger_name
, 'Remote' schema
, a.trigger_type
, SUBSTR(a.triggering_event, 1, 20) triggering_event
, a.table_name
, SUBSTR(a.referencing_names, 1, 20) referencing_names
, SUBSTR(a.when_clause, 1, 20) when_clause
, a.status
, b.hash_value
FROM
user_triggers@remote_schema_link a
, schema_compare_temp b
WHERE
b.object_name(+) = a.trigger_name
AND b.database(+) = 2
AND a.table_name IN (
SELECT table_name
FROM user_tables
)
MINUS
SELECT
a.trigger_name
, 'Remote' schema
, a.trigger_type
, SUBSTR(a.triggering_event, 1, 20) triggering_event
, a.table_name
, SUBSTR(a.referencing_names, 1, 20) referencing_names
, SUBSTR(a.when_clause, 1, 20) when_clause
, a.status
, b.hash_value
FROM
user_triggers a
, schema_compare_temp b
WHERE
b.object_name(+) = a.trigger_name
AND b.database(+) = 1
)
ORDER BY 1, 2, 5, 3;
PROMPT
SET HEADING OFF
SET FEEDBACK OFF
SELECT '+----------------------------------------------------------------------+' || chr(10) ||
'| DIFFERENCES DE VUES |' || chr(10) ||
'+----------------------------------------------------------------------+'
FROM dual;
SET HEADING ON
SET FEEDBACK ON
PROMPT
PROMPT ========================================================
PROMPT Différences pour les vues présentes dans les deux schémas
PROMPT ========================================================
SET FEEDBACK OFF
SET LONG 32767
TRUNCATE TABLE schema_compare_temp
/
CREATE OR REPLACE FUNCTION getLongText ( p_tname IN VARCHAR2
, p_cname IN VARCHAR2
, p_vname IN VARCHAR2) RETURN VARCHAR2
AS
l_sql VARCHAR2(4000);
l_cursor INTEGER DEFAULT dbms_sql.open_cursor;
l_n NUMBER;
l_long_val VARCHAR2(4000);
l_long_len NUMBER;
l_buflen NUMBER := 4000;
l_curpos NUMBER := 0;
BEGIN
l_sql := 'select ' || p_cname || ' from ' || p_tname || ' where UPPER(view_name) = UPPER(:view_name)';
DBMS_SQL.PARSE( l_cursor
, l_sql
, DBMS_SQL.NATIVE);
DBMS_SQL.BIND_VARIABLE(l_cursor, ':view_name', p_vname);
DBMS_SQL.DEFINE_COLUMN_LONG(l_cursor, 1);
l_n := DBMS_SQL.EXECUTE(l_cursor);
IF (DBMS_SQL.FETCH_ROWS(l_cursor) > 0)
THEN
DBMS_SQL.COLUMN_VALUE_LONG( l_cursor
, 1
, l_buflen
, l_curpos
, l_long_val
, l_long_len);
END IF;
DBMS_SQL.CLOSE_CURSOR(l_cursor);
RETURN l_long_val;
END getLongText;
/
CREATE OR REPLACE FUNCTION getLongText2 ( p_tname IN VARCHAR2
, p_cname IN VARCHAR2
, p_vname IN VARCHAR2) RETURN VARCHAR2
AS
l_sql VARCHAR2(4000);
l_cursor INTEGER DEFAULT dbms_sql.open_cursor;
l_n NUMBER;
l_long_val VARCHAR2(4000);
l_long_len NUMBER;
l_buflen NUMBER := 4000;
l_curpos NUMBER := 0;
BEGIN
l_sql := 'select ' || p_cname || ' from ' || p_tname || '@remote_schema_link where UPPER(view_name) = UPPER(:view_name)';
DBMS_SQL.PARSE( l_cursor
, l_sql
, DBMS_SQL.NATIVE);
DBMS_SQL.BIND_VARIABLE(l_cursor, ':view_name', p_vname);
DBMS_SQL.DEFINE_COLUMN_LONG(l_cursor, 1);
l_n := DBMS_SQL.EXECUTE(l_cursor);
IF (DBMS_SQL.FETCH_ROWS(l_cursor) > 0)
THEN
DBMS_SQL.COLUMN_VALUE_LONG( l_cursor
, 1
, l_buflen
, l_curpos
, l_long_val
, l_long_len);
END IF;
DBMS_SQL.CLOSE_CURSOR(l_cursor);
RETURN l_long_val;
END getLongText2;
/
DECLARE
CURSOR c1 IS
SELECT view_name, getLongText('USER_VIEWS', 'TEXT', view_name)
FROM user_views;
CURSOR c2 IS
SELECT view_name, getLongText2('USER_VIEWS', 'TEXT', view_name)
FROM user_views@remote_schema_link;
v_view_name VARCHAR2(30);
v_text VARCHAR2(32767);
v_hash_value NUMBER;
BEGIN
OPEN c1;
LOOP
FETCH c1 INTO v_view_name, v_text;
EXIT WHEN c1%NOTFOUND;
v_hash_value := dbms_utility.get_hash_value(v_text, 1, 65536);
INSERT INTO schema_compare_temp (
database, object_name, object_text, hash_value
) VALUES (
1, v_view_name, '[' || v_text || ']', v_hash_value
);
END LOOP;
CLOSE c1;
OPEN c2;
LOOP
FETCH c2 INTO v_view_name, v_text;
EXIT WHEN c2%NOTFOUND;
v_hash_value := dbms_utility.get_hash_value(v_text, 1, 65536);
INSERT INTO schema_compare_temp (
database, object_name, object_text, hash_value
) VALUES (
2, v_view_name, '[' || v_text || ']', v_hash_value
);
END LOOP;
CLOSE c2;
END;
/
SET FEEDBACK ON
COLUMN view_name FORMAT a30 HEADING 'Vue|Nom'
COLUMN schema FORMAT a7 HEADING 'Schéma'
COLUMN hash_value HEADING 'Empreinte'
(
SELECT
a.view_name
, 'Local' schema
, b.hash_value
FROM
user_views a
, schema_compare_temp b
WHERE
b.object_name(+) = a.view_name
AND b.database(+) = 1
AND a.view_name IN (
SELECT view_name
FROM user_views@remote_schema_link
)
MINUS
SELECT
a.view_name
, 'Local' schema
, b.hash_value
FROM
user_views@remote_schema_link a
, schema_compare_temp b
WHERE
b.object_name(+) = a.view_name
AND b.database(+) = 2
)
UNION ALL
(
SELECT
a.view_name
, 'Remote' schema
, b.hash_value
FROM
user_views@remote_schema_link a
, schema_compare_temp b
WHERE
b.object_name(+) = a.view_name
AND b.database(+) = 2
AND a.view_name IN (
SELECT view_name
FROM user_views
)
MINUS
SELECT
a.view_name
, 'Remote' schema
, b.hash_value
FROM
user_views a
, schema_compare_temp b
WHERE
b.object_name(+) = a.view_name
AND b.database(+) = 1
)
ORDER BY 1, 2;
PROMPT
SET HEADING OFF
SET FEEDBACK OFF
SELECT '+----------------------------------------------------------------------+' || chr(10) ||
'| DIFFERENCES DE FILES D''ATTENTE (JOBS) |' || chr(10) ||
'+----------------------------------------------------------------------+'
FROM dual;
SET HEADING ON
SET FEEDBACK ON
PROMPT
PROMPT ========================================================
PROMPT Différences sur les jobs planifiés
PROMPT ========================================================
COLUMN what FORMAT a30 HEADING 'Action'
COLUMN interval FORMAT a30 HEADING 'Intervalle'
COLUMN broken FORMAT a7 HEADING 'Cassé ?'
(
SELECT
what
, interval
, broken
, 'Remote' schema
FROM
user_jobs@remote_schema_link
MINUS
SELECT
what
, interval
, broken
, 'Remote' schema
FROM
user_jobs
)
UNION ALL
(
SELECT
what
, interval
, broken
, 'Local' schema
FROM
user_jobs
MINUS
SELECT
what
, interval
, broken
, 'Local' schema
FROM
user_jobs@remote_schema_link
)
ORDER BY 1, 2, 3;
PROMPT
SET HEADING OFF
SET FEEDBACK OFF
SELECT '+----------------------------------------------------------------------+' || chr(10) ||
'| DIFFERENCES DE DATABASE LINKS |' || chr(10) ||
'+----------------------------------------------------------------------+'
FROM dual;
SET HEADING ON
SET FEEDBACK ON
PROMPT
PROMPT ========================================================
PROMPT Différences de database links
PROMPT ========================================================
COLUMN db_link FORMAT a30 HEADING 'DB Link'
COLUMN schema FORMAT a7 HEADING 'Schéma'
COLUMN username FORMAT a20 HEADING 'Utilisateur'
COLUMN host FORMAT a20 HEADING 'Hôte'
(
SELECT
db_link
, 'Remote' schema
, username
, host
FROM
user_db_links@remote_schema_link
MINUS
SELECT
db_link
, 'Remote' schema
, username, host
FROM
user_db_links
)
UNION ALL
(
SELECT
db_link
, 'Local' schema
, username, host
FROM
user_db_links
WHERE
db_link NOT LIKE 'REMOTE_SCHEMA_LINK.%'
MINUS
SELECT
db_link
, 'Local' schema
, username
, host
FROM
user_db_links@remote_schema_link
)
ORDER BY 1, 2;
SPOOL OFF
SET TERMOUT ON
PROMPT
PROMPT =============
PROMPT FIN DU RAPPORT
PROMPT =============
PROMPT
PROMPT Rapport enregistré dans &report_name
PROMPT ==============================================================
SET FEEDBACK OFF
DROP TABLE schema_compare_temp;
DROP DATABASE LINK remote_schema_link;
DROP FUNCTION getLongText;
DROP FUNCTION getLongText2;
SET FEEDBACK 6
Colonnes / sortie
Le rapport est structuré en grandes sections, chacune annoncée par un bandeau +---+ :
- Résumé des objets — comptage des objets manquants/en trop par type, des deux côtés.
- Différences de privilèges — rôles, privilèges système, privilèges sur objets.
- Différences d'objets — détail nommé des objets manquants, en trop, ou invalides.
- Différences de colonnes de table — colonnes absentes ou de type différent entre les deux schémas.
- Différences d'index et de contraintes — écarts de définition pour les objets existant des deux côtés.
- Différences de séquences, de synonymes privés, de database links.
- Différences PL/SQL — comparaison du code source (sensible et insensible à la casse) des packages/procédures/fonctions.
- Différences de triggers et de vues — comparées via une empreinte (hash) du corps normalisé, pour ignorer les différences de mise en forme (espaces, tabulations, retours à la ligne).
- Différences de jobs planifiés.
Points de vigilance
- Le mot de passe du schéma distant est saisi en clair dans l'invite SQL*Plus (option
HIDE, donc non affiché à l'écran) puis utilisé pour créer un database link — pensez à révoquer/recréer ce compte ou changer son mot de passe si le script est exécuté dans un contexte partagé. - Le script doit se terminer normalement pour que les objets temporaires (database link, table, fonctions) soient bien supprimés ; en cas d'interruption (CTRL-C, erreur), il faut les supprimer manuellement.
- Sur un schéma volumineux, la comparaison du code source et des vues (hachage complet du texte) peut être longue.
Voir aussi
- Dba column constraints — équivalent en plus léger, limité aux contraintes d'une seule table.
- Dba errors — pour investiguer spécifiquement les objets invalides détectés par la section « Objets invalides ».