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"