КЛАДР

Вариант использования КЛАДР

© 2007 Андрей Дмитриев

- А вы думаете, это нам поможет?
- Я думаю, это нам не помешает!
Из "Одесских джентльменов"

На сегодняшний день из всех официальных классификаторов и справочников, применяемых в РФ, на мой взгляд, самым неоднозначным как по применению, так и по содержанию, является так обзываемый КЛАДР (классификатор адресов). Придуманный и ведомый совместно налоговой и почтовой службами страны, он должен был по видимому служить для облегчения работы с адресными объектами в прикладных задачах. Да что-то, похоже, не задалось. Удобство его применения сведено к минимуму, про наполнение и актуальность тоже много нелестных слов. Но вот раз нет другого источника адресной информации – приходится работать с тем, что имеем. И так, о чём это я? Ах, да...

В последнее время достаточно часто стал натыкаться на просторах Интернета на вопрос типа: "Люди, а кто как использует в своих задачах КЛАДР?" А так как тема сия меня очень интересует (периодически занимаюсь налоговой отчётностью, да вот сейчас работаю в сфере учёта имущества), то внимательно следил за ответами. Да вот только ничего интересного в ответах не появилось: то ли народ в эту сторону особо не думал, то ли говорить никто не хочет, то ли и правда в этом вопросе всё так запущено. Посему решил поделиться своими наработками – благо чуток свободного времени образовалось. Представленное решение, возможно, не бесспорное, но как это работает меня (да и пользователей) вполне устраивает. Чуть-чуть общих слов про это дело.

Суть работы с КЛАДРом проста – составить необходимую адресную строку на основе информации, заложенной в базе данных. Ничего вроде сложного, но хотелось бы делать это быстро (по крайней мере, не медленнее ручного ввода) и удобно (удобство – понятие достаточно неопределённое, но почему-то легко ощущаемое, когда оно есть). Поэтому при разработке части задачи, связанной с составлением адреса, в первую очередь упор делался на создание удобного интерфейса для пользователя. Ну и как всегда оказалось, что сделать что-нибудь приемлемое, можно только решая задачу комплексно. В результате, подстраивая и меняя друг под друга то интерфейсные дела, то структуру хранения данных, то методы получения информации из базы, я пришёл к некоему устраивающему меня решению. Кстати, долго шёл, года три получится, проверил кучу вариантов, поэтому в спорных местах, на вопрос – А почему так? – могу только сказать – Так было удобнее в целом для задачи. А так как интерфейс рисовался на Delphi, решил уместным сбросить статью именно сюда.

