Insertion en vrac de la relation M: N dans PostgreSQL

9

J'ai besoin d'importer des données d'une ancienne base de données vers une nouvelle, avec une structure légèrement différente. Par exemple, dans l'ancienne base de données, il y a un tableau enregistrant les employés et leurs superviseurs:

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

Maintenant, la nouvelle base de données est la suivante:

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

Autrement dit, au lieu d'un tableau simple des employés avec les noms de leurs superviseurs, la nouvelle base de données (plus générique) permet de créer des équipes de personnes. Les employés sont des membres avec rôle 'e', des superviseurs avec rôle 's'.

La question est de savoir comment migrer facilement les données de employeela nouvelle structure, une équipe par paire employé-superviseur. Par exemple, les employés

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

doivent être migrés

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

J'envisagerais d'utiliser un CTE modificateur de données, en insérant d'abord les employés et les superviseurs, puis les équipes parmi eux. Cependant, CTE peut uniquement renvoyer des données de la ligne de table insérée. Ainsi, je ne suis pas en mesure de faire correspondre qui était le superviseur de qui.

La seule solution que je vois consiste à utiliser plpgsql, ce qui permettrait simplement d'itérer sur les données, de conserver les ID d'équipe insérés dans une variable temporaire, puis d'insérer les teammemberlignes appropriées . Mais je suis curieux de savoir s'il existe des solutions plus simples ou plus élégantes.

Il y aura environ plusieurs centaines à plusieurs milliers d'employés. Bien que ce soit généralement une bonne pratique, dans mon cas, je ne voudrais pas générer les nouveaux ID sur la base des anciens, car les anciens ID sont des chaînes *.GM2. Je les stocke dans la old_identcolonne pour référence.

Ondřej Bouda
la source
3
Je suggère d'ajouter quelques identifiants temporaires aux nouvelles tables. De cette façon, vous pouvez y insérer des données tout en conservant les anciennes connexions - puis vous pouvez extraire les lignes nécessaires de l'ancienne table et les insérer dans la table suivante et ainsi de suite. Pour cela, j'utiliserais des instructions SQL distinctes, pas besoin de CTE compliqués ou de fonctions procédurales.
dezso
@dezso Merci pour la suggestion. L'ajout d'un identifiant temporaire teamauquel détiendrait l'ID de la personne pour laquelle l'équipe a été créée résoudrait le problème. Je suis toujours curieux de savoir s'il existe une solution plus élégante (c'est-à-dire n'utilisant pas de DDL).
Ondřej Bouda
@ OndřejBouda il pourrait être possible de construire les tables en tant que requêtes CTE, mais cela pourrait devenir assez compliqué assez rapidement. La solution de table (temporaire) vous offre le luxe de tester les étapes individuellement, en vérifiant le nombre de lignes, par exemple.
dezso

Réponses:

1

Vous disposez de toutes les informations nécessaires pour alimenter la nouvelle base de données à partir de l'ancienne avec 4 instructions d'insertion:

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

Vous devrez peut-être vous adapter au goût. Je suppose que employee.ident peut être mappé sur person.id et que votre SGBD permet d'attribuer des valeurs aux colonnes avec des valeurs générées automatiquement. À part ça, c'est juste du SQL basique, rien d'extraordinaire et, bien sûr , pas de boucles.

Commentaire supplémentaire:

  • La table «équipe» pourrait être (plus conventionnellement) renommée département .
  • Un SERIAL(avec ses 2 milliards de possibilités) devrait être suffisant, pas besoin d'un BIGSERIAL.
  • Il ne semble pas y avoir de mécanisme de base de données pour appliquer la cardinalité 1: 1 du manager à l'équipe. Par définition, chaque équipe n'a-t-elle pas besoin d'un leader? N'y a-t-il pas une contrainte CHECKor FOREIGN KEYpour teammember.role? Peut-être que la question a simplifié ces détails.
  • Le nom de la table "teammember" aurait plus conventionnellement une limite de mot, par exemple TeamMember ou team_member.
James K. Lowden
la source
1
De cette façon, vous aurez des ID en double dans le persontableau.
dezso
0

PL / PgSQL fera le travail.

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
la source