nach oben
indoqa. IT-Experten, die Dinge zu nachhaltigen Lösungen entwickeln.

Block-Join Query mit Apache Solr

Solr kennt mit Block-Joins neben Joins eine zweite Variante, wie bei Abfragen Beziehungen zwischen Dokumenten berücksichtigt werden können. Im Gegensatz zu den im Artikel Join Query mit Apache Solr vorgestellten Variante sind Block-Joins wesentlich performanter. Ihr Nachteil ist, dass die Beziehung (Parent-Child oder 1:n) zwischen den Dokumenten bereits zur Indexierungszeit festgelegt und damit bekannt sein muss.

Dieser Artikel beschreibt anhand eines Beispiels, wie mit Hilfe von Block-Joins ein Buch indexiert werden kann. Ein Buch wird dazu als Parent-Dokument indiziert, ein Kapitel als Child-Dokument.

Benötigen Sie professionelle Unterstützung oder haben Sie konkrete Fragen zu Solr? Indoqa bietet Solr Consulting auf Basis jahrelanger Erfahrung in Analyse, Planung und Umsetzung von Suchanwendungen.
Indoqa Solr Experten kontaktieren

Email

Nachricht

Parent-Child Dokumente indexieren

Solr Schema

Hier ein Auszug der relevanten Felder eines Schema, das ein Buch anhand seiner Kapitel in mehrere Solr-Dokumente aufteilt:

<field name="_root_" type="string" indexed="true" stored="false"/>
<field name="id" type="string" indexed="true" stored="true" required="true" multiValued="false" />
<field name="type" type="string" indexed="true" stored="true" />
<field name="isbn" type="string" indexed="true" stored="true" />
<field name="name" type="text_general" indexed="true" stored="true" />

Folgende Felder sind inbesondere für Block-Joins zu beachten:

_root_
Dieses Feld darf nicht explizit befüllt werden – es wird aber Solr-intern zur Verwaltung der Parent-Child-Beziehungen benötigt.

id
Jedes Dokument, unabhängig davon ob es ein Parent oder ein Child-Dokumente ist, hat einen eindeutigen Kennzeichner.

isbn
Die ISBN dient als Gruppierungsfeld, das für alle Dokumente derselben Gruppe denselben Wert enthält. Dies ist für Expansions (siehe unten) notwendig.

Beispieldaten

Die Beispieldaten bestehen aus zwei Büchern mit jeweils zwei Kapiteln:

SolrInputDocument book1 = new SolrInputDocument();
book1.setField("id", "978-1617291029_book");
book1.setField("name", "Solr in Action");
book1.setField("isbn", "978-1617291029");
book1.setField("type", "book");

SolrInputDocument book1_chapter1 = new SolrInputDocument();
book1_chapter1.setField("id", "978-1617291029_chapter1");
book1_chapter1.setField("name", "Introduction to Solr");
book1_chapter1.setField("isbn", "978-1617291029");
book1_chapter1.setField("type", "chapter");
book1.addChildDocument(book1_chapter1);

SolrInputDocument book1_chapter2 = new SolrInputDocument();
book1_chapter2.setField("id", "978-1617291029_chapter2");
book1_chapter2.setField("name", "Getting to know Solr");
book1_chapter2.setField("isbn", "978-1617291029");
book1_chapter2.setField("type", "chapter");
book1.addChildDocument(book1_chapter2);

SolrInputDocument book2 = new SolrInputDocument();
book2.setField("id", "978-1783553150_book");
book2.setField("name", "Solr Cookbook - Third Edition");
book2.setField("isbn", "978-1783553150");
book2.setField("type", "book");

SolrInputDocument book2_chapter1 = new SolrInputDocument();
book2_chapter1.setField("id", "978-1783553150_chapter1");
book2_chapter1.setField("name", "Apache Solr Configuration");
book2_chapter1.setField("isbn", "978-1783553150");
book2_chapter1.setField("type", "chapter");
book2.addChildDocument(book2_chapter1);

SolrInputDocument book2_chapter2 = new SolrInputDocument();
book2_chapter2.setField("id", "978-1783553150_chapter2");
book2_chapter2.setField("name", "Indexing your data");
book2_chapter2.setField("isbn", "978-1783553150");
book2_chapter2.setField("type", "chapter");
book2.addChildDocument(book2_chapter2);

Die Child-Dokumente werden den Parent-Dokumenten explizit hinzugefügt (siehe Zeilen 12, 19, 32 und 39). Solr stellt damit sicher, dass die Parent-Child-Dokumente als gemeinsamer Block abgelegt werden. Führt man später Updates durch, so muss wiederum der gesamte Block mit einer Operation indexiert werden.

Block-Join Queries

Abhängig davon ob man Child-Dokumente oder Parent-Dokumente als Ergebnis möchte, gibt es zwei Arten von Query-Parsern.

  1. Der Block-Join Parent Query Parser liefert Parent-Dokumente.
  2. Der Block-Join Child Query Parser liefert Child-Dokumente.

Diese werden über lokale Parameter ausgewählt bzw. konfiguriert. Wichtig ist dabei zu beachten, dass die jeweils definierten lokalen Parameter zur Konfiguration der Block-Join Query-Parser dienen und keine Filter sind.

Beispiel Block-Join Parent Query-Parser

{!parent which='type:book'}name:solr AND type:chapter

Ergebnis:

