Sisipkan massal hubungan M: N di PostgreSQL

9

Saya perlu mengimpor data dari database lama ke yang baru, dengan struktur yang sedikit berbeda. Misalnya, dalam database lama, ada tabel yang merekam karyawan dan penyelia mereka:

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

Sekarang, database baru adalah sebagai berikut:

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

Yaitu, alih-alih tabel sederhana karyawan dengan nama atasan mereka, database baru (lebih umum) memungkinkan untuk membuat tim orang. Karyawan adalah anggota dengan peran 'e', pengawas dengan peran 's'.

Pertanyaannya adalah bagaimana cara mudah memigrasikan data dari employeeke struktur baru, satu tim per pasangan karyawan-pengawas. Misalnya karyawan

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

harus dimigrasikan sebagai

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

Saya akan mempertimbangkan untuk menggunakan CTE pemodifikasi data, memasukkan karyawan dan penyelia terlebih dahulu, kemudian tim di antara mereka. Namun, CTE hanya dapat mengembalikan data dari baris tabel yang dimasukkan. Jadi, saya tidak bisa menandingi siapa yang menjadi pengawas siapa.

Satu-satunya solusi yang dapat saya lihat adalah menggunakan plpgsql, yang hanya akan mengulangi data, tahan ID tim yang dimasukkan dalam variabel sementara, dan kemudian masukkan teammemberbaris yang sesuai . Tetapi saya ingin tahu apakah ada solusi yang lebih sederhana atau lebih elegan.

Akan ada sekitar ratusan hingga ribuan karyawan. Meskipun ini umumnya merupakan praktik yang baik, dalam kasus saya, saya tidak ingin membuat ID baru berdasarkan yang lama, karena ID lama adalah string seperti *.GM2. Saya menyimpannya di old_identkolom untuk referensi.

Ondřej Bouda
sumber
3
Saya akan menyarankan menambahkan beberapa pengidentifikasi sementara ke tabel baru. Dengan cara ini Anda bisa memasukkan data ke dalamnya sementara masih memiliki koneksi lama - maka Anda dapat mengambil baris yang diperlukan dari tabel lama dan memasukkannya ke tabel berikutnya dan seterusnya. Untuk ini, saya akan menggunakan pernyataan SQL terpisah, tidak perlu untuk CTE yang rumit atau fungsi prosedural.
dezso
@dezso Terima kasih atas sarannya. Menambahkan pengenal sementara teamyang akan memegang ID orang yang timnya dibuat akan menyelesaikan masalah. Saya masih penasaran apakah ada solusi yang lebih elegan (yaitu, tidak menggunakan DDL).
Ondřej Bouda
@ OndřejBouda dimungkinkan untuk membuat tabel sebagai kueri CTE, tetapi mungkin menjadi cukup rumit dengan cukup cepat. Solusi tabel (temp) memberi Anda kemewahan menguji langkah-langkah secara individual, dengan memeriksa jumlah baris, misalnya.
dezso

Jawaban:

1

Anda memiliki semua informasi yang Anda perlukan untuk mengisi basis data baru dari yang lama dengan 4 pernyataan masukkan:

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

Anda mungkin harus menyesuaikan dengan selera. Saya mengasumsikan employee.ident dapat dipetakan ke person.id, dan bahwa DBMS Anda memungkinkan menetapkan nilai ke kolom dengan nilai yang dihasilkan secara otomatis. Kecuali itu, itu hanya SQL dasar, tidak ada yang mewah dan, tentu saja , tidak ada loop.

Komentar tambahan:

  • Tabel 'tim' mungkin (lebih konvensional) diganti namanya menjadi departemen .
  • A SERIAL(dengan 2 miliar kemungkinan) harus banyak, tidak perlu untuk BIGSERIAL.
  • Tampaknya tidak ada mekanisme database untuk menegakkan kardinalitas manajer ke tim 1: 1. Bukankah setiap tim membutuhkan pemimpin, menurut definisi? Apakah tidak ada CHECKatau FOREIGN KEYkendala untuk teammember.role? Mungkin pertanyaannya menyederhanakan detail ini.
  • Nama tabel "teammember" akan lebih konvensional memiliki batas kata, misalnya TeamMember atau team_member.
James K. Lowden
sumber
1
Dengan cara ini Anda akan memiliki ID duplikat di persontabel.
dezso
0

PL / PgSQL akan melakukan pekerjaan itu.

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
sumber