Масове відношення 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

@dezso Дякую за пропозицію. Додавання тимчасового ідентифікатора, до teamякого було б зберігати посвідчення особи, для якої створена команда, вирішило б проблему. Мені все ще цікаво, чи є більш елегантне (тобто, не використовуючи DDL) рішення.
Ondřej Bouda,

@ 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

Можливо, вам доведеться підлаштувати смак. Я припускаю, що Employ.ident може бути відображений на person.id, і що ваша СУБД дозволяє призначати значення стовпцям з автоматично створеними значеннями. Крім цього, це просто базовий SQL, нічого фантазійного і, звичайно , ніяких циклів.

Додатковий коментар:

  • Таблиця 'команда' може бути (більш умовно) перейменована у відділ .
  • A 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.