• Nie Znaleziono Wyników

Rozdział 3. Język zapytań

3.6 Składnia Select-From

Poniżej podano rdzeń frazy Select-From, który wzorowano na SQL, CQL i MDX:

SELECT definicja_fabryki_danych

[ FROM definicja_źródła, definicja_źródła,.... ] [ WHERE wyraŜenie_where ]

[ GROUP BY identyfikatorAtrybutu, identyfikatorAtrybutu,.... ] [ HAVING wyraŜenie_having ]

Podczas konstruowania składni Select-From dla języka StreamAPAS wzięto pod uwagę fakt, że zestaw operacji służący do analizy danych jest bogaty i stale się powiększa. Istotne jest, aby projektant aplikacji mógł dołączyć brakujące funkcjonalności do systemu. Z myślą o osiągnięciu tego celu połączono elementy obiektowości oraz składnię języka SQL. Operatory strumieniowych baz danych oraz relacyjnych baz danych można podzielić na cztery grupy:

1) operatory na zbiorach, 2) operatory okien, 3) operatory agregacji, 4) i pozostałe.

W oparciu o powyższy podział operatorów prześledzimy kolejne fragmenty frazy Select-From w języku StreamAPAS.

3.6.1 Operacje na zbiorach

Analizując składnię SQL najmniej konsekwentny jest sposób wyrażania operatorów na zbiorach. Operator eliminujący duplikaty jest definiowany wewnątrz klauzuli select.

SELECT [ALL | DISTINCT | DISTINCTROW ]...

Operator sumy zbiorów jest definiowany przy użyciu składni, która dodatkowo udostępnia operator eliminacji duplikatów:

SELECT ...

Z kolei operatory różnicy zbiorów lub części wspólnej zbiorów są zdefiniowane przez składnię:

SELECT ... MINUS SELECT ... SELECT ...

INTERSECT SELECT ...

Podsumowując gramatyka SQL posiada nadmiarowość, jest niekonsekwentna, ponadto dodanie do systemu nowego operatora reprezentującego inną operację na zbiorach zawsze wiąże się z rozszerzeniem gramatyki. Takie cechy nie pozwalają na to, aby swobodnie budować własne operatory należące do tej grupy i automatycznie dołączać je do systemu.

W języku StreamAPAS zdecydowano się wyrażać takie operatory przy użyciu metod statycznych. Metody operują na fabrykach danych oraz dodatkowych parametrach oraz zwracają jako wynik nową fabrykę danych. Przykładowo chcąc zbudować strumień Out będący sumą dwóch strumieni o nazwach I i II, należy użyć wyrażenie:

Out{Set.union(I{}, II{})}

Korzystamy tutaj z metody statycznej union zdefiniowanej w klasie Set. Argumentami metody są dwie fabryki strumieni o identycznych schematach krotek. Wynikiem jest fabryka generująca strumień będący sumą zgodnie z definicją przedstawioną w punkcie 2.8.7. Zastosowana składnia języków obiektowych wdrożona do języka zapytań wyróżnia się dwiema zaletami. Metody statyczne umożliwiają wyrażanie dowolnej funkcji, co otwiera język zapytań na dodawanie nowych operatorów. Zastosowane elementów obiektowości powala grupować operatory ze względu na klasy. Korzystając z tej cechy można uporządkować operatory tematycznie, co ułatwia posługiwanie się bibliotekami operatorów oraz poprawia czytelność kodu.

Do grupy operatorów realizujących obliczenia na zbiorach zaliczamy także wyliczanie różnicy zbiorów oraz wyznaczanie elementów unikalnych. Podobnie te operatory są udostępniane poprzez metody statyczne. Przykładowo operator różnicy ma składnię:

Argumentami aS i aR są fabryki strumieni. Argument aKeyS definiuje klucz składający się z węzłów krotki dla strumienia aS. Argument aKeyR definiuje klucz dla strumienia aR. Oba atrybuty to tekst będący listą identyfikatorów węzłów drzewa atrybutów odseparowanych znakiem ‘,’: <identyfikatorAtrybutu1>

