БД. Справочники. Примеры на MUMPS (Caché Object Script) 2

в 6:55, , рубрики: MUMPS, nosql, бд, Веб-разработка, справочники, структуры данных, субд Caché, метки: , , , ,

В прошлой статье мы рассмотрели пример справочника на MUMPS (Caché Object Script). Были разобраны структуры глобалов и метод retrieve. Мы научились простейшей операции — получению имени элемента по известному идентификатору. Рассматриваемые структуры были одноуровневыми. Опросы и комментарии, после статьи, показали, что тема в целом интересна. Сегодня рассмотрим примеры построения индексов для справочников. Все коды/идентификаторы/имена глобалов — настоящие. Основная идея данных статей — обмен знаниями/опытом разработки и проектирования живых баз данных.

Вкратце напомню основные моменты первой части:

  • cправочник это медленно меняющаяся информация;
  • retrieve — быстрая операция;
  • название элемента справочника меняется в одном месте;
  • Глобал имеет вид: ^ГлобальнаяПеременная(«индекс1»,«индекс2»,...,«индексN»)=«значение»

По просьбе 4dmonster в примерах будут публиковаться полные версии команд. (wrtie вместо w и т.д.)

Освежим в памяти имеющиеся глобалы с данными:

^Dictionary("Vehicle","TransmissionType",1,0,"UpdateTime")="62086,66625"
^Dictionary("Vehicle","TransmissionType",1,0,"uid")=888
^Dictionary("Vehicle","TransmissionType",2,0,"UpdateTime")="62086,66625"
^Dictionary("Vehicle","TransmissionType",2,0,"uid")=888

^NameDictionaryElement(1,"partUri",0)="akp"
^NameDictionaryElement(1,"partUri",0,"UpdateTime")="62086,66625"
^NameDictionaryElement(1,"ru",0)="АКП"
^NameDictionaryElement(1,"ru",0,"UpdateTime")="62086,66625"
^NameDictionaryElement(2,"partUri",0)="meh"
^NameDictionaryElement(2,"partUri",0,"UpdateTime")="62086,66625"
^NameDictionaryElement(2,"ru",0)="МЕХ"
^NameDictionaryElement(2,"ru",0,"UpdateTime")="62086,66625"

Глобал ^Dictionary — содержит все элементы справочников и их свойства, глобал ^NameDictionaryElement — содержит названия элементов справочников на всех языках.

Создать глобалы Ctr+С/V

Команда set — задаёт значение переменной (локальной или глобальной).

set ^Dictionary("Vehicle","TransmissionType",1,0,"UpdateTime")="62086,66625"
set ^Dictionary("Vehicle","TransmissionType",1,0,"uid")=888
set ^Dictionary("Vehicle","TransmissionType",2,0,"UpdateTime")="62086,66625"
set ^Dictionary("Vehicle","TransmissionType",2,0,"uid")=888
set ^NameDictionaryElement(1,"partUri",0)="akp"
set ^NameDictionaryElement(1,"partUri",0,"UpdateTime")="62086,66625"
set ^NameDictionaryElement(1,"ru",0)="АКП"
set ^NameDictionaryElement(1,"ru",0,"UpdateTime")="62086,66625"
set ^NameDictionaryElement(2,"partUri",0)="meh"
set ^NameDictionaryElement(2,"partUri",0,"UpdateTime")="62086,66625"
set ^NameDictionaryElement(2,"ru",0)="МЕХ"
set ^NameDictionaryElement(2,"ru",0,"UpdateTime")="62086,66625"

А теперь посмотрим как может быть устроен индекс справочника, и разберёмся для чего он нужен.

Определение:

Индекс — число, буквы или другая комбинация символов, указывающая место элемента, или группы элементов, в совокупности. Элемент может в себе содержать другие элементы. Тоже самое касается и индекса — индекс всегда составной, даже если состоит из одной или менее частей.

Разберём на составные части индекс глобала в Caché

