Akademia SQL - Część 16: Partycjonowanie tabel – informacje podstawowe     Akademia SQL

Akademia SQL - Część 17: Dane hierarchiczne w SQL Server 2008 – informacje podstawowe Udostępnij na: Facebook

Autor: Damian Widera

Opublikowano: 30 października 2009

Zawartość strony
 Laboratorium   Laboratorium
 Referencje   Referencje

Ze strukturami hierarchicznymi mamy do czynienia na co dzień i serwer baz danych musi umieć efektywnie i wydajnie zapisać i przetwarzać takie informacje. Generalnie rzecz biorąc, związki hierarchiczne to takie, w których jeden element jest nadrzędny w stosunku do drugiego – podrzędnego. Związek taki można określić jako relację rodzic – dziecko (ang. parent – child relationship).

Elementy hierarchii zwane węzłami (ang. node) tworzą „odwrócone” drzewo. Zawiera ono wiele węzłów, ale w jego strukturze są dwa specyficzne.Znajdujący się na górze drzewa, czyli nieposiadający „rodzica”, nazywany jest korzeniem (ang. root), usytuowane u dołu drzewa (w jego gałęziach) nazywane są liśćmi (ang. leaf), a pozostałe –elementami drzewa.

Na rysunku poniżej element ken0 to korzeń, wszystkie elementy oprócz diane1 oraz gigi0 to węzły (elementy) , natomiast rob0, gail0 oraz jossef0 to liście. Diane1 oraz gigi0 nie są w hierarchii (nie są połączone) z innymi elementami:

Rysunek 1: Elementy drzewa.

Przykłady danych hierarchicznych:

  1. struktura organizacyjna firmy,
  2. system katalogów i plików,
  3. struktura projektu informatycznego, np.: projekt, grupa zadań, zadania.

We wcześniejszych wersjach serwera dane hierarchiczne można było przechowywać na kilka sposobów, ale najpopularniejszy były trzy:

  1. utworzenie w tabeli nowej kolumny, której wartość w rekordzie wskazuje na rekord nadrzędny w tej samej lub innej tabeli,
  2. utworzenie w tabeli nowej kolumny, w której dla każdego rekordu przechowywana jest ścieżka określająca pozycję elementu w hierarchii,
  3. przechowywanie danych hierarchicznych w formacie XML zapisanych w osobnej kolumnie.

SQL Server 2008 posiada nowy typ danych do składowania i obsługi danych hierarchicznych. Wspomniany typ to HIERARCHYID, który można opisać w następujący sposób:

a) przechowuje dane w postaci binarnej,

b) jest typem CLR i udostępnia wiele metod wspomagających pracę programistów,

c) umożliwia zapisywanie pozycji węzła w tzw. kanonicznej postaci tekstowej.

W kanonicznej postaci tekstowej zapisuje się pozycję węzła za pomocą ukośników (/) oraz liczb. Ukośnik oddziela poziomy hierarchii, a liczby całkowite oddzielone kropkami decydują, którą pozycję na danym poziomie zajmuje węzeł.

Porównywanie dwóch wartości typu HIERARCHYID może odbywać się zgodnie z zasadą „najpierw w pionie” lub „najpierw w poziomie”.

Stosowanie zasady „najpierw w pionie” powoduje, że węzły w pierwszej gałęzi (przykładowo /1/.../) mają mniejszą wartość, niż znajdujące się w drugiej gałęzi (przykładowo /2/.../). SQL Server 2008 stosuje tę zasadę domyślnie.

Natomiast przyjęcie zasady „najpierw w poziomie” powoduje, że węzeł na wyższym poziomie ma mniejszą wartość, niż znajdujący się na poziomie niższym, niezależnie od położenia obu węzłów w gałęziach hierarchii. W celu uzyskania porządku sortowania zgodnego z tą zasadą trzeba do badania poziomu węzła użyć metody GetLevel i w pierwszej kolejności sortować rekordy według poziomu, a dopiero w dalszej kolejności według wartości z kolumny typu HIERARCHYID.

Laboratorium

Celem laboratorium jest praktyczne zapoznanie się z typem danych HIERARCHYID. W pierwszym zadaniu zostanie pokazane, w jaki sposób można pracować z danymi hierarchicznymi, korzystając z metod dostępnych w starszej wersji systemu. W drugim zadaniu natomiast podamy sposoby korzystania z metod oferowanych przez typ HIERARCHYID. Do wykonania obydwu zadań potrzebna będzie baza danych AdventureWorks.