Итак, чем делиться буду. Сперва своими мыслями по поводу структуры данных КЛАДР. Поругать чужое – без этого никак. Вторым этапом потребуется пояснить, почему я перекроил эту самую структуру (а без этого не обошлось), и что из этого получилось. Третьим делом разрисую прелести выбранного интерфейса, с комментариями и картинками. Четвёртый шаг – выдам некую тестовую утилиту, которая, являясь вполне самостоятельной программой, продемонстрирует всё изложенное. В принципе желающие могут начать просмотр с последнего пункта, с демонстрации. А то потом вдруг и читать будет не интересно.

  1. Немного слов про КЛАДР. Основным недостатком (и непреодолимым в рамках нынешней структуры классификатора) я считаю то, что КЛАДР НЕ является справочником, в том смысле, в котором этот термин используют применительно к базам данных. То есть, нет возможности использовать код (первичный ключ) адреса в некой пользовательской базе для хранения привязки к остальной адресной информации, так как код, в общем случае, неоднозначно определяет привязку к этой самой информации. Поясняю сию мудрую мысль. Это происходит из-за того, что код состоит из непосредственно уникальной части и поля модификации, которое при нулевом значении обозначает, что адрес актуален, а ненулевое – что написание адреса устарело (или адрес аннулирован). Вот по изменённым названиям и происходят чудеса: код тот же, поле модификации то же, а информация уже новая. В результате, поместив в свою таблицу, скажем, код 66000013000000000 несколько лет назад (во времена, когда город ещё назывался Свердловск-45 и КЛАДР уже существовал), сейчас вы по этому коду получите название г. Лесной. Старое название осталось, теперь его закодировали цифрой 66000013000000001, но это по сути своей уже другая запись. Причём отбросить поле модификации нельзя – потеряется уникальность ключа. Такое плавающее кодирование вынуждает разработчика хранить в пользовательской базе данных полное написание адресной строки, что более накладно как для хранения, так и для операций анализа по адресной привязке. Т.е. по сути, классификатор служит только для формирования и сверки написания строки адреса в некий ограниченный временной период, до выхода следующей версии КЛАДР. Что-то маловато для такого объёма информации. А, скажем, для использования классификатора в сфере учёта имущества (да, наверное, и во многих других областях), явно требуется база с сохранением истории адресных изменений. Вот такой жирный минус получается. Есть и другие неприятные моменты, но они обходятся в рабочем порядке и носят чисто техническую подоплёку.

  2. Теперь пошли слова про базу данных и её структуру. Во-первых – это не оригинальный DBF формат. Если помните, то требовалось создать интерфейс, позволяющий создать адресную строку не медленнее её ручного ввода. При этом контроль со стороны программы позволял бы избежать ошибок, сплошь и рядом встречающихся при ручном заколачивании данных. Уже большой плюс, за который стоит бороться. Для определения, каких либо численных критериев быстрой и комфортной работы пользователя были проведены эксперименты над собой и подвернувшимися под руку окружающими. На основании этих опытов были выставлены следующие временнЫе рамки: время на формирование всей необходимой структуры данных, позволяющей начать работу (выбор) не должно превышать 4-5 секунд и время, затрачиваемое пользователем, на формирование итоговой адресной строки не должно превышать 20-25 секунд. Для того чтобы втиснуться в указанные показатели, нужно быстро и надёжно перелопачивать значительный объём данных, составляющих КЛАДР. Сразу на берегу определяем, что работаем только с полным объёмом адресов и по регионам или ещё как делить базу на куски не будем. Напрягаться, так по максимуму. Из этих условий вполне очевиден выбор – для хранения данных используем SQL-сервер. Т.е. у нас "большая" задача, в которую уместно запихнуть дополнительно 800-900 тысяч записей. Под конкретный SQL-сервер не подстраивался, экспериментировал на Firebird 1.5-2.0, Oracle 9.2-10.2 и даже на Interbase 4.2. По скоростным характеристикам получения данных особых временных различий не наблюдалось (исключая последний случай), что порадовало. Для большей конкретики будем считать, что используется более распространенный и доступный Firebird.

    Теперь, что и как переносим. Первым делам создаём набор требуемых таблиц, несколько отличающийся по структуре от оригинального (комментарии по ходу). Первая переносимая таблица – SOCRBASE.DBF. Честно говоря, не понял, зачем она нужна, если наименования типов адресов имеются в основных таблицах KLADR.DBF и STREET.DBF. По-хорошему, вроде должно быть или там, или там. Единственная идея - ради описательного поля (полного названия типа), которое никто, наверное, и не использует. Поэтому создаём из "ненужной" таблицы типов "нужный и правильный" справочник, а таблицы адресов свяжем с ним внешним ключом.

    create table KLADR_ABBREV (
     ABBREV_LEVEL smallint not null,
     ABBREV_ID smallint not null,
     ABBREV_POS smallint default 0 not null,
     ABBREV_NAME as varchar(40) not null,
     ABBREV_SHNAME as varchar(10) not null
    );
    
    alter table KLADR_ABBREV add constraint PK_KLADR_ABBREV primary key (ABBREV_LEVEL, ABBREV_ID);
    

    Комментарии.

    • Поле KOD_T_ST в оригинальной таблице в разряде сотен содержит номер уровня адреса, т.е. дублирует поле LEVEL. Вначале я отказался от составного первичного ключа, но оказалось, что информация об уровне адреса весьма полезна при работе с базой и весьма желательно её иметь в виде отдельного поля, чтобы постоянно не вычислять. Отсюда и составной первичный индекс. При этом данные из поля KOD_T_ST при переносе делятся на две составляющие: сотни уходят в поле ABBREV_LEVEL, а значащая часть соответственно в ABBREV_ID. Кстати, в качестве небольшого экскурса в историю КЛАДР. Году этак 2004, в таблице SOCRBASE.DBF появились записи с совпадающими значениями в поле KOD_T_ST. Я помнится, тогда вздрогнул, усомнившись в своей мысли, что эти коды должны быть уникальными. Но больше создатели КЛАДР так не шутили.
    • Появилось новое поле ABBREV_POS, которое указывает, какое положение занимает название типа по отношению к самому наименованию адреса. Для налоговой отчётности сие поле не требовалось, так как тип всегда пишется за адресной составляющей, но для привычного написания адресов хотелось бы иметь некий флаг, подсказывающий как правильно располагать адрес и его тип относительно друг друга. По значениям принял, что 0 – это расположение "тип-адрес", 1 – "адрес-тип" и "2" - для варианта, когда тип выводить не требуется.

    Далее создаём таблицу адресов. И сразу комментарии. Если сравнить оригинальные таблицы KLADR.DBF и STREET.DBF, то бросается в глаза, что их структура практически не отличается. Мысль объединить эти таблицы, честно говоря, была не первой, но, в конце концов, она почти вынужденно возникла из таких соображений. Во-первых, я всё таки в своих задачах стараюсь хранить адресные коды (где вместо, где вместе, с полным написанием адреса), получая определённые преимущества в обработке данных. Во-вторых, в отличие от налоговой отчётности, в других областях, где может применяться КЛАДР, адрес часто не имеет полного написания. Т.е. вполне обычно иметь ссылку на некий населённый пункт, как на адрес, плюс некое произвольное описание расположения объекта. А вполне может быть и полный адрес с улицей и прочими причиндалами. В обоих этих случаях заманчиво иметь возможность ссылаться на одну таблицу адресных объектов, а не на разные. Т.е. имеем единую точку входа в справочник адресных объектов. И при этом никаких null-значений для внешних ключей. Вот из этих соображений таблица адресов была сделана единой, и пока я считаю это решение весьма правильным.

    Ещё один комментарий касается структуры связей между адресными объектами. Связи эти есть и это дерево. Причём дерево с нежёсткой иерархией. Т.е. уровень родителя в общем случае не предопределён. Региональный уровень (1) запросто цепляется с уровнем улиц (5), а к уровню района (2), как правило, цепляется уровень посёлков (4). Код родительского адреса "зашит" в самой структуре кода. Это показалось мне неправильным и трудоёмким для последующей обработки, поэтому я разношу код по двум полям: непосредственно ключевому и полю-ссылке на родителя. Таким образом, создаётся древовидная структура в хорошо известном виде. Функция, которая этим занимается, приведена ниже.

    {********************************************************
    * Возвращает код региона (если всё правильно распарсено) или 0
    * sa – код из dbf файла
    * adr, pro – получаемые коды адресной строки и её родителя
    * lvl, suf – код уровня адреса и поля модификации 
    ********************************************************}
    function TFrmMain.DecodeAdrCode(const sa: string;
     var adr, pro: int64; var lvl, suf: integer): integer;
    var
     cc1, cc2, cc3, cc4, cc5, ccd, ln, pos: integer;
    begin
     Result := 0;
     ln := Length(sa);
     if (ln <> 13) and (ln <> 17) then Exit;
     pos := 1;
     try
     cc1 := StrToInt(Copy(sa, 1, 2)); // Регион
     cc2 := StrToInt(Copy(sa, 3, 3)); // Район
     cc3 := StrToInt(Copy(sa, 6, 3)); // Город
     cc4 := StrToInt(Copy(sa, 9, 3)); // Нас пункт
     if ln = 13 then
     begin
     cc5 := 0; pos := 12;
     end else begin
     cc5 := StrToInt(Copy(sa, 12, 4)); // Улица
     pos := 16;
     end;
     ccd := StrToInt(Copy(sa, pos, 2)); // Доп. индекс
     except
     cc1 := 0;
     end;
     if cc1 = 0 then
     begin
     adr := 0; pro := 0; lvl := 0; suf := 0; Exit;
     end;
     // Вычисление кода родителя
     if (cc5 > 0) then
     begin
     lvl := 5; pro := cc2 * 10000000000 + cc3 * 10000000 + cc4 * 10000;
     end else
     if cc4 > 0 then
     begin
     lvl := 4; pro := cc2 * 10000000000 + cc3 * 10000000;
     end else
     if cc3 > 0 then
     begin
     lvl := 3; pro := cc2 * 10000000000;
     end else
     if cc2 > 0 then
     begin
     lvl := 2; pro := 0;
     end else begin
     lvl := 1; pro := 0;
     end;
     adr := cc2 * 10000000000 + cc3 * 10000000 + cc4 * 10000 + cc5;
     suf := ccd;
     Result := cc1;
    end;
    

    А теперь о самой таблице. Хотя нет, всё-таки не об одной. Так как я рассказываю ещё и о своих изысканиях, то продемонстрирую два варианта хранения данных по КЛАДР. При первом варианте в адресной таблице хранится как информация, так и связи между адресными объектами. В этом случае речь может идти только о действующих записях (переносятся записи с полем модификации в адресном коде равным 0), иначе возникают проблемы ссылочной целостности данных. Такая структура более понятна и проста, но позволяет хранить только актуальный срез данных, что вполне достаточно для многих задач. Как хранить историю изменения адресов должны, понятно, придумать авторы КЛАДР (если конечно оно им нужно) или другие заинтересованные структуры. ГИС-Ассоциация к примеру концепцию адресного реестра РФ собирается создать, подключив кучу солидных структур. В общем, работа где-то кипит, а ждать не можется. Поэтому экспериментирую уже сейчас. На мой взгляд, в общих чертах это должно быть так: отдельная таблица для адресных объектов во всех их версиях, на которую и идут ссылки из прикладных задач. И отдельная таблица(ы) по связям между адресными объектами (кросс-таблица). Вопросов тут возникает масса, но для пробы я сделал и такой вариант хранения КЛАДР. Для себя я определил следующие термины к этим вариантам: первый вариант – "актуальная база" (окончание в имени таблиц _2), второй – "база с историей" (окончание _20). Этими словами и буду оперировать. Теперь сами варианты.

    Актуальная база:

    create TABLE KLADR_ADDRESS_2 (
     ADDRESSREGION_ID smallint not null,
     ADDRESS_ID bigint not null,
     ADDRESS_PAR bigint not null,
     ADDRESS_NAME varchar(40) not null,
     ABBREV_LEVEL smallint not null,
     ABBREV_ID smallint not null,
     ABBREV_POS smallint default -1 not null,
    ...
    
    );
    
    alter table KLADR_ADDRESS_2 add constraint PK_KLADR_ADDRESS_2
     primary key (ADDRESSREGION_ID, ADDRESS_ID);
    alter table KLADR_ADDRESS_2 add constraint FK_KLADR_ADDRESS_2_1
     foreign key (ABBREV_LEVEL, ABBREV_ID)
     references KLADR_ABBREV (ABBREV_LEVEL, ABBREV_ID);
    alter table KLADR_ADDRESS_2 add constraint FK_KLADR_ADDRESS_2_2
     foreign key (ADDRESSREGION_ID, ADDRESS_PAR)
     references KLADR_ADDRESS_2 (ADDRESSREGION_ID, ADDRESS_ID);
    

    База с историей:

    create table KLADR_ADDRESS_20 (
     ADDRESSREGION_ID smallint not null,
     ADDRESS_ID bigint not null,
     ADDRESS_VERSION smallint default 0 not null,
     ADDRESS_ACTUAL smallint default 0 not null,
     ADDRESS_NAME varchar(40) not null,
     ABBREV_LEVEL smallint not null,
     ABBREV_ID smallint not null,
     ABBREV_POS smallint default -1 not null,
    ... 
    
    );
    
    create table KLADR_ADDRESS_20_DEPEND (
     ADDRESSREGION_ID smallint not null,
     ADDRESS_ID bigint not null,
     ADDRESS_VERSION smallint default 0 not null,
     ADDRESS_PAR bigint not null,
     ADDRESS_PARVER smallint default 0 not null
    );
    
    alter table KLADR_ADDRESS_20 add constraint PK_KLADR_ADDRESS_20
     primary key (ADDRESSREGION_ID, ADDRESS_ID, ADDRESS_VERSION);
    alter table KLADR_ADDRESS_20 add constraint FK_KLADR_ADDRESS_20_1
     foreign key (ABBREV_LEVEL, ABBREV_ID)
     references KLADR_ABBREV (ABBREV_LEVEL, ABBREV_ID);
    
    alter table KLADR_ADDRESS_20_DEPEND
     add constraint FK_KLADR_ADDRESS_20_DEPEND_1
     foreign key (ADDRESSREGION_ID, ADDRESS_ID, ADDRESS_VERSION)
     references KLADR_ADDRESS_20_DEPEND (ADDRESSREGION_ID, ADDRESS_ID, ADDRESS_VERSION);
    alter table KLADR_ADDRESS_20_DEPEND
     add constraint FK_KLADR_ADDRESS_20_DEPEND_2
     foreign key (ADDRESSREGION_ID, ADDRESS_CHILD, ADDRESS_CHILDVERS)
     references KLADR_ADDRESS_20 (ADDRESSREGION_ID, ADDRESS_ID, ADDRESS_VERSION);
    

    Комментарии.

    • Адресный код был разделён на составляющие для оптимизации работы с данными - был выделен в отдельное поле код региона. Это вызвано, прежде всего, особенностями использования адресной информации. Как правило, задачи, в которых применяется КЛАДР, оперируют данными, не выходящими за рамки одного региона и даже мельче. При таком раскладе, выделение кода региона в отдельную сущность значительно упростило и ускорило доступ к данным. Но как следствие, имеем сложный ключ.
    • В таблице с сохранением истории присутствуют два дополнительных поля. Это ADDRESS_VERSION и ADDRESS_ACTUAL. Из названия понятно - это версия данных для конкретного адресного объекта, и признак актуальности версии. По задумке номер версии последовательно увеличивается, а для актуальности: нулевое значение – данные актуальны, отличное от нуля – код произошедших изменений (ещё один справочник надо придумать).
    • Поле ADDRESS_PAR (для обеих версий) и ADDRESS_PARVERS (для версии с историей) представляют собой ссылку на родительский узел. Т.е. древовидная структура формируется достаточно тривиальным способом. Тут надо уточнить, что в состав внешнего ключа таблицы, связывающей родительские и дочерние объекты, входит поле с кодом региона (оно же и в первичном ключе). При таком раскладе возможна связь адресных объектов только внутри одного региона. Для актуальной базы это бесспорно, а вот для базы с историей, что-то сомневаюсь. Но вот пока так – эксперимент продолжается.
    • В адресных базах также имеется поле ABBREV_POS, которое в отличие от таблицы типов адресов имеет ещё одно значение (-1 по умолчанию), при котором взаимное расположение наименования адреса и его типа выполняется на основании значения из справочной таблицы типов адресов. Т.е. по умолчанию используется общее расположение для данного типа, но есть возможность его переопределить.
    • Все дополнительные поля имеют целочисленный тип не равный null (вместо null используется 0), но это дело вкуса и дальнейшего использования.
    • В первоначальных вариантах поле адресного кода состояло из двух – одно для записей из таблицы адресов (KLADR.DBF), другое – для улицы (STREET.DBF), которое для адреса равнялось нулю. Эксперименты показал, что применение и того и другого варианта практически никак не сказывалось на скорости выборки данных. Единственное отличие: при разных полях для адреса и улицы значения умещаются в тип данных integer, при одном поле нужно применять bigint. По жизни использую оба варианта, в зависимости от особенностей задачи.

    Ещё некоторые замечания по базе. Несколько удивила заметная разница в скорости доступа к данным через прямой запрос и через представление, созданное на базе этого же запроса (это касается Firebird, про Oracle что-то уже не помню). Может это только у меня так, но для проверки сделал отдельные представления под требуемые выборки. Разбираюсь. В демо-программе можно выбрать, каким образом получать данные.

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

    Периодически приходится запускать поиск адресного объекта по всей базе. Эта благородная задача так же возложена на sql-сервер, для чего была тоже написана процедура. В общем, всё есть в скрипте, который прилагается.

    Ну вот, вроде с базой разобрались, теперь к тому, что же делалось на Delphi.

  3. А на Delphi делался интерфейс, причём интерфейс работы с древовидным справочником. Позволю чуток воды по этому поводу. Хорошая вещь – справочник с древовидной структурой. Тут тебе группировка и детализация характеристик в одном флаконе, и отслеживание иерархических зависимостей между параметрами, и интерфейс для выбора приятный: компактно отображается, удобно работать. Одно слово – дерево. Вроде чем не счастье. Но, если справочник объёмом элементов с сотню или если чётко представляешь, в каком узле находится нужный параметр, то всё замечательно. А вот если элементов случится за тысячу (5 тысяч), будет ли так же удобно работать с таким справочником? Да если справочник с произвольной структурой и вложенностью? Да ещё если не знаешь, в каком родительском узле искать требуемый элемент, а из всех знаний о предполагаемом выборе только примерное название? При этом нет уверенности, что не существует созвучных названий, и что первый попавшийся узел с нужными буквами и есть именно тот, который ищется. При таком раскладе уже получается, что деревянный интерфейс уже не так красив и универсален. Вот с такие печальные мысли посещали меня вначале работы по привязке КЛАДР к некой задаче по налогам. Поразмыслив, я всё же решился оставить древовидное отображение структуры данных (ну блин, удобно же!), применив несколько увёрток, специфичных только конкретно для использования КЛАДР. Они заключались в следующем:
    • Обычная ситуация, когда работа ведётся с адресным пространством одного региона, т.е. пытаться строить дерево по всей нашей могучей и необъятной ну совсем незачем. Один регион – это уже почти просто. Оставляем возможность переключения между регионами и идём дальше.
    • Большой кусок информации составляют улицы. Не смотря на то, что они также встроены в адресное дерево, нужно ли их впихивать в отображаемую древовидную структуру? Да не дай Бог! Данные по улицам оказались вторичной информацией, которая определяется после выбора нужного населённого пункта. В результате улицы подгружаются в отдельный грид, по мере необходимости при хождении по дереву. Деревянная структура резко худеет. Такое построить уже совсем без проблем. Опять далее. А теперь почему я забочусь об уменьшении объёма данных в древовидной структуре.
    • А нужно ли строить всю оставшуюся структуру дерева сразу? А вот это оказалось нужным! При полностью загруженных данных, становится очень удобным поиск необходимой адресной информации. Почти моментально просматривается весь массив данных, без обращения к серверу. Есть так же возможность при поиске скрыть (сделать недоступными) ненужные элементы, объём просматриваемой информации резко сокращается, да и просто красиво выглядит. (Картинка будет ниже). При этом соблюдается выше означенное ограничение на время построения всего дерева данных – менее 4-5 секунд. Учитывая, что никто не обязывает строить и убивать форму выбора адреса, каждый раз, когда она нужна, то эти несколько секунд превращаются в разовые затраты. Ну, плюс те же секунды при переключении между регионами.

  4. Теперь конкретно о программе. Так как это тестовая утилита, то она содержит определённое количество переключателей и информационных полей, позволяющих оценить некоторые рабочие характеристики (в основном временнЫе) программы. Что означает конкретная кнопка можно узнать из её хинта (надеюсь нигде не пропустил). Что не описывается хинтами или требует пояснения, опишу ниже. Итак, что позволяет программа:
    • Загрузить данные в базу из оригинальных DBF файлов (Главное меню->Перенос данных). Для этого нужно предварительно указать местоположение файлов КЛАДР ("Установить путь до файлов КЛАДР"). Пункт "Ошибки в log-файл" рекомендую оставить включённым. При этом все замечания при переносе данных (а они, поверьте, будут) записываются в файл в директории запуска программы. Иначе придётся закрывать все окошки сообщений по ходу переноса. Перенос осуществляется по максимально простому алгоритму: сначала удаляются все данные из таблиц (если они были ранее загружены), далее пишутся новые. Процесс достаточно медленный и в зависимости от используемой конфигурации сервера тянется от 20 минут и более. Тут надо принять во внимание, что переносятся два варианта данных (т.е. двукратные затраты), а кроме того тормозят имеющиеся внешние ключи, которые конечно же можно было отключит на это время, но при этом теряется возможность отслеживать ошибки по зависимостям адресных объектов. При многократном выполнении данной процедуры (ну вдруг кому поэкспериментировать по полной захотелось), не помешает сделать бэкап/ресторе, причём лучше без сборки мусора. Да, кстати, не забудьте просмотреть лог-файл переноса - достаточно забавно. Типичная ошибка для актуальной базы – родитель помечен как недействующий (т.е. не переносится), а дочерние объекты остались с нулевым флагом модификации. Привязывать их, стало быть, не к чему. В базе с историей ошибок значительно меньше по определению и они больше похожи на мусор. Типа – "Не найден суффикс "ул" для адреса "Привокзальный" код [5000300016351]". По коду понятно – это ж адрес, а не улица.
    • Поправить флаг положения типа по отношению к адресной составляющей (Главное меню->Перенос Данных->Список типов адресов). Редактируемым является только одно поле ("Позиция"). При переносе всем ставится код 0 (т.е. тип-адрес). Рекомендую после переноса пробежаться, поправить. Структура адресов естественнее выглядеть будет.
    • Работа с древовидной структурой адресных объектов, как с актуальной, так и с сохранённой историей (Выбор адреса->[Актуальный | С историей]). Данные загружаются на указанный регион (это значение запоминается и восстанавливается при следующем запуске программы). При первой загрузке – это первый регион. Возможно выбрать следующие способы загрузки информации: выборка прямым запросом, сформированном на клиенте или выборка из представления; выборка всех данных одним запросом или с использованием метода, при котором на каждый узел вызывает отдельный запрос для построения дочерних уровней. Переключение осуществляется кнопками на панели. При загрузке данных запросами на каждый узел, в панели дерева выводятся характеристики работы, показывающие общее число запросов (числитель) и число пустых запросов (знаменатель). Под пустыми запросами я понимаю те запросы, которые не возвращают дочерние записи для узла. По логике пустые запросы могут иметь место только для адресных объектов на уровне 3 (на первом и втором уровнях я такого не встречал, на четвёртом запросы просто не выполняются, так как это уже верхний уровень отображения дерева). То есть это некий КПД использования обращений к серверу. Это я пытался понять, стОит ли создавать флаг наличия дочерних объектов для адресной таблицы. В принципе получается неплохой КПД, но вот для Красноярского края процент пустых запросов составил порядка 90. После обнаружения такого результата собственно и появился метод выборки одним запросом.
    • Возможно также посчитать время загрузки для всех регионов, оценив таким образом скорость работы всей базы. Кнопка легко находится на панели. Данные выводятся в гриде, где обычно находится список улиц.
    • Процесс поиска нужного адресного объекта осуществляется следующим образом. При нахождении курсора в окне дерева адресов или списка улиц просто набирайте на клавиатуре нужное название (набираемые буквы отображаются в соответствующем поле активного элемента). По мере набора список элементов сокращается в соответствии с набором. Если при вводе очередного символа строке фильтра не соответствует ни один элемент, то набранный символ к фильтру не добавляется и раздаётся звуковой сигнал, оповещающий о неправильном вводе. Если для линейного списка улиц это достаточно просто, то для древовидной структуры выполняются следующие дополнительные действия: дочерние уровни, не соответствующие фильтру скрываются; родительские уровни, не соответствующие фильтру переводятся в состояние недоступных. Понятно, что если в ветви нет подходящих элементов, то она скрывается полностью. Т.е. при этом сохраняется древовидная структура, но резко сокращается объём видимых данных. Это хорошо видно на прилагаемых скриншотах. При вводе фильтра можно пользоваться клавишами Backspace для стирания последнего набранного символа и Escape для сброса фильтра. Можно также включить режим фильтрации с первого символа (по умолчанию поиск ведётся по всему названию).
    • Поиск по всей базе осуществляется через отдельную форму (ищите кнопку на панели). Вводите требуемую подстроку и жмите "Поиск". Правда существует ограничение на длину итогового списка. Если она превышает некое число (зашито в серверной процедуре поиска, сейчас 100), то выдаётся ошибка с просьбой уточнить критерий поиска. Список из первых 100 элементов всё равно формируется. При выборе нужного найденного элемента, вы возвращаетесь к главному адресному окну. Способ отображения требуемого адресного элемента также можно задать.

