重複との戦い

動的SQL 使用に関するトピックを続けて、現在のプロジェクトの一部として実装した1つの便利なツールについてお話したいと思います。 ディレクトリ内の重複についてです。 重複することにより、この記事では、たとえば、名前を入力する際のスペルミスの結果として、ディレクトリに作成されたエントリを再度理解します。



私のアプローチの本質は、任意のディレクトリエントリを既存のディレクトリエントリの複製として宣言できるようにすることです。 その結果、重複したエントリが削除され、すべての参照が修正され、正しいエントリを参照するようになります。 また、誤って変更された場合に備えて、そのような変更をロールバックする機能を提供することも非常に重要です。



サービスデータを保存するためのテーブルから始めましょう。



サービステーブル
create table mg_table ( table_name varchar(100) not null, pk_name varchar(100) not null, primary key(table_name) ); create sequence mg_action_seq; create table mg_action ( id bigint default nextval('mg_action_seq') not null, table_name varchar(100) not null references mg_table(table_name), old_id varchar(50) not null, new_id varchar(50) not null, action_time timestamp default now() not null, primary key(id) ); create sequence mg_action_detail_seq; create table mg_action_detail ( id bigint default nextval('mg_action_detail_seq') not null, action_id bigint not null references mg_action(id), table_name varchar(100) not null, pk_name varchar(100) not null, column_name varchar(100) not null, obj_id varchar(50) not null, primary key(id) );
      
      







ここで、mg_tableには、重複マージがサポートされているテーブルに関するデータが含まれています。 このようなテーブルの唯一の要件は、主キーが単一の数値列または文字列列で構成されている必要があることです。 このテーブルは自動的に入力されるため、心配する必要はありません。 mg_actionおよびmg_action_detailテーブルには、変更をロールバックするために必要なデータが含まれます。



補助関数のペアを定義します:



ヘルパー関数
 create or replace function mg_get_pk_column(in p_table varchar) returns varchar as $$ declare l_pk text; l_cn int; begin select max(f.name), count(*) as name into l_pk, l_cn from ( select ps_array_to_set(a.conkey) as nn from pg_constraint a, pg_class b where b.oid = a.conrelid and a.contype = 'p' and b.relname = lower(p_table) ) c, ( select d.attname as name, d.attnum as nn from pg_attribute d, pg_class e where e.oid = d.attrelid and e.relname = lower(p_table) ) f where f.nn = c.nn; if l_cn <> 1 then raise EXCEPTION 'Can''t support composite PK'; end if; return l_pk; end; $$ language plpgsql; create or replace function mg_add_dict(in p_table varchar) returns void as $$ declare l_pk text; l_sql text; begin l_pk := mg_get_pk_column(p_table); perform 1 from mg_table where table_name = lower(p_table); if not FOUND then l_sql := 'create table mg_' || lower(p_table) || ' ' || 'as select * from ' || lower(p_table) || ' limit 0'; execute l_sql; l_sql := 'alter table mg_' || lower(p_table) || ' ' || 'add primary key(' || l_pk || ')'; execute l_sql; insert into mg_table(table_name, pk_name) values (lower(p_table), l_pk); end if; end; $$ language plpgsql;
      
      







mg_get_pk_column関数は、前の記事で説明したクエリを実行します。このクエリは、主キー列の名前を返し、主キーが1つの列で構成されていることを確認します。



mg_add_dict関数は、mg_tableの入力に加えて、変更をロールバックする必要がある場合に、削除された重複が保存されるプレフィックス「mg_」を持つテーブルを作成します。 その構造では、このテーブルは元のテーブルと完全に似ています。



最も興味深いものに渡します:



