28 marca 2007

Java Persistence - Rozdziały 4.5 Klauzula WHERE oraz 4.6 Wyrażenia warunkowe

Rozdział omawiający klauzulę WHERE - 4.5 Klauzula WHERE - jest wyjątkowo skromny jak na specyfikację Java Persistence 1.0. Biorąc pod uwagę, że większość z rozdziałów zawiera po 5-10 stron liczba 0,5 strony nie jest imponująca. Okazało się jednak, że jest to preludium do większej partii materiału jaka została zawarta w rozdziale kolejnym - 4.6 Wyrażenia warunkowe. Być może ilość stron - 10 - nie przekonuje do takiego myślenia, ale mając rozdział 4.6 za sobą, przygotowane przykłady i zestawianie środowiska do wykonania testów równolegle z kilkoma dostawcami JPA - Apache OpenJPA 0.9.7-SNAPSHOT, TopLink Essentials 2.0 BUILD 40 oraz Hibernate EntityManager 3.3.0.GA okazało się, że zabrało mi to wyjątkowo dużo czasu. Na prawdę nie spodziewałem się tego! Wszystko testowałem w środowisku, które opisałem w moim ostatnim artykule Nauka Java Persistence z Apache Maven 2 i dostawcami JPA: OpenJPA, Hibernate i TopLink, do którego zachęcam, ponieważ znacznie upraszcza zrozumienie specyfikacji w praktyce.

4.5 Klauzula WHERE

Klauzula WHERE składa się z wyrażenia warunkowego służącego do wyboru obiektów lub wartości spełniających go. Klauzula WHERE zawęża wynik zapytania SELECT lub obszar działania zapytań UPDATE lub DELETE.

Składnia klauzuli WHERE:

klauzula_WHERE ::= WHERE wyrażenie_warunkowe

Wyrażenie GROUP BY umożliwia grupowanie wartości ze względu na własności encji. Wyrażenie HAVING umożliwia dalsze zawężenie wyniku zapytania do warunków spełnianych przez grupy (będących produktem GROUP BY).

Składnia wyrażenia HAVING:

wyrażenie_HAVING ::= HAVING wyrażenie_warunkowe

GROUP_BY oraz HAVING są opisane szczegółowo w dalszej części specyfikacji - rozdział 4.7, który jest przede mną i będzie tematem kolejnej relacji.

Od razu widoczne jest dlaczego połączyłem relację z lektury specyfikacji rozdziału 4.5 o klauzuli WHERE z rozdziałem kolejnym 4.6 o wyrażeniach warunkowych. Wyrażania warunkowe są integralną częścią klauzuli WHERE i w zasadzie bez ich poznania nie ma co marzyć o zrozumieniu znaczenia WHERE (oczywiście dla użytkowników języka SQL będzie to jedynie przedstawienie możliwości, a może nawet i braku możliwości, wyrażania warunków w JPQL).

4.6 Wyrażenia warunkowe

Rozdział 4.6 omawia konstrukcje, które są dozwolone w klauzulach WHERE oraz HAVING.

Pojawia się uwaga odnośnie stanowych pól (pól-stanów, ang. state-fields, tj. pól, które są "nośnikiem" informacji z bazy danych, albo jeszcze inaczej, pól mapowanych na odpowiednie kolumny w bazie danych), które są odwzorowane (zmapowane) w postaci LOBów lub serializowanej (ang. serialized form). Jako, że nie za bardzo rozumiem problemu wysłałem pytanie na forum EJB na forum.java.sun.com - Conditional Expressions and the note about state-field's serialized form.

4.6.1 Literały