,<identyfikatorAtrybutu2>, …, <identyfikatorAtrybutuN>. Identyfikator to lista nazw

węzłów na ścieżce od korzenia do wskazywanego węzła oddzielona znakiem ‘.’, przy czym pominięty jest pierwszy węzeł reprezentujący nazwę fabryki danych. Zgodnie z definicją operatora przedstawioną w punkcie 2.8.6, krotka t pobrana ze strumienia

aR usuwa krotkę ze strumienia aS, jeżeli jej wartości węzłów aKeyS są identyczne

do wartości węzłów aKeyR należących do krotki t. Przykładowe użycie tej metody demonstruje poniższy przykład:

Out{Set.minus(I{}, II{}, "val", "val")}

Operator wyliczający elementy unikalne jest udostępniany poprzez metodę: Set.distinct(DatasetI aD, String aKey)

Argument aD to wejściowa fabryka strumieni. Argument aKey definiuje węzły drzewa atrybutów względem których krotki wynikowe mają być unikalne. Poniżej podano przykładowe wywołanie tej metody:

P0{Set.distinct(I{}, "val")}

3.6.2 Klauzula select

W relacyjnych bazach danych do utworzenia tabeli służy język DDL. Wraz z pojawieniem się nowych metod składowania danych, składnia DDL jest rozszerzana. Zastosowanie takiego podejścia do definiowania fabryk danych jest niewłaściwe. Nowy typ fabryki danych może utworzyć projektant i dołączyć go do systemu. W takim przypadku składnia języka zapytań musi być na tyle bogata, aby umożliwiała zarządzanie dowolnym typem obiektów wprowadzonym przez użytkownika. Dlatego instancja fabryki danych jest reprezentowana przez obiekt konfigurowany poprzez wywoływanie jego metod. Zaletą tego podejścia jest także czytelność kodu zapytania, ponieważ w jednym miejscu definiowana jest struktura danych oraz proces generacji danych. Składnia elementu definicja_fabryki_danych jest następująca:

<definicja_fabryki_danych> ::= <nazwa_kolekcji_danych>{ <element_definicji>, <element_definicji>,… }

<element_definicji> ::=<drzewo_atrybutów> | <metoda(listaArgumentów)>

Utworzona fabryka danych jest dostępna poprzez etykietę:

nazwa_kolekcji_danych. Wyrażenie objęte w klamrach {…} jest związane z kontekstem

obiektu fabryki danych oraz reprezentuje korzeń struktury danych. Sprawia to, że wewnątrz tego kontekstu można swobodnie korzystać z metod udostępnionych przez fabrykę danych oraz definiować schemat strumienia. Schemat danych jest definiowany poprzez drzewo atrybutów, a parametry są ustawiane poprzez metody fabryki. Obecnie system StramAPAS udostępnia wyłącznie operatory strumieniowe, dlatego wynikiem zapytania zdefiniowanego przez frazę Select-From jest zawsze strumień. Gdy w kolejnej wersji dołączone zostaną tabele relacyjne, wtedy rozważane jest dołączenie etykiety typu fabryki danych zgodnie z poniższym wzorem:

<definicja_fabryki_danych> ::=

<nazwa_kolekcji_danych>:<typ_fabryki_danych>{…} Obiek fabryki strumieni udostępnia dwie metody:

addPK(Node aNode) –do listy atrybutów tworzących klucz główny dodaje kolejny węzeł lub węzły drzewa atrybutów.

clearPK() – usuwa atrybuty tworzące klucz główny.

Poniżej przedstawiono przykładową definicję strumienia tmp z kluczem głównym na atrybucie tmp.name zbudowanym w oparciu o strumień I przedstawiony na rys. 3.2:

select tmp{$I.name, sum = $I.place.x + $I.place.y, $I.place[true], addPK(name)}

from I{}