mg_merge
 create or replace function mg_merge(in p_table varchar, in p_old varchar, in p_new varchar) returns void as $$ declare l_action int; l_pk text; l_sql text; tabs record; begin perform mg_add_dict(p_table); select pk_name into l_pk from mg_table where table_name = lower(p_table); l_action := nextval('mg_action_seq'); insert into mg_action(id, table_name, old_id, new_id) values (l_action, p_table, p_old, p_new); l_sql := 'insert into mg_' || lower(p_table) || ' ' || 'select * from ' || lower(p_table) || ' ' || 'where ' || l_pk || ' = ''' || p_old || ''''; execute l_sql; for tabs in select b.relname as table_name, d.attname as column_name from pg_constraint a, pg_class b, pg_class c, pg_attribute d where a.contype = 'f' and b.oid = a.conrelid and c.oid = a.confrelid and c.relname = lower(p_table) and d.attrelid = b.oid and a.conkey[1] = d.attnum loop l_sql := 'insert into mg_action_detail(action_id, table_name, column_name, obj_id, pk_name) ' || 'select ' || l_action || ', ''' || tabs.table_name || ''', ''' || tabs.column_name || ''', id, ' || '''' || mg_get_pk_column(tabs.table_name::varchar) || ''' ' || 'from ' || lower(tabs.table_name) || ' ' || 'where ' || lower(tabs.column_name) || ' = ''' || p_old || ''''; execute l_sql; l_sql := 'update ' || lower(tabs.table_name) || ' ' || 'set ' || lower(tabs.column_name) || ' = ''' || p_new || ''' ' || 'where ' || lower(tabs.column_name) || ' = ''' || p_old || ''''; execute l_sql; end loop; l_sql := 'delete from ' || lower(p_table) || ' where ' || l_pk || ' = ''' || p_old || ''''; execute l_sql; end; $$ language plpgsql; create or replace function mg_merge(in p_table varchar, in p_old bigint, in p_new bigint) returns void as $$ declare begin perform mg_merge(p_table, p_old::varchar, p_new::varchar); end; $$ language plpgsql;
      
      







この関数は、外部キーを使用してp_tableを参照するすべてのテーブルの検索を実行し、p_oldをp_newに置き換えて、変更をロールバックするために必要なデータを保存します。 ほとんどの場合、主キー列は数値であるため、便宜上、mg_merge関数(varchar、bigint、bigint)はオーバーロードされます。



ロールバック機能を開発することは残っています。



mg_undo
 create or replace function mg_undo() returns void as $$ declare l_action int; l_old varchar(50); l_table text; l_sql text; tabs record; begin select max(id) into l_action from mg_action; if l_action is null then raise EXCEPTION 'Can''t UNDO'; end if; select table_name, old_id into l_table, l_old from mg_action where id = l_action; l_sql := 'insert into ' || l_table || ' ' || 'select * from mg_' || l_table || ' ' || 'where id = ''' || l_old || ''''; execute l_sql; for tabs in select table_name, pk_name, column_name from mg_action_detail where action_id = l_action group by table_name, pk_name, column_name loop l_sql := 'update ' || tabs.table_name || ' ' || 'set ' || tabs.column_name || ' = ''' || l_old || ''' ' || 'where '''' || ' || tabs.pk_name || ' in (' || 'select '''' || obj_id from mg_action_detail '|| 'where table_name = ''' || tabs.table_name || ''' ' || 'and action_id = ' || l_action || ') '; execute l_sql; end loop; l_sql := 'delete from mg_' || l_table || ' where id = ''' || l_old || ''''; execute l_sql; delete from mg_action_detail where action_id = l_action; delete from mg_action where id = l_action; end; $$ language plpgsql;
      
      







変更は、作成とまったく逆の順序でロールバックされます。 このため、mg_undoを渡すための引数は不要です。



すべてがどのように機能するかを見てみましょう。 ルックアップテーブルを作成します。



 create sequence city_seq; create table city ( id bigint default nextval('city_seq') not null, name varchar(100) not null, primary key(id) ); create sequence street_seq; create table street ( id bigint default nextval('street_seq') not null, city_id bigint not null references city(id), name varchar(100) not null, primary key(id) ); create sequence address_seq; create table address ( id bigint default nextval('address_seq') not null, street_id bigint not null references street(id), house varchar(10) not null, apartment varchar(10) not null, primary key(id) );
      
      





...テストデータを入力します。



 insert into city(id, name) values (1, ''); insert into street(id, city_id, name) values (1, 1, ''); insert into street(id, city_id, name) values (2, 1, ' '); insert into address(id, street_id, house, apartment) values (1, 1, '10', '1'); insert into address(id, street_id, house, apartment) values (2, 2, '10', '2');
      
      







さて、Victory Avenue通りとVictory通りを「マージ」するには、次のコマンドを実行するだけで十分です。



 select mg_merge('street', 2, 1);
      
      





前述のmg_undo()関数は、変更をロールバックします。



これが誰かに役立つことを願っています。 ソースはGitHubに投稿されています




All Articles