Inserimento in blocco relazione M: N in PostgreSQL


9

Devo importare dati da un vecchio database a uno nuovo, con una struttura leggermente diversa. Ad esempio, nel vecchio database c'è una tabella che registra i dipendenti e i loro supervisori:

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

Ora, il nuovo database è il seguente:

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

Cioè, invece della semplice tabella di dipendenti con i nomi dei loro supervisori, il nuovo database (più generico) consente di creare team di persone. I dipendenti sono membri con ruolo 'e', supervisori con ruolo 's'.

La domanda è: come migrare facilmente i dati dalla employeenuova struttura, un team per coppia dipendente-supervisore. Ad esempio, dipendenti

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

devono essere migrati come

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

Vorrei prendere in considerazione l'uso di un CTE che modifica i dati, inserendo prima i dipendenti e i supervisori, quindi i team tra di loro. Tuttavia, CTE può restituire dati solo dalla riga della tabella inserita. Quindi, non sono in grado di eguagliare chi era il supervisore di chi.

L'unica soluzione che posso vedere è l'utilizzo plpgsql, che semplicemente scorreva sui dati, conteneva gli ID team inseriti in una variabile temporanea e quindi inseriva le teammemberrighe appropriate . Ma sono curioso di sapere se esistono soluzioni più semplici o più eleganti.

Ci saranno all'incirca da diverse centinaia a diverse migliaia di dipendenti. Sebbene sia generalmente una buona pratica, nel mio caso, non vorrei generare i nuovi ID in base a quelli vecchi, poiché i vecchi ID sono stringhe come *.GM2. Le memorizzo nella old_identcolonna per riferimento.


3
Suggerirei di aggiungere alcuni identificatori temporanei alle nuove tabelle. In questo modo è possibile inserire dati al loro interno pur avendo le vecchie connessioni - quindi è possibile recuperare le righe necessarie dalla vecchia tabella e inserirle nella tabella successiva e così via. Per questo, vorrei usare istruzioni SQL separate, senza bisogno di complicati CTE o funzioni procedurali.
dezso,

@dezso Grazie per il suggerimento. L'aggiunta di un identificatore temporaneo a teamcui contenere l'ID della persona per cui è stata creata la squadra risolverebbe il problema. Sono ancora curioso di sapere se esiste una soluzione più elegante (ovvero, non usare DDL).
Ondřej Bouda,

@ OndřejBouda potrebbe essere possibile creare tabelle come query CTE, ma potrebbe diventare piuttosto complicato abbastanza velocemente. La soluzione della tabella (temp) ti offre il lusso di testare i passaggi individualmente, controllando ad esempio il conteggio delle righe.
dezso,

Risposte:


1

Hai tutte le informazioni necessarie per popolare il nuovo database da quello vecchio con 4 istruzioni insert:

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

Potrebbe essere necessario regolare a piacere. Presumo che employee.ident possa essere mappato su person.id e che il tuo DBMS consenta di assegnare valori a colonne con valori generati automaticamente. Tranne quello, è solo un SQL di base, niente di speciale e, ovviamente , nessun loop.

Commento aggiuntivo:

  • La tabella "team" potrebbe essere (più convenzionalmente) rinominata in reparto .
  • A SERIAL(con le sue 2 miliardi di possibilità) dovrebbe essere abbondante, non c'è bisogno di a BIGSERIAL.
  • Sembra che non vi sia alcun meccanismo di database per applicare la cardinalità 1: 1 del manager alla squadra. Ogni squadra non ha bisogno di un leader, per definizione? Non esiste un vincolo CHECKo FOREIGN KEYper teammember.role? Forse la domanda ha semplificato questi dettagli.
  • Il nome della tabella "teammember" avrebbe più convenzionalmente un limite di parole, ad esempio TeamMember o team_member.

1
In questo modo avrai ID duplicati nella persontabella.
dezso,

0

PL / PgSQL farà il lavoro.

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; $$;
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.