Przeskocz do opisu głównego

Zapytania indeksu Lucene

Info

Podstawowym mechanizmem wyszukiwania w bazie HgDB jest implementacja indeksu Apache Lucene. Lucene Core to biblioteka Java zapewniająca zaawansowane funkcje indeksowania i wyszukiwania, a także sprawdzanie pisowni, podświetlanie trafień i zaawansowane możliwości analizy. W niniejszym artykule zostaną opisane zasady tworzenia zapytań wyszukujących oraz agregujących jakie obowiązują w Mercury DB (HgDb) 3.0.

Zapytania wyszukujące

Zapytania wyszukujące podlegają ogólnym zasadom tworzenia zapytań opisanych na stronie Apache Lucene - Query Parser Syntax.

Info

W celu ułatwienia deweloperom przejścia z klauzul zapytań standardu SQL do nomenklatury charakterystycznej dla indeksu, rozbudowano mechanizm operatorów AND i OR pozwalających potencjalnie analogiczne tworzenie warunków złożonych w zapytaniach.

Usługi wyszukiwania

Aby realizować zadania wyszukiwania spraw rekomenduje się wykorzystanie usługi REST CaseSearchExtRest#sarchByQuery(POST) i jej odpowiedników w implementacji SOAP i Spring Remoting RMI.

Operator AND

Składnia
<warunek1> AND <warunek2> 

Operator AND przekształca kryterium wyszukiwania do dwóch warunków MUST: +<warunek1> +<warunek2>

Przykład: Warunki zapytania mrc_Case_id:12345 AND mrc_status:A zostaną przekształcone do klauzuli Lucene: +mrc_Case_id:12345 +mrc_status:A.

Operator OR

Składnia
<warunek1> OR <warunek2> 

Operator OR przekształca kryterium wyszukiwania do dwóch warunków SHOULD: <warunek1> <warunek2>.

Przykład: Warunki zapytania mrc_Case_id:12345 OR mrc_status:A zostaną przekształcone do klauzuli Lucene: mrc_Case_id:12345 mrc_status:A.

Zapytania złożone

Zapytania zwracają listę spraw, które zostały znalezione w wyniku wyszukiwania.

Info

Przykłady, które wykorzystane są w poniższej instrukcji budowania zapytań, zostały przygotowane w oparciu o nazewnictwo pól indeksu Lucene w modelu 3.0 (zobacz Indeks Lucene).

Tworzenie zapytań złożonych opiszemy opierając się na przykładowej definicji sprawy:

Przykładowy obiekt sprawy złożonej w postaci JSON
{
"mrcCaseHeader": {
"typeCode": "ElixClient",
"dirty": false,
"pkPropertyName": "clientID"
},
"clientID": "22377",
"clientName": "Dobry Klient",
"clientLogo": "n/a",
"clientAddress": {
"mrcCaseHeader": {
"typeCode": "ElixAddress",
"dirty": false,
"pkPropertyName": "correlationId"
},
"correlationId": "22377",
"county": "pleszewski",
"community": "Pleszew",
"city": "Brzezie",
"street": "ul. Zawidowicka",
"buildingNumber": "5",
"status": "W budowie",
"isContract": "false",
"coordinationMeetingPlanedDate": "17-10-2018",
"orders": [
{
"mrcCaseHeader": {
"typeCode": "ElixOrder",
"dirty": false,
"pkPropertyName": null
},
"orderNr": "SC/012121/XB",
"orderType": "Umowa",
"orderDate": "17-10-2018"
},
{
"mrcCaseHeader": {
"typeCode": "ElixOrder",
"dirty": false,
"pkPropertyName": null
},
"orderNr": "SC/012122/SP",
"orderType": "Sprzedaż",
"orderDate": "18-10-2018"
}
],
"source": null
}
}

Przykład pokazuje sprawę złożoną z trzecim poziomem zagnieżdżenia:

  • (1) sprawa główna typu ElixClient.
    • (2) clientAddress - pole wskazujące na sprawę podrzędną typu ElixAddress.
      • (3) orders - pole wskazujące na listę spraw podrzędnych typu ElixOrder.
