понедельник, 23 мая 2016 г.

MUMPS: Межтабличный индекс

Межтабличным индексом называется индексная структура, объединяющая в себе данные не для одного, а для двух или более логических наборов данных. Записи в межтабличном индексе перестраиваются при добавлении, изменении и удалении записей в наборах записей, данные из которых входят в этот индекс. Основным назначением межтабличного индекса является сокращение операций соединения таблиц.

Рассмотрим структуру данных вида Отдел - Подразделение - Сотрудник. Та сущность что правее в этой цепочке входит в ту что левее. Пусть у каждой сущности есть два атрибута - идентификатор записи и название, или ФИО для сотрудника. Схема существенно упрощенная по отношению к реальной, но приведена исключительно в демонстрационных целях. Сущности Подразделение и Сотрудник имеют соответственно дополнительные атрибуты - ссылки на Отдел и Подразделение. Структурно схема данных может быть обозначена так:
Отдел
^Data("Otdel",id)=$lb(Name)
Подразделение
^Data("Podr",id)=$lb(Name,IDOtdel)
Сотрудник
^Data("Sotr",id)=$lb(Name,IDPodr)
Эта схема существенно нормализована, не содержит избыточной информации, и значения атрибутов зависят только от идентификаторов строк. По приведенным данным можно выполнить любые операции - добавления, удаления и изменения. Рассмотрим две тяжелых операции - вывести фамилии сотрудников для заданного отдела и вывести наименование отдела для заданного сотрудника. Обе задачи решаются пошаговым прохождением через промежуточные структуры подразделения. Межтабличные индексы могут решить задачу ускорения вывода. В нашем случае индексов будет два - по одному для каждой из задач.
 CreateRecords()
 k ^Data
 n id
 ; сделаем отделы
 f id=1:1:12 d InsertOtdel("Отдел "_id)
 ; сделаем подразделения
 f id=1:1:30 d InsertPodr("Подразделение "_id,$r(12)+1)
 ; сделаем сотрудников
 f id=1:1:300 d InsertSotr("Сотрудник "_id,$r(30)+1)
 q
InsertOtdel(Name)
 n id
 s id=$i(^Data("Otdel"))
 s ^Data("Otdel",id)=$lb(Name)
 q:$Q id  q
InsertPodr(Name,fk)
 n id
 s id=$i(^Data("Podr"))
 s ^Data("Podr",id)=$lb(Name,fk)
 q:$Q id  q
InsertSotr(Name,fk)
 n id
 s id=$i(^Data("Sotr"))
 s ^Data("Sotr",id)=$lb(Name,fk)
 q:$Q id  q
Здесь при создании записи сотрудника нам становится известным полный путь от сотрудника к отделу. На самом деле это очень серьезное соглашение - может ли быть запись которая ни на что не ссылается. В нашем примере пусть будет нельзя. Также пусть будет нельзя удалять те записи, на которые имеются ссылки. В нашем примере полный путь между сотрудником и отделом определяется, таким образом, в момент создания записи о сотруднике, при удалении записи о сотруднике, а также при изменениях внешних ссылок подразделения на отдел и сотрудника на подразделение.

Структура поддерживаемого индекса будет фактически так или иначе отражать путь в условном графе принадлежности объектов одного другому:
Для получения сотрудников по отделу 
^Data("ind1",otdelid,podrid,sotrid)="" 
Для получения отдела по сотруднику 
^Data("ind2",sotrid,podrid,otdelid)="" 
Пусть в нашей схеме данных поддерживается строгая дисциплина ссылочности: не может быть подразделения без отдела и сотрудника без подразделения. Исходя из этой упрощенной дисциплины значения в индексах меняются при
  • при создании сотрудника
  • при удалении сотрудника
  • при изменение ссылочного значения сотрудника на подразделение
  • при изменении ссылочного значения подразделения на отдел
Соответственно для этих событий вводим функции поддержания индекса:
 indAddSotr(idsotr)
 n idotdel,idpodr
 s idpodr=$li(^Data("Sotr",idsotr),2)
 s idotdel=$li(^Data("Podr",idpodr),2)
 s ^Data("ind1",idotdel,idpodr,idsotr)=""
 s ^Data("ind2",idsotr,idpodr,idotdel)=""
 q
indDelSotr(idsotr)
 n idotdel,idpodr
 s idpodr=$li(^Data("Sotr",idsotr),2)
 s idotdel=$li(^Data("Podr",idpodr),2)
 k ^Data("ind1",idotdel,idpodr,idsotr)
 k ^Data("ind2",idsotr,idpodr,idotdel)
 q
