Массовая вставка отношения M: N в PostgreSQL

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в новую структуру, по одной команде на пару сотрудник-руководитель. Например, работники

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, которое будет просто перебирать данные, удерживать вставленные идентификаторы команды во временной переменной, а затем вставлять соответствующие teammemberстроки. Но мне любопытно, есть ли более простые или более элегантные решения.

Там будет примерно от нескольких сотен до нескольких тысяч сотрудников. Хотя в целом это хорошая практика, в моем случае я не хотел бы генерировать новые идентификаторы на основе старых, поскольку старые идентификаторы похожи на строки *.GM2. Я храню их в old_identстолбце для справки.

Ондржей Буда
источник
3
Я бы предложил добавить несколько временных идентификаторов в новые таблицы. Таким образом, вы можете вставлять в них данные, сохраняя при этом старые соединения, - затем вы можете извлечь необходимые строки из старой таблицы, вставить их в следующую таблицу и так далее. Для этого я бы использовал отдельные операторы SQL, не требуя сложных CTE или процедурных функций.
Дезсо
@dezso Спасибо за предложение. Добавление временного идентификатора, в teamкотором будет храниться идентификатор человека, для которого была создана команда, решит проблему. Мне все еще интересно, есть ли более элегантное решение (то есть без использования DDL).
Ондржей Будда
@ OndřejBouda может быть возможно построить таблицы как запросы CTE, но это может довольно быстро усложниться. Решение таблицы (temp) дает вам возможность протестировать шаги по отдельности, например, путем проверки количества строк.
Дезсо

Ответы:

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, и что ваша СУБД позволяет присваивать значения столбцам с автоматически сгенерированными значениями. За исключением этого, это просто базовый SQL, ничего особенного и, конечно , никаких циклов.

Дополнительный комментарий:

  • Таблица «команда» может быть (более условно) переименована в отдел .
  • А SERIAL(с его 2 миллиардами возможностей) должно быть много, не нужно BIGSERIAL.
  • Похоже, что не существует механизма базы данных для обеспечения количества элементов 1: 1 между менеджером и командой. Разве каждой команде не нужен лидер по определению? Нет ли CHECKили FOREIGN KEYограничения для teammember.role? Возможно, вопрос упростил эти детали.
  • Имя таблицы «teammember» более условно будет иметь границу слова, например, TeamMember или team_member.
Джеймс К. Лоуден
источник
1
Таким образом, вы будете иметь дубликаты идентификаторов в personтаблице.
Дезсо
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; $$;
filiprem
источник