Blog | Entwickler | | 10 Minuten Lesezeit

Eine Produktempfehlungs-Engine in reinem SQL erstellen

Eine Produktempfehlungs-Engine in reinem SQL erstellen

Wichtigste Erkenntnisse

  • Actian Zen V17 AI integriert Vektor-Embeddings, Ähnlichkeitssuche und Vektoroperationen direkt in SQL.
  • In diesem Tutorial wird eine semantische Produktempfehlungs-Engine erstellt, ohne dass eine separate Vektordatenbank oder eine „ Datensynchronisierung “-Schicht erforderlich ist.
  • Produkt-Embeddings werden in einer Vektorsammlung gespeichert und anhand ihrer Ähnlichkeit durchsucht, um verwandte Artikel zu identifizieren.
  • Standard-SQL-JOINs kombinieren Vektor-Rankings mit Echtzeit-Geschäftsregeln wie Lagerbestand, Preisgestaltung und Produktverfügbarkeit.
  • Das gleiche „ einbetten “-Such- und JOIN-Muster kann die semantische Suche ( Betrugserkennung), die Dublettenbereinigung und andere KI-gestützte Anwendungen unterstützen.

Wenn Sie KI-gestützte Anwendungen entwickeln, jonglieren Sie wahrscheinlich mit mehreren Systemen: Ihrer Hauptdatenbank, einem separaten Vektorspeicher und vielleicht einer dazwischenliegenden Caching-Ebene. Was wäre, wenn Sie diese Komplexität hinter sich lassen könnten?

Dank der nativen VectorAI-Unterstützung ermöglicht Ihnen Actian Zen V17 AI, Embeddings zu „ verwalten “, Ähnlichkeitssuchen durchzuführen und Vektoroperationen direkt in SQL zu verarbeiten. Kein Kontextwechsel, keine externen API-Aufrufe, keine Datensynchronisation zwischen Systemen – nur das SQL, das Sie bereits kennen, plus eine neue Funktion.

Das Problem

Ein Kunde auf Ihrer E-Commerce-Website öffnet die Produktseite für einen „Gaming Laptop Pro“. Sie möchten darunter eine Spalte mit dem Titel „Kunden haben sich auch Folgendes angesehen“ anzeigen – basierend auf semantischer Ähnlichkeit und nicht nur auf der „gleichen Kategorie“.

Der übliche Weg, dies zu realisieren, besteht darin, eine separate Vektordatenbank einzurichten, einen Synchronisierungsjob zu programmieren, um sie mit Ihrer Produkttabelle auf dem neuesten Stand zu halten, und einen zusätzlichen Netzwerk-Hop zwischen Ihrer App und zwei Datenspeichern anstelle von einem einzufügen.

Actian Zen V17 AI integriert den Vektorspeicher direkt in die SQL-Engine. Embeddings, Ähnlichkeitssuche und CRUD-Operationen auf Vektor-„Sammlungen“ werden alle als gewöhnliche SQL-Anweisungen über dieselbe Verbindung ausgeführt, die Sie bereits für die Produkttabelle verwenden. In dieser Schritt-für-Schritt-Anleitung wird die oben beschriebene Empfehlungsfunktion in etwa 20 Minuten von Anfang bis Ende erstellt.

WAS SIE BENÖTIGEN: Actian Zen V17 AI, eine ODBC-Verbindung (pyodbc funktioniert einwandfrei) sowie Python mit installierten „Sentence Transformers“ zur lokalen Generierung von Embeddings; für diese Anleitung ist kein OpenAI-Schlüssel erforderlich.

Architekturübersicht

Bevor wir zum ersten „ abfragen “ kommen, hier zunächst ein Überblick darüber, was tatsächlich hinter den Kulissen von `dbo.vector_collection_search()` abläuft: Ihre Anwendung kommuniziert mit der SQL-Engine von Zen V17 genau wie bisher, und die SQL-Engine kommuniziert in Ihrem Namen mit einer neuen VDE-Schicht (Vector Data Engine).

Ihre Bewerbung

Python · Node.js · Java · C#

→ Zen V17 SQL-Engine

SRDE · DBO-Tabellenfunktionen