Помните разбор слова по составу на уроках русского языка? Там где ищется приставка, корень, суфикс, окончание. Здесь что-то очень похожее. Для начала нам потребуется слово (глобальная/локальная переменная). Пусть нашим словом будет ^|«MONTOLOGY»|NameDictionaryElement(1,«ru»,0). Между вертикальными палочками | указано пространство имён | — это необходимо для максимальной полноты демонстрации состава индекса глобала(переменной). Вспомним значение нашего слова — выполним комманду write:

MONTOLOGY>write ^|"MONTOLOGY"|NameDictionaryElement(1,"ru",0)
АКП
MONTOLOGY>

Наше слово ^|«MONTOLOGY»|NameDictionaryElement(1,«ru»,0) означает АКП (автоматическую коробку передач).

Кроме слова, для разбора нам потребуется системная функция $name(), возвращающая имя переменной в каноническом виде. Команда set — задаёт значение переменной (локальной или глобальной). Создадим локальную переменную slovo и продемонстрируем работу функции $name(). В одной строке выполним две команды set и write (команды разделяются как минимум одним пробелом):

MONTOLOGY>set slovo=$name(^|"MONTOLOGY"|NameDictionaryElement(1,"ru",0)) write slovo
^|"MONTOLOGY"|NameDictionaryElement(1,"ru",0)
MONTOLOGY>

Если же теперь, имея только переменную slovo, нам понадобится вновь прочитать (или записать) значение нашего слова — мы можем воспользоваться оператором косвенности @

Пример:

MONTOLOGY>write @slovo
АКП
MONTOLOGY>

Итак, в переменной slovo содержится имя нашего слова ^|«MONTOLOGY»|NameDictionaryElement(1,«ru»,0) в каноническом виде. Приступим к его разбору. Воспользуемся функцией $qsubscript() Используя цикл for можно сократить запись и объяснение. Команда write и цикл for написаны в одной строке, для того, чтобы выполнить эту команду из терминала:

MONTOLOGY>write "Слово: "_slovo,! for i=-1:1:3 { write " значение "_i_"-го индекса: "_$qsubscript(slovo,i),! }
Слово: ^|"MONTOLOGY"|NameDictionaryElement(1,"ru",0)
 значение -1-го индекса: MONTOLOGY
 значение 0-го индекса: ^NameDictionaryElement
 значение 1-го индекса: 1
 значение 2-го индекса: ru
 значение 3-го индекса: 0

MONTOLOGY>

Нижнее подчёркивание "_" — это символ конкатенации (склейки строк). Восклицательный знак "!" внутри комманды write — переход на новую строку. Если быть совсем точным, то граничное значение окончания цикла: число 3 — необходимо заменить на функцию $qlength(slovo). Она возвращает максимальный номер индекса переменной. Однако для простоты я написал просто 3.

Итак мы видим:

  • -1й индекс — это название пространства имён;
  • индекс — это, собственно, имя глобала (до скобок);
  • 1й,2й,3й (и далее) — это индексы глобала в скобках.

То есть переменная (локальная или глобальная) в себе обязательно содержит несколько индексов. Для того, что бы получить значение переменной — нам необходимо знать значения всех её составных индексов.

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

  • Любая переменная в Caché (MUMPS) локальная или глобальная — всегда индексированная.
  • Смысл вкладываемый в i-й индекс переменной (индекс переменной на i-м уровне) — определяется разработчиком.
  • Значение i-го индекса переменной = значение i-го слова в скобках после имени переменной = $qsubscript(переменная,i)
  • Индекс справочника (или индексный глобал) — это спроектированный и наполняемый специальным образом глобал, предназначенный сделать поиск элементов справочников удобным. Никакой дополнительной встроенной или системной «индексности», кроме смысла, вкладываемого в него разработчиком, он не содержит.

Индексы справочников

Индексы необходимы для быстрого поиска информации. В зависимости от типа поиска могут требоваться разные индексы. Рассмотрим пример индекса для простейшего поиска по значению.

