Von Meik Duck auf Mittwoch, 27. Mai 2026
Kategorie: Data Management

PostgreSQL als KI-Enabler: Teil 3 – Hybrid Search mit Metadaten-Filtern und Full-Text Search

Im zweiten Teil dieser Serie wurde gezeigt, wie eine vollständige RAG-Pipeline auf Basis von PostgreSQL aufgebaut wird: Texte werden über ein lokales Embedding-Modell in Vektoren umgewandelt, mithilfe von pgvector in einer PostgreSQL-Datenbank gespeichert und semantisch durchsucht. Die Ergebnisse werden als Kontext an ein lokales LLM übergeben, das daraus eine strukturierte Antwort formuliert. Das Ergebnis ist eine funktionsfähige, lokal betreibbare KI-Pipeline ohne externe APIs.

Dieser Beitrag baut darauf auf und zeigt, wie die Suchqualität durch zwei konkrete Erweiterungen verbessert werden kann: Metadaten-Filter für strukturierte Einschränkungen und Full-Text Search für lexikalische Treffsicherheit. Kombiniert ergeben diese Methoden eine sogenannte Hybrid Search mit der Option zum Filtern, die robuster und präziser ist als eine Vektorsuche allein. 

Eine Vektorsuche ist gut, aber nicht unfehlbar

Die semantische Suche aus Teil 2 funktioniert eindrucksvoll. Eine Anfrage wie „Ich möchte Japan besuchen” findet Tokio, Kyoto und Osaka, obwohl das Wort „Japan” in keinem Datensatz vorkommt. Das Embedding-Modell hat gelernt, dass diese Städte zu Japan gehören und bildet diese Beziehung im Vektorraum ab.

Doch die Vektorsuche hat eine strukturelle Schwäche. Sie denkt in Wahrscheinlichkeiten, nicht in Kategorien. Wenn ein Nutzer explizit nach einem Strandurlaub in Asien unter 1000 Euro sucht, hat die Vektorsuche keine Möglichkeit, Ergebnisse aus anderen Kategorien zuverlässig auszuschließen. Ein thematisch ähnliches Ergebnis aus Europa oder der Luxusklasse kann in der Ergebnisliste trotzdem weit oben erscheinen, weil die semantische Ähnlichkeit hoch genug ist.

Das ist kein Fehler des Modells, sondern eine systembedingte Eigenschaft. Vektoren codieren Bedeutung, keine Kategorien. Für exakte Einschränkungen braucht es relationale Logik.

Metadaten-Filter: Die Stärke von SQL zurückgewinnen

Die einfachste und wirkungsvollste Erweiterung ist die Kombination von Vektorsuche mit klassischen WHERE-Bedingungen. PostgreSQL erlaubt beides in einer einzigen Abfrage, da Vektorspalten und reguläre Spalten gleichwertig in derselben Tabelle liegen. 

Wie die Tabelle erweitert wird

Im Vergleich zu Teil 2 bekommt die Tabelle drei zusätzliche Spalten: kategorie, kontinent und preis_klasse. Diese Metadaten beschreiben das Angebot strukturiert und sind direkt filterbar. 

Beim Befüllen der Tabelle werden die Metadaten gemeinsam mit dem Text und dem Embedding gespeichert: 

Text, Vektor und Metadaten stehen in derselben Zeile, in derselben Tabelle. Die Daten, die semantisch zusammengehören, liegen auch physisch zusammen. 

Wie die gefilterte Suche aussieht

Die Vektorsuche mit Metadaten-Filtern kombiniert das Beste aus zwei Welten: semantische Ähnlichkeit aus dem Embedding und präzise Einschränkungen aus SQL. 

Die WHERE-Bedingung greift, bevor die Distanzberechnung stattfindet. PostgreSQL durchsucht ausschließlich die Zeilen, die den Bedingungen entsprechen. Das reduziert nicht nur die Ergebnismenge auf relevante Treffer, sondern beschleunigt auch die Suche selbst. 

Was erreichen wir dadurch

Ohne Filter findet eine Suche nach „entspannter Luxusurlaub am Wasser” möglicherweise eine Wellness-Auszeit in den österreichischen Alpen mit hohem Ähnlichkeitswert, weil Bergpanoramen und Ruhe semantisch verwandt mit Entspannung sind. Mit den Filtern kontinent = 'Asien' und preis_klasse = 'luxus' werden ausschließlich Einträge berücksichtigt, die beide Bedingungen erfüllen. Die Vektorsuche findet dann die semantisch passendsten Angebote innerhalb dieser Teilmenge.

