PostgreSQLでのM:N関係の一括挿入


9

古いデータベースのデータを、構造が少し異なる新しいデータベースにインポートする必要があります。たとえば、古いデータベースには、従業員とその上司を記録するテーブルがあります。

CREATE TABLE employee (ident TEXT PRIMARY KEY, name TEXT, supervisor_name TEXT)

現在、新しいデータベースは次のとおりです。

CREATE TABLE person (id BIGSERIAL PRIMARY KEY, name TEXT, old_ident TEXT);
CREATE TABLE team (id BIGSERIAL PRIMARY KEY);
CREATE TABLE teammember (person_id BIGINT, team_id BIGINT, role CHAR(1));

つまり、上司の名前を持つ従業員のプレーンテーブルの代わりに、新しい(より汎用的な)データベースを使用して、人々のチームを作成できます。従業員は役割を持つメンバー、役割'e'を持つ上司's'です。

問題は、データをemployee新しい構造に簡単に移行する方法であり、従業員と監督者のペアごとに1つのチームです。たとえば、従業員

employee: ('abc01', 'John', 'Dave'), ('abc02', 'Kyle', 'Emily')

移行する

person: (1, 'John', 'abc01'), (2, 'Dave', NULL), (3, 'Kyle', 'abc02'), (4, 'Emily', NULL)
team: (1), (2)
teammember: (1, 1, 'e'), (2, 1, 's'), (3, 2, 'e'), (4, 2, 's')

私は、データ変更CTEを使用して、最初に従業員とスーパーバイザを挿入し、次にその中にチームを挿入することを検討します。ただし、CTEは挿入されたテーブル行からのデータのみを返す場合があります。したがって、私は誰が誰の監督者であったかを突き合わせることができません。

私が見ることができる唯一の解決策はを使用することですplpgsql。これは単にデータを反復処理し、挿入されたチームIDを一時変数に保持してから、適切なteammember行を挿入します。しかし、私はより単純な、またはよりエレガントな解決策があるかどうか知りたいです。

およそ数百人から数千人の従業員がいるでしょう。これは一般的には良い習慣ですが、私の場合、古いIDはのような文字列であるため、古いIDに基づいて新しいIDを生成したくありません*.GM2old_ident参照用に列に格納します。


3
新しいテーブルに一時的な識別子を追加することをお勧めします。このようにして、古い接続を維持したままデータを挿入できます。その後、古いテーブルから必要な行をフェッチして、次のテーブルなどに挿入できます。このため、個別のSQLステートメントを使用します。複雑なCTEや手続き型関数は必要ありません。
dezso

@dezso提案をありがとう。teamチームを作成した人物のIDを保持する一時的な識別子を追加すると、問題が解決します。けれども、よりエレガントな(つまり、DDLを使用しない)ソリューションがあるかどうか、私はまだ知りたいです。
オンドレイBouda

@OndřejBoudaでは、テーブルをCTEクエリとして作成することは可能ですが、かなり複雑になる可能性があります。(一時)テーブルソリューションは、たとえば行数をチェックすることで、ステップを個別にテストできるという贅沢さを提供します。
dezso

回答:


1

次の4つの挿入ステートメントを使用して、古いデータベースから新しいデータベースにデータを取り込むために必要なすべての情報があります。

create table team_ids (id serial, name TEXT)

insert into team_ids (name)
select distinct supervisor_name from employee

-- now supervisors have ids assigned by "serial" type

insert into person (id, name, old_ident)
select ident, name, ident from employee
union
select ident, supervisor_name, ident from employee

insert into team (id) -- meh
select id from team_ids

insert into teammember (person_id, team_id, role)
select e.ident, t.id, 'e')
from employee as e, join team_ids as t
on t.name = e.supervisor_name
union -- and, I guess
select t.id, t.id, 'm')
from team_ids as t

好みに合わせて調整する必要があるかもしれません。ここでは、employee.identをperson.idにマップでき、DBMSで値が自動生成された列に割り当てられると想定しています。それを除いて、それは単なる基本的なSQLであり、空想的で何もない、そしてもちろん、ループはありません。

追加の解説:

  • 「チーム」テーブルは(より慣習的に)部門名に変更される場合があります。
  • A SERIAL(20億の可能性がある)は十分なはずBIGSERIALです。
  • マネージャーとチームの1対1のカーディナリティを強制するデータベースメカニズムはないようです。定義上、すべてのチームにリーダーが必要なわけではありませんか?teammember.roleにCHECKor FOREIGN KEY制約はありませんか?おそらく、この質問により、これらの詳細は単純化されました。
  • 「teammember」テーブル名には、より慣習的に、例えばTeamMemberやteam_memberなどの単語境界があります。

1
これにより、personテーブルに重複したIDが含まれます。
dezso 2016

0

PL / PgSQLがその仕事をします。

DO $$
DECLARE
  _e record;
  _personid bigint;
  _suppersonid bigint;
  _teamid bigint;
BEGIN
  FOR _e IN
    SELECT ident, name, supervisor_name FROM employee
  LOOP
    -- insert person record for employee
    INSERT INTO person (name, old_ident)
      SELECT _e.name, _e.ident
      RETURNING id INTO _personid;
    -- lookup or insert person record for supervisor
    SELECT id INTO _suppersonid FROM person
      WHERE p.name = _e.supervisor_name;
    IF _suppersonid IS NULL THEN
      INSERT INTO person (name) SELECT _e.supervisor_name
        RETURNING id INTO _suppersonid;
    END IF;
    -- lookup team by supervisor or insert new team
    SELECT team_id INTO _teamid FROM teammember tm
      WHERE tm.person_id = _suppersonid AND tm.role = 's';
    IF _teamid IS NULL THEN
      -- new supervisor: insert new team and supervisor
      INSERT INTO team (id) VALUES(DEFAULT) RETURNING id INTO _teamid;
      INSERT INTO teammember (person_id, team_id, role) SELECT _suppersonid, _teamid, 's';
    END IF;
    -- insert team member (non-supervisor) record
    INSERT INTO teammember (person_id, team_id, role) SELECT _personid, _teamid, 'e';
  END LOOP;
END; $$;
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.