Примем что индексы глобала ^IndexDictionary будут означать следующее:

  1. онтология (грубая классификация справочников) — наша онтология Vehicle (транспортные средства)
  2. название справочника — TransmissionType (тип трансмиссии)
  3. название свойства элемента
  4. значение свойства элемента (или название языка, если предыдущий индекс = «name»)
  5. идентификатор элемента справочника (или название элемента в нижнем регистре, если предыдущий индекс — название языка)
  6. идентификатор элемента справочника (только в том случае, если на прошлом уровне было название элемента, для всех остальных свойств, кроме «name», этот индекс отсутствует)

Распечатаем содержимое глобала ^IndexDictionary выполним комманду:
zwrite ^IndexDictionary(«Vehicle»,«TransmissionType»)
Напоминаю что «MONTOLOGY» — это название пространства имён.

MONTOLOGY>zwrite ^IndexDictionary("Vehicle","TransmissionType")
^IndexDictionary("Vehicle","TransmissionType","name","partUri","akp",1)=1
^IndexDictionary("Vehicle","TransmissionType","name","partUri","meh",2)=1
^IndexDictionary("Vehicle","TransmissionType","name","ru","акп",1)=1
^IndexDictionary("Vehicle","TransmissionType","name","ru","мех",2)=1
^IndexDictionary("Vehicle","TransmissionType","uid",888,1)=1
^IndexDictionary("Vehicle","TransmissionType","uid",888,2)=1
MONTOLOGY>

Создать глобал Ctr+С/V

set ^IndexDictionary("Vehicle","TransmissionType","name","partUri","akp",1)=1
set ^IndexDictionary("Vehicle","TransmissionType","name","partUri","meh",2)=1
set ^IndexDictionary("Vehicle","TransmissionType","name","ru","акп",1)=1
set ^IndexDictionary("Vehicle","TransmissionType","name","ru","мех",2)=1
set ^IndexDictionary("Vehicle","TransmissionType","uid",888,1)=1
set ^IndexDictionary("Vehicle","TransmissionType","uid",888,2)=1

Давайте внимательно посмотрим на структуру индексного глобала. Значения индексированных переменных, не несут никакой смысловой нагрузки, поэтому, для простоты, они везде равны единице. Вся интересующая нас информация — содержится исключительно в индексах. Заметим, что первый и второй индекс глобала ^IndexDictionary, совпадает с первым и вторым индексом глобала ^Dictionary. Значения свойств элементов идут только на 4м или 5м (для имён) уровне. Так как наш глобал должен обеспечивать быстрый поиск по значению, сама его структура подразумевает определённые условия поиска. Все предварительные индексы известны изначально. То есть, перед тем, как что-то начать искать, мы уже знаем: онтологию, тип элемента справочника, имя свойства и его значение. Очевидно, такой индексный глобал не подойдёт для анализа полнотекстовых запросов пользователей. Но в данном случае — это не требуется. Попробуем в этом глобале найти элемент(ы) справочника по значению свойства. Нам понадобится функция $order() возвращающая следующий индекс переменной на заданном уровне. Если следующего индекса нет — функция вернёт пустую строку "". Продемонстрируем работу функции $order():

MONTOLOGY>write $order(^IndexDictionary("Vehicle","TransmissionType",""))
name
MONTOLOGY>write $order(^IndexDictionary("Vehicle","TransmissionType","name"))
uid
MONTOLOGY>write $order(^IndexDictionary("Vehicle","TransmissionType","uid"))

MONTOLOGY>

То есть, следующий индекс на третьем уровне, после пустой строки "", это первое встречающееся значение индекса третьего уровня — «name». Следующий индекс на третьем уровне после «name» это «uid». Следующего индекса на третьем уровне после «uid» нет, распечаталась пустая строка. Для того чтобы найти первый идентификатор (возможно имя не уникально) элемента справочника TransmissionType с русским названием «мех» достаточно выполнить следующую команду:

MONTOLOGY>write $order(^IndexDictionary("Vehicle","TransmissionType","name","ru","мех",""))
2
MONTOLOGY>

retrieveListByIndex

