Этот способ был неоднократно мной использован при внедрении различных программных комплексов и существенно облегчал перенос данных из одной базы в другую. Полной автоматизации процесса переноса данный способ не предлагает, однако позволяет довольно ощутимо сократить время переноса данных и облегчить ручной труд пользователя.
Программный код написан на встроенном языке 1С, однако данный алгоритм можно адаптировать для любых программных сред, ведь главное принцип.
Ситуация, когда какая-то информация в базе данных хранится не в виде структуры полей, а в произвольном виде, знакома многим программистам. Это может быть адрес, наименование предприятия, название поставщиков, покупателей или подрядчиков. И рано или поздно наступает момент, когда всю эту информацию необходимо перенести в структуру справочников.
Самое простое, с точки зрения программиста, решение — это предложить пользователям вручную заполнить поля структуры из текстового поля. При этом программист заранее подготовит структуру справочников, создаст удобный интерфейс и пользователям остается вручную заполнять информацию в базе данных.
Однако по изложенным выше причинам я стараюсь избегать данного способа и прибегаю к нему в самых крайних случаях.
Для начала рассмотрим перенос информации в структуру полей на примере поля с наименованием контрагента. Допустим, в базе данных в документах оплаты контрагент заполнялся в текстовом поле в виде комментария вот в таком виде:
ОАО «Астра» |
ОАО «Астра» |
ОАО«Астра» |
ОАО «Астра» |
ОАО«Астра» |
Общество с огранич.отв. «Астра» |
Общество с огранич.отв. «Астра» |
ООО «Космодесант» |
ООО «Космодесант» |
Создадим в конфигурации справочник Контрагенты. Создадим в документе оплаты реквизит Контрагент и установим тип реквизита как СправочникСсылка.Контрагенты.
Выведем поле для заполнения реквизита на форму документа. При этом поле с текстовым названием контрагента удалять не будем, оно содержит информацию о контрагентах, которую мы будем переносить.
Создадим еще один справочник СоответствиеКонтрагентов с реквизитами: Контрагент (тип реквизита установим как СправочникСсылка.Контрагенты) и ТекстКонтрагент (тип реквизита установим как Строка неограниченной длины, или же можно поставить такую же длину, как и у текстового поля Контрагенты в документе Оплаты). В этот справочник мы занесем соответствие между текстовой информацией из текстового поля документа Оплаты и записью из справочника Контрагенты.
Следующий этап - заполнение справочника СоответствиеКонтрагентов.
Выберем с помощью запроса все значения текстового поля Контрагенты из документа Оплаты. Значения будем выбирать без повторений. Мы исходим из того, что как правило, каждый пользователь заносит текстовую информацию в базу данных по определенному шаблону. Например, кто-то напишет название контрагента как ОАО «Астра» - после ООО всегда ставит пробел и кавычки, кто-то может написать Общество с огранич.отв. «Астра», кто-то ОАО«Астра» без пробелов после ОАО. Таким образом, первый пользователь в документах, заполненных им, будет указывать название контрагента как ОАО «Астра» и таких документов может быть несколько сотен, второй пользователь будет в большинстве случаев заносить название контрагента как Общество с огранич.отв. «Астра», и таких документов тоже может быть несколько сотен. Наша задача отобрать и занести в справочник СоответствиеКонтрагентов все возможные случаи написания названий контрагентов и указать каждому из этих вариантов написания ссылку на контрагента из справочника Контрагенты. При этом нельзя вносить изменения в поле с текстовым названием контрагента, так как в дальнейшем по этому полю мы программно заполним поле Контрагент в документе Оплаты.
Итак, создадим запрос и выберем все возможные варианты написания из текстового поля с названием контрагентов и заполним наш справочник СоответствиеКонтрагентов результатами этого запроса Заполнять будем только поле ТекстКотрагент, поле Контрагент ссылочного типа придется заполнять вручную. Однако заполнение вручную этого поля по трудоемкости гораздо меньше, чем перенос информации вручную из текстового поля в поле ссылочного типа.
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ РАЗЛИЧНЫЕ(ТекстКотрагент) КАК ТекстКонтрагент
| ИЗ Документ.Оплаты ";
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
нэ = Справочники. СоответствиеКонтрагентов.СоздатьЭлемент();
нэ.ТекстКонтрагент = Выборка.ТекстКонтрагент;
нэ.Записать();
КонецЦикла;
Таким образом мы заполнили поле ТекстКонтрагент справочника СоответствиеКонтрагентов той информацией, которую вносил пользователь в базу данных в текстовом виде, но заполнили ее без повторений:
ОАО «Астра»
ОАО«Астра» |
Общество с огранич.отв. «Астра» |
ООО «Космодесант» |
После заполнения поля ТекстКонтрагент справочника СоответствиеКонтрагентов, необходимо заполнить каждому элементу контрагента из справочника Контрагенты. Эту работу как раз и можно поручить пользователям, записей будет гораздо меньше, чем в первоначальном варианте заполнения и на выполнение этой работы у пользователей уйдет меньше времени.
Когда справочник СоответствиеКонтрагентов будет заполнен можно с помощью несложного кода заполнить поля документа Оплаты. Так как документы Оплаты вероятно были созданы в прошлые периоды, и эти периоды уже закрыты, то изменения в документах следует производить без движения данных документов по регистрам, для этого мы используем признак ОбменДанными:
СсылкаНаОбъект.ОбменДанными.Загрузка = Истина;
А при записи документа мы будем использовать режим записи Запись:
СсылкаНаОбъект.Записать(РежимЗаписиДокумента.Запись);
Выбираем запросом все документы Оплаты, у которых заполнено поле ТекстКонтрагент.
С помощью левого соединения соединяем эти документы с элементами справочника СоответствиеКонтрагентов, соединение будет происходить по текстовому полю ТекстКонтрагент. В качестве результата, возвращаемого запросом, выбираем поля ссылка на документ и ссылка на элемент справочника Контрагенты. Вот как это будет выглядеть в коде программы:
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ Док.Ссылка КАК СсылкаДокумент,
| Спр.Контрагент КАК Контрагент
| ИЗ Документ.Оплаты КАК Док
| ЛЕВОЕ СОЕДИНЕНИЕ Справочник.СоответствиеКонтрагентов КАК Спр
| ПО Док.ТекстКонтрагент = Спр.ТекстКонтрагент
|
| ГДЕ Док.ТекстКонтрагент<>""""
|";
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
СсылкаНаОбъект = Выборка.СсылкаДокумент.ПолучитьОбъект();
СсылкаНаОбъект.ОбменДанными.Загрузка = Истина;
СсылкаНаОбъект.Контрагент = Выборка.Контрагент;
СсылкаНаОбъект.Записать(РежимЗаписиДокумента.Запись);
КонецЦикла;
Теперь рассмотрим пример, когда нам надо перенести информацию из текстового поля ТекстАдрес в структуру полей Населенный пункт, Улица, Дом, Корпус, Квартира.
В этом случае нам предстоит провести более сложный анализ хранящейся в текстовом поле ТекстАдрес информации. В качестве элементов, определяющих где заканчивается тот или иной элемент адреса (под элементами адреса мы имеем в виду населенный пункт, улицу, дом, корпус и квартиру) мы будем использовать разделители, которые в тексте использовал пользователь — пробел, запятая. Для выделения из текстовой строки дома, корпуса и квартиры мы будем использовать разделители вида «/», «\», «к.», «корп.», «дом.», «дом », «д.», «д. » и так далее. Состав этих разделителей мы определим по мере заполнения структуры полей.
Итак, для начала нам необходимо создать справочники для хранения адресной информации, это справочник НаселенныеПункты, справочник Улицы и справочник Дома. Справочник НаселенныеПункты будет являться Владельцем справочника Улицы, а справочник Улицы в свою очередь будет являться Владельцем справочника Дома. Для структурных единиц адреса Корпус и Квартира создавать отдельные справочники не будем, Корпус будет указан не как отдельный номер корпуса, а как элемент справочника Дома, то есть например, для дома номер 3 и дома номер 3 корпус 2 в справочнике Дома будут созданы два элемента: 3 и 3корпус2. Информацию о номере квартиры мы будем хранить в виде реквизита строкового типа в тех справочниках, в которых необходимо использовать номер квартиры.
После создания всех необходимых справочников для хранения адресной информации создадим еще один справочник СоответствиеАдресов. Это временный справочник и после разделения адресной информации мы его удалим из конфигурации. Создадим реквизиты справочника СоответствиеАдресов:
ТекстАдрес — строковый тип, используется для хранения адреса в текстовом виде, в котором он хранится в базе в данный момент.
ТекстАдресПодстрока — строковый тип, сюда будем записывать остаток необработанной строки.
ТекстНаселенныйПункт — строковый тип
ТекстУлица — строковый тип
ТекстДом — строковый тип
ТекстКвартира — строковый тип
НаселенныйПункт — тип СправочникСсылка.НаселенныеПункты
Улица - тип СправочникСсылка.Улицы
Дом — тип тип СправочникСсылка.Дома
Квартира — строка
Так же создадим еще четыре вспомогательных справочника СоответствиеНаселенныхПунктов, СоответствиеУлиц, СоответствиеДомов и СоответствиеКвартир.
Алгоритм разделения текстовой строки одинаков для всех справочников:
1). Определяем первое вхождение разделителя (может быть несколько видов разделителей);
2). Отсекаем подстроку от начала строки до разделителя;
3). Записываем отсеченную строку во вспомогательный справочник СоответствиеАдресов в соответствующее текстовое поле;
4). Выбираем с помощью запроса значения отсеченной подстроки без повторений и записываем результаты запроса в соответствующий вспомогательный справочник;
5) Во вспомогательных справочниках в поле ссылочного типа заполнить значения из справочников адресной структуры. Эту работу можно поручить пользователям;
ВАЖНО! - не корректировать значения вспомогательных справочников в текстовых полях. Если в текстовом поле находится информация, которой нельзя подобрать соответствие из справочников адресной структуры, то следует такие поля оставлять пустыми, и в последствии заполнить строки в справочнике СоответствиеАдресов вручную.
6) Заполнить программно поля ссылочного типа в справочнике СоответствиеАдресов;
7) Заполнить программно поля адресной структуры в документах или справочниках базы данных, используя справочник СоответствиеАдресов.
Данный метод не даёт стопроцентный автоматизированный разбор текстовой строки, однако в некоторых случая его можно использовать на практике, т. к. он может сократить время обработки текстовой информации и облегчить труд пользователей.
Разберем на небольшом примере разбор адресной строки. Предположим, в текстовом поле АдресТекст находится информация об адресе вот в таком виде:
1 |
Комсомольск-на-Амуре, Ленина 7/2-12 |
2 |
Комсомольск-на-Амуре пр-т Мира д.8 кв.18 |
3 |
Комсомольск-н\А улица Мира дом 14/2 кв 22 |
4 |
Ленина 4-3 |
5 |
г.Комс. пр.Мира 45-3 |
6 |
Комсомольск-на-Амуре, Октябрьский 12/2-12 |
7 |
Комсомольск-н\А проспект Октябрьский дом 1 кв 20 |
Первым шагом отделим название населенного пункта. Название города записано в нескольких вариантах, но его можно выделить почти во всех случая, кроме четвертой строки, где название горда не указано. Эту строку мы не сможем программно разделить, ее придется впоследствии обрабатывать вручную. Разделители для определения названия города будут использованы такие: пробел и запятая.
Запрос = Новый Запрос;
Запрос.Текст = " Выбрать ТекстАдрес
|ИЗ Справочник.СоответствиеАдресов
|";
ТЗ = Запрос.Выполнить().Выгрузить();
Для каждого стр из ТЗ Цикл
ЧастьАдреса = "";
//Для корректной выборки названия населенного пункта нам необходимо //предварительно заменть в строке адреса «г.К» на «К»
СтрокаДляПоиска = СтрЗаменить(стр.ТекстАдрес, "г.К", "К");
НомерРазделителя1 = СтрНайти(СтрокаДляПоиска, ",");
НомерРазделителя2 = СтрНайти(СтрокаДляПоиска, " ");
Если НомерРазделителя1<НомерРазделителя2 Тогда
НомерРазделителя = НомерРазделителя1;
Если НомерРазделителя = 0 Тогда
НомерРазделителя= НомерРазделителя2;
КонецЕсли;
Иначе
НомерРазделителя = НомерРазделителя2;
КонецЕсли;
Если НомерРазделителя>0 Тогда
ЧастьАдреса =сред(СтрокаДляПоиска,1,НомерРазделителя);
КонецЕсли;
Если ЧастьАдреса<> "" Тогда
ТекстАдресПодстрока = СокрЛП(Прав(СтрокаДляПоиска,СтрДлина(СтрокаДляПоиска)-НомерРазделителя));
Об = стр.Ссылка.ПолучитьОбъект();
Об.ТекстНаселенныйПункт = ЧастьАдреса;
//запишем необработанную часть строки с улицей, домом и корпусом
Об.ТекстАдресПодстрока = Прав(СтрокаДляПоиска,СтрДлина(СтрокаДляПоиска)-НомерРазделителя);
Об.Записать();
КонецЕсли;
КонецЦикла;
В результате получим такое разделение текстовой строки на населенный пункт и остальную часть строки:
ТекстНаселенныйПункт |
ТекстАдресПодстрока |
Комсомольск-на-Амуре, |
Ленина 7/2-12 |
Комсомольск-на-Амуре |
пр-т Мира д.8 кв.18 |
Комсомольск-н\А |
улица Мира дом 14/2 кв 22 |
Ленина |
4-3 |
Комс. |
пр.Мира 45-3 |
Комсомольск-на-Амуре, |
Октябрьский 12/2-12 |
Комсомольск-н\А |
проспект Октябрьский дом 1 кв 20 |
Заполняем справочник СоответствиеНаселенныхПунктов
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ РАЗЛИЧНЫЕ(Текст) КАК Текст
|ИЗ Справочник. СоответствиеАдресов
|
|";
ТЗ = Запрос.Выполнить().Выгрузить();
Для каждого стр из ТЗ Цикл
нс = Справочники.СоответствиеНаселенныхПунктов.Добавить();
нс.Текст = стр.Текст;
нс.Записать();
КонецЦикла;
В итоге справочник СоответствиеНаселенныхПунктов будет заполнен такой информацией:
Комсомольск-на-Амуре, |
Комсомольск-н\А |
Комсомольск-на-Амуре |
Ленина |
Комс. |
Аналогичным образом заполняем улицу, но уже поиск будем проводить по полю ТекстАдресПодстрока. В коде из строки поиска необходимо предварительно убрать наименования улиц и проспектов «пр-т», «ул.», «ул » и т. п.