Zadanie 1.

1. Uruchom konsolę SQL Server Management Studio (SSMS) i połącz się z wybranym serwerem SQL:

Rysunek 2: Konsola SQL Server Management Studio (SSMS).

2. Otwórz nowe okno zapytania, wybierając opcję New Query z głównego menu.

3. Wpisz i uruchom poniższe zapytanie, które wyświetli informacje o pracownikach firmy AdventureWorks. Każdy z pracowników posiada unikalny identyfikator zapisany w kolumnie EmployeeID oraz identyfikator przełożonego (w kolumnie ManagerID). Osoba, która w kolumnie ManagerID ma zapisaną wartość NULL, jest właścicielem firmy, czyli nie posiada przełożonego.

use AdventureWorks

go

SELECT EmployeeID,ManagerID,LoginID

FROM HumanResources.Employee

4. Kolejny fragment kodu tworzy hierarchię pracowników, korzystając z Common Table Expressions:

WITH CTE_Employee AS

(

    SELECT EmployeeID,ManagerID,LoginID, 0 as POZIOM

    FROM HumanResources.Employee 

    WHERE ManagerID IS NULL

    UNION ALL

    SELECT HR.EmployeeID,HR.ManagerID,HR.LoginID, POZIOM+1

    FROM HumanResources.Employee HR JOIN CTE_Employee C

    ON HR.ManagerID = C.EmployeeID

)

SELECT * FROM CTE_Employee

ORDER BY POZIOM,ManagerID,EmployeeID

5. Zmienna POZIOM określa, na jakim poziomie drzewa znajduje się aktualny pracownik. W tym przypadku drzewo ma pięć poziomów. Na pierwszym jest właściciel (ken0), jego można uznać za korzeń drzewa. Na ostatnim poziomie znajduje się 190 pracowników, którzy są liśćmi. Sprawdzić to można, wykonując następujące zapytanie:

WITH CTE_Employee AS

(

    SELECT EmployeeID,ManagerID,LoginID, 0 as POZIOM

    FROM HumanResources.Employee 

    WHERE ManagerID IS NULL

    UNION ALL

    SELECT HR.EmployeeID,HR.ManagerID,HR.LoginID, POZIOM+1

    FROM HumanResources.Employee HR JOIN CTE_Employee C

    ON HR.ManagerID = C.EmployeeID

)

SELECT COUNT(EmployeeID) as [Liczba pracownikow],POZIOM FROM CTE_Employee

GROUP BY POZIOM

ORDER BY POZIOM

Liczba pracownikow POZIOM

------------------ -----------

1                  0

6                  1

27                 2

66                 3

190                4



(5 row(s) affected)

6. Omawiane rozwiązanie charakteryzuje się zwięzłym zapisem i czytelnością kodu. Brakuje w nim jednak informacji o ścieżce od każdego pracownika do właściciela firmy. Innymi słowy: pracownik adventure-works\ben0 o identyfikatorze 261 ma przełożonego o numerze 274 (adventure-works\sheela0), który z kolei jest podwładnym pracownika o numerze 71 (adventure-works\wendy0). Ten ma przełożonego o numerze 140 (adventure-works\laura1), który jest bezpośrednim podwładnym właściciela firmy. Istotna byłaby więc informacja zapisana przy pierwszym z wymienionych pracowników: 1/140/71/274/261.

7. Poniższe zapytanie prezentuje, w jaki sposób dynamicznie zbudować ścieżkę, o której mowa w poprzednim punkcie:

DECLARE @root AS INT;

SET @root = 1;



WITH CTE_Employee

AS

(

  SELECT EmployeeID, ManagerID,LoginID, 0 AS poziom,

    CAST('.' + CAST(EmployeeID AS VARCHAR(10)) + '.'

         AS VARCHAR(MAX)) AS sciezka

  FROM HumanResources.Employee 

  WHERE ManagerID IS NULL



  UNION ALL



  SELECT HR.EmployeeID, HR.ManagerID,HR.LoginID, poziom + 1,

    CAST(C.sciezka + CAST(HR.EmployeeID AS VARCHAR(10)) + '.'

         AS VARCHAR(MAX)) AS sciezka

  FROM HumanResources.Employee  HR JOIN CTE_Employee AS C

    ON HR.ManagerID = C.EmployeeID

)