indChangeSotrFK(idsotr,newpodr)
 n oldpodr,oldotdel,newotdel
 s oldpodr=$li(^Data("Sotr",idsotr),2)
 s oldotdel=$li(^Data("Podr",oldpodr),2)
 s newotdel=$li(^Data("Podr",newpodr),2)
 k ^Data("ind1",oldotdel,oldpodr,idsotr)
 s ^Data("ind1",newotdel,newpodr,idsotr)=""
 k ^Data("ind2",idsotr,oldpodr,oldotdel)
 s ^Data("ind2",idsotr,newpodr,newotdel)=""
 q
indChangePodrFK(idpodr,newotdel)
 n oldotdel,idsotr
 s oldotdel=$li(^Data("Podr",idpodr),2)
 m ^Data("ind1",newotdel,idpodr)=^Data("ind1",oldotdel,idpodr)
 k ^Data("ind1",oldotdel,idpodr)
 s idsotr="" 
 f  s idsotr=$o(^Data("ind1",newotdel, »
        idpodr,idsotr)) q:idsotr=""  d
 . k ^Data("ind2",idsotr,idpodr,oldotdel)
 . s ^Data("ind2",idsotr,idpodr,newotdel)=""
 q
Приведенная схема отображения отдела на сотрудников (индекс ind1) использует составной индекс. Он неудобен для многоиндексной выборки, поскольку отображает соответственно отдел на неупорядоченный набор сотрудников. Для индекса ind2 отображение единственно. Для того, чтобы можно было применять многоиндексную выборку сотрудников лучше ввести дополнительный индекс, отображающий отдел на упорядоченный набор сотрудников:
^Data("ind3",otdelid,sotrid)="" 
 indAddSotr(idsotr)
 n idotdel,idpodr
 s idpodr=$li(^Data("Sotr",idsotr),2)
 s idotdel=$li(^Data("Podr",idpodr),2)
 s ^Data("ind1",idotdel,idpodr,idsotr)=""
 s ^Data("ind3",idotdel,idsotr)=""
 s ^Data("ind2",idsotr,idpodr,idotdel)=""
 q
indDelSotr(idsotr)
 n idotdel,idpodr
 s idpodr=$li(^Data("Sotr",idsotr),2)
 s idotdel=$li(^Data("Podr",idpodr),2)
 k ^Data("ind1",idotdel,idpodr,idsotr)
 k ^Data("ind3",idotdel,idsotr)
 k ^Data("ind2",idsotr,idpodr,idotdel)
 q
indChangeSotrFK(idsotr,newpodr)
 n oldpodr,oldotdel,newotdel
 s oldpodr=$li(^Data("Sotr",idsotr),2)
 s oldotdel=$li(^Data("Podr",oldpodr),2)
 s newotdel=$li(^Data("Podr",newpodr),2)
 k ^Data("ind1",oldotdel,oldpodr,idsotr)
 k ^Data("ind3",oldotdel,idsotr)
 s ^Data("ind1",newotdel,newpodr,idsotr)=""
 s ^Data("ind3",newotdel,idsotr)=""
 k ^Data("ind2",idsotr,oldpodr,oldotdel)
 s ^Data("ind2",idsotr,newpodr,newotdel)=""
 q
indChangePodrFK(idpodr,newotdel)
 n oldotdel,idsotr
 s oldotdel=$li(^Data("Podr",idpodr),2)
 m ^Data("ind1",newotdel,idpodr)=^Data("ind1",oldotdel,idpodr)
 k ^Data("ind1",oldotdel,idpodr)
 s idsotr="" 
 f  s idsotr=$o(^Data("ind1",newotdel, »
       idpodr,idsotr)) q:idsotr=""  d
 . k ^Data("ind2",idsotr,idpodr,oldotdel)
 . s ^Data("ind2",idsotr,idpodr,newotdel)=""
 . k ^Data("ind3",oldotdel,idsotr)
 . s ^Data("ind3",newotdel,idsotr)=""
 q
В случае применения межтабличных индексов следует внимательно пересмотреть политику отношения к ссылкам, а также степень востребованности запросов "через голову", требующих без межтабличных индексов долгих соединений. Возможно, что негативный фактор усложнения операции добавления / удаления и увеличение объема хранения может перевесить выгоды от эпизодического использования прямого индекса между таблицами. В случае использования в индексе идентификаторов записей операции обновления индексов по причине смены внешней ссылки в одной из промежуточных таблиц будут происходить нечасто, поэтому алгоритмическая нагрузка может быть совершенно незначительной.

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

Подробнее о книге "MUMPS СУБД"

Комментариев нет:

Отправить комментарий