→ VDE-Motor

VectorAI DB · localhost:6573

→ Vektor-Speicher

Sammlungen auf der Festplatte

Actian Zen V17 mit AI VectorAI-Architektur: Die SQL-Engine verbindet relationale Daten mit der VDE-Vektorsuche.

Jede Anfrage, sei es eine Suche oder ein Schreibvorgang, durchläuft denselben Rundlauf:

1

Einbettung generieren

Die App wandelt Text und Bilder in Float-Arrays um

→ 2

SET VDE_CREDENTIAL

Authentifizierung im Session-Bereich, einmal pro Verbindung

→ 3

dbo.vector_*-Aufruf

Reine SQL-Anweisung über ODBC, kein CALL erforderlich

→ 4

VDE-Prozesse

ANN-Suche oder Schreiben im Vektorindex

→ 5

SQL-Nachbearbeitung

WHERE / JOIN / ORDER BY in der Ergebnismenge

→ 6

App erhält Ergebnisse

u64_id, Punktzahl, Nutzlast

Der Datenfluss verläuft von der Generierung der Anwendungs-Einbettung über die SQL-Engine zum VDE und zurück.

Der Credential-Handshake verdient es, gesondert erwähnt zu werden, da dies der einzige Schritt ist, bei dem man beim ersten Mal leicht Fehler machen kann:

Authentifizierungstoken

Vom VDE ausgegeben, beginnt mit „vdai_…“ – wird niemals im Text von „ abfragen “ oder in Protokollen gespeichert

Sitzungsvariable

SET VDE_CREDENTIAL = ‚token‘ — wird einmal pro Verbindung festgelegt und läuft bei Trennung der Verbindung ab

VDE-Zugriff gewährt

Ermöglicht die Verwendung von `vector_collection_create`, `vector_point_upsert` und `vector_collection_search` sowie alle CRUD-Operationen

Im VDE-Anmeldeprozess wird das Token als Sitzungsvariable gespeichert und aus allen Protokollen sowie unter Protokolle herausgefiltert.

SCHRITT 1 – Verbinden und Authentifizieren

Stellen Sie über ODBC mithilfe des Zen-Treibers eine Verbindung her. Bei einigen Instanzen sind zudem Anmeldedaten für eine VDE-Sitzung (Vector Data Engine) erforderlich, bevor ein „vector_*“-Aufruf ausgeführt werden kann. Legen Sie diese, falls bei Ihrer Instanz erforderlich, einmal pro Verbindung fest.

import pyodbc




conn = pyodbc.connect(

"Driver={Pervasive ODBC Interface};serverName=localhost;DBQ=Demodata;"

)

cur = conn.cursor()




# Only needed if your instance requires it — if this raises

# "Invalid SET statement", your instance authenticates via the

# ODBC connection itself and you can skip this line.

# cur.execute("SET VDE_CREDENTIAL = 'vdai_your_token_here'")

WARUM DAS WICHTIG IST: In Fällen, in denen es verwendet wird, wird das Token in einer Sitzungsvariablen statt im Text „ abfragen “ gespeichert, sodass es niemals im Plan-Cache, im Slow-abfragen -Protokoll oder unter Protokoll erscheint. SCHRITT 2 Erstellen Sie eine Vektorsammlung

In einer Sammlung werden Ihre Produkt-Embeddings gespeichert. Wählen Sie eine Dimension, die zu Ihrem Embedding-Modell passt, sowie eine Abstandsmetrik, die zu den Daten passt. Bei Text-Embeddings ist die Kosinus-Metrik die Standardwahl.

-- 384 dimensions matches the sentence-transformers model we'll use below

dbo.vector_collection_create ( 'product_embeddings',

'{

"dimension" : 384,

"distance_metric" : "cosine"

}' );

HINWEIS: Die Dimension wird bei der Erstellung festgelegt. Wenn Sie später das Einbettungsmodell wechseln möchten, löschen Sie die Sammlung und erstellen Sie sie neu – eine Größenanpassung vor Ort ist nicht möglich.

SCHRITT 3 – Produkt-Embeddings generieren und laden

