Masseneinfügung M: N-Beziehung in PostgreSQL

9

Ich muss Daten aus einer alten Datenbank in eine neue mit leicht unterschiedlicher Struktur importieren. In der alten Datenbank gibt es beispielsweise eine Tabelle, in der Mitarbeiter und ihre Vorgesetzten aufgezeichnet werden:

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

Die neue Datenbank lautet nun wie folgt:

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));

Das heißt, anstelle einer einfachen Tabelle mit Mitarbeitern mit den Namen ihrer Vorgesetzten können mit der neuen (allgemeineren) Datenbank Teams von Personen erstellt werden. Die Mitarbeiter sind Mitglieder mit Rolle 'e', Vorgesetzte mit Rolle 's'.

Die Frage ist, wie die Daten einfach von employeein die neue Struktur migriert werden können, ein Team pro Mitarbeiter-Vorgesetzter-Paar. Zum Beispiel Mitarbeiter

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

sind zu migrieren als

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')

Ich würde in Betracht ziehen, einen datenmodifizierenden CTE zu verwenden, bei dem zuerst die Mitarbeiter und Vorgesetzten und dann die Teams unter ihnen eingefügt werden. CTE kann jedoch nur Daten aus der eingefügten Tabellenzeile zurückgeben. Daher kann ich nicht mithalten, wer der Vorgesetzte von wem war.

Die einzige Lösung, die ich sehen kann, ist die Verwendung plpgsql, bei der einfach die Daten durchlaufen, die eingefügten Team-IDs in einer temporären Variablen gespeichert und dann die entsprechenden teammemberZeilen eingefügt werden . Aber ich bin gespannt, ob es einfachere oder elegantere Lösungen gibt.

Es werden ungefähr mehrere Hundert bis mehrere Tausend Mitarbeiter beschäftigt sein. Obwohl dies im Allgemeinen eine gute Vorgehensweise ist, möchte ich in meinem Fall die neuen IDs nicht basierend auf den alten generieren, da die alten IDs Zeichenfolgen ähneln *.GM2. Ich speichere sie in der old_identSpalte als Referenz.

Ondřej Bouda
quelle
3
Ich würde vorschlagen, den neuen Tabellen einige temporäre Bezeichner hinzuzufügen. Auf diese Weise können Sie Daten in sie einfügen, während die alten Verbindungen noch vorhanden sind. Anschließend können Sie die erforderlichen Zeilen aus der alten Tabelle abrufen und in die nächste Tabelle einfügen. Dafür würde ich separate SQL-Anweisungen verwenden, ohne dass komplizierte CTEs oder prozedurale Funktionen erforderlich wären.
Dekso
@dezso Danke für den Vorschlag. Das Hinzufügen einer temporären Kennung, teamdie die ID der Person enthält, für die das Team erstellt wurde, würde das Problem lösen. Ich bin immer noch gespannt, ob es eine elegantere Lösung gibt (dh ohne DDL).
Ondřej Bouda
@ OndřejBouda Es ist möglicherweise möglich, die Tabellen als CTE-Abfragen zu erstellen, aber es kann ziemlich schnell ziemlich kompliziert werden. Die (temporäre) Tabellenlösung bietet Ihnen den Luxus, die Schritte einzeln zu testen, indem Sie beispielsweise die Zeilenanzahl überprüfen.
Dekso

Antworten:

1

Sie haben alle Informationen, die Sie benötigen, um die neue Datenbank aus der alten mit 4 Einfügeanweisungen zu füllen:

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

Möglicherweise müssen Sie sich an den Geschmack anpassen. Ich gehe davon aus, dass employee.ident auf person.id abgebildet werden kann und dass Ihr DBMS das Zuweisen von Werten zu Spalten mit automatisch generierten Werten ermöglicht. Abgesehen davon ist es nur einfaches SQL, nichts Besonderes und natürlich keine Schleifen.

Zusätzlicher Kommentar:

  • Die 'Team'-Tabelle könnte (konventioneller) in Abteilung umbenannt werden .
  • A SERIAL(mit seinen 2 Milliarden Möglichkeiten) sollte reichlich sein, keine Notwendigkeit für a BIGSERIAL.
  • Es scheint keinen Datenbankmechanismus zu geben, um die 1: 1-Kardinalität des Managers gegenüber dem Team durchzusetzen. Braucht nicht jedes Team per Definition einen Anführer? Gibt es keine CHECKoder FOREIGN KEYEinschränkung für teammember.role? Vielleicht hat die Frage diese Details vereinfacht.
  • Der Tabellenname "teammember" hätte üblicherweise eine Wortgrenze, zum Beispiel TeamMember oder team_member.
James K. Lowden
quelle
1
Auf diese Weise haben Sie doppelte IDs in der personTabelle.
Dekso
0

PL / PgSQL erledigt den Job.

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; $$;
Filiprem
quelle