Zasada 2 poziomu zagnieżdżenia

W przypadku gdy mamy do czynienia ze sprawami złożonymi, w których występują sprawy nadrzędne i podrzędne, konstruowanie zapytań ma ograniczenie do 2 poziomu zagnieżdżenia. W oparciu o analizowany przykład, sprawy złożonej ElixClient, co do zasady, nie uda nam się znaleźć budując kryteria wyszukiwania wynikające z referencji orders (ElixOrder).

Aby utworzyć zapytanie złożone, do nazw pól spraw podrzędnych należy dodać prefiks w oparciu o następującą zasadę:

<nazwa_pola_sprawy_nadrzędnej>.<nazwa_pola_sprawy_podrzędnej> np. clientAddress.status (zobacz przykładowa sprawa).

Podsumowując, aby znaleźć przykładową sprawę główną ElixClient w oparciu o warunki pól sprawy podrzędnej ElixAddress należy zadać następujące przykłady zapytań.

Przykład 1
mrc_typeCodeValue:ElixAddress AND orders.orderType:Umowa

Opis przykładu: Znajdź wszystkie sprawy adresów (sprawy typu ElixAddress), z którymi powiązane są zamówienia typu "Umowa".

Przykład 2
clientAddress.status:"W budowie" AND clientAddress.city:"Brzezie"

Opis przykładu: Znajdź wszystkie sprawy których adres klienta ma status "W budowie" i znajduje się w mieście "Brzezie".

Uwaga

Powyższe zapytanie, pomimo że prawidłowo skonstruowane i zwróci prawidłowe wyniki, jest zupełnie nie optymalne. Do zapytania należałoby dodać warunki zawężające kryteria sprawy nadrzędnej. W bazie danych mogą być inne typy spraw złożonych mających pole clientAddress, które są referencją do sprawy typu ElixAddress. Dlatego, by zapytanie było bardziej optymalne, należałoby dodać warunki mrc_typeCodeValue:ElixClient oraz clientAddress.mrc_typeCodeValue:ElixAddress. Zatem pełne zapytanie powinno wyglądać:

mrc_typeCodeValue:ElixClient AND clientAddress.mrc_typeCodeValue:ElixAddress AND clientAddress.status:"W budowie" AND clientAddress.city:"Brzezie"

Ogólnie, im więcej warunków dołożymy do kryterium wyszukiwania, tym zapytanie zrealizowane zostanie w bardziej optymalny sposób.

Uwaga

Nieoptymalna konstrukcja zapytań złożonych może spowodować, że dane, które wyszukujemy nie zostaną znalezione. Jedną z akcji zaradczych jest ustawienie odpowiednio dużego parametru maxResults w kontekście wykonywanej operacji, zobacz definicję obiektu Context.

Klauzula PARENT

Specjalna klauzula zapytań złożonych. Zapytania zwracają listę spraw nadrzędnych (rodziców) spraw, które zostały znalezione w wyniku wyszukiwania.

Składnia:
(<zapytanie_złożone_wyszukujące_sprawy_podrzędne>) PARENT <nazwa_pola_sprawy_nadrzędnej>([dodatkowe_kryteria_dla_sprawy nadrzędnej])

gdzie:

  • <zapytanie_złożone_wyszukujące_sprawy_podrzędne> - (wymagany) to będzie zapytanie wyszukujące sprawę typu ElixAddress.
  • <nazwa_pola_sprawy_nadrzędnej> - (wymagany) to będzie będzie nazwa pola clientAddress w sprawie typu ElixClient.
  • [dodatkowe_kryteria_dla_sprawy nadrzędnej] - (opcjonalny) to będzie dodatkowe zapytanie wyszukujące sprawy typu ElixClient.
Przykład 1
(mrc_typeCodeValue:ElixAddress) PARENT clientAddress()

