Skip to Main Content

Verschachtelte Dokumente in Solr

In unterschiedlichen Projekten bin ich immer wieder auf eine Problemstellung gestoßen, die sich mit Solrs Funktion für verschachtelte Dokumente leicht lösen lassen sollte. Aber jedes Mal war es ziemlich schwierig, passende Informationen dazu zu finden.

Meine Erkenntnisse will ich mit dir teilen: in dieser Anleitung zeige ich dir mit ein paar einfachen Beispielen, wie du verschachtelte Dokumente in Solr mit PHP und Solarium indizierst und abfragen kannst.

Was sind verschachtelte Dokumente?

Verschachtelte Dokumente in Solr sind echt praktisch, wenn es darum geht, zusammenhängende Datensätze abzufragen. Diese Funktion macht es leichter, komplexe Datenbeziehungen zu verwalten und zu durchsuchen.

Man kann sich verschachtelte Dokumente, wie verschachtelte Arrays in PHP vorstellen:

$parent = [
	'id' => 1,
	'name' => 'Parent',
	'children' => [
		[
			'id' => 1,
			'name' => 'Child 1'
		],
		[
			'id' => 2,
			'name' => 'Child 2'
		]
	],
];

 

 

Anpassung des Solr-Schemas

Um verschachtelte Dokumente in Solr zu verwenden, ist es wichtig, dass dein Schema bestimmte Felder und Typen hat, die genau dafür gemacht sind. Bevor du mit verschachtelten Dokumenten arbeiten kannst, musst du also zuerst diese Anpassungen im Schema vornehmen. Hier ein Beispiel, wie deinen Config aussehen könnte:

fields.xml

<fields>
	<!-- Fields for nested document functionality -->
	<field name="_root_" type="string" indexed="true" stored="false" docValues="false" />
	<field name="_nest_path_" type="_nest_path_" stored="true"/>
	<field name="_text_" type="text" indexed="true" stored="false" multiValued="true"/>
	<field name="_nest_parent_" type="string" indexed="true" stored="true"/>

	<!-- Custom field for nested locations -->
	<field name="locations" type="locations" indexed="true" stored="true"/>

</fields>

 

 

types.xml

<types>
	<!-- Type for nested document functionality -->
	<fieldType name="_nest_path_" class="solr.NestPathField"/>

	<!-- Custom type for the nested locations need to be multiValued="true" -->
	<fieldType name="locations" class="solr.TextField" indexed="true" stored="true" multiValued="true"/>

</types>

 

 

Für mehr Informationen, wie du verschachtelte Dokumente in Solr indizierst, schau dir die genauen Anleitungen in der Solr-Dokumentation an Indexing Nested Documents in Solr.

 

Daten vorbereiten

Stell dir vor, wir haben ein Unternehmen (Elternteil) und müssen Informationen über Werte in seine verschiedenen Standorte (Kinder) abfragen. Für die verschachtelten Dokumente in Solr braucht jedes Kind-Dokument eine einzigartige ID. Die setzt sich zusammen aus der ID des Unternehmen und der ID des jeweiligen Standorts, getrennt durch ein Ausrufezeichen ("!").
Die Feldnamen müssen mit dem Solr-Schema übereinstimmen.

Hier ist ein Beispiel mit einem hypothetischen Unternehmen, "TechNova":

$company =  [
	'id' => 'ee26f491-d406-438b-8b9f-2b0f99bbe6de',
	'type' => 'company',
	'name' => 'TechNova',
	'industry' => 'Technology',
	'yearFounded' => 2001,
	'locations' => [
		[
			'id' => 'ee26f491-d406-438b-8b9f-2b0f99bbe6de!cbdc1b9b-7871-4401-9a1f-c0140a507a2b',
			'type' => 'location',
			'name' => 'TechNova HQ',
			'department' => 'Research & Development',
			'country' => 'USA',
			'numberOfEmployees' => 1300,
		],
		[
			'id' => 'ee26f491-d406-438b-8b9f-2b0f99bbe6de!785332dc-edc2-4932-bd1e-e97f8a2c2db7',
			'type' => 'location',
			'name' => 'TechNova Europe',
			'department' => 'Sales & Marketing',
			'country' => 'Germany',
			'numberOfEmployees' => 700,
		],
		[
			'id' => 'ee26f491-d406-438b-8b9f-2b0f99bbe6de!187da16e-ba13-4ea0-a080-a5489824c50b',
			'type' => 'location',
			'name' => 'TechNova Asia',
			'department' => 'Customer Support',
			'country' => 'Singapore',
			'numberOfEmployees' => 500,
		],
	],
];

 

 