Metadaten-Filter sind immer dann der richtige Ansatz, wenn Nutzer:innen strukturierte Erwartungen haben, die klar in einem Datenmodell abgebildet werden können, beispielsweise: Kategorien, Preisklassen, Zeiträume, Sprachen oder Zugriffsrechte. 

Full-Text Search: Wenn Bedeutung allein nicht reicht

Die Vektorsuche findet semantisch Verwandtes. Metadaten-Filter grenzen strukturiert ein. Aber es gibt eine dritte Dimension, in der beide Methoden an ihre Grenzen stoßen: lexikalische Präzision.

Sucht ein:e Nutzer:in explizit nach „Santorini”, wird ein Ergebnis über Santorini erwartet und nicht über semantisch ähnliche Küstenorte auf anderen Inseln. Die Vektorsuche kann diesen Unterschied nicht garantieren, weil ein Santorini-Eintrag und ein Mykonos-Eintrag im Vektorraum sehr nah beieinanderliegen können. Full-Text Search hingegen findet genau das, was buchstäblich im Text steht. 

Was ist Full-Text Search?

Full-Text Search (FTS) ist eine lexikalische Suchmethode, die Texte nicht als Zeichenketten, sondern als Mengen linguistisch normalisierter Begriffe behandelt. Der Kernunterschied zu einem einfachen LIKE-Operator: FTS versteht Sprache auf Wortebene.

Konkret bedeutet das: „Strände”, „Strand” und „Strandurlaub” werden auf denselben Wortstamm reduziert. Häufige Wörter wie „und”, „in” oder „der” werden als bedeutungslose Stopwörter ignoriert. Das Ergebnis ist ein tsvector, eine kompakte Repräsentation des Textes, die nur die suchrelevanten Begriffe enthält. 

Stopwörter wie „auf” und „und” fehlen im Ergebnis. Die übrigen Terme sind auf ihre Grundform normalisiert. Genau dieser tsvector wird durchsucht, wenn eine Full-Text-Abfrage gestellt wird. 

Wie Full-Text Search in PostgreSQL funktioniert

PostgreSQL bringt FTS als nativen Bestandteil mit, ohne Extension. Zwei Funktionen sind zentral:

to_tsvector('german', text) wandelt einen Text in einen durchsuchbaren Vektor aus normalisierten Termen um. Der erste Parameter gibt die Sprache an, die für Stemming und Stopwörter verwendet wird, wobei Stemming bedeutet, dass Wörter auf ihren Wortstamm zurückgeführt werden, sodass „Strände”, „Strand” und „Strandurlaub” als dasselbe Konzept erkannt werden.

to_tsquery('german', 'Strand | Meer') wandelt eine Suchanfrage in ein strukturiertes Suchobjekt um. Der Operator „|” steht für ODER, „&” für UND sowie „!” für Negation.

plainto_tsquery('german', text) ist die einfachere Variante: Sie nimmt einen freien Text entgegen und erstellt daraus automatisch eine AND-verknüpfte Suchanfrage, ohne dass Operatoren manuell gesetzt werden müssen.

Die eigentliche Suche erfolgt mit dem Operator @@: 

Diese Abfrage findet alle Einträge, die sowohl einen Stamm von „Strand” als auch einen Stamm von „Tempel” enthalten, unabhängig von der genauen Wortform.

ts_rank berechnet einen numerischen Relevanzwert zwischen 0 und 1 für jeden Treffer. Je häufiger und prominenter die gesuchten Terme im Text vorkommen, desto höher der Score.

PostgreSQL unterstützt zwar eine deutsche Sprachkonfiguration für Stemming und Stopwörter, allerdings mit Einschränkungen. Die mitgelieferte deutsche Wörterliste ist vergleichsweise klein, sodass unbekannte Wörter nicht auf ihren Stamm reduziert werden. „Strandurlaub” landet dann als ganzes Wort im Index, statt auf „strand” und „urlaub” aufgeteilt zu werden. Wer robustere Ergebnisse benötigt, kann auf die Erweiterung pg_trgm ausweichen, die zeichenbasiert sucht und sprachunabhängig funktioniert. 

Wie ein generierter tsvector-Index die Performance sichert