W wyniku przetworzenia powyższego wyrażenia powstaje strumień, którego schemat jest opisany przez drzewo atrybutów o korzeniu tmp. Jego atrybutami na pierwszym poziomie są węzły: name, sum i place. Dodatkowo węzeł place posiada pod-węzły: x i y. Na koniec, przy użyciu metody addPK zdefiniowane są atrybuty tworzące klucz główny. Kolejność członów jest istotna, ponieważ wyrażenie zawiera człony definiujące schemat strumienia oraz metody, które korzystają z węzłów tego schematu. Poniższe wyrażenie jest niepoprawne, ponieważ metoda addPK korzysta z węzła name, który jest jeszcze niezdefiniowany:

select tmp{ addPK(name), $I.name, sum = $I.place.x + $I.place.y, $I.place[true]}

Przyjmijmy, że w przyszłości funkcjonalność fabryki danych strumieni ulegnie rozszerzeniu. Na poziomie języka zapytań objawi się to poprzez udostępnienie nowych metod. Ujawnia się tutaj zaleta zastosowania elementów obiektowości, ponieważ nie ma już konieczności rozszerzania składni języka tak jak ma to miejsce dla DDL.

3.6.3 Klauzula from

Zestaw źródeł danych potrzebny do wyznaczenia zapytania jest tworzony w oparciu o analizę klauzul: where, having, groupby, dlatego nie ma konieczności deklarowania listy źródeł, na których będą realizowane obliczenia w klauzuli: from. Klauzula from służy definiowaniu strumieni pomocniczych na potrzebę bieżącego zapytania. Poniżej podano składnię klauzuli from:

<definicja_źródła> ::= (<specyfikacja_zapytania>) | <nazwa_kolekcji_danych>{}

Aby uniknąć niejednoznaczności w interpretacji, składnia języka nakazuje umieszczanie definicji pod-zapytania wewnątrz nawiasów ‘()’. Poniżej podano przykład, gdzie pod-zapytanie tworzy strumień tmp2.

select tmp1{$tmp2.id, $tmp2.valF}

from (select tmp2{$I.id, $I.place[true]} where $I.place.x < 1000)

Bez użycia nawiasów treść zapytania jest niejednoznaczna, ponieważ klauzula where mogłaby być interpretowana: jako fragment pod-zapytania lub jako fragment głównego zapytania co ilustrują odpowiednio poniższe przypadki 1) i 2): 1)

select tmp1{$tmp2.id, $tmp2.valF}

from (select tmp2{$I.id, $I.place[true]} where $I.place.x < 1000)

2)

select tmp1{$tmp2.id, $tmp2.valF}

from (select tmp2{$I.id, $I.place[true]}) where $I.place.x < 1000

3.6.4 Klauzula where

Klauzula where składa się z wyrażeń definiujących warunki filtracji, łączenia strumieni oraz z elementu charakterystycznego dla języka StreamAPAS, którym jest Proxy. Element ten stanowi mechanizm pośrednictwa do fabryk danych. Nazwa ta w sposób luźny nawiązuje do idei działania wzorca projektowego Proxy.

<wyraŜenie_where>::= [<lista_proxy>][<warunek_where>]

Aby użyć w zapytaniu tabelę historii należy zdefiniować algorytm ekstrakcji danych z fabryki danych poprzez Proxy. Parametry tej ekstrakcji są ustawiane poprzez wywołanie metod obiektu Proxy. Zastosowanie tutaj elementów języków obiektowych sprawia, że składnia języka StreamAPAS umożliwia obsługę wielu typów Proxy. Dla projektanta nowej fabryki danych, zdefiniowanie Proxy ogranicza się do utworzenia klasy w języku Java wraz z odpowiednio adnotowanymi metodami służącymi do jej konfiguracji. Po dołączeniu fabryki danych do systemu, dzięki mechanizmu refleksji adnotowane metody zostają udostępnione na poziomie języka zapytań. Na początku zostanie przedstawiona uproszczona składnia służąca do definiowania Proxy, która wystarcza do zaprezentowania zalet nowego podejścia. Pod koniec rozdziału przedstawiona zostanie pełna składnia Proxy, która daje szerokie możliwości obsługi złożonych fabryk danych.

Uproszczona składnia Proxy sprowadza się do definicji: <lista_proxy>::=<proxy>, <proxy>,…

<proxy> ::= <nazwa_kolekcji_danych>{ <metoda(listaArgumentów)>, metoda(listaArgumentów), …}

