Импорт данных

Материал из Eludia.

Перейти к: навигация, поиск

Содержание

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

Примерная постановка задачи

Допустим, вашему приложению так или иначе доступны 2 внешние таблицы: "организации" и "документы". Каждая строка таблицы "организации" содержит её уникальный код и наименование:

0001  1-я ГКБ
0002  2-я ГКБ

Таблица "документы" содержит уникальный код документа, код организации, регистрационный номер и дату:

000001  0001  №13/286   01.02.2006
000002  0001  №13/253   03.02.2006
000003  0002  №0-У\йеа  01.01.1980

Две внешние таблицы естественным образом отображаются в 2 таблицы в вашей БД. Назовём их organisations и docs. Какую структуру должна иметь таблица organisations? Символьное поле label – это понятно. А нужно ли ещё что-то? В принципе вы можете считать приравнять код организации обязательному полю id и использовать в импорте MySQL-инструкцию REPLACE INTO. Однако поступать так можно только в том случае, если внешнюю БД поддерживаете вы лично. Иначе в любой момент может обнаружиться, что после 0009 идёт 000A или просто вам объявят, что с такого-то числа код стал составным, поскольку во внешнюю БД добавили данные из удалённого филиала.

Чтобы заранее обезопасить себя от подобных неприятностей, лучше считать, что вам доступен натуральный ключ, который в будущем может сменить тип на символьный, и завести под него отдельное поле, скажем, external_id, не забыв построить соответствующий индекс.

Использование sql_select_id

Теперь займёмся импортом организаций. Допустим, мы прочитали запись с кодом $external_id и наименованием $label. Требуется добавить запись в наш справочник, но только если там нет строки с данным кодом. Для этого предназначена API-функция sql_select_id:

my $id = sql_select_id (organisations => {
 fake        => 0,
 label       => $label,
 external_id => $external_id,
}, ['external_id']);
   

Поле fake упомянуто для того, чтобы новая запись была создана как актуальная, а external_id в последнем аргументе фигурирует в качестве ключевого поля. Если бы ключ был составным, скажем, включал бы в себя код внешнего подразделения external_id_department, то вызов функции принял бы вид:

my $id = sql_select_id (organisations => {
 fake        => 0,
 label       => $label,
 external_id => $external_id,
 external_id_department => $external_id_department,
}, ['external_id', 'external_id_department']);
   

Составные ключи

Возможен также поиск по более чем одному натуральному ключу. Например, если для некоторых записей указан ОГРН, а для других – пара ИНН/КПП, то можно вызвать sql_select_id с двумя описаниями ключей:

my $id = sql_select_id (organisations => {
 fake        => 0,
 label       => $label,
 inn         => $inn,
 kpp         => $kpp,
 ogrn        => $ogrn,
}, ['inn', 'kpp'], ['ogrn']);
   

Актуализация найденных записей

Теперь предположим, что вы периодически, скажем, ежедневно, синхронизируете справочник организаций. Рано или поздно какая-либо из его строк модифицируется. Рассмотрим только возможность смены наименования организации при постоянных ИНН/КПП и ОГРН. Итак, чтобы быть уверенным в актуальности справочника, нужно обновлять значение поля label. Для этого можно организовать отдельный вызов sql_do, однако гораздо проще и нагляднее воспользоваться специальной возможностью всё той же sql_select_id: указанием "форсированных" аргументов при помощи префикса '-':

my $id = sql_select_id (organisations => {
 fake        => 0,
 -label      => $label,
 inn         => $inn,
 kpp         => $kpp,
 ogrn        => $ogrn,
}, ['inn', 'kpp'], ['ogrn']);    

Форсированное поле можно использовать в том случае, когда имеется абсолютное доверие к источнику данных. Однако в жизни возможно, например, такое: во внешней БД ОГРН является ключевым полем, а ИНН/КПП заполняются от случая к случаю. Тем не менее, даже такая информация может оказаться нелишней. Одним словом, поля ИНН/КПП нельзя объявлять форсированными, но если в нашей БД они пусты, то на их место надо записать данные из внешнего источника. Именно так работает sql_select_id, никаких дополнительных действий предпринимать не требуется.

Далее при импорте документов нам понадобится вычислять ссылки на организации в нашей БД. Для этого снова следует воспользоваться функцией sql_select_id, только не указывать значение поля label. Можно воспользоваться и sql_select_scalar, но это менее предпочтительно. Если вдруг случится так, что сначала вам придётся импортировать документ, а потом организацию, то sql_select_scalar вернёт пустое значение и документ останется "подвешенным", а sql_select_id заведёт запись с пустым наименованием и нужным кодом, которая может быть найдена и обновлена при дальнейшем импорте. Кроме того, вызов sql_select_id гораздо нагляднее.

Удаление и слияние в справочниках

Теперь обратимся к тому прескорбному случаю, когда некая таблица (к примеру, справочник организаций) может как пополняться из внешнего источника, так и редактироваться пользователями вашей информационной системы. Вообразите себе такой сценарий: ваш оператор заводит карточку организации, после чего та же самая организация появляется в импортируемых данных и порождает дубликат. Допустим, дубликат замечен и ликвидирован (см. выше), причём удалённой оказалась как раз запись, порождённая при синхонизации. Что произойдёт при следующем импорте? Оказывается, ничего страшного: sql_select_id будет выдавать номер актуальной записи. Во-первых, потому что значение external_id скопируется из дубликата в пустое поле актуальной записи. Во-вторых, даже если оно каким-либо образом испортится, всё равно процедура sql_select_id найдёт удалённый дубликат и пройдёт по цепочке ссылок is_merged_to до актуальной записи.

Кэширование id

Если документов много, а организаций, на которые они ссылаются, относительно мало, то sql_select_id будет помногу раз вызываться с одинаковыми аргументами. В этой связи разумно реализовать кэширование результатов этой функции. Тогда поиск организаций с использованием кэша может выглядеть примерно так:

my %organisations = ();

while (...) {

 my $id = 
  $organisations {"$inn/$kpp/$ogrn"} 
  ||= sql_select_id (organisations => {
   fake        => 0,
   -label      => $label,
   inn         => $inn,
   kpp         => $kpp,
   ogrn        => $ogrn,
  }, ['inn', 'kpp'], ['ogrn']);    

}

При использовании кэша надо внимательно следить за габаритными параметрами импортируемых данных, поскольку есть реальная опасность перерасходовать память. В простых случаях стандартные хэши вполне применимы, а для больших объёмов справочников можно использовать модули типа Tie::Cacher.

Вложенные вызовы sql_select_id

При импорте плоской таблицы, где значения полей многих справочников перемешаны с атрибутами одной большой таблицы фактов, в нормализованную реляционную схему, бывает удобно совместить несколько вызовов sql_select_id в рамках одной операции:

my %organisations = ();

while (...) {

 sql_select_id (documents => {
  fake            => 0,
  external_id     => $external_id,
  no              => $no,
  dt              => $dt,

  id_organisation => 

   $organisations {"$inn/$kpp/$ogrn"} ||= 
   sql_select_id (organisations => {
    fake            => 0,
    -label          => $label,
    inn             => $inn,
    kpp             => $kpp,
    ogrn            => $ogrn,
  }, ['inn', 'kpp'], ['ogrn'])

 }, ['external_id']),
 
}
Личные инструменты
Консультации
Разработчику
Администратору