Introduktion till MarkLogic – Del 3, ännu mer runt sök

Share on FacebookShare on Google+Email this to someoneTweet about this on TwitterShare on LinkedIn

I mitt förra inlägg om sökning så gick jag igenom det som kallas för lågnivå API för sökning, cts:search()

Med cts:search så behöver jag bygga upp mina sökningar med hjälp av olika funktioner beroende på hur jag vill söka. Detta kanske kan skapa utmaningar för utvecklare i att kunna hantera alla varianter i fall vi skall försöka göra det flexibelt för användarna.

Frukta inte för det finns det en enklare lösning.

För att kunna använda mer Google linkande sökningar så använder man ett sök modul, search. Denna modul installeras automatiskt med MarkLogic, så du behöver inte ladda ner något.

All kod kan laddas ner här, i fall du inte vill skriva av/kopiera koden, högerklicka på Search part 2.xml och spara ned den lokalt. Du importera den in i Query Console genom att klicka på pilen vid Workspace längs till höger och välja Import Workspace.  Var noga och se till att välja nobelprize i Content Source innan du exekverar koden.

Så om vi vill göra en enkel sökning efter alla nobelpristagare där ordet nuclear förekommer någonstans så skriver jag med search. Glöm inte att välja nobelprize i Content Source i Query Console (http://localhost:8000/qconsole/)

import module namespace search = "http://marklogic.com/appservices/search"
                         at "/MarkLogic/appservices/search/search.xqy";

search:search("nuclear")

Enkel serach:search

När vi använder search:search så får vi ett lite annorlunda resultat, det är mer som en sökning i Google där vi får delar av dokumenten med sökorden markerade, <search:highlight> , samt också information om antalet träffar , hur många som visas per sida och även söksträngen (som ligger längs ner).

Vill jag använda AND och OR så skriver jag det, att sätta – framför ett ord motsvarar inte. Så nedan exempel söker efter alla dokument där order nuclear och usa förekommer eller economics och inte germany.

import module namespace search = "http://marklogic.com/appservices/search"
               at "/MarkLogic/appservices/search/search.xqy";

search:search("(nuclear AND usa) OR (economics -germany)")

Vi söka efter ord som ligger nära varandra så kan vi göra det med NEAR, /3 betyder att jag vill att orden skall finnas max tre ord från varandra och om jag inte anger något nummer utan bara NEAR så är standard max 10 ord separerade.

import module namespace search = "http://marklogic.com/appservices/search" 
            at "/MarkLogic/appservices/search/search.xqy";

search:search("studies NEAR/3 nuclear")

Det går givetvis att anpassa sökresultatet, detta görs via något som heter search options och är en parameter i sökanropet. I exemplet nedan så talar jag om att jag endast vill se firstname, surname, category, year och motivation i mitt sökresultat.

import module namespace search = "http://marklogic.com/appservices/search" 
           at "/MarkLogic/appservices/search/search.xqy";

search:search("(nuclear AND usa) OR (economics -germany)",<options xmlns="http://marklogic.com/appservices/search">
             <transform-results apply="metadata-snippet">
               <preferred-elements>
                 <element ns="http://marklogic.com/swe/nobel-prize" name="firstname"/>
                 <element ns="http://marklogic.com/swe/nobel-prize" name="surname"/>
                 <element ns="http://marklogic.com/swe/nobel-prize" name="category"/>
                 <element ns="http://marklogic.com/swe/nobel-prize" name="year"/>
                 <element ns="http://marklogic.com/swe/nobel-prize" name="motivation"/>
               </preferred-elements>
             </transform-results>
           </options>)

För att förenkla det hela så kan jag lägga mina inställningar i en egen variabel först, jag har även lagt resultatet från sökningen i en variabel och visar den med return.

import module namespace search = "http://marklogic.com/appservices/search"
             at "/MarkLogic/appservices/search/search.xqy";

let $search-options := <options xmlns="http://marklogic.com/appservices/search">
             <transform-results apply="metadata-snippet">
               <preferred-elements>
                 <element ns="http://marklogic.com/swe/nobel-prize" name="firstname"/>
                 <element ns="http://marklogic.com/swe/nobel-prize" name="surname"/>
                 <element ns="http://marklogic.com/swe/nobel-prize" name="category"/>
                 <element ns="http://marklogic.com/swe/nobel-prize" name="year"/>
                 <element ns="http://marklogic.com/swe/nobel-prize" name="motivation"/>
               </preferred-elements>
             </transform-results>
           </options>
let $result := search:search("(nuclear AND usa) OR (economics -germany)", $search-options)
return $result

transform med search:search

Om jag vill ha tillbaka hela dokumenten i mitt resultat, på samma sätt som med cts:search, kan jag använda <transform-results apply=”raw”> i mina search options.

import module namespace search = "http://marklogic.com/appservices/search"
             at "/MarkLogic/appservices/search/search.xqy";

let $search-options := <options xmlns="http://marklogic.com/appservices/search">
             <transform-results apply="raw">
             </transform-results>
           </options>
let $result := search:search("(nuclear AND usa) OR (economics -germany)", $search-options)
return $result

Transform Raw search:search

Men hur gör vi med search:search så att vi begränsar sökningen till specifika element?

Om vi vill hitta alla nobelpristagare som var verksamma i Sverige när de fick priset så behöver vi söka på elementet country, men hur begränsar jag till det?

Att skriva country = sweden eller country:sweden kommer  inte ge något resultat, du kan testa själv. Detta för att vi behöver tala om att vi vill kunna söka på specifika fält innan vi kan söka på dessa.

Man gör det genom att definiera constraints i de search options som används för frågan . På detta sätt kan vi styra användarens möjlighet att söka.

I nedan exempel så har vi en contstraint som heter land, vi kan kalla den vad vi vill, som pekar på fältet country. Vi kan nu söka efter Sverige med ”land:sweden”

import module namespace search = "http://marklogic.com/appservices/search" at "/MarkLogic/appservices/search/search.xqy";

let $search-options := <options xmlns="http://marklogic.com/appservices/search">
                         <constraint name="land">
                           <container>
                             <element ns="http://marklogic.com/swe/nobel-prize" name="country"/>
                           </container>
                         </constraint>
                       </options>

let $query := search:search("land:sweden", $search-options)
return $query

Constraint search:search

Det går givetvis att kombinera de olika search options exemplaren till en stor.

Hittills så har vi  sökt endast med hjälp an den indexering som sker i MarkLogic per automatik när vi laddar data. Indexeringen sker av termer, ord, och struktur, själva dokumentets uppbyggnad. Dock så har den begränsningar som till exempel att alla värden hanteras som text vilket gör att vi inte kan söka till exempel med större än, year > 2014, eller intervall, revenue between 2000 and 3000, eftersom MarkLogic inte vet om att det är andra datatyper.

Det som man använder för att hantera det är något som heter Range Indexes, vilket är mer specifika index där vi specificerar både fält och datatyp. Detta möjliggör för oss att använda större än och linkande men också möjligheter att använda så kallade facets, enkelt sätt är det en lista på alla unika värden i ett Range Index, eller kunna se hur ofta olika termer förekommer tillsammans, vilket kallas co-occurence, och det är också en förutsättning för att vi skall kunna aggregera, count, sum, min etc,  våra dokument.

Jag kommer inte att gå in i det olika detaljerna runt Range Index i detta inlägg, du kan läsa mer om det i Administrations guiden.

Vi kommer behöva index på country, category,   year,  gender, name, city och på våra geo kordinater , latitude och longitude. Följande skript skapar indexerna.

import module namespace admin = "http://marklogic.com/xdmp/admin" at "/MarkLogic/admin.xqy";

let $config := admin:get-configuration()
let $dbid := xdmp:database("nobelprize")

let $rangespec := admin:database-range-element-index("string","http://marklogic.com/swe/nobel-prize", "country", "http://marklogic.com/collation/", fn:false() )
let $config := admin:database-add-range-element-index($config, $dbid, $rangespec)

let $rangespec := admin:database-range-element-index("string","http://marklogic.com/swe/nobel-prize", "category", "http://marklogic.com/collation/", fn:false() )
let $config := admin:database-add-range-element-index($config, $dbid, $rangespec)

let $rangespec := admin:database-range-element-index("gYear", "http://marklogic.com/swe/nobel-prize", "year", "", fn:false() )
let $config := admin:database-add-range-element-index($config, $dbid, $rangespec)

let $rangespec := admin:database-range-element-index("string", "http://marklogic.com/swe/nobel-prize", "gender", "http://marklogic.com/collation/", fn:false() )
let $config := admin:database-add-range-element-index($config, $dbid, $rangespec)

let $rangespec := admin:database-range-element-index("string","http://marklogic.com/swe/nobel-prize", "name", "http://marklogic.com/collation/", fn:false() )
let $config := admin:database-add-range-element-index($config, $dbid, $rangespec)

let $rangespec := admin:database-range-element-index("string","http://marklogic.com/swe/nobel-prize", "city", "http://marklogic.com/collation/", fn:false() )
let $config := admin:database-add-range-element-index($config, $dbid, $rangespec)

let $rangespec := admin:database-geospatial-element-pair-index("","location", "", "latitude", "", "longitude" , "wgs84", fn:false() )
let $config := admin:database-add-geospatial-element-pair-index($config, $dbid, $rangespec)

return admin:save-configuration($config);

När vi exekverar koden så kommer det stå your query returned an empty sequence om allt gick bra. Vi skulle också kunna skapa våra index via administrationsgränsnittet, http://localhost:8001, i fall vi inte ville skriva kod.

Nu när vi har ytterligare index så kan vi till exempel göra en sökning efter alla nobelpristagare vars motivering innehåller nuclear och fått priset efter 2005.

xquery version "1.0-ml";
declare namespace np = "http://marklogic.com/swe/nobel-prize";
import module namespace search = "http://marklogic.com/appservices/search" at "/MarkLogic/appservices/search/search.xqy";

let $search-options := <options xmlns="http://marklogic.com/appservices/search">
                         <constraint name="motivation">
                           <container>
                             <element ns="http://marklogic.com/swe/nobel-prize" name="motivation"/>
                           </container>
                          </constraint>
                         <constraint name="year">
                           <range type="xs:gYear">
                             <element ns="http://marklogic.com/swe/nobel-prize" name="year"/>
                           </range>
                          </constraint>
                        </options>

let $query := search:search("motivation:nuclear AND year GT 2000", $search-options)
return $query

Vi kan också använda oss av buckets, hinkar, i vår sökning. Så om jag i stället för att söka på år mellan 1980 och 1989 vill välja 80talet så kan jag definiera intervall i mina search options.

import module namespace search = "http://marklogic.com/appservices/search"
             at "/MarkLogic/appservices/search/search.xqy";

let $search-options := <options xmlns="http://marklogic.com/appservices/search">
                          <constraint name="decade">
                            <range type="xs:gYear" facet="true">
                              <bucket lt="1930" ge="1920" name="1920s">1920s</bucket>
                              <bucket lt="1940" ge="1930" name="1930s">1930s</bucket>
                              <bucket lt="1950" ge="1940" name="1940s">1940s</bucket>
                              <bucket lt="1960" ge="1950" name="1950s">1950s</bucket>
                              <bucket lt="1970" ge="1960" name="1960s">1960s</bucket>
                              <bucket lt="1980" ge="1970" name="1970s">1970s</bucket>
                              <bucket lt="1990" ge="1980" name="1980s">1980s</bucket>
                              <bucket lt="2000" ge="1990" name="1990s">1990s</bucket>
                              <bucket lt="2010" ge="2000" name="2000s">1990s</bucket>
                              <bucket ge="2010" name="2010s">2010s</bucket>
                              <facet-option>limit=10</facet-option>
                              <element ns="http://marklogic.com/swe/nobel-prize" name="year"/>
                            </range>
                        </constraint>
                      </options>

let $query := search:search("decade:1980s", $search-options)
return $query

Vi har bara skrapat på ytan när det gäller de sökmöjligheter som finns i MarkLogic och jag kommer att skriva om ytterligare delar i senare inlägg.

En bra guide runt sökning är Search Developer’s Guide som går igenom detta och mycket mer. Det finns också en 5-minuters guide till sök api’et som har många bra exempel.

 

Share on FacebookShare on Google+Email this to someoneTweet about this on TwitterShare on LinkedIn

En reaktion på ”Introduktion till MarkLogic – Del 3, ännu mer runt sök

  1. Grymt bra genomgång.

    Har bara använt cts:search hitintills. Detta kommer att bli användbart!

    /Johan

Kommentarer inaktiverade.