Hier ist der Katalog, den wir indexieren: fünf Produkte, jedes mit einem Namen und einer Beschreibung, die wie folgt lauten eingebettet:

ID Name Beschreibung
1 Gaming-Laptop Pro 32 GB RAM, RTX-Grafikkarte, 165-Hz-Display
2 Ultrabook für den geschäftlichen Einsatz Leicht, Akku für den ganzen Tag, Fingerabdrucksensor
3 Mechanische Tastatur RGB-hinterleuchtete, im laufenden Betrieb austauschbare Schalter
4 4K-Monitor 27″ HDR, USB-C, höhenverstellbarer Ständer
5 Drahtlose Gaming-Maus 16.000 DPI, programmierbare Tasten

Kodieren Sie jede Beschreibung mit einem lokalen Einbettungsmodell und laden Sie sie anschließend in die Sammlung:

from sentence_transformers import SentenceTransformer

import json




model = SentenceTransformer('all-MiniLM-L6-v2')  # 384-dim, runs locally




products = [

(1, 'Gaming Laptop Pro', '32GB RAM, RTX graphics, 165Hz display'),

(2, 'Business Ultrabook', 'Lightweight, all-day battery, fingerprint reader'),

(3, 'Mechanical Keyboard', 'RGB backlit, hot-swappable switches'),

(4, '4K Monitor 27"', 'HDR, USB-C, height-adjustable stand'),

(5, 'Wireless Gaming Mouse', '16000 DPI, programmable buttons'),

]




# Batch-encode all descriptions in one model call…

texts = [f"{name}. {desc}" for _, name, desc in products]

vectors = model.encode(texts).tolist()




# …then load them all in a single dbo.vector_point_upsert_batch call.

ids = [pid for pid, _, _ in products]

payloads = [json.dumps({'name': name}) for _, name, _ in products]




# Escape single quotes in the assembled JSON — real product names/

# descriptions can contain apostrophes, which would otherwise break

# out of the SQL string literal below.

ids_json = json.dumps(ids).replace("'", "''")

vectors_json = json.dumps(vectors).replace("'", "''")

payloads_json = json.dumps(payloads).replace("'", "''")




cur.execute(f"""

dbo.vector_point_upsert_batch (

'product_embeddings',

'{ids_json}',

null,

'{vectors_json}',

'{payloads_json}',

{len(ids)},

384

)

""")

conn.commit()

HINWEIS: Es gibt außerdem die Funktion „dbo.vector_point_upsert_batch“, die mehrere Vektoren in einem einzigen Aufruf zusammenfasst.

SCHRITT 4 – Ähnlichkeitssuche durchführen

Und nun der Clou: Ein Kunde schaut sich den „Gaming Laptop Pro“ an. einbetten Die „ abfragen “ funktioniert genauso wie eingebettet im Katalog – suchen Sie einfach nach:

query_vector = model.encode("Gaming Laptop Pro. 32GB RAM, RTX graphics, 165Hz display").tolist()




cur.execute(f"""

SELECT TOP (3)

a.u64_id, a."SIMILARITY_SCORE", a.payload

FROM dbo.vector_collection_search (

'product_embeddings', 'vector',

'{json.dumps(query_vector)}', 3, 384

) a

WHERE a.u64_id != 1                  -- exclude the product being viewed

ORDER BY a."SIMILARITY_SCORE" DESC

""")




for row in cur.fetchall():

# Positional indexing — unquoted identifiers get case-folded

# by the engine, so row.<name> attribute access isn't reliable.

print(row[0], row[1], row[2])   # u64_id, SIMILARITY_SCORE, payload

Ergebnisse dieses Katalogs (gemessen anhand eines tatsächlichen Durchlaufs mit ausschließlich MiniLM-L6-v2 – Ihre genauen Zahlen können je nach Modellversion leicht abweichen, die Rangfolge bleibt jedoch unverändert):

Rang Produkt Ähnlichkeit
1 Drahtlose Gaming-Maus 0.44
2 4K-Monitor 27″ 0.37
3 Mechanische Tastatur 0.25