Indizierung mit Solarium

Aus unseren Beispiel-Daten erstellen wir mit Solarium ein Dokument, das wir dann in einen konfigurierten Solr-Core schreiben können. Wenn du mehr darüber erfahren willst, wie man verschachtelte Dokumente indexiert, wirf einfach einen Blick in die Dokumentation:

$curl = new Curl();

$client = new Client($curl, new EventDispatcher(), [
	'endpoint' => [
		'core_en' => [
			'scheme' => 'http',
			'host' => localhost,
			'port' => 8983,
			'path' => '/',
			'core' => 'core_en',
		],
	],
]);

$update = $client->createUpdate();
$document = $update->createDocument($company);
$update->addDocuments([$document]);
$update->addCommit(null, false, null);
$result = $client->update($update);

 

 

Abfrage verschachtelter Dokumente

Als Beispiel nehmen wir uns die Suche nach Unternehmen vor, bei der wir sowohl die Werte des Unternehmens als auch die seiner Standorte berücksichtigen. Solr macht es möglich, solche verschachtelten Informationen effektiv abzufragen. Wenn du tiefer in das Thema einsteigen möchtest und genau wissen willst, wie die Suche nach verschachtelten Dokumenten in Solr funktioniert, findest du ausführliche Informationen in der Solr-Dokumentation. Schau dir dazu den Abschnitt Searching Nested Documents in Solr an.

 

Erstellung einer Solr-Abfrage

In unserem Beispiel sehen wir eine Suche, die sich auf Werte in den Unternehmen industry:("Technology OR Finance") und auf Werte in den Standorten {!parent which="type:company"} numberOfEmployees:[1 TO 500] fokussiert. Interessant dabei ist, dass du diese Suche leicht um zusätzliche Felder oder um einen Freitext-Suchbegriff erweitern kannst. So kannst du die Suche genau auf deine Bedürfnisse anpassen und genau die Informationen finden, die du brauchst.

Kleiner Tipp:
Wenn du in fields den Wert *,[child limit=1000] hinzufügst, kannst du verschachtelte Dokumente zusammen mit den übergeordneten Dokumenten abfragen.

$query = $client->createSelect([
	'query' => 'type:company',
	'fields' => '*,[child limit=1000]',
	'sort' => ['name' => 'ASC'],
	'rows' => 10000,
]);

$query->addParam('fq', [
	'industry:("Technology OR Finance")',
	'{!parent which="type:company"} numberOfEmployees:[1 TO 500]'
]);

$resultSet = $client->select($query);

 

 

Einen Freitext-Suchbegriff für bessere Suchergebnisse einbauen

Jetzt wollen wir uns ansehen, wie wir unsere Suche um einen Freitext-Suchbegriff für bestimmte Felder erweitern können.

In meinem Beispiel nutze ich Copy-Fields für die Felder, die mit dem Freitext-Suchbegriff durchsucht werden können. Diese Copy-Fields sind mit zusätzlichen Analyzern, Tokenizern und Filtern konfiguriert. Das hilft uns, genauere Treffer bei der Suche zu erzielen.

Falls du mehr über Analyzer, Tokenizer und Filter erfahren möchtest, schau mal in die Solr-Dokumentation: Understanding Analyzers, Tokenizers, and Filters.


Erstellen der Solarium-Abfrage

