Suche verstehen
Wir helfen Ihnen dabei, die Such­anfragen Ihrer Kund*innen und Mit­ar­bei­­ter*innen zu ver­stehen und schnell re­le­vante Ergebnisse zu liefern.

Facettierung mit Solr

In diesem Artikel geben wir einen Überblick über die Möglichkeiten der Facettierung in Solr 5.2.

Was ist Facettierung

Die Facettierung unterteilt die Suchergebnisse in mehrere Kategorien und zählt, wie viele Dokumente in den einzelnen Kategorien enthalten sind. Diese Kategorien werden typischerweise direkt aus den Eigenschaften der Dokumente ermittelt, so dass die Facettierung einen schnellen Überblick über die Zusammensetzung des Suchergebnisses bietet.
Dieser Überblick kann in weiterer Folge verwendet werden, um das Suchergebnis weiter einzuschränken.

Wie kann man in Solr facettieren?

Seit Solr 5 ist es möglich, die Facettensuche nicht nur mit Query-Parametern, sondern auch mittels einer strukturierten JSON API durchzuführen. Mit dieser JSON API ist nicht nur die Abbildung aller bisherigen Varianten möglich, sondern auch die Erstellung sogenannter Sub-Facets. Mit diesen Sub-Facets wird es möglich, Facetten ineinander zu verschachteln.

Die Beispieldaten und Konfigurationen sind als Download verfügbar:
[call-to-action id=”857″/]

Die Daten können mit folgendem Befehl in den Index gespielt werden:

curl "http://localhost:8983/solr/used-cars/update/json?commit=true"\
--data-binary @used-cars.json\
-H "Content-type:application/json"

Verschiedene Arten von Facetten in Solr

Abfragefacette (query facet)

Pro Query wird ermittelt, wie viele Dokumente auf diese Query passen.

Wir wollen nun die Anzahl aller Fahrzeuge in unserem Bestand wissen, deren Motorleistung zwischen 80 und 100 kW liegen.

json.facet={
  high_power: { query : "engine_power:[80 TO 200]" }
}
"response":{"numFound":24,"start":0,"docs":[]},
"facets":{
  "count":24,
  "high_power":{"count":9}
}

 

Begriffsfacette (terms facet)

Pro Begriff des gewünschten Feldes wird ermittelt, wie viele Dokumente diesen Begriff tragen.

Das Feld muss im Schema entweder als indexed=true oder docValues=true definiert sein.

Uns interessiert welche Marken wir in unserem Bestand haben.

json.facet={
 makes : { field : "make" }
}
"response":{"numFound":24,"start":0,"docs":[]},
"facets":{
  "count":24,
  "makes":{
    "buckets":[
      {"val":"Volkswagen","count":4},
      {"val":"Ford","count":3},
      {"val":"Opel","count":3},
      {"val":"Mercedes-Benz","count":2},
      {"val":"Renault","count":2},
      {"val":"Seat","count":2},
      {"val":"Audi","count":1},
      {"val":"BMW","count":1},
      {"val":"Buick","count":1},
      {"val":"Citroen","count":1}
    ]
  }
}

Bereichsfacette (range facet)

Pro Bereich wird ermittelt, wie viele Dokumente in diesem Bereich liegen.

Dies ist nur bei numerischen Feldern oder Datumsfeldern möglich. Da die Bereichsfacette weitere Argumente (start, end, gap) benötigt, ist keine Kurzschreibweise möglich. Die Parameter “type” und “field” müssen explizit ausgewiesen werden.

Nun wollen wir die Anzahl der Fahrzeuge innerhalb der Preisspanne zwischen 1.000 und 1.600 in 100er Schritten aufgelistet haben.

json.facet={
  prices : { type : "range", field : "price", start:1000, end:1600, gap:100 }
}
"response":{"numFound":24,"start":0,"docs":[]},
"facets":{
  "count":24,
  "prices":{
    "buckets":[
      {"val":1000,"count":5},
      {"val":1100,"count":3},
      {"val":1200,"count":5},
      {"val":1300,"count":4},
      {"val":1400,"count":4},
      {"val":1500,"count":2}
    ]
  }
}

Statistiken als Facetten in Solr

Mittels der folgenden Aggregatsfunktionen ist es möglich, Statistiken auf Dokumentfeldern zu errechnen:

AggregierungEffektBeispiel
sumSumme bildensum(price)
avgDurchschnitt bildenavg(number_of_seats)
sumsqSumme der Quadratesumsq(price)
minMinimum Wert ermittelnmin(number_of_doors)
maxMaximum Wert ermittelnmax(number_of_doors)
uniqueAnzahl der unterschiedlichen Einzelwerte ermittelnunique(engine_power)
hllAnzahl der Einzelwerte mithilfe des HyperLogLog Algorithmus ermittelnhll(engine_power)
percentilePerzentil berechnenpercentile(mileage, 2)

Unterfacetten (sub-facets)

Jede bisher gezeigte Facette ist eigentlich eine Unterfacette, da es immer eine implizite Facette – genannt Domäne – gibt. Die Domäne besteht aus allen Dokumenten des Suchergebnises. Eine Unterfacette wird einer bestehenden Facette als Parameter hinzugefügt.

Wir wollen zu allen Topmarken, die wir in unserem Bestand haben, auch die Autos mit den stärksten Motoren ermitteln:

