Loading config/autoload/global.php +12 −12 Original line number Diff line number Diff line Loading @@ -673,13 +673,13 @@ const CONFIG_SYNCHROS = [ 'source' => [ 'name' => 'SyGAL', 'code' => 'app', 'table' => 'src_pre_structure', 'table' => 'SRC_PRE_STRUCTURE', 'connection' => 'default', 'source_code_column' => 'SOURCE_CODE', ], 'destination' => [ 'name' => 'Application', 'table' => 'pre_structure', 'table' => 'PRE_STRUCTURE', 'connection' => 'default', 'source_code_column' => 'SOURCE_CODE', 'id_strategy' => null, Loading Loading @@ -711,13 +711,13 @@ const CONFIG_SYNCHROS = [ 'source' => [ 'name' => 'SyGAL', 'code' => 'app', 'table' => 'src_pre_etablissement', 'table' => 'SRC_PRE_ETABLISSEMENT', 'connection' => 'default', 'source_code_column' => 'SOURCE_CODE', ], 'destination' => [ 'name' => 'Application', 'table' => 'pre_etablissement', 'table' => 'PRE_ETABLISSEMENT', 'connection' => 'default', 'source_code_column' => 'SOURCE_CODE', 'id_strategy' => null, Loading Loading @@ -749,13 +749,13 @@ const CONFIG_SYNCHROS = [ 'source' => [ 'name' => 'SyGAL', 'code' => 'app', 'table' => 'src_pre_ecole_doct', 'table' => 'SRC_PRE_ECOLE_DOCT', 'connection' => 'default', 'source_code_column' => 'SOURCE_CODE', ], 'destination' => [ 'name' => 'Application', 'table' => 'pre_ecole_doct', 'table' => 'PRE_ECOLE_DOCT', 'connection' => 'default', 'source_code_column' => 'SOURCE_CODE', 'id_strategy' => null, Loading Loading @@ -787,13 +787,13 @@ const CONFIG_SYNCHROS = [ 'source' => [ 'name' => 'SyGAL', 'code' => 'app', 'table' => 'src_pre_unite_rech', 'table' => 'SRC_PRE_UNITE_RECH', 'connection' => 'default', 'source_code_column' => 'SOURCE_CODE', ], 'destination' => [ 'name' => 'Application', 'table' => 'pre_unite_rech', 'table' => 'PRE_UNITE_RECH', 'connection' => 'default', 'source_code_column' => 'SOURCE_CODE', 'id_strategy' => null, Loading Loading @@ -825,13 +825,13 @@ const CONFIG_SYNCHROS = [ 'source' => [ 'name' => 'SyGAL', 'code' => 'app', 'table' => 'src_pre_individu', 'table' => 'SRC_PRE_INDIVIDU', 'connection' => 'default', 'source_code_column' => 'SOURCE_CODE', ], 'destination' => [ 'name' => 'Application', 'table' => 'pre_individu', 'table' => 'PRE_INDIVIDU', 'connection' => 'default', 'source_code_column' => 'SOURCE_CODE', 'id_strategy' => null, Loading Loading @@ -863,13 +863,13 @@ const CONFIG_SYNCHROS = [ 'source' => [ 'name' => 'SyGAL', 'code' => 'app', 'table' => 'src_pre_doctorant', 'table' => 'SRC_PRE_DOCTORANT', 'connection' => 'default', 'source_code_column' => 'SOURCE_CODE', ], 'destination' => [ 'name' => 'Application', 'table' => 'pre_doctorant', 'table' => 'PRE_DOCTORANT', 'connection' => 'default', 'source_code_column' => 'SOURCE_CODE', 'id_strategy' => null, Loading doc/dev/substitutions.md +23 −29 Original line number Diff line number Diff line Loading @@ -13,12 +13,12 @@ Structures | 12 | INSA::550 | ED550 | **550** | | 17 | UCN::550 | ED550 | **550** | Colonne pour détection de doublon : `code`. Colonne pour détection de doublon (NPD) : `code`. - Leur `code` étant identique, les structures `12` et `17` peuvent faire l'objet d'une substitution : `structure` `pre_structure` | id | source_code | libelle | **code** | |-----|-------------|----------------------------|----------| Loading @@ -29,12 +29,13 @@ Colonne pour détection de doublon : `code`. `structure_substit` | from_id | to_id | |---------|---------| | 12 | 32 | | 17 | 32 | | from_id | to_id | from_npd | |---------|-------|----------| | 12 | 32 | 550 | | 17 | 32 | 550 | | ... | ... | ... | Les enregistrements substitués ne doivent plus être modifiables. NB : `from_id` pointe vers une `pre_structure`, `to_id` pointe vers une `structure`. Dans toutes les tables où il y a une clé étrangère `structure_id`, il faut faire la jointure avec `structure_substit` pour tenir compte de la substitution. Loading @@ -42,7 +43,7 @@ pour tenir compte de la substitution. - Si un nouveau doublon apparaît `structure` `pre_structure` | id | source_code | libelle | **code** | |-----|-------------|----------------------------|----------| Loading @@ -53,15 +54,16 @@ pour tenir compte de la substitution. | ... | ... | ... | ... | | 61 | ULHN::550 | Ecole Doctrale du Bonheur | **550** | Il faut modifier la substitution pour ajouter la structure `61`. Il faut ajouter à la substitution la structure `61`. `structure_substit` | from_id | to_id | |---------|-------| | 12 | 32 | | 17 | 32 | | 61 | 32 | | from_id | to_id | from_npd | |---------|-------|----------| | 12 | 32 | 550 | | 17 | 32 | 550 | | 61 | 32 | 550 | | ... | ... | ... | Loading @@ -77,18 +79,10 @@ Individus | 12 | INSA::12345 | Hochon | Paul | null | | 17 | UCN::ABCD | Hochon | Paul | 2000-01-01 | Colonnes utilisées pour la détection de doublon : `nom`, `prenom`, `date_naissance`. Colonnes utilisées pour la détection de doublon (NPD) : `nom`, `prenom`, `date_naissance`. - Leur `nom`, `prenom`, `date_naissance` étant identiques, les individus `12` et `17` peuvent faire l'objet d'une substitution : `pre_individu` | id | source_code | nom | prenom | date_naissance | |-----|-------------|--------|--------|----------------| | 12 | INSA::12345 | Hochon | Paul | null | | 17 | UCN::ABCD | Hochon | Paul | 2000-01-01 | - Leurs `nom`, `prenom`, `date_naissance` étant identiques, les individus `12` et `17` peuvent faire l'objet d'une substitution : `individu` Loading @@ -96,7 +90,6 @@ Colonnes utilisées pour la détection de doublon : `nom`, `prenom`, `date_nai |-----|-------------|--------|--------|----------------| | 32 | SYGAL::8889 | Hochon | Paul | 2000-01-01 | `individu_substit` | from_id | to_id | from_npd | Loading @@ -104,6 +97,7 @@ Colonnes utilisées pour la détection de doublon : `nom`, `prenom`, `date_nai | 12 | 32 | hochon_paul_ | | 17 | 32 | hochon_paul_2000-01-01 | NB : `from_id` pointe vers un `pre_individu`, `to_id` pointe vers un `individu`. Dans toutes les tables où il y a une clé étrangère `individu_id`, il faut faire la jointure avec `individu_substit` pour tenir compte de la substitution. Loading @@ -112,7 +106,7 @@ Une évolution amélioration (?) ultérieure serait, plutôt que d'utiliser la s directement dans les tables concernées les fk 'individu_id' par l'id de l'individu substituant. ### Un pre_individu est modifié. ### Si un `pre_individu` est modifié. - Calculer son NPD. Loading Loading @@ -152,15 +146,15 @@ directement dans les tables concernées les fk 'individu_id' par l'id de l'indiv ### Un individu est historisé. - Rechercher dans `individu_substit` s'il est substitué. - S'il n'est pas substitué, stop. - S'il n'est pas substitué, STOP. - S'il est substitué, le retirer de la substitution. - S'il ne reste dans la substitution qu'un seul individu substitué, historiser le substituant et la substitution. ### Un individu est supprimé. Remarque : Ne pas gérer la suppression avec un "delete cascade" sur la fk 'individu_substit.individu_substitue_id' car cela retire la possibilité d'amélioration évoquée plus haut (substitution réelle des fk). Remarque : Ne pas gérer la suppression avec un "delete cascade" sur la fk 'individu_substit.from_id' car cela retire la possibilité d'amélioration évoquée plus haut (remplacement des fk). - Idem historisation. Loading tests/database/substitution/01_substitution.sql +91 −42 Original line number Diff line number Diff line Loading @@ -2,10 +2,19 @@ -- Substitutions -- -- --drop table substit_config; -- create table substit_config ( -- param_name varchar(128) primary key, -- param_value text, -- param_value_default text -- ); --drop table substit_log; create table substit_log ( id bigserial primary key, type varchar(128) not null, operation varchar(64) not null, substitue_id bigint, substituant_id bigint not null, npd varchar(256), Loading @@ -27,14 +36,11 @@ select kcu.table_name as source_table, kcu.constraint_name from information_schema.table_constraints tco join information_schema.key_column_usage kcu on tco.constraint_schema = kcu.constraint_schema and tco.constraint_name = kcu.constraint_name on tco.constraint_schema = kcu.constraint_schema and tco.constraint_name = kcu.constraint_name join information_schema.referential_constraints rco on tco.constraint_schema = rco.constraint_schema and tco.constraint_name = rco.constraint_name on tco.constraint_schema = rco.constraint_schema and tco.constraint_name = rco.constraint_name join information_schema.table_constraints rel_tco on rco.unique_constraint_schema = rel_tco.constraint_schema and rco.unique_constraint_name = rel_tco.constraint_name on rco.unique_constraint_schema = rel_tco.constraint_schema and rco.unique_constraint_name = rel_tco.constraint_name where tco.constraint_type = 'FOREIGN KEY'; Loading Loading @@ -104,6 +110,7 @@ $$; create or replace function substit_insert_log(type varchar, operation varchar, substitue_id bigint, substituant_id bigint, npd varchar, Loading @@ -111,8 +118,8 @@ create or replace function substit_insert_log(type varchar, language plpgsql as $$begin insert into substit_log(type, substitue_id, substituant_id, npd, log) values (type, substitue_id, substituant_id, npd, log); insert into substit_log(type, operation, substitue_id, substituant_id, npd, log) values (type, operation, substitue_id, substituant_id, npd, log); end $$; Loading @@ -138,7 +145,7 @@ begin if found then execute format('select * from substit_fetch_data_for_substituant_%s(%L) limit 1', type, p_npd) into data; execute format('select substit_create_substituant_%s($1)', type) using data into substituant_record_id; perform substit_insert_log(type, null, substituant_record_id, p_npd, perform substit_insert_log(type, 'SUBSTITUANT_CREATE', null, substituant_record_id, p_npd, format('Nouvel enregistrement substituant : %s', substituant_record_id)); while found loop Loading Loading @@ -166,7 +173,7 @@ $$begin execute format('select substit_update_substituant_%s(%s, $1)', type, p_substituant_id) using data; perform substit_insert_log(type, null, p_substituant_id, null, perform substit_insert_log(type, 'SUBSTITUANT_UPDATE', null, p_substituant_id, null, format('Mise à jour du substituant %s', p_substituant_id)); end $$; Loading @@ -193,7 +200,7 @@ begin while found loop execute format('update %s_substit set histo_destruction = current_timestamp, histo_destructeur_id = 1 where id = %s', type, v_substit_record.id); perform substit_insert_log(type, v_substit_record.from_id, p_substituant_id, v_substit_record.npd, perform substit_insert_log(type, 'SUBSTITUTION_HISTO', v_substit_record.from_id, p_substituant_id, v_substit_record.npd, format('Historisation de la substitution de %s par %s', v_substit_record.from_id, v_substit_record.to_id)); fetch next from v_cursor_substit into v_substit_record; end loop; Loading @@ -201,7 +208,7 @@ begin execute format('update %I set histo_destruction = current_timestamp, histo_destructeur_id = 1 where id = %s', type, p_substituant_id); perform substit_insert_log(type, null, substituant_record_id, null, perform substit_insert_log(type, 'SUBSTITUANT_HISTO', null, substituant_record_id, null, format('Historisation du substituant %s', p_substituant_id)); raise notice '=> Fait.'; Loading @@ -227,7 +234,7 @@ begin execute format('insert into %s_substit (from_id, to_id, npd, histo_createur_id) values (%s, %s, %L, 1)', type, p_substitue_id, p_substituant_id, p_substitue_npd); perform substit_insert_log(type, p_substituant_id, p_substitue_id, p_substitue_npd, perform substit_insert_log(type, 'SUBSTITUE_ADD', p_substituant_id, p_substitue_id, p_substitue_npd, format('Ajout de %s à la substitution par %s', p_substitue_id, p_substituant_id)); end $$; Loading Loading @@ -255,14 +262,13 @@ begin raise notice '=> %', substit_record; perform substit_insert_log(type, p_substituant_id, p_substitue_id, null, perform substit_insert_log(type, 'SUBSTITUTION_HISTO', p_substituant_id, p_substitue_id, null, format('Historisation de la substitution de %s par %s', p_substitue_id, p_substituant_id)); -- mise à jour de la substitution -- mise à jour du substituant execute format('select * from substit_fetch_data_for_substituant_%s(%L) limit 1', type, substit_record.npd) into data; -- NB : si aucune donnée n'est retournée, c'est que la substitution n'a plus de raison d'être (0 doublon). if data is null then raise notice 'Aucune donnée trouvée donc suppression de la substitution...'; raise notice 'Aucune donnée trouvée donc la substitution n''a plus de raison d''être : suppression de la substitution...'; perform substit_delete_substitution(type, substit_record.to_id); else perform substit_update_substituant(type, substit_record.to_id, data); Loading Loading @@ -534,14 +540,54 @@ $$; -- drop function substit_replace_foreign_keys_values; create or replace function substit_replace_foreign_keys_values(p_table varchar, p_from_id bigint, p_to_id bigint) returns void create or replace function substit_replace_foreign_keys_values(type varchar) returns void language plpgsql as $$declare v_cursor refcursor; v_substit_fk record; v_col_name varchar(100); v_tab_name varchar(100); v_message text; begin -- -- Parcours des tables ayant une clé étrangère vers la table spécifiée, pour remplacer les valeurs de clés étrangères. -- NB : Le moindre problème d'unicité stoppe le processus. -- open v_cursor for execute format('select * from v_substit_foreign_keys_%s where target_table = %L and source_table <> %L order by source_table', type, type, type||'_substit'); fetch next from v_cursor into v_substit_fk; while found loop v_tab_name = v_substit_fk.source_table; v_col_name = v_substit_fk.fk_column; v_message = format('Substitution ''%s'', remplacement des FK dans %s : %s', upper(type), upper(v_tab_name), v_col_name); execute format('update %I t set %I = ss.to_id from %I ss where ss.histo_destruction is null and t.%I = ss.from_id', v_tab_name, v_col_name, type||'_substit', v_col_name); raise notice '%', v_message; fetch next from v_cursor into v_substit_fk; end loop; close v_cursor; end $$; -- drop function substit_replace_foreign_keys_values; create or replace function substit_replace_foreign_keys_values(type varchar, p_from_id bigint, p_to_id bigint) returns void language plpgsql as $$declare v_substit_fk v_substit_foreign_keys; v_cursor refcursor; v_substit_fk record; v_col_name varchar(100); v_tab_name varchar(100); v_id bigint; v_count int; v_message text; begin -- Loading @@ -549,40 +595,43 @@ begin -- Les remplacements qui déclenchent une erreur d'unicité ne sont pas gérés : il ne sont tout simplement pas faits. -- for v_substit_fk in select * from v_substit_foreign_keys where target_table = p_table and source_table not in (p_table, 'substit_'||p_table, 'etablissement', 'ecole_doct', 'unite_rech') -- todo : à virer, ce n'est pas la solution ! order by source_table loop open v_cursor for execute format('select * from v_substit_foreign_keys_%s where target_table = %L and source_table <> %L order by source_table', type, type, type||'_substit'); fetch next from v_cursor into v_substit_fk; while found loop v_tab_name = v_substit_fk.source_table; v_col_name = v_substit_fk.fk_column; ---------> todo : >>>>>>>>>>> empecher le remplacement de certaines FK précises <<<<<<<<<<<< v_message = format('Correction de la FK %L dans %L : ', v_col_name, v_tab_name); v_message = format('Substitution ''%s %s => %s'', remplacement de FK dans %s : %s = %s', upper(type), p_from_id, p_to_id, upper(v_tab_name), v_col_name, p_from_id); -- Remplacement éventuelle de la valeur de la clé étrangère. -- NB : pas possible d'écarter les historisés et de màj histo_modification car toutes les tables n'ont pas les colonnes histo_* ! begin for v_id in execute format('update %I set %I = %s where %I = %s returning id', v_tab_name, v_col_name, p_to_id, v_col_name, p_from_id, v_tab_name) loop perform substit_insert_log(p_table, p_from_id, p_to_id, null, v_message || format('id %s : %s => %s', v_id, p_from_id, p_to_id)); v_message = v_message || chr(10) || format('- id %s : %s => %s', v_id, p_from_id, p_to_id); v_count = 0; for v_id in execute format('update %I set %I = %s where %I = %s returning id', v_tab_name, v_col_name, p_to_id, v_col_name, p_from_id) loop v_count = v_count + 1; v_message = v_message || format(' remplacée par %s (id %s)', p_to_id, v_id); perform substit_insert_log(type, 'FK_REPLACE', p_from_id, p_to_id, null, v_message); raise notice '%', v_message; end loop; if v_id is not null then if v_count = 0 then v_message = v_message || ' non trouvée'; raise notice '%', v_message; else raise notice '% remplacements effectués', v_count; end if; exception WHEN unique_violation THEN -- échec du remplacement à cause d'une erreur d'unicité perform substit_insert_log(p_table, p_from_id, p_to_id, null, v_message || format('%s => %s impossible (problème d''unicité)', p_from_id, p_to_id)); v_message = v_message || chr(10) || format('- %s => %s impossible (problème d''unicité)', p_from_id, p_to_id); raise notice '%', v_message; perform substit_insert_log(type, 'FK_REPLACE_PROBLEM', p_from_id, p_to_id, null, v_message || format(': %s => %s impossible (problème d''unicité)', p_from_id, p_to_id)); raise notice '%', v_message || format('%s => %s impossible (problème d''unicité)', p_from_id, p_to_id); -- todo : historiser la ligne puisqu'il y aen a forcément une autre ? (mais la table n'a pas forcément de colonne d'histo) end; fetch next from v_cursor into v_substit_fk; end loop; close v_cursor; end $$; Loading @@ -600,7 +649,7 @@ begin 'update %I set histo_destruction = current_timestamp, histo_destructeur_id = %s where id = %s', type, APP_SOURCE_ID, p_substitue_id); perform substit_insert_log(type, p_substitue_id, p_substituant_id, null, format('Historisation du substitué %s', p_substitue_id)); perform substit_insert_log(type, 'SUBSTITUE_HISTO', p_substitue_id, p_substituant_id, null, format('Historisation du substitué %s', p_substitue_id)); end $$; Loading Loading @@ -631,6 +680,6 @@ begin raise notice '%', v_message; perform substit_insert_log(type, p_substitue_id, p_substituant_id, null, v_message); perform substit_insert_log(type, 'SUBSTITE_RESTORE', p_substitue_id, p_substituant_id, null, v_message); end $$; No newline at end of file tests/database/substitution/02_substitution_individu.sql +42 −13 Original line number Diff line number Diff line Loading @@ -4,6 +4,22 @@ --=============================== INDIVIDU ================================- -- -- Vue listant les clés étrangères (FK) pointant vers 'individu' -- dont la valeur doit être remplacée par l'id substituant éventuel. -- -- drop view v_substit_foreign_keys_individu; create or replace view v_substit_foreign_keys_individu as select * from v_substit_foreign_keys where target_table = 'individu' and source_table <> 'individu' and source_table <> 'individu_substit' and not ( source_table = 'doctorant' and fk_column = 'individu_id' ) ; -- -- Mise à jour table INDIVIDU -- Loading Loading @@ -73,13 +89,26 @@ create or replace view src_pre_individu as left join pays p on p.code_pays_apogee::text = tmp.cod_pay_nat::text; drop view if exists v_diff_individu; drop view if exists src_individu; --drop view if exists v_diff_individu; drop view if exists src_individu; create or replace view src_individu as select pre.* select id, source_id, source_code, type, civilite, nom_usuel, nom_patronymique, prenom1, prenom2, prenom3, email, date_naissance, nationalite, supann_id, pays_id_nationalite from pre_individu pre where pre.histo_destruction is null and not exists ( select * from individu_substit where histo_destruction is null and from_id = pre.id select id from individu_substit where histo_destruction is null and from_id = pre.id ); Loading Loading @@ -130,15 +159,15 @@ $$begin -- définies dans la table 'xxxx_substit'. -- > Dans le cas où cela ne change rien du tout aux substitutions existantes, il faudra tout de même : -- - mettre à jour les valeurs dans la colonne 'npd' de la table 'xxxx_substit' en faisant appel -- à la fonction 'substit_npd_xxxx()'; -- à la présente fonction; -- - mettre à jour manuellement les valeurs dans la colonne 'npd_force" de la table 'pre_xxxx'. -- > Dans le cas où cela invalide des substitutions existantes, il faudra : -- - historiser les substitutions concernées dans la table 'xxxx_substit' ; -- - mettre à jour manuellement les valeurs dans la colonne 'npd_force" de la table 'pre_xxxx'. -- return normalized_string(individu.nom_patronymique) || '_' || normalized_string(individu.prenom1) || '_' || return normalized_string(trim(individu.nom_patronymique)) || '_' || normalized_string(trim(individu.prenom1)) || '_' || normalized_string(coalesce(date(individu.date_naissance)::varchar, '')); end; $$; Loading Loading @@ -205,12 +234,12 @@ $$begin select mode() within group (order by i.type) as type, mode() within group (order by i.civilite) as civilite, mode() within group (order by i.nom_patronymique) as nom_patronymique, mode() within group (order by i.nom_usuel) as nom_usuel, mode() within group (order by i.prenom1) as prenom1, mode() within group (order by i.prenom2) as prenom2, mode() within group (order by i.prenom3) as prenom3, mode() within group (order by i.email) as email, mode() within group (order by trim(i.nom_patronymique)) as nom_patronymique, mode() within group (order by trim(i.nom_usuel)) as nom_usuel, mode() within group (order by trim(i.prenom1)) as prenom1, mode() within group (order by trim(i.prenom2)) as prenom2, mode() within group (order by trim(i.prenom3)) as prenom3, mode() within group (order by trim(i.email)) as email, mode() within group (order by i.date_naissance) as date_naissance, mode() within group (order by i.nationalite) as nationalite, mode() within group (order by i.supann_id) as supann_id, Loading tests/database/substitution/03_substitution_structure.sql +30 −7 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
config/autoload/global.php +12 −12 Original line number Diff line number Diff line Loading @@ -673,13 +673,13 @@ const CONFIG_SYNCHROS = [ 'source' => [ 'name' => 'SyGAL', 'code' => 'app', 'table' => 'src_pre_structure', 'table' => 'SRC_PRE_STRUCTURE', 'connection' => 'default', 'source_code_column' => 'SOURCE_CODE', ], 'destination' => [ 'name' => 'Application', 'table' => 'pre_structure', 'table' => 'PRE_STRUCTURE', 'connection' => 'default', 'source_code_column' => 'SOURCE_CODE', 'id_strategy' => null, Loading Loading @@ -711,13 +711,13 @@ const CONFIG_SYNCHROS = [ 'source' => [ 'name' => 'SyGAL', 'code' => 'app', 'table' => 'src_pre_etablissement', 'table' => 'SRC_PRE_ETABLISSEMENT', 'connection' => 'default', 'source_code_column' => 'SOURCE_CODE', ], 'destination' => [ 'name' => 'Application', 'table' => 'pre_etablissement', 'table' => 'PRE_ETABLISSEMENT', 'connection' => 'default', 'source_code_column' => 'SOURCE_CODE', 'id_strategy' => null, Loading Loading @@ -749,13 +749,13 @@ const CONFIG_SYNCHROS = [ 'source' => [ 'name' => 'SyGAL', 'code' => 'app', 'table' => 'src_pre_ecole_doct', 'table' => 'SRC_PRE_ECOLE_DOCT', 'connection' => 'default', 'source_code_column' => 'SOURCE_CODE', ], 'destination' => [ 'name' => 'Application', 'table' => 'pre_ecole_doct', 'table' => 'PRE_ECOLE_DOCT', 'connection' => 'default', 'source_code_column' => 'SOURCE_CODE', 'id_strategy' => null, Loading Loading @@ -787,13 +787,13 @@ const CONFIG_SYNCHROS = [ 'source' => [ 'name' => 'SyGAL', 'code' => 'app', 'table' => 'src_pre_unite_rech', 'table' => 'SRC_PRE_UNITE_RECH', 'connection' => 'default', 'source_code_column' => 'SOURCE_CODE', ], 'destination' => [ 'name' => 'Application', 'table' => 'pre_unite_rech', 'table' => 'PRE_UNITE_RECH', 'connection' => 'default', 'source_code_column' => 'SOURCE_CODE', 'id_strategy' => null, Loading Loading @@ -825,13 +825,13 @@ const CONFIG_SYNCHROS = [ 'source' => [ 'name' => 'SyGAL', 'code' => 'app', 'table' => 'src_pre_individu', 'table' => 'SRC_PRE_INDIVIDU', 'connection' => 'default', 'source_code_column' => 'SOURCE_CODE', ], 'destination' => [ 'name' => 'Application', 'table' => 'pre_individu', 'table' => 'PRE_INDIVIDU', 'connection' => 'default', 'source_code_column' => 'SOURCE_CODE', 'id_strategy' => null, Loading Loading @@ -863,13 +863,13 @@ const CONFIG_SYNCHROS = [ 'source' => [ 'name' => 'SyGAL', 'code' => 'app', 'table' => 'src_pre_doctorant', 'table' => 'SRC_PRE_DOCTORANT', 'connection' => 'default', 'source_code_column' => 'SOURCE_CODE', ], 'destination' => [ 'name' => 'Application', 'table' => 'pre_doctorant', 'table' => 'PRE_DOCTORANT', 'connection' => 'default', 'source_code_column' => 'SOURCE_CODE', 'id_strategy' => null, Loading
doc/dev/substitutions.md +23 −29 Original line number Diff line number Diff line Loading @@ -13,12 +13,12 @@ Structures | 12 | INSA::550 | ED550 | **550** | | 17 | UCN::550 | ED550 | **550** | Colonne pour détection de doublon : `code`. Colonne pour détection de doublon (NPD) : `code`. - Leur `code` étant identique, les structures `12` et `17` peuvent faire l'objet d'une substitution : `structure` `pre_structure` | id | source_code | libelle | **code** | |-----|-------------|----------------------------|----------| Loading @@ -29,12 +29,13 @@ Colonne pour détection de doublon : `code`. `structure_substit` | from_id | to_id | |---------|---------| | 12 | 32 | | 17 | 32 | | from_id | to_id | from_npd | |---------|-------|----------| | 12 | 32 | 550 | | 17 | 32 | 550 | | ... | ... | ... | Les enregistrements substitués ne doivent plus être modifiables. NB : `from_id` pointe vers une `pre_structure`, `to_id` pointe vers une `structure`. Dans toutes les tables où il y a une clé étrangère `structure_id`, il faut faire la jointure avec `structure_substit` pour tenir compte de la substitution. Loading @@ -42,7 +43,7 @@ pour tenir compte de la substitution. - Si un nouveau doublon apparaît `structure` `pre_structure` | id | source_code | libelle | **code** | |-----|-------------|----------------------------|----------| Loading @@ -53,15 +54,16 @@ pour tenir compte de la substitution. | ... | ... | ... | ... | | 61 | ULHN::550 | Ecole Doctrale du Bonheur | **550** | Il faut modifier la substitution pour ajouter la structure `61`. Il faut ajouter à la substitution la structure `61`. `structure_substit` | from_id | to_id | |---------|-------| | 12 | 32 | | 17 | 32 | | 61 | 32 | | from_id | to_id | from_npd | |---------|-------|----------| | 12 | 32 | 550 | | 17 | 32 | 550 | | 61 | 32 | 550 | | ... | ... | ... | Loading @@ -77,18 +79,10 @@ Individus | 12 | INSA::12345 | Hochon | Paul | null | | 17 | UCN::ABCD | Hochon | Paul | 2000-01-01 | Colonnes utilisées pour la détection de doublon : `nom`, `prenom`, `date_naissance`. Colonnes utilisées pour la détection de doublon (NPD) : `nom`, `prenom`, `date_naissance`. - Leur `nom`, `prenom`, `date_naissance` étant identiques, les individus `12` et `17` peuvent faire l'objet d'une substitution : `pre_individu` | id | source_code | nom | prenom | date_naissance | |-----|-------------|--------|--------|----------------| | 12 | INSA::12345 | Hochon | Paul | null | | 17 | UCN::ABCD | Hochon | Paul | 2000-01-01 | - Leurs `nom`, `prenom`, `date_naissance` étant identiques, les individus `12` et `17` peuvent faire l'objet d'une substitution : `individu` Loading @@ -96,7 +90,6 @@ Colonnes utilisées pour la détection de doublon : `nom`, `prenom`, `date_nai |-----|-------------|--------|--------|----------------| | 32 | SYGAL::8889 | Hochon | Paul | 2000-01-01 | `individu_substit` | from_id | to_id | from_npd | Loading @@ -104,6 +97,7 @@ Colonnes utilisées pour la détection de doublon : `nom`, `prenom`, `date_nai | 12 | 32 | hochon_paul_ | | 17 | 32 | hochon_paul_2000-01-01 | NB : `from_id` pointe vers un `pre_individu`, `to_id` pointe vers un `individu`. Dans toutes les tables où il y a une clé étrangère `individu_id`, il faut faire la jointure avec `individu_substit` pour tenir compte de la substitution. Loading @@ -112,7 +106,7 @@ Une évolution amélioration (?) ultérieure serait, plutôt que d'utiliser la s directement dans les tables concernées les fk 'individu_id' par l'id de l'individu substituant. ### Un pre_individu est modifié. ### Si un `pre_individu` est modifié. - Calculer son NPD. Loading Loading @@ -152,15 +146,15 @@ directement dans les tables concernées les fk 'individu_id' par l'id de l'indiv ### Un individu est historisé. - Rechercher dans `individu_substit` s'il est substitué. - S'il n'est pas substitué, stop. - S'il n'est pas substitué, STOP. - S'il est substitué, le retirer de la substitution. - S'il ne reste dans la substitution qu'un seul individu substitué, historiser le substituant et la substitution. ### Un individu est supprimé. Remarque : Ne pas gérer la suppression avec un "delete cascade" sur la fk 'individu_substit.individu_substitue_id' car cela retire la possibilité d'amélioration évoquée plus haut (substitution réelle des fk). Remarque : Ne pas gérer la suppression avec un "delete cascade" sur la fk 'individu_substit.from_id' car cela retire la possibilité d'amélioration évoquée plus haut (remplacement des fk). - Idem historisation. Loading
tests/database/substitution/01_substitution.sql +91 −42 Original line number Diff line number Diff line Loading @@ -2,10 +2,19 @@ -- Substitutions -- -- --drop table substit_config; -- create table substit_config ( -- param_name varchar(128) primary key, -- param_value text, -- param_value_default text -- ); --drop table substit_log; create table substit_log ( id bigserial primary key, type varchar(128) not null, operation varchar(64) not null, substitue_id bigint, substituant_id bigint not null, npd varchar(256), Loading @@ -27,14 +36,11 @@ select kcu.table_name as source_table, kcu.constraint_name from information_schema.table_constraints tco join information_schema.key_column_usage kcu on tco.constraint_schema = kcu.constraint_schema and tco.constraint_name = kcu.constraint_name on tco.constraint_schema = kcu.constraint_schema and tco.constraint_name = kcu.constraint_name join information_schema.referential_constraints rco on tco.constraint_schema = rco.constraint_schema and tco.constraint_name = rco.constraint_name on tco.constraint_schema = rco.constraint_schema and tco.constraint_name = rco.constraint_name join information_schema.table_constraints rel_tco on rco.unique_constraint_schema = rel_tco.constraint_schema and rco.unique_constraint_name = rel_tco.constraint_name on rco.unique_constraint_schema = rel_tco.constraint_schema and rco.unique_constraint_name = rel_tco.constraint_name where tco.constraint_type = 'FOREIGN KEY'; Loading Loading @@ -104,6 +110,7 @@ $$; create or replace function substit_insert_log(type varchar, operation varchar, substitue_id bigint, substituant_id bigint, npd varchar, Loading @@ -111,8 +118,8 @@ create or replace function substit_insert_log(type varchar, language plpgsql as $$begin insert into substit_log(type, substitue_id, substituant_id, npd, log) values (type, substitue_id, substituant_id, npd, log); insert into substit_log(type, operation, substitue_id, substituant_id, npd, log) values (type, operation, substitue_id, substituant_id, npd, log); end $$; Loading @@ -138,7 +145,7 @@ begin if found then execute format('select * from substit_fetch_data_for_substituant_%s(%L) limit 1', type, p_npd) into data; execute format('select substit_create_substituant_%s($1)', type) using data into substituant_record_id; perform substit_insert_log(type, null, substituant_record_id, p_npd, perform substit_insert_log(type, 'SUBSTITUANT_CREATE', null, substituant_record_id, p_npd, format('Nouvel enregistrement substituant : %s', substituant_record_id)); while found loop Loading Loading @@ -166,7 +173,7 @@ $$begin execute format('select substit_update_substituant_%s(%s, $1)', type, p_substituant_id) using data; perform substit_insert_log(type, null, p_substituant_id, null, perform substit_insert_log(type, 'SUBSTITUANT_UPDATE', null, p_substituant_id, null, format('Mise à jour du substituant %s', p_substituant_id)); end $$; Loading @@ -193,7 +200,7 @@ begin while found loop execute format('update %s_substit set histo_destruction = current_timestamp, histo_destructeur_id = 1 where id = %s', type, v_substit_record.id); perform substit_insert_log(type, v_substit_record.from_id, p_substituant_id, v_substit_record.npd, perform substit_insert_log(type, 'SUBSTITUTION_HISTO', v_substit_record.from_id, p_substituant_id, v_substit_record.npd, format('Historisation de la substitution de %s par %s', v_substit_record.from_id, v_substit_record.to_id)); fetch next from v_cursor_substit into v_substit_record; end loop; Loading @@ -201,7 +208,7 @@ begin execute format('update %I set histo_destruction = current_timestamp, histo_destructeur_id = 1 where id = %s', type, p_substituant_id); perform substit_insert_log(type, null, substituant_record_id, null, perform substit_insert_log(type, 'SUBSTITUANT_HISTO', null, substituant_record_id, null, format('Historisation du substituant %s', p_substituant_id)); raise notice '=> Fait.'; Loading @@ -227,7 +234,7 @@ begin execute format('insert into %s_substit (from_id, to_id, npd, histo_createur_id) values (%s, %s, %L, 1)', type, p_substitue_id, p_substituant_id, p_substitue_npd); perform substit_insert_log(type, p_substituant_id, p_substitue_id, p_substitue_npd, perform substit_insert_log(type, 'SUBSTITUE_ADD', p_substituant_id, p_substitue_id, p_substitue_npd, format('Ajout de %s à la substitution par %s', p_substitue_id, p_substituant_id)); end $$; Loading Loading @@ -255,14 +262,13 @@ begin raise notice '=> %', substit_record; perform substit_insert_log(type, p_substituant_id, p_substitue_id, null, perform substit_insert_log(type, 'SUBSTITUTION_HISTO', p_substituant_id, p_substitue_id, null, format('Historisation de la substitution de %s par %s', p_substitue_id, p_substituant_id)); -- mise à jour de la substitution -- mise à jour du substituant execute format('select * from substit_fetch_data_for_substituant_%s(%L) limit 1', type, substit_record.npd) into data; -- NB : si aucune donnée n'est retournée, c'est que la substitution n'a plus de raison d'être (0 doublon). if data is null then raise notice 'Aucune donnée trouvée donc suppression de la substitution...'; raise notice 'Aucune donnée trouvée donc la substitution n''a plus de raison d''être : suppression de la substitution...'; perform substit_delete_substitution(type, substit_record.to_id); else perform substit_update_substituant(type, substit_record.to_id, data); Loading Loading @@ -534,14 +540,54 @@ $$; -- drop function substit_replace_foreign_keys_values; create or replace function substit_replace_foreign_keys_values(p_table varchar, p_from_id bigint, p_to_id bigint) returns void create or replace function substit_replace_foreign_keys_values(type varchar) returns void language plpgsql as $$declare v_cursor refcursor; v_substit_fk record; v_col_name varchar(100); v_tab_name varchar(100); v_message text; begin -- -- Parcours des tables ayant une clé étrangère vers la table spécifiée, pour remplacer les valeurs de clés étrangères. -- NB : Le moindre problème d'unicité stoppe le processus. -- open v_cursor for execute format('select * from v_substit_foreign_keys_%s where target_table = %L and source_table <> %L order by source_table', type, type, type||'_substit'); fetch next from v_cursor into v_substit_fk; while found loop v_tab_name = v_substit_fk.source_table; v_col_name = v_substit_fk.fk_column; v_message = format('Substitution ''%s'', remplacement des FK dans %s : %s', upper(type), upper(v_tab_name), v_col_name); execute format('update %I t set %I = ss.to_id from %I ss where ss.histo_destruction is null and t.%I = ss.from_id', v_tab_name, v_col_name, type||'_substit', v_col_name); raise notice '%', v_message; fetch next from v_cursor into v_substit_fk; end loop; close v_cursor; end $$; -- drop function substit_replace_foreign_keys_values; create or replace function substit_replace_foreign_keys_values(type varchar, p_from_id bigint, p_to_id bigint) returns void language plpgsql as $$declare v_substit_fk v_substit_foreign_keys; v_cursor refcursor; v_substit_fk record; v_col_name varchar(100); v_tab_name varchar(100); v_id bigint; v_count int; v_message text; begin -- Loading @@ -549,40 +595,43 @@ begin -- Les remplacements qui déclenchent une erreur d'unicité ne sont pas gérés : il ne sont tout simplement pas faits. -- for v_substit_fk in select * from v_substit_foreign_keys where target_table = p_table and source_table not in (p_table, 'substit_'||p_table, 'etablissement', 'ecole_doct', 'unite_rech') -- todo : à virer, ce n'est pas la solution ! order by source_table loop open v_cursor for execute format('select * from v_substit_foreign_keys_%s where target_table = %L and source_table <> %L order by source_table', type, type, type||'_substit'); fetch next from v_cursor into v_substit_fk; while found loop v_tab_name = v_substit_fk.source_table; v_col_name = v_substit_fk.fk_column; ---------> todo : >>>>>>>>>>> empecher le remplacement de certaines FK précises <<<<<<<<<<<< v_message = format('Correction de la FK %L dans %L : ', v_col_name, v_tab_name); v_message = format('Substitution ''%s %s => %s'', remplacement de FK dans %s : %s = %s', upper(type), p_from_id, p_to_id, upper(v_tab_name), v_col_name, p_from_id); -- Remplacement éventuelle de la valeur de la clé étrangère. -- NB : pas possible d'écarter les historisés et de màj histo_modification car toutes les tables n'ont pas les colonnes histo_* ! begin for v_id in execute format('update %I set %I = %s where %I = %s returning id', v_tab_name, v_col_name, p_to_id, v_col_name, p_from_id, v_tab_name) loop perform substit_insert_log(p_table, p_from_id, p_to_id, null, v_message || format('id %s : %s => %s', v_id, p_from_id, p_to_id)); v_message = v_message || chr(10) || format('- id %s : %s => %s', v_id, p_from_id, p_to_id); v_count = 0; for v_id in execute format('update %I set %I = %s where %I = %s returning id', v_tab_name, v_col_name, p_to_id, v_col_name, p_from_id) loop v_count = v_count + 1; v_message = v_message || format(' remplacée par %s (id %s)', p_to_id, v_id); perform substit_insert_log(type, 'FK_REPLACE', p_from_id, p_to_id, null, v_message); raise notice '%', v_message; end loop; if v_id is not null then if v_count = 0 then v_message = v_message || ' non trouvée'; raise notice '%', v_message; else raise notice '% remplacements effectués', v_count; end if; exception WHEN unique_violation THEN -- échec du remplacement à cause d'une erreur d'unicité perform substit_insert_log(p_table, p_from_id, p_to_id, null, v_message || format('%s => %s impossible (problème d''unicité)', p_from_id, p_to_id)); v_message = v_message || chr(10) || format('- %s => %s impossible (problème d''unicité)', p_from_id, p_to_id); raise notice '%', v_message; perform substit_insert_log(type, 'FK_REPLACE_PROBLEM', p_from_id, p_to_id, null, v_message || format(': %s => %s impossible (problème d''unicité)', p_from_id, p_to_id)); raise notice '%', v_message || format('%s => %s impossible (problème d''unicité)', p_from_id, p_to_id); -- todo : historiser la ligne puisqu'il y aen a forcément une autre ? (mais la table n'a pas forcément de colonne d'histo) end; fetch next from v_cursor into v_substit_fk; end loop; close v_cursor; end $$; Loading @@ -600,7 +649,7 @@ begin 'update %I set histo_destruction = current_timestamp, histo_destructeur_id = %s where id = %s', type, APP_SOURCE_ID, p_substitue_id); perform substit_insert_log(type, p_substitue_id, p_substituant_id, null, format('Historisation du substitué %s', p_substitue_id)); perform substit_insert_log(type, 'SUBSTITUE_HISTO', p_substitue_id, p_substituant_id, null, format('Historisation du substitué %s', p_substitue_id)); end $$; Loading Loading @@ -631,6 +680,6 @@ begin raise notice '%', v_message; perform substit_insert_log(type, p_substitue_id, p_substituant_id, null, v_message); perform substit_insert_log(type, 'SUBSTITE_RESTORE', p_substitue_id, p_substituant_id, null, v_message); end $$; No newline at end of file
tests/database/substitution/02_substitution_individu.sql +42 −13 Original line number Diff line number Diff line Loading @@ -4,6 +4,22 @@ --=============================== INDIVIDU ================================- -- -- Vue listant les clés étrangères (FK) pointant vers 'individu' -- dont la valeur doit être remplacée par l'id substituant éventuel. -- -- drop view v_substit_foreign_keys_individu; create or replace view v_substit_foreign_keys_individu as select * from v_substit_foreign_keys where target_table = 'individu' and source_table <> 'individu' and source_table <> 'individu_substit' and not ( source_table = 'doctorant' and fk_column = 'individu_id' ) ; -- -- Mise à jour table INDIVIDU -- Loading Loading @@ -73,13 +89,26 @@ create or replace view src_pre_individu as left join pays p on p.code_pays_apogee::text = tmp.cod_pay_nat::text; drop view if exists v_diff_individu; drop view if exists src_individu; --drop view if exists v_diff_individu; drop view if exists src_individu; create or replace view src_individu as select pre.* select id, source_id, source_code, type, civilite, nom_usuel, nom_patronymique, prenom1, prenom2, prenom3, email, date_naissance, nationalite, supann_id, pays_id_nationalite from pre_individu pre where pre.histo_destruction is null and not exists ( select * from individu_substit where histo_destruction is null and from_id = pre.id select id from individu_substit where histo_destruction is null and from_id = pre.id ); Loading Loading @@ -130,15 +159,15 @@ $$begin -- définies dans la table 'xxxx_substit'. -- > Dans le cas où cela ne change rien du tout aux substitutions existantes, il faudra tout de même : -- - mettre à jour les valeurs dans la colonne 'npd' de la table 'xxxx_substit' en faisant appel -- à la fonction 'substit_npd_xxxx()'; -- à la présente fonction; -- - mettre à jour manuellement les valeurs dans la colonne 'npd_force" de la table 'pre_xxxx'. -- > Dans le cas où cela invalide des substitutions existantes, il faudra : -- - historiser les substitutions concernées dans la table 'xxxx_substit' ; -- - mettre à jour manuellement les valeurs dans la colonne 'npd_force" de la table 'pre_xxxx'. -- return normalized_string(individu.nom_patronymique) || '_' || normalized_string(individu.prenom1) || '_' || return normalized_string(trim(individu.nom_patronymique)) || '_' || normalized_string(trim(individu.prenom1)) || '_' || normalized_string(coalesce(date(individu.date_naissance)::varchar, '')); end; $$; Loading Loading @@ -205,12 +234,12 @@ $$begin select mode() within group (order by i.type) as type, mode() within group (order by i.civilite) as civilite, mode() within group (order by i.nom_patronymique) as nom_patronymique, mode() within group (order by i.nom_usuel) as nom_usuel, mode() within group (order by i.prenom1) as prenom1, mode() within group (order by i.prenom2) as prenom2, mode() within group (order by i.prenom3) as prenom3, mode() within group (order by i.email) as email, mode() within group (order by trim(i.nom_patronymique)) as nom_patronymique, mode() within group (order by trim(i.nom_usuel)) as nom_usuel, mode() within group (order by trim(i.prenom1)) as prenom1, mode() within group (order by trim(i.prenom2)) as prenom2, mode() within group (order by trim(i.prenom3)) as prenom3, mode() within group (order by trim(i.email)) as email, mode() within group (order by i.date_naissance) as date_naissance, mode() within group (order by i.nationalite) as nationalite, mode() within group (order by i.supann_id) as supann_id, Loading
tests/database/substitution/03_substitution_structure.sql +30 −7 File changed.Preview size limit exceeded, changes collapsed. Show changes