{
  "responseHeader":{
    "status":0,
    "QTime":1,
    "params":{
      "q":"{!parent which='type:book'}name:solr AND type:chapter",
      "wt":"json"
    }
  },
  "response":{
    "numFound":2,
    "start":0,
    "docs":[
      {
        "id":"978-1617291029_book",
        "name":"Solr in Action",
        "isbn":"978-1617291029",
        "type":"book",
        "_version_":1510930882274263040
      },
      {
        "id":"978-1783553150_book",
        "name":"Solr Cookbook - Third Edition",
        "isbn":"978-1783553150",
        "type":"book",
        "_version_":1510930882276360192
      }
    ]
  }
}

Diese Abfrage sucht in allen Dokumenten im Feld name nach solr. Da das Feld name sowohl von Parent- als auch von Child-Dokumenten verwendet werden, bedarf es noch einer weiteren Einschränkung auf den Dokumententyp chapter.

Der lokale Parameter which konfiguriert das Mapping aller Ergebnisse des Typs chapter auf die jeweiligen Parent-Dokumente des Typs book.

Das Ergebnis besteht somit ausschließlich aus Dokumenten des Typs book.

Block-Join Children Query-Parser

{!child of='type:book'}name:cookbook AND type:book

Ergebnis:

{
  "responseHeader":{
    "status":0,
    "QTime":1,
    "params":{
      "q":"{!child of='type:book'}name:cookbook AND type:book",
      "wt":"json"
    }
  },
  "response":{
    "numFound":2,
    "start":0,
    "docs":[
      {
        "id":"978-1783553150_chapter1",
        "name":"Apache Solr Configuration",
        "isbn":"978-1783553150",
        "type":"chapter"
      },
      {
        "id":"978-1783553150_chapter2",
        "name":"Indexing your data",
        "isbn":"978-1783553150",
        "type":"chapter"
      }
    ]
  }
}

Diese Abfrage sucht in allen Dokumenten im Feld name nach cookbook. Das Ergebnis darf keine Dokumente des Typs chapter enthalten. Dazu wird die Ergebnismenge auf Dokumente des Typs book eingeschränkt.
Der lokale Parameter of konfiguriert das Mapping aller Ergebnisse des Typs book auf die jeweiligen Children-Dokumente des Typs chapter.

Das Ergebnis besteht somit ausschließlich aus Dokumenten des Typs chapter.

Expansions

Bekommt man mit Hilfe des Block-Join Parent Query-Parsers Dokumente des Typs book und möchte wissen, in welchem Kapitel ein Suchbegriff gefunden wurde, kann diese Frage mittels Expansions in derselben Query beantwortet werden:

q: {!parent which='type:book'}name:solr AND type:chapter
expand: true
expand.field: isbn
expand.q: name:solr

Ergebnis:

{
  "responseHeader":{
    "status":0,
    "QTime":2,
    "params":{
      "q":"{!parent which='type:book'}name:solr AND type:chapter",
      "expand.field":"isbn",
      "expand":"true",
      "expand.q":"name:solr",
      "wt":"json"
    }
  },
  "response":{
    "numFound":2,
    "start":0,
    "docs":[
      {
        "id":"978-1617291029_book",
        "name":"Solr in Action",
        "isbn":"978-1617291029",
        "type":"book",
        "_version_":1510931124365295616
      },
      {
        "id":"978-1783553150_book",
        "name":"Solr Cookbook - Third Edition",
        "isbn":"978-1783553150",
        "type":"book",
        "_version_":1510931124367392768
      }
    ]
  },
  "expanded":{
    "978-1617291029":{
      "numFound":2,
      "start":0,
      "docs":[
        {
          "id":"978-1617291029_chapter1",
          "name":"Introduction to Solr",
          "isbn":"978-1617291029",
          "type":"chapter"
        },
        {
          "id":"978-1617291029_chapter2",
          "name":"Getting to know Solr",
          "isbn":"978-1617291029",
          "type":"chapter"
        }
      ]
    },
    "978-1783553150":{
      "numFound":1,
      "start":0,
      "docs":[
        {
          "id":"978-1783553150_chapter1",
          "name":"Apache Solr Configuration",
          "isbn":"978-1783553150",
          "type":"chapter"
        }
      ]
    }
  }
}

Die Query ist aus dem oben beschriebenen Beispiel bereits bekannt. Zusätzlich wird über einige Parameter die Expansion-Component konfiguriert:

  • Der Parameter expand schaltet die Expansion-Funktionalität ein oder aus.
  • Der Parameter expand.field muss jenes Feld referenzieren, das bei allen Dokumente des Blocks aus Parent-Child-Dokumenten denselben Wert hat. Im angeführten Beispiel ist dies die ISBN-Nummer.
  • Der Parameter expand.q enthält eine Abfrage auf welche Child-Dokumente eingeschränkt werden soll. In diesem Fall ist es die Suche nach solr im Feld name.

Alle drei angeführten Parameter sind notwendig, damit die Expansion-Component funktioniert. Zusätzlich kann noch mit dem Parameter expand.rows die Anzahl der Dokumente konfiguriert werden. Sein Default-Wert ist 5.

Scoring

Ab Solr 5.3 (siehe SOLR-6234) kann mit dem lokalen Parameter score konfiguriert werden, wie die Scores, die in der vor dem Mapping durchgeführten Query ermittelt wurden, auf die Zieldokumente übertragen werden sollen. Der Default-Wert ist none und der Score wird dabei auf den Wert 0 gesetzt. Alternativ kann avg (arithmetisches Mittel), max (höchster Score), min (niedrigster Score) gesetzt werden. Bis Solr 5.2.1 wird immer die Variante none verwendet.

Like it? Share it!