Вы находитесь на странице: 1из 10

Задание №4

Хранение и обработка иерархических данных


с использованием системного типа данных HierarchyID

1.Модифицировать таблицы с иерархическими данными из задания №3


(добавить столбец типа HierarchyID) . Заполнить этот столбец данными,
как «вручную», так и с использованием стандартных функций для типа
HierarchyID.

2.Переписать запросы из задания №3, так чтобы использовались данные


из столбца типа HierarchyID.

3. Написать минимум по 3 новых запроса к иерархии для каждой из


таблиц, использую столбец типа HierarchyID .

Учебно-методический материал к выполнению задания

II.2. Материализованные пути. Специальный системный тип данных


HierarchyID в MS SQL Server

Разработчики MS SQL Server предлагают своё технологическое


решение для работы с иерархическими структурами данных на основе
метода материализованного пути.

Это решение включает специальный системный тип данных HierarchyID


вместе с большим набором системных функций(методов) для работы с
данными этого типа.

Разработчики Microsoft предлагают радикальное решение: для таблиц


с иерархическими данными использовать столбец типа HierarchyID не только
для хранения иерархии, но и одновременно в качестве первичного ключа (о
чём говорит и название).

Однако большинство специалистов пока относятся к этому варианту


осторожно и не используют столбцы типа HierarchyID как первичный ключ, а
только как столбец хранения иерархии. Рассмотрим некоторые примеры
такой работы.
а) хранение иерархических данных.

Системный тип HierarchyID основан на системном типе binary. То есть,


это битовая версия иерархического пути, максимальная длина поля 892
байта.

Пример:

Пусть имеется таблица Employee (Сотрудник), в таблице есть


специальный столбец, например, с именем HID типа HierarchyID, в котором
отражена подчиненность сотрудников друг другу.

Выполним запрос на выборку данных из этой таблицы, чтобы


посмотреть как хранятся данные типа HierarchyID:

select EmployeeID,
Name,
HID,
HID.ToSting()
from Employee;

Получим, например, такой результат:

EmployeeID Name HID HID.ToString()


1 Nancy 0x68 /2/ -- уровень 1
2 Andrew 0x / -- уровень 0
3 Janet 0x58 /1/ -- уровень 1
4 Margaret 0x84 /4/ -- уровень 1
5 Steven 0x78 /3/ -- уровень 1
6 Michael 0x6AC0 /3/2/ -- уровень 2
7 Robert 0x5AC0 /3/1/ -- уровень 2
8 Laura 0x8C /5/ -- уровень 1
9 Anne 0x78C0 /3/3/ -- уровень 2

Обратим внимание на следующее:

-значения столбца HID это битовые последовательности (выводятся в


шестнадцатеричном виде);

-4-й столбец в запросе - виртуальный, в нем использована одна из


стандартных функций работы с иерархией ToString(), которая
переводит значения типа HierrachyID в строку символов. Мы
использовали её, чтобы наглядно видеть материализованный путь (в
шестнадцатеричном виде читать неудобно);

-в иерархии задаются не только уровни в глубину, но и логическая


последовательность узлов внутри одного уровня, так сказать «по
горизонтали». Цифры в материализованном пути это не значения
первичного ключа (как в базовом варианте II.1.), а порядковые
номера узлов внутри одного уровня. Чем больше номер, тем «правее
по горизонтали» расположен узел. Уровень вложенности в глубину
определяется только количеством слэшей. Корень дерева считается
нулевым уровнем и всегда обозначается одним прямым слэшем / .

Получается, в таблице отражена следующая иерархия сотрудников:

Для удобства в запросах значения полей HierarchyID можно задавать в


текстовом виде. При этом производится неявное преобразование типов,
например:

select EmployeeID, Name


from Employee
where HID = ’/3/1/’
А что делать, если надо вставить узел между соседними узлами,
например, между Steven /3/ и Margaret /4/ ?

В Mirosoft придумали следующее.

HID нового сотрудника должен быть /3.1/, или /3.5/, или /3.25/, или
любой в таком роде.

А если надо будет вставить узел между /3.1/ и /3.2/? Тогда HID может
быть /3.1.1/ или /3.1.41/ .

Ну а если надо вставить узел «слева» от Janet /1/ ? Тогда адрес может
быть /0/ или даже /-1/ .

То есть, если вы увидите что-то вроде

/-12.3.0.-2/0.2.-54.0.723/45/17.1051.-34/745/-301/

, не удивляйтесь.

б) манипулирование и навигация с использованием данных типа HierarchyID

Пример: Создать таблицу с иерархией и отражением уровня


вложенности в глубину.

create table Employee


( EmployeeID bigint identity(1,1) primery key,
Name varchar(200) not null,
HID hierarchyID,
EmpLevel as HID.GetLevel( ) persisted
);

Обратим внимание на последний столбец. Это вычисляемый столбец,


его значения вычисляются на основе значений столбца HID. Здесь
используется одна из стандартных функций работы с HierarchyID,
функция(метод) GetLevel(). Она возвращает уровень вложенности узла в
глубину. Опция persisted превращает столбец из виртуального
(вычисляемые столбцы по-умолчанию - виртуальные) в реально хранимый в
таблице.
Как манипулировать данными в такой таблице? Есть два варианта:
вариант «вручную» и вариант с использованием стандартных функций для
работы с HierarchyID.
Рассмотрим вариант «вручную». Здесь используются обычные
инструкции insert into, update, delete.

Примеры:

--добавление нового сотрудника