Die Gaming-Maus führt die Liste an, dicht gefolgt vom Monitor und der Tastatur – das Modell erfasst gemeinsame Begriffe rund um technische Daten und Elektronik, nicht eine fest programmierte „Gaming“-Kategorie. Beachten Sie, dass die Werte selbst eher bescheiden ausfallen: Die tatsächliche Kosinus-Ähnlichkeit bei kurzen technischen Begriffen eines kleinen Modells wie diesem liegt typischerweise deutlich unter 1,0, selbst bei wirklich verwandten Begriffen – erwarten Sie keine nahezu perfekten Werte außerhalb von fast identischen Textpassagen.

HINWEIS: Wenn Sie es gewohnt sind, Demos einzubetten, die bei verwandten Artikeln eine Ähnlichkeit von 80–90 % aufweisen, handelt es sich dabei in der Regel um längere, semantisch reichhaltigere Texte. Kurze Formulierungen aus technischen Datenblättern wie diese lassen sich weniger deutlich komprimieren, sodass Werte im Bereich von 0,2 bis 0,5 für tatsächlich verwandte Artikel hier normal sind.

SCHRITT 5 – Geschäftsregeln mit einem JOIN hinzufügen

Eine reine Ähnlichkeitsrangliste ist noch keine einsatzfähige Empfehlung – Sie müssen weiterhin Lagerbestände und Preise berücksichtigen. Da die Ergebnisse als normale Ergebnismenge zurückgegeben werden, führen Sie wie bei jedem anderen „ abfragen “ einen JOIN mit Ihrer Produkttabelle durch:

cur.execute(f"""

SELECT TOP (3)

p.product_id, p.name, p.price, p.in_stock,

sr."SIMILARITY_SCORE"

FROM dbo.vector_collection_search (

'product_embeddings', 'vector',

'{json.dumps(query_vector)}', 10, 384

) sr

INNER JOIN products p ON CAST(sr.u64_id AS INTEGER) = p.product_id

WHERE sr."SIMILARITY_SCORE" > 0.15

AND p.in_stock = 1

AND p.product_id != 1

ORDER BY sr."SIMILARITY_SCORE" DESC

""")

Beachten Sie, dass die Suche 10 statt 3 Kandidaten liefert – lassen Sie Spielraum, bevor die relationalen Filter angewendet werden, damit ein nicht vorrätiger Artikel Ihre endgültige Auswahl nicht auf zwei Produkte reduziert. In diesem Katalog weist der 4K-Monitor zwar den zweithöchsten Rohähnlichkeitswert auf (0,37, noch vor der Tastatur mit 0,25), ist aber nicht vorrätig, sodass der JOIN ihn korrekt aussortiert und die Tastatur sowie das Ultrabook seinen Platz einnehmen. Genau darin liegt der Sinn der Überlagerung von SQL-Filtern auf die Vektorsuche: Die Rangfolge, die ein Modell erzeugt, und die Rangfolge, die Sie tatsächlich ausliefern können, sind zwei verschiedene Dinge.

SCHRITT 6 – Wählen Sie einen Ähnlichkeitsschwellenwert aus

Der oben genannte Schwellenwert (0,15) ist bewusst niedrig gewählt – er dient dazu, die Funktionsweise von JOIN zu veranschaulichen, und stellt keine Empfehlung für den produktiven Einsatz dar. Vektorähnlichkeitswerte sind nur im Zusammenhang mit Ihrem „ use case “, Ihrem Einbettungsmodell und der typischen Formulierung Ihrer Daten aussagekräftig. Betrachten Sie die folgende Tabelle als Ausgangspunkt für die Kalibrierung und nicht als universelle Konstante:

Use case Typischer relativer Bereich Warum
Deduplizierung Höchste Stufe Nur nahezu identische Datensätze
Dringende Empfehlung Höchste Stufe Gleiche Produktreihe
Empfehlung (dieses Tutorial) Mittlere Preisklasse, hier 0,15–0,40 Verwandt, nicht identisch
Semantische Suche Unteres bis mittleres Segment Im Großen und Ganzen zum Thema
Anomalie / Betrugserkennung Unterste Stufe Als verdächtig markieren, was nicht den bekannten Mustern entspricht