Напишем подпрограмму вывода списка элементов справочника по значению любого свойства (кроме имени).

#; --------------------------------------------------------------------------------------------------
#; Имя программы
#; --------------------------------------------------------------------------------------------------
Dictionary
#; --------------------------------------------------------------------------------------------------
#; Получить список элементов справочника по индексу.
#; --------------------------------------------------------------------------------------------------
retrieveListByIndex(ontology,type,index,value) 
	set id="" 
	for {
		set id=$o(^IndexDictionary(ontology,type,index,value,id))
		quit:id=""
		write id,!
	}
	quit
#; --------------------------------------------------------------------------------------------------

Обратите внимание что for {} — бесконечный цикл. Условие выхода описывается внутри него конструкцией quit:id=""
Как только id станет пустой строкой (а это случится тогда, когда следующего индекса на пятом уровне не будет) — подпрограмма выйдет из цикла. Найдём все элементы спровочника TransmissionType добавленные/отредактированные пользователем 888 (uid=888):

MONTOLOGY>do retrieveListByIndex^Dictionary("Vehicle","TransmissionType","uid",888)
1
2

MONTOLOGY>

Модифицируем нашу подпрограмму, для того что бы она дополнительно могла искать элементы, названия которых начинаются с определённой подстроки. Расширим формат вывода (добавим название элемента). Приведу код программы Dictionary вместе с продпрограммой retrieve из прошлой части:

Код Dictionary:

#; --------------------------------------------------------------------------------------------------
#; Имя программы
#; --------------------------------------------------------------------------------------------------
Dictionary
#; --------------------------------------------------------------------------------------------------
#; Получить элемент справочника.
#; --------------------------------------------------------------------------------------------------
retrieve(id,lang="ru",version=0)
	quit $get(^NameDictionaryElement(id,lang,version),"")
#; --------------------------------------------------------------------------------------------------
#; Получить список элементов справочника по индексу.
#; --------------------------------------------------------------------------------------------------
retrieveListByIndex(ontology,type,index,value,str="",lang="ru") 

	#;принудительно переводим в нижний регистр строку, с которой должны начинатся названия элементов
	set str=$zconvert(str,"L")

	set id="" 
	for {
		#;получаем следующий идентификатор(индекс) на текущем уровне
		set id=$order(^IndexDictionary(ontology,type,index,value,id))

		#;выходим из цикла, если на текущем уровне данных больше нет
		quit:id=""

		#;получаем название элемента
		set name=$$retrieve(id,lang)

		#;проверяем начинается ли название элемента строкой str
		if $extract($zconvert(name,"L"),1,$length(str))=str {
			#;выводим результат (идентификатор и название)
			write id_" "_name,!
		}
	}
	quit
#; --------------------------------------------------------------------------------------------------

Поясню новые используемые функции и конструкции:

  • $zconvert() — форматирует строку в зависимости от передаваемого параметра (L — означает перевести в нижний регистр);
  • $length() — возвращает длинну строки;
  • $extract() — выделяет из строки подстроку с определённой начальной и конечной позицией. В нашем случае начиная с первого символа и заканчивая длинной str на входе подпрограммы.
  • set name=$$retrieve(id,lang) — вызывая подпрограмму из текущей программы имя программы можно не указывать, хотя конструкция set name=$$retrieve^Dictionary(id,lang) — вернёт такой же результат.

Попробуем посмотреть на результат работы нашей программы:

MONTOLOGY>do retrieveListByIndex^Dictionary("Vehicle","TransmissionType","uid",888)
1 АКП
2 МЕХ

MONTOLOGY>do retrieveListByIndex^Dictionary("Vehicle","TransmissionType","uid",888,"A")

MONTOLOGY>do retrieveListByIndex^Dictionary("Vehicle","TransmissionType","uid",888,"М")
2 МЕХ

MONTOLOGY>do retrieveListByIndex^Dictionary("Vehicle","TransmissionType","uid",888,"А")
1 АКП