Na początku wyrażenia pojawia się nazwa_kolekcji_danych identyfikująca fabrykę danych obsługiwaną przez Proxy. Następnie przy użyciu nawiasów {…} przechodzi się do kontekstu obiektu Proxy. Wyrażenie wewnątrz nawiasów {…} zawiera wywołania metod konfigurujących. Składnia zezwala wywołać kilka metod oddzielonych przecinkiem. Poprawność semantyczna tworzonej konfiguracji jest weryfikowana przez obiekt Proxy w chwili wywoływania metod, jeżeli zostanie wykryty błąd użycia lub wartości argumentów, wtedy zgłaszany jest błąd kompilacji.

W języku przyjęto, że tabela historii jest wynikiem ekstrakcji danych z fabryki danych, która może reprezentować dane wielowymiarowe. Zgodnie z tą koncepcją okna należą do operatorów ekstrakcji danych zdefiniowane na osi czasu.

Dlatego operatory okien zaliczamy do elementów Proxy. W przypadku fabryki strumieni, Proxy jest konfigurowany poprzez metody:

rangeWindow(Long aCount) – tworzy okno liczebnościowe o rozmiarze

aCount.

fixedWindow(Long aFrom, Long aPeriod)

– tworzy okno kroczące o szerokości

aPeriod[ms] gdzie pierwszy krok jest stawiany

w chwili aFrom. Czas aFrom jest naliczany w milisekundach względem daty początkowej przypadającej na północ w styczniu 1, 1970 UTC.

slideWindow(Long aPeriod) – tworzy okno przesuwne o szerokości

aPeriod[ms]

Implementacja fabryki strumieni została tak zrealizowana, że jeżeli ktoś dla jednego źródła danych przypisze kilka okien czasowych wtedy brane jest pod uwagę ostatnie w wyrażeniu.

Składnia wrażenia warunek_where jest zbliżona do składni języka SQL. Poniżej podano zestaw reguł definiujących dopuszczalne wyrażenia dla tego symbolu:

<expr> ::= <expr> AND <expr> | <expr> OR <expr>

| <expr> XOR <expr>

| <expr> <comparison_operator> <expr>

| <expr> '-' <expr> | <expr> '+' <expr> | <expr> '*' <expr> | <expr> '/' <expr> | <prefixExpr> ; <prefixExpr>::= <atom> | '-' <prefixExpr> | '!' <prefixExpr> ; <atom>::= literal | <identyfikatorAtrybutu> | <method_call> | '(' expr ')' ; <comparison_operator>::= <|<=|>|>=|!=|==

3.6.5 Klauzula group by i having

Działanie klauzul group by i having jest takie samo jak w języku SQL. Jedyna różnica polega na zastosowaniu składni opisanej przez symbol expr.

3.6.6 Operatory agregacji

W języku StreamAPAS operatory agregacji mogą być użyte w klauzulach

select oraz having. Ponadto wywołań operacji agregacji nie można zagnieżdżać.

Innymi słowy operandem agregacji nie może być wartość wyliczona przez inny agregat. W relacyjnej bazie danych zestaw funkcji agregujących jest ubogi, aby go rozszerzyć producenci baz danych wprowadzili rozszerzenia PL/pgSQL i PL/SQL. Od strony składniowej te rozwiązania definiują nowe operatory przy użyciu języka proceduralnego. W systemie StreamAPAS przyjęto, że operatory implementowane są w języku Java, a na poziomie języka zapytań są udostępniane jako metody statyczne podobnie jak operatory na zbiorach. To rozwiązanie pozwala z jednej strony uczynić implementację operatora agregacji na tyle wydajną na ile pozwala maszyna wirtualna Java, z drugiej strony osiągana jest unifikacja interfejsu języka zapytań.

Zestaw podstawowych operatorów agregacji jest zdefiniowany w klasie Agg. Obecnie system StreamAPAS udostępnia poniższy zestaw agregatów dla typów Integer, Long, Float i Double:

Agg.min(arg) Agg.max(arg) Agg.sum(arg) Agg.count()