Wstaw luzem relacja M: N w PostgreSQL

9

Muszę zaimportować dane ze starej bazy danych do nowej, o nieco innej strukturze. Na przykład w starej bazie danych znajduje się tabela rejestrująca pracowników i ich przełożonych:

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

Nowa baza danych wygląda następująco:

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

Oznacza to, że zamiast prostej tabeli pracowników z nazwiskami ich przełożonych, nowa (bardziej ogólna) baza danych umożliwia tworzenie zespołów ludzi. Pracownicy są członkami z rolą 'e', przełożeni z rolą 's'.

Pytanie brzmi, jak łatwo migrować dane employeedo nowej struktury, po jednym zespole na parę pracowników i przełożonych. Na przykład pracownicy

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

mają być migrowane jako

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

Rozważałbym zastosowanie CTE modyfikującego dane, najpierw umieszczając pracowników i przełożonych, a następnie zespoły. CTE może jednak zwracać dane tylko z wstawionego wiersza tabeli. Dlatego nie jestem w stanie dopasować, kto był przełożonym.

Jedyne rozwiązanie, jakie widzę, to użycie plpgsql, które po prostu iteruje dane, przechowuje wstawione identyfikatory zespołów w zmiennej tymczasowej, a następnie wstawia odpowiednie teammemberwiersze. Ale jestem ciekawy, czy istnieją prostsze czy bardziej eleganckie rozwiązania.

Będzie około kilkuset do kilku tysięcy pracowników. Chociaż ogólnie jest to dobra praktyka, w moim przypadku nie chciałbym generować nowych identyfikatorów na podstawie starych, ponieważ stare identyfikatory są ciągami *.GM2. Przechowuję je w old_identkolumnie w celach informacyjnych.

Ondřej Bouda
źródło
3
Sugerowałbym dodanie tymczasowych identyfikatorów do nowych tabel. W ten sposób możesz wstawiać do nich dane, wciąż mając stare połączenia - wtedy możesz pobrać niezbędne wiersze ze starej tabeli i wstawić je do następnej tabeli i tak dalej. W tym celu użyłbym osobnych instrukcji SQL, bez potrzeby stosowania skomplikowanych CTE lub funkcji proceduralnych.
dezso
@dezso Dzięki za sugestię. Dodanie tymczasowego identyfikatora, do teamktórego będzie przechowywany identyfikator osoby, dla której zespół został utworzony, rozwiązałoby problem. Nadal jestem ciekawy, czy istnieje bardziej eleganckie (tj. Bez użycia DDL) rozwiązanie.
Ondřej Bouda
@ OndřejBouda może być możliwe budowanie tabel jako zapytań CTE, ale dość szybko może się to dość skomplikować. Rozwiązanie tabeli (temp) daje luksus testowania poszczególnych kroków, na przykład poprzez sprawdzenie liczby wierszy.
dezso

Odpowiedzi:

1

Masz wszystkie informacje potrzebne do zapełnienia nowej bazy danych ze starej za pomocą 4 instrukcji wstawiania:

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

Być może będziesz musiał dostosować się do smaku. Zakładam, że pracownik.ident można zmapować na person.id i że Twój system DBMS umożliwia przypisywanie wartości do kolumn z automatycznie wygenerowanymi wartościami. Poza tym jest to tylko podstawowy SQL, nic szczególnego i oczywiście żadnych pętli.

Dodatkowy komentarz:

  • Nazwę „zespołu” można (bardziej konwencjonalnie) zmienić na dział .
  • A SERIAL(z 2 miliardami możliwości) powinno być dużo, bez potrzeby BIGSERIAL.
  • Wydaje się, że nie ma mechanizmu bazy danych, który wymusiłby liczebność menedżera w stosunku 1: 1 do zespołu. Czy z definicji nie każdy zespół potrzebuje lidera? Czy nie ma CHECKlub FOREIGN KEYograniczenie dla teammember.role? Być może pytanie uprościło te szczegóły.
  • Nazwa tabeli „członek zespołu” bardziej konwencjonalnie miałaby granicę słowa, na przykład TeamMember lub team_member.
James K. Lowden
źródło
1
W ten sposób będziesz mieć zduplikowane identyfikatory w persontabeli.
dezso
0

PL / PgSQL wykona zadanie.

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
źródło