Opis przykładu: Znajdź wszystkie sprawy typu ElixAddress, a następnie zwróć rodziców (sprawy nadrzędne).

Przykład 2
(mrc_typeCodeValue:ElixAddress) PARENT clientAddress(mrc_typeCodeValue:ElixClient AND mrc_status:A)

Opis przykładu: Znajdź wszystkie sprawy typu ElixAddress, a następnie zwróć rodziców (sprawy nadrzędne), które są typu ElixClient i ich status jest aktywny.

Przykład 3
(mrc_typeCodeValue:ElixAddress AND orders.orderType:Umowa) PARENT clientAddress(mrc_typeCodeValue:ElixClient AND mrc_status:A)

Opis przykładu: Znajdź wszystkie sprawy adresów (sprawy typu ElixAddress), z którymi powiązane są zamówienia typu "Umowa", a następnie zwróć rodziców (sprawy nadrzędne), które są typu ElixClient i ich status jest aktywny.

Wskazówka

Zwróćmy uwagę, że klauzula PARENT praktycznie łamie zasadę ograniczenia 2 poziomu zagnieżdżenia budowania zapytań złożonych i daje możliwość dodania warunków na poziomie 3.

Wykorzystanie dodatkowego pola z zakresem dat

W niektórych usługach realizujących zadania wyszukiwania spraw możliwym jest zdefiniowanie dodatkowego kryterium na zakres dat jako wartość pola additionalDateRange (zobacz opis usługi CaseSearchExtRest). Zapytanie to zostanie dołączone do zapytania głównego <zapytanie_złożone_wyszukujące_sprawy_podrzędne>.

Klauzula CHILD

Specjalna klauzula zapytań złożonych. Zapytana zwracają listę spraw podrzędnych (dzieci) spraw, które zostały znalezione w wyniku wyszukiwania.

Składnia:
(<zapytanie_złożone_wyszukujące_sprawy_nadrzędnej>) CHILD <nazwa_pola_sprawy_nadrzędnej>()

gdzie:

  • <zapytanie_złożone_wyszukujące_sprawy_nadrzędnej> - (wymagany) to będzie zapytanie wyszukujące sprawę typu ElixClient.
  • <nazwa_pola_sprawy_nadrzędnej> - (wymagany) to będzie będzie nazwa pola clientAddress w sprawie, które jest referencją do sprawy podrzędniej (dziecka).
