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 q1 order by q2 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 q1 order by q2 odbywa się w sposób następujący: Oblicza się zależne złączenie q1 join q2 . Jeżeli zapytanie q1 zwróci bag{ r1, r2, ... }, to w wyniku całego zapytania otrzymujemy bag o budowie Analogicznie jeżeli q1 zwró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() Prac[0..*] NrP Stan[1..*] Zar[0..1] ZmieńZar(nowyZar) ZarNetto( ) Dział [0..*] NrD Nazwa Lokacja[1..*] BudżetRoczny() Kieruje[0..1] Szef PracujeW Zatrudnia[1..*]
Przykłady zapytań Podaj referencje do obiektów wszystkich pracowników posortowane według ich nazwisk: Prac order by Nazwisko 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: Prac order 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, (((Lokacja as x) order by x).x) group as lokacje)
Puste i wielowartościowe klucze Przedstawiona semantyka operatora order by posiada kilka niuansów. Pierwszy z nich dotyczy sytuacji, kiedy w zapytaniu q1 order by q2 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: Prac order by Zar 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ć: Prac order by max( 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 q1 order by q2 podzapytanie q2 zwraca wiele wartości. Np. zapytanie: Prac order by Stan 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.: Prac order by ((((Stan as z) order by z).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: Prac order 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: Prac order 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: (Prac order by Zar)[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] (Prac order by Zar)[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 q number as n 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: ((Prac order by Zar) number by r) where r >= 25 and r <= 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 (Prac order by Wiek). (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 (Prac order by Wiek). 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: select PracujeW, count(*), avg(Zar) from Prac group by PracujeW 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: select PracujeW, avg(Zar) from Prac group by PracujeW 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: select PracujeW, Nazwa, avg(Zar) from Prac, Dział where PracujeW = NrD group by PracujeW, 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: select PracujeW, count(*) from Prac group by PracujeW 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ł where count(Zatrudnia) > 50). (NrD, avg(Zatrudnia.Prac.Zar)) (Dział where count(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)) as d) . (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) as z) . (z, count(Prac where z Stan), count(Dział where z (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: select PracujeW, avg(Zar) from Prac group by PracujeW having count(*) > 50 można zapisać jako: SBQL: ((distinct(Prac.PracujeW) as p) join ((Prac where PracujeW = p) group as g) where count(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)) as w) . ((((5*w) as wiekDol, (5*w + 4) as wiekGor) 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.