MONTOLOGY>do retrieveListByIndex^Dictionary("Vehicle","TransmissionType","uid",888,"Ме")
2 МЕХ

MONTOLOGY>do retrieveListByIndex^Dictionary("Vehicle","TransmissionType","uid",888,"Ме","partUri")

MONTOLOGY>do retrieveListByIndex^Dictionary("Vehicle","TransmissionType","uid",888,"m","partUri")
2 meh

MONTOLOGY>do retrieveListByIndex^Dictionary("Vehicle","TransmissionType","uid",888,"","partUri")
1 akp
2 meh

MONTOLOGY>

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

Итак, в нашей справочной подсистеме, имеется уже три важных значимых глобала:

  • ^Dictionary — элементы справочников и их свойства;
  • ^NameDictionaryElement — названия элементов справочников на различных языках;
  • ^IndexDictionary — основной индекс справочников.

Очевидно, что для эффективного обновления/удаления элементов справочника — потребуется ещё один глобал — хранящий обратные ссылки. Это необходимо для того, чтобы не выполнять никакого поиска, при удалении данных (при обновлении же будут выполнятся минимально-необходимые проверки). Назовём этот глобал ^RefsDictionary. Распечатаем все обратные ссылки для элементов 1 и 2:

MONTOLOGY>zwrite ^RefsDictionary(1),^RefsDictionary(2)
^RefsDictionary(1,"^|""MONTOLOGY""|IndexDictionary(""Vehicle"",""TransmissionType"",""name"",""partUri"",""akp"",1)")=1
^RefsDictionary(1,"^|""MONTOLOGY""|IndexDictionary(""Vehicle"",""TransmissionType"",""name"",""ru"",""акп"",1)")=1
^RefsDictionary(1,"^|""MONTOLOGY""|IndexDictionary(""Vehicle"",""TransmissionType"",""uid"",888,1)")=1
^RefsDictionary(2,"^|""MONTOLOGY""|IndexDictionary(""Vehicle"",""TransmissionType"",""name"",""partUri"",""meh"",2)")=1
^RefsDictionary(2,"^|""MONTOLOGY""|IndexDictionary(""Vehicle"",""TransmissionType"",""name"",""ru"",""мех"",2)")=1
^RefsDictionary(2,"^|""MONTOLOGY""|IndexDictionary(""Vehicle"",""TransmissionType"",""uid"",888,2)")=1

MONTOLOGY>

Создать глобал Ctr+С/V

set ^RefsDictionary(1,"^|""MONTOLOGY""|IndexDictionary(""Vehicle"",""TransmissionType"",""name"",""partUri"",""akp"",1)")=1
set ^RefsDictionary(1,"^|""MONTOLOGY""|IndexDictionary(""Vehicle"",""TransmissionType"",""name"",""ru"",""акп"",1)")=1
set ^RefsDictionary(1,"^|""MONTOLOGY""|IndexDictionary(""Vehicle"",""TransmissionType"",""uid"",888,1)")=1
set ^RefsDictionary(2,"^|""MONTOLOGY""|IndexDictionary(""Vehicle"",""TransmissionType"",""name"",""partUri"",""meh"",2)")=1
set ^RefsDictionary(2,"^|""MONTOLOGY""|IndexDictionary(""Vehicle"",""TransmissionType"",""name"",""ru"",""мех"",2)")=1
set ^RefsDictionary(2,"^|""MONTOLOGY""|IndexDictionary(""Vehicle"",""TransmissionType"",""uid"",888,2)")=1

Думаю некоторые из вас, дорогие читатели, уже догадались, зачем нужен ^RefsDictionary и почему он имеет такую структуру. Более детально об этом в следующей статье.

Спасибо за внимание.

Буду рад вопросам, замечаниям и пожеланиям.

P.S.:

У меня практически нет опыта написания технических статей, поэтому «попытки на хабре показать что глобалы это круто» — не замерли, а просто движутся с маленькой скоростью. Возможно, на майские праздники, я смогу больше времени уделить этой теме.

Автор: multik

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js