Przykład 1
(mrc_typeCodeValue:ElixClient AND clientAddress.status:\"W budowie\" AND clientAddress.city:Brzezie) CHILD clientAddress()

Opis przykładu: Znajdź wszystkie sprawy których adres klienta ma status "W budowie" i znajduje się w mieście "Brzezie" i zwróć przypisany do pola clientAddress obiekt sprawy podrzędnej. W wyniku zapytania otrzymamy listę spraw typu ElixAddress.

Klauzula "Group By"

Usługi wykorzystujące klauzulę Group By zwracają wyniki zagregowanych danych. Zbiór danych, które poddane zostaną agregacji zawężamy odpowiednim zapytaniem do indeksu Lucene, które podajemy w osobnym argumencie usługi.

Wskazówka

Aby realizować zadania agregacji danych rekomenduje się wykorzystanie usługi REST CaseSearchExtRest#groupByQuery(POST) i jej odpowiedników w implementacji SOAP i RMI. Zobacz również stronę z przykładami wykorzystania klauzuli "Group By" Przykłady agregacji danych.

Uwaga

Opisane funkcje agregujące dostępne jest w oprogramowaniu o wersji minimum 3.0.2.0.2. Aby sprawdzić wersję oprogramowania zobacz artykuł Jaka jest wersja mojego systemu?.

Uwaga

Implementacja klauzuli Group By wymaga by wartości pól, do których ją stosujemy, muszą być składowane w indeksie Lucene. Informację o tym, czy wartość danego pola jest przechowywana w indeksie, uzyskasz używając metod usługi CaseIndexerFieldsManagerRest. W odpowiedzi, obiekt IndexField opisujący pole wyszukiwania indeksu Lucene, zawiera dane informujące nas o tym czy wartość pola przechowywana jest w indeksie, czy też nie.

Klauzula pozwala na korzystanie z funkcji przetwarzających i agregujących dane, które możemy podzielić na dwie kategorie:

  • Funkcje przekształcające - funkcje, której wynikiem jest nowa wartość pola w wierszu.
  • Funkcja zliczające - funkcje, których wynikiem jest agregacja danych składowanych w różnych wierszach.

Funkcje przekształcające

Do funkcji przekształcających zaliczamy implementacje poniższych opisanych funkcji.

MATH

Funkcja MATH zwraca wynik wyrażenia matematycznego, ma następującą składnię:

Składnia:
MATH(<wyrażenie>)

Parametry (argumenty):

  • <wyrażenie> - wyrażenie matematyczne. Obsługę wyrażenia matematycznego realizuje implementacja klasy Java pl.slawas.math.MathExpression (biblioteka zrealizowana w ramach projektu sccommon-utils).
MathExpression

MathExpression - prosta obsługa wyrażeń arytmetycznych.

 Grammar:
expression = term | expression `+` term | expression `-` term
term = factor | term `*` factor | term `/` factor
factor = `+` factor | `-` factor | `(` expression `)`
| number | functionName factor | factor `^` factor

Wspierane funkcje i operatory matematyczne. Ważne są separatory spacji pomiędzy elementami wyrażenia z wyjątkiem potęgowania!

  • + dodawanie np. 1 + 2, a + b
  • - odejmowanie np. 2 - 1, a - b
  • * mnożenie np. 1 * 2, a * b
  • / dzielenie np. 1 / 2, a / b
  • sqrt pierwiastek kwadratowy np. sqrt(16), sqrt(a)
  • sin sinus, podstawą jest liczba stopni, np, sin(16), sin(a)
  • cos cosinus, podstawą jest liczba stopni, np, cos(16), cos(a)
  • tan tangens, podstawą jest liczba stopni, np, tan(16), tan(a)
  • ^ potęgowanie np. 1^2, a^b
Przykład wykorzystania funkcji MATH:
"groupByClause": "id, MATH(mrc_endDate - mrc_createDate) as howLong",

CONCAT

Funkcja CONCAT zwraca wynik (ciąg) połączenia dwóch lub więcej wartości ciągu. Ma następującą składnię:

Składnia:
CONCAT( <wyrażenie1>, <wyrażenie2> [, <wyrażenie>]... )

Parametry (argumenty):

  • <wyrażenie1>, <wyrażenie2>, <wyrażenie> - wartość ciągu do połączenia z innymi wartościami.
Przykład wykorzystania funkcji CONCAT:
"groupByClause": "concat(clientID, \": \", clientName) as acronym"

DECODE

Funkcja DECODE ma funkcjonalność instrukcji IF-THEN-ELSE. Ma następującą składnię:

Składnia:
DECODE( <wyrażenie>, <warunek> , <wynik> [, <warunek> , <wynik>]... , <domyślnie> )

Parametry (argumenty):

  • <wyrażenie> - wartość do porównania. Jest on automatycznie konwertowany na typ danych pierwszej wartości wyszukiwania przed porównaniem. Wyrażeniem może może być również inna funkcja przekształcająca.
  • <warunek> - wartość porównywana z wyrażeniem. Wszystkie wartości wyszukiwania są automatycznie konwertowane na typ danych pierwszej wartości wyszukiwania przed porównaniem. Warunek jako kryterium wyszukania, może przyjmować również zakres danych np. [* TO 125000] - wyrażenie ma przyjmować wartości mniejsze lub równe 125000.
  • <wynik> - wartość zwracana, jeśli wyrażenie jest spełnia warunek.
  • <domyślnie> - jeśli nie zostaną znalezione żadne dopasowania, funkcja DECODE zwróci wartość domyślną. Wartość domyślna jest wymagana.
Przykład 1: Wykorzystanie funkcji DECODE gdzie jako wyrażenie użyto wartości pola
"groupByClause": "id, decode(months.costs, \"[* TO 125000]\", \"low\", \"[125001 TO 140000]\", \"medium\", \"high\") as cost, count(*) as count"
Przykład 2: Wykorzystanie funkcji DECODE gdzie jako wyrażenie użyto funkcji MATH
"groupByClause": "id, decode(MATH(mrc_endDate - mrc_createDate), \"[* TO -1]\", \"still open\", \"[0 TO 86400000]\", \"1 day\", \"[86400001 TO 172800000]\", \"2 days\", \"very long\") as cost, count(*) as count"

TRUNC

Funkcja TRUNC(data) służy do pobierania daty z częścią obciętą do określonej jednostki miary. Działa według zasad kalendarza gregoriańskiego. Ma następującą składnię:

Składnia:
TRUNC ( data [, format ] )

Parametry (argumenty):

  • data - wartość daty do obcięcia.
  • format - opcjonalnie, określenie jednostki miary do jakiej ma być obcięta data. Wartość pola format może przyjmować następujące wartości:
    • YY - z dokładnością co do roku
    • MO - z dokładnością co do miesiąca
    • WE - z dokładnością co do numeru tygodnia w roku
    • DD - z dokładnością co do dnia
    • HD - z dokładnością co do połowy dnia
    • HH - z dokładnością co do godziny
    • MI - z dokładnością co do minuty
    • SS - z dokładnością co do sekundy
Przykład wykorzystania funkcji TRUNC:
"groupByClause": "trunc(mrc_createDate, DD) as createDatePerMonth, count(1) as count"

Funkcje zliczające (agregujące)

Do funkcji zliczających (agregujących) zaliczamy implementacje poniższych funkcji.

SUM

Funkcja SUM jest funkcją agregującą, która zwraca sumę wszystkich wartości w zestawie wartości. Ma następującą składnię:

Składnia:
SUM ( <wyrażenie> )

Parametry (argumenty):

  • <wyrażenie> - nazwa pola, którego wartości mają zostać sumowane.
Przykład wykorzystania funkcji SUM:
"groupByClause": "id, SUM(months.costs) as sum"

MIN

Funkcja MIN jest funkcją agregującą zwracającą minimalną wartość wyrażenia. Ma następującą składnię:

Składnia:
MIN ( <wyrażenie> )

Parametry (argumenty):

  • <wyrażenie> - nazwa pola, na podstawie którego ma zostać wyznaczona wartość minimalna.
Przykład wykorzystania funkcji MIN:
"groupByClause": "id, MIN(months.costs) as min"

MAX

Funkcja MAX jest funkcją agregującą zwracającą maksymalną wartość wyrażenia. Ma następującą składnię:

Składnia:
MAX( <wyrażenie> )

Parametry (argumenty):

  • <wyrażenie> - nazwa pola, na podstawie którego ma zostać wyznaczona wartość maksymalna.
Przykład wykorzystania funkcji MAX:
"groupByClause": "id, MAX(months.costs) as max"

COUNT

Funkcja COUNT jest funkcją agregującą zwracającą liczbę wyrażenia. Ma następującą składnię:

Składnia:
COUNT ( <wyrażenie> )

Parametry (argumenty):

  • <wyrażenie> - wartości, które mają być policzone.
Przykład wykorzystania funkcji COUNT:
"groupByClause": "trunc(mrc_createDate, DD) as createDatePerMonth, COUNT(1) as count"

AVG

Funkcja AVG jest funkcją agregującą zwracającą średnią arytmetyczną wartość wyrażenia. Ma następującą składnię:

Składnia:
AVG ( <wyrażenie> )

Parametry (argumenty):

  • <wyrażenie> - nazwa pola, na podstawie którego ma zostać wyznaczona średnia arytmetyczna.
Przykład wykorzystania funkcji MAX:
"groupByClause": "id, AVG(months.costs) as avg"