Теперь несколько картинок, демонстрирующих, как всё выше описанное выглядит на практике. Для отображения выбрана родимая Свердловская область.


Рис.1. На данном рисунке изображено построенное дерево адресных объектов по Свердловской области. Красным обведены поля, в которых отображены временнЫе характеристики построения. Для примера выбран город с наибольшим числом улиц в данном регионе.


Рис.2. Здесь отображено состояние окна при работе фильтров. Дерево адресных объектов отфильтровано по строке "таги", а список улиц – по строке "вост" (обведены соответствующие поля). Скорость работы проверьте сами.


Рис. 3. Демонстрирует прикладные возможности утилиты. Можно создать полный адрес и скопировать его в буфер обмена. Сейчас копируется только вариант для налоговой.


Рис.4. Демонстрирует возможности поиска по всей базе. Выделенные поля – искомая строка и время построения списка найденных объектов.

P.S. Что идёт в придачу к статье. Во-первых, конечно же скрипт для создания базы данных (Firebird, версия вроде не принципиальна). Во-вторых, собственно сама программа, демонстрирующая вышесказанное (в качестве клиентской библиотеки на всякий случай используется gds32.dll). В-третьих – исходники. К исходникам следующие комментарии (опять начинаю загибать пальцы).

  • Во-первых, пример написан, а точнее собран из кусков различных программ, с использованием приличного числа сторонних компонентов, которые я использую давно и часто, но прикладывать в комплект не собираюсь. Это VirtualTreeView (основа, рекомендую иметь всем), kbmMemTable, EhLib , FibPlus, TDbf. Большинство из них бесплатные. EhLib пользую версии 3.6 – последний бесплатный релиз. FibPlus пробовал применять тоже последний бесплатный (4.7 помнится) – нормально работает, но пример собран на 6.5. Проект от Delphi 7.
  • Во-вторых опять же из-за того, что программа собрана из кусков, в ней присутствует в некотором количестве посторонний код, который как удалять, так и объяснять нет ни времени, ни желания.
  • В-третьих, целью статью было сосватать идею. Поэтому сперва хотел выложить только ключевые процедуры, но потом решил, что особо прятать нечего. А так может что и с самим КЛАДРом продвинется, хотя бы в смысле его распространения и использования. По поводу заимствования кода в своих задачах: традиционно, для некоммерческих проектов – без ограничений. Для прочих вариантов есть мыло.

P.P.S. Из всего выше сказанного и показанного, мне самому больше всего нравится некая общая идея поиска по древовидной структуре с сокрытием ненужных узлов и ветвей. На счёт новизны – не скажу: в чужих проектах не встречал, от других не слышал, честное слово – сам додумался. Сейчас пользую часто, и помногу: данные в виде древовидной структуры – дело обычное. Так многие справочники под эту методу хорошо выстраиваются. Однако же есть единственный недостаток: структура дерева должна быть полностью построена на момент поиска, что несколько противоречит стандартной идее динамической подгрузки данных из базы по мере необходимости. Если данные и задача позволяют, грузите их смело. И тогда всё у вас будет хорошо.

Copyright© 2007 Андрей Дмитриев Специально для Delphi Plus


Пожалуйста, оцените статью

Отлично
Хорошо
Средне
Плохо
Очень плохо



Новости за месяц

  • Сентябрь
    2019
  • Пн
  • Вт
  • Ср
  • Чт
  • Пт
  • Сб
  • Вс