SELECT *

FROM CTE_Employee

ORDER BY POZIOM,ManagerID,EmployeeID,sciezka;

8. Dla pracownika adventure-works\ben0 o identyfikatorze 261 otrzymujemy następujący wynik:

EmployeeID  ManagerID   LoginID               poziom      sciezka

----------- ----------- ------------------    ----------- -------------------

261         274         adventure-works\ben0  4           .109.140.71.274.261.

Zadanie 2.

1. Podobne operacje dotyczące hierarchii pracowników w firmie wykonane zostaną za pomocą typu danych HIERARCHYID.
2. Otwórz nowe okno zapytania, wybierając opcję New Query z głównego menu.

3. Utwórz tabelę dbo.Organizacja, korzystając z podanego niżej kodu. Informacje o hierarchii pracowników będą w niej przechowywane w polu EmployeeID typu HIERARCHYID:

use AdventureWorks

GO

CREATE TABLE dbo.Organizacja

(

    Pracownik hierarchyid NOT NULL,

    Nazwa nvarchar(50) NOT NULL

)

4. Do tabeli zostanie dodana kolumna Poziom, w której przechowywane będą informacje o poziomie pracownika w hierarchii:

ALTER TABLE dbo.Organizacja 

ADD Poziom AS Pracownik.GetLevel()

5. Dodaj klucz główny do tabeli dbo.Organizacja, który utworzony jest na polu Pracownika. Operacja ta jest konieczna, ponieważ w następnym kroku będzie tworzona kolumna wyliczeniowa Przelozony:

ALTER TABLE dbo.Organizacja ADD CONSTRAINT

    PK_Organizacja PRIMARY KEY

    (

        Pracownik

    )

6. Utwórz kolumnę wyliczeniową Przelozony, która będzie kolumną wyliczeniową trwałą (jej wartość będzie przechowywana w tabeli). Wartość w tej kolumnie jest pobierana z metody GetAncestor(), która pozwala obliczyć osobę nadrzędną w hierarchii i jako parametr przyjmuje liczbę poziomów, o którą należy się przesunąć „w górę”, żeby uzyskać wynik:

ALTER TABLE dbo.Organizacja

ADD Przelozony AS Pracownik.GetAncestor(1) PERSISTED

    REFERENCES dbo.Organizacja(Pracownik)

7. Spójrz na poniższe zapytanie. Jest ono oparte na

WITH CTE_Hierarchia 

AS

(

    SELECT EmployeeID, LoginID, hierarchyid::GetRoot() AS Pracownik

    FROM HumanResources.Employee

    WHERE ManagerID IS NULL

    UNION ALL

    SELECT HR.EmployeeID, HR.LoginID, C.Pracownik.GetDescendant(null, null)

    FROM HumanResources.Employee AS HR

        INNER JOIN CTE_Hierarchia AS C

            ON HR.ManagerID= C.EmployeeID

)

Select *

From CTE_Hierarchia



EmployeeID  LoginID                       Pracownik

----------- -----------------------------------------

109         adventure-works\ken0          0x

6           adventure-works\david0        0x58

12          adventure-works\terri0        0x58

42          adventure-works\jean0         0x58

140         adventure-works\laura1        0x58

148         adventure-works\james1        0x58

273         adventure-works\brian3        0x58

268         adventure-works\stephen0      0x5AC0

284         adventure-works\amy0          0x5AC0

288         adventure-works\syed0         0x5AC0

290         adventure-works\lynn0         0x5AD6



------------------------------------------------

8. Dane w kolumnie Pracownik nie są unikalne i nie mogą posłużyć do zbudowania klucza głównego w tabeli dbo.Organizacja, w której ten klucz miał być oparty tylko na kolumnie Pracownik. Można jednak zmienić sposób wyznaczania kolumny Pracownik, dodając do niej informację o kolumnie EmployeeID, która na pewno zawiera dane unikalne:

WITH CTE_Hierarchia 

AS