Statt to_tsvector bei jeder Abfrage neu zu berechnen, empfiehlt sich eine generierte Spalte. PostgreSQL berechnet den tsvector automatisch beim Einfügen und Aktualisieren von Zeilen und speichert ihn persistent.  

Hybrid Search: Alle Methoden kombinieren

Metadaten-Filter, semantische Vektorsuche und Full-Text Search lösen jeweils ein anderes Problem. Kombiniert ergibt sich eine Suchmethode, die strukturiert einschränkt, semantisch findet und lexikalisch präzisiert. 

Die Grundidee

Die Kombinationslogik ist ein gewichtetes Scoring: Jeder Eintrag erhält einen Hybrid-Score, der aus zwei Teilen besteht, dem FTS-Score und dem Vektor-Score, jeweils mit einem Gewichtungsfaktor versehen.

hybrid_score = fts_score * 0.4 + vector_score * 0.6

Die Gewichtung von 60 % für den Vektor und 40 % für FTS ist ein Ausgangswert, der je nach Anwendungsfall angepasst werden kann. Für Anwendungen, in denen exakte Begriffe entscheidend sind, kann das FTS-Gewicht erhöht werden. Für stark semantische Anfragen überwiegt der Vektor-Anteil. 

Alle drei Methoden in einer einzigen Abfrage:

Die Query gliedert sich in drei Common Table Expressions (CTE). kandidaten wendet zuerst die Metadaten-Filter an und schränkt die Suche auf Asien, Luxusklasse ein. In fts wird Full-Text Search innerhalb der gefilterten Kandidaten ausgeführt und ein ts_rank-Score berechnet. In vec wird die Vektorsuche ebenfalls auf die Kandidaten beschränkt und auf die 50 ähnlichsten Einträge limitiert. Im abschließenden SELECT werden beide Scores per LEFT JOIN zusammengeführt. coalesce stellt sicher, dass Einträge, die nur in einer der beiden Suchen vorkommen, dennoch berücksichtigt werden, mit einem Score von 0 für die fehlende Komponente. 

Warum LEFT JOIN und nicht INNER JOIN?

Ein INNER JOIN würde nur Einträge zurückliefern, die sowohl in den FTS-Ergebnissen als auch in den Vektor-Ergebnissen vorhanden sind. Das wäre zu restriktiv: Ein Eintrag könnte semantisch sehr relevant sein, ohne dass die gesuchten Begriffe buchstäblich vorkommen. Der LEFT JOIN stellt sicher, dass alle Kandidaten eine Chance haben, solange sie in mindestens einem der beiden Systeme eine positive Bewertung erhalten. 

Was gegenüber reiner Vektorsuche besser wird

Ein Eintrag über Tokio mit expliziter Erwähnung von „Städteurlaub” erhält jetzt einen doppelten Vorteil: einen hohen Vektor-Score wegen semantischer Nähe zur Anfrage und einen positiven FTS-Score, weil der Begriff buchstäblich im Text vorkommt. Ein Eintrag, der semantisch ähnlich ist, den Begriff aber nicht enthält, fällt im Vergleich zurück. Das Ergebnis ist eine Rangliste, die sowohl Bedeutung als auch Begrifflichkeit berücksichtigt. 

Fazit

Reine Vektorsuche ist ein leistungsfähiger Einstieg in semantische Suche. Für produktionsreife Anwendungen reicht sie allein aber selten aus. Nutzer:innen haben strukturierte Erwartungen, die in Metadaten abgebildet werden müssen. Und sie verwenden konkrete Begriffe, die lexikalisch treffen sollen, nicht nur semantisch.

Die Methoden aus diesem Beitrag lassen sich gut miteinander kombinieren. Metadaten-Filter nutzen SQL-Logik, die PostgreSQL seit Jahrzehnten beherrscht. Full-Text Search ist ein nativer Bestandteil des Datenbanksystems, ohne Extension. Vektorsuche kommt über pgvector hinzu. Alle drei laufen in einer einzigen Query, auf einer einzigen Tabelle, in einer einzigen Datenbank.

Die Architektur aus Teil 2 bleibt unverändert. Der Austausch der einfachen Vektorsuchabfrage gegen die Hybrid-Query erfordert keine strukturellen Änderungen an der RAG-Pipeline. LLM-Aufruf, Prompt-Konstruktion und Kontextübergabe funktionieren identisch, nur die Qualität der eingereichten Dokumente steigt. 

Seminarempfehlungen

Verwandte Beiträge

Kommentare hinterlassen