insert into Employee ( Name , HID )
values ( ‘Rick’ , ‘/3/1.1/’ );

--сделать 2-го сотрудника директором, «корнем дерева»


update Employee
set HID = ’/’
where EmployeeID = 2;

--переместить сотрудника в иерархии


update Employee
set HID = ’/3/1/’
where EmployeeID = 7;

Теперь рассмотрим вариант работы с использованием специальных


стандартных функций работы с данными HierarchyID. При этом, с точки
зрения программирования, тип HierarchyID рассматривается как класс, а
функции для работы с ним, как методы этого класса. Перечислим основные
методы.

Функции(методы) для работы с HierarchyID:

GetAncestor( <level> ) -возвращает значение HierarchyID предка.


Принимает параметр, в котором указывается уровень предка, например, 1
выбирает непосредственного предка.
GetDescendant( <child1>,  <child2> ) -генерирует новый HierarchyID потомка,
принимает два параметра, с помощью которых можно управлять тем, куда
именно будет помещен потомок.

GetLevel( ) -возвращает уровень вложенности узла.

GetRoot( ) -(статический), генерирует корень иерархии.

IsDescendantOf( <parent> ) -проверяет, является ли узел потомком,


переданного через параметр родителя (на любом уровне).

Parse( <InputString> ) -(статический), конвертирует строковое


представление HierarchyID в собственно HierarchyID .

ToString( ) -конвертирует HierarchyID в строковое представление.

GetReparentedValue( <oldParetNode>,  <newParentNode> ) -позволяет
изменить текущего предка. Первый параметр это HierarchyID узла, который
необходимо переподчинить со всеми его потомками, второй – новый код
HierarchyID для этого узла.

Заполнение таблицы с помощью иерархических методов

Примеры:

1.Вставка корневого элемента.

insert into Employee (HID, Name, Position)


values (hierarchyID::GetRoot(), ‘Andrew’, ‘Director’);

или, если строка с сотрудником уже существует, но не заполнен столбец


HierarchyID:
update Employee
set HID = hierarchyID::GetRoot()
where EmployeeID = 2;

2.Вставка потомков с помощью функции GetDescendant( ) :

--первый потомок

declare @parent hierarchyID;


select @parent= hierarchyID::GetRoot( )
from Employee;

insert into Employee(HID, Name, Position)


values(@parent.GetDescendant(null, null), ‘Janet’, ‘Manager’);

или, если строка существует

update Employee
set HID = @parent.GetDescendant(null, null)
where EmployeeID = 3;

--вставка ещё 2-х потомков

declare @parent hierarchyID;


declare @child_1 hierarchyID,
@child_2 hierarchyID,
@child_3 hierarchyID;
select @parent = HID
from Employee
where ID = 2;
select @child_1 = HID
from Employee
where ID = 3;
--получить иерархический адрес справа от 1-го потомка
set @child_2 = @parent.GetDescendant(@child_1, null);

update Employee
set HID = @child_2
where EmployeeID = 1;

-- получить иерархический адрес между 1-м и 2-м потомками потомками


set @child_3 = @parent.GetDescendant(@child_1, @child_2);
insert into Employee (HID, Name, Position)
values (@child_3, ‘Tom’, ‘Software Engineer’);

Посмотрим, что получилось:

select EmployeeID, HID.ToString() as HID_String, Name


from Employee
order by HID;

EmployeID HID_String Name


2 / Andrew
3 /1/ Janet
10 /1.1/ Tom
1 /2/ Nancy

3.Процесс можно и нужно автоматизировать.


Процедура ввода новых узлов (вариант insert, добавление новых
сотрудников):

create proc AddEmp(@mgrID bigint,


@name varchar(200),
@Position varchar(200))
as
begin
declare @mgrID hierarchyID, @lc HierarchyID;

select @mgrID = HID


from Employee
where EmployeeID = @mgrID;

set transaction isolation level serializable;


begin transaction
select @lc = Max(HID)
from Employee
where HID.GetAncestor(1) = @mgrID;
insert into Employee(HID, Name, Position)
values(@mgrID.GetDescendant(@cl, null), @Name, @Position);
commit;
end;
go;

Использование этой процедуры:

exec AddEmp 2, ‘Steven’, ‘Manager’;


exec AddEmp 2, ‘Margaret’, ‘Software engineer’;
exec AddEmp 5, ‘Michael’, ‘Asistant’;

Обратим внимание, что в процедуре используется явная транзакция:


begin transaction(начать транзакцию) … commit(подтвердить транзакцию).
Транзакция это цепочка операций, которая должна быть выполнена
как единое, целое действие. Сбой любой операции внутри транзакции
приводит к отмене всех операций (откат транзакции - rollback). Только когда
все операции внутри транзакции реализованы успешно, транзакция
считается успешно выполненной в целом - подтверждается(commit).
В данном случае транзакция состоит из 2-х шагов. На 1-м шаге во
вспомогательную переменную @lc помещается иерархический адрес уже
существующего, крайнего справа, потомка нужного нам начальника. На 2-м
шаге новый сотрудник вставляется справа от этого, крайнего справа
сотрудника.
Перед выполнением транзакции выполняется инструкция set
transaction isolation level , которая устанавливает уровень изоляции
транзакции, в данном случае это уровень – serializable , т.е.
последовательный. Это самый строгий уровень изоляции. Он означает, что
при многопользовательской работе, транзакция будет выполняться так, как
будто других транзакций не существует, строго изолировано. До завершения
транзакции доступ к данным всем другим полностью заблокирован.

Оценить