json.facet={
  top_makes : { type : "terms", field : "make", limit : 3, facet :{
    top_engine_power : { type : "terms", field : "engine_power", limit : 3 }
  }}
}
"response":{"numFound":24,"start":0,"docs":[]},
"facets":{
  "count":24,
  "top_makes":{
    "buckets":[
      {"val":"Volkswagen",
        "count":4,
        "top_engine_power":{
          "buckets":[
            {"val":55,"count":2},
            {"val":37,"count":1},
            {"val":81,"count":1}
      ]}},
      {"val":"Ford",
        "count":3,
        "top_engine_power":{
          "buckets":[
            {"val":44,"count":1},
            {"val":85,"count":1},
            {"val":96,"count":1}
      ]}},
      {"val":"Opel",
        "count":3,
        "top_engine_power":{
          "buckets":[
            {"val":33,"count":1},
            {"val":74,"count":1},
            {"val":85,"count":1}
      ]}}
   ]}
 }
}

Sortierung von Facetten

Normalerweise werden die Facettenergebnisse absteigend nach ihrer Anzahl sortiert. Es kann jedoch über den Parameter “sort” angegeben werden, wie sortiert werden soll.

Wir interessieren uns nur für die Automarken, von denen wir die wenigsten Fahrzeuge haben:

json.facet={
  top_makes : { type : "terms", field : "make", limit : 3,  sort : { count : asc }}
}
"response":{"numFound":24,"start":0,"docs":[]},
"facets":{
  "count":24,
  "top_makes":{
    "buckets":[
      {"val":"Audi","count":1},
      {"val":"BMW","count":1},
      {"val":"Buick","count":1}
  ]}
}

[call-to-action-consulting /]

Facetten mit Mehrfachauswahl (multi-select faceting)

Wenn wir eine Filterquery anwenden und dabei gleichzeitig eine Facette auf das Feld errechnen, werden alle Ergebnisse, die durch die Filterquery ausgeschlossen werden, 0 als Anzahl erhalten.
Damit die Anzahl der nicht selektierten Werte errechnet werden kann, muss der Filter mit einem Tag versehen und dieses beim Facettieren ausgeschlossen werden.

Dies war bisher nur über lokale Parameter möglich. Seit Version 5.2 ist dies auch über die JSON API möglich.

Im folgenden Beispiel wurden mittels einer Filterquery die Auswahl auf Volkswagen eingegrenzt:

fq=make:Volkswagen&json.facet={
  top_makes : { type : "terms", field : "make", limit : 3,
    facet :{ top_engine_power : { type : "terms", field : "engine_power", limit : 3 }}
    }
  }

Leider ist nun auch die Facettierung nur auf Volkswagen eingeschränkt. Im Ergebnis wurden alle Werte für “top_makes” mit der Anzahl 0 ausgenommen.

"response":{"numFound":4,"start":0,"docs":[]},
"facets":{
  "count":4,
  "top_makes":{
    "buckets":[
      {"val":"Volkswagen","count":4,
        "top_engine_power":{
          "buckets":[{"val":55,"count":2},{"val":37,"count":1},{"val":81,"count":1}]
        }
     }]
  }
}

Damit wir trotz Filterquery eine Facettierung für die anderen Hersteller erhalten, müssen wir ein Tag verwenden.
In Zeile 1 sehen wir die Syntax um mit einem lokalen Parameter ein Tag zu erzeugen.
In Zeile 4 wird das vorher definierte Tag nun zu den excludeTags aufgenommen, um die getaggte Filterquery bei der Facettierung nicht zu berücksichtigen.

fq={!tag=make}make:Volkswagen&
json.facet={
  top_makes : { type : "terms", field : "make",
    excludeTags:"make",
    limit : 3,
    facet :{ top_engine_power : { type : "terms", field : "engine_power", limit : 3 }}
  }
}

Nun erhalten wir auch die anderen Topmarken in unserer Abfrage.

"response":{"numFound":4,"start":0,"docs":[]},
"facets":{
  "count":4,
  "top_makes":{
    "buckets":[
      {"val":"Volkswagen","count":4,"top_engine_power":{"buckets":[{"val":55,"count":2},{"val":37,"count":1},{"val":81,"count":1}]}},
      {"val":"Ford","count":3,"top_engine_power":{"buckets":[{"val":44,"count":1},{"val":85,"count":1},{"val":96,"count":1}]}},
      {"val":"Opel","count":3,"top_engine_power":{"buckets":[{"val":33,"count":1},{"val":74,"count":1},{"val":85,"count":1}]}}
    ]
  }
}

Weitere Beispiele

  • Welche Preise sind für die Fahrzeuge mit einer Motorleistung zwischen 100kw und 200kW hinterlegt.
json.facet={
  high_power: { type : "query",   q : "engine_power:[100 TO 200]",
  facet : { max_price : "max(price)", min_price : "min(price)", avg_price : "avg(price)" }
 }
}
"response":{"numFound":24,"start":0,"docs":[]},
"facets":{"count":24,
  "high_power":{
    "count":3,
    "max_price":1600.0,
    "min_price":1200.0,
    "avg_price":1433.3333333333333
  }
}
  • Uns interessieren die Preise für alle Marken, von denen wir mindestens zwei Fahrzeuge im Bestand haben, deren Motorleistung zwischen 40kw und 80kw liegt.
json.facet={
  high_power: { type : "query",   q : "engine_power:[40 TO 80]", 
  facet : {
    top_makes : { type : "terms", field : "make", mincount : 2, 
      facet : { max_price : "max(price)", min_price : "min(price)", avg_price : "avg(price)"}
     }
   }
  }
}
"response":{"numFound":24,"start":0,"docs":[]},
"facets":{"count":24,
  "high_power":{"count":13,
    "top_makes":{"buckets":[
      {"val":"Renault","count":2,"max_price":1400.0,"min_price":1100.0,"avg_price":1250.0},
      {"val":"Seat","count":2,"max_price":1400.0,"min_price":1000.0,"avg_price":1200.0},
      {"val":"Volkswagen","count":2,"max_price":1100.0,"min_price":1000.0,"avg_price":1050.0}
  ]}}
}