Betrachten Sie die konkreten Zahlen nicht als allgemeingültig – unsere eigenen Ergebnisse aus Schritt 4 (0,44 bis hinunter auf 0,25 für tatsächlich verwandte Produkte) liegen weit unter dem Bereich von 0,65–0,85, der in Tutorials angegeben wird, die auf längeren, semantisch reichhaltigeren Texten basieren. Modell, Einbettungsdimension und Phrasenlänge beeinflussen die absolute Skala. Was sich jedoch übertragen lässt, ist die relative Reihenfolge: Duplikatausschluss > exakte Übereinstimmung > lose Empfehlung > breite thematische Übereinstimmung > Ausreißer. Beginnen Sie mit großzügigen Einstellungen, protokollieren Sie die Werte, die Ihre Abfragen in der Produktion tatsächlich zurückgeben, und verschärfen Sie den Schwellenwert, sobald Sie Ihre eigene tatsächliche Verteilung erkennen können.

Wo dieses Muster sonst noch zum Tragen kommt

Die oben beschriebene Empfehlungsmaschine ist eine Ausprägung eines allgemeineren Musters: „ einbetten “ → Suche → JOIN mit relationalen Regeln. Die gleichen drei Schritte lassen sich direkt übertragen:

  • Semantische Dokumentensuche – Ersetzen Sie Produktbeschreibungen durch Support-Artikel oder Einträge in der Wissensdatenbank; gleicher Suchaufruf, höhere Top-N-Rate.
  • Betrugserkennung — einbetten Transaktionsmuster anstelle von Produkttexten und ordnen Sie den zurückgegebenen SIMILARITY_SCORE mithilfe einer CASE-Anweisung den Risikostufen zu.
  • Deduplizierung – derselbe Suchaufruf, Schwellenwert auf über 0,95 angehoben, dient dazu, nahezu identische Kunden- oder Katalogdatensätze vor dem Einfügen zu kennzeichnen.

Kurzübersicht: Die von Ihnen verwendeten Funktionen

Alles oberhalb der API ist reines SQL, ohne Wrapper in Form von gespeicherten Prozeduren. Halten Sie diese Tabelle während der Entwicklung griffbereit:

Betrieb Funktion
Eine Sammlung erstellen dbo.vector_collection_create(name, config)
Einen Vektor einfügen oder aktualisieren dbo.vector_point_upsert(collection, id, vector, payload)
Viele Vektoren einfügen dbo.vector_point_upsert_batch(collection, ids, uuids, vectors, payloads, count, dim)
Suche dbo.vector_collection_search(collection, field, abfragen_vector, topN, dim)
Einen Vektor löschen dbo.vector_point_delete(collection, id)
Viele Vektoren löschen dbo.vector_point_delete_batch(Sammlung, IDs, UUIDs, Anzahl)
Eine Sammlung einreichen dbo.vector_collection_drop(name)

Was du geschaffen hast

In sechs Schritten und ohne SQL zu verlassen, haben Sie: eine Vektorsammlung erstellt, reale Produkteinbettungen mit einem einzigen Batch-Aufruf geladen, eine semantische Ähnlichkeitssuche durchgeführt, die Ergebnisse anhand des aktuellen Bestands und der aktuellen Preise mit einem einfachen JOIN gefiltert und einen fundierten Schwellenwert festgelegt, anstatt ihn einfach zu schätzen.

Von hier aus lässt sich das gleiche Muster auf Ihren tatsächlichen Katalog übertragen: Ersetzen Sie das Beispiel mit den fünf Zeilen durch einen Batch-Job für Ihre gesamte Produkttabelle, richten Sie die Suche auf das Produkt aus, das ein angemeldeter Kunde gerade ansieht, und speichern Sie die Top-N-Ergebnisse für Ihre Spalte „Kunden haben sich auch angesehen“ im Cache.

PROBIEREN SIE ALS NÄCHSTES FOLGENDES AUS: Nehmen Sie die Variante zur Betrugserkennung aus dem Abschnitt „Wo dieses Muster sonst noch Anwendung findet“ und passen Sie die oben genannten Schritte 4–6 so an, dass Sie den SIMILARITY_SCORE mithilfe einer CASE-Anweisung anstelle einer WHERE-Klausel in Risikostufen einteilen.