Praktische Hinweise: PostgreSQL + pgvector sowie SQL Server 2025 als Vektordatenspeicher
Schritt-für-Schritt-Anleitung zu den Praktischen Hinweisen: PostgreSQL + pgvector sowie SQL Server 2025 als Vektordatenspeicher – Verträge, Überprüfungen und Code-Blöcke für Teams, die dieses Muster einsetzen.
Die folgenden Anmerkungen skizzieren einen praktischen Ansatz zu „PostgreSQL + pgvector und SQL Server 2025 als Vektordatenspeicher für RAG – Ein Leitfaden für Praktiker“. Der Schwerpunkt liegt auf Verträgen, Überprüfungen sowie Code-Platzhaltern statt auf motivierenden Formulierungen.
Das Vektordatenbank-Landschaftsjahr 2025
Während der Phase „Das Vektordatenbank-Landschaftsjahr“ sollten Sie zunächst den Vertrag festhalten: erforderliche Eingaben, Erfolgsindikatoren sowie das Vorgehen bei teilweisen Fehlern. Diese Checkliste sorgt dafür, dass spätere Codeänderungen transparent bleiben. Betrachten Sie diese Phase als Vertrag zwischen Eingaben und validierten Ausgaben. Benennen Sie die Artefakte, definieren Sie Erfolgsprüfungen und lehnen Sie stille, teilweise abgeschlossene Ergebnisse ab. Messen Sie die Recall-Rate anhand eines festgelegten Fragebogens, bevor Sie Prompts anpassen. Häufige Anpassungen der Prompts beheben selten ein schwaches Suchverhalten.
-- Find the most relevant chunks, but only from documents
-- belonging to enterprise-tier customers — a single SQL query
SELECT c.content, 1 - (c.embedding <=> query_vec) AS score
FROM rag_chunks c
JOIN documents d ON d.filename = c.source
JOIN customers cu ON cu.id = d.customer_id
WHERE cu.tier = 'enterprise'
ORDER BY score DESC
LIMIT 5;
Der Full Stack
Beim Arbeiten an der The Full Stack-Phase sollten Sie zunächst den Vertrag aufschreiben: erforderliche Eingaben, Erfolgsignal sowie das Vorgehen bei teilweisen Fehlern. Diese Checkliste sorgt dafür, dass spätere Codeänderungen transparent bleiben. Notieren Sie außerdem die Laufzeiten sowie die Kosten für Tokens oder Abfragen neben den funktionalen Ergebnissen. Eine frühzeitige Sichtbarkeit der Kosten verhindert überraschende Rechnungen, wenn der Weg von einer Demo in gemeinsame Umgebungen wechselt. Messen Sie außerdem die Trefferquote anhand eines festgelegten Fragekatalogs, bevor Sie die Anfragen anpassen – eine häufige Änderung der Anfragen löst in der Regel kein schwaches Abrufverhalten aus.
Teil 1 — PostgreSQL 18 + pgvector
Beim Arbeiten an der Phase 1 PostgreSQL 18 sollten Sie zunächst den Vertrag aufschreiben: erforderliche Eingaben, Erfolgsignal sowie das Vorgehen bei teilweisen Fehlern. Diese Checkliste sorgt dafür, dass spätere Codeänderungen transparent bleiben. Bewahren Sie die Konfiguration außerhalb des Anwendungscode auf. Umgebungsdateien, Geheimdatenspeicher und Feature-Flags sollten an einem Ort gesammelt sein, den Betreiber ohne das Durchlesen des gesamten Systems überprüfen können. Messen Sie die Trefferquote anhand eines festgelegten Fragekatalogs, bevor Sie die Anfragen anpassen. Eine häufige Änderung der Anfragen behebt selten ein schwaches Suchsystem.
pgvector unter Windows installieren
Beim Bearbeiten des Schritts „pgvector unter Windows installieren“ sollten Sie zunächst einen Leitfaden aufschreiben: erforderliche Eingaben, Erfolgsindikatoren sowie das Vorgehen bei teilweisen Fehlern. Diese Checkliste sorgt dafür, dass spätere Codeänderungen transparent bleiben. Dokumentieren Sie sowohl den erfolgreichen Ablauf als auch den Notfallweg gemeinsam. Wiederholte Versuche, menschliche Überprüfungen sowie die Handhabung von Fehlern gehören zum Produkt selbst und nicht zu späteren Optimierungen. Messen Sie die Trefferquote anhand eines festgelegten Fragekatalogs, bevor Sie die Anfragenanpassungen vornehmen – eine häufige Änderung der Anfragen löst in der Regel kein schwaches Suchverhalten aus.
set "PGROOT=C:\Program Files\PostgreSQL\18"
cd %TEMP%
git clone --branch v0.8.0 https://github.com/pgvector/pgvector.git
cd pgvector
nmake /F Makefile.win
nmake /F Makefile.win install
docker run -d -p 5432:5432 -e POSTGRES_PASSWORD=postgres --name pgvector pgvector/pgvector:pg18
conda install -c conda-forge pgvector
Tabelle in pgAdmin erstellen
Beim Bearbeiten des Schritts „Tabellen erstellen“ in pgAdmin sollten Sie zunächst einen Vertrag aufschreiben: erforderliche Eingaben, Erfolgszeichen sowie das Vorgehen bei teilweisen Fehlern. Diese Checkliste sorgt dafür, dass spätere Codeänderungen transparent bleiben. Ziehen Sie kleine, testbare Einheiten vor großen Skripten vor. Wenn ein Schritt fehlschlägt, sollte der Fehler auf eine einzige Verantwortung verweisen und nicht auf ein verworrenes Ablaufschema. Messen Sie die Trefferquote anhand eines festgelegten Fragekatalogs, bevor Sie die Anfragen anpassen. Häufige Änderungen der Anfragen beheben selten ein schwaches Suchsystem. Beim Bearbeiten des Schritts „Tabellen erstellen“ in pgAdmin sollten Sie zunächst einen Vertrag aufschreiben: erforderliche Eingaben, Erfolgszeichen sowie das Vorgehen bei teilweisen Fehlern. Diese Checkliste sorgt dafür, dass spätere Codeänderungen transparent bleiben. Notieren Sie die Laufzeiten sowie die Kosten für Tokens oder Abfragen neben den funktionalen Ergebnissen. Eine frühzeitige Sichtbarkeit der Kosten verhindert überraschende Rechnungen, wenn der Ablauf von einer Demo-Umgebung in gemeinsam genutzte Umgebungen wechselt.
-- Enable the pgvector extension
CREATE EXTENSION IF NOT EXISTS vector;
-- Main chunks table with VECTOR(768) column
CREATE TABLE IF NOT EXISTS rag_chunks (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
source TEXT NOT NULL,
chunk_index INTEGER NOT NULL,
content TEXT NOT NULL,
file_hash TEXT,
ingested_at TIMESTAMPTZ DEFAULT NOW(),
embedding vector(768) -- pgvector native type
);
-- HNSW index for approximate cosine similarity search
CREATE INDEX IF NOT EXISTS idx_rag_chunks_embedding
ON rag_chunks USING hnsw (embedding vector_cosine_ops);
-- Source filter index
CREATE INDEX IF NOT EXISTS idx_rag_chunks_source
ON rag_chunks (source);
-- Staleness registry
CREATE TABLE IF NOT EXISTS rag_staleness (
doc_name TEXT PRIMARY KEY,
file_hash TEXT NOT NULL,
chunk_count INTEGER,
ingested_at TIMESTAMPTZ DEFAULT NOW(),
version INTEGER DEFAULT 1
);
-- CDC chunk registry
CREATE TABLE IF NOT EXISTS rag_chunk_registry (
doc_name TEXT NOT NULL,
chunk_hash TEXT NOT NULL,
chunk_id TEXT NOT NULL,
PRIMARY KEY (doc_name, chunk_hash)
);
-- Conversation sessions
CREATE TABLE IF NOT EXISTS rag_sessions (
session_id TEXT PRIMARY KEY,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
model TEXT,
embed_model TEXT,
turn_count INTEGER DEFAULT 0
);
-- Conversation turns
CREATE TABLE IF NOT EXISTS rag_turns (
id SERIAL PRIMARY KEY,
session_id TEXT REFERENCES rag_sessions(session_id) ON DELETE CASCADE,
role TEXT NOT NULL CHECK (role IN ('user','assistant')),
content TEXT NOT NULL,
sources TEXT[],
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_rag_turns_session
ON rag_turns (session_id, created_at);
Python-Abhängigkeiten
Die Phase der Python-Abhängigkeiten funktioniert am besten, wenn sie als messbarer Bereich betrachtet wird. Erfassen Sie ein optimales Beispiel, einen Fehlerfall sowie eine Notiz zur Rücksetzung, bevor Sie den Umfang erweitern. Bewahren Sie die Konfiguration außerhalb des Anwendungscode auf. Umgebungsdateien, Geheimdatenspeicher und Feature-Flags sollten an einem Ort gesammelt sein, den Betreuer ohne Durchsicht des gesamten Systems überprüfen können. Fixieren Sie den Interpreter sowie die Abhängigkeitslockdatei, bevor Sie Schleifen erklären. Unterschiede zwischen Laptop und CI sind die häufigsten stillen Störungen bei API-Demos.
uv add google-genai pypdf pgvector psycopg2-binary python-dotenv huggingface_hub
Verbindungs-Einrichtung (Zelle 2)
Die zweite Stufe der Verbindungs-Einrichtung funktioniert am besten, wenn sie als messbare Oberfläche betrachtet wird. Erfassen Sie einen erfolgreichen Fall, einen Fehlerfall sowie die Notizen zur Rücksetzung, bevor Sie den Umfang erweitern. Dokumentieren Sie gemeinsam den erfolgreichen Ablauf sowie den Wiederherstellungsprozess. Versuche, menschliche Überprüfungen und die Handhabung von Fehlnachrichten gehören zum Produkt selbst, nicht zu späteren Optimierungen. Trennen Sie die Strategie zur Aufteilung in Blöcke von der Strategie zum Abrufen. Ein Änderungsbedarf bei einer dieser Strategien sollte nicht dazu führen, dass die andere neu geschrieben werden muss, wenn sich die Qualitätsmetriken ändern.
import psycopg2
from pgvector.psycopg2 import register_vector
PG_HOST = "localhost"
PG_PORT = 5432
PG_DB = "postgres"
PG_USER = "postgres"
PG_PASSWORD = os.environ.get("PG_PASSWORD", "postgres")
def get_pg_conn():
"""Returns a fresh PostgreSQL connection with pgvector registered."""
conn = psycopg2.connect(
host=PG_HOST, port=PG_PORT,
dbname=PG_DB, user=PG_USER, password=PG_PASSWORD
)
register_vector(conn) # tells psycopg2 how to handle vector type
return conn
Speichern von Embeddings (Zelle 6)
Die Storing Embeddings Cell 6-Phase funktioniert am besten, wenn sie als messbare Ebene betrachtet wird. Erfassen Sie vor der Erweiterung des Umfangs ein „goldenes“ Transkript, einen Fehlerfall sowie eine Notiz zur Rücksetzung. Ziehen Sie kleine, testbare Einheiten vor umfangreichen Skripten vor. Wenn ein Schritt fehlschlägt, sollte der Fehler auf eine einzige Verantwortung verweisen und nicht auf einen verworrenen Ablauf. Trennen Sie die Chunking-Strategie von der Abrufstrategie. Eine Änderung sollte nicht dazu führen, dass die andere neu geschrieben werden muss, wenn sich die Qualitätsmetriken ändern.
import numpy as np
def store_in_postgres(chunks, embeddings, doc_name) -> int:
conn = get_pg_conn()
cur = conn.cursor()
for i, (chunk, emb) in enumerate(zip(chunks, embeddings)):
cur.execute("""
INSERT INTO rag_chunks (source, chunk_index, content, embedding)
VALUES (%s, %s, %s, %s)
""", (doc_name, i, chunk, np.array(emb))) # np.array → pgvector handles serialization
conn.commit()
conn.close()
return len(chunks)
Die Storing Embeddings Cell 6-Phase funktioniert am besten, wenn sie als messbare Ebene betrachtet wird. Erfassen Sie vor der Erweiterung des Umfangs ein „goldenes“ Transkript, einen Fehlerfall sowie eine Notiz zur Rücksetzung. Erhalten Sie neben den funktionalen Ergebnissen auch Aufzeichnungen zu Laufzeiten sowie Kosten pro Token oder Abfrage. Eine frühzeitige Sichtbarkeit der Kosten verhindert überraschende Rechnungen, wenn der Ablauf von einer Demo in gemeinsame Umgebungen wechselt.
Abruf mit Kosinusähnlichkeit (Cell 6 fortgesetzt)
Für die Phase der Suche mit kosinusähnlicher Ähnlichkeit sollten vor dem Ändern des Codes die Eingaben, der Verantwortliche für diesen Schritt sowie die Abbruchkriterien definiert werden. Die Operator sollten in der Lage sein, den Schritt von einem bekannten Checkpoint aus erneut auszuführen, ohne auf versteckte Zustände schließen zu müssen. Die Konfiguration sollte außerhalb des Anwendungscode gespeichert werden. Umgebungsdateien, Geheimdatenspeicher sowie Feature-Flags sollten an einem Ort zusammengefasst sein, den die Operator überprüfen können, ohne den gesamten Codeverlauf durchlesen zu müssen. Zitieren Sie die Passagen, die tatsächlich die Antwort untermauern. Ohne Zitate können die Operator nicht zwischen Halluzinationen und Lücken in der Indizierung unterscheiden.
def retrieve_context(query: str) -> list[dict]:
query_embedding = embed_query(query)
conn = get_pg_conn()
cur = conn.cursor()
cur.execute("""
SELECT
content,
source,
chunk_index,
ingested_at,
1 - (embedding <=> %s) AS cosine_score -- <=> is cosine distance
FROM rag_chunks
ORDER BY embedding <=> %s -- sort ascending (smallest distance first)
LIMIT %s
""", (np.array(query_embedding), np.array(query_embedding), TOP_K))
rows = cur.fetchall()
conn.close()
return [{"text": r[0], "source": r[1], "chunk_index": r[2],
"ingested_at": str(r[3]) if r[3] else "",
"score": round(float(r[4]), 4)} for r in rows]
CDC mit pgvector — Upsert-Muster
Für den CDC mit der pgvector Upsert-Ebene sollten vor dem Ändern des Codes die Eingabedaten, der Verantwortliche für diesen Schritt sowie die Abbruchkriterien definiert werden. Die Operator sollten in der Lage sein, den Schritt von einem bekannten Checkpoint aus erneut auszuführen, ohne auf versteckten Zuständen schließen zu müssen. Dokumentieren Sie sowohl den erfolgreichen Ablauf als auch den Notfallweg gemeinsam. Wiederholte Versuche, menschliche Überprüfungen sowie die Handhabung von Fehlern gehören zum Produkt selbst und nicht zu späteren Optimierungen. Zitieren Sie die Passagen, die tatsächlich die Antwort untermauern – ohne Zitate können die Operator nicht zwischen Halluzinationen und Indexierungsfehlern unterscheiden.
cur.execute("""
INSERT INTO rag_staleness (doc_name, file_hash, chunk_count, version)
VALUES (%s, %s, %s, 1)
ON CONFLICT (doc_name) DO UPDATE SET
file_hash = EXCLUDED.file_hash,
chunk_count = EXCLUDED.chunk_count,
ingested_at = NOW(),
version = rag_staleness.version + 1;
""", (doc_name, file_hash, chunk_count))
Teil 2 – SQL Server 2025 (Native Vector)
Für die SQL Server-Phase Teil 2 sollten vor der Codeänderung die Eingaben, der Verantwortliche für den Schritt sowie die Abbruchkriterien definiert werden. Die Operator sollten in der Lage sein, den Schritt von einem bekannten Checkpoint aus erneut auszuführen, ohne auf versteckten Zuständen schließen zu müssen. Es ist besser, kleine, testbare Einheiten statt umfangreicher Skripte zu verwenden. Wenn ein Schritt fehlschlägt, sollte der Fehler auf eine einzige Verantwortungsbereich hinweisen und nicht auf ein verworrenes Pipeline-System. Zitieren Sie die Passagen, die tatsächlich die Antwort untermauern. Ohne Zitate können die Operator nicht zwischen Halluzinationen und Indexierungsproblemen unterscheiden.
Warum SQL Server 2025 keine Erweiterung benötigt
Zur Phase „Warum SQL Server 2025“ sollten die Eingaben, der Verantwortliche für den Schritt sowie die Abbruchkriterien vor dem Ändern des Codes definiert werden. Die Operator sollten in der Lage sein, den Schritt von einem bekannten Checkpoint aus erneut auszuführen, ohne auf versteckten Zuständen schließen zu müssen. Betrachten Sie diese Phase als Vertrag zwischen den Eingaben und den validierten Ausgaben. Benennen Sie die Artefakte, definieren Sie Erfolgskontrollen und lehnen Sie stille, teilweise abgeschlossene Abläufe ab. Zitieren Sie die Passagen, die tatsächlich die Antwort begründen. Ohne Zitate können die Operator nicht zwischen Halluzinationen und Lücken bei der Indizierung unterscheiden.
Einrichtung in SSMS
Zur Einrichtung in der SSMS-Phase sollten die Eingabedaten, der Verantwortliche für den Schritt sowie die Abbruchkriterien vor dem Codeändern definiert werden. Die Operator sollten in der Lage sein, den Schritt von einem bekannten Checkpoint aus erneut auszuführen, ohne auf versteckte Zustände schließen zu müssen. Erfassen Sie neben den funktionalen Ergebnissen auch die Ausführungsdauer sowie die Kosten für Token oder Abfragen. Eine frühzeitige Sichtbarkeit der Kosten verhindert überraschende Rechnungen, wenn der Ablauf von einer Demo-Umgebung in gemeinsam genutzte Umgebungen wechselt. Zitieren Sie die Passagen, die tatsächlich die Antwort untermauern. Ohne Zitate können die Operator nicht zwischen Halluzinationen und Lücken in der Indizierung unterscheiden.
-- Batch 1: Run this first — must commit before vector objects are recognized
ALTER DATABASE SCOPED CONFIGURATION SET PREVIEW_FEATURES = ON;
-- Batch 2: Create all tables
CREATE TABLE rag_chunks (
id INT IDENTITY(1,1) PRIMARY KEY CLUSTERED,
source NVARCHAR(500) NOT NULL,
chunk_index INT NOT NULL,
content NVARCHAR(MAX) NOT NULL,
file_hash NVARCHAR(64),
ingested_at DATETIME2 DEFAULT GETUTCDATE(),
embedding VECTOR(768) -- native SQL Server 2025 type
);
CREATE INDEX idx_rag_source ON rag_chunks(source);
CREATE TABLE rag_staleness (
doc_name NVARCHAR(500) PRIMARY KEY,
file_hash NVARCHAR(64) NOT NULL,
chunk_count INT,
ingested_at DATETIME2 DEFAULT GETUTCDATE(),
version INT DEFAULT 1
);
CREATE TABLE rag_chunk_registry (
doc_name NVARCHAR(500) NOT NULL,
chunk_hash NVARCHAR(64) NOT NULL,
chunk_id NVARCHAR(64) NOT NULL,
PRIMARY KEY (doc_name, chunk_hash)
);
CREATE TABLE rag_sessions (
session_id NVARCHAR(100) PRIMARY KEY,
created_at DATETIME2 DEFAULT GETUTCDATE(),
updated_at DATETIME2 DEFAULT GETUTCDATE(),
model NVARCHAR(200),
embed_model NVARCHAR(200),
turn_count INT DEFAULT 0
);
CREATE TABLE rag_turns (
id INT IDENTITY(1,1) PRIMARY KEY,
session_id NVARCHAR(100) NOT NULL REFERENCES rag_sessions(session_id),
role NVARCHAR(20) NOT NULL CHECK (role IN ('user','assistant')),
content NVARCHAR(MAX) NOT NULL,
sources NVARCHAR(MAX),
created_at DATETIME2 DEFAULT GETUTCDATE()
);
CREATE INDEX idx_rag_turns_session ON rag_turns(session_id, created_at);
-- Batch 3: Must run AFTER Batch 2 commits
-- Cannot run inside a transaction — this is a known SQL Server 2025 preview constraint
CREATE VECTOR INDEX idx_rag_embedding
ON rag_chunks(embedding)
WITH (METRIC = 'COSINE');
Python-Abhängigkeiten
In der Phase der Python-Abhängigkeiten sollten die Eingaben, der Verantwortliche für den Schritt sowie die Abbruchkriterien vor dem Ändern des Codes definiert werden. Die Operator sollten in der Lage sein, den Schritt von einem bekannten Checkpoint aus erneut auszuführen, ohne auf versteckten Zuständen schließen zu müssen. Die Konfiguration sollte außerhalb des Anwendungscode gespeichert werden. Umgebungsdateien, Geheimdatenspeicher und Feature-Flags sollten an einem Ort zusammengefasst sein, den die Operator überprüfen können, ohne den gesamten Ablaufverlauf durchlesen zu müssen. Trennen Sie den Aufbau des Clients von dem Nachrichtenzyklus, damit Provider ausgetauscht werden können, ohne die Zustandsmaschine der Konversation umschreiben zu müssen.
uv add google-genai pypdf pyodbc python-dotenv huggingface_hub fpdf2
Verbindungs-Einrichtung (Zelle 2)
Zur zweiten Phase der Verbindungs-Einrichtung müssen vor dem Ändern des Codes die Eingaben, der Verantwortliche für den Schritt sowie die Abbruchkriterien definiert werden. Die Operator sollten in der Lage sein, den Schritt von einem bekannten Checkpoint aus erneut auszuführen, ohne auf versteckte Zustände schließen zu müssen. Dokumentieren Sie gemeinsam den erfolgreichen Ablauf sowie den Notfallweg. Wiederholungsversuche, menschliche Überprüfungen und die Handhabung von Fehlern gehören zum Produkt selbst und nicht zu späteren Optimierungen. Zitieren Sie die Passagen, die tatsächlich die Antwort untermauern. Ohne Zitate können die Operator nicht zwischen Halluzinationen und Lücken in der Indizierung unterscheiden.
import pyodbc
SQL_SERVER = "YOUR_SERVER_NAME" # from SSMS title bar
SQL_DATABASE = "local_rag"
SQL_CONN_STR = (
f"DRIVER={{ODBC Driver 17 for SQL Server}};"
f"SERVER={SQL_SERVER};"
f"DATABASE={SQL_DATABASE};"
f"Trusted_Connection=yes;" # Windows Authentication - no password needed
)
def get_conn():
return pyodbc.connect(SQL_CONN_STR)
def get_conn_autocommit():
"""Required for CREATE/DROP VECTOR INDEX - cannot run inside a transaction."""
return pyodbc.connect(SQL_CONN_STR, autocommit=True)
Speichern von Embeddings (Zelle 6)
Für die Stufe „Storing Embeddings Cell 6“ sollten vor dem Ändern des Codes die Eingaben, der Verantwortliche für den Schritt sowie die Abbruchkriterien definiert werden. Die Operator sollten in der Lage sein, den Schritt von einem bekannten Checkpoint aus erneut auszuführen, ohne auf den versteckten Zustand schließen zu müssen. Bevorzugen Sie kleine, testbare Einheiten vor umfangreichen Skripten. Wenn ein Schritt fehlschlägt, sollte der Fehler auf eine einzige Verantwortung verweisen und nicht auf ein verworrenes Ablaufverfahren. Zitieren Sie die Passagen, die tatsächlich die Antwort untermauern. Ohne Zitate können die Operator nicht zwischen Halluzinationen und Indexierungsfehlern unterscheiden. Für die Stufe „Storing Embeddings Cell 6“ sollten vor dem Ändern des Codes die Eingaben, der Verantwortliche für den Schritt sowie die Abbruchkriterien definiert werden. Die Operator sollten in der Lage sein, den Schritt von einem bekannten Checkpoint aus erneut auszuführen, ohne auf den versteckten Zustand schließen zu müssen. Erhalten Sie neben den funktionalen Ergebnissen auch Aufzeichnungen der Laufzeiten sowie der Kosten pro Token oder Abfrage. Eine frühzeitige Sichtbarkeit der Kosten verhindert überraschende Rechnungen, wenn der Ablauf von einer Demo-Umgebung in eine gemeinsame Umgebung wechselt.
import json
def vec_to_json(embedding: list[float]) -> str:
return json.dumps(embedding) # '[0.12, 0.34, ...]'
def store_in_sqlserver(chunks, embeddings, doc_name) -> int:
drop_vector_index() # must drop before any INSERT
conn = get_conn()
cur = conn.cursor()
sql = """
INSERT INTO rag_chunks (source, chunk_index, content, embedding)
VALUES (?, ?, ?, CAST(? AS VECTOR(768)))
"""
for i, (chunk, emb) in enumerate(zip(chunks, embeddings)):
cur.setinputsizes([
(_pyodbc.SQL_WVARCHAR, 500, 0),
_pyodbc.SQL_INTEGER,
(_pyodbc.SQL_WVARCHAR, 0, 0),
(_pyodbc.SQL_VARCHAR, 0, 0), # ← must be VARCHAR, not NTEXT
])
cur.execute(sql, (doc_name, i, chunk, vec_to_json(emb)))
conn.commit()
conn.close()
create_vector_index() # recreate after all inserts
return len(chunks)
Auswertung mit VECTOR_DISTANCE (Zelle 6 fortgesetzt)
Beim Arbeiten an der Schrittphase „Auswertung mit VECTORDISTANCE“ sollten Sie zunächst die Anforderungen notieren: erforderliche Eingaben, Erfolgsindikatoren sowie das Vorgehen bei teilweisen Fehlern. Diese Checkliste sorgt dafür, dass spätere Codeänderungen transparent bleiben. Bewahren Sie die Konfiguration außerhalb des Anwendungscode auf. Umgebungsdateien, Geheimdatenspeicher und Feature-Flags sollten an einem Ort gesammelt sein, den Betreiber überprüfen können, ohne den gesamten Code durchzulesen. Messen Sie die Recall-Rate anhand einer festgelegten Fragestellung, bevor Sie die Anfragen anpassen. Eine häufige Änderung der Anfragen behebt in der Regel nicht ein schwaches Auswertungsverhalten.
def retrieve_context(query: str) -> list[dict]:
query_embedding = embed_query(query)
query_vec_json = vec_to_json(query_embedding)
conn = get_conn()
cur = conn.cursor()
cur.setinputsizes([(_pyodbc.SQL_VARCHAR, 0, 0)]) # force VARCHAR for vector param
cur.execute(f"""
SELECT TOP ({TOP_K})
content, source, chunk_index, ingested_at,
VECTOR_DISTANCE('cosine', embedding, CAST(? AS VECTOR(768))) AS distance
FROM rag_chunks
ORDER BY distance ASC;
""", (query_vec_json,))
rows = cur.fetchall()
conn.close()
return [{"text": r[0], "source": r[1], "chunk_index": r[2],
"ingested_at": str(r[3]) if r[3] else "",
"score": round(1 - float(r[4]), 4)} for r in rows]
Ausnahmen bei SQL Server 2025 Vector – alle, auf die wir gestoßen sind
Beim Arbeiten mit der SQL Server 2025 Vector-Ebene sollten Sie zunächst den Ablaufplan aufschreiben: erforderliche Eingaben, Erfolgsindikator sowie das Vorgehen bei teilweisen Fehlern. Diese Checkliste sorgt dafür, dass spätere Codeänderungen transparent bleiben. Dokumentieren Sie sowohl den erfolgreichen Ablauf als auch den Wiederherstellungsprozess gemeinsam. Wiederholte Versuche, menschliche Überprüfungen sowie die Handhabung von Fehlern gehören zum Produkt selbst und nicht zu späteren Optimierungen. Messen Sie die Trefferquote anhand eines festgelegten Fragekatalogs, bevor Sie die Anfragen anpassen – eine häufige Änderung der Anfragen löst in der Regel kein schwaches Suchverhalten auf.
Hack 1 – Unbekannter Objekttyp „VECTOR“ in der CREATE-Anweisung
Beim Bearbeiten der Phase „Gotcha 1: Unbekanntes Objekt“ sollten Sie zunächst den Vertrag aufschreiben: erforderliche Eingaben, Erfolgsignal sowie das Vorgehen bei teilweisen Fehlern. Diese Checkliste sorgt dafür, dass spätere Codeänderungen transparent bleiben. Ziehen Sie kleine, testbare Einheiten vor großen Skripten vor. Wenn ein Schritt fehlschlägt, sollte der Fehler auf eine einzige Verantwortung verweisen und nicht auf ein verworrenes Ablaufschema. Messen Sie die Trefferquote anhand eines festgelegten Fragekatalogs, bevor Sie die Anfragen anpassen. Eine häufige Änderung der Anfragen behebt selten ein schwaches Suchverhalten. Beim Bearbeiten der Phase „Gotcha 1: Unbekanntes Objekt“ sollten Sie zunächst den Vertrag aufschreiben: erforderliche Eingaben, Erfolgssignal sowie das Vorgehen bei teilweisen Fehlern. Diese Checkliste sorgt dafür, dass spätere Codeänderungen transparent bleiben. Notieren Sie die Laufzeiten sowie die Kosten pro Token oder Abfrage neben den funktionalen Ergebnissen. Eine frühzeitige Sichtbarkeit der Kosten verhindert überraschende Rechnungen, wenn der Einsatzbereich von einer Demo in gemeinsame Umgebungen wechselt.
Msg 343, Level 15: Unknown object type 'VECTOR' used in CREATE, DROP, or ALTER statement.
Gotcha 2 – Die Primärschlüsselspalte muss eine einzige 4-Byte-INT-Spalte sein
Die Phase des Primärschlüssels bei Gotcha 2 funktioniert am besten, wenn sie als messbarer Ansatz betrachtet wird. Erfassen Sie vor Erweiterung des Umfangs eine optimale Transkription, einen Fehlerfall sowie die Notizen zur Rücksetzung. Bewahren Sie die Konfiguration außerhalb des Anwendungscode auf. Umgebungsdateien, Geheimdatenspeicher und Feature-Flags sollten an einem Ort gesammelt sein, den Betreiber ohne Durchsicht des gesamten Systems prüfen können. Trennen Sie die Teilungspolitik von der Abrufpolitik. Ein Änderungs an einer sollte nicht dazu führen, dass die andere neu geschrieben werden muss, wenn sich die Qualitätsmetriken ändern.
Msg 42217: Table must have a clustered primary key on a single 4 byte INT column to create a vector index.
-- ❌ Does not work with vector index
id UNIQUEIDENTIFIER PRIMARY KEY DEFAULT NEWID()
-- ✅ Required
id INT IDENTITY(1,1) PRIMARY KEY CLUSTERED
Gotcha 3 – Es ist nicht möglich, INSERT/DELETE/UPDATE auszuführen, solange ein Vektorindex existiert
The Gotcha 3 „Cannot INSERT stage“ funktioniert am besten, wenn sie als messbare Oberfläche betrachtet wird. Erfassen Sie einen erfolgreichen Fall, einen Fehlerfall sowie die Notizen zur Rücksetzung, bevor Sie den Umfang erweitern. Dokumentieren Sie gleichzeitig den erfolgreichen Ablauf und den Wiederherstellungsprozess. Wiederholversuche, menschliche Kontrollen sowie die Handhabung von Fehlern gehören zum Produkt selbst und nicht zu späteren Optimierungen. Trennen Sie die Strategie zur Aufteilung in Blöcke von der Strategie zum Abrufen. Ein Änderungsbedarf bei einer dieser Strategien sollte nicht dazu führen, dass die andere neu geschrieben werden muss, wenn sich die Qualitätsmetriken ändern.
Msg 42231: Data modification statement failed because table 'rag_chunks' has a vector index on it.
def drop_vector_index():
conn = get_conn_autocommit() # autocommit required
conn.cursor().execute("""
IF EXISTS (
SELECT 1 FROM sys.indexes
WHERE name = 'idx_rag_embedding'
AND object_id = OBJECT_ID('rag_chunks')
)
DROP INDEX idx_rag_embedding ON rag_chunks;
""")
conn.close()
def create_vector_index():
conn = get_conn_autocommit() # autocommit required
conn.cursor().execute("""
CREATE VECTOR INDEX idx_rag_embedding
ON rag_chunks(embedding)
WITH (METRIC = 'COSINE');
""")
conn.close()
Gotcha 4 – CREATE VECTOR INDEX kann nicht innerhalb einer Transaktion ausgeführt werden
The Gotcha 4 CREATE VECTOR-Phase funktioniert am besten, wenn sie als messbare Ebene betrachtet wird. Erfassen Sie ein „goldenes Transkript“, einen Fehlerfall sowie eine Notiz zur Rücksetzung, bevor Sie den Umfang erweitern. Ziehen Sie kleine, testbare Einheiten vor umfangreichen Skripten vor. Wenn ein Schritt fehlschlägt, sollte der Fehler auf eine einzige Verantwortung verweisen und nicht auf ein verworrenes Ablaufverfahren. Trennen Sie die Aufteilungspolitik von der Abrufpolitik. Eine Änderung sollte nicht dazu führen, dass die andere neu geschrieben werden muss, wenn sich die Qualitätsmetriken ändern. The Gotcha 4 CREATE VECTOR-Phase funktioniert am besten, wenn sie als messbare Ebene betrachtet wird. Erfassen Sie ein „goldenes Transkript“, einen Fehlerfall sowie eine Notiz zur Rücksetzung, bevor Sie den Umfang erweitern. Protokollieren Sie die Laufzeiten sowie die Kosten für Tokens oder Abfragen neben den funktionalen Ergebnissen. Eine frühzeitige Sichtbarkeit der Kosten verhindert überraschende Rechnungen, wenn der Ablauf von einer Demo in gemeinsame Umgebungen wechselt.
Msg 574: CREATE VECTOR INDEX statement cannot be used inside a user transaction.
# ❌ Fails — implicit transaction
conn = pyodbc.connect(SQL_CONN_STR)
conn.cursor().execute("CREATE VECTOR INDEX ...")
# ✅ Works - no transaction wrapper
conn = pyodbc.connect(SQL_CONN_STR, autocommit=True)
conn.cursor().execute("CREATE VECTOR INDEX ...")
Gotcha 5 – Eine explizite Umwandlung von ntext in Vector ist nicht zulässig
In der Phase der expliziten Umwandlung bei Gotcha 5 müssen die Eingaben, der Verantwortliche für diesen Schritt sowie die Abbruchkriterien vor dem Ändern des Codes definiert werden. Die Operator sollten in der Lage sein, den Schritt von einem bekannten Checkpoint aus erneut auszuführen, ohne auf versteckte Zustände schließen zu müssen. Die Konfiguration sollte außerhalb des Anwendungscode gespeichert werden. Umgebungsdateien, Geheimdatenspeicher sowie Feature-Flags sollten an einem Ort zusammengefasst sein, den die Operator überprüfen können, ohne den gesamten Ablauf durchzulesen. Zitieren Sie die Passagen, die tatsächlich die Antwort begründen. Ohne Zitate können die Operator nicht zwischen Halluzinationen und Lücken in der Indizierung unterscheiden.
Msg 529: Explicit conversion from data type ntext to vector is not allowed.
cur.setinputsizes([
(_pyodbc.SQL_WVARCHAR, 500, 0), # source — Unicode fine
_pyodbc.SQL_INTEGER, # chunk_index
(_pyodbc.SQL_WVARCHAR, 0, 0), # content — Unicode fine
(_pyodbc.SQL_VARCHAR, 0, 0), # embedding ← must be ASCII VARCHAR
])
cur.execute(sql, (doc_name, i, chunk, vec_to_json(emb)))
Gotcha 6 – VECTOR_SEARCH akzeptiert keine Platzhalter für ?-Parameter
Für Gotcha 6 VECTORSEARCH muss man vor dem Ändern des Codes die Schritte definieren, die Eingabedaten festlegen, den Verantwortlichen für jeden Schritt benennen sowie die Abbruchkriterien bestimmen. Die Operator sollten in der Lage sein, den Schritt von einem bekannten Checkpoint aus erneut auszuführen, ohne auf versteckte Zustände schließen zu müssen. Dokumentieren Sie sowohl den erfolgreichen Ablauf als auch die Notfallbehandlung gemeinsam. Wiederholte Versuche, menschliche Überprüfungen sowie die Handhabung fehlerhafter Nachrichten gehören zum Produkt selbst und nicht zu späteren Optimierungen. Zitieren Sie die Passagen, die tatsächlich die Antwort untermauern – ohne Zitate können die Operator nicht zwischen Halluzinationen und Lücken in der Indizierung unterscheiden.
Msg 102: Incorrect syntax near '('.
-- ❌ VECTOR_SEARCH with parameter placeholder — fails
FROM VECTOR_SEARCH(
TABLE = rag_chunks USING VECTOR INDEX idx_rag_embedding,
SIMILAR_TO = CAST(? AS VECTOR(768)), -- pyodbc cannot pass ? here
...
)
-- ✅ VECTOR_DISTANCE - fully parameterized, GA, works perfectly
SELECT TOP (5)
content,
VECTOR_DISTANCE('cosine', embedding, CAST(? AS VECTOR(768))) AS distance
FROM rag_chunks
ORDER BY distance ASC;
PostgreSQL gegen SQL Server – Seite an Seite
Zur Phase PostgreSQL gegen SQL Server sollten die Eingaben, der Verantwortliche für den Schritt sowie die Abbruchkriterien vor dem Codeändern definiert werden. Die Operator sollten in der Lage sein, den Schritt von einem bekannten Checkpoint aus erneut auszuführen, ohne auf versteckten Zuständen schließen zu müssen. Vorzuziehen sind kleine, testbare Einheiten statt umfangreicher Skripte. Wenn ein Schritt fehlschlägt, sollte der Fehler auf eine einzige Verantwortung verweisen und nicht auf ein verworrenes Pipeline-System. Zitieren Sie die Passagen, die tatsächlich die Antwort untermauern. Ohne Zitate können die Operator nicht zwischen Halluzinationen und Indexierungsproblemen unterscheiden.
Was beide Datenbanken bieten, was ChromaDB nicht kann
In der Phase „Was beides für Datenbanken hinzufügen“ sollten die Eingaben, der Verantwortliche für den Schritt sowie die Abbruchkriterien definiert werden, bevor der Code geändert wird. Die Operator sollten in der Lage sein, den Schritt von einem bekannten Checkpoint aus erneut auszuführen, ohne auf versteckten Zuständen schließen zu müssen. Betrachten Sie diese Phase als Vertrag zwischen den Eingaben und den validierten Ausgaben. Benennen Sie die Artefakte, definieren Sie Erfolgskontrollen und lehnen Sie stille, unvollständige Abschlüsse ab. Zitieren Sie die Passagen, die tatsächlich die Antwort begründen. Ohne Zitate können die Operator nicht zwischen Halluzinationen und Lücken in der Indizierung unterscheiden.
-- PostgreSQL: Find chunks from documents belonging to a specific customer
SELECT c.content, 1 - (c.embedding <=> query_vec) AS score
FROM rag_chunks c
JOIN documents d ON d.filename = c.source
JOIN customers cu ON cu.id = d.customer_id
WHERE cu.tier = 'enterprise'
ORDER BY score DESC
LIMIT 5;
-- SQL Server: Same query, T-SQL syntax
SELECT TOP 5
c.content,
1 - VECTOR_DISTANCE('cosine', c.embedding, CAST(? AS VECTOR(768))) AS score
FROM rag_chunks c
JOIN documents d ON d.filename = c.source
JOIN customers cu ON cu.id = d.customer_id
WHERE cu.tier = 'enterprise'
ORDER BY score DESC;
Fazit
Zur Abschlussphase sollten die Eingabedaten, der Verantwortliche für den Schritt sowie die Abbruchkriterien definiert werden, bevor der Code geändert wird. Die Operator sollten in der Lage sein, den Schritt von einem bekannten Checkpoint aus erneut auszuführen, ohne auf versteckte Zustände schließen zu müssen. Zeiten sowie Kosten für Token oder Abfragen sollten neben den funktionalen Ergebnissen aufgezeichnet werden. Eine frühzeitige Sichtbarkeit der Kosten verhindert überraschende Rechnungen, wenn der Prozess von einer Demo-Umgebung in gemeinsam genutzte Umgebungen übergeht. Zitieren Sie die Passagen, die tatsächlich die Antwort untermauern. Ohne Zitate können die Operator nicht zwischen Halluzinationen und Lücken in der Indizierung unterscheiden.