Импорт csv с внешними ключами

Допустим, у меня есть 2 таблицы: Студенты и Группы.

  • Таблица Group имеет 2 столбца: id, GroupName
  • Таблица Student имеет 3 столбца: id, StudentName и GroupID.
  • GroupID — это внешний ключ для поля Group.

Мне нужно импортировать таблицу «Студенты» из CSV, но в моем CSV вместо идентификатора группы появляется название группы. Как я могу импортировать его с помощью pgAdmin без изменения csv?


person zsomai    schedule 14.09.2020    source источник


Ответы (3)


Самый простой вариант — импортировать файл во временную таблицу, которая определяется как файл CSV. Затем вы можете соединить эту таблицу с таблицей групп и использовать INSERT INTO ... SELECT ... для заполнения таблицы студентов.

Конечно, также можно определить представление для объединения двух таблиц и определить триггер INSTEAD OF INSERT для представления, который вставляет значения в базовые таблицы по мере необходимости. Затем вы можете загрузить данные непосредственно в представление.

person Laurenz Albe    schedule 14.09.2020
comment
Большое спасибо ???? - person zsomai; 14.09.2020

Основываясь на ответе Лоренца, используйте следующие сценарии:

Создайте временную таблицу для вставки из файла CSV:

CREATE TEMP TABLE std_temp (id int, student_name char(25), group_name char(25));

Затем импортируйте файл CSV:

COPY std_temp FROM '/home/username/Documents/std.csv' CSV HEADER;

Теперь создайте таблицы std и grp для учащихся и групп:

CREATE TABLE grp (id int, name char(25));
CREATE TABLE std (id int, name char(20), grp_id int);

Настала очередь grp таблицы заполниться на основе distinct значения имени группы. Рассмотрим, как row_number() is use to provide value for id`:

INSERT INTO grp (id, name) select row_number() OVER (), * from (select distinct group_name from std_temp) as foo;

И последний шаг, выберите данные на основе join, затем вставьте их в таблицу std:

insert into std (id, name, grp_id) select std_temp.id, std_temp.student_name,grp.id from std_temp inner join grp on std_temp.group_name = grp.name;

В конце извлеките данные из финальной таблицы std:

select * from std;
person Afshar Mohebi    schedule 14.09.2020

Предложение @LaurenzAlbe является очевидным подходом (ИМХО никогда не загружайте электронную таблицу непосредственно в свои таблицы, это ненадежные звери). Но я считаю, что ваша реализация после загрузки промежуточной таблицы ошибочна.
Во-первых, использование row_number() фактически гарантирует получение дублирующихся идентификаторов для одного и того же имени группы. Идентификаторы всегда будут увеличиваться с 1 на 1 до количества имен групп, независимо от количества ранее загруженных групп, и вы не можете гарантировать идентичную последовательность в последующих электронных таблицах. Что происходит, когда у вас есть группа, которой ранее не существовало. Кроме того, не выполняется проверка того, что имя группы еще не существует. Результат: повторяющиеся имена групп и/или несколько идентификаторов для одного и того же имени.
Во-вторых, вы пытаетесь использовать идентификатор из электронной таблицы в качестве идентификатора в таблице студентов (std), которая полна возможных ошибок. Как вы гарантируете, что номер уникален в электронных таблицах? Даже если он уникален в одной электронной таблице, как убедиться, что в другой таблице не используются те же числа, что и в предыдущей? Или предполагая, что несколько пользователей создают электронные таблицы, в которых номера одного пользователя не перекрываются с другими пользователями, даже если все пользователи очень хорошо осведомлены о числах, которые они используют. Результат: повторяющиеся идентификационные номера.
Гораздо лучшим подходом было бы поместить уникальный ключ в столбец имени таблицы группы, а затем вставить любые имена групп из таблицы стадии в группу, отловив любые ошибки повторяющихся имен (используя при конфликте). Затем загрузите таблицу учеников непосредственно из таблицы этапов, выбрав идентификатор группы из таблицы групп по (теперь уникальному) имени группы.

create table csv_load_temp( junk_num integer, student_name text, group_name text);

create table groups( grp_id integer generated always as identity
                  , name text
                  , grp_key text generated always as ( lower(name) ) stored
                  , constraint grp_pk 
                               primary key (grp_id)
                  , constraint grp_bk
                               unique (grp_key) 
                  ); 

create table students (std_id integer generated always as identity 
                    , name text 
                    , grp_id integer
                    , constraint std_pk 
                                 primary key (std_id)
                    , constraint std2grp_fk
                                 foreign key (grp_id)
                                 references groups(grp_id)
                );
                
-- Function to load Groups and Students
create or replace function establish_students() 
 returns void 
 language sql 
as $$
insert into groups (name) 
     select distinct group_name
       from csv_load_temp
         on conflict (grp_key) do nothing;
         
insert into students (name, grp_id)
     select student_name, grp_id 
       from csv_load_temp t 
   join groups grp
     on (grp.name = t.group_name);
$$; 

Для таблицы групп требуется Postgres v12. В предыдущих версиях удалите столбец grp_key couumn и поместите уникальное ограничение непосредственно в столбец имени. Что делать с заглавными буквами, зависит от вашей бизнес-логики.
См. fiddle для полного примера. Очевидно, что две вставки в функцию Establish_Students могут выполняться автономно и независимо друг от друга. В этом случае сама функция не нужна.

person Belayer    schedule 15.09.2020