MySQL: хранимая процедура для разделения столбца (с разделителями) и вставки в новую таблицу

В моей базе данных есть ненормализованная таблица с именем details, структура и пример данных, как показано ниже (извините за изображение, просто подумал, что это будет более понятно): **Схема**

Моя задача состоит в том, чтобы разделить столбцы - assignee, inventor and ipcsubclass с помощью разделителя | на новые таблицы {detail_inv и inventors}, {detail_asg и assignees} и {detail_ipc и ipcsubclasses}.

Во всех трех случаях схемы таблиц аналогичны. Например, столбцы в таблице изобретателей — id и name, а в таблице detail_inv — detail_id и inventor_id. В каждой строке должно быть только одно имя со всеми именами, уникальными в таблице изобретателей и идентификаторами, чтобы поддерживать связь в таблице detail_inv.

Я попробовал хранимую процедуру с приведенным ниже кодом для изобретателей - я сделал 3 процедуры для 3 столбцов :(

drop procedure if exists normalise_details;

delimiter #

create procedure normalise_details()
proc_main:begin

declare v_cursor_done int unsigned default 0;
declare v_post_id int unsigned;
declare v_tags varchar(2048);
declare v_keyword varchar(50);

declare v_keyword_id mediumint unsigned;

declare v_tags_done int unsigned;
declare v_tags_idx int unsigned;

declare v_cursor cursor for select id, inventor from details order by id;
declare continue handler for not found set v_cursor_done = 1;

set autocommit = 0; 

open v_cursor;
repeat

  fetch v_cursor into v_post_id, v_tags;
  set v_tags_done = 0;       
  set v_tags_idx = 1;

  while not v_tags_done do

    set v_keyword = substring(v_tags, v_tags_idx, 
      if(locate('|', v_tags, v_tags_idx) > 0, 
        locate('|', v_tags, v_tags_idx) - v_tags_idx, 
        length(v_tags)));

      if length(v_keyword) > 0 then

        set v_tags_idx = v_tags_idx + length(v_keyword) + 1;

        set v_keyword = trim(v_keyword);
        insert into inventors (name) values (v_keyword);

        select id into v_keyword_id from inventors where name = v_keyword;
        insert into details_inv (inventor_id, detail_id) values (v_keyword_id, v_post_id);

      else
        set v_tags_done = 1;
      end if;

  end while;

until v_cursor_done end repeat;

close v_cursor;

commit;

end proc_main #


delimiter ;

Когда я пробую это на некоторых случайных тестовых данных, все работает нормально. когда я делаю это на реальном столе, это не работает. Вставляются только частичные данные. SQL не выдает ошибок (за исключением нескольких случаев: «#1172 — результат состоит из более чем одной строки» или «столбец inventor_id не может быть пустым»)

Я попытался изменить код в MySQL - Вставить запятую разделенный список на нормализованные таблицы с помощью хранимой процедуры в соответствии с моими потребностями, но мне это не удалось.

Пожалуйста, помогите мне, моя таблица БД превратилась в беспорядок, и в ней около 500 000 строк, из-за чего мне очень сложно взорвать и управлять огромными массивами в каждом проекте (последний проект с ~ 200 000 строк).


person Fr0zenFyr    schedule 02.09.2013    source источник
comment
снимок экрана с лучшей видимостью находится здесь   -  person Fr0zenFyr    schedule 02.09.2013


Ответы (2)


Глядя на сообщение RolandoMySQLDBA на этот вопрос dba.stackexchange, я чувствую подтверждено в моих первоначальных оговорках относительно запускаемых хранимых процедур. Однако, если вы уверены, что в любой момент времени только несколько строк изменяются при вводе данных пользователем, тогда можно составить быструю процедуру.

Однако, если параллельно работает много пользователей, они могут блокировать друг друга. Я не знаю, произойдет ли это на самом деле, так как хранимая процедура ничего не изменит в таблице details. При необходимости вы можете посетить эту страницу в поисках идей.

Изменить: ТРИГГЕР

Я только что расширил SQLfiddle моего предыдущего сообщения до этого SQLfiddle с триггером, содержащего следующее :

CREATE TRIGGER normdet AFTER INSERT ON detail FOR EACH ROW
BEGIN
  DECLARE n int; DECLARE word VARCHAR(64)

 ;SET n=cntparts(NEW.inventor)
 ;WHILE n>0 DO
   SET word=part(new.inventor,n)
   ;IF NOT EXISTS (SELECT * FROM inv WHERE invname=word) THEN
     INSERT INTO inv (invname) VALUES (word)
   ;END IF
   ;INSERT INTO det2inv (didid,diiid) 
    SELECT NEW.id,invid FROM inv WHERE invname=word
   ;SET n=n-1
 ;END WHILE
  -- and similar loops for assignee and cls ...
;END;

Я также определил другую функцию

CREATE FUNCTION cntparts (var varchar(1024)) RETURNS int
RETURN 1+LENGTH(var)-LENGTH(REPLACE(var,'|',''));

подсчет слов в данном varchar. Это также можно использовать для создания циклов вместо моих фиксированных конструкций UNION для базового преобразования в моем первом посте.

Триггер теперь заботится обо всех новых INSERT. Аналогичный триггер все еще нужно написать, чтобы сделать то же самое для UPDATEs. Это не должно быть слишком сложно сделать...

В моем SQLfiddle я вставил еще одну строку в detail после определения триггера. Результаты представлены двумя сравнительными операторами SELECT, см. fiddle.

ответить на последний комментарий:

Что ж, как я предложил в своем первоначальном ответе, вы должны сначала импортировать все данные (без установки каких-либо триггеров!!!!), а затем просмотреть таблицу detail с операторами SELECT/UNION. Прежде чем вы это сделаете, вы должны узнать максимальное количество слов в каждом из столбцов assignee, inventor и ipsubclass с помощью

SELECT MAX(cntparts(inventor)) invcnt,
       MAX(cntparts(assignee)) asscnt,
       MAX(cntparts(ipsubclass)) clscnt 
FROM detail

Затем вы можете настроить количество SELECT/UNION статусов, необходимых для каждого столбца. Затем заполните таблицы ссылок, как показано в SQLfiddle.

Возможно, весь процесс займет некоторое время, но вы можете безопасно работать с одной таблицей за другой (сначала с фактической таблицей атрибутов, а затем с связанной таблицей ссылок).

После этого вы можете активировать свой триггер, который должен работать только с отдельно добавленными строками.

person Carsten Massmann    schedule 03.09.2013
comment
Спасибо за ссылки, в моем случае данные будут вставлены в таблицу details, поэтому это не повлияет на пользователей, чтобы дополнительно подтвердить доступность таблиц для пользователей, будет только 1 пользователь, вставляющий только 1 проект за раз. - person Fr0zenFyr; 03.09.2013
comment
@Fr0zenFyr Вы, вероятно, уже просмотрели код, но да, триггерная процедура сначала проверяет с IF NOT EXISTS (SELECT * FROM inv WHERE invname=word) перед вставкой записи в таблицы inv (и аналогично для assignee и cls). В моем примере имя «Беппу, Шиори» снова было вставлено в detail, но не снова в inv. Но, конечно, ссылка была установлена ​​в det2inv для этой новой записи. - person Carsten Massmann; 03.09.2013
comment
Да, я видел отредактированный ответ. Спасибо за ваше время и терпение со мной. Я просто пробовал это на своей таблице mySQL. Вы были правы, триггеры вызывают много накладных расходов. Есть ли способ сделать это после полной загрузки данных для проекта (данные импортируются в электронную таблицу с помощью phpMyAdmin) вместо каждой строки. - person Fr0zenFyr; 03.09.2013
comment
На данный момент этот подход является единственным решением, которое я могу принять. Вы правы, что это занимает некоторое время для каждой таблицы, но, по крайней мере, это делает работу. Спасибо за ваше терпение и доброту, снимаю шляпу перед вами, сэр. Я куплю этот ответ, потому что он относится к вашему исходному ответу для массового импорта, а также обеспечивает триггер для обработки простых вставок. - person Fr0zenFyr; 09.09.2013

Прежде всего, мне кажется, вы должны разделить свою таблицу на четыре отдельные:

  1. detail (основная таблица, содержащая: id, projectid, publicationnumber, prioritycountry, prioritydate и status)
  2. inv (таблица изобретателя, содержащая: invid, invname и, возможно, больше данных, связанных с изобретателем)
  3. cls (таблица ipsubclass, содержащая: clsid, clsname и, возможно, описание каждого класса)
  4. assignee (содержит данные компаний-правопреемников, таких как: assid, assname ...)

Поскольку между основной таблицей и inv, cls и assignee будут отношения n:m, вам также следует настроить таблицы ссылок, содержащие отношения, такие как

  • det2inv
  • det2cls
  • det2ass

Задачу реструктуризации можно разбить на несколько этапов:

Прежде всего, вам нужно применить пользовательскую функцию для разделения комбинированных значений. Вы можете сделать это с помощью функции, описанной здесь.

Я еще немного упростил это, так как в вашем примере мы сталкиваемся только с одним разделителем |:

CREATE FUNCTION part( x VARCHAR(255), pos INT) 
RETURNS VARCHAR(255) BEGIN
 DECLARE delim char(1)
 ;SET delim='|'
 ;RETURN TRIM(REPLACE(SUBSTRING(SUBSTRING_INDEX(x, delim, pos),
                   LENGTH(SUBSTRING_INDEX(x, delim, pos -1)) + 1),
       delim, ''))
 ;END;

(Обратите внимание на функцию TRIM, которая удаляет ненужные пробелы...)

Затем вы должны определить целевые таблицы, содержащие ваших изобретателей и возможных подклассов ip (... и правопреемников, чего я еще не сделал):

CREATE TABLE inv (invid int auto_increment PRIMARY KEY, invname nvarchar(64));
CREATE TABLE cls (clsid int auto_increment PRIMARY KEY, clsname nvarchar(6));

Не стесняйтесь расширять таблицы дополнительными столбцами по мере необходимости.

Теперь заполняем таблицы уникальными значениями. Сначала изобретатели в таблице inv:

INSERT INTO inv (invname) 
SELECT inv FROM (
 SELECT part(inventor,1) inv from detail
 UNION 
 SELECT part(inventor,2) from detail
 UNION 
 SELECT part(inventor,3) from detail
 UNION 
 SELECT part(inventor,4) from detail
 UNION 
 SELECT part(inventor,5) from detail
 UNION 
 SELECT part(inventor,6) from detail
 UNION 
 SELECT part(inventor,7) from detail
 UNION 
 SELECT part(inventor,8) from detail
) t WHERE inv>'' ORDER BY inv;

затем ipsubclasses:

INSERT INTO cls (clsname)
SELECT icls FROM (
 SELECT part(iclass,1) icls from detail
 UNION 
 SELECT part(iclass,2) from detail
 UNION 
 SELECT part(iclass,3) from detail
 UNION 
 SELECT part(iclass,4) from detail
 UNION 
 SELECT part(iclass,5) from detail
 UNION 
 SELECT part(iclass,6) from detail
 UNION 
 SELECT part(iclass,7) from detail
 UNION 
 SELECT part(iclass,8) from detail
) t WHERE icls>'' ORDER BY icls;

В моем примере я просмотрел только первые 8 записей каждого поля. Это может быть изменено в соответствии с вашими потребностями, конечно. В итоге вы получите две таблицы с уникальными номерами, содержащие всех возможных изобретателей и все возможные подклассы ip (и аналогичным образом всех правопреемников). Вы можете посмотреть мой SQLfiddle здесь: http://sqlfiddle.com/#!2/aeafe /1

Теперь осталось заполнить таблицы ссылок подходящими ключами (парами идентификаторов из основной таблицы details и ее таблиц атрибутов inv, cls и assignee.

Изменить

Таблицы ссылок заполняются следующими утверждениями:

INSERT INTO det2inv (didid,diiid)
SELECT id,invid FROM inv 
INNER JOIN detail ON INSTR(inventor,invname)>0;

INSERT INTO det2cls (dcdid,dccid)
SELECT id,clsid FROM cls 
INNER JOIN detail ON INSTR(iclass,clsname)>0;

-- ... and a similar one for det2ass

Функция INSTR() не будет работать идеально, так как такие имена, как Hagen, Pete, будут успешно сопоставляться с Gleichenhagen, Peter. Чтобы избежать этих случаев, сравнение должно быть изменено, как показано здесь:

...
INNER JOIN detail ON INSTR(REPLACE(CONCAT('|',inventor,'|'),' ',''),
                           REPLACE(CONCAT('|',invname,'|'),' ',''))>0;

Вы можете увидеть полный рабочий пример здесь: http://sqlfiddle.com/#!2/097be/8

person Carsten Massmann    schedule 02.09.2013
comment
Спасибо за подробное объяснение и очень красивую иллюстрацию с использованием скрипок. Я пойду и проверю, как это работает в моей ситуации, и отчитаюсь здесь, хотя я сомневаюсь в производительности этого подхода для моих огромных данных. Проблема с этим кодом для меня в том, что я не могу сказать о количестве разделителей в столбце, оно может быть от 0 до 80. Делать UNION 80 раз — хорошая идея? Предложенная вами схема таблицы точно такая же, как я сделал (и упомянул в вопросе :), см. Также следующий комментарий. - person Fr0zenFyr; 03.09.2013
comment
Мне было интересно, есть ли способ сделать это с помощью хранимой процедуры, потому что я продолжаю получать новые данные в details, которые я не могу контролировать. Итак, мой план состоял в том, чтобы написать хранимую процедуру и создать триггер для ее запуска каждый раз, когда приходит новый проект. Я действительно не могу зависеть от запуска этой функции вручную. - person Fr0zenFyr; 03.09.2013
comment
Пока у меня нет опыта работы с триггерами. Я воздерживался от них, потому что много лет назад наблюдал ситуацию, когда у моего коллеги были серьезные проблемы с производительностью, вызванные блокировками, наложенными (медленно работающими) запускаемыми хранимыми процедурами. Однако я предполагаю, что в вашем случае было бы возможно выполнить инициированное преобразование входящих/измененных строк, поскольку это нужно будет работать только с ранее выбранным набором строк. посмотрю еще разок... - person Carsten Massmann; 03.09.2013
comment
Спасибо @cars10. Я буду ждать вашего обновленного ответа или нового ответа. - person Fr0zenFyr; 03.09.2013
comment
игнорирует ли UDF вставки уже существующих изобретателей/правопреемников/ipcclasses в таблицы. Я имею в виду, например, если имя ABC corp. уже существует в исполнителях таблицы из-за данных для проекта 1, будет ли оно игнорироваться, когда такое же имя будет найдено в проекте 2, или оно будет вставлено снова? - person Fr0zenFyr; 03.09.2013