Иерархические модели данных. Деревья в СУБД Cache
3.2.6 Строки с разделителями
Элементы строк с разделителями называют полями.
Символ-разделитель нельзя употреблять в самих полях. Одной строкой можно записать, например, список фамилий работников отдела "Ива-нов^Петров^Сидоров". В качестве разделителя можно использовать любой печатаемый символ, в том числе и пробел. Естественно, в этом случае поле не должно содержать пробел. Поля также могут быть строками с разделителями, но разделитель внутри поля должен быть другим. Можно и эти поля представить в виде переменных с разделителями и т.д.
Похоже, что многоуровневое вложение полей не очень удобно. Тем не менее, строки с разделителями могут существенно расширить возможности базы данных. В качестве мысленного упражнения полезно представить этот раздел без рисунков в виде многоуровневой строки с разделителями. Какие разделители выберете?
Очевидно, для строк с разделителями должны поддерживаться следующие операции:
- определение количества полей в строке;
- выделение поля;
- вставка значения в нужное поле, вставка дополнительных полей.
В Cache ObjectScript для решения этих задач используются функции $LENGTH, $FIND, $EXTRACT, $PIECE.
Итак, выбираем в качестве разделителя печатаемый символ, который не может использоваться в тексте полей, скажем: """, "+" и т. д. Строка с разделителями создаётся обычным присваиванием (команда "SET"). Создадим две строки str1="A~77~BD" и str2="A+B~A/B~A B" (листинг 3.23),
определим их длины (не в символах, а в полях) и выделим поля со второго по третье в строке str1.
USER>S str1="A^77^BD" USER>S str2="A+B^A/B^A B" USER>W "Длина str1=",$L(str1,"^") Длина str1=3 USER>W "Длина str2=",$L(str2,"^") Длина str2=3 USER>W $P(str1,"^",2,3) 77^BDПример 3.23. Строки с разделителями
Количество полей определяется уже известной функцией $L[ENGTH] , в которой во втором аргументе записывается знак разделителя. Выделяется нужное поле функцией $PIECE, имеющей формат:
Например, функция $P(str,"^",1) выделяет первое поле, а $P(str, "^",1,3) — поля с первого по третье. Функция $PIECE относится к так называемым левым функциям. Это означает, что она может стоять в левой части команды присваивания. Вставим текст "QQ" на место второго поля, добавим четвёртое поле и седьмое поля (листинг 3.24).
USER>S str1="A^77^BD" USER>S $P(str1,"^",2)="QQ" w str1 A^QQ^BD USER>s $P(str1,"^",4)="PP" w str1 A^QQ^BD^PP USER>s $P(str1,"^",7)="PP" w str1 A^QQ^BD^PP^^^PPПример 3.24. Левая функция $PIECE
Заметим, что вставка в седьмое поле привела к созданию пустых пятого и шестого полей "A^QQ^BD^PP^^^PP", что и вызвало появление в записи строки двух дополнительных циркумплексов.
3.2.7 Списки
Одна из основных структур данных — список — создаётся специальной функцией $LISTBUILD (сокращенно $LB). Её формат:
Создадим список из двух элементов и с помощью команды ZZDUMP разберёмся с его структурой (листинг 3.25).
USER>S x=$LB("Red","Blue") USER>ZZDUMP x 0000: 05 01 52 65 64 06 01 42 6C 75 65Пример 3.25. Структура списка
В левой части строки коды символов, а в правой отпечатанная строка. Точками обозначены непечатаемые символы. Оказывается, элементы списка разделяются парами непечатаемых символов. Первый символ такой пары — это число в шестнадцатиричной системе счисления, равное количеству символов в следующем за разделителем элементе списка плюс 2. Вторым идёт символ с кодом 01. Разделители в конце списка не ставятся. Зададим список из одного пустого элемента и список из единственного элемента, представляющего пустую строку (листинг 3.26). Пустой список эквивалентен пустой строке
USER>S y=$LB(), z=$LB("") ZZDUMP x,y 0000: 01 0000: 02 01Пример 3.26. Пустой элемент и пустая строка —не одно и то же
При вставке в список значения не определённой переменной ошибка не появляется, а вставляется NULL (листинг 3.27).
USER>K a S list1=$LB(1,a,3) USER>F i=1:1:3 W "i=",i,?10,$LISTGET(list1,i),! i=1 1 i=2 i=3 3Пример 3.27. Вставка значения не определённой переменной
Двухаргументная функция $LISTGET была использована для извлечения из списка указанного первым аргументом элемента с номером, указанным вторым аргументом.
Результат конкатенации двух списков есть список. Для работы со списками кроме их создания необходимо определять правильность списков (функция $LISTVALID) и их длину (функция $LISTLENGTH), сравнивать списки ($LISTSAME). Навигация по спискам производится с использованием функций $LISTDATA, $LISTFIND, $LISTGET, $LISTNEXT. Предусмотрены преобразования строк с разделителями в списки (функция $LISTFROMSTRING) и обратное преобразование ($LISTTOSTRING).
Функция $LISTVALID
Булева функция $LISTVALID проверяет правильность списка. Для правильных списков возвращает 1, а для неправильных 0. Формат:
или
Проверяемый список должен быть создан с помощью функций $LIST-BUILD и $LISTFROMSTRING, либо извлечён из существующего списка с помощью $LIST. В листинге 3.28 приведено несколько примеров, достаточных, чтобы разобраться с деталями.
USER>S r="A",s=33,t="",u=$LB("A","B"),v=$LB(1) USER>W $LV(r),?10,$LV(s),?20,$LV(t) 0 0 1 USER>W $LV(u),?40,$LV(v) 1 1 USER>S y=$LB(NULL) w $LV(y) 1Пример 3.28. Функция $LISTVALID
Вы видите, что пустой элемент t есть правильный список.
Функция $LISTLENGTH
Возвращает длину списка. Неопределённые элементы засчитываются. Вложенные списки учитываются как один элемент.
или
Функция $LISTSAME
Проверяет совпадение списков. Формат:
или
Примеры употребления приведены в листинге 3.29.
USER>s x=$LB("A","B"), y=$LB("B","A"), z=$LB("A","B"), v=$LB(), w=$LB("") USER>w "$LS(x,y)="_$LS(x,y),!,"$LS(x,z)="_$LS(x,z),!,"$LS(v,w)="_$LS(v,w),! $LS(x,y)=0 $LS(x,z)=1 $LS(v,w)=0Пример 3.29. Функция $LISTSAME
Обратите внимание на то, что пустая строка (в примере в листинге 3.28) и список из одного пустого элемента (x) оба допустимые списки, но они не равны между собой.
Функция $LISTDATA
Формат:
или
Функция $LISTDATA проверяет указанный элемент списка и возвращает единицу, если элемент в такой позиции существует и имеет значение. Если нет такой позиции или значение элемента не определено, то вернётся ноль (листинг 3.30)
USER>k s x=$LB("Один",,y,"","Пять") USER>s pos="Позиция " USER>f i=0:1:6 w pos,i,?15,"$LD(x,",i,")=",$LD(x,i),! Позиция 0 $LD(x,0)=0 Позиция 1 $LD(x,1)=1 Позиция 2 $LD(x,2)=0 Позиция 3 $LD(x,3)=0 Позиция 4 $LD(x,4)=1 Позиция 5 $LD(x,5)=1 Позиция 6 $LD(x,6)=0Пример 3.30. Функция $LISTDATA
В $LISTDATA, как в остальных функциях для работы со списками, "список" —это выражение, дающее имя существующего списка. Если такого списка нет, возникает ошибка UNDEFINED.
Функция $LISTFIND
Функция $LISTFIND это наша старая знакомая $FIND приспособленная для работы со списками. Она ищет первое вхождение указанного значения в качестве элемента списка. Совпадение должно быть точным. Формат:
или
Если элемент найден, возвращается его номер, а если не найден, вернётся 0 (листинг 3.31).
USER>S x=$LB("A","BB","B","C") USER>W "$LF(x,""A"")=",$LF(x,"A") $LF(x,"A")=1 USER>W "$LF(x,""B"")=",$LF(x,"B") $LF(x,"B")=3 USER>W "$LF(x,""BB"")=",$LF(x,"BB") $LF(x,"BB")=2 USER>W "$LF(x,""D"")=",$LF(x,"D") $LF(x,"D")=0Пример 3.31. Функция $LISTFIND
Прикрепление пустого элемента, как к голове, так и к хвосту списка, не изменяет список (листинг 3.32).
USER>ZZDUMP x 0000: 03 01 41 04 01 42 42 03 01 42 03 01 43 ..A..BB..B..C USER>ZZDUMP ""_x 0000: 03 01 41 04 01 42 42 03 01 42 03 01 43 ..A..BB..B..C USER>ZZDUMP x_"" 0000: 03 01 41 04 01 42 42 03 01 42 03 01 43 ..A..BB..B..CПример 3.32. Подключение пустого элемента в голову или хвост не меняет список
Необходимо помнить, что сравнение выполняется после приведения данных к так называемой канонической форме. Для списка созданного командой
S y=$LB("1.0","+2","003","2*2")
все следующие функции возвращают 0 потому, что числа не приводятся к текстовым константам: $LF(y,1.0), $LF(y,+2), $LF(y,003), $LF(y,4). А для списка созданного командой S y=$LB(7,6) обе следующие функции возвращают 1 потому, что вторые аргументы этих функций согласуются с элементами списка: $LF(y,7.000), $LF(y, 006).
Функция $LISTGET
Возвращает элемент списка в указанной позиции или значение по умолчанию, если этот элемент не определён. Формат:
или
В листинге 3.33 показан пример использования функции $LISTGET и сравнение её с функцией $LIST, которая выдаёт ошибку при появлении неопределённого элемента.
USER>S y=$LB("A",,"B") USER>F i=1:1:3 W "Элемент ",i,?12,$LG(y,i,"null"),! Элемент 1 A Элемент 2 null Элемент 3 B USER>F i=1:1:3 W "Элемент ",i,?12,$LI(y,i),! Элемент 1 A Элемент 2 F i=1:1:3 W "Элемент ",i,?12,$LI(y,i),! ^ <NULL VALUE> USER>Пример 3.33. Сравнение функций $LISTGET и $LIST
Функция $LISTNEXT
Извлекает элементы списка последовательно. Работает очень быстро. Формат:
Локальная переменная "указатель" перед первым вызовом функции должна быть установлена в 0. Тогда список будет анализироваться с начала.
Локальная переменная "значение" не может быть массивом. Инициализировать её не следует. Функция будет помещать в неё значения элементов списка (листинг 3.34).
USER>S y=$LB("A","B","C"), p=0 USER>F i=1:1:10 W "i=",i,?6,"Ф=",$LISTNEXT(y,p,v),?10,"p=",p,?20,"v=",v,! i=1 Ф=1 p=589827 v=A i=2 Ф=1 p=589830 v=B i=3 Ф=1 p=589833 v=C i=4 Ф=0 p=0 v=C i=5 Ф=1 p=589827 v=A i=6 Ф=1 p=589830 v=B i=7 Ф=1 p=589833 v=C i=8 Ф=0 p=0 v=C i=9 Ф=1 p=589827 v=A i=10 Ф=1 p=589830 v=BПример 3.34. Функция $LISTNEXT
Когда $LISTNEXT достигнет конца списка, она вернёт 0, переустановит указатель в 0 и оставит предыдущее извлечённое "значение". Продолжится анализ списка с начала. Элементы вложенных списков не выделяются. Удобно использовать функцию с циклом WHILE. Проверьте командную строку: S p=0 WHILE $LISTNEXT(y,p,v) {W !,v}.
Функция $LISTFROMSTRING
Создаёт список из строки с разделителями. Формат:
или
Пример приведен в листинге 3.35.
USER>S string="A^B^C", list=$LFS(string,"^") USER>ZZDUMP list,string 0000: 03 01 41 03 01 42 03 01 43 ..A..B..C 0000: 41 5E 42 5E 43 A^B^C USER>Пример 3.35. Преобразование строки в список
Функция $LISTTOSTRING
Преобразует список в строку с разделителями. Если разделитель не указан, выбирается запятая. Отсутствие флага или нулевое его значение определяет появление ошибки <NULL VALUE>. Формат:
или
Обратите внимание на то, что функция $LTS не проверяет наличие в исходном списке разделителя, который будет использован в преобразовании. Поэтому в последнем примере в листинге 3.36 ошибочно получена строка из трех элементов с разделителем "пробел".
USER>S x=$LB("A","B"), y=$LTS(x,"разделитель") USER>ZZDUMP y 0000: 0041 0440 0430 0437 0434 0435 043B 0438 Aраздели 0008: 0442 0435 043B 044C 0042 тельB USER>S z=$LB("A B","C") USER>S y=$LTS(z," ") USER>ZZDUMP y 0000: 41 20 42 20 43 A B C USER>Пример 3.36. Функция $LISTTOSTRING