HierarchyID – czyli drzewa po nowemu
Opublikowano: 21 lipca 2009
Autor: Marcin Goł
Każdy z nas spotkał się z problemem zamodelowania struktury drzewiastej – czy chodziło o strukturę organizacyjną przedsiębiorstwa czy o drzewo genealogiczne – nie ma to znaczenia, problem jest ten sam: potrzeba zamodelowania drzewa z możliwością wstawiania, przesuwania węzłów oraz przeszukiwania drzewa z jak najlepszą wydajnością . Jak do tej pory, hierarchiczne struktury najczęściej budowano w oparciu o 2 kolumny, w których jedna kolumna przedstawiała ID węzła, a druga przechowywała identyfikator węzła nadrzędnego. Innym, rzadziej stosowanymi podejściami było tworzenie własnych typów danych (w SQL lub .NET CLR) czy budowa łańcucha znaków przechowującego pełną ścieżkę do korzenia, co jak się przekonamy— zostało również po części zaadoptowane w SQL Server 2008. Do dalszych rozważań o sposobach operowania na drzewach użyjemy tylko klasycznego, określanego jako parent/child oraz nowego opartego o typ danych HierarchyID – dostępny w SQL Server 2008. |
|
Parent/child – słów kilka
Idea nie jest nowa i zapewne wielu osobom znana poprzednich edycji SQL Server. Należy pamiętać, że przed wersją 2005, kiedy nie było możliwości budowania wyrażeń CTE, operacje na strukturach drzewiastych były trudne i mało wydajne. Po pojawieniu się wspomnianej wersji serwera baz danych sytuacja uległa znaczącej zmianie.
Poniżej kilka przykładowych podstawowych zastosowań:
use tempdb
go
if object_id('dbo.structure') is not null
drop table dbo.structure
if object_id('dbo.nodes') is not null
drop table dbo.nodes
go
create table dbo.nodes (id int primary key, name varchar(50))
create table dbo.structure (child int, parent int
constraint str_child_id foreign key (child) references dbo.nodes(id),
constraint str_parent_id foreign key (parent) references dbo.nodes(id)
)
go
-- standardowa struktura drzewiasta zamodelowana przy pomocy parent/child
insert into dbo.nodes (id,name)
values (1,'pierwszy'),
(2,'drugi'),
(3,'trzeci'),
(4,'czwarty'),
(5,'piaty'),
(6,'szosty'),
(7,'siodmy'),
(8,'osmy'),
(9,'dziewiaty')
insert into dbo.structure (child, parent)
values (1,null),
(2,1),(3,1),
(4,2),(5,2),(6,3),(7,3),
(8,4),(9,4)
select t1.child, t2.name, t1.parent, t3.name
from dbo.structure t1
join dbo.nodes t2 on (t1.child = t2.id)
join dbo.nodes t3 on (t1.parent = t3.id)
-- rekurencyjne cte tworzace strukture drzewa
-- dodatkowo mozna uzyc row_number() z partition by/order by celem budowy
-- drzewa w zwracanym secie
;with struct(parent, child, lvl) as
(
select parent, child, 0 as lvl
from dbo.structure
where parent is null
union all
select i2.parent, i2.child, i1.lvl + 1 from struct i1
join dbo.structure i2 on (i1.child = i2.parent)
)
select x1.child as NodeId, x2.name as NodeName,x1.parent as ParentNode,x1.lvl as Level
from struct x1 join dbo.nodes x2
on (x1.child = x2.id)
order by Level,ParentNode
-- rekurencyjne cte budujace sciezke od wskazanego wezla do korzenia
-- zwracana w dosc ciekawy sposob (przy pomocy for xml path(''))
;with struct(parent, child, lvl) as
(
select parent, child, 0 as lvl
from dbo.structure
where child = 9
union all
select i2.parent, i2.child, i1.lvl + 1 from struct i1
join dbo.structure i2 on (i1.parent = i2.child)
)
select (select '/' + cast(x1.child as varchar(20)) + ';' + x2.name
from struct x1 join dbo.nodes x2
on (x1.child = x2.id)
order by x1.lvl desc
for xml path('')) as SciezkaDoRoot
-- przepiecie poddrzewa
update dbo.structure set parent = 7 where child = 6
Jak widać na przykładzie powyższego kodu wyrażenia CTE wprowadzone w SQL Server 2005, spowodowały uproszczenie zapytań przeglądających struktury parent/child.
Owszem, przy dużych drzewach rekurencja może powodować znaczące obciążenie – od czego są indeksy ? Czego więc brakowało ? Co takiego dano nam w nowym typie danych HierarchyID, czego nie mieliśmy w poprzednim podejściu? Jakie są koszty korzystania z HierarchyID ?
Do początku strony
Słów kilka o typie danym jako takim…
HierarchyID (HID) jest typem danych o zmiennej długości, fizycznie będacy w SQL Server biblioteką CLR: Microsoft.SqlServer.Type.
HID charakteryzuje się wysokim stopniem kompresji – dla przykładu przechowanie 100.000 węzłów o stopniu rozgałęzienia drzewa równym 6 (każdy węzeł ma 6 dzieci) – zajmuje 38bitów, co po zaokrągleniu do pełnych bajtów daje nam 40 bitów=5bajtów.
Należy zwrócić uwagę, że typ danych ma limit długości wartości równy 892 bajtów, więc jeśli drzewo potrzebuje przechować informacje wykraczające poza ten limit, to operacja taka się nie powiedzie. Niestety ze względu na specyfikę HID trudno powiedzieć ile węzłów będzie zawierało drzewo, które nie zmieści się w w limicie 892bajtów, ale można wykonać prosty testy:
Budowa drzewa w głąb
if (object_id('dbo.hidtest') is not null)
drop table dbo.hidtest
create table dbo.hidtest ( n HierarchyID)
go
insert into dbo.hidtest (n) values (HierarchyID::GetRoot())
declare @hid HierarchyID
set @hid = (HierarchyID::GetRoot()).GetDescendant(null,null)
declare @i int
set @i = 0
while @i > -1
begin
begin try
insert into hidtest (n) values (@hid)
set @hid = @hid.GetDescendant(null,null)
set @i = @i + 1
end try
begin catch
declare @emes nvarchar(2048),@esev int,@esta int, @enum int
set @emes = ERROR_MESSAGE()
set @esev = ERROR_SEVERITY()
set @esta = ERROR_STATE()
set @enum = ERROR_NUMBER()
raiserror(@emes,@esev,@esta,@enum)
print 'Wartosc HID: ' + @hid.ToString()
print 'Poziom HID: ' + cast (@hid.GetLevel() as varchar(10))
set @i = -1
end catch
end
Powyższy skrypt zbudował u mnie drzewo mające 1428 poziomów (wliczając korzeń), dopiero przy próbie dodania kolejnego otrzymałem błąd:
Msg 6522, Level 16, State 2, Line 21
A .NET Framework error occurred during execution of user-defined routine or aggregate "HierarchyID":
Microsoft.SqlServer.Types.HierarchyIDException: 24006: SqlHierarchyID.GetDescendant failed because its result is too big.
Microsoft.SqlServer.Types.HierarchyIDException:
at Microsoft.SqlServer.Types.OrdPath.AppendBits(UInt16& bitOffset, UInt64 x, UInt16 nBits)
at Microsoft.SqlServer.Types.OrdPath.WriteOrd(UInt16& bitOffset, Int64 ord)
at Microsoft.SqlServer.Types.XmlIdGenerator.GetNextID()
at Microsoft.SqlServer.Types.SqlHierarchyID.GetDescendant(SqlHierarchyID child1, SqlHierarchyID child2)
Poniżej prosty przykład prezentujący uzycie HID:
if object_id ('tempdb.dbo.#t') is not null
drop table #t
create table #t (node HierarchyID, NodeLevel as node.GetLevel() )
go
declare @root HierarchyID
set @root = HierarchyID::GetRoot()
-- korzystajac z "przewidywalnosci" HierarchyID, uzywam stalych wartosci
-- zeby zaprezentowac, jak rozna postac moga przyjmowac dane
insert into #t (node) values (@root)
insert into #t (node) values (@root.GetDescendant(null,null))
insert into #t (node) values (@root.GetDescendant(null,0x58))
insert into #t (node) values (@root.GetDescendant(null,0x48))
insert into #t (node) values (@root.GetDescendant(0x3F80,0x48))
insert into #t (node) values (@root.GetDescendant(null,0x42C0))
insert into #t (node) values (@root.GetDescendant(null,0x3E80))
insert into #t (node) values (@root.GetDescendant(0x3D80,0x3E80))
insert into #t (node) values (@root.GetDescendant(0x3D80,0x3E2C))
insert into #t (node) values (@root.GetDescendant(0x3D80,0x3E24))
insert into #t (node) values (HierarchyID::Parse('/-3.1/').GetDescendant(null,null))
insert into #t (node) values (HierarchyID::Parse('/-3.1/').GetDescendant(0x3E2D60,null))
insert into #t (node) values (HierarchyID::Parse('/-3.1/').GetDescendant(0x3E2DA0,null))
select node as Node, node.ToString() as NodeString, NodeLevel
from #t
order by node
Zapytanie z ostatnich lini polecenia zwraca nam następujący wynik:
Rysunek 1: Zróżnicowanie wartości HierarchyID ze względu na kolejność i miejsce wstawiania.
W powyższym przykładnie użyłem kilku metod udostępnionych przez typ danych HID :
- GetRoot() – metoda zwraca wartość korzenia dla nowego drzewa HID.
- GetDescendant(child1,child2) – metod zwraca wartość HID dla nowego węzła uwzględniając jako położenie argumenty child1, child2. Child1 powinien mieć mniejszą wartość niż child2. Jeśli przekażemy tylko child1, a child2 zostanie null, to zostanie zwrócona wartość dla nowego węzła „po” child1 (wartość HierarchyID rośnie),a w przeciwnym wypadku —przed child2 (wartość maleje). Jeśli przekażemy obydwa parametry , to zostanie zwrócona wartość dla potomka pomiędzy tymi węzłami.
- ToString() – przedstawia hierarchię w postaci łańcucha znaków. Zwracana wartość jest typu nvarchar(4000).
- GetLevel() – zwraca informację o poziomie zagnieżdżenia w drzewie. Wartość dla korzenia to 0, dla jego potomków kolejne liczby naturalne.
Uwaga ogólna do wszystkich nazw metod — są to metody napisane w .NET CLR, a więc niezależnie od wybranego sposobu segregowania (ang. collation) w SQL Server, należy pamiętać że ma znaczenie, czy piszecie nazwy metod wielkimi czy małymi literami. Próba odwołania się do metody getroot() zamiast GetRoot() – powoduje błąd kompilacji i jest jednym z częstszych błędów początkujących.
Pracując z nowym typem danych HierarchyID należy pamiętać, że:
- Korzeń (ang. Root) drzewa zawsze ma wartość 0x (po rzutowaniu string ‘/’),
- HID operuje na liczbach całkowitych,
- Jeśli brakuje miejsca pomiędzy wartościami, to stosowana jest kropka –np. wstawienie nowego węzła pomiędzy /1 i /2 spowoduje że wartość nowego to /1.1/,
- Wartość /1.01/ jest błędna – zera porzedzające są zakazane (ponieważ mogły by wystąpić problemy w parsowaniu) poprawna reprezentacja to /1.1/.
Do początku strony
Efektywne przeglądanie drzewa
Na potrzebny przeszukiwania drzewa przygotowano odpowiedni zestaw procedur oraz dwie typowe strategie indeksowania.
Na początek zajmijmy się procedurami przeszukującymi drzewo:
- GetAncestor(n) – która pobiera ojca o n poziomów wyżej w hierarchii, dla n = 0 zwracany jest wartość węzła na jakim wywołujemy metodę, dla n = 1 dostaniemy jego ojca itd.
- IsDescendantOf(parent) – sprawdza, czy węzeł jest potomkiem węzła przekazanego w argumencie.Wywołanie metody dla samego siebie zwraca 1 (typ danych bit).
W takim razie w jaki sposób pobrać wszystkie węzły z danego poddrzewa? Przeanalizujmy poniższy kod:
declare @subtreeRoot HierarchyID
-- wartosc root poddrzewa
set @subtreeRoot = HierarchyID::Parse('/-3.1/')
select node as Node, node.ToString() as NodeString, NodeLevel
from #t t1
where
t1.node >= @subtreeRoot
and t1.node < @subtreeRoot.GetAncestor(1).GetDescendant(@subtreeRoot,null)
W powyższym przykładzie widać że typ HID obsługuje m.in. operatory wiekszości (‘>’), równości (‘=’). Ze specyfiki budowy typu danych wynika że poddrzewo ma zawsze większe wartości niż korzeń. Znając te zależność – pobranie poddrzewa wskazanego węzła można sprowadzić do jednego prostego SELECT. Ważnym jest że zapytamy o wartości większe niż badany korzeń oraz o mniejsze niż następny węzeł na tym samym poziome co wskazany root.
Zwrócmy teraz uwagę na wywołanie typowe dla języków obiektowych: GetAncestor(…).GetDescendant(…)).
GetAncestor(1) pobiera bezpośredniego ojca węzła dla którego wywołujemy metodę, a dla znalezionego ojca dzieki procedurze GetDescendant(@subtreeRoot,null) pobieramy wartość potomka następnego po wartości korzenia poszukiwanego poddrzewa. Brzmi to być może skomplikowanie, ale zrozumienie charakteru HID pozwala tworzyć o wiele prostsze i szybsze zapytania.
Istnieje metoda DescendantLimit(), która jest jednak metodą prywatną klasy Microsoft.SqlServer.Types.SqlHierarchyID i w związku z tym nie można jej użyć, a jeśli ktoś spróbuje otrzyma poniższy komunikat:
„Msg 6574, Level 16, State 2, Line 19
Method, property or field 'DescendantLimit' of class 'Microsoft.SqlServer.Types.SqlHierarchyID' in assembly 'Microsoft.SqlServer.Types' is not public.”
Trochę szkoda – gdyż upublicznienie tej metody ułatwiło by nam życie - skracając długie wywołanie zaprezentowane powyżej.
Optymalizację przeszukiwania drzewa uzyskamy tworząc odpowiednią strategię indeksowania:
Do początku strony
Strategia #1 – depth-first (DF)
Rysunek 2: Strategia depth-first.
Analizując powyższy rysunek można stwierdzić, że stosując strategię DF wiersze należące do poddrzew danego węzła znajdują się bliżej tego węzła niż węzły znajdujące się na tym samym poziomie co badany węzeł.
Do początku strony
Strategia #2 - breadth-first (BF)
Rysunek 3: Strategia breadth-first.
W tym przypadku jest to zorganizowane w taki sposób, aby w pobliżu danego węzła znajdowały się wiersze leżące na tym samym poziomie zagnieżdżenia.
Utwórzmy zatem indeksy dla każdej ze strategii i sprawdźmy jak zachowują się w praktyce:
create clustered index temp_Breadth_First
ON #t(NodeLevel,Node);
GO
create nonclustered index temp_Depth_First
ON #t(Node);
GO
Przeanalizujmy plany zapytań dla różnych przypadków, dla zapytania pobierającego poddrzewo dla wskazanego węzła czyli:
select node as Node, node.ToString() as NodeString, NodeLevel
from #t t1
where
t1.node >= @subtreeRoot
and t1.node < @subtreeRoot.GetAncestor(1).GetDescendant(@subtreeRoot,null)
a) Brak indeksów:
Rysunek 4: Brak indeksów.
b) Po utworzeniu indeksu klastrowanego – dla strategii BF:
Rysunek 5: Po utworzeniu indeksu klastrowanego – dla strategii BF.
c) Po dodaniu indeks nieklastrowanego dla strategii DF:
Rysunek 6: Po dodaniu indeks nieklastrowanego dla strategii DF.
Jeszcze raz przeanalizujmy zapytanie, pytamy o wiersze z wartością HID większą niż wskazany korzeń oraz mniejszymy od następnej wartości na tym poziomie drzewa.
W przypadku A, SQL Server nie miał zbyt dużego wyboru – brak indeksów – musiał wykonać skan tabeli, w kolejnym przypadku kiedy pojawił się indeks – niestety mało przydatny (jego klucz zawierał kolumny: NodeLevel,Node) – w związku z czym konieczny okazał się skan indeksu. W ostatnim przypadku dzięki temu że funkcja GetDescendant() jest deterministyczna – SQL Server mógł spokojnie zastosować przeszukanie indeksu (czyli skorzystał ze strategii DF w celu pobrania poddrzewa) – znacząco przyspieszając wyszukiwanie.
Sprawdźmy, jak będzie wyglądał plan zapytania, jeśli będziemy chcieli pobrać węzły będace dzieckiem (czyli wpasowujące się w strategie BF). W tym celu wykorzystamy nieptymalne na pierwszy rzut oka polecenia:
select node as Node, node.ToString() as NodeString, NodeLevel
from #t where node.GetAncestor(1) = @subtreeRoot
Zapytanie to będzie miało następujący plan:
a) Bez indeksów
Rysunek 7: Bez indeksów.
b) Po utworzeniu indeksu zgrupowanego (strategia BF):
Rysunek 8: Po utworzeniu indeksu zgrupowanego (strategia BF).
c) Po dodaniu indeksu niezgrupowanego (strategia DF):
Rysunek 9: Po dodaniu indeksu niezgrupowanego (strategia DF).
Przypadek A nas niczym nie zaskakuje – brak indeksów – skan tabeli, jednakże patrząc jeszcze raz na zapytanie zauważamy że wywołujemy metodę GetAncestor() na każdym węźle, w związku z tym czy nie będziemy zawsze skanować tabeli/indeksu?. Odpowiedź na to pytanie znajdziemy przeglądając plan zapytania w formie tekstowej, okazuje się że zapytanie jest przekształcane w następujący sposób:
select node as Node, node.ToString() as NodeString, NodeLevel
from #t where nodelevel = @subtreeRoot.GetLevel()+1
and node >= @subtreeRoot
and node <= @subtreeRoot.DescendantLimit()
-- a jako ze wiemy iz DescendantLimit jest metoda prywatna to mozemy
-- zapytanie napisac po swojemu:
select node as Node, node.ToString() as NodeString, NodeLevel
from #t where nodelevel = @subtreeRoot.GetLevel()+1
and node >= @subtreeRoot
and node <= @subtreeRoot.GetAncestor(1).GetDescendant(@subtreeRoot,null)
Co powoduje że zamiast przeskanowania wszystkich wierszy w tabeli wykorzystywany indeks z wyliczalną kolumną NodeLevel. Ponieważ indeks miał klucz w postaci NodeLevel, Node – powyższe zapytanie idealnie się w niego wpasowywuje.
Podobne przekształcenia są stosowna dla innych metod klasy HierarchyID – można o tym przeczytać tutaj.
Do początku strony
Czego nie umie HierarchyID (i czy aby na pewno)?
Przede wszystkim typ danych HierarchyID nie zarządza poprawnością tworzonych węzłów, nie sprawdza czy są luki w numeracji lub czy nie jest dodawany istniejący już węzeł. Część z tych „braków” możemy ominąć stosując indeksy czy instrukcję CHECK. Są jednak operacje, które wykonać trudniej, np. przenoszenie węzłów czy migracja ze struktury parent/child.
Zacznijmy w takim razie od problemu opisanego jako ostatni, czyli od migracji ze struktury parent/child do struktury z typem danych HierarchyID:
Dla przypomnienia – będziemy posługiwać się tabelami dbo.nodes oraz dbo.structure utworzonymi na poczatku artykułu:
if object_id('dbo.structure') is not null
drop table dbo.structure
if object_id('dbo.nodes') is not null
drop table dbo.nodes
go
create table dbo.nodes (id int primary key, name varchar(50))
create table dbo.structure (child int, parent int
constraint str_child_id foreign key (child) references dbo.nodes(id),
constraint str_parent_id foreign key (parent) references dbo.nodes(id)
)
go
-- standardowa struktura drzewiasta zamodelowana przy pomocy parent/child
insert into dbo.nodes (id,name)
values (1,'pierwszy'),
(2,'drugi'),
(3,'trzeci'),
(4,'czwarty'),
(5,'piaty'),
(6,'szosty'),
(7,'siodmy'),
(8,'osmy'),
(9,'dziewiaty')
insert into dbo.structure (child, parent)
values (1,null),
(2,1),(3,1),
(4,2),(5,2),(6,3),(7,3),
(8,4),(9,4)
Rozwiązując problem migracji możemy zastosować dwa podejścia:
- Znając sposób budowy drzewa HID możemy pobierać po jednym węźle, który już ma ojca – jemu przypisywać wartość HID. Operację tę wykonujemy do momentu,w którym nie będzie już węzłów do ponumerowania. Rozwiązanie to choć zgodne z metodą budowy drzew, jest jednak mało efektywne,
- Drugi pomysł wykorzystuje specyfikę HierarchyID, czyli przydzielanie kolejnych identyfikatorów dla kolejnych dzieci wybranego ojca. Identyfikatory te są przydzielane znaną z poprzedniej wersji serwera skalarną funkcją ROW_NUMBER().
;with str(parent, child, num) as
(
select t1.parent, t1.child,
ROW_NUMBER() OVER (PARTITION BY t1.parent ORDER BY t1.child) as num
from dbo.structure t1
),
paths(path, child) as
(
SELECT HierarchyID::GetRoot() AS Node, t2.child
FROM str t2
WHERE t2.parent is null
UNION ALL
SELECT
CAST(t4.path.ToString() + CAST(t3.Num AS varchar(30)) + '/' AS HierarchyID), t3.child
FROM str t3
JOIN paths t4
ON t3.parent = t4.child
)
SELECT t1.path, t1.path.ToString(), t1.child, t2.name
from paths t1 join dbo.nodes t2 on (t1.child = t2.id)
order by path
Powyższe zapytanie jest poprawioną wersją kodu dostępnego w Books Online (gdzie występują błedy czeskie). Szybka analiza: wyrażenie CTE str(parent, child, num) numeruje wiersze drzewa partycjonując je po węzłach nadrzędnych (czyli tak jak działa HierarchyID). Kolejne paths(path,child) zaczyna przeglądanie drzewa od korzenia i buduje wartości tekstowe HID. Całe zapytanie daje następujący wynik:
Rysunek 10: Migracja parent/child -> HierarchyID w jednym zapytaniu ?.
Przenoszenie poddrzewa w innej miejsce także okazuje się równie łatwe:
declare @nroot HierarchyID, @oroot HierarchyID
select @oroot = node from #t where node = '/-3.1/'
SELECT @nroot = node from #t where node = '/-3.-1/'
select @nroot = @nroot.GetDescendant(max(node), null)
from #t where node.GetAncestor(1) = @nroot
select node as 'OldNode',
node.ToString() as 'OldPath',
node.GetReparentedValue(@oroot, @nroot) AS 'NewNode',
node.GetReparentedValue(@oroot, @nroot).ToString() AS 'NewPath'
from #t
where node.IsDescendantOf(@oroot) = 1
and node.GetLevel() > @oroot.GetLevel()
Przenoszenie poddrzewa – czyli wskazanie że całe poddrzewo węzła @oroot (z tym węzłęm włącznie), będzie przeniesione pod węzeł @nroot. W związku z tym używamy metody GetReparentedValue(), która właśnie pobiera poprzedni i nowy korzeń – a zwraca nową wartość HierarchyID dla węzła, z uwzględnieniem zmiany korzenia.
Po uruchomieniu zapyatnia otrzymujemy nastepujący wynik:
Rysunek 11: Przenoszenie poddrzewa w HierarchyID.
Mając nową ścieżkę wystarczy ją przepisać do wartości HID poddrzewo zostaje przesunięte. Ten sam efekt można osiągnąc przy pomocy funkcji np.: stuff:
stuff(
node.ToString(), -- ze stringa
1, -- zaczynajac od jego pierwszego znaku
len(@oroot.ToString()), -- usun taka liczbe znakow
@nroot.ToString()) -- i zastap je tymi
Proponuję samodzielnie wykonać sprawdzenie tego przypadku.
Można sobie wybrazić inny przypadek przenoszenia poddrzewa, a mianowicie użytkownik podaje poprzedni i nowy korzeń, a procedura przepisuje wszystkie węzły poniżej starego korzenia, bezpośrednio pod nowy. Można to zrealizować modyfikując rozwiązanie podane wyżej, tzn.: należalo by wówczas pobrać węzły będące bezpośrednimi potomkami starego korzenia, i każego tak wycięte poddrzewo przeniesić pod nowy korzeń.
Do początku strony
Na zakończenie
Przeglądając w Internecie zasoby dotyczące HierarchyID można często spotkać zarzuty, że korzystając z niego należy pamiętać o wielu kruczkach i zależnościach – np. o lukach w numeracji czy odpowiedniej przebudowie drzewa. Moja odpowiedź na takie uwagi jest jedna: HierarchyID to typ danych a nie mechanizm budowy struktur drzewiastych. W związku tym to do aplikacji należy zarządzanie jej wartościami. Ponadto brak mechanizmów automatyzacji umożliwia większą elastyczność i wydajność.
Oczywiście typ HierarchyID ma swoje ograniczenia, ale myślę, że możliwości jakie on daje podczas pracy ze strukturami drzewiastymi, zdecydowanie przechyla szalę wagi na jego stronę. Ponadto w przypadku kiedy limit 892 bajtów byłbym zbyt dużym ograniczeniem można wykonać kilka drzew łączonych mechanizmami własnymi – parent/child.
Z ciekawostek można jeszcze powiedzieć, że kiedy są tworzone indeksy na polach typu XML, to typ danych HID jest wykorzystywany wewnątrz silnika baz danych do reprezentacji, w której pozycji drzewa dokumentu aktualnie się znajdujemy… co dowodzi jak bardzo wydajny i elastyczny jest to mechanizm.
Marcin Goł Architekt baz danych, ISCG Aktualnie pracuje w ISCG, gdzie projektuje i optymalizuje bazy danych; w swojej pracy głównie wykorzystuje technologie Microsoft SQL Server. Wcześniej brał udział w wielu wdrożeniach systemów OSS dla sektora telekomunikacyjnego oraz pracował jako administrator środowiska opartego o technologię Microsoft. Lider Polish SQL Server User Group (PLSSUG); aktywny użytkownik portalu WSS.pl, prowadzi blog SQL Server. Specjalizuje się w analizie i rozwiązywaniu problemów wydajnościowych. W styczniu 2009 został nagrodzony tytułem Most Valuable Professional w kategorii SQL, ponadto posiada certyfikaty firmy Microsoft, min.: MCITP: Database Administrator. |
Do początku strony |