(

    SELECT EmployeeID, LoginID, hierarchyid::GetRoot() AS Node

    FROM HumanResources.Employee

    WHERE ManagerID IS NULL

    UNION ALL

    SELECT HR.EmployeeID, HR.LoginID, hierarchyId::Parse(c.Node.ToString() + Cast(HR.EmployeeID as varchar(5)) + '/')

    FROM HumanResources.Employee AS HR

        INNER JOIN CTE_Hierarchia AS C

            ON HR.ManagerID= C.EmployeeID

)

INSERT INTO dbo.Organizacja(Pracownik,Nazwa)

Select Node, LoginID 

From CTE_Hierarchia

9. Sprawdźmy zatem uzyskane wyniki. Zwróć uwagę, że dane typu HIERARCHYID są rzeczywiście w zapisane w formacie binarnym – kolumny Pracownik i Przelozony. Jeśli chcesz zobaczyć, jak wygląda taka informacja w formacie tekstowym, wywołaj na odpowiedniej kolumnie metodę ToString():

SELECT Pracownik,Nazwa,Poziom,Przelozony,Pracownik.ToString() as Sciezka

FROM dbo.Organizacja

ORDER BY Poziom

Rysunek 3: Wynik działania zapytania.

10. Jeśli chciałbyś się dowiedzieć, kto jest szefem firmy, możesz wykonać zapytanie, w którym skorzystasz ze statycznej metody HIERARCHYID::GetRoot():

SELECT *

FROM dbo.Organizacja

WHERE Pracownik = HIERARCHYID::GetRoot();

Rysunek 4: Wynik działania zapytania.

11. Jeśli chcesz wyświetlić informacje o tym, kto jest przełożonym danego pracownika, musisz skorzystać z metody IsDescendantOf():

Declare @Pracownik As HierarchyId

Select @Pracownik = Pracownik From dbo.Organizacja

Where Nazwa = 'adventure-works\grant0'



Select *

From dbo.Organizacja

Where @Pracownik.IsDescendantOf(Pracownik) = 1

Rysunek 5: Wynik działania zapytania.

12. Typ danych HIERARCHYID umożliwia wykonywanie w zasadzie wszystkich operacji, które są potrzebne podczas manipulowania strukturami drzewiastymi. Nie są to jednak operacje proste, wymagają doświadczenia i dlatego nie zostaną zaprezentowane w tej lekcji. W literaturze pokazano miejsca, gdzie można odnaleźć praktyczne przykłady zastosowań typu danych HIERARCHYID.

 Do początku strony Do początku strony

Referencje

Dodatkowe informacje na temat obiektów o zróżnicowanych atrybutach można znaleźć w następujących miejscach:

[1] HierarchyID, czyli drzewa po nowemu – artykuł Marcina Goła (SQL Server MVP) na polskich stronach TechNet

[2] „Serwer SQL 2008. Administracja i programowanie”, Wydawnictwo Helion SA

[3] Artykuł na portalu www.sqlservercentral.com


  Damian Widera, Project Manager & Team Lead (MCT, MCITP – DBA, MCSD.NET)
Od 8 lat zajmuje się projektowaniem, tworzeniem i wdrażaniem aplikacji wykorzystujących platformę .NET, SQL Server oraz Oracle. Obecnie pracuje jako project manager dla LGBS Polska. Pracował także jako trener, programista, administrator baz danych, twórca domumentacji oraz analityk biznesowy. Aktywnie współpracuje z polskim oddziałem Microsoft publikując atykuły, webcasty oraz porady z zakresu SQL Server na stronach TechNet. Jest współautorem książki „Serwer SQL 2008. Administracja i programowanie”.

Speaker na wielu konferencjach, m.in. Microsoft Heroes Happen Here, C2C, European PASS Conference, Microsoft Technology Summit, Energy Launch, TechED. Od 2004 r. posiada certyfikaty firmy Microsoft: MCT, MCITP–DBA oraz MCSD.NET. Jest współtwórcą oraz liderem jednej z najwiekszych grup pasjonatów SQL Server w Polsce – Śląskiej Regionalnej Grupy Microsoft (PLSSUG Katowice). Od listopada 2008 jest prezesem Polish SQL Server Users Group (PLSSUG) w Polsce. W styczniu 2009 nagrodzony tytułem MVP w kategorii SQL Server.
 Do początku strony Do początku strony

Akademia SQL - Część 16: Partycjonowanie tabel – informacje podstawowe     Akademia SQL