Literał łańcuchowy jest ograniczony przez pojedyńcze cudzysłowy, np. 'literał' (podczas, gdy w Javie korzysta się z cudzysłowów, a pojedyńcze cudzysłowy są zarezerwowane dla literałów znakowych - pojedyńczych liter, tj. obiektów typu char lub Character).
Specjalność pojedyńczych cudzysłowów wyłącza się poprzez poprzedzenie ich kolejnym apostrofem, np. 'I''m lovin'' it'. Literały w zapytaniach, jak literały znakowe w Javie, zapisane są w Unicode. Korzystanie z mechanizmu usuwania specjalności pojedyńczego cudzysłowa w Javie, tj. korzystanie z odwrotnego ukośnika (ang. backslash), nie jest wspierane (dla przypomnienia o literałach w Javie polecam część specyfikacji języka Java - 3.10.5 String Literals.

Literał liczbowy zapisywany jest w notacji języka Java oraz SQL (na chwilę obecną nie jest mi znana różnica między nimi, ale wynika to z mojej nikłej znajomości języka SQL).

Literał liczbowy zmiennoprzecinkowy jest wspierany w notacji języka Java oraz SQL (podobnie jak przy literałach liczbowych, nie mam pojęcia jaką to robi różnicę - wyjdzie podczas korzystania z JPQL).

Ciekawostką dla mnie jest literał wyliczeniowy (ang. enum literal), który wykorzystuje składnię literału typu wyliczeniowego w Javie. Klasa typu wyliczeniowego musi zostać wymieniona.

SELECT DISTINCT o FROM Osoba o, IN(o.projekty) p WHERE p.rodzajProjektu = RodzajProjektu.OTWARTY

UWAGA: Powyższy przykład działa jedynie dla Apache OpenJPA 0.9.7-SNAPSHOT. Hibernate EntityManager 3.3.0.GA oraz TopLink Essentials 2.0 BUILD 40 wymagają, aby typ wyliczeniowy był podawany wraz z nazwą pakietu, czyli powyższy przykład zakończy się pomyślnie ze wszystkimi trzema dostawcami JPA, jeśli zostanie podany pakiet typu wyliczeniowego:

SELECT DISTINCT o FROM Osoba o, IN(o.projekty) p WHERE p.rodzajProjektu = pl.jaceklaskowski.jpa.entity.RodzajProjektu.OTWARTY

Wspierane są sufiksy, które wskazują typ literału liczbowego zgodnie z zasadami języka Java. Wsparcie dla literałów liczbowych szestnastkowych oraz ósemkowych nie jest wymagany (ciekawe, co mogłyby modelować pola encji, którym odpowiadałyby literały szesnastkowe i ósemkowe?).

Literały logiczne (boolowskie) to TRUE oraz FALSE (na chwilę obecną wydaje mi się, że bardziej wyrafinowane jest stosowanie literałów wyliczeniowych, które są samoopisujące się i jedyne zastosowanie dla literałów logicznych to odwzorowanie danych już istniejących w bazach danych - dane "spadkowe", ang. legacy data).

Wielkość liter nie jest istotna w predefiniowanych literałach, np. TrUe jest tak samo dobre jak TRUE czy true.

4.6.2 Zmienne identyfikujące (ang. identification variables)

Wszystkie zmienne identyfikujące wykorzystywane w klauzulach WHERE i HAVING zapytań SELECT i DELETE muszą zostać zdefiniowane w klauzuli FROM. Klauzula FROM była opisywana wcześniej (patrz Java Persistence - Rozdział 4.4 Klauzula FROM i deklaracje nawigacyjne). Zmienne identyfikujące używane w klauzuli WHERE zapytania UPDATE muszą być zadeklarowane w klauzuli UPDATE.

Zmienne identyfikujące są "istniejąco definiowane" (ang. existentially quantified) w klauzulach WHERE lub HAVING, co oznacza, że są reprezentacją pojedyńczego elementu kolekcji lub pojedyńczego egzemplarza encyjnego AST i nigdy nie reprezentują kolekcji jako całości.

4.6.3 Wyrażenia ścieżkowe (ang. path expressions)


Zabronione jest korzystanie z wyrażenia ścieżkowego reprezentującego kolekcję (ang. collection valued path expression) w ramach klauzul WHERE i HAVING jako część wyrażenia warunkowego oprócz wyrażenia porównania pustej kolekcji (ang. empty collection comparision expression), wyrażenia operującego na elemencie kolekcji (ang. collection member expression) czy wyrażenie będące argumentem operatora SIZE. Wszystkie wymienione wyrażenia są częścią składni BNF wyrażenia warunkowego i są wyjaśnione w późniejszych sekcjach.

Dla przybliżenia tematu podam operatory odpowiadające wymienionym wyżej wyrażeniom:
  • Wyrażenie porównania pustej kolekcji - operator IS EMPTY
  • Wyrażenie operujące na elemencie kolekcji - operator [NOT] MEMBER [OF]
  • Wyrażenie będące argumentem operatora SIZE - operator SIZE
Wyłącznie wymienione operatory, które operują na kolekcjach encji, mogą być wykorzystane w klauzulach WHERE oraz HAVING.

4.6.4 Parametry wejściowe (ang. input parameters)

Wyrażenia warunkowe mogą korzystać z parametrów pozycyjnych (ang. positional parameters) lub nazwanych (ang. named parameters). Nie mogą być one jednak używane równocześnie w pojedyńczym zapytaniu.

Parametry wejściowe (pozycyjne i nazwane) mogą być jedynie wykorzystywane w klauzulach WHERE lub HAVING.

Specyfikacja podkreśla, że jeśli parametr wejściowy ma wartość NULL, wtedy operacje porównania lub arytmetyczne, w których ten parametr występuje zwróci wartość nieokreśloną. Więcej informacji ma być podanych w sekcji 4.11 (jeszcze przede mną).

4.6.4.1 Parametry pozycyjne

Następujące reguły rządzą parametrami pozycyjnymi:
  • Parametry wejściowe są wyznaczane przez znak zapytania (?), po którym następuje liczba całkowita, np. ?1.
  • Parametry wejściowe rozpoczynają się od 1.
I na potwierdzenie, podczas wykonania następującego zapytania:

Query query = em
.createQuery("SELECT DISTINCT o FROM Osoba o, IN(o.projekty) p WHERE p.rodzajProjektu = ?0");
query.setParameter(0, RodzajProjektu.OTWARTY);

otrzymałem komunikat błędu (dostawca JPA to Apache OpenJPA):

FAILED: testPositionalParameters
<4|false|0.9.7-incubating-snapshot> org.apache.openjpa.persistence.InvalidStateException: The parameter index 0 is invalid. Parameters must be integers starting at 1.
at org.apache.openjpa.persistence.QueryImpl.setParameter(QueryImpl.java:410)
at org.apache.openjpa.persistence.QueryImpl.setParameter(QueryImpl.java:49)
at pl.jaceklaskowski.jpa.chapter4_6.ConditionalExpressionsTest.testPositionalParameters(ConditionalExpressionsTest.java:99)

, co zgadza się z drugim warunkiem, gdzie parametry pozycyjne są liczone począwszy od 1.

UWAGA: Hibernate EntityManager 3.3.0.GA akceptuje numerowanie parametrów pozycyjnych począwszy od 0 (co jest niezgodne ze specyfikacją).

Specyfikacja wyraźnie zaznacza, że dany parametr może być wykorzystany wielokrotnie oraz kolejność użycia parametrów w zapytaniu nie musi odpowiadać kolejności przypisywania im wartości (co okazało się być błędnie zaimplementowane w Apache OpenJPA. Zgłosiłem błąd w bazie błędów OpenJPA - OPENJPA-188 Positional parameters (in)order within query and query.setParameter (in)order, co może ostatecznie okazać się moim niezrozumieniem tematu).

Query query = em
.createQuery("SELECT DISTINCT o FROM Osoba o, IN(o.projekty) p WHERE o.imie LIKE ?2 AND o.nazwisko LIKE ?2 AND p.rodzajProjektu = ?1");
query.setParameter(1, RodzajProjektu.OTWARTY);
query.setParameter(2, "J%");

4.6.4.2 Parametry nazwane

Parametrem nazwanym nazywamy identyfikator poprzedzony znakiem dwukropka (:).

Parametry nazwane są rozróżniane ze względu na wielkość liter.

Query query = em
.createQuery("SELECT DISTINCT o FROM Osoba o, IN(o.projekty) p WHERE o.imie LIKE :imie AND p.rodzajProjektu = :rodzajProjektu");
query.setParameter("rodzajProjektu", RodzajProjektu.OTWARTY);
query.setParameter("imie", "J%");

Dokładne omówienie reguł rządzących parametrami nazwanymi znajduje się w rozdziałach 3.6.1 oraz 4.4.1, który były już relacjonowane w Notatniku - Java Persistence - Rozdział 3.6 Query API oraz Java Persistence - Rozdział 4.4 Klauzula FROM i deklaracje nawigacyjne, odpowiednio.

4.6.5 Składanie wyrażeń warunkowych

Wyrażenia warunkowe mogą składać się z innych wyrażeń warunkowych, operacji porównania, logicznych, wyrażeń ścieżkowych, których wynikiem jest wartość logiczna, literałów logicznych i logicznych parametrów wejściowych.

Wyrażenia arytmetyczne (liczbowe) mogą być używane w wyrażeniach porównujących. Wyrażenia arytmetyczne mogą składać się z innych wyrażeń arytmetycznych, operacji arytmetycznych, wyrażeń ścieżkowych, których wynikiem jest wartość liczbowa, literałów liczbowych oraz liczbowych parametrów wejściowych.

Operacje arytmetyczne korzystają z promocji liczbowej, tj. "sprowadzania" argumentów operacji do wspólnego typu.

Możliwe jest korzystanie z nawiasów zwykłych '()' do grupowania i porządkowania wyrażeń.

Słowa kluczowe występujące podczas składania wyrażeń to OR, AND, NOT. Pozostałe zostaną omówione w nadchodzących rozdziałach. Cała notacja BNF wyrażeń warunkowych przedstawiona została na stronie 90. specyfikacji JPA i wszystkie będą opisane dalej, więc nic straconego.

Funkcje agregujące mogą jedynie być używane w wyrażeniach warunkowych w klauzuli HAVING (więcej w nadchodzącym rodziale).

4.6.6 Operatory i ich kolejność wykonania (siła wiązania)

Rozdział przedstawia ważność operatorów w malejącym porządku, tj. pierwszy jest najsilniejszy.
  • Operator nawigacyjny - symbol kropki (.)
  • Operatory arytmetyczne
    • Unarne - + (plus) oraz - (minus)
    • Binarne - * (mnożenie), / (dzielenie), + (dodawanie), - (odejmowanie)
    • Operatory porównania - = (równy), > (większy), >= (większy bądź równy), < (mniejszy), <= (mniejszy bądź równy), <> (nierówny), [NOT] BETWEEN, [NOT] LIKE, [NOT] IN, IS [NOT] NULL, IS [NOT] EMPTY, [NOT] MEMBER [OF]
  • Operatory logiczne - NOT, AND oraz OR
Opis operatorów znajduje się w kolejnych rozdziałach.

4.6.7 Wyrażenia należenia BETWEEN (pomiędzy)

Operator BETWEEN działa na wyrażeniach tego samego typu - wyrażeniach liczbowych, wyrażeniach łańcuchowych lub wyrażeniach kalendarzowych.

Składnia wyrażenia BETWEEN składa się z kilku wariantów odpowiadających typom wyrażeń. Pierwszy wariant wyrażenia BETWEEN z wyrażeniami arytmetycznymi:

wyrażenie_liczbowe [NOT] BETWEEN wyrażenie_arytmetyczne AND wyrażenie_arytmetyczne

Przykład: Wyszukaj osoby, które uczestniczą w 2 lub 3 projektach.

SELECT DISTINCT o FROM Osoba o WHERE SIZE(o.projekty) BETWEEN 2 AND 3

Przy tworzeniu wspomnianego przykładu ujawniła się istotna różnica między Apache OpenJPA a TopLink Essentials - raportowanie błędów.

Początkowo stworzyłem zapytanie następującej postaci:

SELECT DISTINCT o FROM Osoba o, IN(o.projekty) p WHERE COUNT(p) BETWEEN 2 AND 3 AND p.rodzajProjektu = :rodzajProjektu

TopLink zareagował na niepoprawne zastosowanie COUNT w klauzuli WHERE w następujący sposób:

java.lang.IllegalArgumentException: An exception occured while creating a query in EntityManager
at oracle.toplink.essentials.internal.ejb.cmp3.EntityManagerImpl.createQuery(EntityManagerImpl.java:194)
at pl.jaceklaskowski.jpa.chapter4_6.ConditionalExpressionsTest.testExpressions(ConditionalExpressionsTest.java:155)

podczas, gdy OpenJPA był bardziej wylewny i wskazał dokładne źródło błędu.

Caused by: org.apache.openjpa.lib.jdbc.ReportingSQLException: Invalid use of an aggregate function. {SELECT DISTINCT t0.numer, t0.dzienImienin, t0.dzienUrodzin, t0.imie, t0.kraj, t
0.nazwisko, t0.tytul FROM Osoba t0 INNER JOIN Osoba_Projekt t1 ON t0.numer = t1.Osoba_numer INNER JOIN Projekt t2 ON t1.projekty_nazwa = t2.nazwa WHERE (COUNT(t2.nazwa) >= ? AND CO
UNT(t2.nazwa) <= ? AND t2.rodzajProjektu = ?)} [code=30000, state=42903]
at org.apache.openjpa.lib.jdbc.LoggingConnectionDecorator.wrap(LoggingConnectionDecorator.java:197)
at org.apache.openjpa.lib.jdbc.LoggingConnectionDecorator.access$000(LoggingConnectionDecorator.java:53)
at org.apache.openjpa.lib.jdbc.LoggingConnectionDecorator$LoggingConnection.prepareStatement(LoggingConnectionDecorator.java:224)
at org.apache.openjpa.lib.jdbc.DelegatingConnection.prepareStatement(DelegatingConnection.java:160)
at org.apache.openjpa.lib.jdbc.ConfiguringConnectionDecorator$ConfiguringConnection.prepareStatement(ConfiguringConnectionDecorator.java:137)
at org.apache.openjpa.lib.jdbc.DelegatingConnection.prepareStatement(DelegatingConnection.java:160)
at org.apache.openjpa.jdbc.kernel.JDBCStoreManager$RefCountConnection.prepareStatement(JDBCStoreManager.java:1305)
at org.apache.openjpa.lib.jdbc.DelegatingConnection.prepareStatement(DelegatingConnection.java:149)
at org.apache.openjpa.jdbc.sql.SQLBuffer.prepareStatement(SQLBuffer.java:471)
at org.apache.openjpa.jdbc.sql.SQLBuffer.prepareStatement(SQLBuffer.java:451)
at org.apache.openjpa.jdbc.sql.SelectImpl.execute(SelectImpl.java:323)
at org.apache.openjpa.jdbc.sql.SelectImpl.execute(SelectImpl.java:296)
at org.apache.openjpa.jdbc.sql.LogicalUnion$UnionSelect.execute(LogicalUnion.java:402)
at org.apache.openjpa.jdbc.sql.LogicalUnion.execute(LogicalUnion.java:213)
at org.apache.openjpa.jdbc.sql.LogicalUnion.execute(LogicalUnion.java:203)
at org.apache.openjpa.jdbc.kernel.SelectResultObjectProvider.open(SelectResultObjectProvider.java:91)
at org.apache.openjpa.lib.rop.EagerResultList.(EagerResultList.java:31)
... 31 more

Drugi wariant wyrażenia BETWEEN z wyrażeniami łańcuchowymi:

wyrażenie_łańcuchowe [NOT] BETWEEN wyrażenie_łańcuchowe AND wyrażenie_łańcuchowe

Przykład: Wyszukaj osoby, których nazwisko jest krótsze niż 5 i dłuższe niż 10 znaków.

SELECT DISTINCT o FROM Osoba o WHERE LENGTH(o.nazwisko) NOT BETWEEN 5 AND 10

I kolejny, ostatni, wariant wyrażenia BETWEEN dla wyrażeń kalendarzowych:

wyrażenie_kalendarzowe [NOT] BETWEEN wyrażenie_kalendarzowe AND wyrażenie_kalendarzowe

Przykład: Wyszukaj osoby, których imieniny są w nadchodzących 15-tu dniach.

SELECT DISTINCT o FROM Osoba o WHERE o.dzienImienin BETWEEN :dzisiaj AND :za15dni

Uwaga na wyrażenia, które operują na wartościach nieokreślonych lub NULL (opisane w nadchodzącym rozdziale 4.11).

BETWEEN jest analogiczny do odpowiedniego zapytania korzystającego z operatorów <= (mniejsze bądź równe) i >= (większe bądź równe).

4.6.8 Wyrażenia należenia IN (w)

Składnia użycia operatora porównania [NOT] IN w wyrażeniach warunkowych:

wyrażenie_in ::= wyrażenie_ścieżkowe_pola_stanu [NOT] IN ( element_in {, element_in}* | podzapytanie)
element_in ::= literał | parametr_wejściowy

wyrażenie_ścieżkowe_pola_stanu musi być wartością łańcuchową, liczbową bądź wyliczeniową.

literał bądź parametr_wejściowy musi być tego samego typu co odpowiadające wyrażenie_ścieżkowe_pola_stanu (więcej informacji będzie w rozdziale 4.12).

Wynik podzapytania musi być tego samego typu jak typ odpowiadającego wyrażenie_ścieżkowe_pola_stanu (więcej w rozdziale 4.6.15).

Wyrażenie IN jest równoznacze z wyrażeniem korzystającym z operatora równości oraz OR.

Przykład: Wyszukaj osoby o imieniu Jacek bądź Agata.

SELECT DISTINCT o FROM Osoba o WHERE o.imie IN ('Jacek', 'Agata')

Powyższe zapytanie jest analogiczne do następującego:

SELECT DISTINCT o FROM Osoba o WHERE (o.imie = 'Jacek') OR (o.imie = 'Agata')

Musi istnieć co najmniej jeden element w liście, która definiuje zbiór wartości operatora IN.

Jeśli wartość wyrażenie_ścieżkowe_pola_stanu wyrażenia jest NULL albo nieokreślona, wartość całego wyrażenia jest nieokreślona.

4.6.9 Wyrażenia porównania LIKE (jak)

Składnia operatora porównania [NOT] LIKE jest następująca:

wyrażenie_łańcuchowe [NOT] LIKE wzorzec [ESCAPE znak_wyłączający_specjalność]

wyrażenie_łańcuchowe musi być ciągiem znaków (żeby się nie powtarzać i nie napisać, że musi być wyrażeniem łańcuchowym). wzorzec jest literałem łańcuchowym lub łańcuchowym parametrem wejściowym, w którym niektóre znaki mają specjalne znaczenie:
  • podkreślnik '_' reprezentuje dowolny znak
  • procent '%' reprezentuje dowolny ciąg znaków, włączając ciąg pusty
Pozostałe znaki reprezentują same siebie. Opcjonalny parametr znak_wyłączający_specjalność jest literałem znakowym z jednym znakiem lub parametr wejściowy typu znakowego (char lub Character), który wyłącza specjalność znaków podkreślnika oraz procenta we wzorcu.

Przykład: Wyszukaj osoby, których imię rozpoczyna się 'Jac' i kończy 'k' (np. Jacek, Jack, czy ostatecznie Jaculek), a nazwisko nie rozpoczyna się literą 'K'.

SELECT DISTINCT o FROM Osoba o WHERE o.imie LIKE 'Jac%k' AND o.nazwisko NOT LIKE 'K%'

Jeśli wartość wyrażenie_łańcuchowe lub wzorca jest NULL lub nieokreślona, wartość wyrażenia LIKE jest nieokreślona. Jeśli znak_wyłączający_specjalność jest podany i jest NULL, wartość wyrażenia LIKE jest nieokreślona.

4.6.10 Wyrażenia porównania NULL


Wyrażenie porównania IS [NOT] NULL sprawdza, czy argument jest NULL.

Składnia operatora porównania IS [NOT] NULL jest następująca:

{wyrażenie_ścieżkowe_pojedyńczej_wartości | parametr_wejściowy } IS [NOT] NULL

4.6.11 Wyrażenia porównania pustej kolekcji IS EMPTY

Składnia operatora porównania IS [NOT] EMPTY jest następująca:

wyrażenie_ścieżkowe_reprezentujące_kolekcję IS [NOT] EMPTY

Wyrażenie porównania IS [NOT] EMPTY sprawdza, czy wyrażenie_ścieżkowe_reprezentujące_kolekcję jest pusty, tj. kolekcja nie posiada elementów.

Przykład: Wyszukaj osoby, które uczestniczą w projektach.

SELECT DISTINCT o FROM Osoba o WHERE o.projekty IS NOT EMPTY

Jeśli wartość wyrażenie_ścieżkowe_reprezentujące_kolekcję jest nieokreślona, wartość również będzie nieokreślona.

4.6.12 Wyrażenia porównania elementu kolekcji MEMBER OF

Składnia operatora porównania [NOT] MEMBER [OF] jest następująca:

wyrażenie_encyjne [NOT] MEMBER [OF] wyrażenie_ścieżkowe_reprezentujące_kolekcję
wyrażenie_encyjne ::= wyrażenie_ścieżkowe_pojedyńczej_wartości_asocjacji | wyrażenie_pojedyńczej_encji
wyrażenie_pojedyńczej_encji ::= zmienna_identyfikacyjna | parametr_wejściowy

Wyrażenie porównania [NOT] MEMBER [OF] sprawdza, czy wyrażenie_encyjne jest elementem kolekcji wskazanej przez wyrażenie_ścieżkowe_reprezentujące_kolekcję.

Jeśli wyrażenie_ścieżkowe_reprezentujące_kolekcję jest pustą kolekcją, wartość MEMBER [OF] jest FALSE, a [NOT] MEMBER [OF] jest TRUE.

Jeśli wyrażenie_ścieżkowe_reprezentujące_kolekcję lub wyrażenie_ścieżkowe_pojedyńczej_wartości_asocjacji jest NULL lub nieokreślona, wartość wyrażenia jest nieokreślona.

Przykład: Wyszukaj osoby, które należą do projektu Apache OpenEJB.

Query query = em.createQuery("SELECT DISTINCT o FROM Osoba o WHERE :projekt MEMBER OF o.projekty");
query.setParameter("projekt", new Projekt("Apache OpenEJB", RodzajProjektu.OTWARTY));

UWAGA: Powyższy przykład nie działa na Apache OpenJPA 0.9.7-SNAPSHOT podczas, gdy TopLink 2.0 BUILD 40 oraz Hibernate EntityManager 3.3.0.GA wykonują zapytanie poprawnie.

4.6.13 Wyrażenia istnienia EXISTS

Wyrażenie EXISTS ma wartość TRUE jeśli wartość podzapytania składa się z jednego bądź kilku wartości. W przeciwnym przypadku wartość wyrażenia EXISTS wynosi FALSE.

Składnia operatora istnienia EXISTS jest następująca:

wyrażenie_exists ::= [NOT] EXISTS (podzapytanie)


Przykład: Wyszukaj projekty, które mają zależności od projektów otwartych (wersja bardzo skomplikowana, bo możnaby to prościej obsłużyć).

SELECT DISTINCT p FROM Projekt p WHERE EXISTS (SELECT zaleznosc FROM Projekt zaleznosc WHERE zaleznosc MEMBER OF p.zaleznosci AND zaleznosc.rodzajProjektu = pl.jaceklaskowski.jpa.entity.RodzajProjektu.OTWARTY)

UWAGA: Apache OpenJPA 0.9.7-SNAPSHOT nie zwróci żadnych elementów (niepoprawne działanie) podczas, gdy TopLink Essentials 2.0 BUILD 40 oraz Hibernate EntityManager 3.3.0.GA radzą sobie znakomicie.

UWAGA: Jedynie Apache OpenJPA akceptuje podanie typu wyliczeniowego w skróconej postaci - bez pakietu. TopLink oraz Hibernate wymagają podania pełnej kwalifikowanej nazwy klasy (wraz z pakietem), tj. pl.jaceklaskowski.jpa.entity.RodzajProjektu.OTWARTY vs RodzajProjektu.OTWARTY.

4.6.14 Wyrażenia ALL, ANY, SOME

Wyrażenie warunkowe ALL ma wartość TRUE, jeśli operator porównania przyjmuje wartość TRUE dla wszystkich wartości będących elementami wyniku podzapytania lub jeśli wynik podzapytania jest pusty. Wyrażenie ALL przyjmuje wartość FALSE, jeśli dowolne porównanie zwróci FALSE. Wyrażenie ALL przyjmuje wartość nieokreśloną, jeśli nie jest TRUE albo FALSE.

Wyrażenie warunkowe ANY ma wartość TRUE, jeśli operator porównania ma wartość TRUE dla dowolnej wartości będącej elementem wyniku podzapytania. Wyrażenie ANY jest FALSE, jeśli wynik podzapytania jest pusty lub jeśli operator porównania jest FALSE dla wszystkich wartości będących elementami wyniku podzapytania. Wyrażenie ANY przyjmuje wartość nieokreśloną, jeśli nie jest TRUE albo FALSE.

Operator SOME jest synonimem dla operatora ANY.

Operatory porównania używane z ALL/ANY/SOME to = (równa się), < (mniejszy), <= (mniejszy równy), > (większy), >= (większy równy), <> (nierówny). Wynik podzapytania musi być tego samego typu jak drugi argument operatora porównania (więcej informacji w nadchodzącym rozdziale 4.12).

Składnia wyrażenia ALL/ANY/SOME jest następująca:

wyrażenie_ALL_ANY_SOME ::= { ALL | ANY | SOME} (podzapytanie)

Przykład: Wyszukaj pracowników, których zarobki są większe od wszystkich kierowników w dziale pracownika.

SELECT emp FROM Employee emp WHERE emp.salary > ALL (SELECT m.salary FROM Manager m WHERE m.department = emp.department)

4.6.15 Podzapytania

Podzapytania mogą jedynie występować w klauzulach WHERE oraz HAVING.

Składnię podzapytań przedstawia specyfikacja na stronie 95 (i wydaje się, że jest podobna, jeśli nie identyczna ze składnią zapytań).

Warto pamiętać, że w niektórych przypadkach podzapytanie musi zwracać pojedyńczą wartość, np.

SELECT o FROM Osoba o WHERE SIZE(o.projekty) > (SELECT AVG(SIZE(o1.projekty)) FROM Osoba o1)

Powyższe zapytanie zwraca osoby, których zaangażowanie w projektach przekracza średnią wśród wszystkich pracowników.

UWAGA: Zapytanie nie jest akceptowane przez TopLink Essentials 2.0 BUILD 40 oraz Hibernate EntityManager 3.3.0.GA. Apache OpenJPA 0.9.7-SNAPSHOT wykonuje zapytanie bez zarzutu.

4.6.16 Wyrażenia funkcjonalne

JPQL posiada następujące wbudowane funkcje, które mogą być używane w klauzulach WHERE oraz HAVING.

Jeśli wartość dowolnego argumentu wyrażenia funkcjonalnego jest NULL albo nieokreślona, wartość wyrażenia funkcjonalnego jest również nieokreślona.

4.6.16.1 Funkcje łańcuchowe


Funkcje zwracające ciąg znaków:

CONCAT(pierwszy_ciąg_znaków, drugi_ciąg_znaków) - zwraca ciąg znaków będący sklejeniem swoich argumentów.

SUBSTRING(ciąg_znaków, proste_wyrażenie_arytmetyczne, proste_wyrażenie_arytmetyczne) - zwraca ciąg znaków będący podciągiem ciąg_znaków od pozycji wyznaczonej przez pierwsze proste_wyrażenie_arytmetyczne o długości określonej przez drugie proste_wyrażenie_arytmetyczne.

TRIM([[LEADING | TRAILING | BOTH] [znak_do_usunięcia] FROM] ciąg_znaków) - zwraca ciąg znaków z usuniętymi znakami z ciąg_znaków. Jeśli nie podano znak_do_usunięcia domyślnie będzie to spacja. Parametr znak_do_usunięcia jest pojedyńczym znakiem bądź znakowym parametrem wejściowym o długości 1 (typu char bądź Character). Domyślnym sposobem usuwania - LEADING, TRAILING, BOTH - jest BOTH.

UWAGA: Specyfikacja ostrzega, że korzystanie z parametru znak_do_usunięcia może spowodować, że zapytanie nie będzie przenośne, gdyż nie wszystkie bazy danych wspierają usuwanie znaków innych niż spacja.

LOWER(ciąg_znaków) - zwraca ciąg znaków będący kopią ciąg_znaków ze wszystkimi literami zamienionymi na małe litery.

UPPER(ciąg_znaków) - zwraca ciąg znaków będący kopią ciąg_znaków ze wszystkimi literami zamienionymi na wielkie litery.

Funkcje zwracające wartości liczbowe:

LENGTH(ciąg_znaków) - zwraca długość ciąg_znaków

LOCATE(ciąg_znaków_do_odszukania, ciąg_znaków[, proste_wyrażenie_arytmetyczne]) - zwraca pierwszą pozycję podanego ciąg_znaków_do_odszukania w ciąg_znaków od pozycji proste_wyrażenie_arytmetyczne, włącznie (domyślnie początek ciąg_znaków). Pierwsza pozycja w ciągu to 1. Jeśli ciąg_znaków_do_odszukania nie został odnaleziony zwracana jest wartość 0.

UWAGA: Podobnie jak miało to miejsce w przypadku funkcji TRIM, specyfikacja ostrzega, że korzystanie z parametru proste_wyrażenie_arytmetyczne może spowodować, że zapytanie nie będzie przenośne, gdyż nie wszystkie bazy danych wspierają LOCATE z trzema parametrami.

4.6.16.2 Funkcje arytmetyczne

ABS(proste_wyrażenie_arytmetyczne) - zwraca wartość bezwzględną liczby proste_wyrażenie_arytmetyczne tego samego typu.

SQRT(proste_wyrażenie_arytmetyczne) - zwraca kwadrat proste_wyrażenie_arytmetyczne. Zwracany typ jest double.

MOD(proste_wyrażenie_arytmetyczne, proste_wyrażenie_arytmetyczne) - zwraca resztę z dzielenia pierwszego proste_wyrażenie_arytmetyczne przez drugie proste_wyrażenie_arytmetyczne. Zwracany typ jest integer.

SIZE(wyrażenie_ścieżkowe_reprezentujące_kolekcję) - zwraca numer elementów w wyrażenie_ścieżkowe_reprezentujące_kolekcję. Jeśli wyrażenie_ścieżkowe_reprezentujące_kolekcję jest puste, SIZE zwróci 0.

Argumenty funkcji mogą być liczbowymi typami prostymi jak i ich obiektowymi odpowiednikami.

4.6.16.3 Funkcje kalendarzowe

JPQL dostarcza trzech funkcji kalendarzowych:
  • CURRENT_DATE zwraca aktualną datę w bazie danych.
  • CURRENT_TIME zwraca aktualny czas w bazie danych.
  • CURRENT_TIMESTAMP zwraca aktualny stempel czasowy w bazie danych.
Wszystkie są funkcjami bezparametrowymi.