300 likes | 426 Views
Obiektowe języki zapytań. Wykładowca : Kazimierz Subieta Polsko-Japońska Wyższa Szkoła Technik Komputerowych, Warszawa subieta@pjwstk.edu.pl Instytut Podstaw Informatyki PAN, Warszawa subieta@ipipan.waw.pl. Wykład 11: Operator sortowania order by Operator group by – czy potrzebny?.
E N D
Obiektowe języki zapytań Wykładowca: Kazimierz Subieta Polsko-Japońska Wyższa Szkoła Technik Komputerowych, Warszawa subieta@pjwstk.edu.pl Instytut Podstaw Informatyki PAN, Warszawa subieta@ipipan.waw.pl Wykład 11: Operator sortowania order by Operator group by – czy potrzebny?
Sortowanie w językach zapytań • Operator sortowania order by występuje w SQL i jest proponowany w innych językach takich jak ODMG OQL. • Operator ten umożliwia posortowanie wyniku według wybranego klucza. • Sortowanie jest bardzo ważną operacją w językach zapytań z następujących powodów: • Wyprowadzanie wyników wyszukiwania: wynik powinien być posortowany dla zwiększenia jego czytelności i umożliwienia podjęcia szybkiej decyzji. Np. kierownikowi działu może zależeć na posortowaniu wyniku wyszukiwania według malejących zarobków lub według rosnącego wieku. • Sortowanie zwiększa moc języka zapytań. Niektóre zapytania nie mogą być zadane bez sortowania, np. zapytanie „podaj 50-ciu najlepiej zarabiających pracowników”. Zapytania takie są zwane „zakresowymi” (range queries).
Sortowanie w relacyjnej i obiektowej BD • W modelu relacyjnym sortowanie jest uważane za operator pomocniczy służący wyłącznie do wyprowadzania wyniku. • Nie jest objęte modelem relacyjnym, ponieważ relacje są zbiorami. • Operator jest niewyrażalny w algebrze relacyjnej i innych formalizmach uważanych za podstawę semantyczną języków zapytań. • Niektóre implementacje SQL wprowadziły jednak zapytania zakresowe, dzięki którym można wydobyć z tabeli relacyjnej i-ty wiersz lub wiersze od i-tego do j-tego. • W obiektowych bazach danych sekwencje uzyskały status kolekcji zapisywalnych w bazie danych oraz zwracanych przez zapytania, w związku z czym operator sortowania stał się naturalny i konieczny. • Podobnie w XML: plik XML ustala porządek pod-dokumentów i przez to użytkownicy mogą wykorzystywać ten porządek dla zapisu informacji. Wymagania dla języków zapytań dla XML explicite żądają operatora ustalającego porządek w wyprowadzanym rezultacie.
Definicja operatora order by w SBQL (1) • Operator order by został zaimplementowany w SBQL systemu Loqis. • Niżej objaśnimy (nieco uogólnioną) ideę tego operatora. • Operator ten będzie należał do kategorii nie-algebraicznych. • Przyjmijmy następującą składnię (rozszerzającą podane wcześniej reguły składniowe SBQL): • Semantyka zapytania q1order byq2 • Przedmiotem sortowania jest bag lub sekwencja zwrócona przez q1. • q2 wyznacza klucz sortowania. • Wynikiem jest sekwencja elementów zwróconych przez q1, uporządkowana zgodnie z kluczem q2. Zapytanie q2 wyznacza strukturę (ogólnie: bag struktur) struct{v1, v2, ..., vk}, gdzie vi Di, zaś Di V. • Zbiory Di są dziedzinami, w których istnieje naturalny porządek liniowy, np. zbiór liczb całkowitych, zbiór stringów, zbiór dat, itd. zapytanie ::= zapytanie order by zapytanie
Definicja operatora order by w SBQL (2) • Przetwarzanie zapytania q1order byq2 odbywa się w sposób następujący: • Oblicza się zależne złączenie q1joinq2 . Jeżeli zapytanie q1 zwróci bag{ r1, r2, ... }, to w wyniku całego zapytania otrzymujemy bag o budowie • Analogicznie jeżeli q1zwróci sekwencję. • Jeżeli któreś z vij jest referencją, to wykonuje się automatycznie dereferencję. • Jeżeli wartość vij nie należy do dziedziny Di V, w której obowiązuje naturalny porządek liniowy, wówczas sytuacja ta jest uważana za błędną. • Dokonuje się sortowania powstałego bagu, które zamienia go na sekwencję. Sortowanie następuje według pierwszego klucza, w ramach identycznych wartości pierwszego klucza – według drugiego klucza, itd. • Po sortowaniu otrzymamy sekwencję: powstałą z poprzedniego bagu poprzez jego posortowanie; • Końcowy rezultat uzyskujemy po rzutowaniu powstałej sekwencji na rezultaty q1. Ostateczny rezultat: sequence{ s1, s2, ...}. bag{struct{ r1, v11, v21, ..., vk1}, struct{ r2, v12, v22, ..., vk2},...} sequence{ struct{ s1, vs11, vs21, ..., vsk1 }, struct{ s2, vs12, vs22, ..., vsk2 }, ...}
Przykłady (schemat bazy danych) Osoba[0..*] Nazwisko RokUr Wiek() PracujeW Zatrudnia[1..*] Prac[0..*] NrP Stan[1..*] Zar[0..1] ZmieńZar(nowyZar) ZarNetto( ) Dział [0..*] NrD Nazwa Lokacja[1..*] BudżetRoczny() Kieruje[0..1] Szef
Przykłady zapytań • Podaj referencje do obiektów wszystkich pracowników posortowane według ich nazwisk: Pracorder byNazwisko • Wynikiem będzie sequence{iPrac1, iPrac2, iPrac3...} identyfikatorów obiektów Prac, posortowanych według nazwisk. • Zwrócimy uwagę, że wykorzystaliśmy dziedziczenie. • Podaj identyfikatory wszystkich pracowników posortowane według ich wieku, zaś w ramach tego samego wieku – według nazwisk: Pracorder by (Wiek, Nazwisko) • Podaj działy posortowane według liczby pracowników w działach oraz według nazwisk ich szefów; zwróć nazwę działu oraz jego lokacje posortowane alfabetycznie. (Działorder by (count(Zatrudnia), (Szef.Prac.Nazwisko))) . (Nazwa, (((Lokacjaasx) order byx).x) group aslokacje)
Puste i wielowartościowe klucze • Przedstawiona semantyka operatora order by posiada kilka niuansów. Pierwszy z nich dotyczy sytuacji, kiedy w zapytaniu q1order byq2 podzapytanie q2 zwraca pusty wynik. • Np. w obiektach Prac podobiekt Zar może nie wystąpić. • Wówczas zgodnie z przedstawiona semantyką opartą na zależnym złączeniu zapytanie: Pracorder byZar pominie tych pracowników, dla których Zar nie występuje. • Aby uwzględnić tych pracowników w dostarczonym wyniku, wówczas należy ustalić dla nich klucz sortowania, np. przyjmując, że dla pracowników nie posiadających informacji o zarobku klucz wynosi zero. • Odpowiednie zapytanie (jedno z wielu) może mieć postać: Pracorder bymax( bag(0, Zar) ) • Funkcja max zwróci 0 dla pracowników nie posiadających zarobku i aktualny zarobek dla pozostałych.
Klucze wielowartościowe • Podobna sytuacja następuje wtedy, gdy w zapytaniu q1order byq2 podzapytanie q2 zwraca wiele wartości. • Np. zapytanie: Pracorder byStan • Zgodnie z przedstawioną semantyką opartą na zależnym złączeniu, identyfikatory pracowników posiadających więcej niż jedno stanowisko będą w wyniku powielone tyle razy, ile dany pracownik ma stanowisk. • Następnie te identyfikatory zostaną posortowane według poszczególnych stanowisk. • Taka interpretacja jest logiczna i konsekwentna. • Oczywiście, może on użyć innych środków aby, dla uniknięcia powtórzeń, ustalić takie q2, które zwróci dla pracownika dokładnie jedno stanowisko, np.: Pracorder by ((((Stan asz) order byz).z) [1]) • W tym przypadku programista uporządkował stanowiska pracownika alfabetycznie i wybrał pierwsze stanowisko jako klucz sortowania.
Sortowanie w kolejności rosnącej i malejącej • W SQL i OQL do tego celu służą specjalne słowa kluczowe ASC (ascending) i DESC (descending) umieszczone za danym kluczem sortowania (pierwsze z nich jest domyślne). • Obydwie opcje oznaczają funkcje działające na kluczach sortowania. • ASC jest funkcją identyczności (zwraca swój argument). • DESC jest funkcją zwracającą element będący dopełnieniem swojego argumentu zgodnym z określonym porządkiem liniowym. • Przykładowo, dla liczby całkowitej X dopełnieniem jest –X, lub 1000000000 – X, gdzie jest 1000000000 jest (przykładowym) maksymalnym kluczem. • Podobnie, dopełnieniem stringu może być string, w którym każdy znak w ASCII o kodzie k zostaje zastąpiony przez znak o kodzie 256 - k. • Przykład: podaj identyfikatory wszystkich pracowników posortowane malejąco według wieku i rosnąco według ich nazwisk: Pracorder by (DESC(Wiek), ASC(Nazwisko))
Uwzględnienie porządku alfabetycznego w językach narodowych • Operator order by języka SQL wzbudził w swoim czasie kontrowersje dotyczące sposobu sortowania stringów. • Twórcy tego operatora oparli się na kodzie ASCII, w którym porządek liter odpowiada alfabetowi angielskiemu. • Reguły porządku alfabetycznego są jednak inne np. dla niemieckiego, francuskiego lub polskiego, a ponadto alfabety różnią się zestawem znaków. • W efekcie, dostawcy systemów relacyjnych poprawili tę klauzulę w taki sposób, aby uwzględnić narodowe reguły porządku alfabetycznego. • Powyższą własność można rozwiązać dwojako: (1) Podczas instalacji systemu ustala się, z jakim językiem narodowym ma on pracować, i dla tego języka wewnętrznie ustala się procedurę porównania dwóch stringów będącą podstawą procedury sortowania. Wariant ten uniemożliwia pracę systemu z kilkoma językami jednocześnie. (2) Zdefiniować i zaimplementować rodzinę funkcji, takich jak francuski, niemiecki, polski, węgierski, która dla danego stringu zwróci wartość kompatybilną z porządkiem tego stringu w danym języku narodowym. Rozwiązanie to jest oczywiście możliwe tylko wtedy, gdy system kodowania znaków nie jest zależny od narodowego języka (np. jest to Unicode).
Przykład • Podaj identyfikatory wszystkich pracowników posortowane rosnąco według nazw działów w języku angielskim i malejąco według nazwisk w języku węgierskim: Pracorder by (angielski(PracujeW.Dział.Nazwa), DESC( węgierski(Nazwisko)))
Zapytania zakresowe • Powinna istnieć możliwość wyboru i-tego elementu sekwencji, ostatniego elementu sekwencji, itd. W najprostszym przypadku można zastosować następującą składnię: zapytanie ::= zapytanie [liczba naturalna] zapytanie ::= zapytanie [liczba naturalna .. liczba naturalna] • Podaj 50-ciu najmniej zarabiających pracowników: (Pracorder byZar)[1..50] • W powyższym zapytaniu pierwszy element otrzymuje numer 1. • Język C i jego pochodne (C++, Java), CORBA, OQL, itd. wprowadzają numerację, w której pierwszy element oznacza się numerem 0. W C/C++ było to umotywowane, ale uzasadnienie dla tego atawizmu znikło. • Bardziej ogólna forma: zapytanie ::= zapytanie [zapytanie] zapytanie ::= zapytanie [zapytanie .. zapytanie] (Pracorder byZar)[x..y]
Zapytania zakresowe w systemie Loqis • Jeszcze inną opcją jest rozwiązanie przyjęte w systemie Loqis, gdzie założono specjalny tryb powoływania pomocniczej nazwy: zapytanie ::= zapytanie number as nazwa • Semantyka tej opcji przypomina operator as. Oznacza, że jeżeli q zwraca sequence{r1, r2, r3, ...}, to qnumber asn zwraca sequence{ struct{r1, n(1)}, struct{r2, n(2)}, struct{r3, n(3)}, ...} • Wynik ma dodatkowo binder o nazwie n przechowujący kolejną liczbę naturalną, To umożliwia dowolne warunki na tej liczbie, np.: • Podaj pracowników, którzy w rankingu zarobków zajmują miejsca od 25 do 50: ((Pracorder byZar) number byr) wherer >= 25 andr <= 50 • Jest możliwa dowolna kombinacja tej opcji z innymi opcjami języka zapytań. • Podane rozwiązanie wydaje się najbardziej uniwersalne.
Uporządkowanie a optymalizacja zapytań • Obecność sekwencji w strukturach danych i operacje na sekwencjach w języku zapytań znacznie obniżają potencjał dla optymalizacji zapytań. • W większości metod optymalizacyjnych, w tym w metodach opartych na przepisywaniu (rewriting) i metodach opartych na indeksach, zakłada się dowolny porządek elementów zwracanych przez zapytanie. • Jeżeli ten porządek jest wymuszony, to praktycznie nie pozostaje nic innego prócz sekwencyjnego przetwarzania element po elemencie. • Z tego względu nie staraliśmy się wprowadzać sekwencji na poziomie struktur danych (modele M0-M3), aby nie sugerować, że jest to opcja łatwa i bezbolesna. • Z tego również względu semantyka języka powinna być konstruowana w taki sposób, aby rezygnować z trzymania porządku tak szybko, jak to jest możliwe. • W większości, należy dążyć do tego, aby operator sortowania był w zapytaniu ostatni lub prawie ostatni.
Które operatory mają zachowywać porządek? • Dla niektórych operatorów zachowywanie porządku jest naturalne. • Operator where działający na sekwencji nie musi zmieniać kolejności wynikowych elementów, ale w takim przypadku mogą pojawić się problemy z optymalizacją np. poprzez indeksy. • Dla operatora kropki w zapytaniu (Pracorder byWiek). (Nazwisko, Zarobek) wynikowa lista powinna pozostawić porządek ustalony przez order by w poprzednim pod-zapytaniu; • programista byłby prawdopodobnie zaskoczony, gdyby operator kropki nie utrzymał tego porządku. • Ta własność nie jest jednak oczywista w przypadku zapytania (Pracorder byWiek). Stan gdyż Stan jest atrybutem wielowartościowym w którym porządek nie obowiązuje. • W tej sytuacji należałoby przyjąć, że wynikowa lista identyfikatorów stanowisk pozostaje nieuporządkowana (operator order by jest ignorowany). • Generalnie, dla dowolnego operatora należałoby przyjąć regułę dotyczącą typu kolekcji zwracanego wyniku oraz porządku elementów. Reguły te zwiększą złożoność systemu typów.
Operator group by - czy potrzebny? • Operator group by występuje w SQL i jest proponowany w innych językach takich jak ODMG OQL. W językach relacyjnych okazał się on użyteczny do formułowania niektórych zapytań, szczególnie takich, które wymagały użycia funkcji agregowanych; np. • Mamy tabele Prac( NrP, Nazwisko, Stan, Zar, PracujeW) Dział(NrD, Nazwa, Lokacja, Szef ) • Dla każdego działu podaj jego numer, liczbę pracowników oraz średnią zarobków: SQL: selectPracujeW, count(*), avg(Zar) fromPracgroup byPracujeW • Semantyka tej konstrukcji jest prosta: tabelę pracowników dzieli się na grupy w których atrybut PracujeW przyjmuje tę samą wartość; następnie dla każdej takiej grupy oblicza się wyrażenie znajdujące się w klauzuli select.
Istota operatora group by • Wbrew twierdzeniom spotykanym w niektórych podręcznikach, operator group by w ogólnym przypadku jest niewyrażalny przez inne operatory języka SQL, i tym bardziej jest niewyrażalny w algebrze relacji. • Istotą tego operatora jest to, że na chwilę tabelę relacyjną traktuje się tak, jakby miała trzy poziomy hierarchii: • poziom tabeli, • poziom grup w tej tabeli, • poziom krotek. • Jest to (chwilowe) semantyczne odstępstwo od założeń modelu relacyjnego. • Jeżeli dany formalizm posiada wyłącznie takie operatory, których wejściem są „płaskie” relacje i wyjściem jest „płaska” relacja, to nie jest on w stanie wyrazić semantyki operatora group by. • Powstały specjalne algebry uwzględniające ten operator. • Niestety, są to propozycje wadliwe od strony koncepcji, wobec czego nie będziemy ich dyskutować.
Konsekwencje wprowadzenia operatora group by • Wraz z pojawieniem się operatora group by pojawiła się konieczność wprowadzenia możliwości selekcji niektórych grup wyodrębnionych przez ten operator. • Do tego celu służy specjalna klauzula having, która zawiera predykat operujący na grupach; tylko grupy, dla który ten predykat zwróci true, są uwzględniane przy obliczaniu klauzuli select. Np.: • Dla każdego działu zatrudniającego więcej niż 50 pracowników podaj jego numer oraz średnią zarobków: SQL: selectPracujeW, avg(Zar) fromPrac group byPracujeW having count(*) > 50
Dalsze konsekwencje • Operator grupowania nie jest już tak oczywisty w przypadku złączeń, gdy w klauzuli from znajduje się więcej niż jedna nazwa tabeli. • Dorzucając do powyższego zapytania wymaganie, aby zwróciło ono także nazwę działu, otrzymamy bardziej rozbudowaną formę: • Dla każdego działu zatrudniającego więcej niż 50 pracowników podaj jego numer, nazwę oraz średnią zarobków: SQL: selectPracujeW, Nazwa, avg(Zar) fromPrac, Dział where PracujeW = NrD group byPracujeW, Nazwa having count(*) > 50 • SQL wymaga, aby dowolny atrybut występujący w klauzuli select i nie objęty funkcją agregowaną był wymieniony w klauzuli group by. • Istnieją dalsze niuanse syntaktyczne i semantyczne klauzuli group by oraz klauzul select i having, które są z nią związane. • Wbrew pozornej oczywistości, operator ten okazał się z pragmatycznego punktu widzenia dość trudny dla użytkowników i błędogenny.
Group by w ODMG OQL • Twórcy standardu ODMG przenieśli wzorzec syntaktyczny i semantyczny operatora group by z języka SQL na OQL oraz próbowali uogólnić go dla wprowadzonego przez nich modelu obiektowego. • Zrobili to jednak w sposób nieudolny i mało precyzyjny, przez co semantyka i pragmatyka tej konstrukcji jest niejasna dla ogólnego przypadku i na pewno niekompletna. • Wydaje się, że wprowadzenie tej klauzuli do OQL było motywowane względami koniunkturalnymi, mianowicie lansowaną (nieprawdziwą) tezą, że OQL jest „niewielkim rozszerzeniem” SQL. • Powielanie niekompletnych wyjaśnień i przykładów zawartych w tym standardzie jest pozbawione sensu.
Krytyka opcji group by • Ma historię co najmniej 20-letnią. • Opcja ta była silnie przywiązana do mechanizmu implementacyjnego, w związku z tym optymalizacja zapytań z tą opcja była (i jest do dzisiaj) problematyczna, mimo wielu prac na ten temat. • Opcja ta nie jest w pełni ortogonalna z innymi operatorami języka SQL, w szczególności, jeżeli zapytanie SQL zawiera group by, to zmienia się semantyka klauzuli select. • Podobny brak ortogonalności dotyczy związania z tą opcją funkcji agregowanych. Powoduje to ich podwójną semantykę (przy tej samej składni): funkcje agregowane bez opcji działają na całych tabelach (ewentualnie na ich wyselekcjonowanych fragmentach) , zaś w połączeniu z group by – na grupach. • Obecność dwóch różnych semantycznie klauzul warunku (where i having) jest co najmniej nieelegancka i oznacza redundancję koncepcyjną. • Opcja group by jest również krytykowana za złamanie zasady koncepcyjnej kontynuacji, która żąda małych zmian zapytania w przypadku małych zmian w jego nieformalnym sformułowaniu.
Semantyczne rafy opcji group by • Wada koncepcyjna: jeżeli pewna grupa ma zero elementów, wówczas nie uczestniczy w przetwarzaniu, co prowadzi do błędnych wyników. • Przykładowo, programista chcący policzyć średnią liczbę pracowników w działach użyje zdania: SQL: selectPracujeW, count(*) fromPracgroup byPracujeW następnie przetworzy wynikową tabelę celem obliczenia średniej. • Niestety, ta tabela nie zawiera informacji o działach z zerową liczbą pracowników, w związku z czym wynik będzie błędny; co więcej błąd ten pojawi się bez ostrzeżenia. • Inna wada: Opcja ta grupuje wartości NULL w ramach jednej grupy, co jest sprzeczne z założeniem twórców SQL, że wartości NULL oznaczają wartości nieznane (zatem być może różne) lub nierelewantne. • Np. jeżeli informacja PracujeW przyjęłaby wartość NULL dla niektórych pracowników, to skutek powyższego zdania SQL byłby taki, jakby wszyscy oni pracowali w jednym dziale, zaś liczba grup byłaby o jeden większa od liczby działów. • Średnia liczba pracowników byłaby oczywiście wyliczona błędnie.
Opcja group by jest zbędna! • Języka SQL nie zmienimy, więc powyższa krytyka jest już historyczna. • Opcja ta przeszła do SQL-99, ale szkoda czasu na dyskusję i wyjaśnianie zastosowanych tam rozwiązań, ponieważ szansa, że będą one implementowalne (w spójny sposób) jest bliska zeru. • Możemy natomiast zastanawiać się, czy i w jaki sposób wprowadzić opcję group by do projektowanego przez nas języka, aby pozbyć się wymienionych wyżej wad. Konkluzje są następujące: • W obiektowych językach zapytań operator group by jest zbędny. • Niesposób wskazać racjonalny powód dla jego wprowadzenia. • Można go bez trudu zastąpić przez operator kropki, zależnego złączenia i inne operatory. • Znika przyczyna będąca powodem jego wprowadzenia, mianowicie, chwilowe traktowanie dwu-poziomowej struktury danych (tabeli) jako struktury trzy-poziomowej. • Języki obiektowe nie ograniczają liczby poziomów hierarchii struktur danych i wyników zwracanych przez zapytania, zatem grupowanie można zastąpić przez operatory obsługujące takie hierarchiczne struktury.
Wyżej podane zapytania w SBQL Dział . (NrD, count(Zatrudnia), avg(Zatrudnia.Prac.Zar)) (Działwherecount(Zatrudnia) > 50). (NrD, avg(Zatrudnia.Prac.Zar)) (Działwherecount(Zatrudnia) > 50). (NrD, Nazwa, avg(Zatrudnia.Prac.Zar)) • Podane zapytania wyglądają naturalnie, zaś zasada koncepcyjnej kontynuacji jest w pełni zachowana (porównaj dwa ostatnie zapytania). • Nie występuje też wspomniana rafa semantyczna dotycząca pustych grup: jeżeli któryś dział nie będzie miał pracowników, to count(Zatrudnia) zwróci dla niego 0, co jest zgodne z oczekiwaniem programisty. • Druga rafa semantyczna związana z wartościami zerowymi nie może wystąpić, ponieważ modele składu i SBQL w ogóle nie wprowadzają wartości zerowych. • Ponieważ nie ma opcji group by, niepotrzebne są też dedykowane dla tej opcji metody optymalizacyjne. • Niepotrzebne są też oczywiście specjalne warunki, które w opcji group by znajdują się w klauzuli having.
Recepta na zapytanie w SBQL, które w SQL wymagałoby group by • Ustalamy dyskryminator grup, tj. takie podzapytanie, którego wynik będzie podstawą podziału pewnych zasobów danych na grupy. • Nazywamy dyskryminator pewną nazwą d poprzez operator as. • Dyskryminator wraz z nazwą d obejmujemy nawiasami i po operatorze kropki formułujemy drugie podzapytanie, które dla danej wartości d ustala odpowiadającą jej grupę. Krok ten można pominąć. • Stosujemy dowolne operatory działających na d lub wyodrębnionych w poprzednim kroku grupach celem uzyskania ostatecznego wyniku. • Przykład: Dla każdego działu zatrudniającego programistów podaj jego numer, nazwę i średnią zarobków: (((Działwhere „programista” (Zatrudnia.Prac.Stan)) asd) . (d.NrD, d.Nazwa, avg(d.Zatrudnia.Prac.Zar))
Przykłady wykorzystujące podaną receptę (1) • Dla każdego stanowiska podaj liczbę pracowników, którzy je posiadają oraz liczbę działów, w których ci pracownicy są zatrudnieni (atrybut Stan jest wielowartościowy): (distinct(Prac.Stan) asz) .(z, count(PracwherezStan), count(Działwherez (Zatrudnia.Prac.Stan)) • Pierwsza linia ustala wszystkie stanowiska, usuwa duplikaty i ten dyskryminator nazywa z. Druga i trzecia linia zawiera przetwarzanie tego z. • Recepta ta jest również dobra dla systemów relacyjnych. Np. zdanie SQL: selectPracujeW, avg(Zar) fromPrac group byPracujeW having count(*) > 50 można zapisać jako: SBQL: ((distinct(Prac.PracujeW) asp) join ((PracwherePracujeW = p) group as g) wherecount(g) > 50). (p, avg(g.Zar))
Przykłady wykorzystujące podaną receptę (2) • Dla grup wiekowych pracowników .., 15-19, 20-24, 25-29, .... podaj średni zarobek oraz różnicę pomiędzy maksymalnym i minimalnym zarobkiem; pomiń grupy wiekowe, w których nie ma żadnego pracownika: (distinct(Prac.integerOf(Wiek/5)) asw) . ((((5*w) aswiekDol, (5*w + 4) aswiekGor) join (Prac where Wiek wiekDol and Wiek wiekGor)group as g). (wiekDol, wiekGor, avg(g.Zar), max(g.Zar) – min(g.Zar))) • Pierwsza linia dzieli wiek pracowników przez 5, bierze od tego część całkowitą, usuwa duplikaty i ten dyskryminator nazywa w (nie uwzględnia grup wiekowych, w których nie ma żadnego pracownika). • Druga linia ustala dolny i górny wiek dla każdej grupy. • Trzecia linia ustala grupy referencji do obiektów pracowników znajdujących się w tym przedziale wiekowym i każdą taką grupę nazywa g. • Złączenie czyni nazwy wiekDol, wiekGor oraz g widocznymi dla dalszej części zapytania. Ostatnia linia dokonuje obliczeń wyniku. • Sformułowanie tego zapytania w SQL okazało się niezłą łamigłówką.
Związek pomiędzy group by i group as • Poprzedni przykład pokazuje użycie omówionego wcześniej operatora group as. • Pojawił się on jako skutek drobiazgowej analizy semantyki i pragmatyki przykładów na grupowanie. • Okazało się jednak, że w większości tego rodzaju przykładów: • nie jest on niezbędny, • jest on niezbędny w innych kontekstach, np. wtedy, gdy chcemy wynik zapytania zbudować w postaci hierarchii nazwanych, zagnieżdżonych pod-wyników. • Z tego powodu operatory group as oraz group by nie mają ze sobą istotnego związku, mimo pewnych zależności genealogicznych.
Podsumowanie • Operator sortowania order by jest konieczny do wyprowadzania wyniku oraz do sformułowania niektórych zapytań, wobec czego powinien być zaimplementowany. • Jest to jedno z wymagań dla języków zapytań dla XML • Respektowanie porządku, w którym przetwarzane są kolekcje, zmniejsza potencjał dla optymalizacji zapytań. • Zatem sekwencje i operator porządkowania są z tego względu niekorzystne i powinno być stosowane tylko tam, gdzie jest niezbędne. • Operator group by w obiektowych językach zapytań jest tworem redundantnym. • Jeżeli przy tym przenosi wady z języka SQL, to powinien być również traktowany jako szkodliwy. • Implementacja tego operatora jest więc pozbawiona cienia sensu.