Postgresql 同时插入 Parent/Child Table

Postgresql Simultaneous Insert into Parent/Child Table

使用 Postgresql,我有 tables 与 parent/child 或 general/detail 关系,有点像面向对象的继承:

CREATE TYPE person_kinds AS ENUM ('student', 'teacher');

CREATE TABLE person(id serial primary key, kind person_kinds, name text);
CREATE TABLE teacher(person_id integer primary key references person(id), subject text);
CREATE TABLE student(person_id integer primary key references person(id), grade integer);

也就是这个人table包含了一个人所共有的所有东西,并保留了一个类型标签。 subtables 通过根据类型添加额外的列来亲自细化信息;例如老师教一门学科,学生可能有成绩。这个 table 结构是给定的,我无法更改它。 (例如,我不认为我可以添加继承,但我不相信这会有所帮助)

现在我有一个带有学生数据的临时 table,它是上面的“平面”版本。例如

CREATE TABLE tmp_table(kind person_kinds, name text, grade integer);
INSERT INTO tmp_table(kind, name, grade) 
VALUES ('student', 'Chris', 2),('student','Lara',1),('student','Izzy',3);

我的问题是,如何将临时 table 中的数据插入到上面的 table 中?我遇到了困难,因为学生 table 的插入需要 person-id 作为外键,并且因为 student-table 没有任何“区分”列,所以我无法加入任何内容。

我尝试了以下几行:

with inserted_persons as
( 
  insert into person(type, name) 
  select type, name 
  from tmp_table 
  returning id  --I sadly cannot return any columns from tmp_table here
)
insert into student(person_id, grade) 
select 
  p.id, t.grade 
from 
  -- I don't have a column to join on, and the implicit join here 
  -- is wrong as it crosses the tmp_table with the inserted_persons
  inserted_persons as p, 
  tmp_table as t

我会通过为学生创建一个视图来解决这个问题:

CREATE VIEW v_student AS
SELECT person.id, person.name, student.grade
FROM person
   JOIN student
      ON person.id = student.person_id
WHERE person.kind = 'student';

和一个 INSTEAD OF INSERT 触发器:

CREATE FUNCTION ins_student() RETURNS trigger
   LANGUAGE plpgsql AS
$$DECLARE
   v_id integer;
BEGIN
   INSERT INTO person (kind, name)
      VALUES ('student', NEW.name)
      RETURNING id INTO v_id;

   INSERT INTO student (person_id, grade)
      VALUES (v_id, NEW.grade);

   RETURN NEW;
END;$$;

CREATE TRIGGER ins_student INSTEAD OF INSERT ON v_student
   FOR EACH ROW EXECUTE FUNCTION ins_student();

可以这样使用:

INSERT INTO v_student (name, grade)
SELECT name, grade FROM tmp_table
WHERE kind = 'student';

TABLE person;

 id │  kind   │ name  
════╪═════════╪═══════
  1 │ student │ Chris
  2 │ student │ Lara
  3 │ student │ Izzy
(3 rows)

TABLE student;

 person_id │ grade 
═══════════╪═══════
         1 │     2
         2 │     1
         3 │     3
(3 rows)