Funkcje pozycjonujące
Najnowszy MS SQL Server 2005 doczekał się w końcu mechanizmu numerowania wierszy. Może to brzmi troszeczkę niezwykle, ale wszystkie poprzednie wersje serwera były pozbawione jakiegokolwiek mechanizmu numerowania wierszy w zbiorze wynikowym zapytania. Oczywiście zaimplementowanie takiego mechanizmu nie było zbyt wyrachowanym zadaniem, aczkolwiek wydaje mi się, iż tak podstawowa operacja powinna mieć standardowy mechanizm. Tak też się stało. Mamy dostęp teraz do czterech funkcji pozycjonujących których opisy zamieszczam w poniższej tabeli.
Funkcja |
Opis |
---|---|
RANK |
Zwraca pozycję dla każdego wiersza w określonej części zbioru wynikowego |
DENSE_RANK |
Zwraca następną pozycję dla każdego wiersza w określonej części zbioru wynikowego |
ROW_NUMBER |
Zwraca pozycję porządkową wiersza dla każdego wiersza w pogrupowanym zbiorze wynikowym |
NTILE |
Dzieli wiersze w każdej części zbioru wynikowego na określoną liczbę pozycji opierając się na wartościach |
Teraz, aby wyświetlić kontakty do wszystkich osób posortowanie ze względu na nazwisko z przypisanym mu numerem wiersza wystarczy uruchomić następujący skrypt.
SELECT Row_Number() OVER (ORDER BY
[LastName]) AS RowNumber
,[ContactID]
,[FirstName]
,[LastName]
FROM
[Person].[Contact]
Funkcja Row_Number() zwraca numer wiersza, ale wymagane jest jeszcze określenie według jakich parametrów numer ma być nadawany. W tym celu dodajemy klauzulę OVER z kolumnami, po których chcemy, aby numer był wyliczany. Użycie klauzul numerujących jest jak widać proste, trudniejszą sprawą może być wytłumaczenie, na jakiej zasadzie działają poszczególne funkcje pozycjonujące. Aby ułatwić sobie zadanie posłużę się przykładem.
SELECT Row_Number() OVER (ORDER BY [LastName]) AS
RowNumber
,Rank() OVER (ORDER BY
[LastName]) AS Rank
,Dense_Rank() OVER (ORDER BY
[LastName]) AS DenseRank
,NTile(3) OVER (ORDER BY
[LastName]) AS NTile_3
,NTile(4) OVER (ORDER BY
[LastName]) AS NTile_4
,[ContactID]
,[FirstName]
,[LastName]
FROM [Person].[Contact]
Pewnie niektórzy już myślą o dodaniu do powyższego kawałka kodu linii która wyglądać będzie mniej więcej następująco:
WHERE (RowNumber > 51)
AND (RowNumber < 100)
i dzięki temu będzie miał rozwiązany problem o nazwie „stronicowanie wyników”. Niestety tak jednak się nie stanie. Klauzula WHERE determinuje te wiersze, które mają zostać zaznaczone, a same funkcje pozycjonujące działają na gotowych wynikach. Aby uzyskać skrypt działający zgodnie z naszymi oczekiwaniami moglibyśmy powyższe zapytanie zrobić zapytaniem wewnętrznym i dopiero w zewnętrznym zapytaniu użyć klauzuli WHERE. Moglibyśmy, ale możemy również użyć wyrażeń tablicowych.
Wyrażenia tablicowe (CTE)
Wyrażenia tablicowe, jak wcześniej wspomniałem, są mechanizmem, którego nie było w poprzedniej wersji serwera. Umożliwiają definiowanie wirtualnych widoków, których można użyć w następnym skrypcie. Sama składnia polecenia CTE jest nie skomplikowana.
WITH nazwa_wirtualnej_tabeli
AS
(
zapytanie
)
Po wykonaniu tego zapytania mamy już dostępną wirtualną tabelę na której możemy swobodnie przeprowadzać operacje. Powróćmy więc do utworzenia skryptu, który wybierałby wiersze od 51 – 100 z tabeli Contact przy użyciu CTE.
WITH tblContact
AS
(
SELECT Row_Number() OVER
(ORDER BY [LastName]) AS RowNumber
,Rank() OVER
(ORDER BY [LastName]) AS Rank
,Dense_Rank()
OVER (ORDER BY [LastName]) AS DenseRank
,NTile(3) OVER
(ORDER BY [LastName]) AS NTile_3
,NTile(4) OVER
(ORDER BY [LastName]) AS NTile_4
,[ContactID]
,[FirstName]
,[LastName]
FROM
[AdventureWorks].[Person].[Contact]
)
SELECT *
FROM tblContact
WHERE (RowNumber > 50)
AND (RowNumber <= 100)
Najpierw utworzyliśmy wirtualną tabelę tblContact zawierającą wszystkie dane, które zostały zwrócone w wewnętrznym zapytaniu, łącznie z numerami wierszy wyliczonymi przez funkcje pozycjonujące. Teraz możemy wybrać z wirtualnej tabeli tą stronę danych, której potrzebujemy.
@STRONA@Rekurencja
Za pomocą wyrażeń tabelarycznych możemy zrealizować inną bardzo ciekawą funkcjonalność – rekurencje. Rekurencja we wcześniejszym serwerze nie była wspomagana w żaden sposób. Oczywiście nie ma przeszkody której nie dałoby się obejść. Programiści wypracowali parę sposobów na tworzenie własnych zapytań rekurencyjnych. Teraz sytuacja znacznie się poprawiła. Wprowadzenie wyrażeń tablicowych umożliwia złączenie wyników zapytania wewnętrznego z wyrażeniem CTE, które je zawiera. Ten wyraźny sygnał to zielone światło dla rekurencji.
Poniższy przykład wybiera z tabeli Employee pracowników i przypisanych im przełożonych.
WITH tblManager AS
(
SELECT EmployeeID
, LoginID
, ManagerID
, CAST(NULL as nvarchar(100)) AS MgrLogin
FROM HumanResources.Employee
WHERE ManagerID IS NULL
UNION ALL
SELECT emp.EmployeeID
, emp.LoginID
,
emp.ManagerID
, CAST(mgr.LoginID as nvarchar(100))
FROM HumanResources.Employee
emp INNER JOIN tblManager mgr
ON emp.ManagerID
= mgr.EmployeeID
)
SELECT *
FROM tblManager
ORDER BY ManagerID
Prześledźmy krok po kroku, co tak naprawdę dzieje się w powyższym zapytaniu. Pierwszy krok to zainicjalizowanie wartości początkowej. Wybieramy prezesa firmy. Czyli osobę która nie ma przełożonego. W tym celu wpisujemy następujący skrypt.
SELECT EmployeeID
, LoginID
, ManagerID
, CAST(NULL as
nvarchar(100)) AS MgrLogin
FROM
HumanResources.Employee
Zwraca on osobę o loginie adventure-works\ken0 i EmployeeID równym 109. Następnym krokiem jest złączenie wyników, które już znajdują się w tabeli CTE z Employee z tym, że złączenie dokonujemy po następujących polach:
emp.ManagerID =
mgr.EmployeeID
Oznacza to, że dla wszystkich pracowników, których mamy już
wstawionych w tabeli wirtualnej, wybieramy ich podwładnych z tabeli Employee.
A następnie dla wyników tego zapytania wybieramy pracowników, którzy będą z
kolei przełożonymi kolejnych itd.
W naszym konkretnym przypadku wybierzemy ludzi, których szefem jest pan z
EmployeeID,
a następnie dla nich wybierzemy ich podwładnych itd.
Podsumowanie
Z całej gamy usprawnień, jakie posiada najnowszy SQL Server 2005 w stosunku do poprzednika przedstawiłem te, które moim zdaniem są najbardziej użytecznie i których prędzej czy później będzie trzeba się nauczyć. O ile funkcje pozycjonujące to bardziej ciekawostka, która tylko lekko usprawnia pracę, to wprowadzenie wyrażeń tablicowych znacznie poszerza możliwości rozwoju własnych aplikacji. Jak zademonstrowałem na przykładzie prostego skryptu rekurencyjnego, którego implementacja za pomocą standardowych zabiegów byłaby skomplikowanym zagadnieniem.