PostgreSQL中的批量插入M:N关系


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,它将简单地遍历数据,将插入的团队ID保留在临时变量中,然后插入适当的teammember行。但是我很好奇是否有更简单或更优雅的解决方案。

大约会有数百到数千名员工。尽管通常这是一个好习惯,但就我而言,我不希望基于旧ID生成新ID,因为旧ID是类似的字符串*.GM2。我将它们存储到该old_ident列中以供参考。


3
我建议将一些临时标识符添加到新表中。这样,您可以在仍保持旧连接的同时向其中插入数据-然后您可以从旧表中获取必要的行,并将其插入下一个表中,依此类推。为此,我将使用单独的SQL语句,不需要复杂的CTE或过程函数。
dezso

@dezso感谢您的建议。添加一个临时标识符以team保存创建团队的人员的ID将解决此问题。不过,我仍然很好奇是否有一个更优雅的解决方案(即不使用DDL)。
的Ondrej布达

@OndřejBouda可以将表构建为CTE查询,但是很快就会变得非常复杂。(temp)表解决方案使您可以例如通过检查行数来单独测试步骤。
dezso 2015年

Answers:


1

您具有使用4条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

您可能需要调整以适应口味。我假设employee.ident可以映射到person.id,并且您的DBMS允许将值分配给具有自动生成的值的列。除此之外,它只是基本的SQL,没有花哨的东西,当然也没有循环。

附加评论:

  • “团队”表可以(更常规地)重命名为department
  • SERIAL(设有2种十亿可能性)应该是充足的,不需要使用BIGSERIAL
  • 似乎没有数据库机制可以强制经理对团队进行1:1基数化。顾名思义,不是每个团队都需要一个领导者吗?是不是有一个CHECKFOREIGN KEY约束的teammember.role?也许这个问题简化了这些细节。
  • “ teammember”表名称通常会具有单词边界,例如TeamMember或team_member。

1
这样,您在person表中将有重复的ID 。
dezso '16

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; $$;
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.