Массовая вставка отношения 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; $$;
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.