Hier sehen wir, wie man eine Abfrage mit einem Freitext-Suchbegriff aufbaut. Zusätzlich legen wir für bestimmte Felder eine Gewichtung fest (name_search^10, country_search^9, industry_search^8). Das Caret-Symbol (^) mit einer Zahl dahinter zeigt das Gewicht jedes Feldes an und bestimmt so deren Bedeutung bei der Suche. Durch diese Einstellung in Solr kannst du bestimmte Felder wichtiger machen als andere. Dadurch werden die Suchergebnisse relevanter, basierend auf den festgelegten Gewichten.

$query = $client->createSelect([
    'query' => 'type:company',
    'fields' => '*,[child limit=1000]',
    'sort' => ['name' => 'ASC'],
    'rows' => 10000,
]);

$query->addParam('fq', [
    'industry:("Technology OR Finance")',
    '{!parent which="type:company"} numberOfEmployees:[1 TO 500]'
]);

// Integrating the Search Term
$searchTerm = 'Nova';
$eDismax = $query->getEDisMax();
$eDismax->setQueryFields('name_search^10 country_search^9 industry_search^8');
$query->setQuery($query->getQuery() . ' AND ' . $searchTerm . '*');

$resultSet = $client->select($query);

 

 

Facettierung für Werte in verschachtelten Dokumenten

Als nächstes sehen wir uns an, wie wir eine Facettierung für Werte, die in den Standorten vorkommen, erstellen können. Ich habe recherchiert und herausgefunden, dass "Json Facet Terms" in Solarium die einzige Lösung ist, mit der ich unsere Anforderungen umsetzen konnte.

Dazu erstellen wir ein Array, das einen local_key (den Namen des Feldes, in dem die Standorte stehen, also locations) und ein field (den Namen des Feldes, für das die Facette erstellt werden soll) enthält. Im domain-Teil des Arrays geben wir dann blockChildren mit einem Query für die Unternehmen (type:company) und einen filter für die Facettierung an. Diese Konfiguration erstellt eine Liste von department-Werten, die zu deiner Suche passen, inklusive ihrer Anzahl. Für detailliertere Informationen zur Erstellung solcher Facetten mit der JSON Facet API in Solr, siehe die JSON Facet API-Dokumentation im Apache Solr Referenzhandbuch.

$query = $client->createSelect([
    'query' => 'type:company',
    'fields' => '*,[child limit=1000]',
    'sort' => ['name' => 'ASC'],
    'rows' => 10000,
]);
$query->addParam('fq', ['industry:("Technology OR Finance")']);

$facetSet = $query->getFacetSet();
$nestedJsonFacetTerms = $facetSet->createJsonFacetTerms([
    'local_key' => 'locations',
    'limit' => 10000,
    'field' => 'department',
    'domain' => [
        'blockChildren' => 'type:company',
        'filter' => 'type:location AND {!parent which="type:company"} numberOfEmployees:[1 TO 500]'
    ],
], false);
$facetSet->addFacet($nestedJsonFacetTerms);
$resultSet = $client->select($query);
$facets = $resultSet->getFacetSet()?->getFacet('locations');

 

 

Mein Ziel ist es, mit diesem Beitrag einen Leitfaden für die Arbeit mit verschachtelten Dokumenten in Solr unter Verwendung von PHP und Solarium zu bieten. Er deckt Grundlagen, Schema-Anpassungen, Daten-Vorbereitung sowie fortgeschrittene Themen wie Abfragen und Facettierung ab, soweit mir dies in dem Rahmen möglich ist.
Ausgestattet mit praktischen Beispielen und Code-Snippets kann er eine gute Ressource sein, um die Möglichkeiten von Solr in Projekten besser zu nutzen.
Meine Lösungen sind sicherlich nicht die einzigen Möglichkeiten, aber sie sind die einzigen, die ich aktuell gefunden habe und, die für meine Anforderungen funktioniert haben.

Ich wünsche dir viel Erfolg und Spaß beim Programmieren mit Solr!  🚀 🔍